Lerping a rotation: a bit of confusion.

Hello, i am attempting to make a simple script that rotates an object by 45 degrees when the R key is pressed. I already achieved this with transform.rotate, but that does it instantly. I want it to be a bit more gradual, so i came up with the following:

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.R))
        {
            //transform.Rotate(new Vector3(0, 0, 45));
            oldrotation = transform.rotation;
            nextrotation = new Quaternion(transform.rotation.x, transform.rotation.y, transform.rotation.z + 45, transform.rotation.w);
            StartCoroutine("RotateCube");
        }
    }

    void RotateCube()
    {
        Debug.Log(gameObject.name + " is rotating");
        Quaternion.RotateTowards(oldrotation, nextrotation, speed * Time.deltaTime);
    }
}

it works, i think, because checking the log in the editor shows the message, however, the rotation is not happening. How come?

        Quaternion.RotateTowards(oldrotation, nextrotation, speed * Time.deltaTime);

This just calculates a new rotation. It doesn’t actually rotate anything. You need to assign the rotation to the object:

        Quaternion rot = Quaternion.RotateTowards(oldrotation, nextrotation, speed * Time.deltaTime);
        transform.rotation = rot;

However I will tell you right now this code will not work correctly:

            nextrotation = new Quaternion(transform.rotation.x, transform.rotation.y, transform.rotation.z + 45, transform.rotation.w);

Look into Quaternion.Euler to do this correctly.

i changed the code to the following:

  // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.R))
        {
            //transform.Rotate(new Vector3(0, 0, 45));
            oldrotation = transform.rotation;
            nextrotation = Quaternion.Euler(0, 0, 45);
            StartCoroutine("RotateCube");
        }
    }

    IEnumerator RotateCube()
    {
        Debug.Log(gameObject.name + " is rotating");
        Quaternion rot = Quaternion.RotateTowards(oldrotation, nextrotation, speed * Time.deltaTime);
        transform.rotation = rot;
        yield return null;
    }

and it still does not appear to be working. Am I missing something? I feel really stupid right now.

So, because you are using RotateTowards (and a coroutine) I assume you are trying to get it to smoothly rotate over time, right?

Currently your coroutine only rotates for one frame. That means it will rotate only a tiny amount. You’re almost there! You need to put your current rotation code inside a loop to make it continuously rotate over time. But let’s not jump ahead. Let’s first do the rotation instantly just to make sure we did all of our calculations right:

Try this out first just to make sure the end result is correct:

    IEnumerator RotateCube()
    {
        Debug.Log(gameObject.name + " is rotating");
        transform.rotation = nextRotation
        yield return null;
    }

Then, if that looks good we can try to rotate the thing over time:

IEnumerator RotateCube() {
    Debug.Log(gameObject.name + " is rotating");

    while(transform.rotation != nextrotation) {
        Quaternion rot = Quaternion.RotateTowards(oldrotation, nextrotation, speed * Time.deltaTime);
        transform.rotation = rot;
        yield return null;
    }
}

Weird, it looks fine when doing this test to make sure it looks right:

        IEnumerator RotateCube()
        {
            Debug.Log(gameObject.name + " is rotating");
            transform.rotation = nextRotation
            yield return null;
        }

but change it to the second one, and suddenly it doesnt move.

edit: ignore the above, it now works with the following:

    void Update()
    {
          if (Input.GetKeyDown(KeyCode.R))
          {
              //transform.Rotate(new Vector3(0, 0, 45));
              oldrotation = transform.rotation;
              nextrotation = Quaternion.Euler(0, 0, transform.rotation.z + 45);

              StartCoroutine("RotateCube");
          }

    }

    IEnumerator RotateCube()
    {
        Debug.Log(gameObject.name + " is rotating");
        while(transform.rotation != nextrotation)
        {
            Quaternion rot = Quaternion.Lerp(transform.rotation, nextrotation, speed * Time.deltaTime);
            transform.rotation = rot;
            yield return null;
        }

    }
}

now my question is how to i rotate it by 45 degrees each time, because as is, it appears that “Quaternion.Euler” always ends up with the same result, even though i put transform.rotation.z + 45

My bad I had a small bug in the second version of the script. Try this:

IEnumerator RotateCube() {
    Debug.Log(gameObject.name + " is rotating");
    while(transform.rotation != nextrotation) {
        Quaternion rot = Quaternion.RotateTowards(transform.rotation, nextrotation, speed * Time.deltaTime);
        transform.rotation = rot;
        yield return null;
    }
}

in addition to my question about the 45 degrees each time, i may as well add this, how do i get rid of the slowing down near the end of the lerp of the rotation? once it gets close to the end rotation it slows down quite a lot. I know its because i’m using the current transform position in the lerp, thats just to make sure it works at all. I could have sworn that, at least with lerps of vector3 positions, to fix the slowing down you use both and end point and a start point in the lerp function, however using Quaternion rot = Quaternion.Lerp(oldrotation, nextrotation, speed * Time.deltaTime);
does not rotate the object properly, it appears to hold ALMOST still, though jittering a bit.

Using MoveTowards in your script as I did in my post will get rid of that issue: Lerping a rotation: a bit of confusion.

The other option is to use Lerp properly. speed * Time.deltaTime is not really the right thing to put into the third parameter of Lerp. What you need is a value that smoothly goes from 0 to 1 over a set period of time. The third parameter is basically a percentage of the rotation from A to B. So if you pass in speed * Time.deltaTime, that’s going to pretty much be the same value every frame, subject to the fluctuation of the device’s framerate. If you insist on using Lerp and you want a linear interpolation you need to do something like this:

    IEnumerator RotateCube()
    {
        Debug.Log(gameObject.name + " is rotating");
        var t = 0;
        var lerpDuration = 2f; // duration in seconds
        while(t < 1)
        {
            t += Time.deltaTime / lerpDuration;
            Quaternion rot = Quaternion.Lerp(oldrotation, nextrotation, t);
            transform.rotation = rot;
            yield return null;
        }
    }

Notice that instead of a “speed”, we’re using a “duration”. Lerp is easier to work with using a duration of the animation instead of a speed.

Thank you, i changed it to rotatetowards and that indeed worked. Now i just need to figure out how to get it to rotate 45 degrees from whatever position its at when R is pressed. As is, it only works the first time, Because i think Quaternion.Euler gets the same thing every time even though its transform.position.z + 45

Hm, odd, i tried the following

    // Update is called once per frame
    void Update()
    {
          if (Input.GetKeyDown(KeyCode.R))
          {
              //transform.Rotate(new Vector3(0, 0, 45));
              oldrotation = transform.rotation;
            nextrotation = transform.rotation;
            nextrotation.z += 45;
              StartCoroutine("RotateCube");
          }

    }

    IEnumerator RotateCube()
    {
        Debug.Log(gameObject.name + " is rotating");

        while(transform.rotation != nextrotation)
        {
            Quaternion rot = Quaternion.RotateTowards(transform.rotation, nextrotation, 0.7f);
            transform.rotation = rot;
            yield return null;
        }
        if(transform.rotation == nextrotation)
        {
            Debug.Log(transform.rotation);
            Debug.Log(nextrotation);
            Debug.Log("DONE");
            StopCoroutine("RotateCube");
        }
    }
}

and it doesnt rotate, instead always logging as “DONE” even though the result is blatantly false. these lines:

            Debug.Log(transform.rotation);
            Debug.Log(nextrotation);
            Debug.Log("DONE");
            StopCoroutine("RotateCube");

would suggest that the current rotation and nextrotation are equal, yet the console log shows otherwise: https://i.gyazo.com/e17bc510b605f5300d98d805fabc005b.png

they are blatantly different.

I solved it by removing line 9 and changing line 8 to: nextrotation = Quaternion.Euler(0, 0, transform.rotation.eulerAngles.z + 45);

Oh that part’s simple. Just change your nextrotation calculation to this:

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.R))
        {
            oldrotation = transform.rotation;
            nextrotation = transform.rotation * Quaternion.Euler(0, 0, 45);
            StartCoroutine("RotateCube");
        }
    }

When you multiply quaternions, it’s like applying the two rotations in sequence. So what this says is basically “Give the me the rotation that is my current rotation followed by another 45 degree rotation around the Z axis”.

For a simple rotate (like a plane rudder):

directionObject = Input.GetAxis("Horizontal");
this.transform.localRotation = Quaternion.Euler(0,maxRotate * directionObject,0);```

This will rotate up to 45-degrees each direction (left-right) and return back to center (0) when no input is applied.

How would I smoothly rotate that? I used localRotation and it just snaps directly there, I need it to be smooth. Thanks