Browse Source

Merge pull request #12710 from looeee/FBXTree_less_animation_keys

FBXLoader refactor parseAnimation and remove unused entities from return object
Mr.doob 7 years ago
parent
commit
a377ca1a71
1 changed files with 118 additions and 132 deletions
  1. 118 132
      examples/js/loaders/FBXLoader.js

+ 118 - 132
examples/js/loaders/FBXLoader.js

@@ -16,10 +16,9 @@
  *	NURBS (Open, Closed and Periodic forms)
  *
  * Needs Support:
- * 	Indexed Buffers
- * 	PreRotation support.
  *	Euler rotation order
  *
+ *
  * FBX format references:
  * 	https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
  *
@@ -2122,10 +2121,13 @@
 	}
 
 	// Parses animation information from nodes in
-	// FBXTree.Objects.subNodes.AnimationCurve ( connected to AnimationCurveNode )
-	// FBXTree.Objects.subNodes.AnimationCurveNode ( connected to AnimationLayer and an animated property in some other node )
-	// FBXTree.Objects.subNodes.AnimationLayer ( connected to AnimationStack )
+	// 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, sceneGraph ) {
 
 		var rawNodes = FBXTree.Objects.subNodes.AnimationCurveNode;
@@ -2133,61 +2135,9 @@
 		var rawLayers = FBXTree.Objects.subNodes.AnimationLayer;
 		var rawStacks = FBXTree.Objects.subNodes.AnimationStack;
 
-		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 eMode = FBXTree.GlobalSettings.properties.TimeMode.value;
-
-			if ( eMode === 14 ) {
-
-				if ( 'CustomFrameRate' in FBXTree.GlobalSettings.properties ) {
-
-					fps = FBXTree.GlobalSettings.properties.CustomFrameRate.value;
-
-					fps = ( fps === - 1 ) ? 30 : fps;
-
-				}
-
-			} else if ( eMode <= 17 ) { // for future proofing - if more eModes get added, they will default to 30fps
-
-				fps = timeModeEnum[ eMode ];
-
-			}
-
-		}
-
-		var returnObject = {
-			curves: new Map(),
-			layers: {},
-			stacks: {},
-			length: 0,
-			fps: fps,
-			frames: 0
+		var animations = {
+			takes: {},
+			fps: getFrameRate( FBXTree ),
 		};
 
 		var animationCurveNodes = [];
@@ -2195,7 +2145,7 @@
 
 			if ( nodeID.match( /\d+/ ) ) {
 
-				var animationNode = parseAnimationNode( FBXTree, rawNodes[ nodeID ], connections, sceneGraph );
+				var animationNode = parseAnimationCurveNode( FBXTree, rawNodes[ nodeID ], connections, sceneGraph );
 				animationCurveNodes.push( animationNode );
 
 			}
@@ -2257,14 +2207,6 @@
 
 		tmpMap.forEach( function ( curveNode ) {
 
-			var id = curveNode.containerBoneID;
-			if ( ! returnObject.curves.has( id ) ) {
-
-				returnObject.curves.set( id, { T: null, R: null, S: null } );
-
-			}
-			returnObject.curves.get( id )[ curveNode.attr ] = curveNode;
-
 			if ( curveNode.attr === 'R' ) {
 
 				var curves = curveNode.curves;
@@ -2330,6 +2272,8 @@
 
 		} );
 
+		var layersMap = new Map();
+
 		for ( var nodeID in rawLayers ) {
 
 			var layer = [];
@@ -2363,7 +2307,7 @@
 
 				}
 
-				returnObject.layers[ nodeID ] = layer;
+				layersMap.set( parseInt( nodeID ), layer );
 
 			}
 
@@ -2377,7 +2321,7 @@
 
 			for ( var childIndex = 0; childIndex < children.length; ++ childIndex ) {
 
-				var currentLayer = returnObject.layers[ children[ childIndex ].ID ];
+				var currentLayer = layersMap.get( children[ childIndex ].ID );
 
 				if ( currentLayer !== undefined ) {
 
@@ -2402,22 +2346,22 @@
 			// Do we have an animation clip with actual length?
 			if ( timestamps.max > timestamps.min ) {
 
-				returnObject.stacks[ nodeID ] = {
+				animations.takes[ nodeID ] = {
 					name: rawStacks[ nodeID ].attrName,
 					layers: layers,
 					length: timestamps.max - timestamps.min,
-					frames: ( timestamps.max - timestamps.min ) * returnObject.fps
+					frames: ( timestamps.max - timestamps.min ) * animations.fps
 				};
 
 			}
 
 		}
 
-		return returnObject;
+		return animations;
 
 	}
 
-	function parseAnimationNode( FBXTree, animationCurveNode, connections, sceneGraph ) {
+	function parseAnimationCurveNode( FBXTree, animationCurveNode, connections, sceneGraph ) {
 
 		var rawModels = FBXTree.Objects.subNodes.Model;
 
@@ -2483,6 +2427,7 @@
 				returnObject.containerBoneID = boneID;
 				returnObject.containerID = containerIndices[ containerIndicesIndex ].ID;
 				var model = rawModels[ returnObject.containerID.toString() ];
+
 				if ( 'PreRotation' in model.properties ) {
 
 					returnObject.preRotations = new THREE.Vector3().fromArray( model.properties.PreRotation.value ).multiplyScalar( Math.PI / 180 );
@@ -2513,6 +2458,60 @@
 
 	}
 
+	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 eMode = FBXTree.GlobalSettings.properties.TimeMode.value;
+
+			if ( eMode === 14 ) {
+
+				if ( 'CustomFrameRate' in FBXTree.GlobalSettings.properties ) {
+
+					fps = FBXTree.GlobalSettings.properties.CustomFrameRate.value;
+
+					fps = ( fps === - 1 ) ? 30 : fps;
+
+				}
+
+			} else if ( eMode <= 17 ) { // for future proofing - if more eModes get added, they will default to 30fps
+
+				fps = timeModeEnum[ eMode ];
+
+			}
+
+		}
+
+		return fps;
+
+	}
+
 	// 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 ) {
@@ -2575,16 +2574,16 @@
 
 		}
 
-		var stacks = animations.stacks;
+		var takes = animations.takes;
 
-		for ( var key in stacks ) {
+		for ( var key in takes ) {
 
-			var stack = stacks[ key ];
+			var take = takes[ key ];
 
 			var animationData = {
-				name: stack.name,
+				name: take.name,
 				fps: animations.fps,
-				length: stack.length,
+				length: take.length,
 				hierarchy: []
 			};
 
@@ -2604,14 +2603,14 @@
 
 			}
 
-			for ( var frame = 0; frame <= stack.frames; frame ++ ) {
+			for ( var frame = 0; frame <= take.frames; frame ++ ) {
 
 				for ( var bonesIndex = 0, bonesLength = bones.length; bonesIndex < bonesLength; ++ bonesIndex ) {
 
 					var bone = bones[ bonesIndex ];
 					var boneIndex = bonesIndex;
 
-					var animationNode = stack.layers[ 0 ][ boneIndex ];
+					var animationNode = take.layers[ 0 ][ boneIndex ];
 
 					for ( var hierarchyIndex = 0, hierarchyLength = animationData.hierarchy.length; hierarchyIndex < hierarchyLength; ++ hierarchyIndex ) {
 
@@ -2641,93 +2640,86 @@
 	function generateKey( animations, animationNode, bone, frame ) {
 
 		var key = {
+
 			time: frame / animations.fps,
 			pos: bone.position.toArray(),
 			rot: bone.quaternion.toArray(),
-			scl: bone.scale.toArray()
+			scl: bone.scale.toArray(),
+
 		};
 
 		if ( animationNode === undefined ) return key;
 
 		euler.setFromQuaternion( bone.quaternion, 'ZYX', false );
 
-		try {
 
-			if ( hasCurve( animationNode, 'T' ) && hasKeyOnFrame( animationNode.T, frame ) ) {
+		if ( hasCurve( animationNode, 'T' ) && hasKeyOnFrame( animationNode.T, frame ) ) {
 
-				if ( animationNode.T.curves.x.values[ frame ] ) {
+			if ( animationNode.T.curves.x.values[ frame ] ) {
 
-					key.pos[ 0 ] = 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.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 ];
+			if ( animationNode.T.curves.z.values[ frame ] ) {
 
-				}
+				key.pos[ 2 ] = animationNode.T.curves.z.values[ frame ];
 
 			}
 
-			if ( hasCurve( animationNode, 'R' ) && hasKeyOnFrame( animationNode.R, frame ) ) {
-
-				// Only update the euler's values if rotation is defined for the axis on this frame
-				if ( animationNode.R.curves.x.values[ frame ] ) {
+		}
 
-					euler.x = animationNode.R.curves.x.values[ frame ];
+		if ( hasCurve( animationNode, 'R' ) && hasKeyOnFrame( animationNode.R, frame ) ) {
 
-				}
+			// Only update the euler's values if rotation is defined for the axis on this frame
+			if ( animationNode.R.curves.x.values[ frame ] ) {
 
-				if ( animationNode.R.curves.y.values[ frame ] ) {
+				euler.x = animationNode.R.curves.x.values[ frame ];
 
-					euler.y = animationNode.R.curves.y.values[ frame ];
+			}
 
-				}
+			if ( animationNode.R.curves.y.values[ frame ] ) {
 
-				if ( animationNode.R.curves.z.values[ frame ] ) {
+				euler.y = animationNode.R.curves.y.values[ frame ];
 
-					euler.z = animationNode.R.curves.z.values[ frame ];
+			}
 
-				}
+			if ( animationNode.R.curves.z.values[ frame ] ) {
 
-				quaternion.setFromEuler( euler );
-				key.rot = quaternion.toArray();
+				euler.z = animationNode.R.curves.z.values[ frame ];
 
 			}
 
-			if ( hasCurve( animationNode, 'S' ) && hasKeyOnFrame( animationNode.S, frame ) ) {
-
-				if ( animationNode.T.curves.x.values[ frame ] ) {
+			quaternion.setFromEuler( euler );
+			key.rot = quaternion.toArray();
 
-					key.scl[ 0 ] = animationNode.S.curves.x.values[ frame ];
-
-				}
+		}
 
-				if ( animationNode.T.curves.y.values[ frame ] ) {
+		if ( hasCurve( animationNode, 'S' ) && hasKeyOnFrame( animationNode.S, frame ) ) {
 
-					key.scl[ 1 ] = animationNode.S.curves.y.values[ frame ];
+			if ( animationNode.T.curves.x.values[ frame ] ) {
 
-				}
+				key.scl[ 0 ] = animationNode.S.curves.x.values[ frame ];
 
-				if ( animationNode.T.curves.z.values[ frame ] ) {
+			}
 
-					key.scl[ 2 ] = animationNode.S.curves.z.values[ frame ];
+			if ( animationNode.T.curves.y.values[ frame ] ) {
 
-				}
+				key.scl[ 1 ] = animationNode.S.curves.y.values[ frame ];
 
 			}
 
-		} catch ( error ) {
+			if ( animationNode.T.curves.z.values[ frame ] ) {
 
-			// Curve is not fully plotted.
-			console.log( 'THREE.FBXLoader: ', bone );
-			console.log( 'THREE.FBXLoader: ', error );
+				key.scl[ 2 ] = animationNode.S.curves.z.values[ frame ];
+
+			}
 
 		}
 
@@ -2765,18 +2757,12 @@
 
 		return AXES.every( function ( key ) {
 
-			return isKeyExistOnFrame( attributeNode.curves[ key ], frame );
+			return attributeNode.curves[ key ].values[ frame ] !== undefined;
 
 		} );
 
 	}
 
-	function isKeyExistOnFrame( curve, frame ) {
-
-		return curve.values[ frame ] !== undefined;
-
-	}
-
 	// parse an FBX file in ASCII format
 	function TextParser() {}