Quaternions - Local Rotation

Started by
4 comments, last by carpetfluff 15 years, 4 months ago
I've read every article and forum post on quaternions that I can find, but I'm still banging my head against a wall [depressed] I'm trying to write a quaternion based camera class which has six degrees of freedom. After going through a number of quaternion tutorials I've managed to implement a camera which can be rotated around global axes as follows:

#include "glCamera.h"

glCamera::glCamera()
{
	pDegrees = yDegrees = rDegrees = 0.0f;

	Direction.i = 0.0f;
	Direction.j = 0.0f;
	Direction.k = -1.0f;

	Up.i = 0.0f;
	Up.j = 1.0f;
	Up.k = 0.0f;

	Right.i = 1.0f;
	Right.j = 0.0f;
	Right.k = 0.0f;
}

void glCamera::Render()
{
	GLfloat m[16];
	glQuaternion q;

	qPitch.CreateFromAxisAngle(1.0f, 0.0f, 0.0f, pDegrees);
	qYaw.CreateFromAxisAngle(0.0f, 1.0f, 0.0f, yDegrees);
	qRoll.CreateFromAxisAngle(0.0f, 0.0f, 1.0f, rDegrees);

	q = qRoll * qPitch * qYaw;
	q.CreateMatrix(m);
	
	glMultMatrixf(m);

	q = q.Conjugate();
	q.CreateMatrix(m);

	Direction.i = -m[8];
	Direction.j = -m[9];
	Direction.k = -m[10];

	Up.i = m[4];
	Up.j = m[5];
	Up.k = m[6];

	Right.i = m[0];
	Right.j = m[1];
	Right.k = m[2];

	glTranslatef(-Position.x, -Position.y, -Position.z);
}

void glCamera::ChangePosition(GLfloat x, GLfloat y, GLfloat z)
{
	Position.x += (x * Right.i) + (y * Up.i) - (z * Direction.i);
	Position.y += (x * Right.j) + (y * Up.j) - (z * Direction.j);
	Position.z += (x * Right.k) + (y * Up.k) - (z * Direction.k);
}

void glCamera::ChangeOrientation(GLfloat pitch, GLfloat yaw, GLfloat roll)
{
	pDegrees += pitch;
	yDegrees += yaw;
	rDegrees += roll;

	if(pDegrees > 360.0f)
	{
		pDegrees -= 360.0f;
	}
	else if(pDegrees < -360.0f)
	{
		pDegrees += 360.0f;
	}

	if(yDegrees > 360.0f)
	{
		yDegrees -= 360.0f;
	}
	else if(yDegrees < -360.0f)
	{
		yDegrees += 360.0f;
	}

	if(rDegrees > 360.0f)
	{
		rDegrees -= 360.0f;
	}
	else if(rDegrees < -360.0f)
	{
		rDegrees += 360.0f;
	}
}

Ok, so the first thing I'm not clear on is why the correct Direction/Up/Right vectors have to be extracted from the matrix of the quaternion's conjugate, rather than the matrix of the original quaternion? The tutorials I've found haven't explained why this is the case. What do the vectors extracted from the original quaternion's matrix represent? Secondly, I want to be able to rotate the camera around its own local axes instead of the global axes. I've tried to code it like this:

#include "glCamera.h"

glCamera::glCamera()
{
	Direction.i = 0.0f;
	Direction.j = 0.0f;
	Direction.k = -1.0f;

	Up.i = 0.0f;
	Up.j = 1.0f;
	Up.k = 0.0f;

	Right.i = 1.0f;
	Right.j = 0.0f;
	Right.k = 0.0f;
}

void glCamera::Render()
{
	GLfloat m[16];
	glQuaternion q;

	qOrientation.CreateMatrix(m);
	
	glMultMatrixf(m);

	q = qOrientation.Conjugate();
	q.CreateMatrix(m);

	Direction.i = -m[8];
	Direction.j = -m[9];
	Direction.k = -m[10];

	Up.i = m[4];
	Up.j = m[5];
	Up.k = m[6];

	Right.i = m[0];
	Right.j = m[1];
	Right.k = m[2];

	glTranslatef(-Position.x, -Position.y, -Position.z);
}

void glCamera::ChangePosition(GLfloat x, GLfloat y, GLfloat z)
{
	Position.x += (x * Right.i) + (y * Up.i) - (z * Direction.i);
	Position.y += (x * Right.j) + (y * Up.j) - (z * Direction.j);
	Position.z += (x * Right.k) + (y * Up.k) - (z * Direction.k);
}

void glCamera::ChangePitch(GLfloat pitch)
{
	qPitch.CreateFromAxisAngle(Right.i, Right.j, Right.k, pitch);
	qOrientation = qOrientation * qPitch;
}

void glCamera::ChangeYaw(GLfloat yaw)
{
	qYaw.CreateFromAxisAngle(Up.i, Up.j, Up.k, yaw);
	qOrientation = qOrientation * qYaw;
}

void glCamera::ChangeRoll(GLfloat roll)
{
	qRoll.CreateFromAxisAngle(Direction.i, Direction.j, Direction.k, roll);
	qOrientation = qOrientation * qRoll;
}

This is giving me really weird results though. I think it's because the axes of reference are changing with every rotation, but I'm not sure how else to do it. I assume my original code which rotates around global axes works because the axes of reference never change between rotations. Changing the pitch works fine on it's own, so does changing the yaw. If I try to do one after the other then things mess up, and rolling doesn't seem to work properly at all. Hopefully someone can shed some light on this before I go insane [headshake] Thanks [smile]
Advertisement
Quote:
qPitch.CreateFromAxisAngle(Right.i, Right.j, Right.k, pitch);
qOrientation = qOrientation * qPitch;

That's not what you want. A rotation around the X axis is simply qPitch.CreateFromAxisAngle(1,0,0, pitch). Notice that I didn't say "local rotation" or "global rotation". Rotations, and by extension quaternions representing rotations, are not inherently relative to a given set of axes. qResult = qA * qB will produce a rotation equivalent to a rotation by qA followed by a rotation by qB. Switch the order of the operands if you want to change which one is relative to the other one.
I'm a little confused now. I was under the impression that after rotating the camera its Direction/Up/Right vectors would change, so I would need to perform new rotations around these new vectors?

I'm trying to rotate the camera relative to these vectors (rather than the global XYZ axes) as shown in this image from the Tait-Bryan rotations entry on Wikipedia:

Local Rotation

In this image X0, Y0, and Z0 are the global axes. Let X be Right, Y be Direction, and Z be Up.

1) We rotate around the Up vector initially, which happens to be aligned with the global Z axis:

qYaw.CreateFromAxisAngle(0.0f, 0.0f, 1.0f, yaw)

2) The Right and Direction vectors have been rotated, and are no longer aligned with the global axes. Rotate around the new right vector:

qPitch.CreateFromAxisAngle(Right.i, Right.j, Right.k, pitch)

3) Finally, we rotate around the local Up vector (which is no longer equivalent to the global Z axis):

qYaw.CreateFromAxisAngle(Up.i, Up.j, Up.k, yaw)

Am I just thinking about this in completely the wrong way?
Quote:Original post by carpetfluff
Am I just thinking about this in completely the wrong way?

Yeah, kind of.

Look, the result of qA * qB, qualitatively, consists of a rotation by qA, followed by a rotation by qB in the new axes. This is obvious if you think about it: One of the rotations has to be performed relative to the other one. There's no such thing as performing a rotation in an absolute space. Rotation performance is completely dependent on the space in which the rotation is performed.

So if qB is "a rotation of 45 degrees about the x axis", that's what it means: a rotation around the local x axis. Wherever your x axis is when that rotation comes down the pike, that's what you turn around.
Quote:Original post by carpetfluff
Ok, so the first thing I'm not clear on is why the correct Direction/Up/Right vectors have to be extracted from the matrix of the quaternion's conjugate, rather than the matrix of the original quaternion?


I'm no expert on quaternions but this sounds similar to taking the inverse of the Direction/Up/Right matrix. This inverted matrix is what some would call the view matrix.
Thanks for the replies, I'm getting there slowly...

I've figured how to move & rotate an object the way I want using quaternions, but I'm having trouble making use of the code for my camera.

The order of the quaternion multiplications has reversed now that I'm trying to move the world relative to the camera (instead of the other way around for objects).

#include "glCamera.h"glCamera::glCamera(){	Direction.i = 0.0f;	Direction.j = 0.0f;	Direction.k = -1.0f;	Up.i = 0.0f;	Up.j = 1.0f;	Up.k = 0.0f;	Right.i = 1.0f;	Right.j = 0.0f;	Right.k = 0.0f;}void glCamera::Render(){	GLfloat m[16];	glQuaternion q;	qOrientation.CreateMatrix(m);		glMultMatrixf(m);	q = qOrientation.Conjugate();	q.CreateMatrix(m);	Direction.i = -m[8];	Direction.j = -m[9];	Direction.k = -m[10];	Up.i = m[4];	Up.j = m[5];	Up.k = m[6];	Right.i = m[0];	Right.j = m[1];	Right.k = m[2];	glTranslatef(-Position.x, -Position.y, -Position.z);}void glCamera::ChangePosition(GLfloat x, GLfloat y, GLfloat z){	Position.x += (x * Right.i) + (y * Up.i) - (z * Direction.i);	Position.y += (x * Right.j) + (y * Up.j) - (z * Direction.j);	Position.z += (x * Right.k) + (y * Up.k) - (z * Direction.k);}void glCamera::ChangePitch(GLfloat pitch){	qPitch.CreateFromAxisAngle(1.0f, 0.0f, 0.0f, pitch);	qOrientation = qPitch * qOrientation;}void glCamera::ChangeYaw(GLfloat yaw){	qYaw.CreateFromAxisAngle(0.0f, 1.0f, 0.0f, yaw);	qOrientation = qYaw * qOrientation;}void glCamera::ChangeRoll(GLfloat roll){	qRoll.CreateFromAxisAngle(0.0f, 0.0f, 1.0f, roll);	qOrientation = qRoll * qOrientation;}


This seems to work fine when the camera is at the origin, but the world rotates strangely after changing position. I think I'm getting mixed up somewhere with manipulation of the ModelView matrix and extracting the correct vectors. When I was rotating an object, the vectors were taken from the quaternion's original matrix (rather than the conjugate matrix).

Can anyone see where I'm going wrong?

This topic is closed to new replies.

Advertisement