Jump to content
  • Advertisement
Sign in to follow this  
clapton

Quternions (basics)

This topic is 4868 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello! Yesterday - with a help of various resources - I've managed to implement my first quaternion class. Obviously, I've studied the topic pretty hard before attempting to do some code. Anyway - as you might guess - a problem occured. I have a quaternion representing the current rotation of an object and the second one which 'increments' the rotation (at least in theory...). Everything works fine at the beginning - the object is being rotated. The problem is that the rotation speed keeps decreasing until the rotation angle reaches 90 degrees where the object stops rotating completly! I am not sure what's the cause. Maybe it's a bug in the quaternion class code? Maybe I've misunderstood how to use quaternions properly? I'd be thankful for your help! Here is a source of the quaternion class. (forgive me my bad English commentaries, please)
#define DEG_TO_RAD(x)    x * 0.017453

// NOTE : CQuaternion class is used to represent quaternions of
//        the unit lenght. Though, all formulas used in the code
//		  take into account that every CQuaternion's magnitude
//		  equals 'one'. This way, calculations are more efficient.

class CQuaternion
{
	public:
		
		union 
		{
			float		 q[4];		// Quaternion represented as an array
			struct 
			{
				float w;			// Scalar component
				float x, y, z;  	// Vector component
			};
		};
		
		// Constructors
		CQuaternion() { }
	
		CQuaternion(const CQuaternion &quat) {
			q[0] = quat.q[0]; q[1] = quat.q[1]; q[2] = quat.q[2]; q[3] = quat.q[3];
		}
	
		CQuaternion(float w, float xyz[])  { 
			RotationAxis(w, xyz[0], xyz[1], xyz[2]);
		}
		
		CQuaternion(float w, float x, float y, float z)  { 
			RotationAxis(w, x, y, z);
		}		
		
		// Destructor
		~CQuaternion() { }
		
		// Fundamental operations
				
		// Sets the components of the quaternion to represent a rotation
		void	RotationAxis(float angle, float x, float y, float z) {			
			 q[0] = cosf( DEG_TO_RAD(angle) * 0.5 );  // Scalar component
			 float sint = sinf( DEG_TO_RAD(angle) * 0.5 );
			 q[1] = x * sint; // We assume that (x,y,z) form a unit-lenght vector
			 q[2] = y * sint;
			 q[3] = z * sint;
		}
		
		// Sets the quat to identity quaternion
		void			SetIdentity() { 
		 	 w = 1.0; 
		}
		
		// Negates the quaternion (remember that negated quaternion represent the same angular displacement!)
		void			Negate() 	  {
			 w = -w;
			 x = -x;
			 y = -y;
			 z = -z;
		}
		
		// Quaternion conjugate operation (negating the vector component)
		CQuaternion		Conjugate()  	{
			 x = -x;

			 y = -y;
			 z = -z;
		}
		
		// Inverses the quaternion
		// NOTE : since we deal with unit-length quaternions only,
		// 		  quaternion inverse equals its conjugate. Furthermore,
		//		  instead of negating the vector component of the quaternion
		//		  we will negate 'w' thus reversing what is considered
		//		  positive rotation by flipping the axis of rotation.
		CQuaternion		Inverse() 		{
			w = -w;	
		}
		
		// Quaternion multiplication
		// QResult = [ w1*w2 - v1 * v2  ( w1*v2 + w2*v1 + v2*v1 ) ]
		CQuaternion		Mult(const CQuaternion& quat)	{
	
			CQuaternion QResult;
			QResult.q[0] = quat.q[0]*q[0]
		   				  -quat.q[1]*q[1]
					      -quat.q[2]*q[2]
		   				  -quat.q[3]*q[3];
		   				  
			QResult.q[1] = quat.q[0]*q[1]
		   				  -quat.q[1]*q[0]
					      -quat.q[2]*q[3]
		   				  -quat.q[3]*q[2];			  
			
			QResult.q[2] = quat.q[0]*q[2]
		   				  -quat.q[1]*q[3]
					      -quat.q[2]*q[0]
		   				  -quat.q[3]*q[1];
							 
			QResult.q[3] = quat.q[0]*q[3]
		   				  -quat.q[1]*q[2]
					      -quat.q[2]*q[1]
		   				  -quat.q[3]*q[0];		   				  
		   	return QResult;		   				  
		}
		
		// Quaternion multiplication which assigns the result to *this quaternion
		void		MultAndSet(const CQuaternion& quat)	{
	
			CQuaternion QResult;
			QResult.q[0] = quat.q[0]*q[0]
		   				  -quat.q[1]*q[1]
					      -quat.q[2]*q[2]
		   				  -quat.q[3]*q[3];
		   				  
			QResult.q[1] = quat.q[0]*q[1]
		   				  -quat.q[1]*q[0]
					      -quat.q[2]*q[3]
		   				  -quat.q[3]*q[2];			  
			
			QResult.q[2] = quat.q[0]*q[2]
		   				  -quat.q[1]*q[3]
					      -quat.q[2]*q[0]
		   				  -quat.q[3]*q[1];
							 
			QResult.q[3] = quat.q[0]*q[3]
		   				  -quat.q[1]*q[2]
					      -quat.q[2]*q[1]
		   				  -quat.q[3]*q[0];		   				  
			q[0] = QResult.q[0];
			q[1] = QResult.q[1];
			q[2] = QResult.q[2];
			q[3] = QResult.q[3];
		}			
		
		// Normalizes the quaternion
		void	Normalize() {
			float mag = (float) sqrt(w*w + x*x + y*y + z*z);
			
			if (mag > 0.0)
			{
				float oneOverMag = 1.0f/mag;
				w *= oneOverMag;
				x *= oneOverMag;
				y *= oneOverMag;
				z *= oneOverMag;				
			} 
			else SetIdentity();		// We've got a problem ...			
		}
	
		// Creates a matrix basing	on the rotation stored in the quaternion
		void	GetMatrix(float *matrix)	{
			
			Normalize();
			
			float xx = x*x;
			float yy = y*y;
			float zz = z*z;

			matrix[0]  = 1.0f - 2.0f*(yy+zz);
			matrix[4]  = 2.0f * (x*y+w*z);
			matrix[9]  = 2.0f * (x*z-w*y);
			matrix[12] = 0.0f;

			matrix[1]  = 2.0f * (x*y-w*z);
			matrix[5]  = 1.0f - 2.0f*(xx+zz);
			matrix[9]  = 2.0f * (y*z+w*x);
			matrix[13] = 0.0f;

			matrix[2]  = 2.0f * (x*z+w*y);
			matrix[6]  = 2.0f * (y*z-w*x);
			matrix[10] = 1.0f - 2.0f*(xx+yy);
			matrix[14] = 0.0f;

			matrix[3]  = 0.0f;
			matrix[7]  = 0.0f;
			matrix[11] = 0.0f;
			matrix[15] = 1.0f;			
		}
};



... and here is the actual use of the class :
	static CQuaternion quat(0.0, 0.0, 1.0, 0.0);
	float			   matrix[16];
	
	CQuaternion displace;
	displace.RotationAxis(10.0 * dt, 0.0, 1.0, 0.0);

	quat.MultAndSet(displace);	
	quat.GetMatrix(matrix);
	
	glMultMatrixf(matrix);    



Thanks in advance! [Edited by - clapton on July 23, 2005 11:24:47 AM]

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by Name_Unknown
Yeah it looks like you have w in the wrong place. struct {x,y,z,w} is what I think you want.


Actually, all formulas where written with an assumption that w corresponds to q[0]. Anyway, I've tried what you suggest and it was not the point.

Thanks! :)

Share this post


Link to post
Share on other sites
Double-check your multiplication code. Also, is there any reason you're not zeroing out the vector component when setting to identity?

Share this post


Link to post
Share on other sites
I agree with jyk; your multiplication rules are not correct.

You have to reverse the quaternion component order (you were doing result = b*a, not result = a*b), and change the sign for quite a few of the terms.


// my code...
// the correct multiplication rules (adjusted so that w is element 0... a lot of people use x as element 0 and w as element 3)
// also reordered and resigned so that multiplication columns are easy to follow
result.w = a.w* b.w + a.x*-b.x + a.y*-b.y + a.z*-b.z
result.x = a.w* b.x + a.x* b.w + a.y* b.z + a.z*-b.y
result.y = a.w* b.y + a.x*-b.z + a.y* b.w + a.z* b.x
result.z = a.w* b.z + a.x* b.y + a.y*-b.x + a.z* b.w


// your code...
// this was OK, but only by coincidence, so i changed the quaternion order
QResult.q[0] = q[0]*quat.q[0] - q[1]*quat.q[1] - q[2]*quat.q[2] - q[3]*quat.q[3];

// i had to change the signs and quaternion order for the other three though...
QResult.q[1] = q[0]*quat.q[1] + q[1]*quat.q[0] + q[2]*quat.q[3] - q[3]*quat.q[2];
QResult.q[2] = q[0]*quat.q[2] - q[1]*quat.q[3] + q[2]*quat.q[0] + q[3]*quat.q[1];
QResult.q[3] = q[0]*quat.q[3] + q[1]*quat.q[2] - q[2]*quat.q[1] + q[3]*quat.q[0];






P.S. These multiplication rules are easy to find in this format if you do some searching for "quaternion Julia set"... ie: (at the very bottom of the page).

[Edited by - taby on July 23, 2005 3:48:25 PM]

Share this post


Link to post
Share on other sites
Another thing... Boost has a great quaternion math library. Boost.org

I also have one, which contains a few functions that Boost does not. It also allows you to alter the multiplication rules at runtime; an idea that was passed along to me by Godwin Vickers of Hypercomplex.org.

Check here for the Utilities package (which contains the qmath library):
Julia 4D 2 downloads

My library uses the component order x, y, z, w though, so take note of this before dissecting any code.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
you're negation is wrong to, w doesn't swap sign

Share this post


Link to post
Share on other sites
Quote:
you're negation is wrong to, w doesn't swap sign.
You're thinking of conjugation; negating a quaternion negates all its components.

Share this post


Link to post
Share on other sites
Quote:
Original post by Anonymous Poster
you're negation is wrong to, w doesn't swap sign


Actually, it's the conjugate where the real component is not negated.

The inverse is found by calculating the norm of the quaternion (real.real + imag_vector.imag_vector), then dividing the conjugate by this value.

Quaternion Math Tools, Douglas Sweetser

I've never actually used this form of negation before (must be a method common to 3D rotation?).

P.S. I edited my first post in this thread. It should provide you with the correct multiplication rules that you need.

Share this post


Link to post
Share on other sites
Hi! Great thanks for your replies. Here go my answers ...

Quote:
Original post by jyk
Also, is there any reason you're not zeroing out the vector component when setting to identity?


Sure there is. :) Like I've said, the class is being made for the sake of efficiency mostly and while from practical point of view it represents a quaternion in more mathematical terms it does not. Anyway, getting straight to the problem ...

q = [ cos(omega/2) sin(omega/2) * n ]

where q is a quaternion, n is a vector of a rotation axis.

An angle which represents no angular displacement is k*360 degrees. Please note that if omega is an even multiple of 360 then cos(omega/2) = 1. If it's an odd multiple of 360, cos(omega/2) = -1. In both cases sin(omega/2) = 0, so the value of n is irrelevant.

Quote:
Original post by tabyI agree with jyk; your multiplication rules are not correct.


I love you guys! That was the problem! :D Strange, though, since I actually copied this part from a different source code. ;)

Quote:
The inverse is found by calculating the norm of the quaternion (real.real + imag_vector.imag_vector), then dividing the conjugate by this value.


Exactly. In my case I assumed that every quaternion is unit-length, so the formula simplifies to : inverse = conjugate (since magnitude of the quaternion is 1.0). Please don't blame me for heresies, I didn't say that my quaternion is mathematically correct. :)

Quote:
I've never actually used this form of negation before (must be a method common to 3D rotation?).


You mean the negation I use?

Quote:
It should provide you with the correct multiplication rules that you need.


Works perfectly! :D

Great thanks! :)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!