Search code examples
unity-game-enginerotationquaternionslerp

Unity 3d Quaternion Rotation Lerp of 3d Object


I am working on a Unity project. There is a car wiper and i want to rotate it in a way that when I pres "wipers toggle on" wipers start rotating from 0,0,0 to 0,0,-45 and start lerping. But when I press "wipers toggle off" the wipers must rotate back to 0,0,0. For example if current wipers rotation are 0,0,-20 and I press the "wipers toggle off" key they wipers must be rotateTowards 0,0,0. Also, If I press "wipers toggle on" again the wipers must start rotating from 0,0,0 to 0,0,-45. Now, the situation is that Wipers are rotating but when I press "wipers toggle off" the wipers stop at exactly the same current rotation point where they are for example (0,0,-30). And when I press "wipers toggle on" again the wipers starts from weird different rotation point. Here is my code:

using UnityEngine;
using System.Collections;

public class Wipers : MonoBehaviour 
{
    [SerializeField] protected Vector3 m_from = new Vector3(0.0F, 0.0F, 0.0F);
    [SerializeField] protected Vector3 m_to = new Vector3(0.0F, -45.0F, 0.0F);
    [SerializeField] protected float m_frequency = 1.0F;

    protected virtual void Update() 
    {
        if (ControlFreak2.CF2Input.GetAxis ("wipers") != 0) 
        {
            Quaternion from = Quaternion.Euler(this.m_from);
            Quaternion to = Quaternion.Euler(this.m_to);

            float lerp = 0.5F * (1.0F + Mathf.Sin(Mathf.PI * Time.realtimeSinceStartup * this.m_frequency));
            this.transform.localRotation = Quaternion.Lerp(from, to, lerp);     
        }
    }
}

Solution

    • Your issue when turning on is that you are directly using Time.realtimeSinceStartup which also continues running while the routine is turned off of course! So you never know at which point the wipers will be when turned on.

      → You rather want to use the time since the wipers where started instead.

    • Your issue when turning off is of course that you immediately stop moving.

      → You rather want to always complete a full cycle after the wipers have been turned off.

    Instead of doing this in Update I would suggest to use a Coroutine. Coroutines are way easier to control and maintain than doing stuff in Update directly. In fact they are like small temporary Update like routines and execute right after the real Update has finished.

    A Coroutine you can let continue and complete a full cycle until it is actually done.

    In Update you would only check the user input and start a routine (if none is running already) and the routine can run and terminate a complete cycle independently:

    [SerializeField] protected Vector3 m_from = new Vector3(0.0F, 0.0F, 0.0F);
    [SerializeField] protected Vector3 m_to = new Vector3(0.0F, -45.0F, 0.0F);
    [SerializeField] protected float m_frequency = 1.0F;
    
    // TODO only for debugging
    [SerializeField] private bool ControlFreak2Dummy;
    
    private bool wipersOn;
    private Quaternion from;
    private Quaternion to;
    
    private void Awake()
    {
        // Store these once -> more efficient then recalculate them everytime
        from = Quaternion.Euler(m_from);
        to = Quaternion.Euler(m_to);
    }
    
    protected virtual void Update()
    {
        // TODO switch these back
        //if (ControlFreak2.CF2Input.GetAxis ("wipers") != 0)
        if (ControlFreak2Dummy)
        {
             if(!wipersOn)  
             {
                StartCoroutine(Wipers());
             }
        }
    }
    
    // To make things easier for us this in itself closed routine is exactly ONE FULL wipers cycle
    // so we can determine exactly when a full cycle is done or not
    private IEnumerator Wipers()
    {
        // block concurrent routines
        wipersOn = true;
    
        var duration = 1f / m_frequency;
        var timePassed = 0f;
    
        while (timePassed <= duration)
        {
            // Note: Your sinus factor calculation was a bit strange
            // (or maybe this early I'm way too stupid for maths lol ^^)
            // It was always minimum 0.5 and maximum 1, you rather want to pingpong between 0 and 1
            //
            // This now moves forth and back exactly once between 0 and 1
            var factor = Mathf.Sin(Mathf.PI *  timePassed / duration);
    
            transform.localRotation = Quaternion.Lerp(from, to, factor);
    
            // increase timePassed by the time passed since last frame
            timePassed += Time.deltaTime;
    
            // allows Unity to "pause" here, render this frame and
            // continue from here in the next frame
            yield return null;
        }
    
        // make sur it really terminates in 0
        transform.localRotation = from;
    
        // tell others that this routine is done
        wipersOn = false;
    }
    

    enter image description here