|
9 | 9 | * |
10 | 10 | * Needs Support: |
11 | 11 | * Morph normals / blend shape normals |
12 | | - * Animation tracks for morph targets |
13 | | - * |
14 | | - * Euler rotation order |
15 | 12 | * |
16 | 13 | * FBX format references: |
17 | 14 | * https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure |
|
817 | 814 |
|
818 | 815 | }, null ); |
819 | 816 |
|
820 | | - var preTransform = new THREE.Matrix4(); |
821 | | - |
822 | 817 | // TODO: if there is more than one model associated with the geometry, AND the models have |
823 | 818 | // different geometric transforms, then this will cause problems |
824 | 819 | // if ( modelNodes.length > 1 ) { } |
825 | 820 |
|
826 | 821 | // For now just assume one model and get the preRotations from that |
827 | 822 | var modelNode = modelNodes[ 0 ]; |
828 | 823 |
|
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 = {}; |
843 | 825 |
|
844 | | - if ( 'GeometricScaling' in modelNode ) { |
| 826 | + if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = modelNode.RotationOrder.value; |
845 | 827 |
|
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; |
847 | 831 |
|
848 | | - } |
| 832 | + var transform = generateTransform( transformData ); |
849 | 833 |
|
850 | | - return genGeometry( FBXTree, geoNode, skeleton, morphTarget, preTransform ); |
| 834 | + return genGeometry( FBXTree, geoNode, skeleton, morphTarget, transform ); |
851 | 835 |
|
852 | 836 | } |
853 | 837 |
|
|
1647 | 1631 |
|
1648 | 1632 | bindSkeleton( FBXTree, deformers.skeletons, geometryMap, modelMap, connections ); |
1649 | 1633 | addAnimations( FBXTree, connections, sceneGraph ); |
| 1634 | + |
1650 | 1635 | createAmbientLight( FBXTree, sceneGraph ); |
1651 | 1636 |
|
1652 | 1637 | setupMorphMaterials( sceneGraph ); |
|
1734 | 1719 |
|
1735 | 1720 | } |
1736 | 1721 |
|
1737 | | - setModelTransforms( FBXTree, model, node ); |
| 1722 | + setModelTransforms( model, node ); |
1738 | 1723 | modelMap.set( id, model ); |
1739 | 1724 |
|
1740 | 1725 | } |
|
2147 | 2132 | } |
2148 | 2133 |
|
2149 | 2134 | // parse the model node for transform details and apply them to the model |
2150 | | - function setModelTransforms( FBXTree, model, modelNode ) { |
| 2135 | + function setModelTransforms( model, modelNode ) { |
2151 | 2136 |
|
2152 | | - // http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html |
2153 | | - if ( 'RotationOrder' in modelNode ) { |
| 2137 | + var transformData = {}; |
2154 | 2138 |
|
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 ); |
2164 | 2140 |
|
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; |
2166 | 2143 |
|
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; |
2168 | 2146 |
|
2169 | | - // model.rotation.order = enums[ value ]; |
| 2147 | + if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value; |
2170 | 2148 |
|
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; |
2173 | 2150 |
|
2174 | | - } else if ( value === 6 ) { |
| 2151 | + var transform = generateTransform( transformData ); |
2175 | 2152 |
|
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 ); |
2213 | 2154 |
|
2214 | 2155 | } |
2215 | 2156 |
|
|
2297 | 2238 | var curveNodesMap = parseAnimationCurveNodes( FBXTree ); |
2298 | 2239 |
|
2299 | 2240 | parseAnimationCurves( FBXTree, connections, curveNodesMap ); |
| 2241 | + |
2300 | 2242 | var layersMap = parseAnimationLayers( FBXTree, connections, curveNodesMap ); |
2301 | 2243 | var rawClips = parseAnimStacks( FBXTree, connections, layersMap ); |
2302 | 2244 |
|
|
2380 | 2322 |
|
2381 | 2323 | curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve; |
2382 | 2324 |
|
2383 | | - } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) ) { |
| 2325 | + } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) { |
2384 | 2326 |
|
2385 | 2327 | curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve; |
2386 | 2328 |
|
|
2439 | 2381 | initialPosition: [ 0, 0, 0 ], |
2440 | 2382 | initialRotation: [ 0, 0, 0 ], |
2441 | 2383 | initialScale: [ 1, 1, 1 ], |
| 2384 | + transform: getModelAnimTransform( rawModel ), |
2442 | 2385 |
|
2443 | 2386 | }; |
2444 | 2387 |
|
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 ); |
2448 | 2389 |
|
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 |
2452 | 2391 | // animation value as well |
2453 | 2392 | if ( 'PreRotation' in rawModel ) node.preRotations = rawModel.PreRotation.value; |
| 2393 | + if ( 'PostRotation' in rawModel ) node.postRotations = rawModel.PostRotation.value; |
2454 | 2394 |
|
2455 | 2395 | layerCurveNodes[ i ] = node; |
2456 | 2396 |
|
|
2507 | 2447 |
|
2508 | 2448 | } |
2509 | 2449 |
|
| 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 | + |
2510 | 2470 | // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation |
2511 | 2471 | // hierarchy. Each Stack node will be used to create a THREE.AnimationClip |
2512 | 2472 | function parseAnimStacks( FBXTree, connections, layersMap ) { |
|
2582 | 2542 |
|
2583 | 2543 | var tracks = []; |
2584 | 2544 |
|
| 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 | + |
2585 | 2555 | if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) { |
2586 | 2556 |
|
2587 | | - var positionTrack = generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, rawTracks.initialPosition, 'position' ); |
| 2557 | + var positionTrack = generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' ); |
2588 | 2558 | if ( positionTrack !== undefined ) tracks.push( positionTrack ); |
2589 | 2559 |
|
2590 | 2560 | } |
2591 | 2561 |
|
2592 | 2562 | if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) { |
2593 | 2563 |
|
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 ); |
2595 | 2565 | if ( rotationTrack !== undefined ) tracks.push( rotationTrack ); |
2596 | 2566 |
|
2597 | 2567 | } |
2598 | 2568 |
|
2599 | 2569 | if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) { |
2600 | 2570 |
|
2601 | | - var scaleTrack = generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, rawTracks.initialScale, 'scale' ); |
| 2571 | + var scaleTrack = generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' ); |
2602 | 2572 | if ( scaleTrack !== undefined ) tracks.push( scaleTrack ); |
2603 | 2573 |
|
2604 | 2574 | } |
2605 | 2575 |
|
2606 | 2576 | if ( rawTracks.DeformPercent !== undefined ) { |
| 2577 | + |
2607 | 2578 | var morphTrack = generateMorphTrack( rawTracks, sceneGraph ); |
2608 | 2579 | if ( morphTrack !== undefined ) tracks.push( morphTrack ); |
2609 | 2580 |
|
|
2622 | 2593 |
|
2623 | 2594 | } |
2624 | 2595 |
|
2625 | | - function generateRotationTrack( modelName, curves, initialValue, preRotations ) { |
| 2596 | + function generateRotationTrack( modelName, curves, initialValue, preRotations, postRotations ) { |
2626 | 2597 |
|
2627 | 2598 | if ( curves.x !== undefined ) { |
2628 | 2599 |
|
|
2656 | 2627 |
|
2657 | 2628 | } |
2658 | 2629 |
|
| 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 | + |
2659 | 2640 | var quaternion = new THREE.Quaternion(); |
2660 | 2641 | var euler = new THREE.Euler(); |
2661 | 2642 |
|
|
3802 | 3783 |
|
3803 | 3784 | } |
3804 | 3785 |
|
| 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 | + } |
3805 | 3878 |
|
3806 | 3879 | // Parses comma separated list of numbers and returns them an array. |
3807 | 3880 | // Used internally by the TextParser |
|
0 commit comments