Search code examples
unity-game-enginerotationquaternions

Can't quite understand quaternion rotation (euler angles)


I'm currently working on a puzzle game, the part where I'm struggling - a cog which you can rotate. It's a 3d model and upon clicking on the left side it should rotate counter-clockwise by 40 degrees, opposite on the right side. Anyway the rotation happens on the y-axis, it all works fine until the y rotation goes into negative angles (-40) in that case instead of rotating to the left it rotates back to 0. Here's the code in the Move method called by Update :

StartCoroutine(RotateGearA(Vector3.right * 40, 1));

And the coroutine here:

IEnumerator RotateGearA(Vector3 byAngles, float inTime) {
    isMoving = true;
    var fromAngle = cogA.transform.rotation;
    var toAngle = Quaternion.Euler(cogA.transform.eulerAngles + byAngles);
    for(var t = 0f; t < 1; t += Time.deltaTime/inTime) {
        cogA.transform.rotation = Quaternion.Lerp(fromAngle, toAngle, t);
        yield return null;
    }
}

I'm not sure whether I should implement an if-else statement in the Move() method to handle the edge cases or if there's another approach?


Solution

  • This happens because you don't just add dynamic values to Euler angles.

    When using the .eulerAngles property to set a rotation, it is important to understand that although you are providing X, Y, and Z rotation values to describe your rotation, those values are not stored in the rotation. Instead, the X, Y & Z values are converted to the Quaternion's internal format.

    When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation.

    To avoid these kinds of problems, the recommended way to work with rotations is to avoid relying on consistent results when reading .eulerAngles particularly when attempting to gradually increment a rotation to produce animation. For better ways to achieve this, see the Quaternion * operator.

    So in general in order to add a rotation to an existing one you should rather use the Quaternion operator *

    IEnumerator RotateGearA(Vector3 byAngles, float inTime) 
    {
        isMoving = true;
    
        var fromAngle = cogA.transform.rotation;
        var toAngle = fromAngle * Quaternion.Euler(byAngles);
        for(var t = 0f; t < 1; t += Time.deltaTime / inTime) 
        {
            cogA.transform.rotation = Quaternion.Lerp(fromAngle, toAngle, t);
            yield return null;
        }
    
        // Just to be sure to end with a clean rotation
        cogA.transform.rotation = toAngle;
    
        // You probably wanted to reset this flag when done ;)
        isMoving = false;
    }