I'm trying to implement skeletal animation using assimp and glm. Everything seems to work, except for rotations.
This is the code I use when packing assimp data into my own engine's format. I THINK it should be correct, but since I've already run into problems with matrices (assimp uses row major, glm - column major), I'd rather post this, too:
for(uint32_t l = 0; l < tempChannel.numRotationKeys; ++l){
quatKey tempKey;
tempKey.values = glm::quat(scene->mAnimations[i]->mChannels[j]->mRotationKeys[l].mValue.w,
scene->mAnimations[i]->mChannels[j]->mRotationKeys[l].mValue.x,
scene->mAnimations[i]->mChannels[j]->mRotationKeys[l].mValue.y,
scene->mAnimations[i]->mChannels[j]->mRotationKeys[l].mValue.z);
And this is the code that performs the actual transformations (quite a bit of it is based on http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html), but modifications (e.g. storing a list, where each child element is placed after a parent and keeps a parent's id, as was suggested here: http://blog.tojicode.com/2011/10/building-game-part-3-skinning-animation.html) have been made. Scaling and location transforms seem to be working fine, but rotations don't:
for(unsigned int i = 0; i < skeleton.size(); ++i){
finalTransforms[i] = calcFinalTransform(time, i);
}
glm::mat4 SkeletalMeshObject::calcFinalTransform(double animationTime, unsigned int currentNode){
// Find the node where the keyframes for the current bone are stored
// Return -1, if not found
int nodeId = findNodeAnimation(currentAnimation, skeleton[currentNode].name);
glm::mat4 nodeTransform(skeleton[currentNode].offset);
// If this node isn't animated, pass the default offset to it
if(nodeId >= 0){
unsigned int index;
unsigned int nextIndex;
// Interpolate scaling
glm::vec3 scl;
if(animations[currentAnimation].channels[nodeId].scalingKeys.size() == 1){
scl = animations[currentAnimation].channels[nodeId].scalingKeys[0].values;
} else {
index = findKey(animationTime, KeyType::Scaling, nodeId);
nextIndex = index + 1;
double delta = animations[currentAnimation].channels[nodeId].scalingKeys[nextIndex].time -
animations[currentAnimation].channels[nodeId].scalingKeys[index].time;
double factor = (animationTime - animations[currentAnimation].channels[nodeId].scalingKeys[index].time) / delta;
scl = glm::mix(animations[currentAnimation].channels[nodeId].scalingKeys[index].values,
animations[currentAnimation].channels[nodeId].scalingKeys[nextIndex].values,
(float)factor);
}
// Interpolate rotation
glm::quat rot;
if(animations[currentAnimation].channels[nodeId].rotationKeys.size() == 1){
rot = animations[currentAnimation].channels[nodeId].rotationKeys[0].values;
} else {
index = findKey(animationTime, KeyType::Rotation, nodeId);
nextIndex = index + 1;
double delta = animations[currentAnimation].channels[nodeId].rotationKeys[nextIndex].time -
animations[currentAnimation].channels[nodeId].rotationKeys[index].time;
double factor = (animationTime - animations[currentAnimation].channels[nodeId].rotationKeys[index].time) / delta;
rot = glm::mix(animations[currentAnimation].channels[nodeId].rotationKeys[index].values,
animations[currentAnimation].channels[nodeId].rotationKeys[nextIndex].values,
(float)factor);
}
// Interpolate location
glm::vec3 loc;
if(animations[currentAnimation].channels[nodeId].locationKeys.size() == 1){
loc = animations[currentAnimation].channels[nodeId].locationKeys[0].values;
} else {
index = findKey(animationTime, KeyType::Location, nodeId);
nextIndex = index + 1;
double delta = animations[currentAnimation].channels[nodeId].locationKeys[nextIndex].time -
animations[currentAnimation].channels[nodeId].locationKeys[index].time;
double factor = (animationTime - animations[currentAnimation].channels[nodeId].locationKeys[index].time) / delta;
loc = glm::mix(animations[currentAnimation].channels[nodeId].locationKeys[index].values,
animations[currentAnimation].channels[nodeId].locationKeys[nextIndex].values,
(float)factor);
}
// Get final transform
nodeTransform = glm::translate(loc) * glm::toMat4(rot) * glm::scale(scl);
}
// Store the node's transform
locRotScaleTransforms[currentNode] = nodeTransform;
// Get the id of the parent
int parentId = skeleton[currentNode].parent;
// Hierarchial transform
glm::mat4 hierarchyTransform = nodeTransform;
while(parentId >= 0){
hierarchyTransform = locRotScaleTransforms[parentId] * hierarchyTransform;
parentId = skeleton[parentId].parent;
}
// Apply inverse bind position matrix
hierarchyTransform *= skeleton[currentNode].ibp;
return hierarchyTransform;
}
Here's how the animation looks in blender: And here's in my engine. Couldn't capture a gif from my window, so I'll describe it. First, it rotates along multiple axes (bones in the original animation were constrained to z axis), and as you can also see, it distorts. The frame seen below is almost at an end of animation, when distortions are most visible:
What can I do to make it look right?