Quaternion to euler makes some odd conversions

Wanting to do a level editor of sorts I was converting quaternions from a rotation ‘handle’ to euler angles to display and store, but you find some oddities not seen in the Unity inspector which tracks a euler ‘hint’ value alongside a quaternion for rotations! Quaternion to three hinge joints - Unity Engine - Unity Discussions

Anyway this is most obvious if you rotate on the x-axis so:

transform.rotation = Quaternion.Euler( new Vector3( 110, 0, 0 ) );
Debug.Log( transform.eulerAngles.ToString() );

will give you 70,180,180
I wonder if there is a simple way to avoid this ‘flip’ effect where 2 axis change and the rotation reverses on the original, i.e. if you rotate on x axis beyond 90,0,0 Perhaps only by tracking the previous value and the new one could you then detect it. But it gets much more complicated as you rotate on many axis.

Ultimately it is not unacceptable to have the above but can make it confusing if you show the end user euler angles, which is presumably why Unity track a hinted value

1 Like

This is why we use Quaternions, as the Euler angles are ambiguous. Every use has its own needs.

Usually you can get around this by recomputing your own that are semantically reasonable for your situation.

One ultra simple example is to recompute the “pitch” (nose up, nose down) of a thing by taking the arcsin() of the y component of its transform.forward.

using UnityEngine;

public class PitchAngle : MonoBehaviour
{
    void Update()
    {
        Vector3 forward = transform.forward;

        float y = forward.y;

        float radians = Mathf.Asin(y);

        float degrees = radians * Mathf.Rad2Deg;

        Debug.Log($"pitch: {degrees}");
    }
}

Obviously it is up to you to handle an inverted object. Is its nose considered to be “up” or “down”?

I use stuff like this all over the place in my Jetpack Kurt game: from the control system to the cockpit readouts, attitude, indicator, compass, etc.

1 Like

I just want to clear up some facts. Unity uses an euler angles sequence of Y-X-Z (from a local axis perspective, or Z-X-Y from a world axis perspective). So the X axis is the “middle axis”. As such the middle axis usually is limited to ±90 as conceptionally we define object orientation by an object’s forward and up axis. As soon as you rotate more than 90 on X, the up axis is flipped upside down.

However to flip something upside down you usually would rotate by 180° on the z axis and that’s what the reconstruction does.

Also note that even quaternions have always two ways to represent the same orientation since a quaternion is essentially an axis-angle representation. You can alwasy flip the axis around and rotate the the other way round to get the same result. Euler angles in general have a similar property. You can always flip all axis by 180° and you get the same orientation. Technically none is more reasonable than the other. However with the usual limitation of X being between±90° the conversion Unity did makes sense.

You should never rely on eulerAngles, especially when you only pick out a single angle. EulerAngles is a combined unit and they only make sense as a tripple of numbers.

The EulerAngles hint that Unity stores in the editor is for editing purposes only since the input during edit mode is completely driven by euler angles. At runtime / playmode the angles should also flip when that change has been initiated from script, but I’m not 100% sure if this is still the case.

So like Kurt said, if you need a certain angle, it’s best to calculate it manually based on your desired axis. Kurt’s solution only works when you don’t have any bank angle. But in the end euler anglels in generall are difficult to make sense of when we talk about something like an absolute airplane orientation. Gimbal lock is a thing and it’s also related to the middle axis being 90°. In this case “yaw” and “roll” would be identical and you can not distinguish between them.

If you want to know the angle around a certain axis in relation to certain reference frame, manually converting the relevant direction vector and using Mathf.Atan2 is usually the best way. Though you almost always will run into some kind of flipping issues.

2 Likes

Thanks that’s good info but the local euler angles being different to world one?
The docs say “Z axis, the X axis, and the Y axis, in that order” for setting both euler angles and that is my experience.

I wasn’t talking about local euler angles :slight_smile: Any euler angles are relative to a certain space. local euler angles are simply relative to the “parent space”. What I was talking about is the interpretation within that space. Euler angles perform 3 consecutive rotations, one after the other. Each essentially changes / rotates the space. The question is around which axis does each rotation happen?

When you look at it from an object’s point of view. So you have a rigid object with 3 axis that always rotate with the object, then the rotation around those “local” axis is Y-X-Z. So you assume that you start not rotated in your parent space. That can be world space or a nested space inside a parent. Then you rotate around the objects local Y axis first which will change the orientation of the X axis. Then you rotate around that rotated X axis which will in turn change the orientation of the local Z axis. And finally you rotate around the local Z axis.

However from the actual matrix multiplication order, you do it the other way round. So your object starts not rotated in your parent space and first rotate around the Z axis of your parent space. Then you rotate the object around the X axis of your parent space and finally you rotate around the Y axis of your parent space. The result is the same, but the way to look at it is different.

You can try this with an object on your desk. Just imagine your desk is your parent or world space and then try any combination, it actually works out.

Keep in mind that matrix transformations actually always happen around the origin. While you usually think about an object being positioned at a certain (x,y,z) point and then you rotate it around its local axis, what actually happens is the other way round. The object starts at (0,0,0), is rotated, then it’s translated. then translated again so the origin is the camera position and finally rotated again by the inverse of the camera rotation as the camera essentially is always fix at the origin :slight_smile: It usually makes more sense to think about transformations locally, but there are always those two perspectives and they have the inverse order.

To be more clear. We could rotate an object in 3 steps. Assuming the object starts not rotated within its parent space (assume worldspace for simplicity for now) we can do 3 distinct rotations in order

// around world axis
transform.localEulerAngles = Vector3.zero;
transform.Rotate(0,0,Z,Space.World);
transform.Rotate(X,0,0,Space.World);
transform.Rotate(0,Y,0,Space.World);

// around local axis
transform.localEulerAngles = Vector3.zero;
transform.Rotate(0,Y,0,Space.Self);
transform.Rotate(X,0,0,Space.Self);
transform.Rotate(0,0,Z,Space.Self);

Those should result in the same orientation. Of course when intermediate coordinate spaces are involved things can get more tricky.

We could also do it like this:

Transform obj;

// around world parent axis
obj.localRotation = Quaternion.identity;
obj.localRotation = Quaternion.AngleAxis(Z, Vector3.forward) * obj.localRotation
obj.localRotation = Quaternion.AngleAxis(X, Vector3.right) * obj.localRotation
obj.localRotation = Quaternion.AngleAxis(Y, Vector3.up) * obj.localRotation

// could also be done as a combined quaternion like this:
var rot = Quaternion.AngleAxis(Y, Vector3.up) *
    Quaternion.AngleAxis(X, Vector3.right) *
    Quaternion.AngleAxis(Z, Vector3.forward);
obj.localRotation = rot;

// around local axis
obj.localRotation = Quaternion.identity;
obj.localRotation = Quaternion.AngleAxis(Y, obj.up) * obj.localRotation
obj.localRotation = Quaternion.AngleAxis(X, obj.right) * obj.localRotation
obj.localRotation = Quaternion.AngleAxis(Z, obj.forward) * obj.localRotation

Note that in the second case when we rotate around the local axis, we can not combine the quaternions since we actually need the rotated axis from the previous rotation. The local representation is more like a physical gimbal hierarchy

Ok well I understand it from the unity perspective and how the numbers convert in order to actual orientation, I guess there are other ways to ‘visualize’!

1 Like

Yes. The order that Unity specifies, when rotating around the parent space axis essentially works from the inside outwards. So you start with the innermost gimbal, then with the immediate parent and so on up the chain. Though when thinking about the alignment how you would arrange them if you actually had a physical gimbal hierachy you would start rotating the outermost one first and end up with the innermost last.