[SOLVED - Thanks jyk :)]Camera Class - Yaw and Pitch

Started by
5 comments, last by stenny 15 years, 1 month ago
Hey GameDev! I'm having some trouble figuring out how yawing and pitching my camera in 3d works and I was wondering if anyone could help me out. Over the last two, three days I've been trying out all sorts of matrix multiplications and stuff, but I can't seem to find the right order of multiplification. Forgive me for posting these elaborate pieces of code, but I thought it to be better than having to explain this to you without examples. This is what I currently have: A Frame class. Represents an orientation and location in 3d space:

class Frame
{
public:
	Vector3f	vLocation;
	Vector3f	vForward;
	Vector3f	vUp;

	Frame();

	Matrix44f	GetMatrix();
	void	Move(const float fX, const float fY, const float fZ);
	void	MoveX(const float fDelta);
	void	MoveY(const float fDelta);
	void	MoveZ(const float fDelta);

	void	Rotate(float fYaw, float fPitch, float fRoll);
	void	Yaw(float fAngle);
	void	Pitch(float fAngle);
	void	Roll(float fAngle);
};




A Camera class. Derived from Frame. First I used Frame for the camera orientation, but I got to the conclusion I had to store the yaw, pitch and roll in seperate variables, and then rebuild that camera orientation each frame. Could anyone tell me if this is correct?

class Camera : public Frame
{
public:
	float	fYaw, fPitch, fRoll;

	Camera();

	void	Yaw(float fAngle);
	void	Pitch(float fAngle);
	void	Roll(float fAngle);

	void	Compute();	// Computes the orientation
};




Then, when the mouse moves horizontal or vertical, I give calls to respectively Camera::Yaw() and Camera::Pitch(), functions that both add fAngle to the corresponding member. Nothing more. When the rendering starts, I give a call to Camera::Compute():

void Camera::Compute()
{
	// Reset the orientation
	vForward.x = 0.0f;
	vForward.y = 0.0f;
	vForward.z = -1.0f;

	vUp.x = 0.0f;
	vUp.y = 1.0f;
	vUp.z = 0.0f;

	// AND THIS IS WHERE I THINK THINGS GO WRONG.
	Frame::Yaw(fYaw);
	Frame::Pitch(fPitch);
}
// More functions down there \/

void Frame::Yaw(float fAngle)
{
	// Switch to radians
	fAngle /= DEG_TO_RAD;

	// Calculate the rotation matrix
	Matrix44f m = MatrixRotation(fAngle, vUp.x, vUp.y, vUp.z);

	// Rotate the Forward vector
	vForward.x = m(0, 0) * vForward.x + m(1, 0) * vForward.y + m(2, 0) * vForward.z;
	vForward.y = m(0, 1) * vForward.x + m(1, 1) * vForward.y + m(2, 1) * vForward.z;
	vForward.z = m(0, 2) * vForward.x + m(1, 2) * vForward.y + m(2, 2) * vForward.z;
	vForward.Normalize();
}

void Frame::Pitch(float fAngle)
{
	// Calculate the right vector
	Vector3f vRight = CrossProduct(vUp, vForward);

	// Convert the angle to radians
	fAngle /= DEG_TO_RAD;

	Matrix44f m = MatrixRotation(fAngle, vRight.x, vRight.y, vRight.z);

	vForward.x = m.f[0] * vForward.x + m.f[4] * vForward.y + m.f[8] * vForward.z;
	vForward.y = m.f[1] * vForward.x + m.f[5] * vForward.y + m.f[9] * vForward.z;
	vForward.z = m.f[2] * vForward.x + m.f[6] * vForward.y + m.f[10] * vForward.z;
	vForward.Normalize();

	vUp.x = m.f[0] * vUp.x + m.f[4] * vUp.y + m.f[8] * vUp.z;
	vUp.y = m.f[1] * vUp.x + m.f[5] * vUp.y + m.f[9] * vUp.z;
	vUp.z = m.f[2] * vUp.x + m.f[6] * vUp.y + m.f[10] * vUp.z;
	vUp.Normalize();
}

inline Matrix44f MatrixRotation(float fAngle, float x, float y, float z)
{
	// Calculate the sinus and cosinus of the angle
	float s = sin(fAngle);
	float c = cos(fAngle);
	float onemc = 1 - c;

	// Create the matrix
	Matrix44f n;

	// Fill in the data - GOT THIS CODE FROM THE INTERNET, CAN'T REMEMBER SOURCE
	n.f[0]  = x*x + (1 - x*x) * c;
	n.f[1]  = x*y * onemc + z*s;
	n.f[2]  = x*z * onemc - y*s;
	n.f[3]  = 0.0f;

	n.f[4]  = y*x * onemc - z*s;
	n.f[5]  = y*y + (1 - y*y) * c;
	n.f[6]  = y*z * onemc + x*s;
	n.f[7]  = 0.0f;

	n.f[8]  = z*x * onemc + y*s;
	n.f[9]  = z*y * onemc - x*s;
	n.f[10] = z*z + (1 - z*z) * c;
	n.f[11] = 0.0f;

	n.f[12] = 0.0f;
	n.f[13] = 0.0f;
	n.f[14] = 0.0f;
	n.f[15] = 1.0f;

	// Return the matrix
	return n;
}




Frame::Yaw() and Frame::Pitch() are both computed in local space, but since the forward and up vector are set back to initial values, Yaw is calculated as if global. Then, after we've turned horizontally, we can turn vertically from that position; pitching. At least, that's my train of thought, but apparently I'm doing something wrong. Then, at last, this function is called with the camera class as argument:

void GraphicsUnit::ApplyFrame(Frame& Frame)
{
	gluLookAt(Frame.vLocation.x, Frame.vLocation.y, Frame.vLocation.z,
			  Frame.vLocation.x + Frame.vForward.x, Frame.vLocation.y + Frame.vForward.y, Frame.vLocation.z + Frame.vForward.z,
			  Frame.vUp.x, Frame.vUp.y, Frame.vUp.z);
}




Oh, and of course, the actual problem is that, yawing and pitching seperately work fine, but when I combine these two, the orientation starts doing weird things, as if I'm also rolling or something like that. I'm sure this kind of question has been asked before, but I can't seem to get it work with the information I could find on the internet. Thanks for helping out, - Stijn Frishert [Edited by - stenny on February 28, 2009 5:36:46 AM]
What do I expect? A young man's quest to defeat an evil sorceror while discovering the truth of his origins. A plucky youngster attended by her brutish guardian. A powerful artifact which has been broken into a small number of artifactlets distributed around the world.What do I want? Fewer damn cliches. - Sneftel
Advertisement
Look in to using quaternions instead of simple rotations to avoid gimbel lock.

Stephen M. Webb
Professional Free Software Developer

Quote:Original post by Bregma
Look in to using quaternions instead of simple rotations to avoid gimbel lock.
Gimbal lock isn't the problem here, at least not as the OP described it. (It's also worth noting that using quaternions does not solve the problem of gimbal lock.)

@The OP: First, a few points to take into consideration:

1. The first thing you should do is get the basic math code refactored and into its own logical functions. You don't want to be including code to rotate a vector by a matrix directly in the functions that need to perform this operation. Instead, make an appropriate overloaded operator or named function and use that instead. This way, you won't have the vector rotation code cluttering up your functions, and you'll only have to write and debug it once, rather than multiple times.

2. It looks like your vector rotation code is incorrect. You overwrite the old values of the vector elements with the new values, and then use the new values rather than the old values when computing the new values for the subsequent elements. Does that make sense? (It's a little hard to explain succinctly.) Matrix multiplication (which is what this is) is a non-linear operation, so you need to use a temporary vector or matrix to store the results, and then perform the assignment. Making a function for this operation will help in this regard.

3. I'm not sure, but you might also be indexing the matrix wrong in the matrix-vector mult code (it seems to me like it should be 0,0 - 0,1 - 0,2 across the top row, rather than 0,0 - 1,0 - 2,0). Can you post the code for your () operator?

4. In this case, there's really no need to have a separate class for the camera. The camera is just a coordinate frame, and should have the same functionality, more or less. I would just use the frame class directly (or a more generic derived class, e.g. MovableObject), and then rename the ApplyFrame() function to something more appropriate, such as SetViewMatrix(). Also, I assume you're clearing the OpenGL modelview matrix before calling ApplyFrame()?

I'd start by checking (and fixing, if necessary) the above items, and see if that fixes the problem. If it still doesn't work, post your revised code and I'll take another look at it.
Thanks, jyk! I'll go through that list and report back :)

@ 1) So, I'll be putting matrix-vector multiplication in things like operator*(const Vector3f& v)?

@ 4) Yes, I'm clearing the modelview matrix before applying transformations each frame. The Camera class is derived from Frame though.

[EDIT]
The operator():

inline float operator()(unsigned int i, unsigned j){	if(i >= 4 || j >= 4) { return 0.0f; }	return f[i*4+j];}


I store the floats by columns, as OpenGL does. And now you mention it, I think I'm using the wrong values, yeah.

[Edited by - stenny on February 28, 2009 5:10:04 AM]
What do I expect? A young man's quest to defeat an evil sorceror while discovering the truth of his origins. A plucky youngster attended by her brutish guardian. A powerful artifact which has been broken into a small number of artifactlets distributed around the world.What do I want? Fewer damn cliches. - Sneftel
Yay! That solved it, jyk. Thanks man! I ditched the operator() and accessed the matrix entries directly. Also, the matrix * vector was probably the problem. I now calculate the...stuff with a temporary vector, and then copy it into the final vector.

Thanks again man, it worked!

[EDIT] For people with the same problem, this is my new Yaw function:

void Frame::Yaw(float fAngle){	// Switch to radians	fAngle /= DEG_TO_RAD;	// Calculate the rotation matrix	Matrix44f m = MatrixRotation(fAngle, vUp.x, vUp.y, vUp.z);	// Rotate the vectors	vForward = vForward * m;	vForward.Normalize();}


and the multiplication operator. It's important you do the calculations on a temporary vector:

inline Vector3f operator*(const Vector3f& v, const Matrix44f& m){	Vector3f u;	u.x = v.x * m.f[0] + v.y * m.f[4] + v.z * m.f[8];	u.y = v.x * m.f[1] + v.y * m.f[5] + v.z * m.f[9];	u.z = v.x * m.f[2] + v.y * m.f[6] + v.z * m.f[10];	return u;}


- Stijn
What do I expect? A young man's quest to defeat an evil sorceror while discovering the truth of his origins. A plucky youngster attended by her brutish guardian. A powerful artifact which has been broken into a small number of artifactlets distributed around the world.What do I want? Fewer damn cliches. - Sneftel
Great - 'glad it's working!

I have a couple of suggestions:

First, re-instate your () operator; that's really a better way to do it. Using a 2-d indexing operator hides the details of the matrix storage behind a layer of abstraction, which is a good thing, as it makes your code more resilient to change, and makes it more easily understandable from a mathematical perspective. (Looking at the version you posted, I think you'll want f[i+4*j] rather than f[i*4+j], since you're using column-major storage.)

Second, rather than returning a value of 0 if one of the input indices is out of range, it would be better to assert or throw an exception (most would probably opt for the former in the context of a matrix class). You don't want the index operator failing silently in the case of bad input - it's better to know about it when it happens so that you can track down the problem and fix it.
Yeah, that was still the old function and a quick fix. I'll change it.

Thanks again, man! ^_^
What do I expect? A young man's quest to defeat an evil sorceror while discovering the truth of his origins. A plucky youngster attended by her brutish guardian. A powerful artifact which has been broken into a small number of artifactlets distributed around the world.What do I want? Fewer damn cliches. - Sneftel

This topic is closed to new replies.

Advertisement