forked from BilalY/Rasagar
277 lines
10 KiB
C#
277 lines
10 KiB
C#
using Autodesk.Fbx;
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
namespace UnityEditor.Formats.Fbx.Exporter
|
|
{
|
|
/// <summary>
|
|
/// Base class for QuaternionCurve and EulerCurve.
|
|
/// Provides implementation for computing keys and generating FbxAnimCurves
|
|
/// for euler rotation.
|
|
/// </summary>
|
|
internal abstract class RotationCurve
|
|
{
|
|
private double m_sampleRate;
|
|
public double SampleRate
|
|
{
|
|
get { return m_sampleRate; }
|
|
set { m_sampleRate = value; }
|
|
}
|
|
|
|
private AnimationCurve[] m_curves;
|
|
public AnimationCurve[] GetCurves() { return m_curves; }
|
|
public void SetCurves(AnimationCurve[] value) { m_curves = value; }
|
|
|
|
protected struct Key
|
|
{
|
|
private FbxTime m_time;
|
|
public FbxTime time
|
|
{
|
|
get { return m_time; }
|
|
set { m_time = value; }
|
|
}
|
|
private FbxVector4 m_euler;
|
|
public FbxVector4 euler
|
|
{
|
|
get { return m_euler; }
|
|
set { m_euler = value; }
|
|
}
|
|
}
|
|
|
|
protected RotationCurve() {}
|
|
|
|
public void SetCurve(int i, AnimationCurve curve)
|
|
{
|
|
GetCurves()[i] = curve;
|
|
}
|
|
|
|
protected abstract FbxQuaternion GetConvertedQuaternionRotation(float seconds, UnityEngine.Quaternion restRotation);
|
|
|
|
private Key[] ComputeKeys(UnityEngine.Quaternion restRotation, FbxNode node)
|
|
{
|
|
// Get the source pivot pre-rotation if any, so we can
|
|
// remove it from the animation we get from Unity.
|
|
var fbxPreRotationEuler = node.GetRotationActive()
|
|
? node.GetPreRotation(FbxNode.EPivotSet.eSourcePivot)
|
|
: new FbxVector4();
|
|
|
|
// Get the inverse of the prerotation
|
|
var fbxPreRotationInverse = ModelExporter.EulerToQuaternionXYZ(fbxPreRotationEuler);
|
|
fbxPreRotationInverse.Inverse();
|
|
|
|
// Find when we have keys set.
|
|
var keyTimes = ModelExporter.GetSampleTimes(GetCurves(), SampleRate);
|
|
|
|
// Convert to the Key type.
|
|
var keys = new Key[keyTimes.Count];
|
|
int i = 0;
|
|
foreach (var seconds in keyTimes)
|
|
{
|
|
var fbxFinalAnimation = GetConvertedQuaternionRotation(seconds, restRotation);
|
|
|
|
// Cancel out the pre-rotation. Order matters. FBX reads left-to-right.
|
|
// When we run animation we will apply:
|
|
// pre-rotation
|
|
// then pre-rotation inverse
|
|
// then animation.
|
|
var fbxFinalQuat = fbxPreRotationInverse * fbxFinalAnimation;
|
|
|
|
var finalUnityQuat = new Quaternion((float)fbxFinalQuat.X, (float)fbxFinalQuat.Y, (float)fbxFinalQuat.Z, (float)fbxFinalQuat.W);
|
|
|
|
// Store the key so we can sort them later.
|
|
Key key = new Key();
|
|
key.time = FbxTime.FromSecondDouble(seconds);
|
|
key.euler = ModelExporter.ConvertToFbxVector4(finalUnityQuat.eulerAngles);
|
|
keys[i++] = key;
|
|
}
|
|
|
|
// Sort the keys by time
|
|
System.Array.Sort(keys, (Key a, Key b) => a.time.CompareTo(b.time));
|
|
|
|
return keys;
|
|
}
|
|
|
|
public void Animate(Transform unityTransform, FbxNode fbxNode, FbxAnimLayer fbxAnimLayer, bool Verbose)
|
|
{
|
|
if (!unityTransform || fbxNode == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Find or create the three curves. */
|
|
var fbxAnimCurveX = fbxNode.LclRotation.GetCurve(fbxAnimLayer, Globals.FBXSDK_CURVENODE_COMPONENT_X, true);
|
|
var fbxAnimCurveY = fbxNode.LclRotation.GetCurve(fbxAnimLayer, Globals.FBXSDK_CURVENODE_COMPONENT_Y, true);
|
|
var fbxAnimCurveZ = fbxNode.LclRotation.GetCurve(fbxAnimLayer, Globals.FBXSDK_CURVENODE_COMPONENT_Z, true);
|
|
|
|
/* set the keys */
|
|
using (new FbxAnimCurveModifyHelper(new List<FbxAnimCurve> {fbxAnimCurveX, fbxAnimCurveY, fbxAnimCurveZ}))
|
|
{
|
|
foreach (var key in ComputeKeys(unityTransform.localRotation, fbxNode))
|
|
{
|
|
int i = fbxAnimCurveX.KeyAdd(key.time);
|
|
fbxAnimCurveX.KeySet(i, key.time, (float)key.euler.X);
|
|
|
|
i = fbxAnimCurveY.KeyAdd(key.time);
|
|
fbxAnimCurveY.KeySet(i, key.time, (float)key.euler.Y);
|
|
|
|
i = fbxAnimCurveZ.KeyAdd(key.time);
|
|
fbxAnimCurveZ.KeySet(i, key.time, (float)key.euler.Z);
|
|
}
|
|
}
|
|
|
|
if (Verbose)
|
|
{
|
|
Debug.Log("Exported rotation animation for " + fbxNode.GetName());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert from ZXY to XYZ euler, and remove
|
|
/// prerotation from animated rotation.
|
|
/// </summary>
|
|
internal class EulerCurve : RotationCurve
|
|
{
|
|
public EulerCurve() { SetCurves(new AnimationCurve[3]); }
|
|
|
|
/// <summary>
|
|
/// Gets the index of the euler curve by property name.
|
|
/// x = 0, y = 1, z = 2
|
|
/// </summary>
|
|
/// <returns>The index of the curve, or -1 if property doesn't map to Euler curve.</returns>
|
|
/// <param name="uniPropertyName">Unity property name.</param>
|
|
public static int GetEulerIndex(string uniPropertyName)
|
|
{
|
|
if (string.IsNullOrEmpty(uniPropertyName))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
System.StringComparison ct = System.StringComparison.CurrentCulture;
|
|
bool isEulerComponent = uniPropertyName.StartsWith("localEulerAnglesRaw.", ct);
|
|
|
|
if (!isEulerComponent) { return -1; }
|
|
|
|
switch (uniPropertyName[uniPropertyName.Length - 1])
|
|
{
|
|
case 'x': return 0;
|
|
case 'y': return 1;
|
|
case 'z': return 2;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
protected override FbxQuaternion GetConvertedQuaternionRotation(float seconds, Quaternion restRotation)
|
|
{
|
|
var eulerRest = restRotation.eulerAngles;
|
|
AnimationCurve x = GetCurves()[0], y = GetCurves()[1], z = GetCurves()[2];
|
|
|
|
// The final animation, including the effect of pre-rotation.
|
|
// If we have no curve, assume the node has the correct rotation right now.
|
|
// We need to evaluate since we might only have keys in one of the axes.
|
|
var unityFinalAnimation = new Vector3(
|
|
(x == null) ? eulerRest[0] : x.Evaluate(seconds),
|
|
(y == null) ? eulerRest[1] : y.Evaluate(seconds),
|
|
(z == null) ? eulerRest[2] : z.Evaluate(seconds)
|
|
);
|
|
|
|
// convert the final animation to righthanded coords
|
|
|
|
return ModelExporter.EulerToQuaternionZXY(unityFinalAnimation);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exporting rotations is more complicated. We need to convert
|
|
/// from quaternion to euler. We use this class to help.
|
|
/// </summary>
|
|
internal class QuaternionCurve : RotationCurve
|
|
{
|
|
public QuaternionCurve() { SetCurves(new AnimationCurve[4]); }
|
|
|
|
/// <summary>
|
|
/// Gets the index of the curve by property name.
|
|
/// x = 0, y = 1, z = 2, w = 3
|
|
/// </summary>
|
|
/// <returns>The index of the curve, or -1 if property doesn't map to Quaternion curve.</returns>
|
|
/// <param name="uniPropertyName">Unity property name.</param>
|
|
public static int GetQuaternionIndex(string uniPropertyName)
|
|
{
|
|
if (string.IsNullOrEmpty(uniPropertyName))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
System.StringComparison ct = System.StringComparison.CurrentCulture;
|
|
bool isQuaternionComponent = false;
|
|
|
|
isQuaternionComponent |= uniPropertyName.StartsWith("m_LocalRotation.", ct);
|
|
isQuaternionComponent |= uniPropertyName.EndsWith("Q.x", ct);
|
|
isQuaternionComponent |= uniPropertyName.EndsWith("Q.y", ct);
|
|
isQuaternionComponent |= uniPropertyName.EndsWith("Q.z", ct);
|
|
isQuaternionComponent |= uniPropertyName.EndsWith("Q.w", ct);
|
|
|
|
if (!isQuaternionComponent) { return -1; }
|
|
|
|
switch (uniPropertyName[uniPropertyName.Length - 1])
|
|
{
|
|
case 'x': return 0;
|
|
case 'y': return 1;
|
|
case 'z': return 2;
|
|
case 'w': return 3;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
protected override FbxQuaternion GetConvertedQuaternionRotation(float seconds, Quaternion restRotation)
|
|
{
|
|
AnimationCurve x = GetCurves()[0], y = GetCurves()[1], z = GetCurves()[2], w = GetCurves()[3];
|
|
|
|
// The final animation, including the effect of pre-rotation.
|
|
// If we have no curve, assume the node has the correct rotation right now.
|
|
// We need to evaluate since we might only have keys in one of the axes.
|
|
var fbxFinalAnimation = new FbxQuaternion(
|
|
(x == null) ? restRotation[0] : x.Evaluate(seconds),
|
|
(y == null) ? restRotation[1] : y.Evaluate(seconds),
|
|
(z == null) ? restRotation[2] : z.Evaluate(seconds),
|
|
(w == null) ? restRotation[3] : w.Evaluate(seconds));
|
|
|
|
return fbxFinalAnimation;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exporting rotations is more complicated. We need to convert
|
|
/// from quaternion to euler. We use this class to help.
|
|
/// </summary>
|
|
internal class FbxAnimCurveModifyHelper : System.IDisposable
|
|
{
|
|
public List<FbxAnimCurve> Curves { get; private set; }
|
|
|
|
public FbxAnimCurveModifyHelper(List<FbxAnimCurve> list)
|
|
{
|
|
Curves = list;
|
|
|
|
foreach (var curve in Curves)
|
|
curve.KeyModifyBegin();
|
|
}
|
|
|
|
~FbxAnimCurveModifyHelper()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
System.GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool cleanUpManaged)
|
|
{
|
|
foreach (var curve in Curves)
|
|
curve.KeyModifyEnd();
|
|
}
|
|
}
|
|
}
|