13
votes

I'm writing a program that loads a file containing a scene description and then displays it using OpenGL. I'm using GLM for all of my math operations. Rotations in the scene file are stored in quaternion format. My scene management systems takes rotations for objects in the form of euler angles, and these angles are later converted to a rotation matrix when drawing.

My loading process thus takes the quaternion rotations, converts them to euler angles for storage in my object class, then converts these euler angles to rotation matrices for drawing. I'm using the glm::eulerAngles and glm::eulerAngleYXZ functions (respectively) to perform these two operations.

However, I am getting incorrect results. For example, if I understand correctly the quaternion {0.500 -0.500 0.500 0.500} (that's W X Y Z) should describe the rotation taking an arrow from the +Z axis to the +Y axis. When I run the program, however, I get the arrow pointing along the +X axis.

I would assume that there is some flaw in my understanding of the quaternions, but I am able to get my expected results by skipping the intermediary euler angle form. By converting the quaternion directly to a rotation matrix using glm::toMat4, I get a rotation that points my +Z arrow towards +Y.

I'm having trouble reconciling these two different outputs, considering that both methods seem both simple and correct. To simplify my question, why is it that these two seemingly equivalent methods produce different results:

glm::quat q(.5, -.5, .5, .5);
glm::vec3 euler = glm::eulerAngles(q) * 3.14159f / 180.f; // eulerAngleYXZ takes radians but eulerAngles returns degrees
glm::mat4 transform1 = glm::eulerAngleYXZ(euler.y, euler.x, euler.z);
// transform1 rotates a +Z arrow so that it points at +X

glm::quat q(.5, -.5, .5, .5);
glm::mat4 transform2 = glm::toMat4(q);
// transform2 rotates a +Z arrow so that it points at +Y
3
In case you want your own function for experimenting and know whats behind the scenes I gave an answer here.qrtLs
Aside: at least as of 0.9.9 glm::eulerAngles(q) returns its result in radians. See glm/gtc/quaternion.hpp.wardw

3 Answers

7
votes

You have probably figured this out by now... but

What eulerAngle sequence does the function:

glm::vec3 euler = glm::eulerAngles(q) * 3.14159f / 180.f;

return? If it does not return explicitly an 'YXZ' sequence, you will not be able to use the next function properly:

glm::mat4 transform1 = glm::eulerAngleYXZ(euler.y, euler.x, euler.z);

Your variable 'euler' must be the same sequence type as the function you specify to transform it into a rotation matrix.

After looking here it looks like the function 'glm::eulerAngles' returns 'XYZ' as pitch, yaw, and roll. Thus, assuming they are 'YXZ', or yaw, pitch, roll is incorrect.

As said before, with Euler angles and rotation matrices, order matters!

4
votes

The order of multiplication is important when dealing with Euler angles. YXZ and XYZ produce very different rotations.

You could calculate separate matrices for each axis, and then multiply them together in the order your need.

glm::quat q(.5, -.5, .5, .5);
glm::vec3 euler = glm::eulerAngles(q) * 3.14159f / 180.f;

glm::mat4 transformX = glm::eulerAngleX(euler.x);
glm::mat4 transformY = glm::eulerAngleY(euler.y);
glm::mat4 transformZ = glm::eulerAngleZ(euler.z);

glm::mat4 transform1 =
    transformX * transformY * transformZ; // or some other order
1
votes

I think the result is radian already, no need to convert.

glm::quat q(.5, -.5, .5, .5);
glm::vec3 euler = glm::eulerAngles(q); // * 3.14159f / 180.f;

glm::mat4 transformX = glm::eulerAngleX(euler.x);
glm::mat4 transformY = glm::eulerAngleY(euler.y);
glm::mat4 transformZ = glm::eulerAngleZ(euler.z);

glm::mat4 transform1 =
    transformX * transformY * transformZ; // or some other order