Quaternion-based camera still has gimbal lock.

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.

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’ = qPq^-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 …

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 (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.

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

[QUOTE=thokra;1245749]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’ = qPq^-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 …[/QUOTE]

I was following this tutorial which seemed to explain rotations in plain english (see the section on Euler to Quaternion). It actually works but still gives gimbal lock.

[QUOTE=Dark Photon;1245755]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.[/QUOTE]
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.

@thokra, the rotations actually occur at the top of the “update” function. I was following this tutorial 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.