trouble understanding quaternions

Started by
19 comments, last by okonomiyaki 18 years, 10 months ago
Quote:Another thing. When you talk about swapping the order, I would say that you should swap the order for rotateX, but leave it the same for rotateY (yaw happens around world y-axis, pitch happens around local x-axis).
He's doing both the rotations around the local axes, so order doesn't matter the same way it does for world-axis rotations.

But yes, you should post the quat-to-matrix code, and the code that loads the quat matrix into OpenGL. There are a couple of different things that could be reversing the rotations as you've described...
Advertisement
Ok, here's the code. I create a new array, and then return it. This is just a test app and in my real one at home I have a matrix object which is a cleaner way of doing it.

float* quatToMatrix(const quaternion& q) {	float *mat = new float[16];	float xx, xy, xz, xw, yy, yz, yw, zz, zw;	xx      = q.x * q.x;	xy      = q.x * q.y;	xz      = q.x * q.z;	xw      = q.x * q.w;	yy      = q.y * q.y;	yz      = q.y * q.z;	yw      = q.y * q.w;	zz      = q.z * q.z;	zw      = q.z * q.w;	mat[0]  = 1 - 2 * ( yy + zz );	mat[1]  =     2 * ( xy + zw );	mat[2]  =     2 * ( xz - yw );	mat[4]  =     2 * ( xy - zw );	mat[5]  = 1 - 2 * ( xx + zz );	mat[6]  =     2 * ( yz + xw );	mat[8]  =     2 * ( xz + yw );	mat[9]  =     2 * ( yz - xw );	mat[10] = 1 - 2 * ( xx + yy );	mat[3]  = mat[7] = mat[11] = mat[12] = mat[13] = mat[14] = 0;	mat[15] = 1;	return mat;}


And this how I load it in:
	glLoadMatrixf(quatToMatrix(quatView));


That's all it should take since quatView is updated whenever the rotation occurs.

I've double-checked the matrix code, but there could be something wrong with it. You'd think there'd be a different way to form it depending if it was left-handed or right-handed coordinate system, but I'm not sure how to switch it. I'm pretty sure that's my problem, since OpenGL is right-handed, is it forming a matrix for the left-handed coordinate system? Absolutely no quaternion tutorial mentioned this.
Your quat-to-matrix code is one of two ways to do it; the other is the transpose of what you have there. Which one is the correct one for you depends on several things:

1. Are your matrices row- or column-major?
2. Are you using row vectors or column vectors?
3. Is your coordinate system left- or right-handed?
4. Are your rotations left- or right-handed?

Given that information it can be determined what form you want. The fact is though that since there are only two options, you could probably fix your problem by just transposing the matrix, unless there are inconsistencies elsewhere in your code. I wouldn't advise this though, as without fully understanding the reason for the transpose you're likely to run into more trouble down the road.
Quote:Absolutely no quaternion tutorial mentioned this.
I agree that these issues aren't very well documented, and it can be frustrating. Many references and tutorials simply give the matrix with little or no discussion of the assumed conventions. So you sort of have to fend for yourself.
Quote:Original post by jyk
Your quat-to-matrix code is one of two ways to do it; the other is the transpose of what you have there. Which one is the correct one for you depends on several things:

1. Are your matrices row- or column-major?
2. Are you using row vectors or column vectors?
3. Is your coordinate system left- or right-handed?
4. Are your rotations left- or right-handed?


1. column-major
2. column vectors I guess because it's column major?
3. OpenGL, so it's right-handed
4. I have no idea, how would I tell? by how I multiply the quaternions: here's the quaternion multiplication code:

quaternion operator*(const quaternion& l, const quaternion& r) {	quaternion ret;	ret.w = l.w*r.w - l.x*r.x - l.y*r.y - l.z*r.z;	ret.x = l.w*r.x + l.x*r.w + l.y*r.z - l.z*r.y;	ret.y = l.w*r.y + l.y*r.w + l.z*r.x - l.x*r.z;	ret.z = l.w*r.z + l.z*r.w + l.x*r.y - l.y*r.x;	return ret;}


I should be using the right matrix, as I'm always working in column major and that's what OpenGL uses too, right? I tried transposing it anyway and it didn't help any.

It *has* to be with a mixup of left and right-handed systems. In this NeHe tutorial the guy does something really weird to get the z-axis for translation:

(keeps track of total pitch and heading rotation and then does this to form the OpenGL matrix, a little different method than mine)
	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_qHeading * m_qPitch;	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);


What is going on with the z-axis? How do I make sure my calculations are working in a right-handed coordinate system?

Quote:I wouldn't advise this though, as without fully understanding the reason for the transpose you're likely to run into more trouble down the road.


Yeah, that's why I want to get it working this way. Like I said before, I got the system working by keeping track of the angles then rotating around the global axes, but this way should work as well, and I really want to understand why it's not.
Quote:1. column-major
2. column vectors I guess because it's column major?
3. OpenGL, so it's right-handed
4. I have no idea, how would I tell? by how I multiply the quaternions: here's the quaternion multiplication code:
Hm. I don't know for sure where the problem is, but I'll take a few more guesses.

First of all, if your matrix is column-major that doesn't necessarily mean you're using column vectors. However, since you're using OpenGL we can assume column-major matrices, column vectors, and right-handed coordinate system and rotations.

I didn't see where you extract the forward vector, but I gather you want -z to be forward? In that case (if everything else is correct) I think a positive angle will pitch you up, not down. I'm not sure if that sheds any light on the reverse rotation problem.

Another possible problem. I gather you want a view transform rather than an object transform, but it looks like you're sending the matrix to OpenGL as-is. I would think you'd want to transpose it first. If so, setting up the view transform would look something like this:

// Calculate matrix 'm' from your quaternion, then:

TransposeMatrix(m);
glLoadMatrixf(m);
glTranslatef(-camera.pos.x, -camera.pos.y, -camera.pos.z);

Anyway, it's hard to say for sure what the problem is without seeing all the code. But those are my best guesses at the moment.
Thanks for the help jyk. Maybe if I just start from the beginning it'll help make it clearer. I'm working in OpenGL, right-handed coordinate system, always column-major matrices.

Loading in an identity matrix simply points you down the -z axis. This is OpenGL's default. I think this is what's messing me up.

My camera starts with this- an identity quaternion (1.0f, 0.0f, 0.0f, 0.0f) which converts to an identity matrix and points you down the -z axis. Then, if you move the mouse up or down, it calls rotateXAxis(0.2f * mouseChange), and if it changes left or right, it calls rotateYAxis(0.2f*mouseChange). I posted these two functions in an above post.

And that's it- then it simply converts saved quaternion to a matrix and loads it in. I shouldn't have to transpose it since it's column major. I load it in as the first matrix before all other objects translate/rotate.

I think the only code that I haven't posted it the axis angle conversion. It's simple though. Here it is:
quaternion axisAngleToQuat(const float x, const float y, const float z, const float t) {	quaternion q;	q.x = x * sinf(t/2.0f);	q.y = y * sinf(t/2.0f);	q.z = z * sinf(t/2.0f);	q.w = cosf(t/2.0f);	return q;}


I just don't understand how this process isn't working. Create a new quaternion based on a small rotation around a local axis, and multiply it with the current quaternion. Do this with both x and y axes. Then translate to matrix and load it in. I know that there are a lot of orientation issues with this, but I've gone over it so many times it should work!

edit: I've debugged it a ton, but I can't exactly describe the problem. It looks like it might be trying to work in a left-handed system, but transposing the matrix doesn't help at all. Some of the axes are inverted, etc..
Hey,

I'm off to bed now but am interested in the problem and will look at it tomorrow. If you or someone else hasn't solved it by then, I'll try to post something useful :-)
I believe the problem is is here:
    glLoadMatrixf(quatToMatrix(quatView)); 


It appears from your code that quatView contains the orientation of your camera. The camera's orientation is not the same as the view matrix. In fact, the camera's orientation is the inverse of the view matrix. So, the line should be more like this:
    glLoadMatrixf(inverse(quatToMatrix(quatView))); 


One other thing. In the function quatToMatrix(), you allocate memory, but you never deallocate. You will have a major memory leak on your hands. I suggest you change the function to work like this:
    void quatToMatrix(const quaternion& q, float * mat) {	...	mat[0]  = 1 - 2 * ( yy + zz );	...    } 
And call it like this:
	...	float m[16];	quatToMatrix(quatView, m);	float axisX = m[0];	... 
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Edit: You replied while I was replying JohnBolton. Thanks for the help :) I'll have to look into the matrix inverse, I wonder if doing the quaternion conjugate does the same thing. And I made a note below about the memory leak, I knew someone was going to say that. My quatToMatrix code is different now and it doesn't allocate memory. I have all leaks dumped at the end, so I can see it :) Thanks!

Woo! It works. I am so glad that I understand how this all works now. Thank you all.

Quote:Original post by jyk
Hey,

I'm off to bed now but am interested in the problem and will look at it tomorrow. If you or someone else hasn't solved it by then, I'll try to post something useful :-)


Thanks man! You've been a great help. Made me think about every little detail about it. Hope we both learned something from this (I know I did!).

Two key things were wrong.

AP said this early on, wish I had paid more attention to it. Wasn't exactly sure what he meant then, but now I know:
" (yaw happens around world y-axis, pitch happens around local x-axis)."

Basically my functions look like this now. Notice the change in the y-axis one, change it around the global one. I was rotating it all weird by doing it around the local one.
void camera::rotateXAxis(float ang) {	float *m = quatToMatrix(quatView);	quatView = axisAngleToQuat(m[0], m[1], m[2], ang) * quatView;}void camera::rotateYAxis(float ang) {	quatView = axisAngleToQuat(0.0f, 1.0f, 0.0f, ang) * quatView;}void camera::rotateZAxis(float ang) {	float *m = quatToMatrix(quatView);	quatView = axisAngleToQuat(m[8], m[9], m[10], ang) * quatView;}


(a memory leak isn't created here in the real app because quatToMatrix is different)

The other thing, which was my main problem I believe, was that I needed to conjugate the quaternion. I'm guessing this "converts" it to work in the right-handed coordinate system. So my "view" code looks like this now:
void camera::view() {	//TODO: don't normalize every frame, do a check to see if floating point error is too big	quatView = normalize(quatView);	float *mat = quatToMatrix(conjugate(quatView));	glLoadMatrixf(mat);}


This works great. I haven't implemented translation yet but it shouldn't be too difficult. There may be a couple orientation problems when I start to translate too but I should be able to handle it now.
Quote:Original post by okonomiyaki
I'll have to look into the matrix inverse, I wonder if doing the quaternion conjugate does the same thing.

Yes, the quaternion conjugate is the same as the matrix inverse.

Quote:Original post by okonomiyaki
Notice the change in the y-axis one, change it around the global one. I was rotating it all weird by doing it around the local one.

You are setting yourself up for gimble lock. Imagine you tilt your camera up so it is facing in the direction of the world Y axis. How do you then rotate it around the world Z axis? You can't. You must do all the rotations in the local coordinate system to avoid gimbal lock.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!

This topic is closed to new replies.

Advertisement