Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 89 additions & 7 deletions examples/jsm/loaders/FBXLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -824,8 +824,6 @@ class FBXTreeParser {
indices: [],
weights: [],
transformLink: new Matrix4().fromArray( boneNode.TransformLink.a ),
// transform: new Matrix4().fromArray( boneNode.Transform.a ),
// linkMode: boneNode.Mode,

};

Expand Down Expand Up @@ -918,8 +916,6 @@ class FBXTreeParser {

} );

this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );

this.addGlobalSceneSettings();

sceneGraph.traverse( function ( node ) {
Expand All @@ -942,6 +938,64 @@ class FBXTreeParser {

} );

// Like Blender's FBX importer, use the BindPose section to set the
// rest pose for bones that are not part of a skin cluster. The BindPose
// provides a more authoritative rest pose than the Lcl properties which
// may represent an animation frame rather than the true rest state.
// Bones WITH clusters will get their bind pose from TransformLink
// (set via bindSkeleton below), which takes priority.
const bindPoseMatrices = this.parsePoseNodes();
const clusterBoneIDs = new Set();

for ( const ID in deformers.skeletons ) {

deformers.skeletons[ ID ].rawBones.forEach( function ( _, i ) {

const bone = deformers.skeletons[ ID ].bones[ i ];
if ( bone ) clusterBoneIDs.add( bone.ID );

} );

}

const tempMatrix = new Matrix4();

sceneGraph.traverse( function ( node ) {

if ( node.isBone && node.ID !== undefined && ! clusterBoneIDs.has( node.ID ) ) {

const bindPose = bindPoseMatrices[ node.ID ];

if ( bindPose !== undefined ) {

if ( node.parent ) {

tempMatrix.copy( node.parent.matrixWorld ).invert();
tempMatrix.multiply( bindPose );

} else {

tempMatrix.copy( bindPose );

}

tempMatrix.decompose( node.position, node.quaternion, node.scale );
node.updateMatrix();
node.matrixWorld.copy( bindPose );

}

}

} );

// Bind skeletons after transforms are applied so that bind matrices
// are computed from the final scene state. This ensures the rest pose
// is correct even when the FBX file's Cluster TransformLink matrices
// differ from the reconstructed bone transforms (common in files
// without a BindPose section).
this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );

const animations = new AnimationParser().parse();

// if all the models where already combined in a single group, just return that
Expand Down Expand Up @@ -1460,12 +1514,30 @@ class FBXTreeParser {

bindSkeleton( skeletons, geometryMap, modelMap ) {

const bindMatrices = this.parsePoseNodes();

for ( const ID in skeletons ) {

const skeleton = skeletons[ ID ];

// Compute bone inverses from TransformLink rather than from the
// bones' current matrixWorld. The TransformLink matrices represent
// each bone's global transform at the time the skin weights were
// painted, which may differ from the scene-reconstructed transforms.
const boneInverses = [];

for ( let i = 0, l = skeleton.bones.length; i < l; i ++ ) {

const inverse = new Matrix4();

if ( skeleton.bones[ i ] && skeleton.rawBones[ i ] ) {

inverse.copy( skeleton.rawBones[ i ].transformLink ).invert();

}

boneInverses.push( inverse );

}

const parents = connections.get( parseInt( skeleton.ID ) ).parents;

parents.forEach( function ( parent ) {
Expand All @@ -1481,7 +1553,16 @@ class FBXTreeParser {

const model = modelMap.get( geoConnParent.ID );

model.bind( new Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] );
// Use the mesh's current matrixWorld as bind matrix.
// The BindPose section is intentionally not used here
// since it may contain scale/rotation from the model
// hierarchy that is inconsistent with the TransformLink-
// based bone inverses. Always provide a bind matrix to
// prevent bind() from calling calculateInverses() which
// would overwrite the bone inverses computed above.
model.updateMatrixWorld( true );

model.bind( new Skeleton( skeleton.bones, boneInverses ), model.matrixWorld );

}

Expand All @@ -1495,6 +1576,7 @@ class FBXTreeParser {

}

// Parse BindPose nodes and return a map of node ID to bind matrix.
parsePoseNodes() {

const bindMatrices = {};
Expand Down
Binary file added examples/models/fbx/archer/ArcherRi01.FBX
Binary file not shown.
Binary file added examples/models/fbx/archer/ArcherRi01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/models/fbx/archer/ArcherRi01_W.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/models/fbx/warrior/Warrior.fbx
Binary file not shown.
11 changes: 11 additions & 0 deletions examples/webgl_loader_fbx.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,16 @@
'monkey',
'monkey_embedded_texture',
'vCube',
'archer/ArcherRi01',
'warrior/Warrior',
'stanford-bunny',
'mixamo',
];

const scales = new Map();
scales.set( 'warrior/Warrior', 100 );
scales.set( 'archer/ArcherRi01', 100 );
scales.set( 'stanford-bunny', 0.001 );

init();

Expand Down Expand Up @@ -162,6 +170,9 @@

object = group;

const scale = scales.get( asset );
object.scale.setScalar( scale || 1 );

if ( object.animations && object.animations.length ) {

mixer = new THREE.AnimationMixer( object );
Expand Down
Loading