Skip to content

Commit 378b9d6

Browse files
committed
take rotation order and prerotation into account for euler curves
- export them similar to how quaternion curves are exported, extracting the prerotation from the rotation and ensuring that the eulers have XYZ rotation order
1 parent e85e92f commit 378b9d6

File tree

1 file changed

+175
-28
lines changed

1 file changed

+175
-28
lines changed

Assets/FbxExporters/Editor/FbxExporter.cs

Lines changed: 175 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,8 @@ protected bool ExportTransform (UnityEngine.Transform unityTransform, FbxNode fb
11871187
// This causes issues when converting euler to quaternion, causing the final
11881188
// rotation to be slighlty off.
11891189
// 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.
11901192
fbxNode.SetRotationOrder (FbxNode.EPivotSet.eSourcePivot, FbxEuler.EOrder.eOrderXYZ);
11911193

11921194
UnityEngine.Vector3 unityTranslate;
@@ -1590,10 +1592,8 @@ public UnityToMayaConvertSceneHelper(string uniPropertyName)
15901592

15911593
bool partT = uniPropertyName.StartsWith ("m_LocalPosition.", cc);
15921594
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);
15951595

1596-
convertLtoR |= partTx || partRy || partRz;
1596+
convertLtoR |= partTx;
15971597

15981598
convertDistance |= partT;
15991599
convertDistance |= uniPropertyName.StartsWith ("m_Intensity", cc);
@@ -1648,21 +1648,6 @@ public static bool TryGetValue(string uniPropertyName, out FbxPropertyChannelPai
16481648
return true;
16491649
}
16501650

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-
16661651
// Transform Translation
16671652
if (uniPropertyName.StartsWith ("m_LocalPosition.x", ct) || uniPropertyName.EndsWith ("T.x", ct)) {
16681653
prop = new FbxPropertyChannelPair ("Lcl Translation", Globals.FBXSDK_CURVENODE_COMPONENT_X);
@@ -1745,6 +1730,135 @@ public void Dispose()
17451730
}
17461731
}
17471732

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+
17481862
/// <summary>
17491863
/// Exporting rotations is more complicated. We need to convert
17501864
/// from quaternion to euler. We use this class to help.
@@ -1935,6 +2049,7 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
19352049
* (which is how it should be) but FBX uses Euler angles. So we
19362050
* need to gather up the list of transform curves per object. */
19372051
var quaternions = new Dictionary<UnityEngine.GameObject, QuaternionCurve> ();
2052+
var eulers = new Dictionary<GameObject, EulerCurve> ();
19382053

19392054
foreach (EditorCurveBinding uniCurveBinding in AnimationUtility.GetCurveBindings (uniAnimClip)) {
19402055
Object uniObj = AnimationUtility.GetAnimatedObject (uniRoot, uniCurveBinding);
@@ -1950,26 +2065,46 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
19502065
}
19512066

19522067
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) {
19612069
/* Rotation property; save it to convert quaternion -> euler later. */
19622070

19632071
var uniGO = GetGameObject (uniObj);
1964-
if (!uniGO) { continue; }
2072+
if (!uniGO) {
2073+
continue;
2074+
}
19652075

19662076
QuaternionCurve quat;
19672077
if (!quaternions.TryGetValue (uniGO, out quat)) {
1968-
quat = new QuaternionCurve {sampleRate = uniAnimClip.frameRate};
2078+
quat = new QuaternionCurve { sampleRate = uniAnimClip.frameRate };
19692079
quaternions.Add (uniGO, quat);
19702080
}
19712081
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;
19722101
}
2102+
2103+
/* simple property (e.g. intensity), export right away */
2104+
ExportAnimationCurve (uniObj, uniAnimCurve, uniAnimClip.frameRate,
2105+
uniCurveBinding.propertyName,
2106+
fbxScene,
2107+
fbxAnimLayer);
19732108
}
19742109

19752110
/* now export all the quaternion curves */
@@ -1984,6 +2119,18 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
19842119
}
19852120
quat.Animate (unityGo.transform, fbxNode, fbxAnimLayer, Verbose);
19862121
}
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+
}
19872134
}
19882135

19892136
/// <summary>

0 commit comments

Comments
 (0)