Game Development Stack Exchange is a question and answer site for professional and independent game developers. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I have a simple realisation of entity/component game engine.
Transform component have methods to set local position, local rotation, global position and global rotation.

If transform is being set new global position, then local position also changes, to update local position in such case I'm just applying current transform local matrix to parent's transform world matrix.

Until then I have no problems, I can get updated local transform matrix.
But I'm struggling on how to update local position and rotation value in transform. Only solution I have in mind is to extract translation & rotation values from localMatrix of transform.

For translation it's quite easy - I just take 4th column values. but what's about rotation?
How to extract euler angles from transformation matrix?

Is such solution right?:
To find rotation around Z axis, we can find difference between X axis vector of localTransform and X axis vector of parent.localTransform and store result in Delta, then: localRotation.z = atan2(Delta.y, Delta.x);

Same for rotation around X & Y, just need to swap axis.

share|improve this question
up vote 6 down vote accepted

Normally I store all objects as 4x4 Matrices (you could do 3x3 but easier for me just to have 1 class) instead of translating back and forth between a 4x4 and 3 sets of vector3s (Translation, Rotation, Scale). Euler angles are notoriously difficult to deal with in certain scenarios so I would recommend using Quaternions if you really want to store the components instead of a matrix.

But here is some code I found a while back that works. I hope this helps, unfortunately I do not have the original source for where I found this. I have no idea what odd scenarios it may not work in. I am currently using this to get the rotation of YawPitchRoll rotated, left handed 4x4 matrices.

   union {
        struct 
        {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;
        };
        float m[4][4];
        float m2[16];
    };

    inline void GetRotation(float& Yaw, float& Pitch, float& Roll) const
    {
        if (_11 == 1.0f)
        {
            Yaw = atan2f(_13, _34);
            Pitch = 0;
            Roll = 0;

        }else if (_11 == -1.0f)
        {
            Yaw = atan2f(_13, _34);
            Pitch = 0;
            Roll = 0;
        }else 
        {

            Yaw = atan2(-_31,_11);
            Pitch = asin(_21);
            Roll = atan2(-_23,_22);
        }
    }

Here is another thread I found while trying to answer your question that looked like a similar result to mine.

http://stackoverflow.com/questions/1996957/conversion-euler-to-matrix-and-matrix-to-euler

share|improve this answer
    
It seems that my proposed solution is almost right, just don't know why isntead of atan2 asin is used for pitch. – Denis Narushevich Mar 13 '13 at 16:57
    
Also, how it would help me, if I would store each component in separate mat4x4? How could I then get and e.g. output angle of rotation around some axis? – Denis Narushevich Mar 13 '13 at 16:57
    
Your original question lead me to believe that you are storing your objects as 3 vector3s: Translation, Rotation, and Scale. Then when you creating a localTransform out of those doing some work and later attempting to convert (localTransform*globalTransform) back into 3 vector3s. I could be totally wrong I was just getting that impression. – NtscCobalt Mar 13 '13 at 17:00
    
Yeah I don't know the math well enough for why pitch is done with ASIN but the linked question uses the same math so I believe it is correct. I've been using this function for a while to without any issue. – NtscCobalt Mar 13 '13 at 17:02

There's a great writeup on this process by Mike Day: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf

It is also now implemented in glm, as of version 0.9.7.0, 02/08/2015. Check out the implementation.

To understand the math, you should look at the values that are in your rotation matrix. In addition, you have to know the order in which the rotations were applied to create your matrix in order to properly extract the values.

A rotation matrix from Euler angles is formed by combining rotations around the x-, y-, and z-axes. For instance, rotating θ degrees around Z can be done with the matrix

     ┌ cosθ  -sinθ  0 ┐
Rz = │ sinθ   cosθ  0 │
     └   0      0   1 ┘

Similar matrices exist for rotating about the X and Y axes:

     ┌  1    0     0   ┐
Rx = │  0  cosθ  -sinθ │
     └  0  sinθ   cosθ ┘

     ┌  cosθ  0  sinθ ┐
Ry = │   0    1   0   │
     └ -sinθ  0  cosθ ┘

     ┌  1    0     0   ┐┌  cosθ  0  sinθ ┐
Rx = │  0  cosθ  -sinθ ││   0    1   0   │
     └  0  sinθ   cosθ ┘└ -sinθ  0  cosθ ┘

We can multiply these matrices together to create one matrix that is the result of all three rotations. It's important to note that the order that these matrices are multiplied together is important, because matrix multiplication is not commutative. This means that Rx*Ry*Rz ≠ Rz*Ry*Rx. Let's consider one possible rotation order, z-y-x. When the three matrices are combined, it results in a matrix that looks like this:

         ┌      CyCz              -CySz        Sy  ┐
RxRyRz = │  SxSySz + CxSz   -SxSySz + CxCz   -SxCy │
         └ -CxSyCz + SxSz    CxSySz + SxCz    CxCy ┘

where Cx is the cosine of the x angle of rotation, Sx is the sine of the x angle of rotation, etc.

Now, the challenge is to extract the original x, y, and z values that went into the matrix.

Let's first get the x angle out. If we know the sin(x) and cos(x), we can use the inverse tangent function atan2 to give us back our angle. Unfortunately, those values don't appear by themselves in our matrix. But, if we take a closer look at elements M[1][2] and M[2][2], we can see we do know -sin(x)*cos(y) as well as cos(x)*cos(y). Since the tangent function is the ratio of the opposite and adjacent sides of a triangle, scaling both values by the same amount (in this case cos(y)) will yield the same result. Thus,

x = atan2(-M[1][2], M[2][2])

Now let's try to get y. We know sin(y) from M[0][2]. If we had cos(y), we could use atan2 again, but we don't have that value in our matrix. However, due to the Pythagorean identity, we know that:

cosY = sqrt(1 - M[0][2])

So, we can calculate y:

y = atan2(M[0][2], cosY)

Last, we need to calculate z. This is where Mike Day's approach differs from the previous answer. Since at this point we know the amount of x and y rotation, we can construct an XY rotation matrix, and find the amount of z rotation necessary to match the target matrix. The RxRy matrix looks like this:

       ┌   Cy     0     Sy  ┐
RxRy = │  SxSy   Cx   -SxCy │
       └ -CxSy   Sx    CxCy ┘

Since we know that RxRy * Rz is equal to our input matrix M, we can use this matrix to get back to Rz:

M = RxRy * Rz

inverse(RxRy) * M = Rz

The inverse of a rotation matrix is its transpose, so we can expand this to:

┌ Cy   SxSy  -CxSy ┐┌M00  M01  M02┐   ┌ cosZ  -sinZ  0 ┐
│  0    Cx     Sx  ││M10  M11  M12│ = │ sinZ   cosZ  0 │
└ Sy  -SxCy   CxCy ┘└M20  M21  M22┘   └   0      0   1 ┘

We can now solve for sinZ and cosZ by performing the matrix multiplication. We only need to calculate the elements [1][0] and [1][1].

sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)

Here's a full implementation for reference:

#include <iostream>
#include <cmath>

class Vec4 {
public:
    Vec4(float x, float y, float z, float w) :
        x(x), y(y), z(z), w(w) {}

    float dot(const Vec4& other) const {
        return x * other.x +
            y * other.y +
            z * other.z +
            w * other.w;
    };

    float x, y, z, w;
};

class Mat4x4 {
public:
    Mat4x4() {}

    Mat4x4(float v00, float v01, float v02, float v03,
            float v10, float v11, float v12, float v13,
            float v20, float v21, float v22, float v23,
            float v30, float v31, float v32, float v33) {
        values[0] =  v00;
        values[1] =  v01;
        values[2] =  v02;
        values[3] =  v03;
        values[4] =  v10;
        values[5] =  v11;
        values[6] =  v12;
        values[7] =  v13;
        values[8] =  v20;
        values[9] =  v21;
        values[10] = v22;
        values[11] = v23;
        values[12] = v30;
        values[13] = v31;
        values[14] = v32;
        values[15] = v33;
    }

    Vec4 row(const int row) const {
        return Vec4(
            values[row*4],
            values[row*4+1],
            values[row*4+2],
            values[row*4+3]
        );
    }

    Vec4 column(const int column) const {
        return Vec4(
            values[column],
            values[column + 4],
            values[column + 8],
            values[column + 12]
        );
    }

    Mat4x4 multiply(const Mat4x4& other) const {
        Mat4x4 result;
        for (int row = 0; row < 4; ++row) {
            for (int column = 0; column < 4; ++column) {
                result.values[row*4+column] = this->row(row).dot(other.column(column));
            }
        }
        return result;
    }

    void extractEulerAngleXYZ(float& rotXangle, float& rotYangle, float& rotZangle) const {
        rotXangle = atan2(-row(1).z, row(2).z);
        float cosYangle = sqrt(pow(row(0).x, 2) + pow(row(0).y, 2));
        rotYangle = atan2(row(0).z, cosYangle);
        float sinXangle = sin(rotXangle);
        float cosXangle = cos(rotXangle);
        rotZangle = atan2(cosXangle * row(1).x + sinXangle * row(2).x, cosXangle * row(1).y + sinXangle * row(2).y);
    }

    float values[16];
};

float toRadians(float degrees) {
    return degrees * (M_PI / 180);
}

float toDegrees(float radians) {
    return radians * (180 / M_PI);
}

int main() {
    float rotXangle = toRadians(15);
    float rotYangle = toRadians(30);
    float rotZangle = toRadians(60);

    Mat4x4 rotX(
        1, 0,               0,              0,
        0, cos(rotXangle), -sin(rotXangle), 0,
        0, sin(rotXangle),  cos(rotXangle), 0,
        0, 0,               0,              1
    );
    Mat4x4 rotY(
         cos(rotYangle), 0, sin(rotYangle), 0,
         0,              1, 0,              0,
        -sin(rotYangle), 0, cos(rotYangle), 0,
        0,               0, 0,              1
    );
    Mat4x4 rotZ(
        cos(rotZangle), -sin(rotZangle), 0, 0,
        sin(rotZangle),  cos(rotZangle), 0, 0,
        0,               0,              1, 0,
        0,               0,              0, 1
    );

    Mat4x4 concatenatedRotationMatrix =
        rotX.multiply(rotY.multiply(rotZ));

    float extractedXangle = 0, extractedYangle = 0, extractedZangle = 0;
    concatenatedRotationMatrix.extractEulerAngleXYZ(
        extractedXangle, extractedYangle, extractedZangle
    );

    std::cout << toDegrees(extractedXangle) << ' ' <<
        toDegrees(extractedYangle) << ' ' <<
        toDegrees(extractedZangle) << std::endl;

    return 0;
}
share|improve this answer
    
Note, however, the problem when y = pi/2 and thus cos(y) == 0. Then it is NOT the case that M[1][3] and M[2][3] can be used to obtain x because the ratio is undefined, and not atan2 value can be obtained. I believe that this is equivalent to the gimbal lock problem. – Pieter Geerkens Dec 3 '15 at 23:18
    
@PieterGeerkens, you're right, that is gimbal lock. BTW, your comment revealed that I had a typo in that section. I refer to the matrix indices with the first one at 0, and since they are 3x3 matrices, the last index is 2, not 3. I've corrected M[1][3] with M[1][2], and M[2][3] with M[2][2]. – Chris Dec 4 '15 at 0:17

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.