Search Unity

Importing motions to editor curves from a fbx animation via script

Discussion in 'Animation' started by phaem, Jul 4, 2017.

  1. phaem

    phaem

    Joined:
    Jan 5, 2015
    Posts:
    76
    Hello Unity community!

    Currently I'm working on a game with a lot of locomotion animation, 100+ clips. I need to use some curves from those clips in the game's logic and I would like to import them via script. But the problem is editor curves must be normalized to the time of 1 to match the source clip length. And when I try to normalize the curve time, it does not looks like original because of tangents.

    Second problem is the root motion. Root transform position is stored in curves named RootT.x, RootT.y and RootT.z, but how to apply those curves to the game object's transform? When I try just to apply values from Animator.GetFloat (i.e. reading those exported curves from a state playing) the object movement is unpredictable.



    Multiplying tangents to normalization factor (see code attached at the bottom) does not helps. The curve is not the same after normalization. This will lead to invalid values and possibly broken logic based on those values at runtime.

    I have two questions on this.

    1. How do I normalize my curves to match source curves?
    2. How does the root motion works? I've extracted curves for RootT.*, how to apply them when animation is playing?

    Code (CSharp):
    1.  
    2.         [ContextMenu("Import Motion Curves")]
    3.         public void ImportMotionCurves()
    4.         {
    5.             var animator = GetComponent<Animator>();
    6.  
    7.             if (animator != null)
    8.             {
    9.                 var controller = animator.runtimeAnimatorController as AnimatorController;
    10.  
    11.                 for(int i = 0; i < controller.animationClips.Length; i++)
    12.                 {
    13.                     ImportAnimationClip(controller.animationClips[i]);
    14.                 }
    15.             }
    16.         }
    17.  
    18.         private void ImportAnimationClip(AnimationClip clip)
    19.         {
    20.             var path = AssetDatabase.GetAssetPath(clip);
    21.             var importer = AssetImporter.GetAtPath(path) as ModelImporter;
    22.  
    23.             if (importer == null)
    24.             {
    25.                 Debug.LogErrorFormat("Unable to find importer for clip {0} at {1}", clip.name, path);
    26.                 return;
    27.             }
    28.  
    29.             var clips = importer.clipAnimations;
    30.             var defaultClips = importer.defaultClipAnimations;
    31.             var clipFound = false;
    32.  
    33.             var bindings = AnimationUtility.GetCurveBindings(clip);
    34.  
    35.             var rootX = default(ClipAnimationInfoCurve);
    36.             var rootY = default(ClipAnimationInfoCurve);
    37.             var rootZ = default(ClipAnimationInfoCurve);
    38.  
    39.             var rightFootY = default(ClipAnimationInfoCurve);
    40.             var leftFootY = default(ClipAnimationInfoCurve);
    41.  
    42.             rootX.name = "RootX";
    43.             rootY.name = "RootY";
    44.             rootZ.name = "RootZ";
    45.             rightFootY.name = "RightFootY";
    46.             leftFootY.name = "LeftFootY";
    47.  
    48.             Debug.LogFormat("Clip {0} length: {1}s", clip.name, clip.length);
    49.  
    50.             EditorUtility.SetDirty(importer);
    51.             for (int i = 0; i < clips.Length; i++)
    52.             {
    53.                 if (clips[i].name == clip.name)
    54.                 {
    55.                     clips[i].curves = new ClipAnimationInfoCurve[0];
    56.                     break;
    57.                 }
    58.             }
    59.             importer.SaveAndReimport();
    60.  
    61.             for (int j = 0; j < bindings.Length; j++)
    62.             {
    63.                 switch (bindings[j].propertyName)
    64.                 {
    65.                     case "RootT.x":
    66.                         rootX.curve = NormalizeCurve(AnimationUtility.GetEditorCurve(clip, bindings[j]));
    67.                         rootX.curve.preWrapMode = WrapMode.Loop;
    68.                         rootX.curve.postWrapMode = WrapMode.Loop;
    69.                         break;
    70.  
    71.                     case "RootT.y":
    72.                         rootY.curve = NormalizeCurve(AnimationUtility.GetEditorCurve(clip, bindings[j]));
    73.                         rootY.curve.preWrapMode = WrapMode.Loop;
    74.                         rootY.curve.postWrapMode = WrapMode.Loop;
    75.                         break;
    76.  
    77.                     case "RootT.z":
    78.                         rootZ.curve = NormalizeCurve(AnimationUtility.GetEditorCurve(clip, bindings[j]));
    79.                         rootZ.curve.preWrapMode = WrapMode.Loop;
    80.                         rootZ.curve.postWrapMode = WrapMode.Loop;
    81.                         break;
    82.  
    83.                     case "RightFootT.y":
    84.                         rightFootY.curve = NormalizeCurve(AnimationUtility.GetEditorCurve(clip, bindings[j]));
    85.                         rightFootY.curve.preWrapMode = WrapMode.Loop;
    86.                         rightFootY.curve.postWrapMode = WrapMode.Loop;
    87.                         break;
    88.  
    89.                     case "LeftFootT.y":
    90.                         leftFootY.curve = NormalizeCurve(AnimationUtility.GetEditorCurve(clip, bindings[j]));
    91.                         leftFootY.curve.preWrapMode = WrapMode.Loop;
    92.                         leftFootY.curve.postWrapMode = WrapMode.Loop;
    93.                         break;
    94.  
    95.                     default:
    96.                         break;
    97.                 }
    98.             }
    99.  
    100.             EditorUtility.SetDirty(importer);
    101.  
    102.             for (int i = 0; i < clips.Length; i++)
    103.             {
    104.                 if (clips[i].name == clip.name)
    105.                 {
    106.                     clipFound = true;
    107.                     clips[i].curves = new ClipAnimationInfoCurve[] { rootX, rootY, rootZ, rightFootY, leftFootY };
    108.                     break;
    109.                 }
    110.             }
    111.  
    112.             if (!clipFound)
    113.             {
    114.                 for (int i = 0; i < defaultClips.Length; i++)
    115.                 {
    116.                     if (defaultClips[i].name == clip.name)
    117.                     {
    118.                         Debug.LogWarningFormat("Clip {0} found in default clips only!", clip.name);
    119.  
    120.                         clipFound = true;
    121.                         Array.Resize(ref clips, clips.Length + 1);
    122.                         clips[clips.Length - 1] = defaultClips[i];
    123.                         clips[clips.Length - 1].curves = new ClipAnimationInfoCurve[] { rootX, rootY, rootZ, rightFootY, leftFootY };
    124.                         break;
    125.                     }
    126.                 }
    127.             }
    128.  
    129.             if (clipFound)
    130.             {
    131.                 importer.clipAnimations = clips;
    132.             }
    133.             else
    134.             {
    135.                 Debug.LogErrorFormat("Clip {0} not found in {1}", clip.name, importer.assetPath);
    136.             }
    137.  
    138.             EditorUtility.SetDirty(importer);
    139.             importer.SaveAndReimport();
    140.             // Debug.LogFormat("Updated clip {0} at path {1}", clip.name, importer.assetPath);
    141.         }
    142.  
    143.         private AnimationCurve NormalizeCurve(AnimationCurve curve)
    144.         {
    145.             var keys = curve.keys;
    146.             var length = keys[keys.Length - 1].time;
    147.             var factor = 1 / length;
    148.             for (int i = 0; i < curve.length; i++)
    149.             {
    150.                 keys[i].time = keys[i].time * factor;
    151.                 keys[i].inTangent *= factor;
    152.                 keys[i].outTangent *= factor;
    153.             }
    154.             curve.keys = keys;
    155.             return curve;
    156.         }
    157.  
     
  2. mplanck

    mplanck

    Joined:
    Mar 22, 2014
    Posts:
    1
    These were helpful insights into how to hack into the modelImporter. FYI, I found a bug on your NormalizeCurve function. Lines 151 and 152 should be:

    Code (csharp):
    1.  
    2.             keys[i].inTangent *= 1/factor;
    3.             keys[i].outTangent *= 1/factor;
    4.  
    Thanks!