Lesson: Quaternion Camera Class
Hi! My name if Vic Hollis and I've been coming to NeHe for a couple of years now. And I think its time I finally gave something back. Lately I have been studying Quaternions for doing rotations and to be entirely honest I still don't understand them quite as well as I should. Since I have started using them I can tell you that using Quaternions for 3D rotations and finding positions in your scene can really make things easier. Of course you don't have to use Quaternions for this sort of thing you can always deal with matrices and vector math. And by using Quaternions you will still have to deal with them somewhat. In this Lesson I'm going to create a simple class to represent a Quaternion and a Camera. Anyone who is interested in these classes is welcome to use them in your source. I will not be discussing the math behind the Quaternion so I am going to avoid that subject all together. There is no shortage of information out there about the mathematics behind Quaternions. What I am mostly interested in as a developer (and I bet most everyone else reading this) is getting results. In this lesson I will create a height map that we can use for some terrain so we will have something to fly around and I will make a Camera class that uses Quaternions so we can set our perspective on things and conviently get World coordinates to translate to based on orientation and speed. The model that I will be using for flying is a Wing Commander style of flying that you find in most space sims. I'll leave it to you guys to find other ways to fly around your scene but after this lesson that shouldn't be to much of a problem for you with a little bit of work.

A Quaternion is really an odd thing to think about. After months of trying to visually understand them I just gave up and accepted them as something that just was. That leap of faith actually help me understand them all the better believe it or not and I would recommend anyone struggling with them to do the same. I'm sure some of you have heard of the effects of gimbal lock. Well its something that happens when your start multiplying lots of rotations together. Quaternions give us a way around this and thats why they are so useful. Well I think I have rambled enough below is the function we will be using the most from the Quaternion class.
void glQuaternion::CreateFromAxisAngle(GLfloat x, GLfloat y, GLfloat z, GLfloat degrees)
{
	// First we want to convert the degrees to radians
	// since the angle is assumed to be in radians
	GLfloat angle = GLfloat((degrees / 180.0f) * PI);

	// Here we calculate the sin( theta / 2) once for optimization
	GLfloat result = (GLfloat)sin( angle / 2.0f );

	// Calcualte the w value by cos( theta / 2 )
	m_w = (GLfloat)cos( angle / 2.0f );

	// Calculate the x, y and z of the quaternion
	m_x = GLfloat(x * result);
	m_y = GLfloat(y * result);
	m_z = GLfloat(z * result);
}
You can think of this function just like you would a call to glRotatef(). All the parameters are the same with the exception of the order. Also you will see later in the lesson that you can use them interchangable in OpenGL. As you can see from the above the only member varibles we need to represent a Quaternion are an x, y, z, and w coordinates. The x, y, and z coordinates are the axis of the rotation and the degrees is the number of degrees you want to rotate around that axis. Below is the second most usefull function in the class CreateMatrix()
void glQuaternion::CreateMatrix(GLfloat *pMatrix)
{
	// Make sure the matrix has allocated memory to store the rotation data
	if(!pMatrix) return;
	
	// First row
	pMatrix[ 0] = 1.0f - 2.0f * ( m_y * m_y + m_z * m_z );
	pMatrix[ 1] = 2.0f * (m_x * m_y + m_z * m_w);
	pMatrix[ 2] = 2.0f * (m_x * m_z - m_y * m_w);
	pMatrix[ 3] = 0.0f;
	
	// Second row
	pMatrix[ 4] = 2.0f * ( m_x * m_y - m_z * m_w );
	pMatrix[ 5] = 1.0f - 2.0f * ( m_x * m_x + m_z * m_z );
	pMatrix[ 6] = 2.0f * (m_z * m_y + m_x * m_w );
	pMatrix[ 7] = 0.0f;

	// Third row
	pMatrix[ 8] = 2.0f * ( m_x * m_z + m_y * m_w );
	pMatrix[ 9] = 2.0f * ( m_y * m_z - m_x * m_w );
	pMatrix[10] = 1.0f - 2.0f * ( m_x * m_x + m_y * m_y );
	pMatrix[11] = 0.0f;

	// Fourth row
	pMatrix[12] = 0;
	pMatrix[13] = 0;
	pMatrix[14] = 0;
	pMatrix[15] = 1.0f;

	// Now pMatrix[] is a 4x4 homogeneous matrix that can be applied to an OpenGL Matrix
}
This function, as its name implies, Creates a 4x4 homogeneous matrix that can be sent to glMultMatrixf(). That means that you will never have to use glRotatef() directly when using this class to represent your rotations. You must first call the CreateFromAxisAngle() if you want this function to give you a valid matrix to use with glMultMatrixf(). The next function is an operator and that is the multiplication operator. This class is not going to do us much good unless we can combine rotations. In order to do that we need the ability to multiply Quaternions.
glQuaternion glQuaternion::operator *(glQuaternion q)
{
	glQuaternion r;

	r.m_w = m_w*q.m_w - m_x*q.m_x - m_y*q.m_y - m_z*q.m_z;
	r.m_x = m_w*q.m_x + m_x*q.m_w + m_y*q.m_z - m_z*q.m_y;
	r.m_y = m_w*q.m_y + m_y*q.m_w + m_z*q.m_x - m_x*q.m_z;
	r.m_z = m_w*q.m_z + m_z*q.m_w + m_x*q.m_y - m_y*q.m_x;

	return(r);
}
That is all there is too multiplying Quaternions. One thing to note about multiplication with Quaternions is the results are not communitive. Which basically means that Quaternion a * Quaternion b does not equal Quaternion b * Quaternion a. The same applies with matrices as well. This will actually come into play later in the tutorial when we find our World coordinates to translate to. Thats all we need for the Quaternion class. Next up is the very basic Camera class.
void glCamera::SetPrespective()
{
	GLfloat Matrix[16];
	glQuaternion q;

	// Make the Quaternions that will represent our rotations
	m_qPitch.CreateFromAxisAngle(1.0f, 0.0f, 0.0f, m_PitchDegrees);
	m_qHeading.CreateFromAxisAngle(0.0f, 1.0f, 0.0f, m_HeadingDegrees);

	// Combine the pitch and heading rotations and store the results in q
	q = m_qPitch * m_qHeading;
	q.CreateMatrix(Matrix);

	// Let OpenGL set our new prespective on the world!
	glMultMatrixf(Matrix);

	// Create a matrix from the pitch Quaternion and get the j vector
	// for our direction.
	m_qPitch.CreateMatrix(Matrix);
	m_DirectionVector.j = Matrix[9];

	// Combine the heading and pitch rotations and make a matrix to get
	// the i and j vectors for our direction.
	q = m_qHeading * m_qPitch;
	q.CreateMatrix(Matrix);
	m_DirectionVector.i = Matrix[8];
	m_DirectionVector.k = Matrix[10];

	// Scale the direction by our speed.
	m_DirectionVector *= m_ForwardVelocity;

	// Increment our position by the vector
	m_Position.x += m_DirectionVector.i;
	m_Position.y += m_DirectionVector.j;
	m_Position.z += m_DirectionVector.k;

	// Translate to our new position.
	glTranslatef(-m_Position.x, -m_Position.y, m_Position.z);
}
Ok this part of the code needs a bit of explaining. First off we need to declare an array of floats because OpenGL represents matrices as a 1 dimensional array. We also need a temporary Quaternion 'q' to store the results of our Quaternion multiplication. What we are doing here is defining 2 Quaternions to represent rotations about the X and Y axises. Then we multiply these two Quaternions together to get our Orientation in the scene (IE which direction we are facing). The next thing we need is a direction vector based on our Orientation so that we can translate to a position in the scene. It just so happens that the matrix we created based on the m_Pitch contains a value that we can use for the 'j' vector coordinate of our direction vector. An interesting thing to note here is that the 3rd row of our matrix (elements 8, 9, 10) will alwasy contain translation coordinates! So we don't have to use computations to re-figure our direction vector. Remember when I said that multiplication is not communative with Quaternions? Well here we are using that to our advantage to get our 'i' and 'k' coordinates for our vector. We multiply the m_Heading * m_Pitch which will give us a different Matrix. The 'i' and 'k' coordinates are stored in this matrix and since we are using unit length Quaternions to represent our rotations then the values in the 3rd row of the Matrix can be used as unit length vectors. Now that we have a vector we can scale that vector by our speed and add that back to the position. Now all thats left is to translate to that position. Now bear in mind that this function models the Wing Commander style of flying around. It will not work for a Microsoft Flight Sim flying style. Next up is the InitGL() function.
	// Try to load our height map
	if(!hMap.LoadRawFile("Art/Terrain1.raw", MAP_SIZE * MAP_SIZE)) 
	{
		MessageBox(NULL, "Failed to load Terrain1.raw.", "Error", MB_OK);
	}

	// Try to load our texture for the height map
	if(!hMap.LoadTexture("Art/Dirt1.bmp"))
	{
		MessageBox(NULL, "Failed to load terrain texture.", "Error", MB_OK);
	}

	// Now set up our max values for the camera
	Cam.m_MaxForwardVelocity = 5.0f;
	Cam.m_MaxPitchRate = 5.0f;
	Cam.m_MaxHeadingRate = 5.0f;
	Cam.m_PitchDegrees = 0.0f;
	Cam.m_HeadingDegrees = 0.0f;
The only difference between our Init function and the NeHe base code init is I added the above code. All it does is load a raw file created to generate the height map and then loads a texture for the terrain. Oh yea we are also setting the Cameras maximum allowed velocity, pitch, and heading. Lets take a look at keyboard handler below.
void CheckKeys(void)
{
	if(keys[VK_UP])
	{
		Cam.ChangePitch(5.0f);
	}
	
	if(keys[VK_DOWN])
	{
		Cam.ChangePitch(-5.0f);
	}

	if(keys[VK_LEFT])
	{
		Cam.ChangeHeading(-5.0f);
	}

	if(keys[VK_RIGHT])
	{
		Cam.ChangeHeading(5.0f);
	}

	if(keys['W'] == TRUE)
	{
		Cam.ChangeVelocity(0.1f); 
	}

	if(keys['S'] == TRUE)
	{
		Cam.ChangeVelocity(-0.1f);
	}
}
All we need to do here is check for a key press and then take an appropriate action like change the camera heading. The Camera class has the following member functions to do this for you: ChangeVelocity(), ChangeHeading(), and ChangePitch(). These guys handle all the details of changing degrees we want to rotate by. We need these because we have to do a little checking for cases like if we are upside down or not when rotating. I will not be adding the code here because its pretty basic. Next is the mouse handler.
void CheckMouse(void)
{
	GLfloat DeltaMouse;
	POINT pt;
	GetCursorPos(&pt);

	MouseX = pt.x;
	MouseY = pt.y;

	if(MouseX < CenterX)
	{
		DeltaMouse = GLfloat(CenterX - MouseX);
		Cam.ChangeHeading(-0.2f * DeltaMouse);
	}
	else if(MouseX > CenterX)
	{
		DeltaMouse = GLfloat(MouseX - CenterX);
		Cam.ChangeHeading(0.2f * DeltaMouse);
	}

	if(MouseY < CenterY)
	{
		DeltaMouse = GLfloat(CenterY - MouseY);
		Cam.ChangePitch(-0.2f * DeltaMouse);
	}
	else if(MouseY > CenterY)
	{
		DeltaMouse = GLfloat(MouseY - CenterY);
		Cam.ChangePitch(0.2f * DeltaMouse);
	}
	
	MouseX = CenterX;
	MouseY = CenterY;

	SetCursorPos(CenterX, CenterY);
}
This function basically does the same as the keyboard handler with the exception of DeltaMouse. DeltaMouse just holds the change of Mouse coordinates. If you move the mouse fast then DeltaMouse will be larger. If you were to move the mouse slowly then DeltaMouse would be small. This will allow us to make a nice smooth transition when rotating instead of very jerky movements as with the keyboard handler. Finally the only thing that remains is the DrawGLScene().
int DrawGLScene(GLvoid)						// Here's Where We Do All The Drawing
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear Screen And Depth Buffer
	glLoadIdentity();					// Reset The Current Modelview Matrix

	Cam.SetPrespective();

	// Lets make the height map really big since we move so fast.
	glScalef(hMap.m_ScaleValue, hMap.m_ScaleValue * HEIGHT_RATIO, hMap.m_ScaleValue);
	hMap.DrawHeightMap();

	CheckKeys();
	CheckMouse();

	return TRUE;						// Everything Went OK
}
Here we load the identity matrix and set our prespective on things by way of the camera class we made. Next scale and draw the height map. Check the mouse and keyboard for changes and that is it. There are a few other things worth mentioning here about Quaternions and OpenGL in general. I could have written this entire lesson with only glRotatef() to do my rotations and glGetFloatv() to get the model view matrix for my direction vector. So whats the point of using Quaternions? Well you don't really need too, at least not for something like this. The only reason I wrote this lesson is to illustrate that single point. I think its a common misconception among people that you need really high tech math classes in order to get something like a flight sim of some kind up and running. OpenGL keeps track of most of the information for you so there is really no need to put extra computations for this sort of thing in your code it will only slow things down. I've seen lots of code that does all sorts of crazy vector math to do this stuff as well as matrices and what not. I believed at one time that Quaternions would allow me to do all this stuff if I could just understand them. After finally understanding how Quaternions worked it only served to show me that I never needed them in the first place. Don't get me wrong. I'm not saying Quaternions are useless or anything. They do have their uses I am sure there are instances where you might want to use them and now you can. I just wanted to point out that you do not really need them to fly around your scene and that just plain old OpenGL code would suffice. If you were to change the SetPrespective function in the glCamera class to the code below you would get the exact same result as you would if you were using the above SetPrespective function using Quaternions, but you might be wondering about the gimbal lock that I mentioned earlier. Well to put it plainly it will never happen in this code! The reason we don't have to worry about the nasty effects of gimbal lock with the below function is because we are loading the identity matrix every time we draw the scene in our DrawGLScene() function like all good GL programmers are supposed to do. Gimbal lock will only reer its ugly head when you are combing several rotations and you do not load the Identity matrix every time you load your scene.
void glCamera::SetPrespective()
{
	GLfloat Matrix[16];

	glRotatef(m_HeadingDegrees, 0.0f, 1.0f, 0.0f);
	glRotatef(m_PitchDegrees, 1.0f, 0.0f, 0.0f);

	glGetFloatv(GL_MODELVIEW_MATRIX, Matrix);

	m_DirectionVector.i = Matrix[8];
	m_DirectionVector.k = Matrix[10];

	glLoadIdentity();

	glRotatef(m_PitchDegrees, 1.0f, 0.0f, 0.0f);

	glGetFloatv(GL_MODELVIEW_MATRIX, Matrix);
	m_DirectionVector.j = Matrix[9];

	glRotatef(m_HeadingDegrees, 0.0f, 1.0f, 0.0f);

	// Scale the direction by our speed.
	m_DirectionVector *= m_ForwardVelocity;

	// Increment our position by the vector
	m_Position.x += m_DirectionVector.i;
	m_Position.y += m_DirectionVector.j;
	m_Position.z += m_DirectionVector.k;

	// Translate to our new position.
	glTranslatef(-m_Position.x, -m_Position.y, m_Position.z);
}
I hope you guys can use this and I also hope this helps some of you guys out with your own projects. Just want to thank Jeff for making an awsome site where information is not hard to come by. Also I need to thank DigiBen at http://www.gametutorials.com for his tutorial on Quaternions. I had to learn this stuff from somewhere :)

- Cheers Vic

* DOWNLOAD Visual C++ Code For This Lesson.

* DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( Conversion by Dominique Louis )
* DOWNLOAD Code Warrior 5.3 Code For This Lesson. ( Conversion by Scott Lupton )
* DOWNLOAD Dev C++ Code For This Lesson. ( Conversion by Mike )
* DOWNLOAD Linux Code For This Lesson. ( Conversion by Kimmo Karlsson )