未验证 提交 5c9f2b76 编写于 作者: M Mr.doob 提交者: GitHub

Merge pull request #12729 from looeee/FBXLoader_more_animation_refactoring

FBXLoader refactor animation and conditional animation parsing
...@@ -1497,7 +1497,7 @@ ...@@ -1497,7 +1497,7 @@
bindSkeleton( FBXTree, deformers, geometryMap, modelMap, connections, sceneGraph ); bindSkeleton( FBXTree, deformers, geometryMap, modelMap, connections, sceneGraph );
addAnimations( FBXTree, connections, sceneGraph ); addAnimations( FBXTree, connections, sceneGraph, modelMap );
createAmbientLight( FBXTree, sceneGraph ); createAmbientLight( FBXTree, sceneGraph );
...@@ -2109,15 +2109,6 @@ ...@@ -2109,15 +2109,6 @@
//Skeleton is now bound, return objects to starting world positions. //Skeleton is now bound, return objects to starting world positions.
sceneGraph.updateMatrixWorld( true ); sceneGraph.updateMatrixWorld( true );
// Silly hack with the animation parsing. We're gonna pretend the scene graph has a skeleton
// to attach animations to, since FBX treats animations as animations for the entire scene,
// not just for individual objects.
sceneGraph.skeleton = {
bones: Array.from( modelMap.values() ),
};
} }
// Parses animation information from nodes in // Parses animation information from nodes in
...@@ -2128,122 +2119,98 @@ ...@@ -2128,122 +2119,98 @@
// Multiple animation takes are stored in AnimationLayer and AnimationStack // Multiple animation takes are stored in AnimationLayer and AnimationStack
// Note: There is also FBXTree.Takes, however this seems to be left over from an older version of the // Note: There is also FBXTree.Takes, however this seems to be left over from an older version of the
// format and is no longer used // format and is no longer used
function parseAnimations( FBXTree, connections, sceneGraph ) { function parseAnimations( FBXTree, connections, modelsArray ) {
var rawNodes = FBXTree.Objects.subNodes.AnimationCurveNode;
var rawCurves = FBXTree.Objects.subNodes.AnimationCurve; var rawCurves = FBXTree.Objects.subNodes.AnimationCurve;
var rawCurveNodes = FBXTree.Objects.subNodes.AnimationCurveNode;
var rawLayers = FBXTree.Objects.subNodes.AnimationLayer; var rawLayers = FBXTree.Objects.subNodes.AnimationLayer;
var rawStacks = FBXTree.Objects.subNodes.AnimationStack; var rawStacks = FBXTree.Objects.subNodes.AnimationStack;
// since the actual transformation data is stored in FBXTree.Objects.subNodes.AnimationCurve,
// if this is undefined we can safely assume there are no animations
if ( FBXTree.Objects.subNodes.AnimationCurve === undefined ) return undefined;
var animations = { var animations = {
takes: {}, takes: {},
fps: getFrameRate( FBXTree ), fps: getFrameRate( FBXTree ),
}; };
var animationCurveNodes = []; var curveNodesMap = new Map();
for ( var nodeID in rawNodes ) {
if ( nodeID.match( /\d+/ ) ) {
var animationNode = parseAnimationCurveNode( FBXTree, rawNodes[ nodeID ], connections, sceneGraph ); for ( var nodeID in rawCurveNodes ) {
animationCurveNodes.push( animationNode );
} if ( nodeID.match( /\d+/ ) ) {
} var animationNode = parseAnimationCurveNode( FBXTree, rawCurveNodes[ nodeID ], connections, modelsArray );
var tmpMap = new Map(); if ( animationNode !== null ) {
for ( var animationCurveNodeIndex = 0; animationCurveNodeIndex < animationCurveNodes.length; ++ animationCurveNodeIndex ) {
if ( animationCurveNodes[ animationCurveNodeIndex ] === null ) { curveNodesMap.set( animationNode.id, animationNode );
continue; }
} }
tmpMap.set( animationCurveNodes[ animationCurveNodeIndex ].id, animationCurveNodes[ animationCurveNodeIndex ] );
} }
var animationCurves = [];
for ( nodeID in rawCurves ) { for ( nodeID in rawCurves ) {
if ( nodeID.match( /\d+/ ) ) { if ( nodeID.match( /\d+/ ) ) {
var animationCurve = parseAnimationCurve( rawCurves[ nodeID ] ); var animationCurve = parseAnimationCurve( rawCurves[ nodeID ] );
// seems like this check would be necessary? var conns = connections.get( animationCurve.id );
if ( ! connections.has( animationCurve.id ) ) continue;
animationCurves.push( animationCurve ); if ( conns !== undefined ) {
var firstParentConn = connections.get( animationCurve.id ).parents[ 0 ]; var firstParentConn = conns.parents[ 0 ];
var firstParentID = firstParentConn.ID; var firstParentID = firstParentConn.ID;
var firstParentRelationship = firstParentConn.relationship; var firstParentRelationship = firstParentConn.relationship;
var axis = ''; var axis = '';
if ( firstParentRelationship.match( /X/ ) ) { if ( firstParentRelationship.match( /X/ ) ) {
axis = 'x'; axis = 'x';
} else if ( firstParentRelationship.match( /Y/ ) ) { } else if ( firstParentRelationship.match( /Y/ ) ) {
axis = 'y'; axis = 'y';
} else if ( firstParentRelationship.match( /Z/ ) ) { } else if ( firstParentRelationship.match( /Z/ ) ) {
axis = 'z'; axis = 'z';
} else { } else {
continue; continue;
} }
curveNodesMap.get( firstParentID ).curves[ axis ] = animationCurve;
tmpMap.get( firstParentID ).curves[ axis ] = animationCurve; }
} }
} }
tmpMap.forEach( function ( curveNode ) { var emptyCurve = {
if ( curveNode.attr === 'R' ) {
var curves = curveNode.curves;
// Some FBX files have an AnimationCurveNode
// which isn't any connected to any AnimationCurve.
// Setting animation parameter for them here.
if ( curves.x === null ) {
curves.x = {
version: null,
times: [ 0.0 ],
values: [ 0.0 ]
};
}
if ( curves.y === null ) { times: [ 0.0 ],
values: [ 0.0 ]
curves.y = { };
version: null,
times: [ 0.0 ],
values: [ 0.0 ]
};
} // loop over rotation values, convert to radians and add any pre rotation
curveNodesMap.forEach( function ( curveNode ) {
if ( curves.z === null ) { if ( curveNode.attr === 'R' ) {
curves.z = { var curves = curveNode.curves;
version: null,
times: [ 0.0 ],
values: [ 0.0 ]
};
} if ( curves.x === null ) curves.x = emptyCurve;
if ( curves.y === null ) curves.y = emptyCurve;
if ( curves.z === null ) curves.z = emptyCurve;
curves.x.values = curves.x.values.map( THREE.Math.degToRad ); curves.x.values = curves.x.values.map( THREE.Math.degToRad );
curves.y.values = curves.y.values.map( THREE.Math.degToRad ); curves.y.values = curves.y.values.map( THREE.Math.degToRad );
...@@ -2251,10 +2218,14 @@ ...@@ -2251,10 +2218,14 @@
if ( curveNode.preRotations !== null ) { if ( curveNode.preRotations !== null ) {
var preRotations = new THREE.Euler().setFromVector3( curveNode.preRotations, 'ZYX' ); var preRotations = curveNode.preRotations.map( THREE.Math.degToRad );
preRotations.push( 'ZYX' );
preRotations = new THREE.Euler().fromArray( preRotations );
preRotations = new THREE.Quaternion().setFromEuler( preRotations ); preRotations = new THREE.Quaternion().setFromEuler( preRotations );
var frameRotation = new THREE.Euler(); var frameRotation = new THREE.Euler();
var frameRotationQuaternion = new THREE.Quaternion(); var frameRotationQuaternion = new THREE.Quaternion();
for ( var frame = 0; frame < curves.x.times.length; ++ frame ) { for ( var frame = 0; frame < curves.x.times.length; ++ frame ) {
frameRotation.set( curves.x.values[ frame ], curves.y.values[ frame ], curves.z.values[ frame ], 'ZYX' ); frameRotation.set( curves.x.values[ frame ], curves.y.values[ frame ], curves.z.values[ frame ], 'ZYX' );
...@@ -2287,13 +2258,13 @@ ...@@ -2287,13 +2258,13 @@
for ( var childIndex = 0; childIndex < children.length; childIndex ++ ) { for ( var childIndex = 0; childIndex < children.length; childIndex ++ ) {
// Skip lockInfluenceWeights // Skip lockInfluenceWeights
if ( tmpMap.has( children[ childIndex ].ID ) ) { if ( curveNodesMap.has( children[ childIndex ].ID ) ) {
var curveNode = tmpMap.get( children[ childIndex ].ID ); var curveNode = curveNodesMap.get( children[ childIndex ].ID );
var boneID = curveNode.containerBoneID; var modelID = curveNode.containerModelID;
if ( layer[ boneID ] === undefined ) { if ( layer[ modelID ] === undefined ) {
layer[ boneID ] = { layer[ modelID ] = {
T: null, T: null,
R: null, R: null,
S: null S: null
...@@ -2301,7 +2272,7 @@ ...@@ -2301,7 +2272,7 @@
} }
layer[ boneID ][ curveNode.attr ] = curveNode; layer[ modelID ][ curveNode.attr ] = curveNode;
} }
...@@ -2347,10 +2318,12 @@ ...@@ -2347,10 +2318,12 @@
if ( timestamps.max > timestamps.min ) { if ( timestamps.max > timestamps.min ) {
animations.takes[ nodeID ] = { animations.takes[ nodeID ] = {
name: rawStacks[ nodeID ].attrName, name: rawStacks[ nodeID ].attrName,
layers: layers, layers: layers,
length: timestamps.max - timestamps.min, length: timestamps.max - timestamps.min,
frames: ( timestamps.max - timestamps.min ) * animations.fps frames: ( timestamps.max - timestamps.min ) * animations.fps
}; };
} }
...@@ -2361,7 +2334,8 @@ ...@@ -2361,7 +2334,8 @@
} }
function parseAnimationCurveNode( FBXTree, animationCurveNode, connections, sceneGraph ) { // parse a node in FBXTree.Objects.subNodes.AnimationCurveNode
function parseAnimationCurveNode( FBXTree, animationCurveNode, connections, modelsArray ) {
var rawModels = FBXTree.Objects.subNodes.Model; var rawModels = FBXTree.Objects.subNodes.Model;
...@@ -2369,68 +2343,40 @@ ...@@ -2369,68 +2343,40 @@
id: animationCurveNode.id, id: animationCurveNode.id,
attr: animationCurveNode.attrName, attr: animationCurveNode.attrName,
internalID: animationCurveNode.id, containerModelID: - 1,
attrX: false,
attrY: false,
attrZ: false,
containerBoneID: - 1,
containerID: - 1,
curves: { curves: {
x: null, x: null,
y: null, y: null,
z: null z: null
}, },
preRotations: null preRotations: null,
}; };
if ( returnObject.attr.match( /S|R|T/ ) ) { if ( returnObject.attr.match( /S|R|T/ ) === null ) return null;
for ( var attributeKey in animationCurveNode.properties ) {
if ( attributeKey.match( /X/ ) ) {
returnObject.attrX = true;
}
if ( attributeKey.match( /Y/ ) ) {
returnObject.attrY = true;
}
if ( attributeKey.match( /Z/ ) ) {
returnObject.attrZ = true;
}
}
} else {
return null;
}
var conns = connections.get( returnObject.id ); var conns = connections.get( returnObject.id );
var containerIndices = conns.parents; var containerIndices = conns.parents;
for ( var containerIndicesIndex = containerIndices.length - 1; containerIndicesIndex >= 0; -- containerIndicesIndex ) { for ( var containerIndicesIndex = containerIndices.length - 1; containerIndicesIndex >= 0; -- containerIndicesIndex ) {
var boneID = findIndex( sceneGraph.skeleton.bones, function ( bone ) { var modelID = findIndex( modelsArray, function ( model ) {
return bone.FBX_ID === containerIndices[ containerIndicesIndex ].ID; return model.FBX_ID === containerIndices[ containerIndicesIndex ].ID;
} ); } );
if ( boneID > - 1 ) {
returnObject.containerBoneID = boneID; if ( modelID > - 1 ) {
returnObject.containerID = containerIndices[ containerIndicesIndex ].ID;
var model = rawModels[ returnObject.containerID.toString() ]; returnObject.containerModelID = modelID;
var model = rawModels[ containerIndices[ containerIndicesIndex ].ID.toString() ];
// if the animated model is pre rotated, we'll have to apply the prerotations to every
// animation value as well
if ( 'PreRotation' in model.properties ) { if ( 'PreRotation' in model.properties ) {
returnObject.preRotations = new THREE.Vector3().fromArray( model.properties.PreRotation.value ).multiplyScalar( Math.PI / 180 ); returnObject.preRotations = model.properties.PreRotation.value;
} }
break; break;
...@@ -2443,17 +2389,15 @@ ...@@ -2443,17 +2389,15 @@
} }
// parse single node in FBXTree.Objects.subNodes.AnimationCurve
function parseAnimationCurve( animationCurve ) { function parseAnimationCurve( animationCurve ) {
return { return {
version: null,
id: animationCurve.id, id: animationCurve.id,
internalID: animationCurve.id,
times: animationCurve.subNodes.KeyTime.properties.a.map( convertFBXTimeToSeconds ), times: animationCurve.subNodes.KeyTime.properties.a.map( convertFBXTimeToSeconds ),
values: animationCurve.subNodes.KeyValueFloat.properties.a, values: animationCurve.subNodes.KeyValueFloat.properties.a,
attrFlag: animationCurve.subNodes.KeyAttrFlags.properties.a,
attrData: animationCurve.subNodes.KeyAttrDataFloat.properties.a,
}; };
} }
...@@ -2564,63 +2508,71 @@ ...@@ -2564,63 +2508,71 @@
} }
function addAnimations( FBXTree, connections, sceneGraph ) { function addAnimations( FBXTree, connections, sceneGraph, modelMap ) {
var animations = parseAnimations( FBXTree, connections, sceneGraph ); // create a flattened array of all models and bones in the scene
var modelsArray = Array.from( modelMap.values() );
if ( sceneGraph.animations === undefined ) { sceneGraph.animations = [];
sceneGraph.animations = []; var animations = parseAnimations( FBXTree, connections, modelsArray );
} if ( animations === undefined ) return;
var takes = animations.takes; // Silly hack with the animation parsing. We're gonna pretend the scene graph has a skeleton
// to attach animations to, since FBX treats animations as animations for the entire scene,
// not just for individual objects.
sceneGraph.skeleton = {
for ( var key in takes ) { bones: modelsArray,
var take = takes[ key ]; };
var animationData = { for ( var key in animations.takes ) {
name: take.name,
fps: animations.fps,
length: take.length,
hierarchy: []
};
var bones = sceneGraph.skeleton.bones; var take = animations.takes[ key ];
for ( var bonesIndex = 0, bonesLength = bones.length; bonesIndex < bonesLength; ++ bonesIndex ) { var clip = addTake( take, animations.fps, modelsArray );
var bone = bones[ bonesIndex ]; sceneGraph.animations.push( clip );
var name = bone.name.replace( /.*:/, '' ); }
var parentIndex = findIndex( bones, function ( parentBone ) {
return bone.parent === parentBone; }
} ); function addTake( take, fps, modelsArray ) {
animationData.hierarchy.push( { parent: parentIndex, name: name, keys: [] } );
} var animationData = {
name: take.name,
fps: fps,
length: take.length,
hierarchy: []
};
for ( var frame = 0; frame <= take.frames; frame ++ ) { animationData.hierarchy = modelsArray.map( ( model ) => {
for ( var bonesIndex = 0, bonesLength = bones.length; bonesIndex < bonesLength; ++ bonesIndex ) { return { name: model.name, keys: [] };
var bone = bones[ bonesIndex ]; } );
var boneIndex = bonesIndex;
var animationNode = take.layers[ 0 ][ boneIndex ];
for ( var hierarchyIndex = 0, hierarchyLength = animationData.hierarchy.length; hierarchyIndex < hierarchyLength; ++ hierarchyIndex ) { // fill empty keys with animation data
// this just loops over all frames and assumes that the animation has been baked at one keyframe per frame
for ( var frame = 0; frame <= take.frames; frame ++ ) {
var node = animationData.hierarchy[ hierarchyIndex ]; for ( var i = 0, length = modelsArray.length; i < length; ++ i ) {
if ( node.name === bone.name ) { var model = modelsArray[ i ];
node.keys.push( generateKey( animations, animationNode, bone, frame ) ); var animationNode = take.layers[ 0 ][ i ];
} for ( var hierarchyIndex = 0, hierarchyLength = animationData.hierarchy.length; hierarchyIndex < hierarchyLength; ++ hierarchyIndex ) {
var node = animationData.hierarchy[ hierarchyIndex ];
if ( node.name === model.name ) {
node.keys.push( generateKey( fps, animationNode, model, frame ) );
} }
...@@ -2628,30 +2580,29 @@ ...@@ -2628,30 +2580,29 @@
} }
sceneGraph.animations.push( THREE.AnimationClip.parseAnimation( animationData, bones ) );
} }
return THREE.AnimationClip.parseAnimation( animationData, modelsArray );
} }
var euler = new THREE.Euler(); var euler = new THREE.Euler();
var quaternion = new THREE.Quaternion(); var quaternion = new THREE.Quaternion();
function generateKey( animations, animationNode, bone, frame ) { function generateKey( fps, animationNode, model, frame ) {
var key = { var key = {
time: frame / animations.fps, time: frame / fps,
pos: bone.position.toArray(), pos: model.position.toArray(),
rot: bone.quaternion.toArray(), rot: model.quaternion.toArray(),
scl: bone.scale.toArray(), scl: model.scale.toArray(),
}; };
if ( animationNode === undefined ) return key; if ( animationNode === undefined ) return key;
euler.setFromQuaternion( bone.quaternion, 'ZYX', false ); euler.setFromQuaternion( model.quaternion, 'ZYX', false );
if ( hasCurve( animationNode, 'T' ) && hasKeyOnFrame( animationNode.T, frame ) ) { if ( hasCurve( animationNode, 'T' ) && hasKeyOnFrame( animationNode.T, frame ) ) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册