Quaternion Camera Implementation

Started by
4 comments, last by Coldon 16 years, 5 months ago
I'm trying to get a quaternion camera working but i'm struggling a bit, strangely enough not with the maths behind the quaternion but rather how to implement it in opengl. The tutorials i've found all use different methods. Some directly modify the modelview matrix while others make using of transformations via glRotate/glTranslate's and finally some use the gluLookAt() function (i know this is a shortcut to the transforms). My quaternion maths is right and my class can produce anything needed for any of the above methods. can you give me a brief overview of where i need to start and how to implement the camera? like should i use heading and pitch variables (kinda like euler angles), should i use a heading vector and work from that? Whats the difference between these methods? Is there a prefered solution? here are my vector, quaternion and camera classes, i've implemented a basic rotate method (from the NEHE tutorial) which doesnt work as intended, it seems to rotate spirally ?!? Point and Vector Classes :

class point3f
{	
public:
		
	//members
	//------------------------------------------------------------------------------

	float x, y, z;
	
	//constructors
	//------------------------------------------------------------------------------

	point3f() : x(0),y(0),z(0) {}		
	point3f(float ix, float iy, float iz) : x(ix), y(iy), z(iz) {}

	//methods
	//------------------------------------------------------------------------------

	//addition
	point3f operator +(point3f p)
	{
		return point3f(x + p.x, y + p.y, z + p.z);		 
	}

	//subtraction
	point3f operator -(point3f p)
	{
		return point3f(x -p.x, y - p.y, z - p.z);		 
	}

	//convert to array
	void toArray(float* a)
	{	
		a[0] = x;
		a[1] = y;
		a[2] = z;
	}
};

//vector class
class vector3f
{

public:

	//members
	//------------------------------------------------------------------------------

	float x, y, z;

	//constructors
	//------------------------------------------------------------------------------

	//default constructor
	vector3f() : x(0),y(0),z(0) {}	

	//create vector from point
	vector3f(point3f p) : x(p.x), y(p.y), z(p.z) {}
	
	vector3f operator =(point3f p)
	{
		return vector3f(p);
	}

	//create vector with co-ordinates
	vector3f(float ix, float iy, float iz) : x(ix), y(iy), z(iz) {}

	//methods
	//------------------------------------------------------------------------------
	
	//cross product
	vector3f crossProduct(const vector3f &v)
	{
		vector3f cp;

		//cross product
		cp.x =	y * v.z - z * v.y;
		cp.y =	-( x * v.z - z * v.x );
		cp.z =	x * v.y - y * v.x;

		return cp;
	}

	//normalize
	void normalize()
	{
		float sumSquares = x*x + y*y + z*z;

		//dont normalise if vector is close enough
		if ( abs(sumSquares - 1.0) > VECTOR_TOLERANCE )
		{
			float L = sqrt(sumSquares);
			
			x /= L;
			y /= L;
			z /= L;
		}
	}

	//return normalize copy of current vector
	vector3f getNormalized()
	{
		//copy current quarternion and return conjugate
		vector3f v(*this);
		v.normalize();

		return v;
	}

	//dot product
	float dotProduct(const vector3f &v)
	{
		return x * v.x + y * v.y + z * v.z;
	}

	//shortcut for cross product
	vector3f operator *(vector3f &v)
	{
		return crossProduct(v);
	}

	//addition
	vector3f operator +(vector3f &v)
	{
		return vector3f(x + v.x, y + v.y, z + v.z);		 
	}

	//subtraction
	vector3f operator -(vector3f &v)
	{
		return vector3f(x - v.x, y - v.y, z - v.z);		 
	}
};
Quaternion Class:

class quaternion
{
public:
	
	//members
	//------------------------------------------------------------------------------
	
	float x,y,z,w;

public:		
	
	//constructors
	//------------------------------------------------------------------------------

	//empty constructor
	quaternion() : x(0), y(0), z(0), w(0) {}
	
	//specific constructor
	quaternion(float xi, float yi, float zi, float wi) : x(xi), y(yi), z(zi), w(wi) {}

	//from vector
	quaternion(vector3f v) : x(v.x), y(v.y), z(v.z), w(0) {}

	//create from an angle and an arbitrary axis
	quaternion(float degrees, vector3f axis)
	{
		float theta = (degrees / 180.0f) * PI;			
		float sinTheta = sin( theta / 2.0f );
		float cosTheta = sin( theta / 2.0f );			
	
		//calculate quaternion values
		x = axis.x * sinTheta;
		y = axis.y * sinTheta;
		z = axis.z * sinTheta;
		w = cosTheta;
	}

	//methods
	//------------------------------------------------------------------------------

	//define quaternion multiplication operator
	quaternion operator *(const quaternion &b)
	{
		quaternion result;

		result.x = w * b.x + x * b.w + y * b.z - z * b.y;
		result.y = w * b.y - x * b.z + y * b.w + z * b.x;
		result.z = w * b.z + x * b.y - y * b.x + z * b.w;
		result.w = w * b.w - x * b.x - y * b.y - z * b.z;			

		//return new quaterion
		return(result);
	}

	//define vector multiplication operator
	vector3f operator *(const vector3f &vi)
	{
		//create quaternion from normalized vector
		vector3f vn(vi);
		vn.normalize();
		quaternion v(vn), result;
			
		//apply multiplications 
		result = v * getConjugate();
		result = *this * result;
	 
		return vector3f(result.x, result.y, result.z);
	}

	//normalise quaterion
	void normalize()
	{		  
		float sumSquares = x * x + y * y + z * z + w * w;
		
		if ( abs(sumSquares - 1.0) > QUATERNION_TOLERANCE )
		{			
			float L = sqrt(sumSquares);
		
			x /= L;
			y /= L;
			z /= L;
			w /= L;
		}
	}

	//return normalized copy of current quaternion
	quaternion getNormalized()
	{
		//copy current quarternion and return conjugate
		quaternion q(*this);
		q.normalize();

		return q;
	}

	//conjugate quaterion
	void conjugate()
	{
		x = -x;
		y = -y;
		z = -z;			
	}

	//return conjugate copy of current quaternion
	quaternion getConjugate()
	{
		//copy current quarternion and return conjugate
		quaternion q(*this);
		q.conjugate();

		return q;
	}

	//to vector
	vector3f toVector()
	{		
		return vector3f(x, y, z);		
	}

	//to matrix (homogenous 4x4 matrix)
	void toMatrix(float* matrix)
	{		
		// First row
		matrix[0]	= 1.0f - 2.0f * ( y * y + z * z );
		matrix[1]	= 2.0f * (x * y + z * w);
		matrix[2]	= 2.0f * (x * z - y * w);
		matrix[3]	= 0.0f;
		
		// Second row
		matrix[4]	= 2.0f * ( x * y - z * w );
		matrix[5]	= 1.0f - 2.0f * ( x * x + z * z );
		matrix[6]	= 2.0f * (z * y + x * w );
		matrix[7]	= 0.0f;

		// Third row
		matrix[8]	= 2.0f * ( x * z + y * w );
		matrix[9]	= 2.0f * ( y * z - x * w );
		matrix[10]	= 1.0f - 2.0f * ( x * x + y * y );
		matrix[11]	= 0.0f;

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

};
Camera Class:

class camera
{
	public:

		//constructors
		//------------------------------------------------------------------------------

		camera(vector3f p, vector3f v, vector3f u) : position(p), view(v), up(u) {}
	
		//methods
		//------------------------------------------------------------------------------

		void rotate(float h, float p)
		{
			GLfloat m[16];			

			// Make the Quaternions that will represent our rotations
			quaternion qh(h,vector3f(1,0,0));
			quaternion qp(p,vector3f(0,1,0));			
			
			// Combine the pitch and heading rotations and store the results in q
			quaternion result = qp * qh;
			result.toMatrix(m);

			// Let OpenGL set our new prespective on the world!
			glMultMatrixf(m);
		}
		
		void changePosition()
		{

		}
};
[Edited by - Coldon on October 17, 2007 3:32:14 AM]

"In theory, theory and practice are the same. In Practice, they never are."
My Technical Blog : http://www.takinginitiative.net/

Advertisement
use Cos if you write Cos ;)

float cosTheta = sin( theta / 2.0f ); // SHOULD BE COS


I don't think that's your only problem here :/ your methods look well implemented, though. could you describe the artifacts?



again it's me.

I never implemented a quat-based camera, but like the trackball it can be imagined as the task to rotate one starting point P to a target point P' on the unit sphere. to compute the quaternion you are looking for, you have to
a) compute the rotation axis (P.cross(P'))
b) the rotation angle (acos(P.dot(P'))

you can then use your constructor "quaternion(float degrees, vector3f axis)" to build the rotation Q that moves P into P'. last step should be to convert Q into a 4x4 matrix (I use glLoadMatrix()).

I don't know if it's necessary for a camera-implementation, but for the trackball you have to store the last computed quaternion as starting point for the next rotation...but not sure here.

greetx,
me.
hey, lol - that was a dumb mistake with the cos function, dangers of cutting and pasting. I'll try what you suggest and get back to you tomorrow.

"In theory, theory and practice are the same. In Practice, they never are."
My Technical Blog : http://www.takinginitiative.net/

I have a quaternion camera implementation in opengl and direct3d over on my site that might be of help to you.
http://www.dhpoware.com
@dpoon:

your camera examples are excellent! I really like your coding style as its really similar to my own and its neat clear and concise!

That stupid typo was causing the problems, once i fixed it the quaternion rotations work as intended, i'm busy getting movement working but its proving to be a little tricky but i'll get it (eventually)...

"In theory, theory and practice are the same. In Practice, they never are."
My Technical Blog : http://www.takinginitiative.net/

This topic is closed to new replies.

Advertisement