Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Rotation direction for AnimationCurves

Discussion in 'Scripting' started by ratamorph, Feb 23, 2009.

  1. ratamorph

    ratamorph

    Joined:
    Sep 2, 2007
    Posts:
    458
    I'm using animation curves to smoothly transition between my cameras but sometimes the rotation goes the opposite way that I want(the long way) so it ends up doing an almost complete 360 spin when I just want to rotate a few degrees.

    How can I control the direction of rotation?
     
  2. ratamorph

    ratamorph

    Joined:
    Sep 2, 2007
    Posts:
    458
    Here's what I'm doing...

    Code (csharp):
    1.  
    2. var fromToVectorPosition : Vector3 = targetTransform.localPosition - transform.localPosition;
    3.     var fromToVectorRotation : Vector3 = targetTransform.localEulerAngles - transform.localEulerAngles;
    4.    
    5.     //Debug.Log("mag pos " + fromToVectorPosition.magnitude + " mag rot " + fromToVectorRotation.magnitude);
    6.     alreadyAtTarget = false;
    7.    
    8.     if(fromToVectorPosition.magnitude <=  error  fromToVectorRotation.magnitude <=  error)
    9.     {
    10.         alreadyAtTarget = true;
    11.         return;
    12.     }
    13.    
    14.     var localPosXCurve = AnimationCurve.EaseInOut(0, transform.localPosition.x, animationLenghtInSeconds, targetTransform.localPosition.x);
    15.     var localPosYCurve = AnimationCurve.EaseInOut(0, transform.localPosition.y, animationLenghtInSeconds, targetTransform.localPosition.y);
    16.     var localPosZCurve = AnimationCurve.EaseInOut(0, transform.localPosition.z, animationLenghtInSeconds, targetTransform.localPosition.z);
    17.  
    18.     var localRotXCurve = AnimationCurve.EaseInOut(0, transform.localRotation.x, animationLenghtInSeconds, targetTransform.localRotation.x);
    19.     var localRotYCurve = AnimationCurve.EaseInOut(0, transform.localRotation.y, animationLenghtInSeconds, targetTransform.localRotation.y);
    20.     var localRotZCurve = AnimationCurve.EaseInOut(0, transform.localRotation.z, animationLenghtInSeconds, targetTransform.localRotation.z);
    21.     var localRotWCurve = AnimationCurve.EaseInOut(0, transform.localRotation.w, animationLenghtInSeconds, targetTransform.localRotation.w);
    22.  
    23.     animationClip = new AnimationClip();
    24.    
    25.     animationClip.SetCurve("", Transform, "localPosition.x", localPosXCurve );
    26.     animationClip.SetCurve("", Transform, "localPosition.y", localPosYCurve );
    27.     animationClip.SetCurve("", Transform, "localPosition.z", localPosZCurve );
    28.  
    29.     animationClip.SetCurve("", Transform, "localRotation.x", localRotXCurve );
    30.     animationClip.SetCurve("", Transform, "localRotation.y", localRotYCurve );
    31.     animationClip.SetCurve("", Transform, "localRotation.z", localRotZCurve );
    32.     animationClip.SetCurve("", Transform, "localRotation.w", localRotWCurve );
    33.    
    34.     animation.AddClip(animationClip, "test");
    35.     animation.Play("test");
    36.  
    I tried to use localEulerAngles as the property in SetCurve and it seems like that property doesn't exist, is there a comprehensive list of the properties that SetCurve can use? or is it just what it says in
    http://unity3d.com/support/documentation/ScriptReference/AnimationClip.SetCurve.html
     
  3. ratamorph

    ratamorph

    Joined:
    Sep 2, 2007
    Posts:
    458
    After some more experimentation I noticed something kinda strange about my issue...

    Say the camera or object starts with a rotation along the Y axis of 340, after performing the curve it returns back to the original position as intended. The rotation angle is now something like 340.0308 and when it animates again it turns the long way doing the spin I described.

    Then I run the same test but before it animates again I modify the value of the rotation in the inspector back to 340 and it rotates the way I want.

    Also noticed that for some instances where the rotation would go the long way right away I could make it rotate the way I want by just subtracting the rotation value from 360 and setting the transform in the inspector to that number; something like -20 instead of 340, the transform would automatically set it back to 340 and the rotation works as expected.

    Not sure if any of my findings mean anything.. I'm just trying to figure out what's going on so I can fix it.

    Any help or advice would be greatly apreciated.
     
  4. hatman

    hatman

    Joined:
    Feb 3, 2009
    Posts:
    333
    did you get any further on this in the end?
     
  5. ratamorph

    ratamorph

    Joined:
    Sep 2, 2007
    Posts:
    458
    No, I still haven't, I tought of maybe just do the camera movement with animation curves and have a separate script which sets the lookat for the camera so it always faces the target, but this doesn't really fix or clarify the issue. Which is to do it all with animation curves, and even then the camera not always has a target, sometimes I just want to have it look in a general direction without having to have a target for it.

    Are yout trying to do something similar? let me know if you find something.
     
  6. tinrocket

    tinrocket

    Joined:
    Jan 26, 2009
    Posts:
    63
    I think I've just run into a similar problem. I'm using animation clip for the first time and it appears that the curves just interpolate between the separate XYZW scalar values, rather than performing a Slerp interpolation the quaternion.
     
  7. ratamorph

    ratamorph

    Joined:
    Sep 2, 2007
    Posts:
    458
    Yeah, and it appears you can't create animation curves for the euler angles. So how are we supposed to make sure the rotations we make using animation curves go in the direction we desire rather than the direction they choose to go? What are we missing? I've read the docs trying to find a clue to my problem and haven't found anything.

    One thing rings a bell, I read somewhere that transforms in Unity can represent rotation of more than 360, so that would explain the spin, but still gives me no clue on how to fix it.

    Nobody else having this problem other than the people that replied?
     
  8. tinrocket

    tinrocket

    Joined:
    Jan 26, 2009
    Posts:
    63
    Euler angle interpolation would be just as bad. I think there should be an overloaded version of SetCurve that can take quaternions instead of floats.
     
  9. ratamorph

    ratamorph

    Joined:
    Sep 2, 2007
    Posts:
    458
    I got a chance to ask the devs about this at GDC, and indeed the issue is the euler angle inerpolation that animation curves currently use. I was told that in 2.6 they'll use quaternion interpolation so it'll fix the problem.

    For now I'm using animation curves for the displacement of the camera and the smoothLookAt script that comes with Unity to make the camera face the target. This seems to work the best for now.
     
  10. simonal

    simonal

    Joined:
    Apr 17, 2009
    Posts:
    16
    I seem to run into the run into the same issue in Unity 2.6.1. Was it never resolved?
     
  11. Matkins

    Matkins

    Joined:
    Aug 24, 2009
    Posts:
    152
    It's been 2.5 years since the original post, and this problem still appears to exist. I can easily set a curve for postion and scale, but rotation is just totally messed up. Why do my (x,y,z,w) quaternion curves occasionally interpolate the wrong way... the "long" way? Why can i not see these curves in the animation editor but instead am forced to look at a euler interpretation? Why can't i set a euler curve? I don't really care how it has to be done, as long as it works, but I'm yet to find a way that works without these strange results.
     
  12. ADeveloper

    ADeveloper

    Joined:
    Nov 11, 2011
    Posts:
    4
    Perhaps this issue (at the link below) with Animation Curves is the reason for the effects you are observing?...

    http://forum.unity3d.com/threads/111590-Animation-Curve-Glitches

    The issue occurs with Eulers and Quaternions, seemingly due to the Animation Curve interpolator insisting on creating a smooth curve, rather than a smooth animation. We too hope that Unity fixes this glaring error, or at least provides an alternative such as no interpolation.
     
  13. johot

    johot

    Joined:
    Apr 11, 2011
    Posts:
    201
    I want to bump this thread.

    I am trying to generate animationclips using a script. I have basically built my own little animation editor with stuff we needed. But as noted here, when we create an AnimationCurve for the rotation it sometimes rotates the wrong way. It's like this curve doesn't use quaternion interpolation but instead uses euler interpolation. I can't find any flag or workaround to set the correct interpolation mode except for in the editor.

    My current workaround works by going into the Unity animation editor when I have generated an animationclip and click on the curves once (or move the tangent handles slightly), that seems to fix the problem and I can see the curves changing shape. But this is not an option I can live with, especially if I one day want to sell this animation editor in the asset store.

    Have anyone found another way around this? This is a major bug that really needs to be fixed or I will be stuck basically :(
     
  14. ADeveloper

    ADeveloper

    Joined:
    Nov 11, 2011
    Posts:
    4
    As far as we can deduce (as the docs are unclear on this), quaternion versus Euler interpolation is only related to the input methodology within the Unity Animation Editor (i.e. whether you have WXYZ (0..1)or XYZ (0..359) graphical curves). The impression is that they all get turned into quaternions within a final AnimationCurve. You will note that Animations consist of AnimationClips, that AnimationClips consist of AnimationCurves, and that AnimationClip rotations can only be driven by quaternion components (localRotation.w/x/y/z).

    Given that, if your keys ever contain a numerical discontinuity (which is often desirable to get a smooth animation, such as when rotating between 359 and 0 degrees), or a jump of greater than 180 degrees between 2 key frames (which should really be avoided whenever possible), then AnimationCurve's built-in linear interpolator will linearly interpolate each quaternion component (wxyz) between those 2 keys and cause the in between animation to rotate in the wrong direction.

    If Unity provided an AnimationCurve option to not interpolate between key frames then one could just provide enough key frames to ensure smooth animation (and there'd be a performance increase). Alternatively, the AnimationCurve could just detect large numerical discontinuities and disable interpolation during them (we use this technique in some other software).

    We filed a bug (#429206) against this. If you too believe this is a bug then please also file a bug report.
     
  15. johot

    johot

    Joined:
    Apr 11, 2011
    Posts:
    201
    @ADeveloper

    Hmm I might have found a workaround. Look at this example:

    Code (csharp):
    1.  
    2. Vector3 v1 = new Vector3(0.0f, 0.0f, -40.0f);
    3. Vector3 v2 = new Vector3(0.0f, 0.0f, 320.0f);
    4.            
    5. Quaternion q1 = Quaternion.Euler(v1);
    6. Quaternion q2 = Quaternion.Euler(v2);
    7.  
    In this case, it's the same rotation but this gives different quaternions, q1 != q2. I could use this to make things rotate the way I wanted. So by using a negative or positive z rotation (using euler) and then converting to quaternions I could get around the issue. But I haven't fully confirmed it yet. Try to see if you can get it working. :)
     
    soltex1 likes this.
  16. buffonomics

    buffonomics

    Joined:
    Jun 10, 2009
    Posts:
    59
    This is still an issue in unity 3.4. Come on Unity!
    This makes using the animation system quite grieving.
     
  17. tyrine

    tyrine

    Joined:
    Jan 7, 2014
    Posts:
    1
    We are currently using Unity 4.3 and encountered the same issue when importing animations. It seems that unity is trying to interpolate the curve by looking at the exact numerical value of the given euler angles (the logical way).

    e.g.
    keyframe-01 angle = -170 degree
    keyfram0-02 angle = 170 degree

    We would expect it to rotate 20 degree clockwise, however the fact is it rotates from -170 back to 0 then 170 anti-clockwise.

    If looking at a linear line of the interpolation:

    ---|--------|------------------------------|-------------------------------|
    -190....-170..................................0...................................170

    It is obvious that 170 could be changed to it's equivalent of -190 and in that way the rotation meets our expectation. The workaround is to find that nearest equivalent of current euler angel by comparing to previous one.

    See if this helps and hope unity could fix this someday.:cool:
     
    Last edited: Mar 27, 2014
    soltex1 likes this.
  18. kilik128

    kilik128

    Joined:
    Jul 15, 2013
    Posts:
    909
    anyone can help i got

    Keyx[k] = Keyframe(k*EveryTime,iam.transform.localRotation.x);
    Keyy[k] = Keyframe(k*EveryTime,iam.transform.localRotation.y);
    Keyz[k] = Keyframe(k*EveryTime,iam.transform.localRotation.z);
    Keyw[k] = Keyframe(k*EveryTime,iam.transform.localRotation.w);

    animClip.SetCurve(""+GetGameObjectPath(iam), Transform, "localRotation.x", animx);
    animClip.SetCurve(""+GetGameObjectPath(iam), Transform, "localRotation.y", animy);
    animClip.SetCurve(""+GetGameObjectPath(iam), Transform, "localRotation.z", animz);
    animClip.SetCurve(""+GetGameObjectPath(iam), Transform, "localRotation.x", animw);

    and it's look not working
     
  19. NazimR

    NazimR

    Joined:
    Mar 12, 2014
    Posts:
    17
    Has anyone submitted this as a bug for the 4.5 series?
     
  20. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    Still an issue here. :/

    This is ridiculous something like this is still a problem. Does anyone have a workaround for this for the animation editor?
     
    BadSeedProductions likes this.
  21. CyberScopes

    CyberScopes

    Joined:
    Oct 25, 2014
    Posts:
    7
    Ive found a work around thats a bit annoying but it works for me. If you edit the rotation values specifically in the animation rather than from the inspector, the values wont auto-adjust. You will need to expand each transform in the animation tab and set each x,y and z value from there.
     
  22. BadSeedProductions

    BadSeedProductions

    Joined:
    Dec 26, 2014
    Posts:
    144
  23. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    I cannot believe this issue is over 7 years old.

    Oh wait, I can definitely believe that. It's Unity.

    As unbelievably believable as it is, I still cannot believe that I can take a gameobject, rotate it by 1 degree, and the tweening inbetween will be insanity (sprite flipping around in all sorts of lunacy) in the opposite direction, rather than a single 1 degree tilt in the correct direction.

    Is there any solution to this? It is after all 7 years since. Besides manually keyframing every single frame (or until the animation clip stops freaking out).
     
  24. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    I've found a quick fix for this. At least in my single instance, among many times with this problem.

    So my sprite starts at Rotation 0,0,0.
    Desired Effect: Rotate backwards, from 0 to 280 degrees. (Only a -80 degree rotation. NOT a +280 rotation.)

    Solution: Alter the first frame from Rotation 0, to rotation 359.9 (automatically changes to 359.8999).

    I believe this is the solution as above, but I didn't "get it" until after I figured this out.


    The difference of 0.11111 rotation is not noticeable in any way, and the character is rotation that way ANYWAY.

    This automatically tweens it from 359.89 to subtract -80 degrees, rotating correctly.

    Otherwise, Unity's animation system automatically assumes (even though you're rotating negatively from start in the Editor) that it's always +Rotation, which is the problem. Unity will rotate +280 degrees, rather than -80 degrees.

    So just change 0 to 359.8999.

    I'm sure this is not a final solution, but it only requires a single key frame change, if all you're doing is one way rotation, one time. (I doubt this resolves the problem if you're rotating back & forth in all kinds of directions throughout the animation).
     
  25. _--_

    _--_

    Joined:
    Nov 22, 2013
    Posts:
    11
    my solution was to use curves.
     
  26. _--_

    _--_

    Joined:
    Nov 22, 2013
    Posts:
    11
    And keep all rotation points in the "Curves" tab below the animation Line
     
  27. unity_123uzunince123

    unity_123uzunince123

    Joined:
    Mar 21, 2020
    Posts:
    2
    Code (CSharp):
    1. public static void KlibeRotasyonEkle(this AnimationClip ac, float basEuler, float sonEuler, float sure, string xyz = "y")
    2.     {
    3.         if (basEuler > sonEuler)
    4.         {
    5.             if (basEuler - sonEuler > 180)
    6.                 basEuler -= 360;
    7.         }
    8.         else
    9.         {
    10.             if (sonEuler - basEuler > 180)
    11.                 sonEuler -= 360;
    12.         }
    13.         ac.SetCurve("", typeof(Transform), "localEulerAngles." + xyz, new AnimationCurve(new Keyframe(0, basEuler), new Keyframe(sure, sonEuler)));
    14.     }
    basEuler: initial euler
    sonEuler: final euler
    sure: time
     
  28. Rathmansbach

    Rathmansbach

    Joined:
    Oct 26, 2023
    Posts:
    5
  29. Rathmansbach

    Rathmansbach

    Joined:
    Oct 26, 2023
    Posts:
    5
    This is my working example of a component that records the rotation of a transform when created and updated and then finally saves the recorded data to an animation clip:

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. public class AnimationRecorderRotationData
    5. {
    6.     // Set things
    7.     private Animator animator;
    8.     private Transform animationTransform;
    9.     private AnimationClip animationClip;
    10.     private float recordingStartTime;
    11.  
    12.     // Data during recording
    13.     private int lastKeyFrameNumber = -1;
    14.     private float lastKeyFrameTime = 0;
    15.     private Quaternion lastRotation;
    16.  
    17.     // Derived
    18.     private AnimationCurve rotationCurveX;
    19.     private AnimationCurve rotationCurveY;
    20.     private AnimationCurve rotationCurveZ;
    21.  
    22.     public AnimationRecorderRotationData(Animator animator, Transform animationTransform, AnimationClip animationClip)
    23.     {
    24.         this.animator = animator;
    25.         this.animationTransform = animationTransform;
    26.         this.animationClip = animationClip;
    27.  
    28.         // Instanciate
    29.         rotationCurveX = new AnimationCurve();
    30.         rotationCurveY = new AnimationCurve();
    31.         rotationCurveZ = new AnimationCurve();
    32.  
    33.     }
    34.  
    35.     public void SaveKeyFrame()
    36.     {
    37.         // First keyframe
    38.         if (lastKeyFrameNumber == -1)
    39.         {
    40.             // Set recording start time
    41.             recordingStartTime = Time.time;
    42.  
    43.             Vector3 firstEulerAngles = NormalizeEulerAngles(animationTransform.localRotation.eulerAngles);
    44.  
    45.             // Set initial values
    46.             rotationCurveX.AddKey(0f, firstEulerAngles.x);
    47.             rotationCurveY.AddKey(0f, firstEulerAngles.y);
    48.             rotationCurveZ.AddKey(0f, firstEulerAngles.z);
    49.  
    50.             // Update last Keyframe values
    51.             lastKeyFrameNumber = 0;
    52.             lastKeyFrameTime = 0f;
    53.             lastRotation = Quaternion.Euler(firstEulerAngles);
    54.             return;
    55.         }
    56.  
    57.         // Current keyframe blendshape values
    58.         int currKeyFrameNumber = Mathf.Max(Mathf.FloorToInt((Time.time - recordingStartTime) * animationClip.frameRate), 0);
    59.  
    60.         // Animation keyframe has not yet updated
    61.         if (currKeyFrameNumber <= lastKeyFrameNumber)
    62.         {
    63.             return;
    64.         }
    65.  
    66.         // Calculations only when Update keyframe
    67.         float currKeyFrameTime = currKeyFrameNumber / animationClip.frameRate;
    68.  
    69.         // When skipped multiple keyframes, catch up to current one by interpolation
    70.         while (currKeyFrameNumber > lastKeyFrameNumber + 1)
    71.         {
    72.             int nextKeyFrameNumber = lastKeyFrameNumber + 1;
    73.             float nextKeyFrameTime = nextKeyFrameNumber / animationClip.frameRate;
    74.  
    75.             // Interpolate
    76.             float t = Mathf.InverseLerp(lastKeyFrameTime, currKeyFrameTime, nextKeyFrameTime);
    77.             Vector3 nextEulerAngles = NormalizeEulerAngles(Quaternion.Lerp(lastRotation, animationTransform.localRotation, t).eulerAngles);
    78.  
    79.             // Set Keys
    80.             rotationCurveX.AddKey(nextKeyFrameTime, nextEulerAngles.x);
    81.             rotationCurveY.AddKey(nextKeyFrameTime, nextEulerAngles.y);
    82.             rotationCurveZ.AddKey(nextKeyFrameTime, nextEulerAngles.z);
    83.  
    84.             // Update last values
    85.             lastKeyFrameNumber = nextKeyFrameNumber;
    86.             lastKeyFrameTime = nextKeyFrameTime;
    87.             lastRotation = Quaternion.Euler(nextEulerAngles);
    88.         }
    89.  
    90.         // Update current Keyframe
    91.         Vector3 currEulerAngles = NormalizeEulerAngles(animationTransform.localRotation.eulerAngles);
    92.         rotationCurveX.AddKey(currKeyFrameTime, currEulerAngles.x);
    93.         rotationCurveY.AddKey(currKeyFrameTime, currEulerAngles.y);
    94.         rotationCurveZ.AddKey(currKeyFrameTime, currEulerAngles.z);
    95.  
    96.         // Update last values
    97.         lastKeyFrameNumber = currKeyFrameNumber;
    98.         lastKeyFrameTime = currKeyFrameTime;
    99.         lastRotation = Quaternion.Euler(currEulerAngles);
    100.     }
    101.  
    102.     private static Vector3 NormalizeEulerAngles(Vector3 eulers)
    103.     {
    104.         eulers.x = NormalizeAngle(eulers.x);
    105.         eulers.y = NormalizeAngle(eulers.y);
    106.         eulers.z = NormalizeAngle(eulers.z);
    107.         return eulers;
    108.     }
    109.  
    110.     private static float NormalizeAngle(float angle)
    111.     {
    112.         if (angle > 270f)
    113.         {
    114.             angle -= 360f;
    115.         } else if (angle < -270f)
    116.         {
    117.             angle += 360f;
    118.         }
    119.  
    120.         return angle;
    121.     }
    122.  
    123.     public void WriteDataToAnimationClip()
    124.     {
    125.         // Relative Path
    126.         string relativePath = AnimationUtility.CalculateTransformPath(animationTransform, animator.transform);
    127.  
    128.         // Set animation clips
    129.         animationClip.SetCurve(relativePath, typeof(Transform), "localEulerAngles.x", rotationCurveX);
    130.         animationClip.SetCurve(relativePath, typeof(Transform), "localEulerAngles.y", rotationCurveY);
    131.         animationClip.SetCurve(relativePath, typeof(Transform), "localEulerAngles.z", rotationCurveZ);
    132.     }
    133. }
    134.  
    This is how to use it for example:

    Code (CSharp):
    1. // Initialize, for example in Start()
    2. rotationData = new AnimationRecorderRotationData(animator, transformsForRotation, animationClip);
    3.  
    4. // Update, for example in Update()
    5. rotationData.SaveKeyFrame();
    6.  
    7. // Save, for example in OnDestroy()
    8. rotationData.WriteDataToAnimationClip();
    9.  
    The solution was to use the euler angles, normalize them between -180 and 180 and then use localEulerAngles when writing the curve to the animation clip.

    Sources: https://forum.unity.com/threads/converting-animation-curve-from-quaternion-to-euler-via-script.256272/
    and https://github.com/Unity-Technologi...AnimationWindow/RotationCurveInterpolation.cs