1

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?

genpfault
  • 51,148
  • 11
  • 85
  • 139
McBob
  • 1,011
  • 2
  • 13
  • 25
  • I don't know much about quaternion interpolation but maybe this could help: http://stackoverflow.com/questions/1684594/can-i-interpolate-rotation-from-two-quaternions-created-from-yaw-pitch-roll – num3ric Nov 15 '12 at 08:58
  • "*Im trying to optimize my skeletal animation system by using tracks (curve) instead of keyframe.*" I've written a few skeletal animation systems. Why do you think this is an optimization? Is this a memory or performance optimization that you're trying to achieve? – Nicol Bolas Nov 15 '12 at 10:42
  • Well both... by using curve for each component I don't have to store the all data for each frame... Only values "from frame", "to frame" then simply interpolate only the components that have a curve. If you have wrote skeletal animation systems you must have face similar problem as Im facing... Its sure that it have something to do with the interpolation... Is it possible to only interpolate a component of a quaternion independently or the full (XYZW) is required for lerp (or slerp)? The jaggyness seems to be caused by the lack of data Im I right? – McBob Nov 15 '12 at 14:10

2 Answers2

2

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.

JCooper
  • 6,395
  • 1
  • 25
  • 31
0

Naive considerations

Linear interpolation is fine for things that operate additively, i.e. that add something to something else every time you execute the corresponding operation. Queternions, however, are multiplicative: you multiply them to chain them.

For this reason, I originally suggested computing the following:

pow(secondQuaternion, f)*pow(firstQuaternion, 1. - f)

Wikipedia has a section on computing powers of quaternions, among other things. As your comment below states that this does not work, the above is for reference only.

Proper interpolation

Since writing this post, I've read a bit more about slerp (spherical linear interpolation) and found that wikipedia has a section on quaternion slerp. Your comment above suggests that the term is already familiar to you. The formula is a bit more complicated than what I wrote above, but it is still rather related due to the way it uses powers. I guess you'd do best by adapting or porting an available implementatin of that formula. This page for example comes with a bit of code.

Fixing data

As to your updated question

Any ideas how to properly fix the curves?

Fixing errors while maintaining correct data requires some idea of what kinds of errors do occur. So I'd start by trying to locate the source of that error, if at all possible. If that can be fixed to generate correct data, then good. If not, it should still give you a better idea of what to expect, and when.

MvG
  • 57,380
  • 22
  • 148
  • 276