Math/Lerp problem with rotations

Hi. I have a 2D game and I want platforms that rotate between 2 states along the Z.

  • I want to be able to place the platform at any angle and tell it how many degrees to move (ie:90 or -45).
  • I want to control it by time (ie: between start and end it will take 3 sec to complete).
  • I want it to have a constant speed (ie: if its already mid-rotation, it should take half the total duration to complete).

My problem is that it currently works if I leave the starting platform at 0 degrees Z. If I rotate it to other angles, it gets messed up. Maybe you guys can tweak what I have, or maybe I’ve gone about this the wrong way, any help is appreciated!

Here’s what I have:

IEnumerator Turn(Vector3 target)
	{
		float t = 0;
		float tt = duration; //amount of time it should take to go from start to end rotation
		Quaternion r = transform.rotation; //current rotation
		Quaternion tRot = Quaternion.Euler(target); //target is the eulerAngle I want to reach
		
		Vector3 euler = transform.localEulerAngles;
		
		//Maintains constant speed based on the total duration of time
		tt = Mathf.Abs(((Mathf.Abs(target.z) - euler.z)/targetAngle) * duration); //**problem line**
		
		print (tt + " = " + Mathf.Abs(target.z) + " - " + euler.z + " / " + targetAngle + " * " + duration);
		
		while (t < tt)
		{
			t += Time.deltaTime;
			transform.rotation = Quaternion.Lerp(r, tRot, t/tt);
			yield return 0;
		}
		transform.rotation = tRot;

	}

See Quaternion.RotateTowards - e.g.

private Quaternion _targetRotation;
void Update()
{
    transform.rotation = Quaternion.RotateTowards(transform.rotation, _targetRotation, 90.0f * Time.deltaTime);
}

This will rotate the object towards _targetRotation at a constant rate of 90 degrees per second. Other code can set _targetRotation however you like.

If you do it with coroutines then I’d still suggest using this construct, and testing Quaternion.Angle to determine when the interpolation is complete. Something like this:

IEnumerator Turn(Quaternion targetRotation, float turnRate)
{
    while (Quaternion.Angle(transform.rotation. targetRotation) < turnRate * Time.deltaTime)
    {
        transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, turnRate * Time.deltaTime);
        yield return 0;
    }

    transform.rotation = tRot;
}

Note that it recalculates turnRate * Time.deltaTime each time through the loop, as the delta may be different each time your coroutine is resumed.

Thanks!
Your solution isn’t perfect but its the best I’ve found. I’d rather have time controls rather than speed controls, but I’ll manage.

And in case someone else decides to use your little script, there’s a small mistake. The while statement should be:

 while (Quaternion.Angle(transform.rotation, targetRotation) > turnRate * Time.deltaTime)

As it’s written it just snaps to the end rotation with no interpolation.

This also has a problem if the Quaternion.Angle == 180. It tends to go clockwise in that case, I haven’t found a way to mitigate this other than changing my rotations ever so slightly to be just above or just below a 180 angle. But this is limiting if, say, I wanted to rotate 270 degrees. It would take the shortest route, not the intended 3/4 turn.

You explicitly said you wanted it to move with constant speed though, which is what this does.

From your second post it sounds like you’d get the result you want from interpolating the vector of Euler angles instead - you can certainly do that, Vector3.MoveTowards works in the same way as Quaternion.RotateTowards and you can feed the interpolated Vector3 through Quaternion.Euler every frame:

    private Vector3 _targetEuler;
    private Vector3 _currentEuler;

    public void Update()
    {
        _currentEuler = Vector3.MoveTowards(_currentEuler, _targetEuler, speed * Time.deltaTime);
        transform.rotation = Quaternion.Euler(_currentEuler);
    }

If you only want to rotate one axis, Mathf,MoveTowardsAngle is another option, it will always take the shortest route.

e.g.:

var targetAngle:float;
var rotateTime:float; //in seconds

var rotateSpeed:float = Mathf.Abs(targetAngle - transform.localEulerAngles.z)/rotateTime; //calculate rotation speed, degree per second

function Update() {
    transform.localEulerAngles.z = Mathf.MoveTowardsAngle(transform.localEulerAngles.z, targetAngle, rotateSpeed * Time.deltaTime);
}