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,

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 -
            double factor = (animationTime - animations[currentAnimation].channels[nodeId].scalingKeys[index].time) / delta;
            scl =     glm::mix(animations[currentAnimation].channels[nodeId].scalingKeys[index].values,
        // 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 -
            double factor = (animationTime -     animations[currentAnimation].channels[nodeId].rotationKeys[index].time) / delta;
            rot =     glm::mix(animations[currentAnimation].channels[nodeId].rotationKeys[index].values,
        // 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 -
            double factor = (animationTime -     animations[currentAnimation].channels[nodeId].locationKeys[index].time) / delta;
            loc =     glm::mix(animations[currentAnimation].channels[nodeId].locationKeys[index].values,

        // 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?

  • \$\begingroup\$ Your quaternion to matrix code might be a bit off, since AssImp's matrices are "flipped". \$\endgroup\$
    – Stefnotch
    Jul 8 '16 at 14:24

First thing I see is that you shouldn't read the quaternion in reverse order.

Also you shouldn't use glm::mix, use glm::slerp instead.

And here is how I construct the bone transform:

mat = glm::mat4_cast( currentrotation );
mat[0][0] *= currentscale.x; mat[1][0] *= currentscale.x; mat[2][0] *= currentscale.x;
mat[0][1] *= currentscale.y; mat[1][1] *= currentscale.y; mat[2][1] *= currentscale.y;
mat[0][2] *= currentscale.z; mat[1][2] *= currentscale.z; mat[2][2] *= currentscale.z;
mat[0][3] = currentposition.x; mat[1][3] = currentposition.y; mat[2][3] = currentposition.z;

Also I would check all my matrix multiplication, it should be in reverse order to how would you do it with Assimp matrices.

I base all this on personal experience from implementing an animated mesh with assimp and glm myself.(which now works) But it might not be the correct way of doing things.

  • \$\begingroup\$ I'm almost sure I'm reading the quaternion in the right order. Here's how glm defines the constructor: glm.g-truc.net/0.9.4/api/a00076_source.html#l00068 \$\endgroup\$
    – Manvis
    Dec 20 '13 at 15:52
  • \$\begingroup\$ @Manvis You are correct, I should have double checked. Edited answer. \$\endgroup\$
    – akaltar
    Dec 20 '13 at 15:58

I think the missing piece is parents starting transform. Meaning the bone was not at the origin to start with, so it should not be animated as it is. I saw you are using parents "animation" transform in the end withing while, but where is the starting point of the bone?

I am saving the transforms while loading the mesh like this:

void ModelAsset::createMeshes(const aiScene *scene, aiNode *aiNode, glm::mat4 parentTransform) {
    parentTransform = parentTransform * GLMConverter::AssimpToGLM(aiNode->mTransformation);

    for (unsigned int i = 0; i < aiNode->mNumMeshes; ++i) {
            aiMesh *currentMesh;
            currentMesh = scene->mMeshes[aiNode->mMeshes[i]];
            for (unsigned int j = 0; j < currentMesh->mNumBones; ++j) {
                meshOffsetmap[currentMesh->mBones[j]->mName.C_Str()] = GLMConverter::AssimpToGLM(
                std::string boneName =  currentMesh->mBones[j]->mName.C_Str();
                boneName += "_parent";
                meshOffsetmap[boneName] = parentTransform;

//some other code

    for (unsigned int i = 0; i < aiNode->mNumChildren; ++i) {
        createMeshes(scene, aiNode->mChildren[i], parentTransform);

and when I am calculating the animation:

    transforms[boneNode->boneID] =
            globalInverseTransform * meshOffsetmap.at(boneNode->name + "_parent") * nodeTransform * meshOffsetmap.at(boneNode->name)

If you would like to check out t Full source code can be found at https://github.com/enginmanap/limonEngine/blob/master/src/Assets/ModelAsset.cpp


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .