Search code examples
mathquaternions

Quaternion Component Interpolation


Im trying to optimize my skeletal animation system by using tracks (curve) instead of keyframe. Each curve take care of a specific component then (for now) I linearly interpolate the values. Work fine for my bone positions, however Im having a hard time getting rid of the "jagyness" of the quaternion component interpolation...

Basically I have 1 curve for each component (XY and Z) for each bones quaternion and I use the following code to interpolate the XY and Z curves independently:

// Simple lerp... (f is always a value between 0.0f and 1.0f)
return ( curve->data_array[ currentframe ].value * ( 1.0f - f ) ) +
       ( curve->data_array[ nextframe ].value * f );

When I interpolate the quaternion XYZ then I use the following code to rebuild the W component of the quaternion before normalizing it and affecting it to my bone before drawing:

Quaternion QuaternionW( const Quaternion q )
{
    Quaternion t = { q.x, q.y, q.z };

    float l = 1.0f - ( q.x * q.x ) - ( q.y * q.y ) - ( q.z * q.z );

    t.w = ( l < 0.0f ) ? 0.0f : -sqrtf( l );

    return t;
}

The drawing look fine at the exception that the bones become all jerky from time to time, would it be due to the floating point precision? Or the recalculation of the W component? Or there is absolutely no way I can linearly interpolate each component of a quaternion this way?

ps: On a side note, in my curve interpolation function if I replace the code above with:

return curve->data_array[ currentframe ].value;

instead or linearly interpolating, everything is fine... So the data is obviously correct... Im puzzled...

[ EDIT ]

After more research I found that the problem comes from the frame data... I got i.e. the following:

Frame0: quat.x = 0.950497

Frame1: quat.x = -0.952190

Frame2: quat.x = 0.953192

This is what causes the inversion and jaggyness... I tried to detect this case and inverse the sign of the data but it still doesn't fix the problem fully as some frame now simply look weird (visually when drawing).

Any ideas how to properly fix the curves?


Solution

  • Your data are probably not wrong. Quaternion representations of orientation have the funny property of being 2x redundant. If you negate all four elements of a quaternion, you're left with the same orientation. It's easy to see this if you think of the quaternion as an axis/angle representation: Rotating by Θ around axis a, is the same as rotating by around axis -a.

    So what should you do about it? As mentioned before, slerp is the right thing to do. Quaternion orientations exist on the unit hypersphere. If you linearly interpolate between points on a sphere, you leave the sphere. However, if the points are close by each other, it's often not a big deal (although you should still renormalize afterward). What you absolutely do need to make sure you do is check the inner-product of your two quaternions before interpolating them: e.g.,

    k=q0[0]*q1[0] + q0[1]*q1[1] + q0[2]*q1[2] + q0[3]*q1[3];
    

    If k<0, negate one of the quaternions: for (ii=0;ii<4;++ii) q1[ii]=-q1[ii]; This makes sure that you're not trying to interpolate the long way around the circle. This does mean, however, that you have to treat the quaternions as a whole, not in parts. Completely throwing away one component is particularly problematic because you need its sign to keep the quaternion from being ambiguous.