Skip to content

Commit cb5fafa

Browse files
authored
Merge pull request #14523 from looeee/fbxloader_euler
FBXLoader: support for alternate Euler orders and post rotation
2 parents 7d29bcf + 9eafb9e commit cb5fafa

File tree

1 file changed

+163
-90
lines changed

1 file changed

+163
-90
lines changed

examples/js/loaders/FBXLoader.js

Lines changed: 163 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
*
1010
* Needs Support:
1111
* Morph normals / blend shape normals
12-
* Animation tracks for morph targets
13-
*
14-
* Euler rotation order
1512
*
1613
* FBX format references:
1714
* https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
@@ -817,37 +814,24 @@
817814

818815
}, null );
819816

820-
var preTransform = new THREE.Matrix4();
821-
822817
// TODO: if there is more than one model associated with the geometry, AND the models have
823818
// different geometric transforms, then this will cause problems
824819
// if ( modelNodes.length > 1 ) { }
825820

826821
// For now just assume one model and get the preRotations from that
827822
var modelNode = modelNodes[ 0 ];
828823

829-
if ( 'GeometricRotation' in modelNode ) {
830-
831-
var array = modelNode.GeometricRotation.value.map( THREE.Math.degToRad );
832-
array[ 3 ] = 'ZYX';
833-
834-
preTransform.makeRotationFromEuler( new THREE.Euler().fromArray( array ) );
835-
836-
}
837-
838-
if ( 'GeometricTranslation' in modelNode ) {
839-
840-
preTransform.setPosition( new THREE.Vector3().fromArray( modelNode.GeometricTranslation.value ) );
841-
842-
}
824+
var transformData = {};
843825

844-
if ( 'GeometricScaling' in modelNode ) {
826+
if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = modelNode.RotationOrder.value;
845827

846-
preTransform.scale( new THREE.Vector3().fromArray( modelNode.GeometricScaling.value ) );
828+
if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value;
829+
if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value;
830+
if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value;
847831

848-
}
832+
var transform = generateTransform( transformData );
849833

850-
return genGeometry( FBXTree, geoNode, skeleton, morphTarget, preTransform );
834+
return genGeometry( FBXTree, geoNode, skeleton, morphTarget, transform );
851835

852836
}
853837

@@ -1647,6 +1631,7 @@
16471631

16481632
bindSkeleton( FBXTree, deformers.skeletons, geometryMap, modelMap, connections );
16491633
addAnimations( FBXTree, connections, sceneGraph );
1634+
16501635
createAmbientLight( FBXTree, sceneGraph );
16511636

16521637
setupMorphMaterials( sceneGraph );
@@ -1734,7 +1719,7 @@
17341719

17351720
}
17361721

1737-
setModelTransforms( FBXTree, model, node );
1722+
setModelTransforms( model, node );
17381723
modelMap.set( id, model );
17391724

17401725
}
@@ -2147,69 +2132,25 @@
21472132
}
21482133

21492134
// parse the model node for transform details and apply them to the model
2150-
function setModelTransforms( FBXTree, model, modelNode ) {
2135+
function setModelTransforms( model, modelNode ) {
21512136

2152-
// http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
2153-
if ( 'RotationOrder' in modelNode ) {
2137+
var transformData = {};
21542138

2155-
var enums = [
2156-
'XYZ', // default
2157-
'XZY',
2158-
'YZX',
2159-
'ZXY',
2160-
'YXZ',
2161-
'ZYX',
2162-
'SphericXYZ',
2163-
];
2139+
if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = parseInt( modelNode.RotationOrder.value );
21642140

2165-
var value = parseInt( modelNode.RotationOrder.value, 10 );
2141+
if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
2142+
if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
21662143

2167-
if ( value > 0 && value < 6 ) {
2144+
if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
2145+
if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
21682146

2169-
// model.rotation.order = enums[ value ];
2147+
if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
21702148

2171-
// Note: Euler order other than XYZ is currently not supported, so just display a warning for now
2172-
console.warn( 'THREE.FBXLoader: unsupported Euler Order: %s. Currently only XYZ order is supported. Animations and rotations may be incorrect.', enums[ value ] );
2149+
if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
21732150

2174-
} else if ( value === 6 ) {
2151+
var transform = generateTransform( transformData );
21752152

2176-
console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
2177-
2178-
}
2179-
2180-
}
2181-
2182-
if ( 'Lcl_Translation' in modelNode ) {
2183-
2184-
model.position.fromArray( modelNode.Lcl_Translation.value );
2185-
2186-
}
2187-
2188-
if ( 'Lcl_Rotation' in modelNode ) {
2189-
2190-
var rotation = modelNode.Lcl_Rotation.value.map( THREE.Math.degToRad );
2191-
rotation.push( 'ZYX' );
2192-
model.quaternion.setFromEuler( new THREE.Euler().fromArray( rotation ) );
2193-
2194-
}
2195-
2196-
if ( 'Lcl_Scaling' in modelNode ) {
2197-
2198-
model.scale.fromArray( modelNode.Lcl_Scaling.value );
2199-
2200-
}
2201-
2202-
if ( 'PreRotation' in modelNode ) {
2203-
2204-
var array = modelNode.PreRotation.value.map( THREE.Math.degToRad );
2205-
array[ 3 ] = 'ZYX';
2206-
2207-
var preRotations = new THREE.Euler().fromArray( array );
2208-
2209-
preRotations = new THREE.Quaternion().setFromEuler( preRotations );
2210-
model.quaternion.premultiply( preRotations );
2211-
2212-
}
2153+
model.applyMatrix( transform );
22132154

22142155
}
22152156

@@ -2297,6 +2238,7 @@
22972238
var curveNodesMap = parseAnimationCurveNodes( FBXTree );
22982239

22992240
parseAnimationCurves( FBXTree, connections, curveNodesMap );
2241+
23002242
var layersMap = parseAnimationLayers( FBXTree, connections, curveNodesMap );
23012243
var rawClips = parseAnimStacks( FBXTree, connections, layersMap );
23022244

@@ -2380,7 +2322,7 @@
23802322

23812323
curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
23822324

2383-
} else if ( animationCurveRelationship.match( /d|DeformPercent/ ) ) {
2325+
} else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
23842326

23852327
curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
23862328

@@ -2439,18 +2381,16 @@
24392381
initialPosition: [ 0, 0, 0 ],
24402382
initialRotation: [ 0, 0, 0 ],
24412383
initialScale: [ 1, 1, 1 ],
2384+
transform: getModelAnimTransform( rawModel ),
24422385

24432386
};
24442387

2445-
if ( 'Lcl_Translation' in rawModel ) node.initialPosition = rawModel.Lcl_Translation.value;
2446-
2447-
if ( 'Lcl_Rotation' in rawModel ) node.initialRotation = rawModel.Lcl_Rotation.value;
2388+
node.transform = getModelAnimTransform( rawModel );
24482389

2449-
if ( 'Lcl_Scaling' in rawModel ) node.initialScale = rawModel.Lcl_Scaling.value;
2450-
2451-
// if the animated model is pre rotated, we'll have to apply the pre rotations to every
2390+
// if the animated model is pre or post rotated, we'll have to apply the pre rotations to every
24522391
// animation value as well
24532392
if ( 'PreRotation' in rawModel ) node.preRotations = rawModel.PreRotation.value;
2393+
if ( 'PostRotation' in rawModel ) node.postRotations = rawModel.PostRotation.value;
24542394

24552395
layerCurveNodes[ i ] = node;
24562396

@@ -2507,6 +2447,26 @@
25072447

25082448
}
25092449

2450+
function getModelAnimTransform( modelNode ) {
2451+
2452+
var transformData = {};
2453+
2454+
if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = parseInt( modelNode.RotationOrder.value );
2455+
2456+
if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
2457+
if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
2458+
2459+
if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
2460+
if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
2461+
2462+
if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
2463+
2464+
if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
2465+
2466+
return generateTransform( transformData );
2467+
2468+
}
2469+
25102470
// parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation
25112471
// hierarchy. Each Stack node will be used to create a THREE.AnimationClip
25122472
function parseAnimStacks( FBXTree, connections, layersMap ) {
@@ -2582,28 +2542,39 @@
25822542

25832543
var tracks = [];
25842544

2545+
var initialPosition = new THREE.Vector3();
2546+
var initialRotation = new THREE.Quaternion();
2547+
var initialScale = new THREE.Vector3();
2548+
2549+
if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
2550+
2551+
initialPosition = initialPosition.toArray();
2552+
initialRotation = new THREE.Euler().setFromQuaternion( initialRotation ).toArray(); // todo: euler order
2553+
initialScale = initialScale.toArray();
2554+
25852555
if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
25862556

2587-
var positionTrack = generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, rawTracks.initialPosition, 'position' );
2557+
var positionTrack = generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
25882558
if ( positionTrack !== undefined ) tracks.push( positionTrack );
25892559

25902560
}
25912561

25922562
if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
25932563

2594-
var rotationTrack = generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.initialRotation, rawTracks.preRotations );
2564+
var rotationTrack = generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotations, rawTracks.postRotations );
25952565
if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
25962566

25972567
}
25982568

25992569
if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) {
26002570

2601-
var scaleTrack = generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, rawTracks.initialScale, 'scale' );
2571+
var scaleTrack = generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
26022572
if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
26032573

26042574
}
26052575

26062576
if ( rawTracks.DeformPercent !== undefined ) {
2577+
26072578
var morphTrack = generateMorphTrack( rawTracks, sceneGraph );
26082579
if ( morphTrack !== undefined ) tracks.push( morphTrack );
26092580

@@ -2622,7 +2593,7 @@
26222593

26232594
}
26242595

2625-
function generateRotationTrack( modelName, curves, initialValue, preRotations ) {
2596+
function generateRotationTrack( modelName, curves, initialValue, preRotations, postRotations ) {
26262597

26272598
if ( curves.x !== undefined ) {
26282599

@@ -2656,6 +2627,16 @@
26562627

26572628
}
26582629

2630+
if ( postRotations !== undefined ) {
2631+
2632+
postRotations = postRotations.map( THREE.Math.degToRad );
2633+
postRotations.push( 'ZYX' );
2634+
2635+
postRotations = new THREE.Euler().fromArray( postRotations );
2636+
postRotations = new THREE.Quaternion().setFromEuler( postRotations );
2637+
2638+
}
2639+
26592640
var quaternion = new THREE.Quaternion();
26602641
var euler = new THREE.Euler();
26612642

@@ -3802,6 +3783,98 @@
38023783

38033784
}
38043785

3786+
var tempMat = new THREE.Matrix4();
3787+
var tempEuler = new THREE.Euler();
3788+
var tempVec = new THREE.Vector3();
3789+
var translation = new THREE.Vector3();
3790+
var rotation = new THREE.Matrix4();
3791+
3792+
// generate transformation from FBX transform data
3793+
// ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm
3794+
// transformData = {
3795+
// eulerOrder: int,
3796+
// translation: [],
3797+
// rotationOffset: [],
3798+
// preRotation
3799+
// rotation
3800+
// postRotation
3801+
// scale
3802+
// }
3803+
// all entries are optional
3804+
function generateTransform( transformData ) {
3805+
3806+
var transform = new THREE.Matrix4();
3807+
translation.set( 0, 0, 0 );
3808+
rotation.identity();
3809+
3810+
var order = ( transformData.eulerOrder ) ? getEulerOrder( transformData.eulerOrder ) : getEulerOrder( 0 );
3811+
3812+
if ( transformData.translation ) translation.fromArray( transformData.translation );
3813+
if ( transformData.rotationOffset ) translation.add( tempVec.fromArray( transformData.rotationOffset ) );
3814+
3815+
if ( transformData.rotation ) {
3816+
3817+
var array = transformData.rotation.map( THREE.Math.degToRad );
3818+
array.push( order );
3819+
rotation.makeRotationFromEuler( tempEuler.fromArray( array ) );
3820+
3821+
}
3822+
3823+
if ( transformData.preRotation ) {
3824+
3825+
var array = transformData.preRotation.map( THREE.Math.degToRad );
3826+
array.push( order );
3827+
tempMat.makeRotationFromEuler( tempEuler.fromArray( array ) );
3828+
3829+
rotation.premultiply( tempMat );
3830+
3831+
}
3832+
3833+
if ( transformData.postRotation ) {
3834+
3835+
var array = transformData.postRotation.map( THREE.Math.degToRad );
3836+
array.push( order );
3837+
tempMat.makeRotationFromEuler( tempEuler.fromArray( array ) );
3838+
3839+
tempMat.getInverse( tempMat );
3840+
3841+
rotation.multiply( tempMat );
3842+
3843+
}
3844+
3845+
if ( transformData.scale ) transform.scale( tempVec.fromArray( transformData.scale ) );
3846+
3847+
transform.setPosition( translation );
3848+
transform.multiply( rotation );
3849+
3850+
return transform;
3851+
3852+
}
3853+
3854+
// Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order
3855+
// ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
3856+
function getEulerOrder( order ) {
3857+
3858+
var enums = [
3859+
'ZYX', // -> XYZ extrinsic
3860+
'YZX', // -> XZY extrinsic
3861+
'XZY', // -> YZX extrinsic
3862+
'ZXY', // -> YXZ extrinsic
3863+
'YXZ', // -> ZXY extrinsic
3864+
'XYZ', // -> ZYX extrinsic
3865+
//'SphericXYZ', // not possible to support
3866+
];
3867+
3868+
if ( order === 6 ) {
3869+
3870+
console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
3871+
return enums[ 0 ];
3872+
3873+
}
3874+
3875+
return enums[ order ];
3876+
3877+
}
38053878

38063879
// Parses comma separated list of numbers and returns them an array.
38073880
// Used internally by the TextParser

0 commit comments

Comments
 (0)