Z rotates with X and Y (quaternion rotation)

Started by
5 comments, last by JoeJ 8 years, 2 months ago

I am trying to rotate an object in my game by using quaternions to avoid gimbal lock.

This is my rotate function:


void TransformComponent::Rotate(glm::vec3 axis, float angle)
{
    m_rotation *= glm::angleAxis(angle, axis);
}

It works perfectly fine when I only rotate around a single axis.
But somehow when I rotate around the x and y axis (to achieve an fps camera) the z axis rotates with the other two axes.

This is how I use the Rotate function:


m_pCamera->getTransform()->Rotate(glm::vec3(0,1,0), -g_pWindow->getMouseMotion().x * 0.1f * deltaTime);
m_pCamera->getTransform()->Rotate(glm::vec3(1,0,0), g_pWindow->getMouseMotion().y * 0.1f * deltaTime);

Does anybody know what causes this problem and how I could fix this strange behavior?

Thanks in advance,

TheKrane

Advertisement

Your first rotation causes the X axis not to be the same X axis that is was before rotation, meaning (1,0,0) after rotation and before rotation aren't the same, you should also rotate the X axis with the same rotation, also note that the afterwards Y rotation will have the same problem if you don't rotate the Y axis by the same rotation done around the X axis.

https://en.wikipedia.org/wiki/File:Euler2a.gif illustrate the way it should be done, which is Y X' and the consequent rotation should be Y" X"' and so on.

https://en.wikipedia.org/wiki/Euler_angles#Intrinsic_rotations

I'm not quite sure if I get what you're saying.

Wouldn't the new X axis just be the local x axis of my object?

Because using the local Up and Right vectors instead of vec3(0,1,0) and vec3(1,0,0) is not working either.

Can you explain what you mean further by using some sort of pseudo code?

Thanks in advance,

TheKrane

EDIT: Changing my Rotate function to


m_rotation *= glm::angleAxis(angle, glm::vec3(glm::vec4(axis,0)*glm::mat4_cast(m_rotation)));

did not do the trick.

I am trying to rotate an object in my game by using quaternions to avoid gimbal lock.

Gimbal lock is the loss of one degree of freedom. Using quaternions does not protect you from gimbal lock.

Wouldn't the new X axis just be the local x axis of my object?

See it this way: Whenever a transformation is done it is done w.r.t. the global space. Ignoring identity transforms, due to transformation a new local space is created.

Let's use 2 rotation as an example. A vector v is transformed:

v' := R2 * R1 * v = ( R2 * R1 ) * v = R2 * ( R1 * v )

Please notice that matrix multiplication is an associative operation, hence using parentheses as above is allowed. Although the 2nd form is what we use in practice due to efficiency reasons, the 3rd form is IMO best suited to think of what happens. It tells us that v is transformed by R1, and the rotated result is transformed by R2.

Now, if you want to use not the global but the local axes when applying R2, you need to first transform R1 * v into a space that is consistent with the global space. Then, after application of R2, rotate it back into the former local space. I.e. wrap R2 accordingly like so:

v'' := ( R1 * R2 * R1-1) * ( R1 * v )

Notice again that parentheses are pure cosmetic stuff here. They have no mathematical effect. Hence we can rewrite the above stuff as

= R1 * R2 * ( R1-1 * R1 ) * v

and find that the inner term is the identity matrix, hence
= R1 * R2 * v
So here we've shown that the desire to use the local axes for rotation is fulfilled by reversing the order of rotations! For incremental rotations perhaps the 1st form of computing v'' may be better suited.
I think there is another problem. Gimbal lock is expected here - if you look upwards in some FPS and move mouse sideways, camera spins but does not change direction.

The main mistake here seems mapping cursor position to a rotation. If mouse X has constant value but does not change, camera will keep rotatiing every frame.
What you want is to map mouse cursor to an orientation, not rotation, something like:


static vec2 oldMousePos(0,0);
static vec2 cameraAngles(0,0);

vec2 newMousePos (-g_pWindow->getMouseMotion().x, g_pWindow->getMouseMotion().y);
vec2 mouseDiff = newMousePos - oldMousePos; // mouse movement per frame
oldMousePos = newMousePos; 

cameraAngles += mouseDiff * mouseSensitivity; // no need to involve timestep

camera.m_rotation = Identity();
camera.m_rotation *= glm::angleAxis(cameraAngles[1], glm::vec3(1,0,0));
camera.m_rotation *= glm::angleAxis(cameraAngles[0], glm::vec3(0,1,0)); // do the most important axis (horizontal mouse movement) last

Thanks for the explanations guys.

I used JoeJ's approach and it's working now.

It still feels like an easy hack, but it's working. I hope I won't run into similar problems later on.

Thanks a lot!

It still feels like an easy hack


Most likely because you miss experience with 3D rotations. Maybe it helps to comment some of your statements:

"Z rotates with X and Y"
This is no problem - Z axis (and Y) must rotate if you rotate somthing around it's X axis - otherwise it's no rotation.
Same if you rotate around Y: Z & X rotate, Y stays the same.

I am trying to rotate an object in my game by using quaternions to avoid gimbal lock

I hear this often but it's nonsense. People tend to think Gimbal Lock is the bad guy each time they have a problem with rotations and Quats will fix it.
Matrices and Quaternions both can do any rotation in one step - there is no difference in regard to Gimbal Lock.
Euler angles may in deed cause unwanted Ginbal Lock problems, because the represent a series of multiple rotations in specified order.
But for human interaction they are still most comfortable, which is why we use them for editing animation curves or FPS camera.

So, by this example we used 2 Euler angles to get expected behaviour, but we still need math like quats or matrices to get the work done.

void TransformComponent::Rotate(glm::vec3 axis, float angle)
{
m_rotation *= glm::angleAxis(angle, axis);
}


There are 2 issues about this code:

First, you modify an orientation by rotation. Over time, floting point precission will cause degeneration of m_rotation.
Unlike position, orientation requires some constraints to be hold to stay a valid orientation:
A matrix needs its axis unit length and orthogonal to each other, a Quaternion must stay normalized.
So, you may need to orthonormalize or normalize m_rotation after such an operation.
(Or you avoid the issue by keeping only the euler angles like i did).

Second, and that may really help you in practice (in contrast to the other blah blah):
I suggest you make two functions for world OR local space:

void TransformComponent::RotateGlobal(glm::vec3 axis, float angle)
{
m_rotation *= glm::angleAxis(angle, axis);
}

void TransformComponent::RotateLocal(glm::vec3 axis, float angle)
{
m_rotation = glm::angleAxis(angle, axis) * m_rotation;
}

This way you always make clear if you rotate around the local object space or global world space.
(Note the only difference is multiplication order - i don't know glm's convention and it may be the other way around - maybe that caused some of your initial problems)

This topic is closed to new replies.

Advertisement