2
0
Эх сурвалжийг харах

Merge pull request #14396 from looeee/FBXLoader_support_animated_morphs

FBXLoader added support for animated morph targets
Mr.doob 7 жил өмнө
parent
commit
57b3ab9bf5

+ 117 - 32
examples/js/loaders/FBXLoader.js

@@ -110,7 +110,7 @@
 			var materials = parseMaterials( FBXTree, textures, connections );
 			var deformers = parseDeformers( FBXTree, connections );
 			var geometryMap = parseGeometries( FBXTree, connections, deformers );
-			var sceneGraph = parseScene( FBXTree, connections, deformers.skeletons, geometryMap, materials );
+			var sceneGraph = parseScene( FBXTree, connections, deformers, geometryMap, materials );
 
 			return sceneGraph;
 
@@ -371,7 +371,7 @@
 
 		var texture;
 
-		if ( textureNode.FileName.slice( -3 ).toLowerCase() === 'tga' ) {
+		if ( textureNode.FileName.slice( - 3 ).toLowerCase() === 'tga' ) {
 
  			texture = THREE.Loader.Handlers.get( '.tga' ).load( fileName );
 
@@ -419,7 +419,7 @@
 		var name = materialNode.attrName;
 		var type = materialNode.ShadingModel;
 
-		//Case where FBX wraps shading model in property object.
+		// Case where FBX wraps shading model in property object.
 		if ( typeof type === 'object' ) {
 
 			type = type.value;
@@ -633,11 +633,10 @@
 						id: nodeID,
 					};
 
-					morphTarget.rawTargets = parseMorphTargets( relationships, deformerNode, DeformerNodes, connections );
+					morphTarget.rawTargets = parseMorphTargets( relationships, DeformerNodes, connections );
 					morphTarget.id = nodeID;
 
 					if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' );
-					morphTarget.parentGeoID = relationships.parents[ 0 ].ID;
 
 					morphTargets[ nodeID ] = morphTarget;
 
@@ -701,7 +700,7 @@
 	}
 
 	// The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel"
-	function parseMorphTargets( relationships, deformerNode, deformerNodes, connections ) {
+	function parseMorphTargets( relationships, deformerNodes, connections ) {
 
 		var rawMorphTargets = [];
 
@@ -734,18 +733,7 @@
 
 			targetRelationships.children.forEach( function ( child ) {
 
-				if ( child.relationship === 'DeformPercent' ) {
-
-					// TODO: animation of morph targets is currently unsupported
-					rawMorphTarget.weightCurveID = child.ID;
-					// weightCurve = FBXTree.Objects.AnimationCurveNode[ weightCurveID ];
-
-				} else {
-
-					rawMorphTarget.geoID = child.ID;
-					// morphGeo = FBXTree.Objects.Geometry[ geoID ];
-
-				}
+				if ( child.relationship === undefined ) rawMorphTarget.geoID = child.ID;
 
 			} );
 
@@ -1626,11 +1614,11 @@
 	}
 
 	// create the main THREE.Group() to be returned by the loader
-	function parseScene( FBXTree, connections, skeletons, geometryMap, materialMap ) {
+	function parseScene( FBXTree, connections, deformers, geometryMap, materialMap ) {
 
 		var sceneGraph = new THREE.Group();
 
-		var modelMap = parseModels( FBXTree, skeletons, geometryMap, materialMap, connections );
+		var modelMap = parseModels( FBXTree, deformers.skeletons, geometryMap, materialMap, connections );
 
 		var modelNodes = FBXTree.Objects.Model;
 
@@ -1657,16 +1645,52 @@
 
 		} );
 
-		bindSkeleton( FBXTree, skeletons, geometryMap, modelMap, connections );
-
+		bindSkeleton( FBXTree, deformers.skeletons, geometryMap, modelMap, connections );
 		addAnimations( FBXTree, connections, sceneGraph );
-
 		createAmbientLight( FBXTree, sceneGraph );
 
+		setupMorphMaterials( sceneGraph );
+
 		return sceneGraph;
 
 	}
 
+	function setupMorphMaterials( sceneGraph ) {
+
+		sceneGraph.traverse( function ( child ) {
+
+			if ( child.isMesh ) {
+
+				if ( child.geometry.morphAttributes.position || child.geometry.morphAttributes.normal ) {
+
+					var uuid = child.uuid;
+					var matUuid = child.material.uuid;
+
+					// if a geometry has morph targets, it cannot share the material with other geometries
+					var sharedMat = false;
+
+					sceneGraph.traverse( function ( child ) {
+
+						if ( child.isMesh ) {
+
+							if ( child.material.uuid === matUuid && child.uuid !== uuid ) sharedMat = true;
+
+						}
+
+					} );
+
+					if ( sharedMat === true ) child.material = child.material.clone();
+
+					child.material.morphTargets = true;
+
+				}
+
+			}
+
+		} );
+
+	}
+
 	// parse nodes in FBXTree.Objects.Model
 	function parseModels( FBXTree, skeletons, geometryMap, materialMap, connections ) {
 
@@ -2273,7 +2297,6 @@
 		var curveNodesMap = parseAnimationCurveNodes( FBXTree );
 
 		parseAnimationCurves( FBXTree, connections, curveNodesMap );
-
 		var layersMap = parseAnimationLayers( FBXTree, connections, curveNodesMap );
 		var rawClips = parseAnimStacks( FBXTree, connections, layersMap );
 
@@ -2294,7 +2317,7 @@
 
 			var rawCurveNode = rawCurveNodes[ nodeID ];
 
-			if ( rawCurveNode.attrName.match( /S|R|T/ ) !== null ) {
+			if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
 
 				var curveNode = {
 
@@ -2321,6 +2344,13 @@
 
 		var rawCurves = FBXTree.Objects.AnimationCurve;
 
+		// TODO: Many values are identical up to roundoff error, but won't be optimised
+		// e.g. position times: [0, 0.4, 0. 8]
+		// position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809]
+		// clearly, this should be optimised to
+		// times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809]
+		// this shows up in nearly every FBX file, and generally time array is length > 100
+
 		for ( var nodeID in rawCurves ) {
 
 			var animationCurve = {
@@ -2350,6 +2380,10 @@
 
 					curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
 
+				} else if ( animationCurveRelationship.match( /d|DeformPercent/ ) ) {
+
+					curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
+
 				}
 
 			}
@@ -2424,9 +2458,40 @@
 
 							layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
 
-						}
+						} else if ( curveNode.curves.morph !== undefined ) {
 
+							if ( layerCurveNodes[ i ] === undefined ) {
+
+								var deformerID;
+
+								connections.get( child.ID ).parents.forEach( function ( parent ) {
+
+									if ( parent.relationship !== undefined ) deformerID = parent.ID;
+
+								} );
+
+								var morpherID = connections.get( deformerID ).parents[ 0 ].ID;
+								var geoID = connections.get( morpherID ).parents[ 0 ].ID;
+
+								// assuming geometry is not used in more than one model
+								var modelID = connections.get( geoID ).parents[ 0 ].ID;
+
+								var rawModel = FBXTree.Objects.Model[ modelID ];
+
+								var node = {
+
+									modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ),
+									morphName: FBXTree.Objects.Deformer[ deformerID ].attrName,
+
+								};
+
+								layerCurveNodes[ i ] = node;
 
+							}
+
+							layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
+
+						}
 
 					}
 
@@ -2478,7 +2543,7 @@
 
 	}
 
-	// take raw animation data from parseAnimations and connect it up to the loaded models
+	// take raw animation clips and turn them into three.js animation clips
 	function addAnimations( FBXTree, connections, sceneGraph ) {
 
 		sceneGraph.animations = [];
@@ -2487,12 +2552,11 @@
 
 		if ( rawClips === undefined ) return;
 
-
 		for ( var key in rawClips ) {
 
 			var rawClip = rawClips[ key ];
 
-			var clip = addClip( rawClip );
+			var clip = addClip( rawClip, sceneGraph );
 
 			sceneGraph.animations.push( clip );
 
@@ -2500,13 +2564,13 @@
 
 	}
 
-	function addClip( rawClip ) {
+	function addClip( rawClip, sceneGraph ) {
 
 		var tracks = [];
 
 		rawClip.layer.forEach( function ( rawTracks ) {
 
-			tracks = tracks.concat( generateTracks( rawTracks ) );
+			tracks = tracks.concat( generateTracks( rawTracks, sceneGraph ) );
 
 		} );
 
@@ -2514,7 +2578,7 @@
 
 	}
 
-	function generateTracks( rawTracks ) {
+	function generateTracks( rawTracks, sceneGraph ) {
 
 		var tracks = [];
 
@@ -2539,6 +2603,12 @@
 
 		}
 
+		if ( rawTracks.DeformPercent !== undefined ) {
+			var morphTrack = generateMorphTrack( rawTracks, sceneGraph );
+			if ( morphTrack !== undefined ) tracks.push( morphTrack );
+
+		}
+
 		return tracks;
 
 	}
@@ -2607,6 +2677,21 @@
 
 	}
 
+	function generateMorphTrack( rawTracks, sceneGraph ) {
+
+		var curves = rawTracks.DeformPercent.curves.morph;
+		var values = curves.values.map( function ( val ) {
+
+			return val / 100;
+
+		} );
+
+		var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ];
+
+		return new THREE.NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values );
+
+	}
+
 	function getKeyframeTrackValues( times, curves, initialValue ) {
 
 		var prevValue = initialValue;