@@ -1187,6 +1187,8 @@ protected bool ExportTransform (UnityEngine.Transform unityTransform, FbxNode fb
1187
1187
// This causes issues when converting euler to quaternion, causing the final
1188
1188
// rotation to be slighlty off.
1189
1189
// Fixed by exporting the rotations as eulers with XYZ rotation order.
1190
+ // Can't just set the rotation order to ZXY on export as Maya incorrectly imports the
1191
+ // rotation. Appears to first convert to XYZ rotation then set rotation order to ZXY.
1190
1192
fbxNode . SetRotationOrder ( FbxNode . EPivotSet . eSourcePivot , FbxEuler . EOrder . eOrderXYZ ) ;
1191
1193
1192
1194
UnityEngine . Vector3 unityTranslate ;
@@ -1590,10 +1592,8 @@ public UnityToMayaConvertSceneHelper(string uniPropertyName)
1590
1592
1591
1593
bool partT = uniPropertyName . StartsWith ( "m_LocalPosition." , cc ) ;
1592
1594
bool partTx = uniPropertyName . EndsWith ( "Position.x" , cc ) || uniPropertyName . EndsWith ( "T.x" , cc ) ;
1593
- bool partRy = uniPropertyName . Equals ( "localEulerAnglesRaw.y" , cc ) ;
1594
- bool partRz = uniPropertyName . Equals ( "localEulerAnglesRaw.z" , cc ) ;
1595
1595
1596
- convertLtoR |= partTx || partRy || partRz ;
1596
+ convertLtoR |= partTx ;
1597
1597
1598
1598
convertDistance |= partT ;
1599
1599
convertDistance |= uniPropertyName . StartsWith ( "m_Intensity" , cc ) ;
@@ -1648,21 +1648,6 @@ public static bool TryGetValue(string uniPropertyName, out FbxPropertyChannelPai
1648
1648
return true ;
1649
1649
}
1650
1650
1651
- // Transform Rotation (EULER)
1652
- // NOTE: Quaternion Rotation handled by QuaternionCurve
1653
- if ( uniPropertyName . StartsWith ( "localEulerAnglesRaw.x" , ct ) ) {
1654
- prop = new FbxPropertyChannelPair ( "Lcl Rotation" , Globals . FBXSDK_CURVENODE_COMPONENT_X ) ;
1655
- return true ;
1656
- }
1657
- if ( uniPropertyName . StartsWith ( "localEulerAnglesRaw.y" , ct ) ) {
1658
- prop = new FbxPropertyChannelPair ( "Lcl Rotation" , Globals . FBXSDK_CURVENODE_COMPONENT_Y ) ;
1659
- return true ;
1660
- }
1661
- if ( uniPropertyName . StartsWith ( "localEulerAnglesRaw.z" , ct ) ) {
1662
- prop = new FbxPropertyChannelPair ( "Lcl Rotation" , Globals . FBXSDK_CURVENODE_COMPONENT_Z ) ;
1663
- return true ;
1664
- }
1665
-
1666
1651
// Transform Translation
1667
1652
if ( uniPropertyName . StartsWith ( "m_LocalPosition.x" , ct ) || uniPropertyName . EndsWith ( "T.x" , ct ) ) {
1668
1653
prop = new FbxPropertyChannelPair ( "Lcl Translation" , Globals . FBXSDK_CURVENODE_COMPONENT_X ) ;
@@ -1745,6 +1730,135 @@ public void Dispose()
1745
1730
}
1746
1731
}
1747
1732
1733
+ class EulerCurve {
1734
+ public double sampleRate ;
1735
+ public AnimationCurve x ;
1736
+ public AnimationCurve y ;
1737
+ public AnimationCurve z ;
1738
+
1739
+ public struct Key {
1740
+ public FbxTime time ;
1741
+ public FbxVector4 euler ;
1742
+ }
1743
+
1744
+ public EulerCurve ( ) { }
1745
+
1746
+ public static int GetEulerIndex ( string uniPropertyName ) {
1747
+ System . StringComparison ct = System . StringComparison . CurrentCulture ;
1748
+ bool isEulerComponent = uniPropertyName . StartsWith ( "localEulerAnglesRaw." , ct ) ;
1749
+
1750
+ if ( ! isEulerComponent ) { return - 1 ; }
1751
+
1752
+ switch ( uniPropertyName [ uniPropertyName . Length - 1 ] ) {
1753
+ case 'x' : return 0 ;
1754
+ case 'y' : return 1 ;
1755
+ case 'z' : return 2 ;
1756
+ default : return - 1 ;
1757
+ }
1758
+ }
1759
+
1760
+ public void SetCurve ( int i , AnimationCurve curve ) {
1761
+ switch ( i ) {
1762
+ case 0 : x = curve ; break ;
1763
+ case 1 : y = curve ; break ;
1764
+ case 2 : z = curve ; break ;
1765
+ default : throw new System . IndexOutOfRangeException ( ) ;
1766
+ }
1767
+ }
1768
+
1769
+ Key [ ] ComputeKeys ( UnityEngine . Vector3 restRotation , FbxNode node ) {
1770
+ // Get the source pivot pre-rotation if any, so we can
1771
+ // remove it from the animation we get from Unity.
1772
+ var fbxPreRotationEuler = node . GetRotationActive ( )
1773
+ ? node . GetPreRotation ( FbxNode . EPivotSet . eSourcePivot )
1774
+ : new FbxVector4 ( ) ;
1775
+
1776
+ // Get the inverse of the prerotation
1777
+ var fbxPreRotationInverse = ModelExporter . EulerToQuaternion ( fbxPreRotationEuler ) ;
1778
+ fbxPreRotationInverse . Inverse ( ) ;
1779
+
1780
+ // Find when we have keys set.
1781
+ var animCurves = new AnimationCurve [ ] { x , y , z } ;
1782
+ var keyTimes =
1783
+ ( FbxExporters . Editor . ModelExporter . ExportSettings . BakeAnimation )
1784
+ ? ModelExporter . GetSampleTimes ( animCurves , sampleRate )
1785
+ : ModelExporter . GetKeyTimes ( animCurves ) ;
1786
+
1787
+ // Convert to the Key type.
1788
+ var keys = new Key [ keyTimes . Count ] ;
1789
+ int i = 0 ;
1790
+ foreach ( var seconds in keyTimes ) {
1791
+
1792
+ // The final animation, including the effect of pre-rotation.
1793
+ // If we have no curve, assume the node has the correct rotation right now.
1794
+ // We need to evaluate since we might only have keys in one of the axes.
1795
+ var fbxFinalAnimation = Quaternion . Euler (
1796
+ ( x == null ) ? restRotation [ 0 ] : x . Evaluate ( seconds ) ,
1797
+ ( y == null ) ? restRotation [ 1 ] : y . Evaluate ( seconds ) ,
1798
+ ( z == null ) ? restRotation [ 2 ] : z . Evaluate ( seconds )
1799
+ ) ;
1800
+
1801
+ // convert the final animation to righthanded coords
1802
+ var finalEuler = ModelExporter . ConvertQuaternionToXYZEuler ( fbxFinalAnimation ) ;
1803
+
1804
+ // convert it back to a quaternion for multiplication
1805
+ var quat = ModelExporter . EulerToQuaternion ( new FbxVector4 ( finalEuler ) ) ;
1806
+
1807
+ // Cancel out the pre-rotation. Order matters. FBX reads left-to-right.
1808
+ // When we run animation we will apply:
1809
+ // pre-rotation
1810
+ // then pre-rotation inverse
1811
+ // then animation.
1812
+ var fbxFinalQuat = fbxPreRotationInverse * quat ;
1813
+
1814
+ // Store the key so we can sort them later.
1815
+ Key key ;
1816
+ key . time = FbxTime . FromSecondDouble ( seconds ) ;
1817
+ key . euler = ModelExporter . QuaternionToEuler ( fbxFinalQuat ) ;
1818
+ keys [ i ++ ] = key ;
1819
+ }
1820
+
1821
+ // Sort the keys by time
1822
+ System . Array . Sort ( keys , ( Key a , Key b ) => a . time . CompareTo ( b . time ) ) ;
1823
+
1824
+ return keys ;
1825
+ }
1826
+
1827
+ public void Animate ( Transform unityTransform , FbxNode fbxNode , FbxAnimLayer fbxAnimLayer , bool Verbose ) {
1828
+
1829
+ /* Find or create the three curves. */
1830
+ var fbxAnimCurveX = fbxNode . LclRotation . GetCurve ( fbxAnimLayer , Globals . FBXSDK_CURVENODE_COMPONENT_X , true ) ;
1831
+ var fbxAnimCurveY = fbxNode . LclRotation . GetCurve ( fbxAnimLayer , Globals . FBXSDK_CURVENODE_COMPONENT_Y , true ) ;
1832
+ var fbxAnimCurveZ = fbxNode . LclRotation . GetCurve ( fbxAnimLayer , Globals . FBXSDK_CURVENODE_COMPONENT_Z , true ) ;
1833
+
1834
+ /* set the keys */
1835
+ using ( new FbxAnimCurveModifyHelper ( new List < FbxAnimCurve > { fbxAnimCurveX , fbxAnimCurveY , fbxAnimCurveZ } ) )
1836
+ {
1837
+ foreach ( var key in ComputeKeys ( unityTransform . localRotation . eulerAngles , fbxNode ) ) {
1838
+
1839
+ int i = fbxAnimCurveX . KeyAdd ( key . time ) ;
1840
+ fbxAnimCurveX . KeySet ( i , key . time , ( float ) key . euler . X ) ;
1841
+
1842
+ i = fbxAnimCurveY . KeyAdd ( key . time ) ;
1843
+ fbxAnimCurveY . KeySet ( i , key . time , ( float ) key . euler . Y ) ;
1844
+
1845
+ i = fbxAnimCurveZ . KeyAdd ( key . time ) ;
1846
+ fbxAnimCurveZ . KeySet ( i , key . time , ( float ) key . euler . Z ) ;
1847
+ }
1848
+ }
1849
+
1850
+ // Uni-35616 unroll curves to preserve continuous rotations
1851
+ var fbxCurveNode = fbxNode . LclRotation . GetCurveNode ( fbxAnimLayer , false /*should already exist*/ ) ;
1852
+
1853
+ FbxAnimCurveFilterUnroll fbxAnimUnrollFilter = new FbxAnimCurveFilterUnroll ( ) ;
1854
+ fbxAnimUnrollFilter . Apply ( fbxCurveNode ) ;
1855
+
1856
+ if ( Verbose ) {
1857
+ Debug . Log ( "Exported rotation animation for " + fbxNode . GetName ( ) ) ;
1858
+ }
1859
+ }
1860
+ }
1861
+
1748
1862
/// <summary>
1749
1863
/// Exporting rotations is more complicated. We need to convert
1750
1864
/// from quaternion to euler. We use this class to help.
@@ -1935,6 +2049,7 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
1935
2049
* (which is how it should be) but FBX uses Euler angles. So we
1936
2050
* need to gather up the list of transform curves per object. */
1937
2051
var quaternions = new Dictionary < UnityEngine . GameObject , QuaternionCurve > ( ) ;
2052
+ var eulers = new Dictionary < GameObject , EulerCurve > ( ) ;
1938
2053
1939
2054
foreach ( EditorCurveBinding uniCurveBinding in AnimationUtility . GetCurveBindings ( uniAnimClip ) ) {
1940
2055
Object uniObj = AnimationUtility . GetAnimatedObject ( uniRoot , uniCurveBinding ) ;
@@ -1950,26 +2065,46 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
1950
2065
}
1951
2066
1952
2067
int index = QuaternionCurve . GetQuaternionIndex ( uniCurveBinding . propertyName ) ;
1953
- if ( index == - 1 )
1954
- {
1955
- /* simple property (e.g. intensity), export right away */
1956
- ExportAnimationCurve ( uniObj , uniAnimCurve , uniAnimClip . frameRate ,
1957
- uniCurveBinding . propertyName ,
1958
- fbxScene ,
1959
- fbxAnimLayer ) ;
1960
- } else {
2068
+ if ( index >= 0 ) {
1961
2069
/* Rotation property; save it to convert quaternion -> euler later. */
1962
2070
1963
2071
var uniGO = GetGameObject ( uniObj ) ;
1964
- if ( ! uniGO ) { continue ; }
2072
+ if ( ! uniGO ) {
2073
+ continue ;
2074
+ }
1965
2075
1966
2076
QuaternionCurve quat ;
1967
2077
if ( ! quaternions . TryGetValue ( uniGO , out quat ) ) {
1968
- quat = new QuaternionCurve { sampleRate = uniAnimClip . frameRate } ;
2078
+ quat = new QuaternionCurve { sampleRate = uniAnimClip . frameRate } ;
1969
2079
quaternions . Add ( uniGO , quat ) ;
1970
2080
}
1971
2081
quat . SetCurve ( index , uniAnimCurve ) ;
2082
+
2083
+ continue ;
2084
+ }
2085
+
2086
+ index = EulerCurve . GetEulerIndex ( uniCurveBinding . propertyName ) ;
2087
+ if ( index >= 0 ) {
2088
+ var uniGO = GetGameObject ( uniObj ) ;
2089
+ if ( ! uniGO ) {
2090
+ continue ;
2091
+ }
2092
+
2093
+ EulerCurve euler ;
2094
+ if ( ! eulers . TryGetValue ( uniGO , out euler ) ) {
2095
+ euler = new EulerCurve { sampleRate = uniAnimClip . frameRate } ;
2096
+ eulers . Add ( uniGO , euler ) ;
2097
+ }
2098
+ euler . SetCurve ( index , uniAnimCurve ) ;
2099
+
2100
+ continue ;
1972
2101
}
2102
+
2103
+ /* simple property (e.g. intensity), export right away */
2104
+ ExportAnimationCurve ( uniObj , uniAnimCurve , uniAnimClip . frameRate ,
2105
+ uniCurveBinding . propertyName ,
2106
+ fbxScene ,
2107
+ fbxAnimLayer ) ;
1973
2108
}
1974
2109
1975
2110
/* now export all the quaternion curves */
@@ -1984,6 +2119,18 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
1984
2119
}
1985
2120
quat . Animate ( unityGo . transform , fbxNode , fbxAnimLayer , Verbose ) ;
1986
2121
}
2122
+
2123
+ foreach ( var kvp in eulers ) {
2124
+ var unityGo = kvp . Key ;
2125
+ var euler = kvp . Value ;
2126
+
2127
+ FbxNode fbxNode ;
2128
+ if ( ! MapUnityObjectToFbxNode . TryGetValue ( unityGo , out fbxNode ) ) {
2129
+ Debug . LogError ( string . Format ( "no FbxNode found for {0}" , unityGo . name ) ) ;
2130
+ continue ;
2131
+ }
2132
+ euler . Animate ( unityGo . transform , fbxNode , fbxAnimLayer , Verbose ) ;
2133
+ }
1987
2134
}
1988
2135
1989
2136
/// <summary>
0 commit comments