Archived

This topic is now archived and is closed to further replies.

RandomAxess

Quaternion over-rotation problem?

Recommended Posts

so i''ve been following some tutorials trying to make a quaternion-based system. I''m testing it out on my camera, but i''m getting a weird problem. I set the ''a'' and ''d'' keys to be my rotate "left" and "right" respectively, each rotating around the camera vUp 5.0 degrees or -5.0 degrees respectively. What i do is, i initially have the quaternion to be the unit quaternion (or as i do it, the vUp axis rotated by 0.0 radians). Then, i take the axis and angle i want to rotate by, turn that into a quaternion using the following formula: angle = angle * (M_PI / 180.0); Quaternion result; result.a = cosf(angle / 2.0); result.v[0] = vector[0] * sinf(angle / 2.0); result.v[1] = vector[1] * sinf(angle / 2.0); result.v[2] = vector[2] * sinf(angle / 2.0); then, take the current quaternion, current = result * current; and then construct a matrix out of this. the reason I want to construct the matrix is that eventually i want to use SLERP Anyway, when i hit the keys, they do infact rotate the camera, but on consecutive hits, it either over-rotates or rotates the wrong way in respect to rhe key (aka i hit ''d'' and it rotates left). help is very much appreciated

Share this post


Link to post
Share on other sites
What does your quaterion operator * do?
Why do you need a matrix to slerp?

I''ve found the following code in various places around the web, and modified it slightly such that the results match the D3DX functions. You could optimize the generation of return values to get a slight speed boost when using the MSVC.NET 7.1 compiler.


EQuat operator *(const EQuat &q)
{
EQuat ret;
EFloat A, B, C, D, E, F, G, H;

A = (q.w + q.x)*(w + x);
B = (q.z - q.y)*(y - z);
C = (q.w - q.x)*(y + z);
D = (q.y + q.z)*(w - x);
E = (q.x + q.z)*(x + y);
F = (q.x - q.z)*(x - y);
G = (q.w + q.y)*(w - z);
H = (q.w - q.y)*(w + z);

ret.w = B + (-E - F + G + H) / 2.0f;
ret.x = A - (E + F + G + H) / 2.0f;
ret.y = C + (E - F + G - H) / 2.0f;
ret.z = D + (E - F - G + H) / 2.0f;
return ret;
}


EQuat SLerp(EQuat &q, EFloat ratio)
{
EQuat ret;
EFloat omega, cosom, sinom, scale0, scale1;

// calc cosine

cosom = x * q.x + y * q.y + z * q.z + w * q.w;

// adjust signs (if necessary)

if ( cosom < 0.0 )
{
cosom = -cosom;
scale1 = -1.0f;
}
else
{
scale1 = 1.0f;
}

if ( (1.0 - cosom) > 0.0001f/*DELTA*/ ) // Must define delta.

{
// standard case (slerp)

omega = EACOS(cosom);
sinom = ESIN(omega);
scale0 = ESIN((1.0f - ratio) * omega) / sinom;
scale1 *= ESIN(ratio * omega) / sinom;
}
else
{
// "from" and "to" quaternions are very close

// ... so we can do a linear interpolation

scale0 = 1.0f - ratio;
scale1 *= ratio;
}

// calculate final values

ret.x = (EFloat) (scale0 * x + scale1 * q.x);
ret.y = (EFloat) (scale0 * y + scale1 * q.y);
ret.z = (EFloat) (scale0 * z + scale1 * q.z);
ret.w = (EFloat) (scale0 * w + scale1 * q.w);

return ret;
}

Share this post


Link to post
Share on other sites
Again, you don''t need a matrix to use SLERP - that''s one of the points of using quaternions.

What do you mean by ''over-rotate''? Do you mean it rotates too far?

Also, make sure you''re multiplying in the right order, i.e. current * result vs. result * current. Which one is right depends on how you have your quat mult function set up.

The problem could also be happening at the stage where you convert from your quaternion to a matrix.

Also, remember that you need to transpose the matrix to get your view matrix. If you forget to do this, your camera will probably rotate in the wrong direction.

Share this post


Link to post
Share on other sites
the quaternion is of this form:

float a (the "real" value)
Vec3f v (the 3 values cooresponding to i,j,k)

General answers to questions you guys raised:
I don''t rotate along the wrong axis, so i know the matrix is set in the right direction.
jyk:
I will try not using the matrix-form instead.

In any case, I used the tutorials under Quaternions from gamedev, so i''ll copy what they have, which is essentially exactly what i do:
quat1 * quat2
where quat1 = {w1, x1, y1, z1}
and quat2 = {w2, x2, y2, z2}

w=w1w2 - x1x2 - y1y2 - z1z2
x = w1x2 + x1w2 + y1z2 - z1y2
y = w1y2 + y1w2 + z1x2 - x1z2
z = w1z2 + z1w2 + x1y2 - y1x2

Share this post


Link to post
Share on other sites
neglected to answer one question:
When i say over-rotate, i mean, lets say i need to go 5 degrees to the left, it will go 5 degrees initially, but the next time i do it, it will go, say, 7 degrees. also, when i hit the button to go right (AKA -5 degrees) it will sometimes keep going to the left a few clicks, then start going to the right, but not at equal amounts.

Share this post


Link to post
Share on other sites
OK, so i removed the matrix, which was the problem, and now turning left and right works just fine.

However, looking up and down go slightly diagonally up or down, thus if i click up and down simultaniously a couple times, it looks like its moving on a jagged line.

the way i do my rotations is this:
unitize(rot_axis);
Quaternion rotation;
rotation = axis_to_quat(angle, rot_axis);
unitize_quat(rotation);

Quaternion vector;
vector.a = 0.0;
vector.v = direction; //direction is the camera "foreward"

vector = multiply_quat(multiply_quat(rotation, vector), conjugate(rotation));
direction = vector.v;


vector.v = up; //up is the camera up vector
vector = multiply_quat(multiply_quat(rotation, vector), conjugate(rotation));
up = vector.v;

Share this post


Link to post
Share on other sites
Hm. I''ve never rotated a vector directly with a quat before (I use the matrix form), but it looks like you''re doing it correctly. When you rotate up and down, what are you using for the rotation axis? The cross product of the direction and up vectors?

You also may need to normalize your direction and up vectors occasionally, although I doubt that''s your problem...

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
You are mixing up your representation of the camera orientation. There is no need to store a forward and up vector. All you need to store is a single quaternion. The quaternion contains all the information about rotation about all three axes.

A simple way to generate the forward and up vectors you really want would be to just choose a starting forward vector (maybe (0,0,-1)) and a starting up vector (possibly (0,1,0)). These vectors would never actually be altered. You would use the quaternion on those vectors to get the current forward and up vectors. The quaternion is the only thing that needs to be updated. Also this quaternion is what would be stored between frames.

The code could look something like this:

read input
pick rotation to apply based on user input
current = rotation * current

forward = rotation * (0,0,-1) * conj(rotation)
up = rotation * (0,0,-1) * conj(rotation)


Actually now that I look at it this looks similiar to what you had before. But maybe if this works then it means the bug was in how you were building the matrix or something? Really building the matrix directly is just an alternative to generating the forward and up vectors and having opengl generate the matrix for you...

Share this post


Link to post
Share on other sites
hrm, interesting, i will try implementing it that way anonymous.

Also, what you have is actually different than mine...in mine, i change the foreward and up vectors, in yours they are constant.

my question is, with this "constant" quaternion, what exactly do i do with that? in your code, you don''t do anything with it. Or, instead of

current = rotation * current

forward = rotation * (0,0,-1) * conj(rotation)

did you mean

forward = current * (0,0,-1) * conj(current)

Share this post


Link to post
Share on other sites
I''m pretty sure he meant to use current (correct me if I''m wrong, anon).

The only tricky part I see is "pick rotation to apply based on user input." In order to construct this quaternion, you''ll need to already know your forward, up, and side vectors (they will be the axes you rotate around for roll, yaw, and pitch, respectively). So you may have to calculate them twice...or something :-|

In any case, it''s probably better to do it as anon has said, that is, start from scratch with (0, 0, 1), etc. each frame rather than doing it cumulatively. This will help you avoid precision errors.

Share this post


Link to post
Share on other sites
ok, i''ve re-tried what you have suggested Annon. I''m still having the exact same problem tho, I rotate in a diagonal up-right or diagonal down-right when i hit up or down (respectively). The right and left still work fine, however.

This is my code.
iUp = initial Up vector
iDir = initial "foreward" vector.

direction and up are the vectors used by glLookAt.

Rotate(TVec3& rot_axis, float angle)
{
cout << rot_axis << endl;
unitize(rot_axis);

//update the current quaternion
Quaternion axis;
axis = axis_to_quat(angle, rot_axis);
unitize_quat(axis);
current = multiply_quat(axis, current);

direction = rotate_vector(iDir, current);
up = rotate_vector(iUp, current);

}

axis_to_quat is the following:

Quaternion axis_to_quat(float angle, Vec3f euler)
{
//first, convert to radians.
angle = angle * (M_PI / 180.0);

//second, declare a resulting quaternion
Quaternion result;

//thrid, do the appropriate math to generate the quaternion
result.a = cos(angle / 2.0);
result.v[0] = euler[0] * sin(angle / 2.0);
result.v[1] = euler[1] * sin(angle / 2.0);
result.v[2] = euler[2] * sin(angle / 2.0);

return result;
}

multiply_quat:

Quaternion multiply_quat(Quaternion& q1, Quaternion& q2)
{
//create our new quaternion
Quaternion q3;
//set the a value
//note that this onLY results if it is a UNIT
//quaternion

q3.v[0] = (q1.a * q2.v[0]) + (q1.v[0] * q2.a) + (q1.v[1] * q2.v[2]) - (q1.v[2] * q1.v[1]);
q3.v[1] = (q1.a * q2.v[1]) - (q1.v[0] * q2.v[2]) + (q1.v[1] * q2.a) + (q1.v[2] * q2.v[0]);
q3.v[2] = (q1.a * q2.v[2]) + (q1.v[0] * q2.v[1]) - (q1.v[1] * q2.v[0]) + (q1.v[2] * q2.a);
q3.a = (q1.a * q2.a) - (q1.v[0] * q2.v[0]) - (q1.v[1] * q2.v[1]) - (q1.v[2] * q2.v[2]);

//now, return that quaternion
return q3;
}

and finally rotate_vector:
Vec3f rotate_vector(Vec3f& vector, Quaternion& quat)
{
//create a temporary vector-based quaternion
Quaternion v;
v.a = 0.0;
v.v = vector;
//now, do this process: quat * v * quat''
v = multiply_quat(multiply_quat(quat, v), conjugate(quat));

//return the v value
return v.v;
}

in case you are wondering, the TVec3 is basically the equivalent to Vec3f, it casts into Vec3f and does everything the same, it is used by the utility kit i learnt graphics on (namely MxGUI which uses FLTK).

Share this post


Link to post
Share on other sites
Unless I missed it, you''re not showing us where you get the appropriate axes for yaw, pitch and roll. Perhaps you can show us the code where you read the keyboard and call the rotate function.

Share this post


Link to post
Share on other sites
the axis i''m rotating about is gotten from a key press. So lets say i look left:

if(key == ''a'') //rotate left
{
cam.Rotate(cam.up, 5.0);
canvas->redraw(); //redraw the canvas
}

i''m not including the keypress function because its really not that important.
What the above does is if i click ''a'', then i''m rotating 5 degrees about the camera''s up vector. The redraw just updates the window.

If i wanted to look up, i rotate around the cross product of up and foreward vector:

else if(key == ''w'') //rotate up
{
cam.Rotate(cross(cam.direction, cam.up), 5.0);
canvas->redraw(); //redraw the canvas
}

where cam.direction is the camera''s foreward direction vector.

Share this post


Link to post
Share on other sites
Hmm. The only thing I can think of is that as you are rotating your direction and up vectors incrementally, they could be drifting away from the ''true'' forward and up vectors as defined by your quaternion, which could produce unpredictable results. But I''m not sure how this would cause the exact symptom you''re describing.

Stupid question, but are you sure the ''rotate right'' code block isn''t getting triggered also when you look up and down?

When you rotate up, do you rotate at a 45 degree angle, i.e. do you rotate right at the same rate you rotate up?

In any case, you might look into how to extract a matrix from a quaternion. This way, you could get your forward, up and side vectors directly from your quaternion each frame, and they would be guaranteed to be accurate. I''m not sure if this would fix your problem, but it might be a good idea.

Share this post


Link to post
Share on other sites
well, i commented out the direction and up updates one at a time just to see what each on does. When i only update the direction vector, it moves up and to the right. When i only update the up vector, it "spins" along the direction vector...

My thinking is there is something wrong with my math? But what i don''t understand is why it works fine when rotating around the up vector ( right and left initially set to (0,0,1)).

Share this post


Link to post
Share on other sites
Ah, finally! a conclusion!
Thanks to everyone for their continued support in trying to solve the problems i was encountering.

As it turns out, i went back, re-examined the math and the code, and I used a multiplication function similar to namethatnobodyelsetook showed. I''ll be looking into setting it into a matrix as well, just incase it ever needs to be used in the future, but for now i''m quite happy.

As for the current working implementation, i get the axis i wish to rotate about and the angle. i then construct the quaternion out of it, take the vUP and vForeward vectors and do:
v'' = q * v * q''

it works perfectly, and having thouroughly tested against the values from doing a matrix-based rotation, it is an exact match.

Once again, thank you everyone for their help and keeping up with this thread.

Share this post


Link to post
Share on other sites