Search Unity

Help with using lerp to rotate 3d object multiple times

Discussion in 'Scripting' started by al-gco, Jul 11, 2019.

  1. al-gco

    al-gco

    Joined:
    May 12, 2016
    Posts:
    7
    My situation is this:

    I have an object whose rotation in Euler angles is (0, 12, 0) and I want to rotate it using a lerp function to eg (3600, 12, 0). That is to say, I want it to rotate around the x axis 10 times.

    I want to use lerp because I want to use an animation curve to control the rate of the spin - starting slowly, speeding up, finishing slowly. I can't use a sin curve or similar ease function as I need something more idiosyncratic, hence an animation curve I can edit. Also the end rotation and duration of the spin are set in code as they vary, so I can't use an animation. And to make it super complicated, the start rotation also varies!

    My first thought was a simple Vector3.lerp between the euler angles, but this doesn't work - as Unity uses Quaternions internally it comes out wonky at the end.

    I then thought I would convert the start and end positions into Quaternion values and slerp between them, but that only rotates once because that's all that Quaternions can represent.

    So now I have this monstrosity:

    Code (CSharp):
    1. while (timer < spinTime)
    2.     {
    3.         transform.localRotation = Quaternion.Euler(Vector3.Lerp(startRot, endRot, spinCurve.Evaluate(timer / spinTime)));
    4.    
    5.         yield return null;
    6.    
    7.         timer += Time.deltaTime;
    8.     }
    Which also doesn't work because even though the start rotation and end rotation as Euler angles have the same y and z values, when converted to Quaternions they don't, so it spins around multiple axes.

    I also tried manually forming the start rotation value to force the z value to remain as 0:

        Vector3 startRot= Quaternion.Euler(transform.localEulerAngles.x, transform.localEulerAngles.y, 0).eulerAngles;

    But you guessed it, even though transform.localEulerAngles.z == 0, the startRot this generates does not match this:
        Vector3 startRot= Transform.localEulerAngles;

    So when the lerp starts it jumps to a different starting position.

    Help!

    (Also posted on Unity Answers: https://answers.unity.com/questions/1647788/how-can-i-use-lerp-to-rotate-an-object-multiple-ti.html)
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    Quaternions can be "multiplied" (or rather, just combined; the Quaternion class overrides the * operator). You can do something like this:
    Code (csharp):
    1. Quaternion startRot = transform.localRotation;
    2. while (timer < spinTime)
    3.     {
    4. Quaternion spinningRot = Quaternion.AngleAxis(spinCurve.Evaluate(timer / spinTime), Vector3.up);
    5.         transform.localRotation = startRot * SpinningRot;
    6.  
    7.         yield return null;
    8.  
    9.         timer += Time.deltaTime;
    10.     }
     
  3. al-gco

    al-gco

    Joined:
    May 12, 2016
    Posts:
    7
    A light in the darkness! But this doesn't fully make sense to me. spinCurve.Evaluate(timer / spinTime) only provides a value between 0 and 1 - how do I define what the end rotation should be?
     
  4. al-gco

    al-gco

    Joined:
    May 12, 2016
    Posts:
    7
    I'm doing this in the loop and it almost works:

    transform.localRotation = Quaternion.AngleAxis(Mathf.Lerp(0, endX, playLoop.dunkSpinCurve.Evaluate(timer / spinTime)), Vector3.right);


    Thanks for pointing me in this direction! I'll keep banging away at it.
     
  5. al-gco

    al-gco

    Joined:
    May 12, 2016
    Posts:
    7
    No, I'm missing the point, I think. AngleAxis rotates by an amount, not to an amount.

    I am going to look into multiplying Quaternions. I will also stop posting my every thought from this point on.
     
  6. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    You're not too off base. So on its own, AngleAxis rotates to an amount. It's the * that turns this into by an amount.
     
  7. al-gco

    al-gco

    Joined:
    May 12, 2016
    Posts:
    7
    I solved my problem! Not in a generally applicable way, so probably not too helpful to others, but just in case...

    In fact the first thing I tried (Vector3.lerp between the Euler angles) was the correct method. The problem was that I was creating end rotation values based on what I was seeing in the inspector, but when I called transform.localEulerAngles I was getting quite different numbers. As my start values were not what I expected I was getting unwanted results.

    My solution is to not get the start values from transform.localEulerAngles, but to deduce what they should be based on the game state, and use that as the initial lerp value. Luckily my project makes this pretty easy as there are not many possible starting positions.

    The lesson is that transform.localEulerAngles should be used with caution as it does not provide a predictable result, and it will probably not match what is visible in the inspector.

    As I say not pretty or generally applicable, but working! @StarManta thank you for your help, you got me to do some fruitful Googling!
     
  8. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    Yes indeed. I actually have written an article about this very thing, and thought it best to steer you away from Euler angles entirely ;)
    tldr is that Euler angles are pretty terrible in 90% of use cases.
     
    al-gco likes this.
  9. al-gco

    al-gco

    Joined:
    May 12, 2016
    Posts:
    7
    Maybe that other 10% is when you're trying to lerp with multiple rotations around an axis? ;) Great article!