Chris Banwell
Chris Banwell

Reputation: 82

Ensure rotation takes the same amount of time regardless of angle to be rotated in Unity

I hope somebody can help. I have a script that rotates a globe, over which a plane (which is stationary) flies from 1 country to another. This all works fine and is achieved with the following script

while (!DestinationReached)
    {
        Vector3 planetToCountry = (Destination.localPosition - Vector3.zero);//.normalized; //vector that points from planet to country
        Vector3 planetToCamera = (camTr.position - tr.position).normalized; //vector that points from planet to camera/plane

        Quaternion a = Quaternion.LookRotation(planetToCamera);
        Quaternion b = Quaternion.LookRotation(planetToCountry);

        b = Quaternion.Inverse(b);

        newRotation = a * b;
        tr.rotation = Quaternion.Slerp(tr.rotation, newRotation, 0.01f);

        if (Approximately(tr.rotation, newRotation, 0.0001f)) //if here the plane has arrived
        {
            Debug.Log("Destination reached");
            DestinationReached = true;
        }
        yield return new WaitForEndOfFrame();
        
    }

It essentially calculates the angle between the plane (the camera is attached to the plane GO and views it from above) and the destination the globe needs to rotate to so that the plane looks as though it flies to the destination.

The issue I have is I need to make the flight time uniform regardless of the angle the globe must rotate, so lets say it must be 5 seconds, regardless if the plane flies from Paris to Ireland or Paris to Australia. Anybody have any ideas on how to do this.

I have to admit, I nicked this script for the web, as my Vector and Quaternion mathematics is hopeless :)

Upvotes: 0

Views: 37

Answers (2)

derHugo
derHugo

Reputation: 90580

If you want to be flexible and e.g. add some easing at beginning and end but still finish within a fixed duration I would do it like this (I'll just assume here that your calculating the final rotation is working as intended)

// Adjust the duration via the Inspector
[SerializeField] private float duration = 5f;

private IEnumerator RotateRoutine()
{
    // calculate these values only once!

    // store the initial rotation
    var startRotation = tr.rotation;

    // Calculate and store your target ratoation
    var planetToCountry = (Destination.localPosition - Vector3.zero);
    var planetToCamera = (camTr.position - tr.position);

    var a = Quaternion.LookRotation(planetToCamera);
    var b = Quaternion.LookRotation(planetToCountry);
    b = Quaternion.Inverse(b);

    var targetRotation = a * b;

    if(duration <= 0)
    {
        Debug.LogWarning("Rotating without duration!", this);
    }
    else
    {
        // track the time passed in this routine
        var timePassed = 0f;
        while (timePassed < duration)
        {
            // This will be a factor from 0 to 1
            var factor = timePassed / duration;
            // Optionally you can alter the curve of this factor
            // and e.g. add some easing-in and - out
            factor = Mathf.SmoothStep(0, 1, factor);
   
            // rotate from startRotation to targetRotation via given factor
            tr.rotation = Quaternion.Slerp(startRotation, targetRotation, factor);

            // increase the timer by the time passed since last frame
            timePassed += Time.deltaTime;

            // Return null simply waits one frame
            yield return null;   
        }
    }

    // Assign the targetRotation fix in order to eliminate 
    // an offset in the end due to time imprecision
    tr.rotation = targetRotation;

    Debug.Log("Destination reached");
}

Upvotes: 1

Lotan
Lotan

Reputation: 4283

So the problem here is the t used on your Quaternion.Slerp method, it's constant. This t is the "step" the slerp will do, so if it's contstant, it won't depend on time, it will depend on distance.

Try instead to do something like this, being timeToTransition the time you want that every rotation will match:

public IEnumerator RotatintCoroutine(float timeToTransition)
{
    float step = 0f;
    while (step < 1)
    {
        step += Time.deltaTime / timeToTransition;
        //...other stuff
        tr.rotation = Quaternion.Slerp(tr.rotation, newRotation, step);
        //...more stuff if you want
        yield return null;
    }
}

Edit: addapted to your code should look like this

float timeToFly = 5f;
while (!DestinationReached)
{
    step += Time.deltaTime / timeToTransition;

    Vector3 planetToCountry = (Destination.localPosition - Vector3.zero);//.normalized; //vector that points from planet to country
    Vector3 planetToCamera = (camTr.position - tr.position).normalized; //vector that points from planet to camera/plane

    Quaternion a = Quaternion.LookRotation(planetToCamera);
    Quaternion b = Quaternion.LookRotation(planetToCountry);

    b = Quaternion.Inverse(b);

    newRotation = a * b;
    tr.rotation = Quaternion.Slerp(tr.rotation, newRotation, step);

    if (step >= 1) //if here the plane has arrived
    {
        Debug.Log("Destination reached");
        DestinationReached = true;
    }
    yield return null();    
}

Upvotes: 0

Related Questions