rotation order

is there a way to control the rotation order?

It appears the current order is zxy.

Is there a way to modify that, or do we just work with it that way?

The order is XYZ, in Euler angles; that is rotation around the x axis, rotation around Y, rotation around Z.

If you don’t like the order provided by Euler angles, just do it manually:

// For example, in whichever order you like....

rotation = Quaternion.AngleAxis(xAngle, Vector3.right) *
	Quaternion.AngleAxis(zAngle, Vector3.forward) *
	Quaternion.AngleAxis(yAngle, Vector3.up);
4 Likes

@manta-
I don’t believe that is true-

This is proven by creating a cube in Maya (setting the rotation order to XYZ), and one in unity. Start entering Euler values and you quickly see they are different.

EDIT:

Geez, I didn’t even notice maya is right-handed, and unity is left handed. This sort of thing makes my head hurt.

Wrong.
See the official docs [here].

5 Likes

I was doing a rudimentary fbx writer script and encountered similar problems with the rotations quaternion to euler order being not correct.

I stumbled upon a couple of c++ functions providing quaternion to euler conversion with various orders applied. I dont understand much of how it actually does the math but i ported it into c# and experimented.

When converting a unity rotation to a fbx rotation i found that quaternion2Euler(conv, RotSeq.zyx) seemed to do the trick.

Anyway here is the original c++ code source from a guy called Birdy:
http://bediyap.com/programming/convert-quaternion-to-euler-rotations/

And here is my port of the functions doing the work:

    enum RotSeq
    {
        zyx, zyz, zxy, zxz, yxz, yxy, yzx, yzy, xyz, xyx, xzy,xzx
    };

    Vector3 twoaxisrot(float r11, float r12, float r21, float r31, float r32){
        Vector3 ret = new Vector3();
        ret.x = Mathf.Atan2( r11, r12 );
        ret.y = Mathf.Acos ( r21 );
        ret.z = Mathf.Atan2( r31, r32 );
        return ret;
    }

    Vector3 threeaxisrot(float r11, float r12, float r21, float r31, float r32){
        Vector3 ret = new Vector3();
        ret.x = Mathf.Atan2( r31, r32 );
        ret.y = Mathf.Asin ( r21 );
        ret.z = Mathf.Atan2( r11, r12 );
        return ret;
    }

    Vector3 quaternion2Euler(Quaternion q, RotSeq rotSeq)
    {
        switch(rotSeq){
        case RotSeq.zyx:
            return threeaxisrot( 2*(q.x*q.y + q.w*q.z),
                q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                -2*(q.x*q.z - q.w*q.y),
                2*(q.y*q.z + q.w*q.x),
                q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z);
           

        case RotSeq.zyz:
            return twoaxisrot( 2*(q.y*q.z - q.w*q.x),
                2*(q.x*q.z + q.w*q.y),
                q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                2*(q.y*q.z + q.w*q.x),
                -2*(q.x*q.z - q.w*q.y));
           

        case RotSeq.zxy:
            return threeaxisrot( -2*(q.x*q.y - q.w*q.z),
                q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                2*(q.y*q.z + q.w*q.x),
                -2*(q.x*q.z - q.w*q.y),
                q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z);
           

        case RotSeq.zxz:
            return twoaxisrot( 2*(q.x*q.z + q.w*q.y),
                -2*(q.y*q.z - q.w*q.x),
                q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                2*(q.x*q.z - q.w*q.y),
                2*(q.y*q.z + q.w*q.x));
           

        case RotSeq.yxz:
            return threeaxisrot( 2*(q.x*q.z + q.w*q.y),
                q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                -2*(q.y*q.z - q.w*q.x),
                2*(q.x*q.y + q.w*q.z),
                q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z);

        case RotSeq.yxy:
            return twoaxisrot( 2*(q.x*q.y - q.w*q.z),
                2*(q.y*q.z + q.w*q.x),
                q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                2*(q.x*q.y + q.w*q.z),
                -2*(q.y*q.z - q.w*q.x));
           

        case RotSeq.yzx:
            return threeaxisrot( -2*(q.x*q.z - q.w*q.y),
                q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                2*(q.x*q.y + q.w*q.z),
                -2*(q.y*q.z - q.w*q.x),
                q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z);
           

        case RotSeq.yzy:
            return twoaxisrot( 2*(q.y*q.z + q.w*q.x),
                -2*(q.x*q.y - q.w*q.z),
                q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                2*(q.y*q.z - q.w*q.x),
                2*(q.x*q.y + q.w*q.z));
           

        case RotSeq.xyz:
            return threeaxisrot( -2*(q.y*q.z - q.w*q.x),
                q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                2*(q.x*q.z + q.w*q.y),
                -2*(q.x*q.y - q.w*q.z),
                q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
           

        case RotSeq.xyx:
            return twoaxisrot( 2*(q.x*q.y + q.w*q.z),
                -2*(q.x*q.z - q.w*q.y),
                q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                2*(q.x*q.y - q.w*q.z),
                2*(q.x*q.z + q.w*q.y));
           

        case RotSeq.xzy:
            return threeaxisrot( 2*(q.y*q.z + q.w*q.x),
                q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                -2*(q.x*q.y - q.w*q.z),
                2*(q.x*q.z + q.w*q.y),
                q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
           

        case RotSeq.xzx:
            return twoaxisrot( 2*(q.x*q.z - q.w*q.y),
                2*(q.x*q.y + q.w*q.z),
                q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                2*(q.x*q.z + q.w*q.y),
                -2*(q.x*q.y - q.w*q.z));
           
        default:
            Debug.LogError("No good sequence");
            return Vector3.zero;

        }

    }
2 Likes

Sorry for resurrecting an old thread but just wanted to thank thoobes, this code saved my life.

Also, I am not sure if I did something wrong but, although Unity’s documentation states that the rotation order is ZXY, when comparing the euler values from quaternion.eulerAngles and the values I get from the same quaternion using the function above, it seems Unity’s order is ZYX. If anyone knows why would this happen please let me know.

Cheers!

1 Like

Sorry for replying on such an old thread but I just had to express my gratitude for this comment made by some random person 12 years ago. I just spent the last 5 hours troubleshooting some issues with camera rotations in a game where you walk around a spherical planet and this comment GREATLY helped me solve them. Thanks for the help!

I can definitely confirm that ZYX is the correct orientation for Unity.
Also, thank you for the code thoobes!!

Converting EulerAngles to a 3*3 Matrix requires to use ZXY order, which can be described as can be seen in the code below.

7331842--890689--upload_2021-7-15_13-55-55.png

The bottom method is simple the Z-rotation-matrix, multiplied by X-rotation-matrix, multiplied by the Y rotation Matrix (= ZXY Matrix). This is the order that Unity uses for their matrix calculation.

  public static float[,] CreateFromEuler(Vector3 eulerInRadians)
    {
        float cx = Mathf.Cos(eulerInRadians.x);
        float cy = Mathf.Cos(eulerInRadians.y);
        float cz = Mathf.Cos(eulerInRadians.z);

        float sx = Mathf.Sin(eulerInRadians.x);
        float sy = Mathf.Sin(eulerInRadians.y);
        float sz = Mathf.Sin(eulerInRadians.z);

        float[,] lmx = new float[3,3];

        lmx[0, 0] = cz * cy + sz * sx * sy;
        lmx[1, 0] = sz * cx;
        lmx[2, 0] = -cz * sy + cy * sz * sx;
        lmx[0, 1] = -sz * cy + cz * sx * sy;
        lmx[1, 1] = cz * cx;
        lmx[2, 1] = sz * sy + cz * sx * cy;
        lmx[0, 2] = cx * sy;
        lmx[1, 2] = -sx;
        lmx[2, 2] = cx * cy;

        return lmx;
    }
2 Likes