1
+ using Unity . FbxSdk ;
2
+ using UnityEngine ;
3
+ using System . Collections . Generic ;
4
+
5
+ namespace FbxExporters
6
+ {
7
+ namespace Editor
8
+ {
9
+ /// <summary>
10
+ /// Base class for QuaternionCurve and EulerCurve.
11
+ /// Provides implementation for computing keys and generating FbxAnimCurves
12
+ /// for euler rotation.
13
+ /// </summary>
14
+ public abstract class RotationCurve {
15
+ public double sampleRate ;
16
+ public AnimationCurve [ ] m_curves ;
17
+
18
+ public struct Key {
19
+ public FbxTime time ;
20
+ public FbxVector4 euler ;
21
+ }
22
+
23
+ public RotationCurve ( ) { }
24
+
25
+ public void SetCurve ( int i , AnimationCurve curve ) {
26
+ m_curves [ i ] = curve ;
27
+ }
28
+
29
+ protected abstract FbxQuaternion GetConvertedQuaternionRotation ( float seconds , UnityEngine . Quaternion restRotation ) ;
30
+
31
+ private Key [ ] ComputeKeys ( UnityEngine . Quaternion restRotation , FbxNode node ) {
32
+ // Get the source pivot pre-rotation if any, so we can
33
+ // remove it from the animation we get from Unity.
34
+ var fbxPreRotationEuler = node . GetRotationActive ( )
35
+ ? node . GetPreRotation ( FbxNode . EPivotSet . eSourcePivot )
36
+ : new FbxVector4 ( ) ;
37
+
38
+ // Get the inverse of the prerotation
39
+ var fbxPreRotationInverse = ModelExporter . EulerToQuaternion ( fbxPreRotationEuler ) ;
40
+ fbxPreRotationInverse . Inverse ( ) ;
41
+
42
+ // Find when we have keys set.
43
+ var keyTimes =
44
+ ( FbxExporters . Editor . ModelExporter . ExportSettings . BakeAnimation )
45
+ ? ModelExporter . GetSampleTimes ( m_curves , sampleRate )
46
+ : ModelExporter . GetKeyTimes ( m_curves ) ;
47
+
48
+ // Convert to the Key type.
49
+ var keys = new Key [ keyTimes . Count ] ;
50
+ int i = 0 ;
51
+ foreach ( var seconds in keyTimes ) {
52
+ var fbxFinalAnimation = GetConvertedQuaternionRotation ( seconds , restRotation ) ;
53
+
54
+ // Cancel out the pre-rotation. Order matters. FBX reads left-to-right.
55
+ // When we run animation we will apply:
56
+ // pre-rotation
57
+ // then pre-rotation inverse
58
+ // then animation.
59
+ var fbxFinalQuat = fbxPreRotationInverse * fbxFinalAnimation ;
60
+
61
+ // Store the key so we can sort them later.
62
+ Key key ;
63
+ key . time = FbxTime . FromSecondDouble ( seconds ) ;
64
+ key . euler = ModelExporter . QuaternionToEuler ( fbxFinalQuat ) ;
65
+ keys [ i ++ ] = key ;
66
+ }
67
+
68
+ // Sort the keys by time
69
+ System . Array . Sort ( keys , ( Key a , Key b ) => a . time . CompareTo ( b . time ) ) ;
70
+
71
+ return keys ;
72
+ }
73
+
74
+ public void Animate ( Transform unityTransform , FbxNode fbxNode , FbxAnimLayer fbxAnimLayer , bool Verbose ) {
75
+
76
+ /* Find or create the three curves. */
77
+ var fbxAnimCurveX = fbxNode . LclRotation . GetCurve ( fbxAnimLayer , Globals . FBXSDK_CURVENODE_COMPONENT_X , true ) ;
78
+ var fbxAnimCurveY = fbxNode . LclRotation . GetCurve ( fbxAnimLayer , Globals . FBXSDK_CURVENODE_COMPONENT_Y , true ) ;
79
+ var fbxAnimCurveZ = fbxNode . LclRotation . GetCurve ( fbxAnimLayer , Globals . FBXSDK_CURVENODE_COMPONENT_Z , true ) ;
80
+
81
+ /* set the keys */
82
+ using ( new FbxAnimCurveModifyHelper ( new List < FbxAnimCurve > { fbxAnimCurveX , fbxAnimCurveY , fbxAnimCurveZ } ) )
83
+ {
84
+ foreach ( var key in ComputeKeys ( unityTransform . localRotation , fbxNode ) ) {
85
+
86
+ int i = fbxAnimCurveX . KeyAdd ( key . time ) ;
87
+ fbxAnimCurveX . KeySet ( i , key . time , ( float ) key . euler . X ) ;
88
+
89
+ i = fbxAnimCurveY . KeyAdd ( key . time ) ;
90
+ fbxAnimCurveY . KeySet ( i , key . time , ( float ) key . euler . Y ) ;
91
+
92
+ i = fbxAnimCurveZ . KeyAdd ( key . time ) ;
93
+ fbxAnimCurveZ . KeySet ( i , key . time , ( float ) key . euler . Z ) ;
94
+ }
95
+ }
96
+
97
+ // Uni-35616 unroll curves to preserve continuous rotations
98
+ var fbxCurveNode = fbxNode . LclRotation . GetCurveNode ( fbxAnimLayer , false /*should already exist*/ ) ;
99
+
100
+ FbxAnimCurveFilterUnroll fbxAnimUnrollFilter = new FbxAnimCurveFilterUnroll ( ) ;
101
+ fbxAnimUnrollFilter . Apply ( fbxCurveNode ) ;
102
+
103
+ if ( Verbose ) {
104
+ Debug . Log ( "Exported rotation animation for " + fbxNode . GetName ( ) ) ;
105
+ }
106
+ }
107
+ }
108
+
109
+ /// <summary>
110
+ /// Convert from ZXY to XYZ euler, and remove
111
+ /// prerotation from animated rotation.
112
+ /// </summary>
113
+ public class EulerCurve : RotationCurve {
114
+ public EulerCurve ( ) { m_curves = new AnimationCurve [ 3 ] ; }
115
+
116
+ /// <summary>
117
+ /// Gets the index of the euler curve by property name.
118
+ /// x = 0, y = 1, z = 2
119
+ /// </summary>
120
+ /// <returns>The index of the curve, or -1 if property doesn't map to Euler curve.</returns>
121
+ /// <param name="uniPropertyName">Unity property name.</param>
122
+ public static int GetEulerIndex ( string uniPropertyName ) {
123
+ System . StringComparison ct = System . StringComparison . CurrentCulture ;
124
+ bool isEulerComponent = uniPropertyName . StartsWith ( "localEulerAnglesRaw." , ct ) ;
125
+
126
+ if ( ! isEulerComponent ) { return - 1 ; }
127
+
128
+ switch ( uniPropertyName [ uniPropertyName . Length - 1 ] ) {
129
+ case 'x' : return 0 ;
130
+ case 'y' : return 1 ;
131
+ case 'z' : return 2 ;
132
+ default : return - 1 ;
133
+ }
134
+ }
135
+
136
+ protected override FbxQuaternion GetConvertedQuaternionRotation ( float seconds , Quaternion restRotation )
137
+ {
138
+ var eulerRest = restRotation . eulerAngles ;
139
+ AnimationCurve x = m_curves [ 0 ] , y = m_curves [ 1 ] , z = m_curves [ 2 ] ;
140
+
141
+ // The final animation, including the effect of pre-rotation.
142
+ // If we have no curve, assume the node has the correct rotation right now.
143
+ // We need to evaluate since we might only have keys in one of the axes.
144
+ var unityFinalAnimation = Quaternion . Euler (
145
+ ( x == null ) ? eulerRest [ 0 ] : x . Evaluate ( seconds ) ,
146
+ ( y == null ) ? eulerRest [ 1 ] : y . Evaluate ( seconds ) ,
147
+ ( z == null ) ? eulerRest [ 2 ] : z . Evaluate ( seconds )
148
+ ) ;
149
+
150
+ // convert the final animation to righthanded coords
151
+ var finalEuler = ModelExporter . ConvertQuaternionToXYZEuler ( unityFinalAnimation ) ;
152
+
153
+ return ModelExporter . EulerToQuaternion ( new FbxVector4 ( finalEuler ) ) ;
154
+ }
155
+ }
156
+
157
+ /// <summary>
158
+ /// Exporting rotations is more complicated. We need to convert
159
+ /// from quaternion to euler. We use this class to help.
160
+ /// </summary>
161
+ public class QuaternionCurve : RotationCurve {
162
+
163
+ public QuaternionCurve ( ) { m_curves = new AnimationCurve [ 4 ] ; }
164
+
165
+ /// <summary>
166
+ /// Gets the index of the curve by property name.
167
+ /// x = 0, y = 1, z = 2, w = 3
168
+ /// </summary>
169
+ /// <returns>The index of the curve, or -1 if property doesn't map to Quaternion curve.</returns>
170
+ /// <param name="uniPropertyName">Unity property name.</param>
171
+ public static int GetQuaternionIndex ( string uniPropertyName ) {
172
+ System . StringComparison ct = System . StringComparison . CurrentCulture ;
173
+ bool isQuaternionComponent = false ;
174
+
175
+ isQuaternionComponent |= uniPropertyName . StartsWith ( "m_LocalRotation." , ct ) ;
176
+ isQuaternionComponent |= uniPropertyName . EndsWith ( "Q.x" , ct ) ;
177
+ isQuaternionComponent |= uniPropertyName . EndsWith ( "Q.y" , ct ) ;
178
+ isQuaternionComponent |= uniPropertyName . EndsWith ( "Q.z" , ct ) ;
179
+ isQuaternionComponent |= uniPropertyName . EndsWith ( "Q.w" , ct ) ;
180
+
181
+ if ( ! isQuaternionComponent ) { return - 1 ; }
182
+
183
+ switch ( uniPropertyName [ uniPropertyName . Length - 1 ] ) {
184
+ case 'x' : return 0 ;
185
+ case 'y' : return 1 ;
186
+ case 'z' : return 2 ;
187
+ case 'w' : return 3 ;
188
+ default : return - 1 ;
189
+ }
190
+ }
191
+
192
+ protected override FbxQuaternion GetConvertedQuaternionRotation ( float seconds , Quaternion restRotation )
193
+ {
194
+ AnimationCurve x = m_curves [ 0 ] , y = m_curves [ 1 ] , z = m_curves [ 2 ] , w = m_curves [ 3 ] ;
195
+
196
+ // The final animation, including the effect of pre-rotation.
197
+ // If we have no curve, assume the node has the correct rotation right now.
198
+ // We need to evaluate since we might only have keys in one of the axes.
199
+ var fbxFinalAnimation = new FbxQuaternion (
200
+ ( x == null ) ? restRotation [ 0 ] : x . Evaluate ( seconds ) ,
201
+ ( y == null ) ? restRotation [ 1 ] : y . Evaluate ( seconds ) ,
202
+ ( z == null ) ? restRotation [ 2 ] : z . Evaluate ( seconds ) ,
203
+ ( w == null ) ? restRotation [ 3 ] : w . Evaluate ( seconds ) ) ;
204
+
205
+ // convert the final animation to righthanded coords
206
+ var finalEuler = ModelExporter . ConvertQuaternionToXYZEuler ( fbxFinalAnimation ) ;
207
+
208
+ return ModelExporter . EulerToQuaternion ( finalEuler ) ;
209
+ }
210
+ }
211
+
212
+ /// <summary>
213
+ /// Exporting rotations is more complicated. We need to convert
214
+ /// from quaternion to euler. We use this class to help.
215
+ /// </summary>
216
+ public class FbxAnimCurveModifyHelper : System . IDisposable
217
+ {
218
+ public List < FbxAnimCurve > Curves { get ; private set ; }
219
+
220
+ public FbxAnimCurveModifyHelper ( List < FbxAnimCurve > list )
221
+ {
222
+ Curves = list ;
223
+
224
+ foreach ( var curve in Curves )
225
+ curve . KeyModifyBegin ( ) ;
226
+ }
227
+
228
+ ~ FbxAnimCurveModifyHelper ( ) {
229
+ Dispose ( ) ;
230
+ }
231
+
232
+ public void Dispose ( )
233
+ {
234
+ foreach ( var curve in Curves )
235
+ curve . KeyModifyEnd ( ) ;
236
+ }
237
+ }
238
+ }
239
+ }
0 commit comments