|
@@ -9,9 +9,6 @@
|
|
|
*
|
|
|
* Needs Support:
|
|
|
* Morph normals / blend shape normals
|
|
|
- * Animation tracks for morph targets
|
|
|
- *
|
|
|
- * Euler rotation order
|
|
|
*
|
|
|
* FBX format references:
|
|
|
* https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
|
|
@@ -839,8 +836,6 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
}, null );
|
|
|
|
|
|
- var preTransform = new THREE.Matrix4();
|
|
|
-
|
|
|
// TODO: if there is more than one model associated with the geometry, AND the models have
|
|
|
// different geometric transforms, then this will cause problems
|
|
|
// if ( modelNodes.length > 1 ) { }
|
|
@@ -848,28 +843,16 @@ THREE.FBXLoader = ( function () {
|
|
|
// 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 );
|
|
|
- array[ 3 ] = 'ZYX';
|
|
|
-
|
|
|
- preTransform.makeRotationFromEuler( new THREE.Euler().fromArray( array ) );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( 'GeometricTranslation' in modelNode ) {
|
|
|
-
|
|
|
- preTransform.setPosition( new THREE.Vector3().fromArray( modelNode.GeometricTranslation.value ) );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( 'GeometricScaling' in modelNode ) {
|
|
|
+ var transformData = {};
|
|
|
|
|
|
- preTransform.scale( new THREE.Vector3().fromArray( modelNode.GeometricScaling.value ) );
|
|
|
+ if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = modelNode.RotationOrder.value;
|
|
|
+ if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value;
|
|
|
+ if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value;
|
|
|
+ if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value;
|
|
|
|
|
|
- }
|
|
|
+ var transform = generateTransform( transformData );
|
|
|
|
|
|
- return this.genGeometry( geoNode, skeleton, morphTarget, preTransform );
|
|
|
+ return this.genGeometry( geoNode, skeleton, morphTarget, transform );
|
|
|
|
|
|
},
|
|
|
|
|
@@ -1637,6 +1620,7 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
|
|
|
this.addAnimations( sceneGraph );
|
|
|
+
|
|
|
this.createAmbientLight( sceneGraph );
|
|
|
|
|
|
this.setupMorphMaterials( sceneGraph );
|
|
@@ -2054,67 +2038,19 @@ THREE.FBXLoader = ( function () {
|
|
|
// parse the model node for transform details and apply them to the model
|
|
|
setModelTransforms: function ( model, modelNode ) {
|
|
|
|
|
|
- // http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
|
|
|
- if ( 'RotationOrder' in modelNode ) {
|
|
|
-
|
|
|
- var enums = [
|
|
|
- 'XYZ', // default
|
|
|
- 'XZY',
|
|
|
- 'YZX',
|
|
|
- 'ZXY',
|
|
|
- 'YXZ',
|
|
|
- 'ZYX',
|
|
|
- 'SphericXYZ',
|
|
|
- ];
|
|
|
-
|
|
|
- var value = parseInt( modelNode.RotationOrder.value, 10 );
|
|
|
-
|
|
|
- if ( value > 0 && value < 6 ) {
|
|
|
-
|
|
|
- // model.rotation.order = enums[ value ];
|
|
|
-
|
|
|
- // Note: Euler order other than XYZ is currently not supported, so just display a warning for now
|
|
|
- console.warn( 'THREE.FBXLoader: unsupported Euler Order: %s. Currently only XYZ order is supported. Animations and rotations may be incorrect.', enums[ value ] );
|
|
|
+ var transformData = {};
|
|
|
|
|
|
- } else if ( value === 6 ) {
|
|
|
-
|
|
|
- console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
+ if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = parseInt( modelNode.RotationOrder.value );
|
|
|
+ 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;
|
|
|
|
|
|
- if ( 'Lcl_Translation' in modelNode ) {
|
|
|
+ var transform = generateTransform( transformData );
|
|
|
|
|
|
- model.position.fromArray( modelNode.Lcl_Translation.value );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( 'Lcl_Rotation' in modelNode ) {
|
|
|
-
|
|
|
- var rotation = modelNode.Lcl_Rotation.value.map( THREE.Math.degToRad );
|
|
|
- rotation.push( 'ZYX' );
|
|
|
- model.quaternion.setFromEuler( new THREE.Euler().fromArray( rotation ) );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( 'Lcl_Scaling' in modelNode ) {
|
|
|
-
|
|
|
- model.scale.fromArray( modelNode.Lcl_Scaling.value );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( 'PreRotation' in modelNode ) {
|
|
|
-
|
|
|
- var array = modelNode.PreRotation.value.map( THREE.Math.degToRad );
|
|
|
- array[ 3 ] = 'ZYX';
|
|
|
-
|
|
|
- var preRotations = new THREE.Euler().fromArray( array );
|
|
|
-
|
|
|
- preRotations = new THREE.Quaternion().setFromEuler( preRotations );
|
|
|
- model.quaternion.premultiply( preRotations );
|
|
|
-
|
|
|
- }
|
|
|
+ model.applyMatrix( transform );
|
|
|
|
|
|
},
|
|
|
|
|
@@ -2263,6 +2199,7 @@ THREE.FBXLoader = ( function () {
|
|
|
var curveNodesMap = this.parseAnimationCurveNodes();
|
|
|
|
|
|
this.parseAnimationCurves( curveNodesMap );
|
|
|
+
|
|
|
var layersMap = this.parseAnimationLayers( curveNodesMap );
|
|
|
var rawClips = this.parseAnimStacks( layersMap );
|
|
|
|
|
@@ -2346,7 +2283,7 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
|
|
|
|
|
|
- } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) ) {
|
|
|
+ } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
|
|
|
|
|
|
curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
|
|
|
|
|
@@ -2406,18 +2343,14 @@ THREE.FBXLoader = ( function () {
|
|
|
initialPosition: [ 0, 0, 0 ],
|
|
|
initialRotation: [ 0, 0, 0 ],
|
|
|
initialScale: [ 1, 1, 1 ],
|
|
|
+ transform: this.getModelAnimTransform( rawModel ),
|
|
|
|
|
|
};
|
|
|
|
|
|
- if ( 'Lcl_Translation' in rawModel ) node.initialPosition = rawModel.Lcl_Translation.value;
|
|
|
-
|
|
|
- if ( 'Lcl_Rotation' in rawModel ) node.initialRotation = rawModel.Lcl_Rotation.value;
|
|
|
-
|
|
|
- if ( 'Lcl_Scaling' in rawModel ) node.initialScale = rawModel.Lcl_Scaling.value;
|
|
|
-
|
|
|
// 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;
|
|
|
|
|
|
layerCurveNodes[ i ] = node;
|
|
|
|
|
@@ -2474,6 +2407,26 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
},
|
|
|
|
|
|
+ getModelAnimTransform: function ( modelNode ) {
|
|
|
+
|
|
|
+ var transformData = {};
|
|
|
+
|
|
|
+ if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = parseInt( modelNode.RotationOrder.value );
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ return generateTransform( transformData );
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
// 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 ) {
|
|
@@ -2529,23 +2482,33 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
var tracks = [];
|
|
|
|
|
|
+ var initialPosition = new THREE.Vector3();
|
|
|
+ var initialRotation = new THREE.Quaternion();
|
|
|
+ var initialScale = new THREE.Vector3();
|
|
|
+
|
|
|
+ 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 ) {
|
|
|
|
|
|
- var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, rawTracks.initialPosition, 'position' );
|
|
|
+ var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
|
|
|
if ( positionTrack !== undefined ) tracks.push( positionTrack );
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
|
|
|
|
|
|
- var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.initialRotation, rawTracks.preRotations );
|
|
|
+ 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, rawTracks.initialScale, 'scale' );
|
|
|
+ var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
|
|
|
if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
|
|
|
|
|
|
}
|
|
@@ -2570,7 +2533,7 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
},
|
|
|
|
|
|
- generateRotationTrack: function ( modelName, curves, initialValue, preRotations ) {
|
|
|
+ generateRotationTrack: function ( modelName, curves, initialValue, preRotations, postRotations ) {
|
|
|
|
|
|
if ( curves.x !== undefined ) {
|
|
|
|
|
@@ -2604,6 +2567,16 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ 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();
|
|
|
|
|
@@ -2615,7 +2588,8 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
quaternion.setFromEuler( euler );
|
|
|
|
|
|
- if ( preRotations !== undefined )quaternion.premultiply( preRotations );
|
|
|
+ if ( preRotations !== undefined ) quaternion.premultiply( preRotations );
|
|
|
+ if ( postRotations !== undefined ) quaternion.multiply( postRotations );
|
|
|
|
|
|
quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
|
|
|
|
|
@@ -3853,6 +3827,99 @@ THREE.FBXLoader = ( function () {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ var tempMat = new THREE.Matrix4();
|
|
|
+ var tempEuler = new THREE.Euler();
|
|
|
+ var tempVec = new THREE.Vector3();
|
|
|
+ var translation = new THREE.Vector3();
|
|
|
+ var rotation = new THREE.Matrix4();
|
|
|
+
|
|
|
+ // generate transformation from FBX transform data
|
|
|
+ // ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm
|
|
|
+ // transformData = {
|
|
|
+ // eulerOrder: int,
|
|
|
+ // translation: [],
|
|
|
+ // rotationOffset: [],
|
|
|
+ // preRotation
|
|
|
+ // rotation
|
|
|
+ // postRotation
|
|
|
+ // scale
|
|
|
+ // }
|
|
|
+ // all entries are optional
|
|
|
+ function generateTransform( transformData ) {
|
|
|
+
|
|
|
+ var transform = new THREE.Matrix4();
|
|
|
+ translation.set( 0, 0, 0 );
|
|
|
+ rotation.identity();
|
|
|
+
|
|
|
+ var order = ( transformData.eulerOrder ) ? getEulerOrder( transformData.eulerOrder ) : getEulerOrder( 0 );
|
|
|
+
|
|
|
+ if ( transformData.translation ) translation.fromArray( transformData.translation );
|
|
|
+ if ( transformData.rotationOffset ) translation.add( tempVec.fromArray( transformData.rotationOffset ) );
|
|
|
+
|
|
|
+ if ( transformData.rotation ) {
|
|
|
+
|
|
|
+ var array = transformData.rotation.map( THREE.Math.degToRad );
|
|
|
+ array.push( order );
|
|
|
+ rotation.makeRotationFromEuler( tempEuler.fromArray( array ) );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( transformData.preRotation ) {
|
|
|
+
|
|
|
+ var array = transformData.preRotation.map( THREE.Math.degToRad );
|
|
|
+ array.push( order );
|
|
|
+ tempMat.makeRotationFromEuler( tempEuler.fromArray( array ) );
|
|
|
+
|
|
|
+ rotation.premultiply( tempMat );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( transformData.postRotation ) {
|
|
|
+
|
|
|
+ var array = transformData.postRotation.map( THREE.Math.degToRad );
|
|
|
+ array.push( order );
|
|
|
+ tempMat.makeRotationFromEuler( tempEuler.fromArray( array ) );
|
|
|
+
|
|
|
+ tempMat.getInverse( tempMat );
|
|
|
+
|
|
|
+ rotation.multiply( tempMat );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if ( transformData.scale ) transform.scale( tempVec.fromArray( transformData.scale ) );
|
|
|
+
|
|
|
+ transform.setPosition( translation );
|
|
|
+ transform.multiply( rotation );
|
|
|
+
|
|
|
+ return transform;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order
|
|
|
+ // ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
|
|
|
+ function getEulerOrder( order ) {
|
|
|
+
|
|
|
+ var enums = [
|
|
|
+ 'ZYX', // -> XYZ extrinsic
|
|
|
+ 'YZX', // -> XZY extrinsic
|
|
|
+ 'XZY', // -> YZX extrinsic
|
|
|
+ 'ZXY', // -> YXZ extrinsic
|
|
|
+ 'YXZ', // -> ZXY extrinsic
|
|
|
+ 'XYZ', // -> ZYX extrinsic
|
|
|
+ //'SphericXYZ', // not possible to support
|
|
|
+ ];
|
|
|
+
|
|
|
+ if ( order === 6 ) {
|
|
|
+
|
|
|
+ console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
|
|
|
+ return enums[ 0 ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return enums[ order ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
// Parses comma separated list of numbers and returns them an array.
|
|
|
// Used internally by the TextParser
|
|
|
function parseNumberArray( value ) {
|