|
@@ -2144,68 +2144,90 @@
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- // Parses animation information from nodes in
|
|
|
|
- // FBXTree.Objects.subNodes.AnimationCurve: child of an AnimationCurveNode, holds the raw animation data (e.g. x axis rotation )
|
|
|
|
- // FBXTree.Objects.subNodes.AnimationCurveNode: child of an AnimationLayer and connected to whichever node is being animated
|
|
|
|
- // FBXTree.Objects.subNodes.AnimationLayer: child of an AnimationStack
|
|
|
|
- // FBXTree.Objects.subNodes.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
|
|
|
|
- // format and is no longer used
|
|
|
|
- function parseAnimations( FBXTree, connections, modelsArray ) {
|
|
|
|
-
|
|
|
|
- var rawCurves = FBXTree.Objects.subNodes.AnimationCurve;
|
|
|
|
- var rawCurveNodes = FBXTree.Objects.subNodes.AnimationCurveNode;
|
|
|
|
- var rawLayers = FBXTree.Objects.subNodes.AnimationLayer;
|
|
|
|
- var rawStacks = FBXTree.Objects.subNodes.AnimationStack;
|
|
|
|
|
|
+ function parseAnimations( FBXTree, connections ) {
|
|
|
|
|
|
// since the actual transformation data is stored in FBXTree.Objects.subNodes.AnimationCurve,
|
|
// 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 this is undefined we can safely assume there are no animations
|
|
if ( FBXTree.Objects.subNodes.AnimationCurve === undefined ) return undefined;
|
|
if ( FBXTree.Objects.subNodes.AnimationCurve === undefined ) return undefined;
|
|
|
|
|
|
- var animations = {
|
|
|
|
|
|
+ var curveNodesMap = parseAnimationCurveNodes( FBXTree );
|
|
|
|
|
|
- takes: {},
|
|
|
|
- fps: getFrameRate( FBXTree ),
|
|
|
|
|
|
+ parseAnimationCurves( FBXTree, connections, curveNodesMap );
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ var layersMap = parseAnimationLayers( FBXTree, connections, curveNodesMap );
|
|
|
|
+ var rawClips = parseAnimStacks( FBXTree, connections, layersMap );
|
|
|
|
+
|
|
|
|
+ return rawClips;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // parse nodes in FBXTree.Objects.subNodes.AnimationCurveNode
|
|
|
|
+ // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation )
|
|
|
|
+ // and is referenced by an AnimationLayer
|
|
|
|
+ function parseAnimationCurveNodes( FBXTree ) {
|
|
|
|
+
|
|
|
|
+ var rawCurveNodes = FBXTree.Objects.subNodes.AnimationCurveNode;
|
|
|
|
|
|
var curveNodesMap = new Map();
|
|
var curveNodesMap = new Map();
|
|
|
|
|
|
for ( var nodeID in rawCurveNodes ) {
|
|
for ( var nodeID in rawCurveNodes ) {
|
|
|
|
|
|
- var animationNode = parseAnimationCurveNode( FBXTree, rawCurveNodes[ nodeID ], connections, modelsArray );
|
|
|
|
|
|
+ var rawCurveNode = rawCurveNodes[ nodeID ];
|
|
|
|
+
|
|
|
|
+ if ( rawCurveNode.attrName.match( /S|R|T/ ) !== null ) {
|
|
|
|
+
|
|
|
|
+ var curveNode = {
|
|
|
|
|
|
- if ( animationNode !== null ) {
|
|
|
|
|
|
+ id: rawCurveNode.id,
|
|
|
|
+ attr: rawCurveNode.attrName,
|
|
|
|
+ curves: {},
|
|
|
|
|
|
- curveNodesMap.set( animationNode.id, animationNode );
|
|
|
|
|
|
+ };
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ curveNodesMap.set( curveNode.id, curveNode );
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
- for ( nodeID in rawCurves ) {
|
|
|
|
|
|
+ return curveNodesMap;
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // parse nodes in FBXTree.Objects.subNodes.AnimationCurve and connect them up to
|
|
|
|
+ // previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated
|
|
|
|
+ // axis ( e.g. times and values of x rotation)
|
|
|
|
+ function parseAnimationCurves( FBXTree, connections, curveNodesMap ) {
|
|
|
|
+
|
|
|
|
+ var rawCurves = FBXTree.Objects.subNodes.AnimationCurve;
|
|
|
|
+
|
|
|
|
+ for ( var nodeID in rawCurves ) {
|
|
|
|
|
|
- var animationCurve = parseAnimationCurve( rawCurves[ nodeID ] );
|
|
|
|
|
|
+ var animationCurve = {
|
|
|
|
+
|
|
|
|
+ id: rawCurves[ nodeID ].id,
|
|
|
|
+ times: rawCurves[ nodeID ].subNodes.KeyTime.properties.a.map( convertFBXTimeToSeconds ),
|
|
|
|
+ values: rawCurves[ nodeID ].subNodes.KeyValueFloat.properties.a,
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
|
|
var conns = connections.get( animationCurve.id );
|
|
var conns = connections.get( animationCurve.id );
|
|
|
|
|
|
if ( conns !== undefined ) {
|
|
if ( conns !== undefined ) {
|
|
|
|
|
|
- var firstParentConn = conns.parents[ 0 ];
|
|
|
|
- var firstParentID = firstParentConn.ID;
|
|
|
|
- var firstParentRelationship = firstParentConn.relationship;
|
|
|
|
|
|
+ var animationCurveID = conns.parents[ 0 ].ID;
|
|
|
|
+ var animationCurveRelationship = conns.parents[ 0 ].relationship;
|
|
var axis = '';
|
|
var axis = '';
|
|
|
|
|
|
- if ( firstParentRelationship.match( /X/ ) ) {
|
|
|
|
|
|
+ if ( animationCurveRelationship.match( /X/ ) ) {
|
|
|
|
|
|
axis = 'x';
|
|
axis = 'x';
|
|
|
|
|
|
- } else if ( firstParentRelationship.match( /Y/ ) ) {
|
|
|
|
|
|
+ } else if ( animationCurveRelationship.match( /Y/ ) ) {
|
|
|
|
|
|
axis = 'y';
|
|
axis = 'y';
|
|
|
|
|
|
- } else if ( firstParentRelationship.match( /Z/ ) ) {
|
|
|
|
|
|
+ } else if ( animationCurveRelationship.match( /Z/ ) ) {
|
|
|
|
|
|
axis = 'z';
|
|
axis = 'z';
|
|
|
|
|
|
@@ -2215,515 +2237,317 @@
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- curveNodesMap.get( firstParentID ).curves[ axis ] = animationCurve;
|
|
|
|
|
|
+ curveNodesMap.get( animationCurveID ).curves[ axis ] = animationCurve;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- var emptyCurve = {
|
|
|
|
-
|
|
|
|
- times: [ 0.0 ],
|
|
|
|
- values: [ 0.0 ]
|
|
|
|
-
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- // loop over rotation values, convert to radians and add any pre rotation
|
|
|
|
- curveNodesMap.forEach( function ( curveNode ) {
|
|
|
|
-
|
|
|
|
- if ( curveNode.attr === 'R' ) {
|
|
|
|
-
|
|
|
|
- var curves = curveNode.curves;
|
|
|
|
-
|
|
|
|
- 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.y.values = curves.y.values.map( THREE.Math.degToRad );
|
|
|
|
- curves.z.values = curves.z.values.map( THREE.Math.degToRad );
|
|
|
|
-
|
|
|
|
- if ( curveNode.preRotations !== null ) {
|
|
|
|
-
|
|
|
|
- var preRotations = curveNode.preRotations.map( THREE.Math.degToRad );
|
|
|
|
- preRotations.push( 'ZYX' );
|
|
|
|
- preRotations = new THREE.Euler().fromArray( preRotations );
|
|
|
|
- preRotations = new THREE.Quaternion().setFromEuler( preRotations );
|
|
|
|
-
|
|
|
|
- var frameRotation = new THREE.Euler();
|
|
|
|
- var frameRotationQuaternion = new THREE.Quaternion();
|
|
|
|
-
|
|
|
|
- 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' );
|
|
|
|
- frameRotationQuaternion.setFromEuler( frameRotation ).premultiply( preRotations );
|
|
|
|
- frameRotation.setFromQuaternion( frameRotationQuaternion, 'ZYX' );
|
|
|
|
- curves.x.values[ frame ] = frameRotation.x;
|
|
|
|
- curves.y.values[ frame ] = frameRotation.y;
|
|
|
|
- curves.z.values[ frame ] = frameRotation.z;
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ // parse nodes in FBXTree.Objects.subNodes.AnimationLayer. Each layers holds references
|
|
|
|
+ // to various AnimationCurveNodes and is referenced by an AnimationStack node
|
|
|
|
+ // note: theoretically a stack can multiple layers, however in practice there always seems to be one per stack
|
|
|
|
+ function parseAnimationLayers( FBXTree, connections, curveNodesMap ) {
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ var rawLayers = FBXTree.Objects.subNodes.AnimationLayer;
|
|
|
|
|
|
var layersMap = new Map();
|
|
var layersMap = new Map();
|
|
|
|
|
|
for ( var nodeID in rawLayers ) {
|
|
for ( var nodeID in rawLayers ) {
|
|
|
|
|
|
- var layer = [];
|
|
|
|
|
|
+ var layerCurveNodes = [];
|
|
|
|
|
|
var connection = connections.get( parseInt( nodeID ) );
|
|
var connection = connections.get( parseInt( nodeID ) );
|
|
|
|
|
|
if ( connection !== undefined ) {
|
|
if ( connection !== undefined ) {
|
|
|
|
|
|
|
|
+ // all the animationCurveNodes used in the layer
|
|
var children = connection.children;
|
|
var children = connection.children;
|
|
|
|
|
|
- for ( var childIndex = 0; childIndex < children.length; childIndex ++ ) {
|
|
|
|
-
|
|
|
|
- // Skip lockInfluenceWeights
|
|
|
|
- if ( curveNodesMap.has( children[ childIndex ].ID ) ) {
|
|
|
|
-
|
|
|
|
- var curveNode = curveNodesMap.get( children[ childIndex ].ID );
|
|
|
|
- var modelIndex = curveNode.modelIndex;
|
|
|
|
-
|
|
|
|
- if ( layer[ modelIndex ] === undefined ) {
|
|
|
|
-
|
|
|
|
- layer[ modelIndex ] = {
|
|
|
|
- T: null,
|
|
|
|
- R: null,
|
|
|
|
- S: null
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- layer[ modelIndex ][ curveNode.attr ] = curveNode;
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
|
|
+ children.forEach( function ( child, i ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ if ( curveNodesMap.has( child.ID ) ) {
|
|
|
|
|
|
- layersMap.set( parseInt( nodeID ), layer );
|
|
|
|
|
|
+ var curveNode = curveNodesMap.get( child.ID );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ if ( layerCurveNodes[ i ] === undefined ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ var modelID = connections.get( child.ID ).parents[ 1 ].ID;
|
|
|
|
|
|
- for ( var nodeID in rawStacks ) {
|
|
|
|
|
|
+ var rawModel = FBXTree.Objects.subNodes.Model[ modelID.toString() ];
|
|
|
|
|
|
- var layers = [];
|
|
|
|
- var children = connections.get( parseInt( nodeID ) ).children;
|
|
|
|
- var timestamps = { max: 0, min: Number.MAX_VALUE };
|
|
|
|
|
|
+ var node = {
|
|
|
|
|
|
- for ( var childIndex = 0; childIndex < children.length; ++ childIndex ) {
|
|
|
|
|
|
+ modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ),
|
|
|
|
+ initialPosition: [ 0, 0, 0 ],
|
|
|
|
+ initialRotation: [ 0, 0, 0 ],
|
|
|
|
+ initialScale: [ 1, 1, 1 ],
|
|
|
|
|
|
- var currentLayer = layersMap.get( children[ childIndex ].ID );
|
|
|
|
-
|
|
|
|
- if ( currentLayer !== undefined ) {
|
|
|
|
|
|
+ };
|
|
|
|
|
|
- layers.push( currentLayer );
|
|
|
|
|
|
+ if ( 'Lcl_Translation' in rawModel.properties ) node.initialPosition = rawModel.properties.Lcl_Translation.value;
|
|
|
|
|
|
- for ( var currentLayerIndex = 0, currentLayerLength = currentLayer.length; currentLayerIndex < currentLayerLength; ++ currentLayerIndex ) {
|
|
|
|
|
|
+ if ( 'Lcl_Rotation' in rawModel.properties ) node.initialRotation = rawModel.properties.Lcl_Rotation.value;
|
|
|
|
|
|
- var layer = currentLayer[ currentLayerIndex ];
|
|
|
|
|
|
+ if ( 'Lcl_Scaling' in rawModel.properties ) node.initialScale = rawModel.properties.Lcl_Scaling.value;
|
|
|
|
|
|
- if ( layer ) {
|
|
|
|
|
|
+ // if the animated model is pre rotated, we'll have to apply the pre rotations to every
|
|
|
|
+ // animation value as well
|
|
|
|
+ if ( 'PreRotation' in rawModel.properties ) node.preRotations = rawModel.properties.PreRotation.value;
|
|
|
|
|
|
- getCurveNodeMaxMinTimeStamps( layer, timestamps );
|
|
|
|
|
|
+ layerCurveNodes[ i ] = node;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Do we have an animation clip with actual length?
|
|
|
|
- if ( timestamps.max > timestamps.min ) {
|
|
|
|
|
|
+ layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
|
|
|
|
|
|
- animations.takes[ nodeID ] = {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- name: rawStacks[ nodeID ].attrName,
|
|
|
|
- layers: layers,
|
|
|
|
- length: timestamps.max - timestamps.min,
|
|
|
|
- frames: ( timestamps.max - timestamps.min ) * animations.fps
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ layersMap.set( parseInt( nodeID ), layerCurveNodes );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- return animations;
|
|
|
|
|
|
+ return layersMap;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- // parse a node in FBXTree.Objects.subNodes.AnimationCurveNode
|
|
|
|
- function parseAnimationCurveNode( FBXTree, animationCurveNode, connections, modelsArray ) {
|
|
|
|
-
|
|
|
|
- var rawModels = FBXTree.Objects.subNodes.Model;
|
|
|
|
-
|
|
|
|
- var returnObject = {
|
|
|
|
-
|
|
|
|
- id: animationCurveNode.id,
|
|
|
|
- attr: animationCurveNode.attrName,
|
|
|
|
- modelIndex: - 1,
|
|
|
|
- curves: {
|
|
|
|
- x: null,
|
|
|
|
- y: null,
|
|
|
|
- z: null
|
|
|
|
- },
|
|
|
|
- preRotations: null,
|
|
|
|
-
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- if ( returnObject.attr.match( /S|R|T/ ) === null ) return null;
|
|
|
|
-
|
|
|
|
- // get a list of parents - one of these will be the model being animated by this curve
|
|
|
|
- var parents = connections.get( returnObject.id ).parents;
|
|
|
|
|
|
+ // parse nodes in FBXTree.Objects.subNodes.AnimationStack. These are the top level node in the animation
|
|
|
|
+ // hierarchy. Each Stack node will be used to create a THREE.AnimationClip
|
|
|
|
+ function parseAnimStacks( FBXTree, connections, layersMap ) {
|
|
|
|
|
|
- for ( var i = parents.length - 1; i >= 0; -- i ) {
|
|
|
|
-
|
|
|
|
- // the index of the model in the modelsArray
|
|
|
|
- var modelIndex = findIndex( modelsArray, function ( model ) {
|
|
|
|
|
|
+ var rawStacks = FBXTree.Objects.subNodes.AnimationStack;
|
|
|
|
|
|
- return model.FBX_ID === parents[ i ].ID;
|
|
|
|
|
|
+ // connect the stacks (clips) up to the layers
|
|
|
|
+ var rawClips = {};
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ for ( var nodeID in rawStacks ) {
|
|
|
|
|
|
- if ( modelIndex > - 1 ) {
|
|
|
|
|
|
+ var children = connections.get( parseInt( nodeID ) ).children;
|
|
|
|
|
|
- returnObject.modelIndex = modelIndex;
|
|
|
|
|
|
+ if ( children.length > 1 ) {
|
|
|
|
|
|
- var model = rawModels[ parents[ i ].ID.toString() ];
|
|
|
|
|
|
+ // it seems like stacks will always be associated with a single layer. But just in case there are files
|
|
|
|
+ // where there are multiple layers per stack, we'll display a warning
|
|
|
|
+ console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' );
|
|
|
|
|
|
- //if the animated model is pre rotated, we'll have to apply the pre rotations to every
|
|
|
|
- // animation value as well
|
|
|
|
- if ( 'PreRotation' in model.properties ) {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- returnObject.preRotations = model.properties.PreRotation.value;
|
|
|
|
|
|
+ var layer = layersMap.get( children[ 0 ].ID );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ rawClips[ nodeID ] = {
|
|
|
|
|
|
- break;
|
|
|
|
|
|
+ name: rawStacks[ nodeID ].attrName,
|
|
|
|
+ layer: layer,
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ };
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- return returnObject;
|
|
|
|
|
|
+ return rawClips;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- // parse single node in FBXTree.Objects.subNodes.AnimationCurve
|
|
|
|
- function parseAnimationCurve( animationCurve ) {
|
|
|
|
|
|
+ // take raw animation data from parseAnimations and connect it up to the loaded models
|
|
|
|
+ function addAnimations( FBXTree, connections, sceneGraph ) {
|
|
|
|
|
|
- return {
|
|
|
|
|
|
+ sceneGraph.animations = [];
|
|
|
|
|
|
- id: animationCurve.id,
|
|
|
|
- times: animationCurve.subNodes.KeyTime.properties.a.map( convertFBXTimeToSeconds ),
|
|
|
|
- values: animationCurve.subNodes.KeyValueFloat.properties.a,
|
|
|
|
|
|
+ var rawClips = parseAnimations( FBXTree, connections );
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ if ( rawClips === undefined ) return;
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ for ( var key in rawClips ) {
|
|
|
|
|
|
- function getFrameRate( FBXTree ) {
|
|
|
|
-
|
|
|
|
- var fps = 30; // default framerate
|
|
|
|
-
|
|
|
|
- if ( 'GlobalSettings' in FBXTree && 'TimeMode' in FBXTree.GlobalSettings.properties ) {
|
|
|
|
-
|
|
|
|
- /* Autodesk time mode documentation can be found here:
|
|
|
|
- * http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/class_fbx_time.html,topicNumber=cpp_ref_class_fbx_time_html
|
|
|
|
- */
|
|
|
|
- var timeModeEnum = [
|
|
|
|
- 30, // 0: eDefaultMode
|
|
|
|
- 120, // 1: eFrames120
|
|
|
|
- 100, // 2: eFrames100
|
|
|
|
- 60, // 3: eFrames60
|
|
|
|
- 50, // 4: eFrames50
|
|
|
|
- 48, // 5: eFrames48
|
|
|
|
- 30, // 6: eFrames30 (black and white NTSC )
|
|
|
|
- 30, // 7: eFrames30Drop
|
|
|
|
- 29.97, // 8: eNTSCDropFrame
|
|
|
|
- 29.97, // 90: eNTSCFullFrame
|
|
|
|
- 25, // 10: ePal ( PAL/SECAM )
|
|
|
|
- 24, // 11: eFrames24 (Film/Cinema)
|
|
|
|
- 1, // 12: eFrames1000 (use for date time))
|
|
|
|
- 23.976, // 13: eFilmFullFrame
|
|
|
|
- 30, // 14: eCustom: use GlobalSettings.properties.CustomFrameRate.value
|
|
|
|
- 96, // 15: eFrames96
|
|
|
|
- 72, // 16: eFrames72
|
|
|
|
- 59.94, // 17: eFrames59dot94
|
|
|
|
- ];
|
|
|
|
|
|
+ var rawClip = rawClips[ key ];
|
|
|
|
|
|
- var eMode = FBXTree.GlobalSettings.properties.TimeMode.value;
|
|
|
|
|
|
+ var clip = addClip( rawClip );
|
|
|
|
|
|
- if ( eMode === 14 ) {
|
|
|
|
-
|
|
|
|
- if ( 'CustomFrameRate' in FBXTree.GlobalSettings.properties ) {
|
|
|
|
|
|
+ sceneGraph.animations.push( clip );
|
|
|
|
|
|
- fps = FBXTree.GlobalSettings.properties.CustomFrameRate.value;
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- fps = ( fps === - 1 ) ? 30 : fps;
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ function addClip( rawClip ) {
|
|
|
|
|
|
- } else if ( eMode <= 17 ) { // for future proofing - if more eModes get added, they will default to 30fps
|
|
|
|
|
|
+ var tracks = [];
|
|
|
|
|
|
- fps = timeModeEnum[ eMode ];
|
|
|
|
|
|
+ rawClip.layer.forEach( function ( rawTracks ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ tracks = tracks.concat( generateTracks( rawTracks ) );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- return fps;
|
|
|
|
|
|
+ return new THREE.AnimationClip( rawClip.name, - 1, tracks );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- // Sets the maxTimeStamp and minTimeStamp variables if it has timeStamps that are either larger or smaller
|
|
|
|
- // than the max or min respectively.
|
|
|
|
- function getCurveNodeMaxMinTimeStamps( layer, timestamps ) {
|
|
|
|
|
|
+ function generateTracks( rawTracks ) {
|
|
|
|
|
|
- if ( layer.R ) {
|
|
|
|
|
|
+ var tracks = [];
|
|
|
|
|
|
- getCurveMaxMinTimeStamp( layer.R.curves, timestamps );
|
|
|
|
|
|
+ if ( rawTracks.T !== undefined ) {
|
|
|
|
|
|
- }
|
|
|
|
- if ( layer.S ) {
|
|
|
|
-
|
|
|
|
- getCurveMaxMinTimeStamp( layer.S.curves, timestamps );
|
|
|
|
|
|
+ var positionTrack = generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, rawTracks.initialPosition, 'position' );
|
|
|
|
+ if ( positionTrack !== undefined ) tracks.push( positionTrack );
|
|
|
|
|
|
}
|
|
}
|
|
- if ( layer.T ) {
|
|
|
|
-
|
|
|
|
- getCurveMaxMinTimeStamp( layer.T.curves, timestamps );
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Sets the maxTimeStamp and minTimeStamp if one of the curve's time stamps
|
|
|
|
- // exceeds the maximum or minimum.
|
|
|
|
- function getCurveMaxMinTimeStamp( curve, timestamps ) {
|
|
|
|
|
|
|
|
- if ( curve.x ) {
|
|
|
|
|
|
+ if ( rawTracks.R !== undefined ) {
|
|
|
|
|
|
- getCurveAxisMaxMinTimeStamps( curve.x, timestamps );
|
|
|
|
|
|
+ var rotationTrack = generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.initialRotation, rawTracks.preRotations );
|
|
|
|
+ if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
|
|
|
|
|
|
}
|
|
}
|
|
- if ( curve.y ) {
|
|
|
|
|
|
|
|
- getCurveAxisMaxMinTimeStamps( curve.y, timestamps );
|
|
|
|
|
|
+ if ( rawTracks.S !== undefined ) {
|
|
|
|
|
|
- }
|
|
|
|
- if ( curve.z ) {
|
|
|
|
-
|
|
|
|
- getCurveAxisMaxMinTimeStamps( curve.z, timestamps );
|
|
|
|
|
|
+ var scaleTrack = generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, rawTracks.initialScale, 'scale' );
|
|
|
|
+ if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // Sets the maxTimeStamp and minTimeStamp if one of its timestamps exceeds the maximum or minimum.
|
|
|
|
- function getCurveAxisMaxMinTimeStamps( axis, timestamps ) {
|
|
|
|
-
|
|
|
|
- timestamps.max = axis.times[ axis.times.length - 1 ] > timestamps.max ? axis.times[ axis.times.length - 1 ] : timestamps.max;
|
|
|
|
- timestamps.min = axis.times[ 0 ] < timestamps.min ? axis.times[ 0 ] : timestamps.min;
|
|
|
|
|
|
+ return tracks;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- function addAnimations( FBXTree, connections, sceneGraph, modelMap ) {
|
|
|
|
|
|
+ function generateVectorTrack( modelName, curves, initialValue, type ) {
|
|
|
|
|
|
- // create a flattened array of all models and bones in the scene
|
|
|
|
- var modelsArray = Array.from( modelMap.values() );
|
|
|
|
|
|
+ var times = getTimesForAllAxes( curves );
|
|
|
|
+ var values = getKeyframeTrackValues( times, curves, initialValue );
|
|
|
|
|
|
- sceneGraph.animations = [];
|
|
|
|
|
|
+ return new THREE.VectorKeyframeTrack( modelName + '.' + type, times, values );
|
|
|
|
|
|
- var animations = parseAnimations( FBXTree, connections, modelsArray );
|
|
|
|
-
|
|
|
|
- if ( animations === undefined ) return;
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- // 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 = {
|
|
|
|
|
|
+ function generateRotationTrack( modelName, curves, initialValue, preRotations ) {
|
|
|
|
|
|
- bones: modelsArray,
|
|
|
|
|
|
+ if ( curves.x !== undefined ) curves.x.values = curves.x.values.map( THREE.Math.degToRad );
|
|
|
|
+ if ( curves.y !== undefined ) curves.y.values = curves.y.values.map( THREE.Math.degToRad );
|
|
|
|
+ if ( curves.z !== undefined ) curves.z.values = curves.z.values.map( THREE.Math.degToRad );
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ var times = getTimesForAllAxes( curves );
|
|
|
|
+ var values = getKeyframeTrackValues( times, curves, initialValue );
|
|
|
|
|
|
- for ( var key in animations.takes ) {
|
|
|
|
|
|
+ if ( preRotations !== undefined ) {
|
|
|
|
|
|
- var take = animations.takes[ key ];
|
|
|
|
|
|
+ preRotations = preRotations.map( THREE.Math.degToRad );
|
|
|
|
+ preRotations.push( 'ZYX' );
|
|
|
|
|
|
- var clip = addTake( take, animations.fps, modelsArray );
|
|
|
|
-
|
|
|
|
- sceneGraph.animations.push( clip );
|
|
|
|
|
|
+ preRotations = new THREE.Euler().fromArray( preRotations );
|
|
|
|
+ preRotations = new THREE.Quaternion().setFromEuler( preRotations );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function addTake( take, fps, modelsArray ) {
|
|
|
|
|
|
+ var quaternion = new THREE.Quaternion();
|
|
|
|
+ var euler = new THREE.Euler();
|
|
|
|
|
|
- var animationData = {
|
|
|
|
- name: take.name,
|
|
|
|
- fps: fps,
|
|
|
|
- length: take.length,
|
|
|
|
- hierarchy: []
|
|
|
|
- };
|
|
|
|
|
|
+ var quaternionValues = [];
|
|
|
|
|
|
- animationData.hierarchy = modelsArray.map( ( model, i ) => {
|
|
|
|
|
|
+ for ( var i = 0; i < values.length; i += 3 ) {
|
|
|
|
|
|
- var keys = [];
|
|
|
|
|
|
+ euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], 'ZYX' );
|
|
|
|
|
|
- // note: assumes that the animation has been baked at one keyframe per frame
|
|
|
|
- for ( var frame = 0; frame <= take.frames; frame ++ ) {
|
|
|
|
|
|
+ quaternion.setFromEuler( euler );
|
|
|
|
|
|
- var animationNode = take.layers[ 0 ][ i ];
|
|
|
|
- keys.push( generateKey( fps, animationNode, model, frame ) );
|
|
|
|
|
|
+ if ( preRotations !== undefined )quaternion.premultiply( preRotations );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
|
|
|
|
|
|
- return { name: model.name, keys: keys };
|
|
|
|
-
|
|
|
|
- } );
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- return THREE.AnimationClip.parseAnimation( animationData, modelsArray );
|
|
|
|
|
|
+ return new THREE.QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- var euler = new THREE.Euler();
|
|
|
|
- var quaternion = new THREE.Quaternion();
|
|
|
|
-
|
|
|
|
- function generateKey( fps, animationNode, model, frame ) {
|
|
|
|
-
|
|
|
|
- var key = {
|
|
|
|
|
|
+ function getKeyframeTrackValues( times, curves, initialValue ) {
|
|
|
|
|
|
- time: frame / fps,
|
|
|
|
- pos: model.position.toArray(),
|
|
|
|
- rot: model.quaternion.toArray(),
|
|
|
|
- scl: model.scale.toArray(),
|
|
|
|
|
|
+ var prevValue = initialValue;
|
|
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- if ( animationNode === undefined ) return key;
|
|
|
|
-
|
|
|
|
- euler.setFromQuaternion( model.quaternion, 'ZYX', false );
|
|
|
|
-
|
|
|
|
- if ( hasCurve( animationNode, 'T' ) && hasKeyOnFrame( animationNode.T, frame ) ) {
|
|
|
|
-
|
|
|
|
- if ( animationNode.T.curves.x.values[ frame ] ) {
|
|
|
|
-
|
|
|
|
- key.pos[ 0 ] = animationNode.T.curves.x.values[ frame ];
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( animationNode.T.curves.y.values[ frame ] ) {
|
|
|
|
-
|
|
|
|
- key.pos[ 1 ] = animationNode.T.curves.y.values[ frame ];
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( animationNode.T.curves.z.values[ frame ] ) {
|
|
|
|
-
|
|
|
|
- key.pos[ 2 ] = animationNode.T.curves.z.values[ frame ];
|
|
|
|
|
|
+ var values = [];
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
|
|
+ var xIndex = - 1;
|
|
|
|
+ var yIndex = - 1;
|
|
|
|
+ var zIndex = - 1;
|
|
|
|
|
|
- if ( hasCurve( animationNode, 'R' ) && hasKeyOnFrame( animationNode.R, frame ) ) {
|
|
|
|
|
|
+ times.forEach( function ( time ) {
|
|
|
|
|
|
- // Only update the euler's values if rotation is defined for the axis on this frame
|
|
|
|
- if ( animationNode.R.curves.x.values[ frame ] ) {
|
|
|
|
|
|
+ if ( curves.x ) xIndex = curves.x.times.indexOf( time );
|
|
|
|
+ if ( curves.y ) yIndex = curves.y.times.indexOf( time );
|
|
|
|
+ if ( curves.z ) zIndex = curves.z.times.indexOf( time );
|
|
|
|
|
|
- euler.x = animationNode.R.curves.x.values[ frame ];
|
|
|
|
|
|
+ // if there is an x value defined for this frame, use that
|
|
|
|
+ if ( xIndex !== - 1 ) {
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( animationNode.R.curves.y.values[ frame ] ) {
|
|
|
|
-
|
|
|
|
- euler.y = animationNode.R.curves.y.values[ frame ];
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
|
|
+ var xValue = curves.x.values[ xIndex ];
|
|
|
|
+ values.push( xValue );
|
|
|
|
+ prevValue[ 0 ] = xValue;
|
|
|
|
|
|
- if ( animationNode.R.curves.z.values[ frame ] ) {
|
|
|
|
|
|
+ } else {
|
|
|
|
|
|
- euler.z = animationNode.R.curves.z.values[ frame ];
|
|
|
|
|
|
+ // otherwise use the x value from the previous frame
|
|
|
|
+ values.push( prevValue[ 0 ] );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- quaternion.setFromEuler( euler );
|
|
|
|
- key.rot = quaternion.toArray();
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
|
|
+ if ( yIndex !== - 1 ) {
|
|
|
|
|
|
- if ( hasCurve( animationNode, 'S' ) && hasKeyOnFrame( animationNode.S, frame ) ) {
|
|
|
|
|
|
+ var yValue = curves.y.values[ yIndex ];
|
|
|
|
+ values.push( yValue );
|
|
|
|
+ prevValue[ 1 ] = yValue;
|
|
|
|
|
|
- if ( animationNode.T.curves.x.values[ frame ] ) {
|
|
|
|
|
|
+ } else {
|
|
|
|
|
|
- key.scl[ 0 ] = animationNode.S.curves.x.values[ frame ];
|
|
|
|
|
|
+ values.push( prevValue[ 1 ] );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- if ( animationNode.T.curves.y.values[ frame ] ) {
|
|
|
|
|
|
+ if ( zIndex !== - 1 ) {
|
|
|
|
|
|
- key.scl[ 1 ] = animationNode.S.curves.y.values[ frame ];
|
|
|
|
|
|
+ var zValue = curves.z.values[ zIndex ];
|
|
|
|
+ values.push( zValue );
|
|
|
|
+ prevValue[ 2 ] = zValue;
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if ( animationNode.T.curves.z.values[ frame ] ) {
|
|
|
|
|
|
+ } else {
|
|
|
|
|
|
- key.scl[ 2 ] = animationNode.S.curves.z.values[ frame ];
|
|
|
|
|
|
+ values.push( prevValue[ 2 ] );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- return key;
|
|
|
|
|
|
+ return values;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- var AXES = [ 'x', 'y', 'z' ];
|
|
|
|
-
|
|
|
|
- function hasCurve( animationNode, attribute ) {
|
|
|
|
|
|
+ // For all animated objects, times are defined separately for each axis
|
|
|
|
+ // Here we'll combine the times into one sorted array without duplicates
|
|
|
|
+ function getTimesForAllAxes( curves ) {
|
|
|
|
|
|
- if ( animationNode === undefined ) {
|
|
|
|
|
|
+ var times = [];
|
|
|
|
|
|
- return false;
|
|
|
|
|
|
+ // first join together the times for each axis, if defined
|
|
|
|
+ if ( curves.x !== undefined ) times = times.concat( curves.x.times );
|
|
|
|
+ if ( curves.y !== undefined ) times = times.concat( curves.y.times );
|
|
|
|
+ if ( curves.z !== undefined ) times = times.concat( curves.z.times );
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var attributeNode = animationNode[ attribute ];
|
|
|
|
-
|
|
|
|
- if ( ! attributeNode ) {
|
|
|
|
|
|
+ // then sort them and remove duplicates
|
|
|
|
+ times = times.sort( function ( a, b ) {
|
|
|
|
|
|
- return false;
|
|
|
|
|
|
+ return a - b;
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return AXES.every( function ( key ) {
|
|
|
|
|
|
+ } ).filter( function ( elem, index, array ) {
|
|
|
|
|
|
- return attributeNode.curves[ key ] !== null;
|
|
|
|
|
|
+ return array.indexOf( elem ) == index;
|
|
|
|
|
|
} );
|
|
} );
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- function hasKeyOnFrame( attributeNode, frame ) {
|
|
|
|
-
|
|
|
|
- return AXES.every( function ( key ) {
|
|
|
|
-
|
|
|
|
- return attributeNode.curves[ key ].values[ frame ] !== undefined;
|
|
|
|
-
|
|
|
|
- } );
|
|
|
|
|
|
+ return times;
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|