PDA

View Full Version : Quaternion-based camera still has gimbal lock.



chex_5
12-14-2012, 12:13 AM
Hello, I'm not quite sure if this is question has already been asked before but I couldn't find anything after searching Google for hours.

Basically, I'm trying to move my camera class away from vector rotations, due to the inevitable gimbal lock issue, and towards quaternion-based orientations. So far though, I've tried several different implementations using quaternions (even switched to matrices with no luck) but no matter what I try, I keep getting the same results that I had with rotating vectors. Can someone look over my code and help me figure this out? I feel as though the gimbal lock is rooted somewhere in between my euler angle conversion and the fact that I base my rotations on world axes.



//-----------------------------------------------------------------------------
// Camera Object - Movement & Orientation
//-----------------------------------------------------------------------------

void camera::look( const glm::vec3& inTarget ) {
glm::vec3 diffTarget ( pos - inTarget );
glm::quat rotations ( 0.f, diffTarget );
glm::vec3 angles ( glm::angle( rotations ) );
pitch = angles.x;
yaw = angles.y;
roll = angles.z;
}

void camera::move( float dx, float dy, float dz ) {
pos.x += dx;
pos.y += dy;
pos.z += dz;
}

void camera::rotate( float dPitch, float dYaw, float dRoll ) {
pitch = dPitch;
yaw = dYaw;
roll = dRoll;
}

//-----------------------------------------------------------------------------
// Camera - Updating
//-----------------------------------------------------------------------------
void camera::update() {
//update the rotations
glm::quat rotateX( std::cos( pitch ), std::sin( pitch ), 0.f, 0.f );
glm::quat rotateY( std::cos( yaw ), 0.f, std::sin( yaw ), 0.f );
glm::quat rotateZ( std::cos( roll ), 0.f, 0.f, std::sin( roll ) );

glm::quat endRot = rotateZ * rotateY * rotateX;

switch( camType ) {
default:
case CAM_TYPE_FPS:
target.x = endRot.x;
target.y = endRot.y;
target.z = endRot.z;
break;

case CAM_TYPE_SPECTATOR:
eye = endRot;
break;
}

mvp
= projMat
* glm::lookAt( pos, target, up )
* glm::mat4_cast( eye );
}

I also tried removing the yaw, pitch, and roll variables from my camera class since that seemed more natural, although my brain just isn't really wired for quaternions.

thokra
12-14-2012, 01:51 AM
Mathematically, gimbal lock using quaternions is impossible. If you still get similar problems, you're doing it wrong.

EDIT: BTW, where do you actually rotate? You rotate in R^3 using a quaternion using: P' = q*P*q^-1



glm::quat rotateX( std::cos( pitch ), std::sin( pitch ), 0.f, 0.f );
glm::quat rotateY( std::cos( yaw ), 0.f, std::sin( yaw ), 0.f );
glm::quat rotateZ( std::cos( roll ), 0.f, 0.f, std::sin( roll ) );
glm::quat endRot = rotateZ * rotateY * rotateX;


where q == endRot. I can't see any rotations in your code.



case CAM_TYPE_FPS:
target.x = endRot.x;
target.y = endRot.y;
target.z = endRot.z;
break;

case CAM_TYPE_SPECTATOR:
eye = endRot;


This is not a rotation. This is complete nonsense. You set the components of the concatenated quaternion as the focal point passed to glmLookAt. Think about this for a minute ...

Dark Photon
12-14-2012, 05:35 AM
Mathematically, gimbal lock using quaternions is impossible

This is actually common misconception. Quaternions in-and-of-themselves don't solve gimbal lock. You can produce gimbal lock with quaternions (see below). However, if you use them right, you can get rid of gimbal lock.

Gimbal lock arises from representating a rotation transform as multiple component rotations about different axes (e.g. heading, pitch, roll; or rotate about X, then Y, then Z; etc.) -- aka Euler angles. This scenario allows you to rotate one axis onto another, resulting in a loss of a degree of freedom and the dreaded gimble lock.

What you need to do is represent your 3D rotations with "1" and only one quaternion. Not "3". Then use that for interpolation and compositing.

For more info, see Quaternions and their Applications to Rotation in 3D Space (http://www.morris.umn.edu/academic/math/Ma4901/gravelle.pdf) (see the "Gimbal Lock with Quaternions" section).

Then once you get real comfortable with quaternions and need to interpolate rotation and translation together, check out Dual Quaternions.

thokra
12-14-2012, 05:52 AM
This is actually common misconception. Quaternions in-and-of-themselves don't solve gimble lock. If you use them right, you can get rid of gimble lock however.

Yes, you're absolutely correct. I should have been more precise.

chex_5
12-14-2012, 12:33 PM
Mathematically, gimbal lock using quaternions is impossible. If you still get similar problems, you're doing it wrong.

EDIT: BTW, where do you actually rotate? You rotate in R^3 using a quaternion using: P' = q*P*q^-1



glm::quat rotateX( std::cos( pitch ), std::sin( pitch ), 0.f, 0.f );
glm::quat rotateY( std::cos( yaw ), 0.f, std::sin( yaw ), 0.f );
glm::quat rotateZ( std::cos( roll ), 0.f, 0.f, std::sin( roll ) );
glm::quat endRot = rotateZ * rotateY * rotateX;


where q == endRot. I can't see any rotations in your code.



case CAM_TYPE_FPS:
target.x = endRot.x;
target.y = endRot.y;
target.z = endRot.z;
break;

case CAM_TYPE_SPECTATOR:
eye = endRot;


This is not a rotation. This is complete nonsense. You set the components of the concatenated quaternion as the focal point passed to glmLookAt. Think about this for a minute ...

I was following this tutorial (http://www.gamedev.net/page/resources/_/technical/math-and-physics/quaternion-powers-r1095) which seemed to explain rotations in plain english (see the section on Euler to Quaternion). It actually works but still gives gimbal lock.


Gimbal lock arises from representating a rotation transform as multiple component rotations about different axes (e.g. heading, pitch, roll; or rotate about X, then Y, then Z; etc.) -- aka Euler angles. This scenario allows you to rotate one axis onto another, resulting in a loss of a degree of freedom and the dreaded gimble lock.

What you need to do is represent your 3D rotations with "1" and only one quaternion. Not "3". Then use that for interpolation and compositing.
This part really confuses me. If I am rotating using only one quaternion, then how do I manage a rotation around all 3 axes? I know I can have if "point" in a single direction, but I have to rotate it to get there first.

chex_5
12-15-2012, 12:15 AM
@thokra, the rotations actually occur at the top of the "update" function. I was following this tutorial (http://www.gamedev.net/page/resources/_/technical/math-and-physics/quaternion-powers-r1095) that I found while searching for help. I'm almost positive that the problem is caused by my use of yaw, pitch, and roll, but if I want to rotate my camera, how could I possibly do it without those angles?
@Dark Photon, thanks for the article. I read through it but I'm still trying to comprehend everything.