Interpolating Rotation Keyframes

I’ve been working on the last section of the model editor that I have been working on for the last 5 months now, and have reached another thing that I can’t quite understand. I have added support for Keyframe animation and interpolationg the Positional data was easy, however the Rotational data seems to be a bit elusive. Every model that has keyframes defines them as Axis/Angle and I was hoping someone might have a simple function to calculate an interpolated frame from 2 Axis/Angle Keys.

Sorry, no simple function exists. I would suggest doing what you already know I’m about to say: turn those Ang/Axis rotations into Quaternions and SLERP them.

You can also linearly interpolate the axis and then linearly interpolate the angle. It works ok visually except for a possible singularity when the axis is (0,0,0). Of course with quaternions you don’t have that kind of problem. I was working on a 3DS loader a while ago and I had to work on the rotation key frames and it turned out that SLERPing these rotations didn’t give the same result (visually speaking) as 3DS MAX but interpolating separately the axis / angle did.

I have downloaded the Quaternion.ps document and realized it was well over my head(mathematically speaking). I do wish to achieve a 3dsMax quality of interpolating as the original models were made in Max. I tried linearly interpolating the Axis/Angle and while this works well for the Angle, perhaps you could give me a hint as to how to properly linearly interpolate the Axis.

I had just hacked something in using the code I used to interpolate the Positional data(knowing it wasn’t that simple) just to see, and now the Sub-Objects rotate in odd ways. I figure I am going to need to figure out the Angle of the Axis Vector and the Right angle to the 2 Axis vectors and then figure out the angle increments, but I am lost(whoosh…right over my head) and haven’t a clue how to start. My Vector/Matrix math skills are nearly non-existent.

If you guys could point me at some easy to understand tutorials or if you have something simple showing Linear Interpolation for Axis’s, I would be very greatful.

Linear interpolation of the axis is just like linear interpolation of position (you have a before & after vector you interpolate between) except that afterward you need to renormalize the vector. The only time this is a problem is when the vectors are 180 degrees apart. Quaternions are a much cleaner solution (though who am I to speak, since I dont use them). I know you dont understand the math, but I think few people who use them do. Maybe you can just bite the bullet and borrow some code for a Quaternion/Matrix conversion functions and a quaternion SLERP function. (I’m not normally one to condone using code you dont understand, but this might be one of my exceptions).

Thank you all for responding.

I have figured out a way to do it. It seems that the Rotations that are stored are Rotation offsets as opposed to some Global Rotation value. Maybe that’s how it is and I just didn’t notice. So…I found an algorithm for converting Axis/Angle to Matrix and luckily, it works. I don’t fully understand what it does, but I understand it enough now I think to get by.

Quaternions weren’t as complicated to implement, but the math was way over my head. Hopefully I wont need them anytime soon.

If you can find the axis of rotation (which should be pretty simple if you have say two rotation matrices) then you can just LERP the angle, that should give you SLERPing just like quaternions.

This should do what you need:

-Blake


// ---------------------------------------------------------
// spherical linear interpolation between two quaternions
// pass two quaternions, blend factor, and result quaternion
// ---------------------------------------------------------
void SlerpQuats( CQuatf *quat1,
CQuatf *quat2,
float slerp,
CQuatf *result )
{
double omega, cosom, sinom, scale0, scale1;

// dot product gives the angle between the quats:
cosom = quat1->GetW() * quat2->GetW() + 
		  quat1->GetX() * quat2->GetX() + 
		  quat1->GetY() * quat2->GetY() + 
		  quat1->GetZ() * quat2->GetZ(); 

// make sure the quats are not facing exactly opposite directions:
if ((1.0 + cosom) > SLERP_THRESHOLD)
{		
  // avoid divide by zero via lerping when a slerp
  // would give us a 'sinom' of zero.
  if ((1.0 - cosom) > SLERP_THRESHOLD) 
  {
	 // this is the slerp kernel:
	 omega  = acos(cosom);
	 sinom  = sin(omega);
	 scale0 = sin((1.0 - slerp) * omega) / sinom;
	 scale1 = sin(slerp * omega) / sinom;
  } 
  else 
  {
	 // lerp when quat difference is tiny:
	 scale0 = 1.0 - slerp;
	 scale1 = slerp;
  }
} 
else 
{
  // the two quats point in exactly opposite directions,
  // in order to avoid divide by zero we'll calculate a
  // perpendicular quat and slerp in that direction:
  
  result->SetW(  quat2->GetZ() ); // this logic gives a perpendicular quat?
  result->SetX( -quat2->GetY() ); 
  result->SetY(  quat2->GetX() );
  result->SetZ( -quat2->GetW() );

  scale0 = sin((1.0 - slerp) * (float)HALF_PI);
  scale1 = sin(slerp * (float)HALF_PI);		
}

result->SetW( (float)(scale0 * quat1->GetW() + scale1 * quat2->GetW()) );
result->SetX( (float)(scale0 * quat1->GetX() + scale1 * quat2->GetX()) );
result->SetY( (float)(scale0 * quat1->GetY() + scale1 * quat2->GetY()) );
result->SetZ( (float)(scale0 * quat1->GetZ() + scale1 * quat2->GetZ()) );

}

// --------------------------------------------------------------------------------
// passed a quat structure in axis angle (radians) form, convert to a correct quat.
// --------------------------------------------------------------------------------
void AxisAngleRToQuat( CQuatf *quat )
{
assert( quat );

double halfAngle = (double)quat->GetW() * 0.5;
double scale = sin( halfAngle );

quat->SetW( (float)cos( halfAngle ) );
quat->SetX( (float)(scale * (double)quat->GetX()) );
quat->SetY( (float)(scale * (double)quat->GetY()) );
quat->SetZ( (float)(scale * (double)quat->GetZ()) );

}

void AxisAngleRToQuat( float *w, float *x, float *y, float *z )
{
assert( w && x && y && z );

double halfAngle = (double)(*w) * 0.5;
double scale = sin( halfAngle );

(*w) = (float)cos( halfAngle );
(*x) = (float)(scale * (double)(*x));
(*y) = (float)(scale * (double)(*y));
(*z) = (float)(scale * (double)(*z));
}

// -----------------------------------
// convert a quaternion into a matrix:
// -----------------------------------
void QuatToMatrix( CQuatf& quat, CMat4f& mat )
{
double tx = 2.0 * (double)quat.GetX();
double ty = 2.0 * (double)quat.GetY();
double tz = 2.0 * (double)quat.GetZ();
double twx = -tx * (double)quat.GetW();
double twy = -ty * (double)quat.GetW();
double twz = -tz * (double)quat.GetW();
double txx = tx * (double)quat.GetX();
double txy = ty * (double)quat.GetX();
double txz = tz * (double)quat.GetX();
double tyy = ty * (double)quat.GetY();
double tyz = tz * (double)quat.GetY();
double tzz = tz * (double)quat.GetZ();

// don't ask me why, but these indexes are
// best rembered as x,y locations in the matrix
// rather than the "col,row" they are named-
// which seems to imply a "row,col" order...
mat.SetVal( 0, 0, (float)(1.0 - (tyy + tzz)) );    
mat.SetVal( 1, 0, (float)(txy - twz) );                      
mat.SetVal( 2, 0, (float)(txz + twy) );              
 mat.SetVal( 3, 0, 0.0f );                                 

mat.SetVal( 0, 1, (float)(txy + twz) );            
mat.SetVal( 1, 1, (float)(1.0 - (txx + tzz)) );    
mat.SetVal( 2, 1, (float)(tyz - twx) );             
mat.SetVal( 3, 1, 0.0f );                            

 mat.SetVal( 0, 2, (float)((txz - twy)) );    
 mat.SetVal( 1, 2, (float)(tyz + twx) );    
mat.SetVal( 2, 2, (float)(1.0 - (txx + tyy)) );    
 mat.SetVal( 3, 2, 0.0f );   

 mat.SetVal( 0, 3, 0.0f );    
 mat.SetVal( 1, 3, 0.0f );    
 mat.SetVal( 2, 3, 0.0f );    
 mat.SetVal( 3, 3, 1.0f );   

}

// -------------------------------------------------------------------
// calculate the inverse of a quaternion:
// (is this faster because it attempts to avoid normalizing the quat?)
// -------------------------------------------------------------------
void QuatInverse( CQuatf& srcQuat, CQuatf& destQuat )
{
CQuatf workQuat = srcQuat;

// do a quat dot, which is the length of the quat:
float a = workQuat.GetW() * workQuat.GetW() + workQuat.GetX() * workQuat.GetX() +
workQuat.GetY() * workQuat.GetY() + workQuat.GetZ() * workQuat.GetZ();

// check if this is not a unit quat:
if (fabs( a - 1.0f ) > 0.001)
{
// normalize the quat:

a = (float)(1.0 / sqrt( a ));

workQuat *= a;

}

// when a quat is normalized, the inverse is the conjugate:
destQuat = workQuat.Conjugate();
}

// ----------------------------------------------------------
// it seems that this operation solves quate a few problems
// when working with quats. Basically, two quats with the
// negative values in each component of each other quat
// represent the same orientation, but when slerping opposite
// directions of rotation will be executed.
// This routine will take two quats and flip the components
// of the 2nd quat if the rotation direction that will be
// slerped is not the shortest rotation direction.
// ----------------------------------------------------------
void UnaliasQuat( CQuatf *quat0, CQuatf *quat1 )
{
assert( quat0 && quat1 );

double cosom;

// dot product gives the angle between the quats:
cosom = quat0->GetW() * quat1->GetW() +
quat0->GetX() * quat1->GetX() +
quat0->GetY() * quat1->GetY() +
quat0->GetZ() * quat1->GetZ();

// if the angle between the two is less than zero,
// flip the second quat to force rotation around
// in the opposite direction.
if (cosom < 0.0)
{
quat1->SetW( -quat1->GetW() );
quat1->SetX( -quat1->GetX() );
quat1->SetY( -quat1->GetY() );
quat1->SetZ( -quat1->GetZ() );
}
}

bsenftner - Thank you for that code. I will look through it and see if I can hopefully understand what it is doing. I am not typically a code whore, but in the case of this, I think I have no choice.

I don’t fully understand it myself… but it’s been used in game productions for a few years now, so I know that it works. I glombed it together from reading GameDeveloper and various peoples demos and web pages.

Good luck.
-Blake

bsenftner, could you post your entire Quaternion code? I’ll appreciate it a lot… I’d like to use quaternions, but now I’m really far to completely understand the math behind it to make my own Quat. library.

Thanks…

  • Royconejo.

I’ve run across a wee bit of a problem using Quats. I thought it would be nice to simply convert the Axis/Angles into Quats, and then when I needed to calculate a rotation matrix, to simply use SLERP. This doesn’t seem to work right as some of the Angles are greater than 180 degrees.

Is there a way to use Quaternions and still be able to have angles greater than 180 degrees. I would really love to know how they did it in 3dsmax, because in there you can use any angle you wish and it happily calculates the interpolated Axis/Angles. I would like to be able to do this as well.

My first thought:
Just subtract 360 from the angle until it is within the -180 to 180 range.

My second thought:
you probably mean you want to interpolate over an angle larger than 180 degrees (ex: you want to make the object spin completely around 3 times). In this case, I think you would need to subdivide the interpolation into incremental rotations of 180 degrees or less. (disclaimer: I havent actually worked with quats, just theorizing based on my understanding of them).

In response to royconejo: the logic posted pretty much is my entire quat code. What is missing is the original CQuatf class definition. I don’t really want to post that, but I can tell you that I based it upon the same named class from the GLOW toolkit. The differences being optimizations and removal of some methods. I recommend that you check out the GLOW toolkit for a good basic quat class and grow your own from there. (Besides, what I posted is quite the gift, anyway )

-Blake

Thanks, I’ll do that…

  • Royconejo.

First off, I want to thank you all for responding with so much usefull information.

Secondly, I have found a way to do it and I thought I should share in case anyone else runs into this problem.

The model files contain Global Positional information and Rotational Axis/Angle offsets. I tried using simple linear interpolation of the matrixes but that only produced wobble. I then tried Quaternions but found them to be overly complicated so I started looking for another way.

I took each keyframe that was imported from the model and created Rotation Matrix’s from the Axis/Angles. Each Rotation Matrix was a Global Rotation for that particular key. Then when I need to calculate an interpolated frame I take the current keys Matrix and then using the same function to create the keys originally, multiply the next keys Axis/Angle with the Angle of the next key multiplied by the position between the two frames (0.0-1.0). This works surprisingly well and could be implemented quite easily by simply letting OpenGL calculate the Matrixes from the Axis/Angles(not what I did).

I’m not sure if this is similar to using Quaternions but it does what I need without to much complexity. The only drawback is that I do not know in-between frames what the Global Axis/Angle is so therefore I can not display that to the user, but for a simple model editor that has already taken twice as long as I thought, it’s good enough.

Thanks again for all the help . I’m sure this won’t be the last time I need it. Also, sorry for what some will title a ‘Non-OpenGL’ related question, but I think that the people on this board are quite intelligent, and I only program in OpenGL and quite frankly, I haven’t found any explanations of Quats to be understandable except for those posted by members here.