2

What is the proper way of applying bone matrices to vertices so that the actual geometry represents the transformed shape without using GPU?

I have a character model I am applying a skeleton to that I want to 3D print in different poses, so I want to use the STLExporter to export a 3D printable file, but it only exports the original geometry.

If I can scene.traverse() over each SkinnedMesh and geometry.applyMatrix(???) with the correct bone matrix in the right order, I should be able to export an STL with the transform applied, right?

var objects = [];
var triangles = 0;
scene.traverse( function ( object ) {
    if ( object.isMesh ) {
        var geometry = object.geometry;
        if ( geometry.isBufferGeometry ) {
            geometry = new THREE.Geometry().fromBufferGeometry( geometry );
        }
        // TODO: Apply skeleton transform
        if ( geometry.isGeometry ) {
            triangles += geometry.faces.length;
            objects.push( {
                geometry: geometry,
                matrixWorld: object.matrixWorld
            } );
        }
    }
} );
wizulus
  • 5,653
  • 2
  • 23
  • 40

2 Answers2

3

have a look at this pull request: https://github.com/mrdoob/three.js/pull/8953/files

Especially this part:

THREE.SkinnedMesh.prototype.boneTransform = 
...snip...
for ( var i = 0; i < 4; i ++ ) {
+
+           var skinWeight = skinWeights[ properties[ i ] ];
+
+           if ( skinWeight != 0 ) {
+
+               var boneIndex = skinIndices[ properties[ i ] ];
+               tempMatrix.multiplyMatrices( this.skeleton.bones[ boneIndex ].matrixWorld, this.skeleton.boneInverses[ boneIndex ] );
+               result.add( temp.copy( clone ).applyMatrix4( tempMatrix ).multiplyScalar( skinWeight ) );
+
+           }
+
+       }

So to bake the deformations you have to do the following steps:

  1. Go over all vertices
  2. Get their corresponding skinIndices and skinWeights
  3. Get the associated bones for each vertex and their weights
  4. Go over all bones
  5. Get the transform matrix of the bone and translate it to the coordinate system of the Geometry
  6. Weight the matrix via the associated skinWeights[boneIdx]
  7. Apply the final matrix

edit: A version of the exporter that fixes the issue. (https://github.com/mrdoob/three.js/blob/1be00d5ad075b2305b86c6385403d0e56b4621dd/examples/js/exporters/STLExporter.js)

cioddi
  • 763
  • 4
  • 12
quasi
  • 113
  • 2
  • 5
  • welcome to Stack Overflow. Thank you for answering my question. It looks like I have some work to do to adapt that code. – wizulus Jul 18 '18 at 19:05
  • Thank you for the explanation quasi. @wizulus have you managed to get this incorporated into the exporter. If not I will try my best in the upcoming weeks as I currently have the exact same problem you described above. The STL exporter in the current master just ignores all the bone transforms on skinnedMesh vertices. https://gist.github.com/jcarletto27/e271bbb7639c4bed2427 This is a solution for an older version but since the skinIndices and skinWeights are stored differently, probably due to the new animation system, this does not work anymore. – cioddi Jun 23 '19 at 11:02
  • I made a version of the exporter that works with the current master and incorporates vertex bone transformations on SkinnedMesh objects. https://github.com/mrdoob/three.js/blob/1be00d5ad075b2305b86c6385403d0e56b4621dd/examples/js/exporters/STLExporter.js – cioddi Jun 24 '19 at 11:35
3

Baking in the scene with normals preservation and without exporting to STL.

Usage:

bakeSkeleton(mesh)

The transformation will be applied to the provided object and all its children. getBoneNormalTransform is made from built-in boneTransform on SkinnedMesh but adapted for normals.


function bakeSkeleton ( target ) {
  var v1 = new Vector3();

  target.traverse( function ( object ) {
    if ( !object.isSkinnedMesh ) return;
    if ( object.geometry.isBufferGeometry !== true ) throw new Error( 'Only BufferGeometry supported.' );

    var positionAttribute = object.geometry.getAttribute( 'position' );
    var normalAttribute = object.geometry.getAttribute( 'normal' );

    for ( var j = 0; j < positionAttribute.count; j ++ ) {
      object.boneTransform( j, v1 );
      positionAttribute.setXYZ( j, v1.x, v1.y, v1.z);

      getBoneNormalTransform.call( object, j, v1 );
      normalAttribute.setXYZ( j, v1.x, v1.y, v1.z );
    }

    positionAttribute.needsUpdate = true;
    normalAttribute.needsUpdate = true;

    object.skeleton.bones.forEach(bone => bone.rotation.set(0,0,0));
  } );
}


const getBoneNormalTransform = (function () {

  var baseNormal = new Vector3();

  var skinIndex = new Vector4();
  var skinWeight = new Vector4();

  var vector = new Vector3();
  var matrix = new Matrix4();
  var matrix3 = new Matrix3();

  return function ( index, target ) {

    var skeleton = this.skeleton;
    var geometry = this.geometry;

    skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
    skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );

    baseNormal.fromBufferAttribute( geometry.attributes.normal, index ).applyNormalMatrix( matrix3.getNormalMatrix(this.bindMatrix) );

    target.set( 0, 0, 0 );

    for ( var i = 0; i < 4; i ++ ) {

      var weight = skinWeight.getComponent( i );

      if ( weight !== 0 ) {

        var boneIndex = skinIndex.getComponent( i );

        matrix.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );

        target.addScaledVector( vector.copy( baseNormal ).applyNormalMatrix( matrix3.getNormalMatrix(matrix) ), weight );

      }

    }
    matrix3.getNormalMatrix(this.bindMatrixInverse);
    return target.applyNormalMatrix( matrix3 );

  };

}())

Pawel
  • 16,093
  • 5
  • 70
  • 73