Archived

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

rileyriley

Gimbal lock sucks. Euler tricks or Quaternions?

Recommended Posts

I''m trying to rotate a little thingy around in 3d space, using Euler angles, and of course run into gimbal lock. Now for about the last week I''ve been perusing articles about quaternions, and have a question for the forum - if I''m planning on writing a pretty large scale game with objects that need to be rotated around every axis you can think of, is it worth it to create a quaternion engine, or should I struggle through the hoops of making the Euler angles work correctly? Thanks for any feedback - - riley

Share this post


Link to post
Share on other sites
Quaternions are great - but have one major downfall: they''re completely impossible to visualise.

When the code works, using quaternions is a breeze, but when your trying to track down bugs it''s very difficult to get a feel for what''s happening.

Also, much of the documentation that I''ve downloaded tends to be either plain wrong or right-but-with-so-many-preconditions as to be less than useless.

Share this post


Link to post
Share on other sites
Yes, that is what I''m having the biggest problem with .. they seem to be ENTIRELY unintuitive.

Anyway, I''ve decided to take a shot at using them. I have all of the basic operations (multiplying, etc) worked into a neat little class - but I have no idea how to use the class! I don''t understand how to use quaternions to, say, rotate an object 40 degrees around the x axis and 50 around the y axis.

I have an idea that all I have to do is:

Quaternion q1(40, Vector(1, 0, 0));
Quaternion q2(50, Vector(0, 1, 0));
q1.normalize();
q2.normalize();

Quaternion q3 = q1*q2;
glRotated(q3.w, q3.v.x, q3.v.y, q3.v.z);

but that doesn''t seem to be working, and I have no idea if that''s because my Quaternion operations are wrong or because that is not, in fact, how you even use Quaternions at all. Does anyone know of any resources that give actual examples of using quaternions instead of just a lot of the same basic theory over and over again?

Thanks -
- riley

Share this post


Link to post
Share on other sites
i suggest you look at several sources to make sure that you''re using the right quaternion functions. i remember i had run into several different versions, and i had to spend a while flipping signs around to get it working. the closest "intuitive" feel you can get to quaternions is the axis-angle relationship. if you look at the conversion between quaternion and axis-angle representations, you''ll see that it is a straightforward trigonometric function. so, i haven''t tried this yet, but if you did an axis angle representation first, then perhaps the quaternion conversion will not be so hard.

a2k

Share this post


Link to post
Share on other sites
The way I tested my quaternion class was to implement rotation based on a vector (which it looks like you''ve done) then convert it back to a matrix. I then use multiply the current modelview matrix by the quaternion matrix. I could comment out that code and use my matrix class instead, as soon as both produced the same output, I knew I did it correctly.

[Resist Windows XP''s Invasive Production Activation Technology!]

Share this post


Link to post
Share on other sites
Alright, thanks for the suggestion. I''m at work now so I can''t test anything (arggghhagh) but I''ll do that as soon as I get home.

My only fear is that the two methods _won''t_ match up, and that I won''t have any idea what to change to get the quaternions working properly, you know what I mean? Right now I''m just blindly hacking away, calling multiplications and whatnot willy nilly without any real Idea of what''s going on. Does anyone know of any resources that really explain the entire process of representing a rotation and then _applying_ it, say with the vector/angle notation used by OpenGL?

Thanks -
- riley

Share this post


Link to post
Share on other sites
quote:
Original post by rileyriley
Quaternion q3 = q1*q2;
glRotated(q3.w, q3.v.x, q3.v.y, q3.v.z);

but that doesn''t seem to be working, and I have no idea if that''s because my Quaternion operations are wrong or because that is not, in fact, how you even use Quaternions at all.


I would say that''s the problem. q3.w is a value that is the cosine of half the rotation angle. OpenGL expects a rotation angle as degrees. So you''d first have to convert the cosine into the actual angle. Then double the result and convert it degrees. Sort of like this:

  
#define PI 3.1415926535897932384626433832795
double aRadians = acos (q3.w);
double aDegrees = aRadians * PI / 90.0f;


(If you''re wondering about the division by 90, it''s just a division by 180 and multiplying by 2, of course).

Share this post


Link to post
Share on other sites
bleh.. I've been messing with this for the last couple of hours, and I just can't see what I'm doing wrong.

    			Vector p = game->players[i]->getRotation();

Quaternion q1(p.x, Vector(1, 0, 0));
Quaternion q2(p.y, Vector(0, 1, 0));

q1.normalize();
q2.normalize();

Quaternion q3 = q1*q2;

q3.normalize();
q3.applyGLRotation();


That's the main drawing section. The vector p holds Euler angles for the player's rotation. The other functions are defined below:

  Quaternion::Quaternion(double degrees, const Vector & nv)
{
w = cos(degrees*DEG_TO_RAD/2.0);
v = nv;
}
  const void Quaternion::normalize()
{
double n = sqrt(w*w + v.x*v.x + v.y*v.y + v.z*v.z);
w /= n;
v.x /= n;
v.y /= n;
v.z /= n;
}
  const Quaternion Quaternion::operator *(const Quaternion & q) const
{
Quaternion temp;
temp.w = w*q.w - v.dot(q.v);
temp.v = v.cross(q.v) + q.v*w + v*q.w;
return temp;
}
  const void Quaternion::applyGLRotation() const
{
double degree = acos(w) * RAD_TO_DEG_TIMES_TWO;
//if (degree

glRotated(degree, v.x, v.y, v.z);
}


These calculations do not result in anything even resembling a correct rotation. Even when I forego all but one rotation, and just call q1.applyGLRotation(), the rotation around the x axis is incorrect. Can anyone spot what I'm doing wrong? I feel awful about this whole thing - this is the most blindly I've ever used anything, and I have no idea what the crap I'm doing.

Thanks for any feedback -
- riley

edit - fixed some source tags
edit - ^ more

Edited by - rileyriley on November 7, 2001 1:20:53 PM

Share this post


Link to post
Share on other sites
Furthermore, it seems to me that even if the above did work correctly, it wouldn''t solve the problem of Gimbal lock. Am I mistaken about that or am I missing some very major concept about quaternions?

Thanks -
- riley

Share this post


Link to post
Share on other sites
What exactly goes wrong. Does it rotate by the incorrect amount? Does it rotate around the wrong axis?

If you set the GL_MODELVIEW matrix to identity and then use your quaternion function to rotate the mesh over 90 degrees around the X axis, what happens?

Edited by - Kippesoep on November 8, 2001 10:35:55 AM

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I believe your constructor is missing part of the quarterion definition. Unfortunately I''m at work and don''t have any of my books on the subject but a quarterion is:
q=(cos(theta/2), sin(theta/2)*(x,y,z))

it looks like you left out the sin(theta/2) in your calculations

Share this post


Link to post
Share on other sites
Hmm.. thanks Anonymous Poster, it turns out I _SHOULD_ include the entire definition of things that I want to work. The results are now much better. I can see that the gimbal lock problem has disappeared.

However, there is still a problem. As I increase the rotation, it starts to oscillate back and forth. I can increase it to 45 degrees before the direction of rotation actually changes, but there''s a smooth slowdown before it gets there - it doesn''t just bounce back at 45 degrees. Then it gets to 135 degrees (which looks like -45 degrees on the screen, after the reversal) and the process repeats.

I''m guessing this is some sort of artifact to do with acos and asin returning unwanted values for some degrees, and that it could perhaps be solved by checking the initial value of the degree and changing the return value of acos and asin accordingly. Does this sound likely to anyone? I''d ordinarily plug it in right away but I have to go to class (arghh, you and your "education.")

Thanks a lot -
- riley

Share this post


Link to post
Share on other sites
Here''s some of my Quaternion code, looks like you''re omitting some stuff :

  
inline void CQuaternion::FromAxisAngle(float in_angle, C3DVector& in_axis)
{
float sin_a = sin(in_angle / 2.0f);
float cos_a = cos(in_angle / 2.0f);

if(in_axis.GetSquaredMagnitude() != 1)
{
in_axis.Normalize();
}

v[0] = in_axis[0] * sin_a;
v[1] = in_axis[1] * sin_a;
v[2] = in_axis[2] * sin_a;
v[3] = cos_a;

Normalize();
}

inline void CQuaternion::ToAxisAngle(float& in_Angle, C3DVector& in_Axis)
{
Normalize();

float cos_angle = v[3];
in_Angle = acos( cos_angle ) * 2;
float sin_angle = sqrt( 1.0 - cos_angle * cos_angle );


if ( fabs( sin_angle ) < 0.0005 )
{
sin_angle = 1;
}

in_Axis(0) = v[0] / sin_angle;
in_Axis(1) = v[1] / sin_angle;
in_Axis(2) = v[2] / sin_angle;
}



Orb

Share this post


Link to post
Share on other sites
Sorry I''ve left this thread idle for so long..

Anyway, I''m still having a lot of trouble with quaternions. I realized that what I actually need is to be able to convert spherical coordinates to quaternions, not axis/angle coordinates. Here is what I have so far:
  Quaternion::Quaternion(double pitch, double yaw, double roll)
//spherical coordinates

{
double sin_r = sin(roll * DEG_TO_RAD / 2.0);
double cos_r = cos(roll * DEG_TO_RAD / 2.0);

double sin_p = sin(pitch * DEG_TO_RAD);
double cos_p = cos(pitch * DEG_TO_RAD);

double sin_y = sin(yaw * DEG_TO_RAD);
double cos_y = cos(yaw * DEG_TO_RAD);

v.x = sin_r * cos_p * sin_y;
v.y = sin_r * sin_p;
v.z = sin_r * sin_p * cos_y;
w = cos_r;

normalize();
}

This is copied pretty much directly from the link that Null and Void provided. Now, OpenGL''s glRotate takes axis/angle coordinates, so I need to convert this quaternion to axis/angle:
  const void Quaternion::applyGLRotation() const
{
double cos_a = w;
double angle = acos(cos_a) * RAD_TO_DEG_TIMES_TWO;
double sin_a = sqrt(1.0 - cos_a * cos_a);

if (fabs(sin_a) < 0.0005)
sin_a = 1;

glRotated(angle, v.x / sin_a, v.y / sin_a, v.z / sin_a);
}

This is also copied pretty much directly from that link.

Now, each frame, when I want to draw the object in question:
  			Vector p = game->players[i]->getRotation();

Quaternion q1(p.x, p.y, p.z);
q1.applyGLRotation();
drawObject();

This doesn''t give anything like correct results. It''s just screwy. First of all, the roll affects both the pitch & yaw factors, which isn''t right. Also, pitch and yaw aren''t independent of eachother, which they should be. The whole thing is just wrong, and from the definitions I''ve seen on the internet, and the conversions to and from different coordinate storage formats (e.g. axis/angle), I don''t see how quaternions ever work at all. Does anyone know of actual working sample code that I could look at? Or some reference to an explanation of how to use Quaternions for the specific task of rotation? I am unbelievable frustrated with the whole thing and am starting to think that it would have been better to just calculate rotations relative to the object''s local coordinate system to avoid gimbal lock.

Thanks for any help -
- Riley

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Diana Gruber wrote a good article on camera, search for it on the internet. You can use matrices for rotations without getting gimbal lock. The gimbal lock occurs because you''re multiplying three matrices into one matrix and some terms cancel out leaving out one dimension. To avoid it you can multiply your vector by the first matrix then multiply the result by the second matrix then new result by third matrix to. Or you could skip an angle when going from 89 to 91 when concatenating three matrices into one. What I do is go into the camera matrix, grab each of the axis and move them a little bit. That way they stay orthogonal to each other. Then I dot product camera''s position with the new right, up, and look vectors. Here''s a little snippet:

  
// Pitch up

if(R_KEY == Key)
{
D3DXMatrixRotationAxis(&m_Pitch, &m_Right, m_fPitchAngle * fTime);
D3DXVec3TransformNormal(&m_Look, &m_Look, &m_Pitch);
D3DXVec3TransformNormal(&m_Up, &m_Up, &m_Pitch);

m_View(0, 1) = m_Up.x;
m_View(1, 1) = m_Up.y;
m_View(2, 1) = m_Up.z;

m_View(0, 2) = m_Look.x;
m_View(1, 2) = m_Look.y;
m_View(2, 2) = m_Look.z;

m_View(3, 0) = -D3DXVec3Dot(&m_Position, &m_Right);
m_View(3, 1) = -D3DXVec3Dot(&m_Position, &m_Up);
m_View(3, 2) = -D3DXVec3Dot(&m_Position, &m_Look);

g_3DGfx.SetGfxApiViewTransform((D3DMATRIX&)m_View);
goto END;
}


The m_fPitchAngle is constant and time is how long it took to render the frame. Actually, the time is how long the cpu took to go through my geometry since gpu just returns as soon as you issue commands to it. So, if you''re on a fast computer each camera vector is moved little bit but on slow computer it''s moved in larger swings, thus you complete travel in same amount of time on both computers. Anyways, the important thing here is that look, up and right camera vectors in the camera matrix stay perpendicular to each other each time you rotate/move camera. Then you take your geometry and multiply it by this camera matrix. The last row is negative because instead of moving camera you move the geometry. Hope this helps

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
I know what you’re trying to achieve. If you push forward on a joystick you want your spacecraft to rotate about its own local x-axis regardless of direction that axis is pointing in world space. If you push left you want it to rotate around its own local z-axis, again, regardless of the direction that axis is pointing in world space, and the same again for the y-axis.

Am I right? I hope so because I’m going to, hopefully, show you how to do it. I’m afraid you’ll need to utilise DirectX as it has some very good helper functions for use with quaternions.

Store the orientation of any objects in your scene (including your camera) as quaternions instead of roll, pitch, and heading, as it’s not clear to me how roll and heading relate to each other when pitch is +/- 90 degrees. Here’s a couple of functions that probably haven''t been tabulated correctly:


D3DXVECTOR3 * Maths::TransformVector(D3DXQUATERNION *pOrientation, D3DXVECTOR3 *pAxis)
{
D3DVECTOR AxisVector;
D3DXMATRIX Matrix;

D3DXMatrixRotationQuaternion(&Matrix, pOrientation); // Build a matrix from the quaternion.

AxisVector.x=pAxis->x*Matrix._11+pAxis->y*Matrix._21+pAxis->z*Matrix._31+Matrix._41; // Transform the queried axis vector by the matrix.
AxisVector.y=pAxis->x*Matrix._12+pAxis->y*Matrix._22+pAxis->z*Matrix._32+Matrix._42;
AxisVector.z=pAxis->x*Matrix._13+pAxis->y*Matrix._23+pAxis->z*Matrix._33+Matrix._43;

memcpy(pAxis, &AxisVector, sizeof(AxisVector)); // Copy axis.

return(pAxis);
}


bool Maths::RotateXAxis(D3DXQUATERNION *pOrientation, float Angle)
{
bool Success=false;

if(pOrientation)
{
Success=true;
D3DXQUATERNION Rotation;

D3DXQuaternionRotationAxis(&Rotation, TransformVector(pOrientation, &D3DXVECTOR3(1.0f, 0.0f, 0.0f)), D3DXToRadian(Angle));
*pOrientation*=Rotation;
}

return(Success);
}


So, if the player pushes forward on the joystick, you call RotateXAxis(), passing the orientation and the angle of rotation for that frame, which will probably be something very small such as half a degree. The function will “rotate” the quaternion you passed along the x-axis that has been transformed to local coordinates. Making the function work for other axes is a simple matter of altering the D3DXVECTOR3(1.0f, 0.0f, 0.0f) bit of the code.

When you are ready to draw your object, ask DirectX to build a matrix for the quaternion using the D3DXMatrixRotationQuaternion() function.

Don’t forget to initialise the quaternion to valid orientation to start with (probably straight and level). If my memory serves me correctly, it won’t work if you don’t. There’s a function in DX to do this but I can’t remember what it is.

However, this method does not work if you want rotational velocities i.e. a spacecraft freely rotating in space on one or more axes not under any power. This is beyond my mathematical ability, as things such as gyroscopic effects have to be taken into account. The method I have shown is only suitable for “powered” rotations, if you know what I mean.

Hope I’ve helped.

Justin.

P.S. Don’t worry - even NASA had problems with gimbal-lock in the days of Apollo.

Share this post


Link to post
Share on other sites
Thanks for your replies. I will look into quaternions in DX, but I thought that they were only supported in retained mode. Also, I was really trying to implement the quaternions myself, though at this point I''ve all but given up (*gasp*) on that. I''ll also look up that article on getting around using quaternions.

Thanks -
- Riley

Share this post


Link to post
Share on other sites
i would suggest not to give up. more power to you once you solve it and finally understand why. i''ve dealt with this problem for well over a month, and it was quite rewarding in the end. and it sounds like you''ve almost got it.

a2k

Share this post


Link to post
Share on other sites
I''m coming very near the month mark ;x

Thanks for the encouragement, though. I actually got reinterested in it on another little scrap of information.. it is just frustrating because I don''t understand the math, and sources sometimes say different things, and sometimes it looks like they say different things but they are actually mathematically equivalent after 14 regrouping steps, or something. I am finally finding some resources on _how to use_ quaternions, as opposed to just on what they _are.._ I''ll let the forum know if (WHEN) I am victorious ;p

Share this post


Link to post
Share on other sites
i can also tell you of another i experience i remember of quaternions. remember the time step you''re using when you rotate. since i was still a beginner back then, i operated the quaternions from the last transformed point, which made all the points spin outta control and off the screen, instead of from a fixed set of vertices. what i mean is, it''s good just to have a set of fixed vertices to reference, and then transform these points to another set of data, and keeping the original vertices intact. that way, the transformation you''d be doing is global, not incremental. in this way, you would be incrementing the transformation, but not the actual vertices.

a2k

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster
Retained mode doesn''t exist in DX8. It''s not missed.


LOL, oh yes.


Superpig
- saving pigs from untimely fates
- sleeps in a ham-mock at www.thebinaryrefinery.cjb.net

Share this post


Link to post
Share on other sites