quaternion rotation changing sign?

Started by
4 comments, last by Zakwayda 17 years, 1 month ago
I am writing an animation exporter for 3ds max and I've run into some problem: in my test animation, somewhere around frame 50 there is this sudden jump to some arbitrary location that lasts a fraction of second. Apart from this jump, the whole animation plays perfectly. So I printed out the rotation data I get out of 3ds max for every frame: [ w, x, y, z] frame 47: rotation: [-0.348678, 0.937242, 0, 0] frame 48: rotation: [-0.394149, 0.919046, 0, 0] frame 49: rotation: [-0.438674, 0.898646, 0, 0] frame 50: rotation: [-0.482147, 0.87609, 0, 0] frame 51: rotation: [0.524464, -0.851433, 0, 0] frame 52: rotation: [0.565522, -0.824733, 0, 0] frame 53: rotation: [0.605224, -0.796055, 0, 0] And what worries me is this sudden change of the sign of quaternion values at frame 51. I suppose this playback jump has something to do with interpolation between frame 50 and 51. Does anybody know how to approach this kind of a problem? [Edited by - therealremi on March 7, 2007 3:59:11 AM]
Advertisement
My guess is that you're not interpolating along the shortest arc. Can you post your quaternion interpolation function?
I took it 'as is' from some 3d math book, guessing it is too difficult to visualize quaternions.

quaternion slerp(const quaternion& q0, const quaternion& q1, float t){	float cosOmega = dot_product(q0, q1);	// If negative dot, use –q1. Two quaternions q and –q	// represent the same rotation, but may produce	// different slerp. We chose q or –q to rotate using	// the acute angle.	float q1w = q1.w; float q1x = q1.x;	float q1y = q1.y; float q1z = q1.z;	if (cosOmega < 0.0f) 	{		q1w = -q1w;		q1x = -q1y;		q1y = -q1y;		q1z = -q1z;		cosOmega = -cosOmega;	}	// We should have two unit quaternions, so dot should be <= 1.0	//assert(cosOmega < 1.1f);	// Compute interpolation fraction, checking for quaternions	// almost exactly the same	float k0, k1;	if (cosOmega > 0.9999f) 	{		// Very close - just use linear interpolation,		// which will protect againt a divide by zero		k0 = 1.0f - t;		k1 = t;	} 	else 	{		// Compute the sin of the angle using the		// trig identity sin^2(omega) + cos^2(omega) = 1		float sinOmega = sqrt(1.0f - cosOmega*cosOmega);		// Compute the angle from its sin and cosine		float omega = atan2(sinOmega, cosOmega);		// Compute inverse of denominator, so we only have		// to divide once		float oneOverSinOmega = 1.0f / sinOmega;		// Compute interpolation parameters		k0 = sin((1.0f - t) * omega) * oneOverSinOmega;		k1 = sin(t * omega) * oneOverSinOmega;	}	quaternion result;	result.x = k0*q0.x + k1*q1x;	result.y = k0*q0.y + k1*q1y;	result.z = k0*q0.z + k1*q1z;	result.w = k0*q0.w + k1*q1w;	return result;}

There's a typo in the code here:
if (cosOmega < 0.0f) {    q1w = -q1w;    q1x = -q1y;    q1y = -q1y;    q1z = -q1z;    cosOmega = -cosOmega;}
Try fixing that and see if the problem doesn't clear up (I'm guessing it will).
I love you jyk!! The problem is gone (and all the other problems that this typo could have caused). You might have saved me a couple of days of testing and hair pulling.
Quote:Original post by therealremi
I love you jyk!! The problem is gone (and all the other problems that this typo could have caused). You might have saved me a couple of days of testing and hair pulling.
Great :) Now, here are a couple of tips that you might find useful:

1. I realize you got that code from elsewhere, but that's why you should work with proper objects and overloaded operators in vector, matrix, and quaternion code. If you manipulate the elements manually like in your posted example, it's not a matter of if you'll introduce a bug at some point, but rather a matter of when :)

2. A unit-length quaternion and its negative represent the same rotation. Although it seems like there's a discontinuity from frame 50 to 51, the data is fine. If you negate the last three entries (which leaves their rotations unchanged) you can see the progression more clearly (a rotation about the x axis).

3. Because each rotation in 3-space is represented by two unit-length quaternions, there is more than one way to interpolate between two rotations using a quaternion representation. Typically you want the 'shortest' of these, i.e. you want to interpolate along the shortest arc.

4. A typical slerp function will ensure that the interpolation is along the shortest arc by checking the dot product of the two quats and negating one of them if the dot product is less than zero. This happens exactly once in the data you posted, between keyframes 50 and 51. The typo was in the block of code corresponding to this negation, which is why the bug only manifested at this point in the animation.

'Hope some of that helps :)

This topic is closed to new replies.

Advertisement