|
@@ -2174,7 +2174,7 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
sceneGraph.animations = [];
|
|
sceneGraph.animations = [];
|
|
|
|
|
|
- var rawClips = this.parseAnimations();
|
|
|
|
|
|
+ var rawClips = new AnimationParser().parse( this.FBXTree, this.connections );
|
|
|
|
|
|
if ( rawClips === undefined ) return;
|
|
if ( rawClips === undefined ) return;
|
|
|
|
|
|
@@ -2190,616 +2190,628 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
},
|
|
},
|
|
|
|
|
|
- parseAnimations: function () {
|
|
|
|
|
|
+ addClip: function ( rawClip, sceneGraph ) {
|
|
|
|
|
|
- // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve,
|
|
|
|
- // if this is undefined we can safely assume there are no animations
|
|
|
|
- if ( this.FBXTree.Objects.AnimationCurve === undefined ) return undefined;
|
|
|
|
|
|
+ var tracks = [];
|
|
|
|
|
|
- var curveNodesMap = this.parseAnimationCurveNodes();
|
|
|
|
|
|
+ var self = this;
|
|
|
|
+ rawClip.layer.forEach( function ( rawTracks ) {
|
|
|
|
|
|
- this.parseAnimationCurves( curveNodesMap );
|
|
|
|
|
|
+ tracks = tracks.concat( self.generateTracks( rawTracks, sceneGraph ) );
|
|
|
|
|
|
- var layersMap = this.parseAnimationLayers( curveNodesMap );
|
|
|
|
- var rawClips = this.parseAnimStacks( layersMap );
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- return rawClips;
|
|
|
|
|
|
+ return new THREE.AnimationClip( rawClip.name, - 1, tracks );
|
|
|
|
|
|
},
|
|
},
|
|
|
|
|
|
- // parse nodes in FBXTree.Objects.AnimationCurveNode
|
|
|
|
- // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation )
|
|
|
|
- // and is referenced by an AnimationLayer
|
|
|
|
- parseAnimationCurveNodes: function () {
|
|
|
|
|
|
+ generateTracks: function ( rawTracks, sceneGraph ) {
|
|
|
|
|
|
- var rawCurveNodes = this.FBXTree.Objects.AnimationCurveNode;
|
|
|
|
|
|
+ var tracks = [];
|
|
|
|
|
|
- var curveNodesMap = new Map();
|
|
|
|
|
|
+ var initialPosition = new THREE.Vector3();
|
|
|
|
+ var initialRotation = new THREE.Quaternion();
|
|
|
|
+ var initialScale = new THREE.Vector3();
|
|
|
|
|
|
- for ( var nodeID in rawCurveNodes ) {
|
|
|
|
|
|
+ if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
|
|
|
|
|
|
- var rawCurveNode = rawCurveNodes[ nodeID ];
|
|
|
|
|
|
+ initialPosition = initialPosition.toArray();
|
|
|
|
+ initialRotation = new THREE.Euler().setFromQuaternion( initialRotation ).toArray(); // todo: euler order
|
|
|
|
+ initialScale = initialScale.toArray();
|
|
|
|
|
|
- if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
|
|
|
|
|
|
+ if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
|
|
|
|
|
|
- var curveNode = {
|
|
|
|
|
|
+ var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
|
|
|
|
+ if ( positionTrack !== undefined ) tracks.push( positionTrack );
|
|
|
|
|
|
- id: rawCurveNode.id,
|
|
|
|
- attr: rawCurveNode.attrName,
|
|
|
|
- curves: {},
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
|
|
|
|
|
|
- curveNodesMap.set( curveNode.id, curveNode );
|
|
|
|
|
|
+ var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotations, rawTracks.postRotations );
|
|
|
|
+ if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) {
|
|
|
|
+
|
|
|
|
+ var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
|
|
|
|
+ if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- return curveNodesMap;
|
|
|
|
|
|
+ if ( rawTracks.DeformPercent !== undefined ) {
|
|
|
|
+
|
|
|
|
+ var morphTrack = this.generateMorphTrack( rawTracks, sceneGraph );
|
|
|
|
+ if ( morphTrack !== undefined ) tracks.push( morphTrack );
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return tracks;
|
|
|
|
|
|
},
|
|
},
|
|
|
|
|
|
- // parse nodes in FBXTree.Objects.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)
|
|
|
|
- parseAnimationCurves: function ( curveNodesMap ) {
|
|
|
|
|
|
+ generateVectorTrack: function ( modelName, curves, initialValue, type ) {
|
|
|
|
|
|
- var rawCurves = this.FBXTree.Objects.AnimationCurve;
|
|
|
|
|
|
+ var times = this.getTimesForAllAxes( curves );
|
|
|
|
+ var values = this.getKeyframeTrackValues( times, curves, initialValue );
|
|
|
|
|
|
- // 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
|
|
|
|
|
|
+ return new THREE.VectorKeyframeTrack( modelName + '.' + type, times, values );
|
|
|
|
|
|
- for ( var nodeID in rawCurves ) {
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- var animationCurve = {
|
|
|
|
|
|
+ generateRotationTrack: function ( modelName, curves, initialValue, preRotations, postRotations ) {
|
|
|
|
|
|
- id: rawCurves[ nodeID ].id,
|
|
|
|
- times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ),
|
|
|
|
- values: rawCurves[ nodeID ].KeyValueFloat.a,
|
|
|
|
|
|
+ if ( curves.x !== undefined ) {
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ this.interpolateRotations( curves.x );
|
|
|
|
+ curves.x.values = curves.x.values.map( THREE.Math.degToRad );
|
|
|
|
|
|
- var relationships = this.connections.get( animationCurve.id );
|
|
|
|
|
|
+ }
|
|
|
|
+ if ( curves.y !== undefined ) {
|
|
|
|
|
|
- if ( relationships !== undefined ) {
|
|
|
|
|
|
+ this.interpolateRotations( curves.y );
|
|
|
|
+ curves.y.values = curves.y.values.map( THREE.Math.degToRad );
|
|
|
|
|
|
- var animationCurveID = relationships.parents[ 0 ].ID;
|
|
|
|
- var animationCurveRelationship = relationships.parents[ 0 ].relationship;
|
|
|
|
|
|
+ }
|
|
|
|
+ if ( curves.z !== undefined ) {
|
|
|
|
|
|
- if ( animationCurveRelationship.match( /X/ ) ) {
|
|
|
|
|
|
+ this.interpolateRotations( curves.z );
|
|
|
|
+ curves.z.values = curves.z.values.map( THREE.Math.degToRad );
|
|
|
|
|
|
- curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve;
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- } else if ( animationCurveRelationship.match( /Y/ ) ) {
|
|
|
|
|
|
+ var times = this.getTimesForAllAxes( curves );
|
|
|
|
+ var values = this.getKeyframeTrackValues( times, curves, initialValue );
|
|
|
|
|
|
- curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve;
|
|
|
|
|
|
+ if ( preRotations !== undefined ) {
|
|
|
|
|
|
- } else if ( animationCurveRelationship.match( /Z/ ) ) {
|
|
|
|
|
|
+ preRotations = preRotations.map( THREE.Math.degToRad );
|
|
|
|
+ preRotations.push( 'ZYX' );
|
|
|
|
|
|
- curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
|
|
|
|
|
|
+ preRotations = new THREE.Euler().fromArray( preRotations );
|
|
|
|
+ preRotations = new THREE.Quaternion().setFromEuler( preRotations );
|
|
|
|
|
|
- } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
|
|
|
|
|
|
+ if ( postRotations !== undefined ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ postRotations = postRotations.map( THREE.Math.degToRad );
|
|
|
|
+ postRotations.push( 'ZYX' );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ postRotations = new THREE.Euler().fromArray( postRotations );
|
|
|
|
+ postRotations = new THREE.Quaternion().setFromEuler( postRotations ).inverse();
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ var quaternion = new THREE.Quaternion();
|
|
|
|
+ var euler = new THREE.Euler();
|
|
|
|
|
|
- // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references
|
|
|
|
- // to various AnimationCurveNodes and is referenced by an AnimationStack node
|
|
|
|
- // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack
|
|
|
|
- parseAnimationLayers: function ( curveNodesMap ) {
|
|
|
|
|
|
+ var quaternionValues = [];
|
|
|
|
|
|
- var rawLayers = this.FBXTree.Objects.AnimationLayer;
|
|
|
|
|
|
+ for ( var i = 0; i < values.length; i += 3 ) {
|
|
|
|
|
|
- var layersMap = new Map();
|
|
|
|
|
|
+ euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], 'ZYX' );
|
|
|
|
|
|
- for ( var nodeID in rawLayers ) {
|
|
|
|
|
|
+ quaternion.setFromEuler( euler );
|
|
|
|
|
|
- var layerCurveNodes = [];
|
|
|
|
|
|
+ if ( preRotations !== undefined ) quaternion.premultiply( preRotations );
|
|
|
|
+ if ( postRotations !== undefined ) quaternion.multiply( postRotations );
|
|
|
|
|
|
- var connection = this.connections.get( parseInt( nodeID ) );
|
|
|
|
|
|
+ quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
|
|
|
|
|
|
- if ( connection !== undefined ) {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- // all the animationCurveNodes used in the layer
|
|
|
|
- var children = connection.children;
|
|
|
|
|
|
+ return new THREE.QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
|
|
|
|
|
|
- var self = this;
|
|
|
|
- children.forEach( function ( child, i ) {
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- if ( curveNodesMap.has( child.ID ) ) {
|
|
|
|
|
|
+ generateMorphTrack: function ( rawTracks, sceneGraph ) {
|
|
|
|
|
|
- var curveNode = curveNodesMap.get( child.ID );
|
|
|
|
|
|
+ var curves = rawTracks.DeformPercent.curves.morph;
|
|
|
|
+ var values = curves.values.map( function ( val ) {
|
|
|
|
|
|
- // check that the curves are defined for at least one axis, otherwise ignore the curveNode
|
|
|
|
- if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) {
|
|
|
|
|
|
+ return val / 100;
|
|
|
|
|
|
- if ( layerCurveNodes[ i ] === undefined ) {
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- var modelID;
|
|
|
|
|
|
+ var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ];
|
|
|
|
|
|
- self.connections.get( child.ID ).parents.forEach( function ( parent ) {
|
|
|
|
|
|
+ return new THREE.NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values );
|
|
|
|
|
|
- if ( parent.relationship !== undefined ) modelID = parent.ID;
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ // For all animated objects, times are defined separately for each axis
|
|
|
|
+ // Here we'll combine the times into one sorted array without duplicates
|
|
|
|
+ getTimesForAllAxes: function ( curves ) {
|
|
|
|
|
|
- var rawModel = self.FBXTree.Objects.Model[ modelID.toString() ];
|
|
|
|
|
|
+ var times = [];
|
|
|
|
|
|
- var node = {
|
|
|
|
|
|
+ // 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 );
|
|
|
|
|
|
- modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ),
|
|
|
|
- initialPosition: [ 0, 0, 0 ],
|
|
|
|
- initialRotation: [ 0, 0, 0 ],
|
|
|
|
- initialScale: [ 1, 1, 1 ],
|
|
|
|
- transform: self.getModelAnimTransform( rawModel ),
|
|
|
|
|
|
+ // then sort them and remove duplicates
|
|
|
|
+ times = times.sort( function ( a, b ) {
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ return a - b;
|
|
|
|
|
|
- // 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 ) node.preRotations = rawModel.PreRotation.value;
|
|
|
|
- if ( 'PostRotation' in rawModel ) node.postRotations = rawModel.PostRotation.value;
|
|
|
|
|
|
+ } ).filter( function ( elem, index, array ) {
|
|
|
|
|
|
- layerCurveNodes[ i ] = node;
|
|
|
|
|
|
+ return array.indexOf( elem ) == index;
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
|
|
|
|
|
|
+ return times;
|
|
|
|
|
|
- } else if ( curveNode.curves.morph !== undefined ) {
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- if ( layerCurveNodes[ i ] === undefined ) {
|
|
|
|
|
|
+ getKeyframeTrackValues: function ( times, curves, initialValue ) {
|
|
|
|
|
|
- var deformerID;
|
|
|
|
|
|
+ var prevValue = initialValue;
|
|
|
|
|
|
- self.connections.get( child.ID ).parents.forEach( function ( parent ) {
|
|
|
|
|
|
+ var values = [];
|
|
|
|
|
|
- if ( parent.relationship !== undefined ) deformerID = parent.ID;
|
|
|
|
|
|
+ var xIndex = - 1;
|
|
|
|
+ var yIndex = - 1;
|
|
|
|
+ var zIndex = - 1;
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ times.forEach( function ( time ) {
|
|
|
|
|
|
- var morpherID = self.connections.get( deformerID ).parents[ 0 ].ID;
|
|
|
|
- var geoID = self.connections.get( morpherID ).parents[ 0 ].ID;
|
|
|
|
|
|
+ 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 );
|
|
|
|
|
|
- // assuming geometry is not used in more than one model
|
|
|
|
- var modelID = self.connections.get( geoID ).parents[ 0 ].ID;
|
|
|
|
|
|
+ // if there is an x value defined for this frame, use that
|
|
|
|
+ if ( xIndex !== - 1 ) {
|
|
|
|
|
|
- var rawModel = self.FBXTree.Objects.Model[ modelID ];
|
|
|
|
|
|
+ var xValue = curves.x.values[ xIndex ];
|
|
|
|
+ values.push( xValue );
|
|
|
|
+ prevValue[ 0 ] = xValue;
|
|
|
|
|
|
- var node = {
|
|
|
|
|
|
+ } else {
|
|
|
|
|
|
- modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ),
|
|
|
|
- morphName: self.FBXTree.Objects.Deformer[ deformerID ].attrName,
|
|
|
|
|
|
+ // otherwise use the x value from the previous frame
|
|
|
|
+ values.push( prevValue[ 0 ] );
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- layerCurveNodes[ i ] = node;
|
|
|
|
|
|
+ if ( yIndex !== - 1 ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ var yValue = curves.y.values[ yIndex ];
|
|
|
|
+ values.push( yValue );
|
|
|
|
+ prevValue[ 1 ] = yValue;
|
|
|
|
|
|
- layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
|
|
|
|
|
|
+ } else {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ values.push( prevValue[ 1 ] );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ if ( zIndex !== - 1 ) {
|
|
|
|
|
|
- layersMap.set( parseInt( nodeID ), layerCurveNodes );
|
|
|
|
|
|
+ var zValue = curves.z.values[ zIndex ];
|
|
|
|
+ values.push( zValue );
|
|
|
|
+ prevValue[ 2 ] = zValue;
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+
|
|
|
|
+ values.push( prevValue[ 2 ] );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- return layersMap;
|
|
|
|
|
|
+ return values;
|
|
|
|
|
|
},
|
|
},
|
|
|
|
|
|
- getModelAnimTransform: function ( modelNode ) {
|
|
|
|
|
|
+ // Rotations are defined as Euler angles which can have values of any size
|
|
|
|
+ // These will be converted to quaternions which don't support values greater than
|
|
|
|
+ // PI, so we'll interpolate large rotations
|
|
|
|
+ interpolateRotations: function ( curve ) {
|
|
|
|
|
|
- var transformData = {};
|
|
|
|
|
|
+ for ( var i = 1; i < curve.values.length; i ++ ) {
|
|
|
|
|
|
- if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = parseInt( modelNode.RotationOrder.value );
|
|
|
|
|
|
+ var initialValue = curve.values[ i - 1 ];
|
|
|
|
+ var valuesSpan = curve.values[ i ] - initialValue;
|
|
|
|
|
|
- if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
|
|
|
|
- if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
|
|
|
|
|
|
+ var absoluteSpan = Math.abs( valuesSpan );
|
|
|
|
|
|
- if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
|
|
|
|
- if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
|
|
|
|
|
|
+ if ( absoluteSpan >= 180 ) {
|
|
|
|
|
|
- if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
|
|
|
|
|
|
+ var numSubIntervals = absoluteSpan / 180;
|
|
|
|
|
|
- if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
|
|
|
|
|
|
+ var step = valuesSpan / numSubIntervals;
|
|
|
|
+ var nextValue = initialValue + step;
|
|
|
|
|
|
- return generateTransform( transformData );
|
|
|
|
|
|
+ var initialTime = curve.times[ i - 1 ];
|
|
|
|
+ var timeSpan = curve.times[ i ] - initialTime;
|
|
|
|
+ var interval = timeSpan / numSubIntervals;
|
|
|
|
+ var nextTime = initialTime + interval;
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ var interpolatedTimes = [];
|
|
|
|
+ var interpolatedValues = [];
|
|
|
|
|
|
- // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation
|
|
|
|
- // hierarchy. Each Stack node will be used to create a THREE.AnimationClip
|
|
|
|
- parseAnimStacks: function ( layersMap ) {
|
|
|
|
|
|
+ while ( nextTime < curve.times[ i ] ) {
|
|
|
|
|
|
- var rawStacks = this.FBXTree.Objects.AnimationStack;
|
|
|
|
|
|
+ interpolatedTimes.push( nextTime );
|
|
|
|
+ nextTime += interval;
|
|
|
|
|
|
- // connect the stacks (clips) up to the layers
|
|
|
|
- var rawClips = {};
|
|
|
|
|
|
+ interpolatedValues.push( nextValue );
|
|
|
|
+ nextValue += step;
|
|
|
|
|
|
- for ( var nodeID in rawStacks ) {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- var children = this.connections.get( parseInt( nodeID ) ).children;
|
|
|
|
|
|
+ curve.times = inject( curve.times, i, interpolatedTimes );
|
|
|
|
+ curve.values = inject( curve.values, i, interpolatedValues );
|
|
|
|
|
|
- if ( children.length > 1 ) {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- // 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.' );
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- var layer = layersMap.get( children[ 0 ].ID );
|
|
|
|
|
|
+ // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
|
|
|
|
+ createAmbientLight: function ( sceneGraph ) {
|
|
|
|
|
|
- rawClips[ nodeID ] = {
|
|
|
|
|
|
+ if ( 'GlobalSettings' in this.FBXTree && 'AmbientColor' in this.FBXTree.GlobalSettings ) {
|
|
|
|
|
|
- name: rawStacks[ nodeID ].attrName,
|
|
|
|
- layer: layer,
|
|
|
|
|
|
+ var ambientColor = this.FBXTree.GlobalSettings.AmbientColor.value;
|
|
|
|
+ var r = ambientColor[ 0 ];
|
|
|
|
+ var g = ambientColor[ 1 ];
|
|
|
|
+ var b = ambientColor[ 2 ];
|
|
|
|
|
|
- };
|
|
|
|
|
|
+ if ( r !== 0 || g !== 0 || b !== 0 ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ var color = new THREE.Color( r, g, b );
|
|
|
|
+ sceneGraph.add( new THREE.AmbientLight( color, 1 ) );
|
|
|
|
|
|
- return rawClips;
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- addClip: function ( rawClip, sceneGraph ) {
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- var tracks = [];
|
|
|
|
|
|
+ setupMorphMaterials: function ( sceneGraph ) {
|
|
|
|
|
|
- var self = this;
|
|
|
|
- rawClip.layer.forEach( function ( rawTracks ) {
|
|
|
|
|
|
+ sceneGraph.traverse( function ( child ) {
|
|
|
|
|
|
- tracks = tracks.concat( self.generateTracks( rawTracks, sceneGraph ) );
|
|
|
|
|
|
+ if ( child.isMesh ) {
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ if ( child.geometry.morphAttributes.position || child.geometry.morphAttributes.normal ) {
|
|
|
|
|
|
- return new THREE.AnimationClip( rawClip.name, - 1, tracks );
|
|
|
|
|
|
+ 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;
|
|
|
|
|
|
- generateTracks: function ( rawTracks, sceneGraph ) {
|
|
|
|
|
|
+ sceneGraph.traverse( function ( child ) {
|
|
|
|
|
|
- var tracks = [];
|
|
|
|
|
|
+ if ( child.isMesh ) {
|
|
|
|
|
|
- var initialPosition = new THREE.Vector3();
|
|
|
|
- var initialRotation = new THREE.Quaternion();
|
|
|
|
- var initialScale = new THREE.Vector3();
|
|
|
|
|
|
+ if ( child.material.uuid === matUuid && child.uuid !== uuid ) sharedMat = true;
|
|
|
|
|
|
- if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- initialPosition = initialPosition.toArray();
|
|
|
|
- initialRotation = new THREE.Euler().setFromQuaternion( initialRotation ).toArray(); // todo: euler order
|
|
|
|
- initialScale = initialScale.toArray();
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
|
|
|
|
|
|
+ if ( sharedMat === true ) child.material = child.material.clone();
|
|
|
|
|
|
- var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
|
|
|
|
- if ( positionTrack !== undefined ) tracks.push( positionTrack );
|
|
|
|
|
|
+ child.material.morphTargets = true;
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotations, rawTracks.postRotations );
|
|
|
|
- if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) {
|
|
|
|
|
|
+ };
|
|
|
|
|
|
- var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
|
|
|
|
- if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
|
|
|
|
|
|
+ // parse animation data from FBXTree
|
|
|
|
+ function AnimationParser() {}
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ AnimationParser.prototype = {
|
|
|
|
|
|
- if ( rawTracks.DeformPercent !== undefined ) {
|
|
|
|
|
|
+ constructor: AnimationParser,
|
|
|
|
|
|
- var morphTrack = this.generateMorphTrack( rawTracks, sceneGraph );
|
|
|
|
- if ( morphTrack !== undefined ) tracks.push( morphTrack );
|
|
|
|
|
|
+ parse: function ( FBXTree, connections ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ this.FBXTree = FBXTree;
|
|
|
|
+ this.connections = connections;
|
|
|
|
|
|
- return tracks;
|
|
|
|
|
|
+ // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve,
|
|
|
|
+ // if this is undefined we can safely assume there are no animations
|
|
|
|
+ if ( this.FBXTree.Objects.AnimationCurve === undefined ) return undefined;
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ var curveNodesMap = this.parseAnimationCurveNodes();
|
|
|
|
|
|
- generateVectorTrack: function ( modelName, curves, initialValue, type ) {
|
|
|
|
|
|
+ this.parseAnimationCurves( curveNodesMap );
|
|
|
|
|
|
- var times = this.getTimesForAllAxes( curves );
|
|
|
|
- var values = this.getKeyframeTrackValues( times, curves, initialValue );
|
|
|
|
|
|
+ var layersMap = this.parseAnimationLayers( curveNodesMap );
|
|
|
|
+ var rawClips = this.parseAnimStacks( layersMap );
|
|
|
|
|
|
- return new THREE.VectorKeyframeTrack( modelName + '.' + type, times, values );
|
|
|
|
|
|
+ return rawClips;
|
|
|
|
|
|
},
|
|
},
|
|
|
|
|
|
- generateRotationTrack: function ( modelName, curves, initialValue, preRotations, postRotations ) {
|
|
|
|
-
|
|
|
|
- if ( curves.x !== undefined ) {
|
|
|
|
|
|
+ // parse nodes in FBXTree.Objects.AnimationCurveNode
|
|
|
|
+ // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation )
|
|
|
|
+ // and is referenced by an AnimationLayer
|
|
|
|
+ parseAnimationCurveNodes: function () {
|
|
|
|
|
|
- this.interpolateRotations( curves.x );
|
|
|
|
- curves.x.values = curves.x.values.map( THREE.Math.degToRad );
|
|
|
|
|
|
+ var rawCurveNodes = this.FBXTree.Objects.AnimationCurveNode;
|
|
|
|
|
|
- }
|
|
|
|
- if ( curves.y !== undefined ) {
|
|
|
|
|
|
+ var curveNodesMap = new Map();
|
|
|
|
|
|
- this.interpolateRotations( curves.y );
|
|
|
|
- curves.y.values = curves.y.values.map( THREE.Math.degToRad );
|
|
|
|
|
|
+ for ( var nodeID in rawCurveNodes ) {
|
|
|
|
|
|
- }
|
|
|
|
- if ( curves.z !== undefined ) {
|
|
|
|
|
|
+ var rawCurveNode = rawCurveNodes[ nodeID ];
|
|
|
|
|
|
- this.interpolateRotations( curves.z );
|
|
|
|
- curves.z.values = curves.z.values.map( THREE.Math.degToRad );
|
|
|
|
|
|
+ if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ var curveNode = {
|
|
|
|
|
|
- var times = this.getTimesForAllAxes( curves );
|
|
|
|
- var values = this.getKeyframeTrackValues( times, curves, initialValue );
|
|
|
|
|
|
+ id: rawCurveNode.id,
|
|
|
|
+ attr: rawCurveNode.attrName,
|
|
|
|
+ curves: {},
|
|
|
|
|
|
- if ( preRotations !== undefined ) {
|
|
|
|
|
|
+ };
|
|
|
|
|
|
- preRotations = preRotations.map( THREE.Math.degToRad );
|
|
|
|
- preRotations.push( 'ZYX' );
|
|
|
|
|
|
+ curveNodesMap.set( curveNode.id, curveNode );
|
|
|
|
|
|
- preRotations = new THREE.Euler().fromArray( preRotations );
|
|
|
|
- preRotations = new THREE.Quaternion().setFromEuler( preRotations );
|
|
|
|
|
|
+ }
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- if ( postRotations !== undefined ) {
|
|
|
|
-
|
|
|
|
- postRotations = postRotations.map( THREE.Math.degToRad );
|
|
|
|
- postRotations.push( 'ZYX' );
|
|
|
|
-
|
|
|
|
- postRotations = new THREE.Euler().fromArray( postRotations );
|
|
|
|
- postRotations = new THREE.Quaternion().setFromEuler( postRotations ).inverse();
|
|
|
|
|
|
+ return curveNodesMap;
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- var quaternion = new THREE.Quaternion();
|
|
|
|
- var euler = new THREE.Euler();
|
|
|
|
|
|
+ // parse nodes in FBXTree.Objects.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)
|
|
|
|
+ parseAnimationCurves: function ( curveNodesMap ) {
|
|
|
|
|
|
- var quaternionValues = [];
|
|
|
|
|
|
+ var rawCurves = this.FBXTree.Objects.AnimationCurve;
|
|
|
|
|
|
- for ( var i = 0; i < values.length; i += 3 ) {
|
|
|
|
|
|
+ // 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
|
|
|
|
|
|
- euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], 'ZYX' );
|
|
|
|
|
|
+ for ( var nodeID in rawCurves ) {
|
|
|
|
|
|
- quaternion.setFromEuler( euler );
|
|
|
|
|
|
+ var animationCurve = {
|
|
|
|
|
|
- if ( preRotations !== undefined ) quaternion.premultiply( preRotations );
|
|
|
|
- if ( postRotations !== undefined ) quaternion.multiply( postRotations );
|
|
|
|
|
|
+ id: rawCurves[ nodeID ].id,
|
|
|
|
+ times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ),
|
|
|
|
+ values: rawCurves[ nodeID ].KeyValueFloat.a,
|
|
|
|
|
|
- quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
|
|
|
|
|
|
+ };
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ var relationships = this.connections.get( animationCurve.id );
|
|
|
|
|
|
- return new THREE.QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
|
|
|
|
|
|
+ if ( relationships !== undefined ) {
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ var animationCurveID = relationships.parents[ 0 ].ID;
|
|
|
|
+ var animationCurveRelationship = relationships.parents[ 0 ].relationship;
|
|
|
|
|
|
- generateMorphTrack: function ( rawTracks, sceneGraph ) {
|
|
|
|
|
|
+ if ( animationCurveRelationship.match( /X/ ) ) {
|
|
|
|
|
|
- var curves = rawTracks.DeformPercent.curves.morph;
|
|
|
|
- var values = curves.values.map( function ( val ) {
|
|
|
|
|
|
+ curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve;
|
|
|
|
|
|
- return val / 100;
|
|
|
|
|
|
+ } else if ( animationCurveRelationship.match( /Y/ ) ) {
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve;
|
|
|
|
|
|
- var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ];
|
|
|
|
|
|
+ } else if ( animationCurveRelationship.match( /Z/ ) ) {
|
|
|
|
|
|
- return new THREE.NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values );
|
|
|
|
|
|
+ curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
|
|
|
|
|
|
- // For all animated objects, times are defined separately for each axis
|
|
|
|
- // Here we'll combine the times into one sorted array without duplicates
|
|
|
|
- getTimesForAllAxes: function ( curves ) {
|
|
|
|
|
|
+ curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
|
|
|
|
|
|
- var times = [];
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- // 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 );
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- // then sort them and remove duplicates
|
|
|
|
- times = times.sort( function ( a, b ) {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- return a - b;
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- } ).filter( function ( elem, index, array ) {
|
|
|
|
|
|
+ // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references
|
|
|
|
+ // to various AnimationCurveNodes and is referenced by an AnimationStack node
|
|
|
|
+ // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack
|
|
|
|
+ parseAnimationLayers: function ( curveNodesMap ) {
|
|
|
|
|
|
- return array.indexOf( elem ) == index;
|
|
|
|
|
|
+ var rawLayers = this.FBXTree.Objects.AnimationLayer;
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ var layersMap = new Map();
|
|
|
|
|
|
- return times;
|
|
|
|
|
|
+ for ( var nodeID in rawLayers ) {
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ var layerCurveNodes = [];
|
|
|
|
|
|
- getKeyframeTrackValues: function ( times, curves, initialValue ) {
|
|
|
|
|
|
+ var connection = this.connections.get( parseInt( nodeID ) );
|
|
|
|
|
|
- var prevValue = initialValue;
|
|
|
|
|
|
+ if ( connection !== undefined ) {
|
|
|
|
|
|
- var values = [];
|
|
|
|
|
|
+ // all the animationCurveNodes used in the layer
|
|
|
|
+ var children = connection.children;
|
|
|
|
|
|
- var xIndex = - 1;
|
|
|
|
- var yIndex = - 1;
|
|
|
|
- var zIndex = - 1;
|
|
|
|
|
|
+ var self = this;
|
|
|
|
+ children.forEach( function ( child, i ) {
|
|
|
|
|
|
- times.forEach( function ( time ) {
|
|
|
|
|
|
+ if ( curveNodesMap.has( child.ID ) ) {
|
|
|
|
|
|
- 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 );
|
|
|
|
|
|
+ var curveNode = curveNodesMap.get( child.ID );
|
|
|
|
|
|
- // if there is an x value defined for this frame, use that
|
|
|
|
- if ( xIndex !== - 1 ) {
|
|
|
|
|
|
+ // check that the curves are defined for at least one axis, otherwise ignore the curveNode
|
|
|
|
+ if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) {
|
|
|
|
|
|
- var xValue = curves.x.values[ xIndex ];
|
|
|
|
- values.push( xValue );
|
|
|
|
- prevValue[ 0 ] = xValue;
|
|
|
|
|
|
+ if ( layerCurveNodes[ i ] === undefined ) {
|
|
|
|
|
|
- } else {
|
|
|
|
|
|
+ var modelID;
|
|
|
|
|
|
- // otherwise use the x value from the previous frame
|
|
|
|
- values.push( prevValue[ 0 ] );
|
|
|
|
|
|
+ self.connections.get( child.ID ).parents.forEach( function ( parent ) {
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ if ( parent.relationship !== undefined ) modelID = parent.ID;
|
|
|
|
|
|
- if ( yIndex !== - 1 ) {
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- var yValue = curves.y.values[ yIndex ];
|
|
|
|
- values.push( yValue );
|
|
|
|
- prevValue[ 1 ] = yValue;
|
|
|
|
|
|
+ var rawModel = self.FBXTree.Objects.Model[ modelID.toString() ];
|
|
|
|
|
|
- } else {
|
|
|
|
|
|
+ var node = {
|
|
|
|
|
|
- values.push( prevValue[ 1 ] );
|
|
|
|
|
|
+ modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ),
|
|
|
|
+ initialPosition: [ 0, 0, 0 ],
|
|
|
|
+ initialRotation: [ 0, 0, 0 ],
|
|
|
|
+ initialScale: [ 1, 1, 1 ],
|
|
|
|
+ transform: self.getModelAnimTransform( rawModel ),
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ };
|
|
|
|
|
|
- if ( zIndex !== - 1 ) {
|
|
|
|
|
|
+ // 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 ) node.preRotations = rawModel.PreRotation.value;
|
|
|
|
+ if ( 'PostRotation' in rawModel ) node.postRotations = rawModel.PostRotation.value;
|
|
|
|
|
|
- var zValue = curves.z.values[ zIndex ];
|
|
|
|
- values.push( zValue );
|
|
|
|
- prevValue[ 2 ] = zValue;
|
|
|
|
|
|
+ layerCurveNodes[ i ] = node;
|
|
|
|
|
|
- } else {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- values.push( prevValue[ 2 ] );
|
|
|
|
|
|
+ layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ } else if ( curveNode.curves.morph !== undefined ) {
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ if ( layerCurveNodes[ i ] === undefined ) {
|
|
|
|
|
|
- return values;
|
|
|
|
|
|
+ var deformerID;
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ self.connections.get( child.ID ).parents.forEach( function ( parent ) {
|
|
|
|
|
|
- // Rotations are defined as Euler angles which can have values of any size
|
|
|
|
- // These will be converted to quaternions which don't support values greater than
|
|
|
|
- // PI, so we'll interpolate large rotations
|
|
|
|
- interpolateRotations: function ( curve ) {
|
|
|
|
|
|
+ if ( parent.relationship !== undefined ) deformerID = parent.ID;
|
|
|
|
|
|
- for ( var i = 1; i < curve.values.length; i ++ ) {
|
|
|
|
|
|
+ } );
|
|
|
|
|
|
- var initialValue = curve.values[ i - 1 ];
|
|
|
|
- var valuesSpan = curve.values[ i ] - initialValue;
|
|
|
|
|
|
+ var morpherID = self.connections.get( deformerID ).parents[ 0 ].ID;
|
|
|
|
+ var geoID = self.connections.get( morpherID ).parents[ 0 ].ID;
|
|
|
|
|
|
- var absoluteSpan = Math.abs( valuesSpan );
|
|
|
|
|
|
+ // assuming geometry is not used in more than one model
|
|
|
|
+ var modelID = self.connections.get( geoID ).parents[ 0 ].ID;
|
|
|
|
|
|
- if ( absoluteSpan >= 180 ) {
|
|
|
|
|
|
+ var rawModel = self.FBXTree.Objects.Model[ modelID ];
|
|
|
|
|
|
- var numSubIntervals = absoluteSpan / 180;
|
|
|
|
|
|
+ var node = {
|
|
|
|
|
|
- var step = valuesSpan / numSubIntervals;
|
|
|
|
- var nextValue = initialValue + step;
|
|
|
|
|
|
+ modelName: THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ),
|
|
|
|
+ morphName: self.FBXTree.Objects.Deformer[ deformerID ].attrName,
|
|
|
|
|
|
- var initialTime = curve.times[ i - 1 ];
|
|
|
|
- var timeSpan = curve.times[ i ] - initialTime;
|
|
|
|
- var interval = timeSpan / numSubIntervals;
|
|
|
|
- var nextTime = initialTime + interval;
|
|
|
|
|
|
+ };
|
|
|
|
|
|
- var interpolatedTimes = [];
|
|
|
|
- var interpolatedValues = [];
|
|
|
|
|
|
+ layerCurveNodes[ i ] = node;
|
|
|
|
|
|
- while ( nextTime < curve.times[ i ] ) {
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- interpolatedTimes.push( nextTime );
|
|
|
|
- nextTime += interval;
|
|
|
|
|
|
+ layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
|
|
|
|
|
|
- interpolatedValues.push( nextValue );
|
|
|
|
- nextValue += step;
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- curve.times = inject( curve.times, i, interpolatedTimes );
|
|
|
|
- curve.values = inject( curve.values, i, interpolatedValues );
|
|
|
|
|
|
+ } );
|
|
|
|
+
|
|
|
|
+ layersMap.set( parseInt( nodeID ), layerCurveNodes );
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ return layersMap;
|
|
|
|
|
|
- // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
|
|
|
|
- createAmbientLight: function ( sceneGraph ) {
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- if ( 'GlobalSettings' in this.FBXTree && 'AmbientColor' in this.FBXTree.GlobalSettings ) {
|
|
|
|
|
|
+ getModelAnimTransform: function ( modelNode ) {
|
|
|
|
|
|
- var ambientColor = this.FBXTree.GlobalSettings.AmbientColor.value;
|
|
|
|
- var r = ambientColor[ 0 ];
|
|
|
|
- var g = ambientColor[ 1 ];
|
|
|
|
- var b = ambientColor[ 2 ];
|
|
|
|
|
|
+ var transformData = {};
|
|
|
|
|
|
- if ( r !== 0 || g !== 0 || b !== 0 ) {
|
|
|
|
|
|
+ if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = parseInt( modelNode.RotationOrder.value );
|
|
|
|
|
|
- var color = new THREE.Color( r, g, b );
|
|
|
|
- sceneGraph.add( new THREE.AmbientLight( color, 1 ) );
|
|
|
|
|
|
+ if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
|
|
|
|
+ if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
|
|
|
|
+ if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
|
|
|
|
|
|
- },
|
|
|
|
|
|
+ if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
|
|
|
|
|
|
- setupMorphMaterials: function ( sceneGraph ) {
|
|
|
|
|
|
+ return generateTransform( transformData );
|
|
|
|
|
|
- sceneGraph.traverse( function ( child ) {
|
|
|
|
|
|
+ },
|
|
|
|
|
|
- if ( child.isMesh ) {
|
|
|
|
|
|
+ // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation
|
|
|
|
+ // hierarchy. Each Stack node will be used to create a THREE.AnimationClip
|
|
|
|
+ parseAnimStacks: function ( layersMap ) {
|
|
|
|
|
|
- if ( child.geometry.morphAttributes.position || child.geometry.morphAttributes.normal ) {
|
|
|
|
|
|
+ var rawStacks = this.FBXTree.Objects.AnimationStack;
|
|
|
|
|
|
- var uuid = child.uuid;
|
|
|
|
- var matUuid = child.material.uuid;
|
|
|
|
|
|
+ // connect the stacks (clips) up to the layers
|
|
|
|
+ var rawClips = {};
|
|
|
|
|
|
- // if a geometry has morph targets, it cannot share the material with other geometries
|
|
|
|
- var sharedMat = false;
|
|
|
|
|
|
+ for ( var nodeID in rawStacks ) {
|
|
|
|
|
|
- sceneGraph.traverse( function ( child ) {
|
|
|
|
|
|
+ var children = this.connections.get( parseInt( nodeID ) ).children;
|
|
|
|
|
|
- if ( child.isMesh ) {
|
|
|
|
|
|
+ if ( children.length > 1 ) {
|
|
|
|
|
|
- if ( child.material.uuid === matUuid && child.uuid !== uuid ) sharedMat = true;
|
|
|
|
|
|
+ // 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.' );
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ var layer = layersMap.get( children[ 0 ].ID );
|
|
|
|
|
|
- if ( sharedMat === true ) child.material = child.material.clone();
|
|
|
|
|
|
+ rawClips[ nodeID ] = {
|
|
|
|
|
|
- child.material.morphTargets = true;
|
|
|
|
|
|
+ name: rawStacks[ nodeID ].attrName,
|
|
|
|
+ layer: layer,
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ };
|
|
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- } );
|
|
|
|
|
|
+ return rawClips;
|
|
|
|
|
|
},
|
|
},
|
|
|
|
|