The very first thing that pops into my head is: Have you tried using an existing slerp and quaternion-to-matrix from a library you know works? It could be a good sanity check before trying anything else.
I tried using CML and many different variations of those functions I found online. Also Assimp has a built in slerp function called Interpolate and a quaternion to matrix called simply GetMatrix both of which have the exact same results as my functions.
For example here is my implementation using these built in Assimp functions:
void Actor::UpdateAnimation( int32_t bone, Matrix44* matrix, double time )
{
// Initialize animation rotation matrix.
Matrix44 animRot;
Matrix44 animRot2;
animRot = MatrixIdentity();
animRot2 = MatrixIdentity();
// Get animation rotation.
Curve uCurve;
Curve lCurve;
uCurve.time = 0xffffffffUL;
uCurve.value = Vector4( 0.0f, 0.0f, 0.0f, 0.0f );
lCurve.time = 0;
lCurve.value = Vector4( 0.0f, 0.0f, 0.0f, 0.0f );
// Find one frame that are directly before and one that is directly after the current time.
for( uint32_t i = 0; i < mesh->bones[bone]->Rot_Keys.size(); i++)
{
Curve nCurve = mesh->bones[bone]->Rot_Keys;
if(nCurve.time > lCurve.time && nCurve.time < time )
{
lCurve = nCurve;
}
if(nCurve.time < uCurve.time && nCurve.time > time )
{
uCurve = nCurve;
}
}
// If we can't find an upper curve then just set it to the lower curve.
if(uCurve.time == 0xffffffffUL)
{
uCurve = lCurve;
}
// Check if we found any frames then interpolate between the two rotations and create a matrix from the quaternion.
if(uCurve.time > 0)
{
//Vector4 currentRot;
//currentRot = Slerp(lCurve.value, uCurve.value, (float)(time - lCurve.time) / (float)(uCurve.time - lCurve.time) );
//animRot = MatrixRotationQuaternion( currentRot );
Vector4 currentRot;
currentRot = Slerp(lCurve.value, uCurve.value, (float)(time - lCurve.time) / (float)(uCurve.time - lCurve.time) );
aiQuaternion aiCur, aiLow, aiHi;
aiLow.w = lCurve.value.w;
aiLow.x = lCurve.value.x;
aiLow.y = lCurve.value.y;
aiLow.z = lCurve.value.z;
aiHi.w = uCurve.value.w;
aiHi.x = uCurve.value.x;
aiHi.y = uCurve.value.y;
aiHi.z = uCurve.value.z;
aiCur.Interpolate( aiCur, aiLow, aiHi, (float)(time - lCurve.time) / (float)(uCurve.time - lCurve.time) );
aiMatrix3x3 aiMat;
aiMat = aiCur.GetMatrix();
animRot._11 = aiMat.a1;
animRot._12 = aiMat.a2;
animRot._13 = aiMat.a3;
animRot._14 = 0.0f;
animRot._21 = aiMat.b1;
animRot._22 = aiMat.b2;
animRot._23 = aiMat.b3;
animRot._24 = 0.0f;
animRot._31 = aiMat.c1;
animRot._32 = aiMat.c2;
animRot._33 = aiMat.c3;
animRot._34 = 0.0f;
animRot._41 = 0.0f;
animRot._42 = 0.0f;
animRot._43 = 0.0f;
animRot._44 = 1.0f;
}
// Offset the center of rotation.
animRot2 = MatrixInverse( Vector4(), mesh->bones[bone]->matrix) * animRot * mesh->bones[bone]->matrix;
// Get difference from original pose to the animated pose.
matrices[bone] = animRot2 * (*matrix) * MatrixInverse( Vector4(), mesh->bones[bone]->matrix);
// Loop through all child bones and update their animations.
for( uint32_t i = 0; i < mesh->bones[bone]->childen.size(); i++)
{
Matrix44 accumulate;
Matrix44 Bone2Parent;
Bone2Parent = MatrixInverse( Vector4(), mesh->bones[bone]->matrix ) * mesh->bones[mesh->bones[bone]->childen]->matrix;
accumulate = animRot2 * (*matrix) * Bone2Parent;
Actor::UpdateAnimation( mesh->bones[bone]->childen, &accumulate, time );
}
}
The exact same result. I can be almost completely sure that is not the problem.