|
@@ -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;
|