Quaternions but still gimbal lock?

Started by
5 comments, last by b34r 19 years, 8 months ago
Ok, I changed my character animation system to use quaternions instead of Euler angles/matrices directly, but I still have exact same gimbal lock problem (ie. certain body parts rotate around wrong axes, while others work just fine). Here's the code:

// --- FOR EACH BONE ---
// seqtime is how long the animation sequence has lasted, in seconds.
// prevKey and curKey are the rotation keys (Euler angles) between which we are currently interpolating.
// bones->rotation quaternion contains bone's reference rotation relative to it's parent.
// bones->translation -- a matrix containing bone's translation relative to it's parent.

Quaternion q0, q1, qInt;
Matrix     A;

fraction = (seqtime - prevKey[TIME])/(curKey[TIME] - prevKey[TIME]);

q0.FromEuler(prevKey[YAW], prevKey[PITCH], prevKey[ROLL]);
q1.FromEuler(curKey[YAW], curKey[PITCH], curKey[ROLL]);

qInt = Quaternion::Slerp(q0, q1, fraction);
qBoneAnim = bones->rotation * qInt;

// Construct final animation matrix for this bone
qBoneAnim.ToMatrix(A);

if(bones->parent == -1)
  animMatrices.Identity();
else
  animMatrices = animMatrices[bones->parent];

animMatrices.MultiplyMatrix(bones->translation);
animMatrices.MultiplyMatrix(A);



It seems that if you construct quaternion from Euler angles, it's still prone to gimbal lock. How then should I do the transformations, if not this way? As quats can't represent translation, I'm forced to convert them to matrices here and there.
Advertisement
qBoneAnim = bones->rotation * qInt;

I dont see what this line is here for. Once you SLERP between the previous frame of animation and the current one then you have your rotations and should convert it to a matrix then. Ofcourse this might be due to your animation file (I use Milkshape animations) so might not be a problem.

Also are you Interpolating the translation between the previous frame of animation and this one? (I assume you just havent shown tha thappening).

If thats not your problem then I cant see anything wrong with the code you have posted.

If possible you could try using the DirectX maths helper functions to make sure your maths work is correct.

If you are still stuck and you think it might help I can email you some working animation code and you can see where you are going wrong if you want.
the problem is that at the root you are still just using euler angles. if on each frame you take eulers and convert them to quaternions you'll still get gimbal lock. it makes sense. you're essentially just doing a lot of extra complicate math to end up with a euler representation of your object's orientation.

the fix is to not use eulers _at all_ for the current rotational state of your model. you will only store the delta eulers in the animation. convert the deltas to quaternions and post multiply them into your object's rotational quaternions and you should be fine. so from your code, i believe, you will need to get rid of the curKey variable and replace it with a member variable in the bone object/struct that will persist the bones rotation between frames. then you'll need to do some quick math to calculate the delta rotation that this frame represents.

-me
Drazgal: I don't support translation keyframes for bones because there's no need to use them in character animation (except bone reference pose translation).

Palidine:
As a matter of a fact, I have also tried storing the current rotation state as quaternion rather than Euler angles. To get yaw, pitch and roll deltas per frame, I subtract current fraction value from previous frame's fraction, construct the delta quaternion, and post-multiply current rotation by the delta. Is this what you mean? The problem is that bones dont follow their keyframes, they just spin around uncontrollably. Here's the code:

for each bone i{  fraction = (seqtime - prevKey[TIME])/(curKey[TIME] - prevKey[TIME]);  q.FromEuler((curKey[YAW] - prevKey[YAW])*(fraction - prevFraction),              (curKey[PITCH] - prevKey[PITCH])*(fraction - prevFraction),              (curKey[ROLL] - prevKey[ROLL])*(fraction - prevFraction));  prevFraction = fraction;  qBoneAnim = qBoneAnim * q;  // Construct animMatrices as usual  // ...}// Increment seqtime by frame timeseqtime += frametime;
you can't linear interpolate between your euler angles, wich is what you do in your last posted code. this will of course result in huge chaotic issues.

but converting both ends first to quaternions, and then spherical linear interpolate should lead to the correct result.
If that's not the help you're after then you're going to have to explain the problem better than what you have. - joanusdmentia

My Page davepermen.net | My Music on Bandcamp and on Soundcloud

Ok, here's an image showing the problem:



The leftmost image is from the animation program - this is how the model should look like.
In middle image I used quaternions constructed from Euler angles and SLERP. You can see that guy's arms wave erraneously from left side to right side - so there's gimbal lock in the arm bones.
Rightmost image shows what happens if I use the incremental rotation code from my last post. Guy gets into pretty bad shape.
The problem with your Euler Angles version really looks like a problem of rotation order... Make sure you are applying each axis rotation in the same order as Lightwave (the rotation order field in the scene)...
Praise the alternative.

This topic is closed to new replies.

Advertisement