浏览代码

Merge pull request #13856 from looeee/FBXLoader_support_morphtargets

FBXLoader:  support morphtargets
Mr.doob 7 年之前
父节点
当前提交
bc3aa48ed4
共有 1 个文件被更改,包括 411 次插入231 次删除
  1. 411 231
      examples/js/loaders/FBXLoader.js

+ 411 - 231
examples/js/loaders/FBXLoader.js

@@ -8,7 +8,9 @@
  * Versions lower than this may load but will probably have errors
  *
  * Needs Support:
- *  Morph targets / blend shapes
+ *  Morph normals / blend shape normals
+ *  Animation tracks for morph targets
+ *
  *	Euler rotation order
  *
  * FBX format references:
@@ -95,9 +97,9 @@
 			var images = parseImages( FBXTree );
 			var textures = parseTextures( FBXTree, new THREE.TextureLoader( this.manager ).setPath( resourceDirectory ), images, connections );
 			var materials = parseMaterials( FBXTree, textures, connections );
-			var skeletons = parseDeformers( FBXTree, connections );
-			var geometryMap = parseGeometries( FBXTree, connections, skeletons );
-			var sceneGraph = parseScene( FBXTree, connections, skeletons, geometryMap, materials );
+			var deformers = parseDeformers( FBXTree, connections );
+			var geometryMap = parseGeometries( FBXTree, connections, deformers );
+			var sceneGraph = parseScene( FBXTree, connections, deformers.skeletons, geometryMap, materials );
 
 			return sceneGraph;
 
@@ -592,6 +594,7 @@
 	function parseDeformers( FBXTree, connections ) {
 
 		var skeletons = {};
+		var morphTargets = {};
 
 		if ( 'Deformer' in FBXTree.Objects ) {
 
@@ -601,9 +604,9 @@
 
 				var deformerNode = DeformerNodes[ nodeID ];
 
-				if ( deformerNode.attrType === 'Skin' ) {
+				var relationships = connections.get( parseInt( nodeID ) );
 
-					var relationships = connections.get( parseInt( nodeID ) );
+				if ( deformerNode.attrType === 'Skin' ) {
 
 					var skeleton = parseSkeleton( relationships, DeformerNodes );
 					skeleton.ID = nodeID;
@@ -613,18 +616,37 @@
 
 					skeletons[ nodeID ] = skeleton;
 
+				} else if ( deformerNode.attrType === 'BlendShape' ) {
+
+					var morphTarget = {
+						id: nodeID,
+					};
+
+					morphTarget.rawTargets = parseMorphTargets( relationships, deformerNode, DeformerNodes, connections, FBXTree );
+					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;
+
 				}
 
 			}
 
 		}
 
-		return skeletons;
+		return {
+
+			skeletons: skeletons,
+			morphTargets: morphTargets,
+
+		};
 
 	}
 
 	// Parse single nodes in FBXTree.Objects.Deformer
-	// The top level deformer nodes have type 'Skin' and subDeformer nodes have type 'Cluster'
+	// The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster'
 	// Each skin node represents a skeleton and each cluster node represents a bone
 	function parseSkeleton( connections, deformerNodes ) {
 
@@ -632,25 +654,25 @@
 
 		connections.children.forEach( function ( child ) {
 
-			var subDeformerNode = deformerNodes[ child.ID ];
+			var boneNode = deformerNodes[ child.ID ];
 
-			if ( subDeformerNode.attrType !== 'Cluster' ) return;
+			if ( boneNode.attrType !== 'Cluster' ) return;
 
 			var rawBone = {
 
 				ID: child.ID,
 				indices: [],
 				weights: [],
-				transform: new THREE.Matrix4().fromArray( subDeformerNode.Transform.a ),
-				transformLink: new THREE.Matrix4().fromArray( subDeformerNode.TransformLink.a ),
-				linkMode: subDeformerNode.Mode,
+				transform: new THREE.Matrix4().fromArray( boneNode.Transform.a ),
+				transformLink: new THREE.Matrix4().fromArray( boneNode.TransformLink.a ),
+				linkMode: boneNode.Mode,
 
 			};
 
-			if ( 'Indexes' in subDeformerNode ) {
+			if ( 'Indexes' in boneNode ) {
 
-				rawBone.indices = subDeformerNode.Indexes.a;
-				rawBone.weights = subDeformerNode.Weights.a;
+				rawBone.indices = boneNode.Indexes.a;
+				rawBone.weights = boneNode.Weights.a;
 
 			}
 
@@ -667,21 +689,76 @@
 
 	}
 
+	// The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel"
+	function parseMorphTargets( relationships, deformerNode, deformerNodes, connections ) {
+
+		var rawMorphTargets = [];
+
+		for ( var i = 0; i < relationships.children.length; i ++ ) {
+
+			if ( i === 8 ) {
+
+				console.warn( 'FBXLoader: maximum of 8 morph targets supported. Ignoring additional targets.' );
+
+				break;
+
+			}
+
+			var child = relationships.children[ i ];
+
+			var morphTargetNode = deformerNodes[ child.ID ];
+
+			var rawMorphTarget = {
+
+				name: morphTargetNode.attrName,
+				initialWeight: morphTargetNode.DeformPercent,
+				id: morphTargetNode.id,
+				fullWeights: morphTargetNode.FullWeights.a
+
+			};
+
+			if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return;
+
+			var targetRelationships = connections.get( parseInt( child.ID ) );
+
+			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 ];
+
+				}
+
+			} );
+
+			rawMorphTargets.push( rawMorphTarget );
+
+		}
+
+		return rawMorphTargets;
+
+	}
+
 	// Parse nodes in FBXTree.Objects.Geometry
-	function parseGeometries( FBXTree, connections, skeletons ) {
+	function parseGeometries( FBXTree, connections, deformers ) {
 
 		var geometryMap = new Map();
 
 		if ( 'Geometry' in FBXTree.Objects ) {
 
-			var geometryNodes = FBXTree.Objects.Geometry;
-
+			var geoNodes = FBXTree.Objects.Geometry;
 
-
-			for ( var nodeID in geometryNodes ) {
+			for ( var nodeID in geoNodes ) {
 
 				var relationships = connections.get( parseInt( nodeID ) );
-				var geo = parseGeometry( FBXTree, relationships, geometryNodes[ nodeID ], skeletons );
+				var geo = parseGeometry( FBXTree, relationships, geoNodes[ nodeID ], deformers );
 
 				geometryMap.set( parseInt( nodeID ), geo );
 
@@ -694,25 +771,27 @@
 	}
 
 	// Parse single node in FBXTree.Objects.Geometry
-	function parseGeometry( FBXTree, relationships, geometryNode, skeletons ) {
+	function parseGeometry( FBXTree, relationships, geoNode, deformers ) {
 
-		switch ( geometryNode.attrType ) {
+		switch ( geoNode.attrType ) {
 
 			case 'Mesh':
-				return parseMeshGeometry( FBXTree, relationships, geometryNode, skeletons );
+				return parseMeshGeometry( FBXTree, relationships, geoNode, deformers );
 				break;
 
 			case 'NurbsCurve':
-				return parseNurbsGeometry( geometryNode );
+				return parseNurbsGeometry( geoNode );
 				break;
 
 		}
 
 	}
 
-
 	// Parse single node mesh geometry in FBXTree.Objects.Geometry
-	function parseMeshGeometry( FBXTree, relationships, geometryNode, skeletons ) {
+	function parseMeshGeometry( FBXTree, relationships, geoNode, deformers ) {
+
+		var skeletons = deformers.skeletons;
+		var morphTargets = deformers.morphTargets;
 
 		var modelNodes = relationships.parents.map( function ( parent ) {
 
@@ -731,6 +810,14 @@
 
 		}, null );
 
+		var morphTarget = relationships.children.reduce( function ( morphTarget, child ) {
+
+			if ( morphTargets[ child.ID ] !== undefined ) morphTarget = morphTargets[ child.ID ];
+
+			return morphTarget;
+
+		}, null );
+
 		var preTransform = new THREE.Matrix4();
 
 		// TODO: if there is more than one model associated with the geometry, AND the models have
@@ -740,7 +827,6 @@
 		// For now just assume one model and get the preRotations from that
 		var modelNode = modelNodes[ 0 ];
 
-
 		if ( 'GeometricRotation' in modelNode ) {
 
 			var array = modelNode.GeometricRotation.value.map( THREE.Math.degToRad );
@@ -762,68 +848,172 @@
 
 		}
 
-		return genGeometry( FBXTree, relationships, geometryNode, skeleton, preTransform );
+		return genGeometry( FBXTree, geoNode, skeleton, morphTarget, preTransform );
 
 	}
 
 	// Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry
-	function genGeometry( FBXTree, relationships, geometryNode, skeleton, preTransform ) {
+	function genGeometry( FBXTree, geoNode, skeleton, morphTarget, preTransform ) {
+
+		var geo = new THREE.BufferGeometry();
+		if ( geoNode.attrName ) geo.name = geoNode.attrName;
+
+		var geoInfo = getGeoInfo( geoNode, skeleton );
+
+		var buffers = genBuffers( geoInfo );
+
+		var positionAttribute = new THREE.Float32BufferAttribute( buffers.vertex, 3 );
+
+		preTransform.applyToBufferAttribute( positionAttribute );
+
+		geo.addAttribute( 'position', positionAttribute );
+
+		if ( buffers.colors.length > 0 ) {
+
+			geo.addAttribute( 'color', new THREE.Float32BufferAttribute( buffers.colors, 3 ) );
+
+		}
+
+		if ( skeleton ) {
+
+			geo.addAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( buffers.weightsIndices, 4 ) );
+
+			geo.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( buffers.vertexWeights, 4 ) );
+
+			// used later to bind the skeleton to the model
+			geo.FBX_Deformer = skeleton;
+
+		}
+
+		if ( buffers.normal.length > 0 ) {
+
+			var normalAttribute = new THREE.Float32BufferAttribute( buffers.normal, 3 );
+
+			var normalMatrix = new THREE.Matrix3().getNormalMatrix( preTransform );
+			normalMatrix.applyToBufferAttribute( normalAttribute );
+
+			geo.addAttribute( 'normal', normalAttribute );
+
+		}
+
+		buffers.uvs.forEach( function ( uvBuffer, i ) {
+
+			// subsequent uv buffers are called 'uv1', 'uv2', ...
+			var name = 'uv' + ( i + 1 ).toString();
+
+			// the first uv buffer is just called 'uv'
+			if ( i === 0 ) {
+
+				name = 'uv';
+
+			}
+
+			geo.addAttribute( name, new THREE.Float32BufferAttribute( buffers.uvs[ i ], 2 ) );
+
+		} );
+
+		if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+
+			// Convert the material indices of each vertex into rendering groups on the geometry.
+			var prevMaterialIndex = buffers.materialIndex[ 0 ];
+			var startIndex = 0;
+
+			buffers.materialIndex.forEach( function ( currentIndex, i ) {
+
+				if ( currentIndex !== prevMaterialIndex ) {
+
+					geo.addGroup( startIndex, i - startIndex, prevMaterialIndex );
+
+					prevMaterialIndex = currentIndex;
+					startIndex = i;
+
+				}
+
+			} );
+
+			// the loop above doesn't add the last group, do that here.
+			if ( geo.groups.length > 0 ) {
 
-		var vertexPositions = ( geometryNode.Vertices !== undefined ) ? geometryNode.Vertices.a : [];
-		var vertexIndices = ( geometryNode.PolygonVertexIndex !== undefined ) ? geometryNode.PolygonVertexIndex.a : [];
+				var lastGroup = geo.groups[ geo.groups.length - 1 ];
+				var lastIndex = lastGroup.start + lastGroup.count;
+
+				if ( lastIndex !== buffers.materialIndex.length ) {
+
+					geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex );
+
+				}
+
+			}
+
+			// case where there are multiple materials but the whole geometry is only
+			// using one of them
+			if ( geo.groups.length === 0 ) {
+
+				geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] );
+
+			}
 
-		// create arrays to hold the final data used to build the buffergeometry
-		var vertexBuffer = [];
-		var normalBuffer = [];
-		var colorsBuffer = [];
-		var uvsBuffer = [];
-		var materialIndexBuffer = [];
-		var vertexWeightsBuffer = [];
-		var weightsIndicesBuffer = [];
+		}
+
+		addMorphTargets( FBXTree, geo, geoNode, morphTarget, preTransform );
+
+		return geo;
+
+	}
+
+	function getGeoInfo( geoNode, skeleton ) {
+
+		var geoInfo = {};
 
-		if ( geometryNode.LayerElementColor ) {
+		geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : [];
+		geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : [];
 
-			var colorInfo = getColors( geometryNode.LayerElementColor[ 0 ] );
+		if ( geoNode.LayerElementColor ) {
+
+			geoInfo.color = getColors( geoNode.LayerElementColor[ 0 ] );
 
 		}
 
-		if ( geometryNode.LayerElementMaterial ) {
+		if ( geoNode.LayerElementMaterial ) {
 
-			var materialInfo = getMaterials( geometryNode.LayerElementMaterial[ 0 ] );
+			geoInfo.material = getMaterials( geoNode.LayerElementMaterial[ 0 ] );
 
 		}
 
-		if ( geometryNode.LayerElementNormal ) {
+		if ( geoNode.LayerElementNormal ) {
 
-			var normalInfo = getNormals( geometryNode.LayerElementNormal[ 0 ] );
+			geoInfo.normal = getNormals( geoNode.LayerElementNormal[ 0 ] );
 
 		}
 
-		if ( geometryNode.LayerElementUV ) {
+		if ( geoNode.LayerElementUV ) {
+
+			geoInfo.uv = [];
 
-			var uvInfo = [];
 			var i = 0;
-			while ( geometryNode.LayerElementUV[ i ] ) {
+			while ( geoNode.LayerElementUV[ i ] ) {
 
-				uvInfo.push( getUVs( geometryNode.LayerElementUV[ i ] ) );
+				geoInfo.uv.push( getUVs( geoNode.LayerElementUV[ i ] ) );
 				i ++;
 
 			}
 
 		}
 
-		var weightTable = {};
+		geoInfo.weightTable = {};
 
 		if ( skeleton !== null ) {
 
+			geoInfo.skeleton = skeleton;
+
 			skeleton.rawBones.forEach( function ( rawBone, i ) {
 
 				// loop over the bone's vertex indices and weights
 				rawBone.indices.forEach( function ( index, j ) {
 
-					if ( weightTable[ index ] === undefined ) weightTable[ index ] = [];
+					if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = [];
 
-					weightTable[ index ].push( {
+					geoInfo.weightTable[ index ].push( {
 
 						id: i,
 						weight: rawBone.weights[ j ],
@@ -836,19 +1026,35 @@
 
 		}
 
+		return geoInfo;
+
+	}
+
+	function genBuffers( geoInfo ) {
+
+		var buffers = {
+			vertex: [],
+			normal: [],
+			colors: [],
+			uvs: [],
+			materialIndex: [],
+			vertexWeights: [],
+			weightsIndices: [],
+		};
+
 		var polygonIndex = 0;
 		var faceLength = 0;
 		var displayedWeightsWarning = false;
 
 		// these will hold data for a single face
-		var vertexPositionIndexes = [];
+		var facePositionIndexes = [];
 		var faceNormals = [];
 		var faceColors = [];
 		var faceUVs = [];
 		var faceWeights = [];
 		var faceWeightIndices = [];
 
-		vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) {
+		geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) {
 
 			var endOfFace = false;
 
@@ -858,11 +1064,10 @@
 			//  a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5
 			//  }
 			// Negative numbers mark the end of a face - first face here is 0, 1, 3, -3
-			// to find index of last vertex multiply by -1 and subtract 1: -3 * - 1 - 1 = 2
+			// to find index of last vertex bit shift the index: ^ - 1
 			if ( vertexIndex < 0 ) {
 
 				vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1
-				vertexIndices[ polygonVertexIndex ] = vertexIndex;
 				endOfFace = true;
 
 			}
@@ -870,21 +1075,21 @@
 			var weightIndices = [];
 			var weights = [];
 
-			vertexPositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 );
+			facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 );
 
-			if ( colorInfo ) {
+			if ( geoInfo.color ) {
 
-				var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, colorInfo );
+				var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color );
 
 				faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] );
 
 			}
 
-			if ( skeleton ) {
+			if ( geoInfo.skeleton ) {
 
-				if ( weightTable[ vertexIndex ] !== undefined ) {
+				if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) {
 
-					weightTable[ vertexIndex ].forEach( function ( wt ) {
+					geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) {
 
 						weights.push( wt.weight );
 						weightIndices.push( wt.id );
@@ -950,23 +1155,23 @@
 
 			}
 
-			if ( normalInfo ) {
+			if ( geoInfo.normal ) {
 
-				var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, normalInfo );
+				var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal );
 
 				faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] );
 
 			}
 
-			if ( materialInfo && materialInfo.mappingType !== 'AllSame' ) {
+			if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
 
-				var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, materialInfo )[ 0 ];
+				var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ];
 
 			}
 
-			if ( uvInfo ) {
+			if ( geoInfo.uv ) {
 
-				uvInfo.forEach( function ( uv, i ) {
+				geoInfo.uv.forEach( function ( uv, i ) {
 
 					var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv );
 
@@ -985,235 +1190,210 @@
 
 			faceLength ++;
 
-			// we have reached the end of a face - it may have 4 sides though
-			// in which case the data is split to represent two 3 sided faces
 			if ( endOfFace ) {
 
-				for ( var i = 2; i < faceLength; i ++ ) {
-
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ 0 ] ] );
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ 1 ] ] );
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ 2 ] ] );
-
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ ( i - 1 ) * 3 ] ] );
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ ( i - 1 ) * 3 + 1 ] ] );
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ ( i - 1 ) * 3 + 2 ] ] );
+				genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength );
 
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ i * 3 ] ] );
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ i * 3 + 1 ] ] );
-					vertexBuffer.push( vertexPositions[ vertexPositionIndexes[ i * 3 + 2 ] ] );
-
-					if ( skeleton ) {
-
-						vertexWeightsBuffer.push( faceWeights[ 0 ] );
-						vertexWeightsBuffer.push( faceWeights[ 1 ] );
-						vertexWeightsBuffer.push( faceWeights[ 2 ] );
-						vertexWeightsBuffer.push( faceWeights[ 3 ] );
+				polygonIndex ++;
+				faceLength = 0;
 
-						vertexWeightsBuffer.push( faceWeights[ ( i - 1 ) * 4 ] );
-						vertexWeightsBuffer.push( faceWeights[ ( i - 1 ) * 4 + 1 ] );
-						vertexWeightsBuffer.push( faceWeights[ ( i - 1 ) * 4 + 2 ] );
-						vertexWeightsBuffer.push( faceWeights[ ( i - 1 ) * 4 + 3 ] );
+				// reset arrays for the next face
+				facePositionIndexes = [];
+				faceNormals = [];
+				faceColors = [];
+				faceUVs = [];
+				faceWeights = [];
+				faceWeightIndices = [];
 
-						vertexWeightsBuffer.push( faceWeights[ i * 4 ] );
-						vertexWeightsBuffer.push( faceWeights[ i * 4 + 1 ] );
-						vertexWeightsBuffer.push( faceWeights[ i * 4 + 2 ] );
-						vertexWeightsBuffer.push( faceWeights[ i * 4 + 3 ] );
+			}
 
-						weightsIndicesBuffer.push( faceWeightIndices[ 0 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ 1 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ 2 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ 3 ] );
+		} );
 
-						weightsIndicesBuffer.push( faceWeightIndices[ ( i - 1 ) * 4 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] );
+		return buffers;
 
-						weightsIndicesBuffer.push( faceWeightIndices[ i * 4 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ i * 4 + 1 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ i * 4 + 2 ] );
-						weightsIndicesBuffer.push( faceWeightIndices[ i * 4 + 3 ] );
+	}
 
-					}
+	// Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris
+	function genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) {
 
-					if ( colorInfo ) {
+		for ( var i = 2; i < faceLength; i ++ ) {
 
-						colorsBuffer.push( faceColors[ 0 ] );
-						colorsBuffer.push( faceColors[ 1 ] );
-						colorsBuffer.push( faceColors[ 2 ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] );
 
-						colorsBuffer.push( faceColors[ ( i - 1 ) * 3 ] );
-						colorsBuffer.push( faceColors[ ( i - 1 ) * 3 + 1 ] );
-						colorsBuffer.push( faceColors[ ( i - 1 ) * 3 + 2 ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] );
 
-						colorsBuffer.push( faceColors[ i * 3 ] );
-						colorsBuffer.push( faceColors[ i * 3 + 1 ] );
-						colorsBuffer.push( faceColors[ i * 3 + 2 ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] );
+			buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] );
 
-					}
+			if ( geoInfo.skeleton ) {
 
-					if ( materialInfo && materialInfo.mappingType !== 'AllSame' ) {
+				buffers.vertexWeights.push( faceWeights[ 0 ] );
+				buffers.vertexWeights.push( faceWeights[ 1 ] );
+				buffers.vertexWeights.push( faceWeights[ 2 ] );
+				buffers.vertexWeights.push( faceWeights[ 3 ] );
 
-						materialIndexBuffer.push( materialIndex );
-						materialIndexBuffer.push( materialIndex );
-						materialIndexBuffer.push( materialIndex );
+				buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] );
+				buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] );
+				buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] );
+				buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] );
 
-					}
+				buffers.vertexWeights.push( faceWeights[ i * 4 ] );
+				buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] );
+				buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] );
+				buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] );
 
-					if ( normalInfo ) {
+				buffers.weightsIndices.push( faceWeightIndices[ 0 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ 1 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ 2 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ 3 ] );
 
-						normalBuffer.push( faceNormals[ 0 ] );
-						normalBuffer.push( faceNormals[ 1 ] );
-						normalBuffer.push( faceNormals[ 2 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] );
 
-						normalBuffer.push( faceNormals[ ( i - 1 ) * 3 ] );
-						normalBuffer.push( faceNormals[ ( i - 1 ) * 3 + 1 ] );
-						normalBuffer.push( faceNormals[ ( i - 1 ) * 3 + 2 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] );
+				buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] );
 
-						normalBuffer.push( faceNormals[ i * 3 ] );
-						normalBuffer.push( faceNormals[ i * 3 + 1 ] );
-						normalBuffer.push( faceNormals[ i * 3 + 2 ] );
+			}
 
-					}
+			if ( geoInfo.color ) {
 
-					if ( uvInfo ) {
+				buffers.colors.push( faceColors[ 0 ] );
+				buffers.colors.push( faceColors[ 1 ] );
+				buffers.colors.push( faceColors[ 2 ] );
 
-						uvInfo.forEach( function ( uv, j ) {
+				buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] );
+				buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] );
+				buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] );
 
-							if ( uvsBuffer[ j ] === undefined ) uvsBuffer[ j ] = [];
+				buffers.colors.push( faceColors[ i * 3 ] );
+				buffers.colors.push( faceColors[ i * 3 + 1 ] );
+				buffers.colors.push( faceColors[ i * 3 + 2 ] );
 
-							uvsBuffer[ j ].push( faceUVs[ j ][ 0 ] );
-							uvsBuffer[ j ].push( faceUVs[ j ][ 1 ] );
+			}
 
-							uvsBuffer[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] );
-							uvsBuffer[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] );
+			if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
 
-							uvsBuffer[ j ].push( faceUVs[ j ][ i * 2 ] );
-							uvsBuffer[ j ].push( faceUVs[ j ][ i * 2 + 1 ] );
+				buffers.materialIndex.push( materialIndex );
+				buffers.materialIndex.push( materialIndex );
+				buffers.materialIndex.push( materialIndex );
 
-						} );
+			}
 
-					}
+			if ( geoInfo.normal ) {
 
-				}
+				buffers.normal.push( faceNormals[ 0 ] );
+				buffers.normal.push( faceNormals[ 1 ] );
+				buffers.normal.push( faceNormals[ 2 ] );
 
-				polygonIndex ++;
-				faceLength = 0;
+				buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] );
+				buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] );
+				buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] );
 
-				// reset arrays for the next face
-				vertexPositionIndexes = [];
-				faceNormals = [];
-				faceColors = [];
-				faceUVs = [];
-				faceWeights = [];
-				faceWeightIndices = [];
+				buffers.normal.push( faceNormals[ i * 3 ] );
+				buffers.normal.push( faceNormals[ i * 3 + 1 ] );
+				buffers.normal.push( faceNormals[ i * 3 + 2 ] );
 
 			}
 
-		} );
+			if ( geoInfo.uv ) {
 
-		var geo = new THREE.BufferGeometry();
-		geo.name = geometryNode.name;
+				geoInfo.uv.forEach( function ( uv, j ) {
 
-		var positionAttribute = new THREE.Float32BufferAttribute( vertexBuffer, 3 );
+					if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = [];
 
-		preTransform.applyToBufferAttribute( positionAttribute );
+					buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] );
+					buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] );
 
-		geo.addAttribute( 'position', positionAttribute );
-
-		if ( colorsBuffer.length > 0 ) {
-
-			geo.addAttribute( 'color', new THREE.Float32BufferAttribute( colorsBuffer, 3 ) );
-
-		}
-
-		if ( skeleton ) {
+					buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] );
+					buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] );
 
-			geo.addAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( weightsIndicesBuffer, 4 ) );
+					buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] );
+					buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] );
 
-			geo.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( vertexWeightsBuffer, 4 ) );
+				} );
 
-			// used later to bind the skeleton to the model
-			geo.FBX_Deformer = skeleton;
+			}
 
 		}
 
-		if ( normalBuffer.length > 0 ) {
+	}
 
-			var normalAttribute = new THREE.Float32BufferAttribute( normalBuffer, 3 );
+	function addMorphTargets( FBXTree, parentGeo, parentGeoNode, morphTarget, preTransform ) {
 
-			var normalMatrix = new THREE.Matrix3().getNormalMatrix( preTransform );
-			normalMatrix.applyToBufferAttribute( normalAttribute );
+		if ( morphTarget === null ) return;
 
-			geo.addAttribute( 'normal', normalAttribute );
+		parentGeo.morphAttributes.position = [];
+		parentGeo.morphAttributes.normal = [];
 
-		}
+		morphTarget.rawTargets.forEach( function ( rawTarget ) {
 
-		uvsBuffer.forEach( function ( uvBuffer, i ) {
+			var morphGeoNode = FBXTree.Objects.Geometry[ rawTarget.geoID ];
 
-			// subsequent uv buffers are called 'uv1', 'uv2', ...
-			var name = 'uv' + ( i + 1 ).toString();
+			if ( morphGeoNode !== undefined ) {
 
-			// the first uv buffer is just called 'uv'
-			if ( i === 0 ) {
-
-				name = 'uv';
+				genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform );
 
 			}
 
-			geo.addAttribute( name, new THREE.Float32BufferAttribute( uvsBuffer[ i ], 2 ) );
-
 		} );
 
-		if ( materialInfo && materialInfo.mappingType !== 'AllSame' ) {
-
-			// Convert the material indices of each vertex into rendering groups on the geometry.
-			var prevMaterialIndex = materialIndexBuffer[ 0 ];
-			var startIndex = 0;
-
-			materialIndexBuffer.forEach( function ( currentIndex, i ) {
-
-				if ( currentIndex !== prevMaterialIndex ) {
-
-					geo.addGroup( startIndex, i - startIndex, prevMaterialIndex );
+	}
 
-					prevMaterialIndex = currentIndex;
-					startIndex = i;
+	// a morph geometry node is similar to a standard  node, and the node is also contained
+	// in FBXTree.Objects.Geometry, however it can only have attributes for position, normal
+	// and a special attribute Index defining which vertices of the original geometry are affected
+	// Normal and position attributes only have data for the vertices that are affected by the morph
+	function genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform ) {
 
-				}
+		var morphGeo = new THREE.BufferGeometry();
+		if ( morphGeoNode.attrName ) morphGeo.name = morphGeoNode.attrName;
 
-			} );
+		var vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : [];
 
-			// the loop above doesn't add the last group, do that here.
-			if ( geo.groups.length > 0 ) {
+		// make a copy of the parent's vertex positions
+		var vertexPositions = ( parentGeoNode.Vertices !== undefined ) ? parentGeoNode.Vertices.a.slice() : [];
 
-				var lastGroup = geo.groups[ geo.groups.length - 1 ];
-				var lastIndex = lastGroup.start + lastGroup.count;
+		var morphPositions = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : [];
+		var indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : [];
 
-				if ( lastIndex !== materialIndexBuffer.length ) {
+		for ( var i = 0; i < indices.length; i ++ ) {
 
-					geo.addGroup( lastIndex, materialIndexBuffer.length - lastIndex, prevMaterialIndex );
+			var morphIndex = indices[ i ] * 3;
 
-				}
+			// FBX format uses blend shapes rather than morph targets. This can be converted
+			// by additively combining the blend shape positions with the original geometry's positions
+			vertexPositions[ morphIndex ] += morphPositions[ i * 3 ];
+			vertexPositions[ morphIndex + 1 ] += morphPositions[ i * 3 + 1 ];
+			vertexPositions[ morphIndex + 2 ] += morphPositions[ i * 3 + 2 ];
 
-			}
+		}
 
-			// case where there are multiple materials but the whole geometry is only
-			// using one of them
-			if ( geo.groups.length === 0 ) {
+		// TODO: add morph normal support
+		var morphGeoInfo = {
+			vertexIndices: vertexIndices,
+			vertexPositions: vertexPositions,
+		};
 
-				geo.addGroup( 0, materialIndexBuffer.length, materialIndexBuffer[ 0 ] );
+		var morphBuffers = genBuffers( morphGeoInfo );
 
-			}
+		var positionAttribute = new THREE.Float32BufferAttribute( morphBuffers.vertex, 3 );
+		positionAttribute.name = morphGeoNode.attrName;
 
-		}
+		preTransform.applyToBufferAttribute( positionAttribute );
 
-		return geo;
+		parentGeo.morphAttributes.position.push( positionAttribute );
 
 	}
 
-
 	// Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists
 	function getNormals( NormalNode ) {
 
@@ -1367,7 +1547,7 @@
 	}
 
 	// Generate a NurbGeometry from a node in FBXTree.Objects.Geometry
-	function parseNurbsGeometry( geometryNode ) {
+	function parseNurbsGeometry( geoNode ) {
 
 		if ( THREE.NURBSCurve === undefined ) {
 
@@ -1376,20 +1556,20 @@
 
 		}
 
-		var order = parseInt( geometryNode.Order );
+		var order = parseInt( geoNode.Order );
 
 		if ( isNaN( order ) ) {
 
-			console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geometryNode.Order, geometryNode.id );
+			console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id );
 			return new THREE.BufferGeometry();
 
 		}
 
 		var degree = order - 1;
 
-		var knots = geometryNode.KnotVector.a;
+		var knots = geoNode.KnotVector.a;
 		var controlPoints = [];
-		var pointsValues = geometryNode.Points.a;
+		var pointsValues = geoNode.Points.a;
 
 		for ( var i = 0, l = pointsValues.length; i < l; i += 4 ) {
 
@@ -1399,11 +1579,11 @@
 
 		var startKnot, endKnot;
 
-		if ( geometryNode.Form === 'Closed' ) {
+		if ( geoNode.Form === 'Closed' ) {
 
 			controlPoints.push( controlPoints[ 0 ] );
 
-		} else if ( geometryNode.Form === 'Periodic' ) {
+		} else if ( geoNode.Form === 'Periodic' ) {
 
 			startKnot = degree;
 			endKnot = knots.length - 1 - startKnot;