[solved] Problem porting SkinnedMesh from old Geometry to BufferGeometry

My Bumblebee Mara no longer works on https://codepen.io/hofk/pen/eWdZMb ,
because the old Geometry removed with r125 is used.


UPDATE January 21 - I got it to work on codepen.

On my page the old variant remains (see Mara above) and the new one can reached here under Mara.

Decisive was my sloppiness, :frowning_face: I used twice the same data fields for skin with push. You can see it below.



I try to change it to BufferGeometry and have a display error. I cannot find the cause.

For a better search I only created the body (it works correctly) and a limb ( that’s where the error is).

Actually boneLimbSegCount = 3; then the representation is completely meaningless.

With boneLimbSegCount = 32; it appears partly as expected.

I have tried different variations, so far without success. :thinking: :question:

See Skeleton_x


= 3


= 32

2022-01-17 18.14.52


<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/problem-porting-skinnedmesh-from-old-geometry-to-buffergeometry/33748  -->
<head> <meta charset="utf-8">
    <title> Skeleton_x </title>
</head>
<body> 
    <div style="position: absolute; top: 20px; left: 25px; text-align:left;">
    <div > Skeleton </br>
        <label><input type="checkbox" id="move" > move </label>
         
    </div>
</body>

<script type ="module">

// @author hofk

import * as THREE from '../jsm/three.module.136.js';
import { mergeBufferGeometries, mergeBufferAttributes } from '../jsm/BufferGeometryUtils.136.js'
import {OrbitControls} from '../jsm/OrbitControls.136.js';

document.getElementById('move').checked = true;
window.addEventListener( 'resize', function ( ) {
            camera.aspect = window.innerWidth / window.innerHeight;
            renderer.setSize( window.innerWidth, window.innerHeight ); }, false 
);    
 
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 20000 );
camera.position.set(-200,100,300);

const renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0xbbffcc, 1 );

const container = document.createElement('div');
document.body.appendChild(container);
container.appendChild(renderer.domElement); 

new OrbitControls( camera, renderer.domElement );
 
const light1 = new THREE.PointLight(0xffffff,1,0); 
const light2 = light1.clone(); 
light1.position.set( -100, 300, 1000 ); 
light2.position.set(    0, 800, -500 );      
scene.add(light1);                        
scene.add(light2); 

const clock  = new THREE.Clock( true );

const materialHead = new THREE.MeshPhongMaterial( { color: 0x997711, emissive: 0xaa6600, wireframe: true } );
const materialBody = new THREE.MeshBasicMaterial( { color: 0x897429, wireframe: true } ); 
const materialLimb = new THREE.MeshPhongMaterial( { color: 0x896215, emissive: 0xa96415, wireframe: true } );

const geometryNeck = new THREE.OctahedronBufferGeometry( 10, 2 );

const bodyHeight            = 80;
const boneBodyCount         =  4;
const boneBodySegCount      =  4;
const bodySegHeightCount    = boneBodySegCount * boneBodyCount; 
const boneBodyHeight        = bodyHeight / boneBodyCount;

const outlineBody = [

 [0.01,-bodyHeight ],[10,-79.75],[20,-79.25],[30,-76],[35,-75],[40,-70],[42,-65],[44,-60],[45,-55],[45,-50],[44,-45],[41,-35],[36,-27],[31,-20],[17.5,-10],[10,0] //  -bodyHeight  -->  0
 
];

const pointsBody = [];

for ( let i = 0; i < outlineBody.length ; i++ ) {

   pointsBody.push(new THREE.Vector2( outlineBody[ i ][ 0 ], outlineBody[ i ][ 1 ] ) );
   
}

const rSegmentCount = 18;

const geometryBody = new THREE.LatheBufferGeometry( pointsBody, rSegmentCount );

const vertex = new THREE.Vector3( );
const skinIndices = [];
const skinWeights = [];

const positionBody = geometryBody.attributes.position;

for ( let i = 0; i < positionBody.count; i ++ ) {

    vertex.fromBufferAttribute( positionBody, i );
    
    const skinIndex = boneBodyCount - 1 - Math.floor( ( i % bodySegHeightCount) / boneBodySegCount );      
    const skinWeight = ( ( bodyHeight - vertex.y ) % boneBodyHeight ) / boneBodyHeight;
    
    skinIndices.push(    skinIndex, skinIndex + 1, 0, 0 ); 
    skinWeights.push( 1 - skinWeight, skinWeight , 0, 0 );
    
}

geometryBody.setAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( skinIndices, 4 ) );
geometryBody.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeights, 4 ) );

const meshBody  = new THREE.SkinnedMesh( geometryBody, materialBody );
 
 // .... limb ....
 
const limbRadius         =  3;
const limbHeight         = 60;
const boneLimbCount      =  3;
const boneLimbSegCount   = 32; // original 3; // !!!!!!!
const limbRadSegCount    = 0.5 * rSegmentCount;
const limbHeightSegCount =  boneLimbSegCount * boneLimbCount;
const boneLimbHeight     = limbHeight / boneLimbCount;
const limbHalfHeight     = limbHeight * 0.5;
const openEnded          = false;
 
const cylinderGeometry = new THREE.CylinderBufferGeometry( limbRadius, limbRadius, limbHeight, limbRadSegCount, limbHeightSegCount, openEnded ); 
const sphereGeometrie = new THREE.SphereBufferGeometry( 7, 16, 16 );
sphereGeometrie.translate ( 0, limbHalfHeight + boneLimbHeight / 4, 0 );
const geos = [ cylinderGeometry, sphereGeometrie ]
const  geometryLimb = mergeBufferGeometries( geos );
 
const positionLimb = geometryLimb.attributes.position;
 
for ( let i = 0; i <   positionLimb.count; i ++ ) { 

    vertex.fromBufferAttribute( positionLimb, i );
    
    const y = vertex.y + limbHalfHeight;
    
    const skinIndex = Math.floor( y / limbHeight );     
    const skinWeight = ( y % limbHeight ) / limbHeight;
      
    skinIndices.push(    skinIndex, skinIndex + 1, 0, 0 );
    skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 );

}

geometryLimb.setAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( skinIndices, 4 ) );
geometryLimb.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeights, 4 ) );

const meshLimb = new THREE.SkinnedMesh( geometryLimb, materialLimb );
 
const bonesBody = createBones( 0 ,-boneBodyHeight, boneBodyCount);

const bonesLimb = createBones( -limbHalfHeight, boneLimbHeight, boneLimbCount );
 
const meshNeck  = new THREE.Mesh( geometryNeck, materialHead );
 
meshBody.add( meshNeck );

meshLimb.rotation.z =  1.57;  
meshLimb.position.x = -limbHalfHeight;

const skeletonBody = new THREE.Skeleton( bonesBody );

meshBody.add( bonesBody[ 0 ] );
meshBody.bind( skeletonBody );

scene.add( meshBody );
const skeletonHelperBody = new THREE.SkeletonHelper( meshBody );
scene.add( skeletonHelperBody );

const skeletonLimb = new THREE.Skeleton( bonesLimb );
meshLimb.add( bonesLimb[ 0 ] );
meshLimb.bind( skeletonLimb );
meshBody.add( meshLimb );

const skeletonHelperLimb = new THREE.SkeletonHelper( meshLimb );  ;
scene.add( skeletonHelperLimb );

let time;

animate();
//...............
 
function createBones( positionY, height, boneCount ) {

    const bones = [];                  
    const basicBone = new THREE.Bone(); 
    bones.push( basicBone );   
    basicBone.position.y = positionY; 
    let prevBone = basicBone;
    
    for ( let i = 1; i < boneCount+1; i ++ ) { 
    
        const bone = prevBone.clone();                 
        bone.position.y = height;                  
        bones.push( bone );                        
        prevBone.add( bone );                     
        prevBone = bone;
        
    }
    
    return bones;
    
}

function animate() {

    requestAnimationFrame( animate );
    
     time = clock.getElapsedTime();  //Date.now() * 0.001;    
    
    if ( document.getElementById( "move" ).checked) {
    
        for ( var i = 1; i < meshBody.skeleton.bones.length; i ++ ) { 
        
            meshBody.skeleton.bones[ i ].rotation.z = 0.2 * Math.sin( 1.8 * time ) / meshBody.skeleton.bones.length;
            
        }

        for ( var i = 0; i < boneLimbCount + 1; i ++ ) {
        
            meshLimb.skeleton.bones[ i ].rotation.x = 0.6 * Math.cos( 1.2 * time ) / boneLimbCount;
            meshLimb.skeleton.bones[ i ].rotation.z = 0.6 * Math.sin( 1.2 * time ) / boneLimbCount;
            
        }
        
        meshNeck.rotation.x = -0.20 * ( 0.8 + Math.sin( -1.57 + 1.2 * time ) );
        meshNeck.rotation.y = -0.15 * ( 0.6 + Math.sin( -1.57 + 1.9 * time ) );
        
        meshBody.rotation.x = 0.2;
        
    } 
    
    renderer.render( scene, camera );
}
</script>
</html>

Instead of trying to port something, it is often easier to programme it from scratch. Otherwise, one sticks too much to the template and overlooks essential differences.

That is why I have simplified the official example from the docs Three.js Bones Browser so that I can experiment on this basis. Maybe that will lead to the goal.

SkinnedMeshTest_01


UPDATE
SkinnedMeshTest_02

Another, even more simplified variant for tests. Some terms changed. The term
segmentCount at sizing = { } in the original is the bone count ( constant 4 ) and not to be confused with the segments of the geometry.

In the original
sizing.segmentCount * 3, // heightSegments
( 4*3 height segments of the cylinder!)

By the way, in SkinnedMesh Docs: three.js docs

Code Example
const geometry = new THREE.CylinderGeometry( 5, 5, 5, 15, 5, 30 );
no matching definition for example: ( 5, 5, 32, 8, 12, true)


SkinnedMeshTest_01


<!DOCTYPE html>
<!-- adapted from	https://threejs.org/docs/scenes/bones-browser.html  -->
<!-- https://discourse.threejs.org/t/problem-porting-skinnedmesh-from-old-geometry-to-buffergeometry/33748 -->
	<head>
		<meta charset="utf-8">
		<title> SkinnedMeshTest_01 </title>
	</head>
	<body> </body> 
	
	<script type="module">
	
	import { Bone,Color,CylinderGeometry,DoubleSide,Float32BufferAttribute,MeshBasicMaterial,PerspectiveCamera,
			 PointLight,Scene,SkinnedMesh,Skeleton,SkeletonHelper,Vector3,Uint16BufferAttribute,WebGLRenderer
	} from '../jsm/three.module.136.js';	
	import { OrbitControls } from '../jsm/OrbitControls.136.js';

	let  mesh, bones, skeletonHelper;

	const scene = new Scene();
	scene.background = new Color( 0xdedede );
	const camera = new PerspectiveCamera( 65, window.innerWidth / window.innerHeight, 0.1, 200 );
	camera.position.set( 0,40,40 );
	const renderer = new WebGLRenderer( { antialias: true } );
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );
	document.body.appendChild( renderer.domElement );
	new OrbitControls( camera, renderer.domElement );
 
	initBones();

	function createGeometry( sizing ) {

		const geometry = new CylinderGeometry(
			5, // radiusTop
			5, // radiusBottom
			sizing.height,
			8, // radiusSegments
			sizing.segmentCount * 3, // heightSegments
			true // openEnded
		);

		const position = geometry.attributes.position;

		const vertex = new Vector3();

		const skinIndices = [];
		const skinWeights = [];

		for ( let i = 0; i < position.count; i ++ ) {

			vertex.fromBufferAttribute( position, i );

			const y = ( vertex.y + sizing.halfHeight ); 
			
			const skinIndex = Math.floor( y / sizing.segmentHeight );
			const skinWeight = ( y % sizing.segmentHeight ) / sizing.segmentHeight;

			skinIndices.push( skinIndex, skinIndex + 1, 0, 0 );
			skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 );

		}

		geometry.setAttribute( 'skinIndex', new Uint16BufferAttribute( skinIndices, 4 ) );
		geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeights, 4 ) );

		return geometry;

	}

	function createBones( sizing ) {

		bones = [];

		let prevBone = new Bone();
		bones.push( prevBone );
		prevBone.position.y = -sizing.halfHeight;

		for ( let i = 0; i < sizing.segmentCount; i ++ ) {

			const bone = new Bone();
			bone.position.y = sizing.segmentHeight;;
			bones.push( bone );
			prevBone.add( bone );
			prevBone = bone;

		}

		return bones;

	}

	function createMesh( geometry, bones ) {

		const material = new MeshBasicMaterial( { color: 0xff44ee, side: DoubleSide, wireframe: true } );

		const mesh = new SkinnedMesh( geometry,	material );
		const skeleton = new Skeleton( bones );

		mesh.add( bones[ 0 ] );

		mesh.bind( skeleton );

		skeletonHelper = new SkeletonHelper( mesh );
		skeletonHelper.material.linewidth = 2;
		scene.add( skeletonHelper );

		return mesh;

	}
	
	function initBones() {

		const segmentHeight = 8;
		const segmentCount = 4;
		const height = segmentHeight * segmentCount;
		const halfHeight = height * 0.5;

		const sizing = {
			segmentHeight: segmentHeight,
			segmentCount: segmentCount,
			height: height,
			halfHeight: halfHeight
		};

		const geometry = createGeometry( sizing );
		const bones = createBones( sizing );
		mesh = createMesh( geometry, bones );

		scene.add( mesh );

	}

	function render( ) {

		requestAnimationFrame( render );

		const time = Date.now() * 0.001;
		
			for ( let i = 0; i < mesh.skeleton.bones.length; i ++ ) {

				mesh.skeleton.bones[ i ].rotation.x = 0.6 * Math.cos( 1.2 * time ) / mesh.skeleton.bones.length;
				mesh.skeleton.bones[ i ].rotation.z = 0.6 * Math.sin( 1.2 * time ) / mesh.skeleton.bones.length;
			}

		renderer.render( scene, camera );

	}

	render( );

</script>
</html>

SkinnedMeshTest_02

<!DOCTYPE html>
<!-- adapted from    https://threejs.org/docs/scenes/bones-browser.html  -->
<!-- https://discourse.threejs.org/t/problem-porting-skinnedmesh-from-old-geometry-to-buffergeometry/33748 -->
    <head>
        <meta charset="utf-8">
        <title> SkinnedMeshTest_02 </title>
    </head>
    <body> </body> 

    <script type="module">

    import * as THREE from '../jsm/three.module.136.js';    
    import { OrbitControls } from '../jsm/OrbitControls.136.js';

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera( 65, window.innerWidth / window.innerHeight, 0.001, 1000 );
    camera.position.set( 2, 7, 7 );
    const renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.setClearColor( 0xdedede, 1 );
    document.body.appendChild( renderer.domElement );
    new OrbitControls( camera, renderer.domElement );
    scene.add( new THREE.AxesHelper( 2 ) );

    const material = new THREE.MeshBasicMaterial( { color: 0xff44ee, side: THREE.DoubleSide, wireframe: true } );

    // inputs
    const radius = 0.5;
    const radiusSegments = 6;
    const boneHeight = 0.75;
    const boneCount = 4;
    const segmentsBone = 5;

    const height = boneHeight * boneCount;

    const geometry = new THREE.CylinderGeometry( radius, radius, height, radiusSegments, boneCount * segmentsBone, true );

    const bones = [];
    let prevBone = new THREE.Bone();
    bones.push( prevBone );
    prevBone.position.y = -height / 2 

    for ( let i = 0; i < boneCount; i ++ ) {

        const bone = new THREE.Bone();
        bone.position.y = boneHeight;
        bones.push( bone );
        prevBone.add( bone );
        prevBone = bone;

    }

    const pos = geometry.attributes.position;
    const vertex = new THREE.Vector3( );
    const skinIndices = [];
    const skinWeights = [];

    for ( let i = 0; i < pos.count; i ++ ) {

        vertex.fromBufferAttribute( pos, i );

        const y = ( vertex.y + height / 2 ); 
        
        const skinIndex = Math.floor( y / boneHeight );
        const skinWeight = ( y % boneHeight ) / boneHeight;

        skinIndices.push( skinIndex, skinIndex + 1, 0, 0 );
        skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 );

    }

    geometry.setAttribute( 'skinIndex',  new THREE.Uint16BufferAttribute( skinIndices, 4 ) );
    geometry.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeights, 4 ) );

    const mesh = new THREE.SkinnedMesh( geometry, material );
    const skeleton = new THREE.Skeleton( bones );
    mesh.add( bones[ 0 ] );
    mesh.bind( skeleton );
    scene.add( mesh );
    mesh.position.y = height / 2;
    
    scene.add( new THREE.SkeletonHelper( mesh ) );
    
    let time;

    bones[ 0 ].position.z = -0.5;   bones[ 2 ].rotation.y = 0.7;  bones[ 4 ].rotation.y = -0.7;     // tests 

    animate( );

    function animate( ) {

        requestAnimationFrame( animate );

        time = Date.now() * 0.001;
        
        for ( let i = 0; i < mesh.skeleton.bones.length; i ++ ) {  // shorter:  bones.length

            mesh.skeleton.bones[ i ].rotation.x = 0.1 * Math.cos( 1.3 * time );      // tests 
            mesh.skeleton.bones[ i ].rotation.z = 0.1 * Math.sin( 1.3 * time );

        }

        renderer.render( scene, camera );

    }

</script>
</html>

Solution

:sunny: SkinnedMeshTest_03
:slightly_smiling_face:


<!DOCTYPE html>
<!-- adapted from    https://threejs.org/docs/scenes/bones-browser.html  -->
<!-- https://discourse.threejs.org/t/problem-porting-skinnedmesh-from-old-geometry-to-buffergeometry/33748 -->
    <head>
        <meta charset="utf-8">
        <title> SkinnedMeshTest_03 </title>
    </head>
    <body> </body> 

    <script type="module">

    import * as THREE from '../jsm/three.module.136.js';
    import { mergeBufferGeometries } from '../jsm/BufferGeometryUtils.136.js'
    import { OrbitControls } from '../jsm/OrbitControls.136.js';

    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera( 65, window.innerWidth / window.innerHeight, 0.001, 1000 );
    camera.position.set( 2, 7, 7 );
    const renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.setClearColor( 0xdedede, 1 );
    document.body.appendChild( renderer.domElement );
    new OrbitControls( camera, renderer.domElement );
    scene.add( new THREE.AxesHelper( 2 ) );

    const material = new THREE.MeshBasicMaterial( { color: 0xff44ee, side: THREE.DoubleSide, wireframe: true } );

    // inputs
    const radius = 0.5;
    const radiusSegments = 16;
    const boneHeight = 0.75;
    const boneCount = 4;
    const segmentsBone = 6;

    const height = boneHeight * boneCount;

    const cylinderGeometry = new THREE.CylinderBufferGeometry( radius, radius, height, radiusSegments, boneCount * segmentsBone, true );   
    const cylPosCount = cylinderGeometry.attributes.position.count;
    
    const bones = [];
    let prevBone = new THREE.Bone();
    bones.push( prevBone );
    prevBone.position.y = -height / 2 

    for ( let i = 0; i < boneCount; i ++ ) {

        const bone = new THREE.Bone();
        bone.position.y = boneHeight;
        bones.push( bone );
        prevBone.add( bone );
        prevBone = bone;

    }

    const sphereGeometry = new THREE.SphereBufferGeometry( radius, radiusSegments, radiusSegments );
    sphereGeometry.translate ( 0, height / 2, 0 );
 
    const limbGeometry = mergeBufferGeometries( [ cylinderGeometry, sphereGeometry ] );
 
    const pos = limbGeometry.attributes.position;
    const vertex = new THREE.Vector3( );
    const skinIndices = [];
    const skinWeights = [];

    for ( let i = 0; i < pos.count; i ++ ) { //  cylPosCount

        vertex.fromBufferAttribute( pos, i );

        const y = ( vertex.y + height / 2 ); 
        
        const skinIndex = Math.floor( y / boneHeight );
        const skinWeight = i < cylPosCount ? ( y % boneHeight ) / boneHeight : 0;

        skinIndices.push( skinIndex, skinIndex + 1, 0, 0 );
        skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 );

    }

    limbGeometry.setAttribute( 'skinIndex',  new THREE.Uint16BufferAttribute( skinIndices, 4 ) );
    limbGeometry.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeights, 4 ) );
   
    const mesh = new THREE.SkinnedMesh( limbGeometry, material );
    const skeleton = new THREE.Skeleton( bones );
    mesh.add( bones[ 0 ] );
    mesh.bind( skeleton );
    scene.add( mesh );
    mesh.position.y = height / 2;
    
    scene.add( new THREE.SkeletonHelper( mesh ) );
    
    let time;

    bones[ 2 ].rotation.y = 0.7;  bones[ 4 ].rotation.y = -0.7;     // tests 

    animate( );

    function animate( ) {

        requestAnimationFrame( animate );

        time = Date.now() * 0.001;
        
        for ( let i = 0; i < mesh.skeleton.bones.length; i ++ ) {  // shorter:  bones.length

            mesh.skeleton.bones[ i ].rotation.x = 0.1 * Math.cos( 1.3 * time );      // tests 
            mesh.skeleton.bones[ i ].rotation.z = 0.1 * Math.sin( 1.3 * time );

        }

        renderer.render( scene, camera );

    }

</script>
</html>
1 Like