Quaternions

Started by
11 comments, last by Eelco 19 years, 2 months ago
Hello, I'm trying to implement the Quaternion based camera presented here, but I am not getting the results I'd expect. I've read the top few links on Google about quaternions, but that's about as far as my understanding goes. What happens is I load up the scene, move my mouse, and nothing happens. I checked my view vector throughout the program and all I'm gettings are some very small numbers for the components. here's a copy of what I believe to be all of the relevant source code.

void Camera::mouseMove()
{
	POINT mousePos;
	int middleX = screenWidth  >> 1; // divide the width by two
	int middleY = screenWidth >> 1;  // divide the height by two
	
	float angleX = 0.0f;	// This is the direction for looking up or down
	float angleY = 0.0f;	// This will be the value we need to rotate around the Y axis (Left and Right)
	
	// Get the mouse's current X,Y position
	GetCursorPos(&mousePos);						
	
	// curser didn't move, quit wasting my time!
	if( (mousePos.x == middleX) && (mousePos.y == middleY) ) 
		return;

	// move the mouse back to the center of the screen
	SetCursorPos(middleX, middleY);							

	angleX = (float)( (middleX - mousePos.x) ) / mouseSensivity;		
	angleY = (float)( (middleY - mousePos.y) ) / mouseSensivity;

	Vector axis = Vector::cross(Vector(c_view.getX() - c_position.getX(),
							           c_view.getY() - c_position.getY(),
									   c_view.getZ() - c_position.getZ()), c_up);
	axis = Vector::normalize(axis);

	// Rotate around the y axis
    rotateCamera(angleY, axis.getX(), axis.getY(), axis.getZ());
    // Rotate around the x axis
    rotateCamera(angleX, 0, 1, 0);
	
	updateCamera();
}

void Camera::rotateCamera(float theta, float x, float y, float z)
{
  Quaternion temp;
  Quaternion qview;
  Quaternion final;

  float sintheta = sin(theta / 2);
  temp.setX(x * sintheta);
  temp.setY(y * sintheta);
  temp.setZ(z * sintheta);
  temp.setW(    cos(theta/2));

  qview.setX(c_view.getX());
  qview.setY(c_view.getY());
  qview.setZ(c_view.getZ());
  qview.setW(0);

  final = Quaternion::multiply(Quaternion::multiply(temp, qview), Quaternion::conjugate(temp));

  c_view.setX(final.getX());
  c_view.setY(final.getY());
  c_view.setZ(final.getZ());
}

void Camera::updateCamera()
{
	gluLookAt(c_position.getX(), c_position.getY(), c_position.getZ(),
			  c_view.getX()    , c_view.getY()    , c_view.getZ(),
			  c_up.getX()      , c_up.getY()      , c_up.getZ());
}


and here is the quaternion multiplication

Quaternion Quaternion::normalize(Quaternion quat)
{
	float length = sqrt( (quat.getX() * quat.getX()) + (quat.getY() * quat.getY())
		               + (quat.getZ() * quat.getZ()) + (quat.getW() * quat.getW()) );

	quat.setX(quat.getX() / length);
	quat.setY(quat.getY() / length);
	quat.setZ(quat.getZ() / length);
	quat.setW(quat.getW() / length);
		
	return quat;
}

Quaternion Quaternion::conjugate(Quaternion quat)
{
	quat.setX( -quat.getX() );
	quat.setY( -quat.getY() );
	quat.setZ( -quat.getZ() );

	return quat;
}

Quaternion Quaternion::multiply(Quaternion one, Quaternion two)
{
	Quaternion three = Quaternion();

	three.setX(one.getW() * two.getX() + 
			   one.getX() * two.getW() + 
			   one.getY() * two.getZ() -
			   one.getZ() * two.getY());

	three.setY(one.getW() * two.getY() - 
		       one.getX() * two.getZ() + 
			   one.getY() * two.getW() + 
			   one.getZ() * two.getX());

	three.setZ(one.getW() * two.getZ() + 
		       one.getX() * two.getY() - 
			   one.getY() * two.getX() + 
			   one.getZ() * two.getW());

	three.setW(one.getW() * two.getW() -
		       one.getX() * two.getX() -
			   one.getY() * two.getY() - 
			   one.getZ() * two.getZ());

	return three;
}


Not the best code I've written (obviously, since it doesn't work :) ), but hopefully you guys should be able to see what I'm doing wrong. Thanks for your help. EDIT: if it matters, mouseSensivity is 2.0f; Thanks
Advertisement
I think someone else was having trouble with this recently. Just glancing over that tutorial, it doesn't look right to me. It would make sense to rotate a view *vector* using a rotation quaternion, but the tutorial (and your code) appears to be rotating a point, i.e. a view *target*.

I'd have to dig into it a little more to be sure of my answer, but you might try the following:

Comment out this:

Vector axis = Vector::cross(Vector(c_view.getX() - c_position.getX(),
c_view.getY() - c_position.getY(),
c_view.getZ() - c_position.getZ()), c_up);

And replace it with:

Vector axis = Vector::cross(c_view, c_up);

Then comment out:

gluLookAt(c_position.getX(), c_position.getY(), c_position.getZ(),
c_view.getX() , c_view.getY() , c_view.getZ(),
c_up.getX() , c_up.getY() , c_up.getZ());

And replace it with:

Vector target = c_position + c_view;
gluLookAt(c_position.getX(), c_position.getY(), c_position.getZ(),
target.getX(), target.getY(), target.getZ(),
c_up.getX(), c_up.getY() , c_up.getZ());

(This is all assuming that your quat mult function is correct, which I didn't check.)

Anyway, I'd try those changes and see what happens.

[Edit: My code is changing your view target to a view vector. One thing I forget is that your view vector initialization will probably have to be changed. Setting it to [0 0 -1] might work.]
So, I tried doing what you suggested and I am still having the same results as before (move mouse, nothing happens). However, when I print the target vector to a file and look at the values, it looks like it's doing something right. I don't know exactly what to expect, but the fact that all of them are unit vectors is certainly encouraging.

I don't really have much (read "any") experience with the gluLookAt function, so I'm guessing my problem now lies in some other silly mistake that I'm making elseware in my code. Thanks for the help, what you said has given me a better understanding of quaternions.
Why not just use standard Euclidean Vector algebra i.e. arbitrary axis rotations?
Quaternions are just an overrated buzz-word IMHO.

[Edited by - iMalc on February 6, 2005 2:56:31 AM]
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Aside from the complexity of understanding the Quaternion rotations, this was a very simple program to write. Even then, this is not the most difficult piece of math I've digested; certainly plently of people will have no problems getting a working quaternion camera.

The question then becomes why do something that looks like (after just glancing at the link, correct me if I'm wrong) it's code would be drawn out and ugly when you could write something as quick and concise as quaternions. Once again I'm baseing this off a quick glance at your link, but I would also guess that a quaternion camera would be more efficient at run-time.

A little complexity will never stop a programmer from writing more efficient code =)

Update: I found the bug in my program, the camera works perfectly now.
Thanks for your help.

[Edited by - Fibonacci One on February 5, 2005 12:28:44 PM]
Quote:Update: I found the bug in my program, the camera works perfectly now.

What was the bug?

Quote:Quaternions are just an overrated buzz-word IMHO.

Well, they are certainly overkill for some applications, but 'overrated buzz-word'? I don't know about that. (Although to be fair, you aren't the only one who feels that way - there have been some rather fiery discussions about this around the net.)

However, consider:

1. It's much easier to interpolate quaternions than matrices, which has all kinds of useful applications.

2. Quats take up less memory, which can be useful for, say, keyframe animations or console platforms.

3. Quats can be concatentated faster than matrices, which again can make a difference in an animation system.

4. Quats can be renormalized faster than matrices.

I will say that quats are probably not necessary for a standard 'FPS' camera, i.e. one where the only rotational DOFs are yaw and pitch. A 6DOF 'Descent-style' camera can still be done with matrices only (e.g. Descent :-), but a quat-based implementation is a good alternative and has some advantages.

Anyway, glad you got it working.
Quote:
What was the bug?


In addition to what you helped me with, I had set my program to call gluLookAt after I finished rendering... Doh!

The bugs that are easiest to fix are often the most embarassing to admit to ever having in the first place =).
Quote:Original post by jyk
Quote:Update: I found the bug in my program, the camera works perfectly now.

What was the bug?

Quote:Quaternions are just an overrated buzz-word IMHO.

Well, they are certainly overkill for some applications, but 'overrated buzz-word'? I don't know about that. (Although to be fair, you aren't the only one who feels that way - there have been some rather fiery discussions about this around the net.)

However, consider:

1. It's much easier to interpolate quaternions than matrices, which has all kinds of useful applications.

2. Quats take up less memory, which can be useful for, say, keyframe animations or console platforms.

3. Quats can be concatentated faster than matrices, which again can make a difference in an animation system.

4. Quats can be renormalized faster than matrices.

I will say that quats are probably not necessary for a standard 'FPS' camera, i.e. one where the only rotational DOFs are yaw and pitch. A 6DOF 'Descent-style' camera can still be done with matrices only (e.g. Descent :-), but a quat-based implementation is a good alternative and has some advantages.

Anyway, glad you got it working.
Okay, in all fairness, perhaps I have used quaternions as little as you have arbitrary axis rotations. Though I do understand how to use both. Unfortunately what you've posted is simply myths about arbitrary axis rotations. (Sorry I said matricies earlier, I meant axes)

1. It's only easier for someone who's used it a lot more than the alternative. for a quat you have to interpolate 4 values right? For the arbitrary rotation you only have to interpolate the angle.

2. The information necessary to describe an arbitrary rotation is the same size as that of a quaternion.

3. Quats have to be converted back into a matrix at some point. The equivalent arbitrary rotation matrix is directly generated from an axis/angle with the same amount of effort.

4. This applies equally in both cases. You only have to normalise a quat if the arbitrary vector you were to rotate around was not unit length, I think.

I'm very sorry if I digress. I don't know why I'm bothering, but the truth is, the two methods are equivalent and neither has any real advantage over the other.
Quats really have only become more popular because of their cool name and funky imaginary mathematics. Read the rest of the article and you'll see.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:
3. Quats have to be converted back into a matrix at some point. The equivalent arbitrary rotation matrix is directly generated from an axis/angle with the same amount of effort.

To convert axis-angle to matrix, you need sin and cos. It is MUCH more expensive operation. Also, have you tried to combine axis and angle rotations together?
If you would really work with either thing, you would know that axis and angle is essentially converted to quaternion prior to doing something, e.g. conversion to matrix. Quaternion stores axis and sine and cosine of half-angle. To do something with axis and angle, you need to get sine and cosine of angle. What's the point in storing angle itself if we rarely need it?

Another thing. Quaternion is easily converted to axis and angle and back. You might have quaternion class with get_axisangle , set_axisangle. How it could possibly be more complex than axis and angle? Quaternions is , indeed, equivalent to axis and angle, but offer many computational benefits such as fast combination of several rotations.

Let anybody who think axis and angle is any simpler than quaternions will right now write combination of rotations. (You'll see that essentially, axis and angle is converted to quaternion, multiplied, and converted back. So in program where you use axis and angle, you'll do tons of sin and cos and asin and acos for absolutely no reason.)

That is, you have some point P that you want to rotate by A, then by B to obtain point P', where A and B is axis-angle structs. Compute C that rotation of P by C gives P'
If you can't do it, i consider that you don't know axis and angle.
Quote:1. [slerp is] only easier for someone who's used it a lot more than the alternative. for a quat you have to interpolate 4 values right? For the arbitrary rotation you only have to interpolate the angle.

You can interpolate a rotation around a *single axis* by interpolating a single value (the angle). But slerp interpolates between two *orientations*. I imagine there's a way to do this with axis/angle, but I've never seen an implementation so I can't say whether it would be more or less efficient than slerp.

Quote:2. The information necessary to describe an arbitrary rotation is the same size as that of a quaternion.

Yes, initially I thought you were referring to matrices.

Quote:3. Quats have to be converted back into a matrix at some point. The equivalent arbitrary rotation matrix is directly generated from an axis/angle with the same amount of effort.

Axis-angle to matrix requires sin() and cos(). Quat to matrix requires only multiplications and additions. So for that particular operation, at least, quats are faster.

Quote:4. This applies equally in both cases. You only have to normalise a quat if the arbitrary vector you were to rotate around was not unit length, I think.

Right. Again, my original statement was in regard to matrices.

One thing that is as you say a 'myth' is that quaternions are the best or only way to prevent gimbal lock. The same thing can be accomplished with axis-angle or matrix representations.

I'm not a mathematician, so I can't give a rigorous comparison of quaternion and axis-angle representations and their equivalence or lack thereof. For example, I've never looked into concatenating axis-angle rotations, so I'm not even sure how to do it and therefore can't say how it compares to the equivalent quaternion operation. (Dmytry's post seems to suggest that it essentially involves conversion to quaternion form; if that's true, quats would be a win in this case.)

Based on my current knowledge, however, quaternions are easier and/or faster to concatentate, interpolate, and convert to matrix form than axis-angle. As I am always trying to learn, though, I welcome any corrections or clarifications.

Regardless, I do question the common assertion that people only use quaternions because they sound 'cool' :-) That may be true for a beginner, but you've got to give more experienced programmers some credit! I have seen slerp discussed and implemented countless times, but I've never seen an equivalent implementation for interpolating axis-angle rotations (that doesn't mean there isn't one). So for me at least, quaternions are a better and more powerful choice.

This topic is closed to new replies.

Advertisement