Sign in to follow this  
okonomiyaki

trouble understanding quaternions

Recommended Posts

okonomiyaki    548
Quaternions are somewhat simple. I could use them right now in my program if I wanted to, but honestly I'm not sure how exactly it works. A quaternion is basically an extension of the common complex number (x,yi), correct? A common representation would be (w, (xi, yj, zk)). I can understand how quaternions are multiplied, etc, because it follows the common rules of complex number algebra. I guess I don't quite understand how they relate to the real-world 3d coordinate system. I suppose it's the imaginary part that I don't quite understand. I know what an imaginary number is (i*i = -1), but what implications does the imaginary part have on the quaternion? Could somebody explain "complex number space" to me, and what part the imaginary part plays? I found this conversion routine off of this article: -- If the axis of rotation is (ax, ay, az) and the angle is theta (radians) then the angle= 2 * acos(w) ax= x / scale ay= y / scale az= z / scale where scale = sqrt (x2 + y2 + z2) -- It seems to me that it's basically normalizing the (x,y,z) component as you would a regular 3d vector. What happened to the imaginary part? Don't you have to cancel it out in some way? Or is it that when you normalize a quaternion (by the normal q = q/ |q|) that it happens to "align" with 3d space? If so, what's the conceptual basis for this?

Share this post


Link to post
Share on other sites
JohnBolton    1372
It is very useful to think of a quaternion as an encoded axis-angle representation, but conversion from quat to axis-angle doesn't give you much insight. Here is how to convert from axis-angle (a,t) to quat (x,y,z,w):

q.x = a.x * sin(t/2)
q.y = a.y * sin(t/2)
q.x = a.z * sin(t/2)
q.w = cos(t/2)

Q = q.xi + q.yj + q.zk + q.w

As you can see, the imaginary components are all scaled by the same value, so when converting to axis-angle, simply normalizing them gives you the axis. You could also scale it by sqrt(1-q.w2), which is slightly faster.

BTW, rotations are represented by unit quaternions (|Q| == 1).

Share this post


Link to post
Share on other sites
okonomiyaki    548
Interesting. Thanks for the info. It's getting clearer. and yeah, I knew about the unit quaternion rule. Just trying to figure out why. I'm assuming it's something like homogenous coordinates, which really deals with 4d space until the 4th component is 1, in which it aligns with 3d space. It's probably something like that (some weird complex number space).

Anyway, I have a lot better grasp now after thinking about it more. I'm implementing a camera system to help me learn them.

One last question though. What's the best way to transform a 3d vector by a quaternion? Say I have a quaternion that represents my current orientation, and I want the x-axis of my local viewing orientation. You can extract the matrix from the quat, tranform (1,0,0) by it, but that's quite a lot of operations. Is there an easier way?

Thanks a lot for the help.

Share this post


Link to post
Share on other sites
Vern777    160
After reading this from mathworld:
http://mathworld.wolfram.com/EulerParameters.html

I guess using unit quaternion for rotation is related to this:

http://mathworld.wolfram.com/EulerParameters.html

Share this post


Link to post
Share on other sites
ajas95    767
Quote:
Original post by okonomiyaki
One last question though. What's the best way to transform a 3d vector by a quaternion? Say I have a quaternion that represents my current orientation, and I want the x-axis of my local viewing orientation. You can extract the matrix from the quat, tranform (1,0,0) by it, but that's quite a lot of operations. Is there an easier way?


If you just want the x-axis, you don't need to multiply by (1,0,0) once you've generated the matrix. The first column vector of the matrix is the x-axis, and generating the matrix is a pretty cheap operation.

Alternatively, you can treat the vector as a quaternion with a w-component of 0 and do a normal quat * quat transformation.

Personally, I would convert to matrix, then multiply. If there's one good thing about matrices, it's that their orientation is easy to visualize since the coordinate axes are right there. Nice little sanity check since quats are notoriously unintuitive to visualize.

Share this post


Link to post
Share on other sites
joanusdmentia    1060
Quote:
Original post by okonomiyaki
I knew about the unit quaternion rule. Just trying to figure out why.

Because constraining the quaternion so that it has a length of 1 prevents any scaling during the rotation. A unit quaternion can be thought of as a point on the surface of a unit sphere. This is exactly why quaternions are so useful, interpolating between them is interpolating between 2 points on the sphere, whereas interploation rotation matrices would be like drawing a straight like between the 2 points (thus cutting inside the sphere, and not producing the expected rotations).

Quote:
One last question though. What's the best way to transform a 3d vector by a quaternion? Say I have a quaternion that represents my current orientation, and I want the x-axis of my local viewing orientation. You can extract the matrix from the quat, tranform (1,0,0) by it, but that's quite a lot of operations. Is there an easier way?

You can apply the rotation directly to the vector like this:
v' = q*v*q-1
where v is the quaternion (w=1, x=vx, y=vy, z=vz)
and q is your rotation quaternion

However, this is useless if you want to concatonate the rotation with other transformations or send it to the gfx card, so when you're reading to apply the rotation to 3d points you'll normally convert it to a rotation matrix.


EDIT: Btw, quaternions are far wierder than i*i=-1

i*i = -1
j*j = -1
k*k = -1
i*j = k
j*i = -k
j*k = i
k*j = -i
k*i = j
i*k = -j

...or something like that [smile]

Share this post


Link to post
Share on other sites
Sneftel    1788
If you want to understand the math behind quaternions, I'd suggest looking at the 2D analogue: simple complex numbers. Simple complex numbers can be used to represent rotations in the 2D plane. Consider the case where you represent an angle θ as a complex number using the formulae

f(θ)=cos θ + isin θ
f-1(c)=atan2(cimag, creal)

Notice, BTW, that I'm cheating a bit with the second formula by using C functions. The full algorithm is a bit complicated. Don't worry about it, though.

Bingo: you end up representing an angle as a real part and a single imaginary component. If you want to graph this complex number, you might choose to split it into its two components, put the real component as the X axis and the imaginary component as the Y axis, and treat it as a 2D vector. When you're treating it like this, you'll notice that the complex numbers generated by feeding all angles into the above formula all fall on the unit circle.

Now think about combining rotations. First, notice that f(0) = 1. (Or, if you prefer, 1 + 0i.) And notice that any complex number multiplied by 1 is itself. That's the analogue of multiplying by the multiplicative identity quaternion. Also note that any integral multiple of 2π will produce the identity complex number, showing how the angles "wrap around".

Play around with this system a bit; in particular, multiplying arbitrary rotations by f(π/2) and f(π). It really has most of what quaternions do, in a more accessible arena.

Share this post


Link to post
Share on other sites
okonomiyaki    548
Quote:
Original post by ajas95
Personally, I would convert to matrix, then multiply. If there's one good thing about matrices, it's that their orientation is easy to visualize since the coordinate axes are right there. Nice little sanity check since quats are notoriously unintuitive to visualize.


Hm.. ok, that should be too hard. I'll just synch the matrix with the quaternion when I need to, and simply read from the matrix. That should be pretty fast.

Quote:

Because constraining the quaternion so that it has a length of 1 prevents any scaling during the rotation.


Ah, ok.. I got it.

Quote:

If you want to understand the math behind quaternions, I'd suggest looking at the 2D analogue: simple complex numbers. Simple complex numbers can be used to represent rotations in the 2D plane. Consider the case where you represent an angle θ as a complex number using the formulae


I was looking at simple complex numbers at first, but I had no idea you could represent rotations with simple complex numbers. Thanks, I googled about it and found some more resources. It's all.. very strange to visualize, but I guess you shouldn't try to visualize it. Seeing rotations with complex numbers helped a lot though.

Thanks guys

Share this post


Link to post
Share on other sites
okonomiyaki    548
Man, I hate to bump this, but this is killing me. I figured it'd be better to post here than to make a whole new thread...

Why doesn't this work? I've fiddled around with it in all ways possible.. I got the camera system working doing it a different way, but I like doing it this way and plus I just want to know what's wrong so that I can understand quaternions better.

Basically you call rotateXAxis and rotateYAxis to orientate the camera. In rendering it simply converts the quaternion to a matrix and loads it in OpenGL.

But these functions produce quirky results:


void camera::rotateXAxis(float ang) {
float *m = quatToMatrix(quatView);
float axisX = m[0];
float axisY = m[1];
float axisZ = m[2];

quatView = axisAngleToQuat(axisX, axisY, axisZ, ang) * quatView;
}

void camera::rotateYAxis(float ang) {
float *m = quatToMatrix(quatView);
float axisX = m[4];
float axisY = m[5];
float axisZ = m[6];

quatView = axisAngleToQuat(axisX, axisY, axisZ, ang) * quatView;
}




The matrix is column-major, so that should extract the local axes right? And then you simply rotate around it a little, and apply the rotation... seems simple enough. What's wrong?

I printf'd each axis when it rotates it. Say I start facing (0.0f, 0.0f, -1.0f). As I start looking down, the y-axis should start rotating downward towards the -z axis. However, the y-axis I get from the matrix is *positive* z, so it seems like it rotates the opposite way. I'm a little confused, can anyone clarify this? Thanks!

Edit: Also, I tried multiplying it in the different order, but it didn't help.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Can you post your quatToMatrix() code?

What I'm worried about is the statement:

float *m = quatToMatrix(quatView);

which implies you're creating a local matrix in quatToMatrix then returning a pointer to it... which is one of those "really bad" things.

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).

Share this post


Link to post
Share on other sites
jyk    2094
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...

Share this post


Link to post
Share on other sites
okonomiyaki    548
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.

Share this post


Link to post
Share on other sites
jyk    2094
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.

Share this post


Link to post
Share on other sites
okonomiyaki    548
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.

Share this post


Link to post
Share on other sites
jyk    2094
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.

Share this post


Link to post
Share on other sites
okonomiyaki    548
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..

Share this post


Link to post
Share on other sites
jyk    2094
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 :-)

Share this post


Link to post
Share on other sites
JohnBolton    1372
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];
...

Share this post


Link to post
Share on other sites
okonomiyaki    548
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.

Share this post


Link to post
Share on other sites
JohnBolton    1372
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.

Share this post


Link to post
Share on other sites
okonomiyaki    548
Quote:
Original post by JohnBolton
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.


You're right, but I hate the fact that if I do it around the local Y-axis, the camera has the tendency to tilt the scene. Say you look up, and then you immediately look right. The scene is now tilted as if you had tilted the "up" vector. You can fix this a couple of ways, but the easiest thing for me is to just use the global axis and limit how far you can look up/down.

Fixing gimbal lock wasn't the primary purpose for this either- I just wanted to learn more about quaternions. If I have problems with it in the future and I can easily switch it to use the local axis :)

Thanks again, if anyone wants me to post the full final code just tell me!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this