I have been trying to learn how to implement skeletal animation (skinning) from COLLADA files using ASSIMP and glm. But after a week of being stuck reading different tutorials and posts I have found here and there I still can not get even the simplest animation to work correctly. My results are just bones that look like they are incorrectly translated and rotates around wrong axes.
This mesh have three bones starting at origo and going straight up through a tube, they then swing from side to side when the animation is played. LEFT: My result at key frame 2. RIGHT: Blenders result at key frame 2.
So now I have two questions related to the animation matrices and hopefully someone might help me understand what I am doing wrong.
First, when I read the bone data ( or joints but I called it bone in my code so I will refer to it as bone here also ) I get the bones matrix from mOffsetMatrix. I am not 100% sure if this matrix contains a bones transform relative to its parent bone or if it contains the final position. I have tried to use it as both relative and absolute transform but my final result is still wrong so I dont know which is right.
If the mOffsetMatrix contains the relative transform of a bone then I have to calculate the absolute transform by multiplying it with the transform of its parent bone? I tried doing this with the following code.
// struct for storing bones (joints)
struct Bone
{
std::string name;
glm::mat4 relativeTransform;
glm::mat4 absoluteTransform;
int parent; // id to parent bone
};
std::vector<Bone> skeleton; // contains all the bones
for (int i = 0; i < skeleton.size(); ++i)
{
// if bone have no parent, relative transform is absolute transform
skeleton[i].absoluteTransform = skeleton[i].relativeTransform;
// if bone have parent, add parent absolute transform
if (skeleton[i].parent != -1)
{
skeleton[i].absoluteTransform = skeleton[skeleton[i].parent].absoluteTransform * skeleton[i].relativeTransform;
}
}
Skeleton is a vector containing all of the bones where parent bones always appear before its children so as long as I start calculating from the beginning of the list it should be correct. Also i should mention that i have a function that identify bone parents so the parent id is correct.
Then in regards of the animation step. For now I try to only animate individual key frames so I do not currently do any interpolation, this is so that incorrect interpolation does not affect my result. But I dont know if i calculate the current animation transforms correct. In the end it is the bonesAnimation.current matrices that I calculate for each bone/frame and send to my shaders.
// struct containing keyframe data for each bone
struct BoneAnimationChannel
{
std::string name;
std::vector<float> positionTime;
std::vector<float> rotationTime;
std::vector<float> scalingTime;
std::vector<glm::vec3> positionKeys;
std::vector<glm::quat> rotationKeys;
std::vector<glm::vec3> scalingKeys;
glm::mat4 current; // current transform to be used
};
std::vector<BoneAnimationChannel> bonesAnimation; // contain animation data for all bones
I store the animation data for each bone in a struct and have one instance of the struct for each bone in the vector bonesAnimation. Then i calculate the current transform for each bone at a certain time using the following code.
// update animation
std::vector<glm::mat4> unMultiplied;
for (int i = 0; i < skeleton.size(); ++i)
{
// current bone
auto bone = boneAnimation[i];
int id = 1; // id to keyframe
glm::mat4 currentPose = glm::toMat4(bone->rotationKeys[id]);
currentPose[3][0] = bone->positionKeys[id].x;
currentPose[3][3] = bone->positionKeys[id].y;
currentPose[3][2] = bone->positionKeys[id].z;
currentPose = glm::scale(currentPose, bone->scalingKeys[id]);
// add to list of animation transforms
unMultiplied.push_back(currentPose);
// bones parent id
int parentId = skeleton[i].parent;
// transform for current bone
glm::mat4 result = unMultiplied[i]; // if no parent bone
// if bone have parent, add parents transform
if (parentId != -1)
{
result = unMultiplied[parentId] * unMultiplied[i];
}
boneAnimation[i].current = result;
}
It is possible that my problem lays elsewhere but I think that these two places are the most likely sources for my problems so I start by asking if these calculations look correct. I have tried every different combination of matrices I can think of, I have even tried to invert them or transpose them to see if that is the problem but still I am completely stuck.
Maybe i even need some more transform other than the mOffsetMatrix for each bone and the position vector, scaling vector and rotation quaternion for the animation channels.
I hope this make some kinda of sense and i really appreciate any help i can get.
Thanks in advance :)
EDIT: This is the whole file where i load mesh and animations https://db.tt/EG965vuz, and in the end of this file is where i calculate current animation pose https://db.tt/cBbYZck2