Converting Quaternion to Euler Angle

Overview

Conceptually, there are many ways to represent rotation in 3D game programming, the most common being QuaternionEuler AngleMatrix and Axis Angle. A lot of resources can be found on the web on each representation.

However, there are not so many “concrete” implementation on how to convert from quaternion to euler angle. This article will focus solely on this matter. It will discuss on what you need to know and give a working example code in XNA.

Convention

One problem in Euler angle is there are possibly more than one way to represent a rotation. Euler angle can be defined as three numbers (ex, ey, ez) whereby each number represent a rotation relative to its axis.

Problem arises when we need to construct a rotation matrix based on the Euler angle. We can construct a rotation matrix of an axis, i.e.:

XNA RotationX Matrix XNA RotationY Matrix XNA RotationZ Matrix

Derivation

To create a single rotation matrix, we need to know the order of multiplication. In XNA, the order is Z-X-Y, so the rotation matrix will be:

image
image
image
image

Your engine may adopt a different convention; thus it will lead to a different result.

From, the result above, we can get ex:

image

also ey:

image

and finally ez:

image

Using .NET reflection, we can know how XNA derive matrix from quaternion Q=(qx,qy,qz,qw) and thus we have all the information we need:

image
image
image
image
image

and finally, we can get the euler angle ex, ey, ez:

image
image
image

Special Case

There is a problem when ex=90 or ex=-90degree. Since cos(ex) will be 0, so M12 = M22 = M31 = M33 = 0 and yields ey and ez undefined. To handle this, let see how XNA converts Euler Angle to Quaternion:

image

When ex=90:

image

it yields

image

If we choose ez=0, then we get ey = 2 * atan2(qy, qw)

Similarly, when ex=-90:

image

it yields

image

If we choose ez=0, then we get ey = 2 * atan2(qy, qw).

Implementation So Far

Based on the theory above, we can write a function to convert from Quaternion to Euler Angle. This method guarantees if you transform point P to P’ by using quaternion Q, you can also apply rotation using euler angle E to transform P to P’.

(NOTE: This method does not guarantee that EulerToQuaternion(E) = Q).

public static Vector3 QuaternionToEuler2(Quaternion q)
{
    Vector3 euler;

    float sqx = q.X * q.X;
    float sqy = q.Y * q.Y;
    float sqz = q.Z * q.Z;
    float sqw = q.W * q.W;

    float unit = sqx + sqy + sqz + sqw;
    float test = (q.X * q.W - q.Y * q.Z);

    // Handle singularity
    if (test > 0.4999999f * unit)
    {
        euler.X = MathHelper.PiOver2;
        euler.Y = 2.0f * (float)System.Math.Atan2(q.Y, q.W);
        euler.Z = 0;
    }
    else if (test < -0.4999999f * unit)
    {
        euler.X = -MathHelper.PiOver2;
        euler.Y = 2.0f * (float)System.Math.Atan2(q.Y, q.W);
        euler.Z = 0;
    }
    else
    {
        float ey_Y = 2 * (q.X * q.Z + q.Y * q.W);
        float ey_X = 1 - 2 * (sqy + sqx);
        float ez_Y = 2 * (q.X * q.Y + q.Z * q.W);
        float ez_X = 1 - 2 * (sqx + sqz);
        euler.X = (float)System.Math.Asin(2 * test);
        euler.Y = (float)System.Math.Atan2(ey_Y, ey_X);
        euler.Z = (float)System.Math.Atan2(ez_Y, ez_X);
    }

    // Convert to degrees
    euler.X = MathHelper.ToDegrees(euler.X);
    euler.Y = MathHelper.ToDegrees(euler.Y);
    euler.Z = MathHelper.ToDegrees(euler.Z);

    return euler;
}

Antipodal Quaternion

As I have mentioned above, the Euler angle E obtained by the method above does not guarantee that converting it back to quaternion will yield Q. Using the method above, it may yield its antipodal, i.e. -Q. It is important to note that Antipodal quaternions, Q and −Q, represent the same rotation.

Thus we need some way to ensure property EulerToQuaternionQuaternionToEuler(Q) ) = Q. Unfortunately, I don’t know a good way to do this. I found a workaround by trying to add/subtract 360 from the Euler angle. If you find a better way, I would be happy to update this posting.

public static Vector3 QuaternionToEuler(Quaternion q)
{
    Vector3 euler;

    float sqx = q.X * q.X;
    float sqy = q.Y * q.Y;
    float sqz = q.Z * q.Z;
    float sqw = q.W * q.W;

    float unit = sqx + sqy + sqz + sqw;
    float test = (q.X * q.W - q.Y * q.Z);

    // Handle singularity
    if (test > 0.4999999f * unit)
    {
        euler.X = MathHelper.PiOver2;
        euler.Y = 2.0f * (float)System.Math.Atan2(q.Y, q.W);
        euler.Z = 0;
    }
    else if (test < -0.4999999f * unit)
    {
        euler.X = -MathHelper.PiOver2;
        euler.Y = 2.0f * (float)System.Math.Atan2(q.Y, q.W);
        euler.Z = 0;
    }
    else
    {
        float ey_Y = 2 * (q.X * q.Z + q.Y * q.W);
        float ey_X = 1 - 2 * (sqy + sqx);
        float ez_Y = 2 * (q.X * q.Y + q.Z * q.W);
        float ez_X = 1 - 2 * (sqx + sqz);
        euler.X = (float)System.Math.Asin(2 * test);
        euler.Y = (float)System.Math.Atan2(ey_Y, ey_X);
        euler.Z = (float)System.Math.Atan2(ez_Y, ez_X);
    }

    Quaternion qNeg = -q;
    Quaternion qTest;
    Quaternion.CreateFromYawPitchRoll(euler.Y, euler.X, euler.Z, out qTest);
    if (ExMath.Equals(ref qNeg, ref qTest))
    {
        if (euler.X < 0)
            euler.X += TwoPi;
        else
            euler.X -= TwoPi;

        Quaternion.CreateFromYawPitchRoll(euler.Y, euler.X, euler.Z, out qTest);
        if (ExMath.Equals(ref qNeg, ref qTest))
        {
            if (euler.Y < 0)
                euler.Y += TwoPi;
            else
                euler.Y -= TwoPi;
        }
    }

    // Convert to degrees
    euler.X = MathHelper.ToDegrees(euler.X);
    euler.Y = MathHelper.ToDegrees(euler.Y);
    euler.Z = MathHelper.ToDegrees(euler.Z);

    return euler;
}

Final Note

As a final note, the code above uses ExMath.Equals(ref Quaternion a, ref Quaternion b) which compares quaternion components with some epsilon:

public static bool Equals(ref Quaternion a, ref Quaternion b)
{
    return ((System.Math.Abs(a.X - b.X) < ExMath.Epsilon) &&
              (System.Math.Abs(a.Y - b.Y) < ExMath.Epsilon) &&
              (System.Math.Abs(a.Z - b.Z) < ExMath.Epsilon) &&
              (System.Math.Abs(a.W - b.W) < ExMath.Epsilon));
}

Jan16

Leave a Reply