Skip to content

Commit fa0ba82

Browse files
committed
transfer motion from source to dest
- dest becomes the first object between source and dest to have animation
1 parent 15ca76b commit fa0ba82

File tree

2 files changed

+242
-31
lines changed

2 files changed

+242
-31
lines changed

Assets/FbxExporters/Editor/ExportModelSettings.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ public interface IExportOptions {
108108
bool AnimateSkinnedMesh { get; }
109109
bool UseMayaCompatibleNames { get; }
110110
bool ExportUnrendered { get; }
111+
Transform AnimationSource { get; }
112+
Transform AnimationDest { get; }
111113
}
112114

113115
public abstract class ExportOptionsSettingsBase<T> : ScriptableObject where T : ExportOptionsSettingsSerializeBase, new()
@@ -137,6 +139,8 @@ public abstract class ExportOptionsSettingsSerializeBase : IExportOptions
137139
public void SetAnimatedSkinnedMesh(bool animatedSkinnedMesh){ this.animatedSkinnedMesh = animatedSkinnedMesh; }
138140
public bool UseMayaCompatibleNames { get { return mayaCompatibleNaming; } }
139141
public void SetUseMayaCompatibleNames(bool useMayaCompNames){ this.mayaCompatibleNaming = useMayaCompNames; }
142+
public Transform AnimationSource { get { return animSource; } }
143+
public Transform AnimationDest { get { return animDest; } }
140144
public abstract ExportSettings.Include ModelAnimIncludeOption { get; }
141145
public abstract ExportSettings.LODExportType LODExportType { get; }
142146
public abstract ExportSettings.ObjectPosition ObjectPosition { get; }

Assets/FbxExporters/Editor/FbxExporter.cs

Lines changed: 238 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,53 +1791,88 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
17911791
*/
17921792
var rotations = new Dictionary<GameObject, RotationCurve> ();
17931793

1794+
var unityCurves = new Dictionary<GameObject, List<UnityCurve>> ();
1795+
17941796
foreach (EditorCurveBinding uniCurveBinding in AnimationUtility.GetCurveBindings (uniAnimClip)) {
17951797
Object uniObj = AnimationUtility.GetAnimatedObject (uniRoot, uniCurveBinding);
1796-
if (!uniObj) { continue; }
1798+
if (!uniObj) {
1799+
continue;
1800+
}
17971801

17981802
AnimationCurve uniAnimCurve = AnimationUtility.GetEditorCurve (uniAnimClip, uniCurveBinding);
1799-
if (uniAnimCurve == null) { continue; }
1800-
1801-
if (Verbose)
1802-
{
1803-
Debug.Log (string.Format ("Exporting animation curve bound to {0} {1}",
1804-
uniCurveBinding.propertyName, uniCurveBinding.path));
1803+
if (uniAnimCurve == null) {
1804+
continue;
18051805
}
18061806

18071807
var uniGO = GetGameObject (uniObj);
18081808
if (!uniGO) {
18091809
continue;
18101810
}
1811-
1812-
// Do not create the curves if the component is a SkinnedMeshRenderer and if the option in FBX Export settings is toggled on.
1813-
if (removeAnimationsFromSkinnedMeshRenderer && (uniGO.GetComponent<SkinnedMeshRenderer>() != null || uniGO.GetComponentInChildren<SkinnedMeshRenderer>() != null))
1814-
{
1815-
continue;
1816-
}
1817-
1818-
int index = QuaternionCurve.GetQuaternionIndex (uniCurveBinding.propertyName);
1819-
if (index >= 0) {
1820-
/* Rotation property; save it to convert quaternion -> euler later. */
1821-
RotationCurve rotCurve = GetRotationCurve<QuaternionCurve>(uniGO, uniAnimClip.frameRate, ref rotations);
1822-
rotCurve.SetCurve (index, uniAnimCurve);
1823-
continue;
1824-
}
18251811

1826-
index = EulerCurve.GetEulerIndex (uniCurveBinding.propertyName);
1827-
if (index >= 0) {
1828-
RotationCurve rotCurve = GetRotationCurve<EulerCurve> (uniGO, uniAnimClip.frameRate, ref rotations);
1829-
rotCurve.SetCurve (index, uniAnimCurve);
1812+
if (unityCurves.ContainsKey (uniGO)) {
1813+
unityCurves [uniGO].Add (new UnityCurve(uniCurveBinding.propertyName, uniAnimCurve));
18301814
continue;
18311815
}
1816+
unityCurves.Add (uniGO, new List<UnityCurve> (){ new UnityCurve(uniCurveBinding.propertyName, uniAnimCurve) });
1817+
}
1818+
1819+
// transfer root motion
1820+
var animSource = ExportOptions.AnimationSource;
1821+
var animDest = ExportOptions.AnimationDest;
18321822

1833-
/* simple property (e.g. intensity), export right away */
1834-
ExportAnimationCurve (uniGO, uniAnimCurve, uniAnimClip.frameRate,
1835-
uniCurveBinding.propertyName,
1836-
fbxScene,
1837-
fbxAnimLayer);
1823+
// list of source to dest
1824+
var transformsInHierarchy = new List<Transform> ();
1825+
var curr = animDest;
1826+
while (curr != animSource) {
1827+
transformsInHierarchy.Add (curr);
1828+
curr = curr.parent;
18381829
}
1830+
transformsInHierarchy.Add (animSource);
1831+
transformsInHierarchy.Reverse ();
1832+
1833+
while (transformsInHierarchy.Count >= 2) {
1834+
var source = transformsInHierarchy [0];
1835+
transformsInHierarchy.RemoveAt (0);
1836+
var dest = transformsInHierarchy [0];
1837+
1838+
TransferMotion (source, dest, uniAnimClip.frameRate, ref unityCurves);
1839+
}
1840+
1841+
foreach (var kvp in unityCurves) {
1842+
var uniGO = kvp.Key;
1843+
foreach (var uniCurve in kvp.Value) {
1844+
var propertyName = uniCurve.propertyName;
1845+
var uniAnimCurve = uniCurve.uniAnimCurve;
1846+
1847+
// Do not create the curves if the component is a SkinnedMeshRenderer and if the option in FBX Export settings is toggled on.
1848+
if (removeAnimationsFromSkinnedMeshRenderer && (uniGO.GetComponent<SkinnedMeshRenderer> () != null || uniGO.GetComponentInChildren<SkinnedMeshRenderer> () != null)) {
1849+
continue;
1850+
}
18391851

1840-
/* now export all the quaternion curves */
1852+
int index = QuaternionCurve.GetQuaternionIndex (propertyName);
1853+
if (index >= 0) {
1854+
// Rotation property; save it to convert quaternion -> euler later.
1855+
RotationCurve rotCurve = GetRotationCurve<QuaternionCurve> (uniGO, uniAnimClip.frameRate, ref rotations);
1856+
rotCurve.SetCurve (index, uniAnimCurve);
1857+
continue;
1858+
}
1859+
1860+
index = EulerCurve.GetEulerIndex (propertyName);
1861+
if (index >= 0) {
1862+
RotationCurve rotCurve = GetRotationCurve<EulerCurve> (uniGO, uniAnimClip.frameRate, ref rotations);
1863+
rotCurve.SetCurve (index, uniAnimCurve);
1864+
continue;
1865+
}
1866+
1867+
// simple property (e.g. intensity), export right away
1868+
ExportAnimationCurve (uniGO, uniAnimCurve, uniAnimClip.frameRate,
1869+
propertyName,
1870+
fbxScene,
1871+
fbxAnimLayer);
1872+
}
1873+
}
1874+
1875+
// now export all the quaternion curves
18411876
foreach (var kvp in rotations) {
18421877
var unityGo = kvp.Key;
18431878
var rot = kvp.Value;
@@ -1851,6 +1886,178 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
18511886
}
18521887
}
18531888

1889+
1890+
private void TransferMotion(Transform source, Transform dest, float sampleRate, ref Dictionary<GameObject, List<UnityCurve>> unityCurves){
1891+
// get sample times for curves in dest + source
1892+
// at each sample time, evaluate all 18 anim curves, creating 2 transform matrices
1893+
// combine the matrices, get the new values, apply to the 9 new anim curves for dest
1894+
if (dest.parent != source) {
1895+
Debug.LogError ("dest must be a child of source");
1896+
return;
1897+
}
1898+
1899+
List<UnityCurve> sourceUnityCurves;
1900+
if (!unityCurves.TryGetValue (source.gameObject, out sourceUnityCurves)) {
1901+
return; // nothing to do
1902+
}
1903+
1904+
List<UnityCurve> destUnityCurves;
1905+
if (!unityCurves.TryGetValue (dest.gameObject, out destUnityCurves)) {
1906+
destUnityCurves = new List<UnityCurve> ();
1907+
}
1908+
1909+
List<AnimationCurve> animCurves = new List<AnimationCurve> ();
1910+
foreach (var curve in sourceUnityCurves) {
1911+
// TODO: check if curve is anim related
1912+
animCurves.Add(curve.uniAnimCurve);
1913+
}
1914+
foreach (var curve in destUnityCurves) {
1915+
animCurves.Add (curve.uniAnimCurve);
1916+
}
1917+
1918+
var sampleTimes = GetSampleTimes (animCurves.ToArray (), sampleRate);
1919+
// need to create 9 new UnityCurves, one for each property
1920+
var posKeyFrames = new Keyframe[3][];
1921+
var rotKeyFrames = new Keyframe[3][];
1922+
var scaleKeyFrames = new Keyframe[3][];
1923+
1924+
for (int t = 0; t < posKeyFrames.Length; t++) {
1925+
posKeyFrames [t] = new Keyframe[sampleTimes.Count];
1926+
rotKeyFrames[t] = new Keyframe[sampleTimes.Count];
1927+
scaleKeyFrames[t] = new Keyframe[sampleTimes.Count];
1928+
}
1929+
1930+
int i = 0;
1931+
foreach (var currSampleTime in sampleTimes)
1932+
{
1933+
var sourceLocalMatrix = GetTransformMatrix (currSampleTime, source, sourceUnityCurves);
1934+
var destLocalMatrix = GetTransformMatrix (currSampleTime, dest, destUnityCurves);
1935+
1936+
// child * parent
1937+
var mewLocalMatrix = destLocalMatrix * sourceLocalMatrix;
1938+
1939+
// FBX is transposed relative to Unity: transpose as we convert.
1940+
FbxMatrix matrix = new FbxMatrix ();
1941+
matrix.SetColumn (0, new FbxVector4 (mewLocalMatrix.GetRow (0).x, mewLocalMatrix.GetRow (0).y, mewLocalMatrix.GetRow (0).z, mewLocalMatrix.GetRow (0).w));
1942+
matrix.SetColumn (1, new FbxVector4 (mewLocalMatrix.GetRow (1).x, mewLocalMatrix.GetRow (1).y, mewLocalMatrix.GetRow (1).z, mewLocalMatrix.GetRow (1).w));
1943+
matrix.SetColumn (2, new FbxVector4 (mewLocalMatrix.GetRow (2).x, mewLocalMatrix.GetRow (2).y, mewLocalMatrix.GetRow (2).z, mewLocalMatrix.GetRow (2).w));
1944+
matrix.SetColumn (3, new FbxVector4 (mewLocalMatrix.GetRow (3).x, mewLocalMatrix.GetRow (3).y, mewLocalMatrix.GetRow (3).z, mewLocalMatrix.GetRow (3).w));
1945+
1946+
// FBX wants translation, rotation (in euler angles) and scale.
1947+
// We assume there's no real shear, just rounding error.
1948+
FbxVector4 translation, rotation, shear, scale;
1949+
double sign;
1950+
matrix.GetElements (out translation, out rotation, out shear, out scale, out sign);
1951+
1952+
for (int k = 0; k < 3; k++) {
1953+
posKeyFrames [k][i] = new Keyframe(currSampleTime, (float)translation [k]);
1954+
rotKeyFrames [k][i] = new Keyframe(currSampleTime, (float)rotation [k]);
1955+
scaleKeyFrames [k][i] = new Keyframe(currSampleTime, (float)scale [k]);
1956+
}
1957+
i++;
1958+
}
1959+
1960+
// create the new list of unity curves, and add it to dest's curves
1961+
var newUnityCurves = new List<UnityCurve>();
1962+
string posPropName = "m_LocalPosition.";
1963+
string rotPropName = "localEulerAnglesRaw.";
1964+
string scalePropName = "m_LocalScale.";
1965+
var xyz = new string[]{ "x", "y", "z" };
1966+
for (int j = 0; j < 3; j++) {
1967+
var posUniCurve = new UnityCurve ( posPropName + xyz[j], new AnimationCurve(posKeyFrames[j]) );
1968+
newUnityCurves.Add (posUniCurve);
1969+
1970+
var rotUniCurve = new UnityCurve ( rotPropName + xyz[j], new AnimationCurve(rotKeyFrames[j]) );
1971+
newUnityCurves.Add (rotUniCurve);
1972+
1973+
var scaleUniCurve = new UnityCurve ( scalePropName + xyz[j], new AnimationCurve(scaleKeyFrames[j]) );
1974+
newUnityCurves.Add (scaleUniCurve);
1975+
}
1976+
1977+
unityCurves.Remove (source.gameObject);
1978+
unityCurves [dest.gameObject] = newUnityCurves;
1979+
}
1980+
1981+
private Matrix4x4 GetTransformMatrix(float currSampleTime, Transform orig, List<UnityCurve> unityCurves){
1982+
var sourcePos = orig.localPosition;
1983+
var sourceRot = orig.localRotation;
1984+
var sourceScale = orig.localScale;
1985+
foreach (var uniCurve in unityCurves) {
1986+
float currSampleValue = uniCurve.uniAnimCurve.Evaluate((float)currSampleTime);
1987+
string propName = uniCurve.propertyName;
1988+
// try position, scale, quat then euler
1989+
int temp = QuaternionCurve.GetQuaternionIndex(propName);
1990+
if (temp >= 0) {
1991+
sourceRot [temp] = currSampleValue;
1992+
continue;
1993+
}
1994+
temp = EulerCurve.GetEulerIndex (propName);
1995+
if (temp >= 0) {
1996+
var euler = sourceRot.eulerAngles;
1997+
euler [temp] = currSampleValue;
1998+
sourceRot.eulerAngles = euler;
1999+
continue;
2000+
}
2001+
temp = GetPositionIndex (propName);
2002+
if (temp >= 0) {
2003+
sourcePos [temp] = currSampleValue;
2004+
continue;
2005+
}
2006+
temp = GetScaleIndex (propName);
2007+
if (temp >= 0) {
2008+
sourceScale [temp] = currSampleValue;
2009+
}
2010+
}
2011+
2012+
return Matrix4x4.TRS(sourcePos, sourceRot, sourceScale);
2013+
}
2014+
2015+
struct UnityCurve {
2016+
public string propertyName;
2017+
public AnimationCurve uniAnimCurve;
2018+
2019+
public UnityCurve(string propertyName, AnimationCurve uniAnimCurve){
2020+
this.propertyName = propertyName;
2021+
this.uniAnimCurve = uniAnimCurve;
2022+
}
2023+
}
2024+
2025+
private int GetPositionIndex(string uniPropertyName){
2026+
System.StringComparison ct = System.StringComparison.CurrentCulture;
2027+
bool isEulerComponent = uniPropertyName.StartsWith ("m_LocalPosition.", ct);
2028+
2029+
if (!isEulerComponent) { return -1; }
2030+
2031+
switch (uniPropertyName [uniPropertyName.Length - 1]) {
2032+
case 'x':
2033+
return 0;
2034+
case 'y':
2035+
return 1;
2036+
case 'z':
2037+
return 2;
2038+
default:
2039+
return -1;
2040+
}
2041+
}
2042+
2043+
private int GetScaleIndex(string uniPropertyName){
2044+
System.StringComparison ct = System.StringComparison.CurrentCulture;
2045+
bool isEulerComponent = uniPropertyName.StartsWith ("m_LocalScale.", ct);
2046+
2047+
if (!isEulerComponent) { return -1; }
2048+
2049+
switch (uniPropertyName [uniPropertyName.Length - 1]) {
2050+
case 'x':
2051+
return 0;
2052+
case 'y':
2053+
return 1;
2054+
case 'z':
2055+
return 2;
2056+
default:
2057+
return -1;
2058+
}
2059+
}
2060+
18542061
/// <summary>
18552062
/// Gets or creates the rotation curve for GameObject uniGO.
18562063
/// </summary>

0 commit comments

Comments
 (0)