Search code examples
c++openglglm-mathassimp

Trying to load animations in OpenGL from an MD5 file using Assimp GLM


I'm trying to follow the tutorial at here ( at ogldev ) mentioned in this answer .

I am however facing a few issues which I believe to be related to the Row Major Order for Assimp vs the Column major order fro GLM, although I am quite not sure.

I've tried a few variations and orders to see if any of those work, but to no avail.

Here ( Gist ) is the Class which I use to load the complete MD5 file. And the current Result I have.

And, this is the part where I think it is going wrong, when I try to update the bone transformation matrices.

void SkeletalModel::ReadNodeHierarchyAnimation(float _animationTime, const aiNode* _node,
        const glm::mat4& _parentTransform)
    {

        std::string node_name = _node->mName.data;

        const aiAnimation * p_animation = scene->mAnimations[0];

        glm::mat4 node_transformation(1.0f);

        convert_aimatrix_to_glm(node_transformation, _node->mTransformation);
        // Transpose it.
        node_transformation = glm::transpose(node_transformation);

        const aiNodeAnim * node_anim = FindNodeAnim(p_animation, node_name);

        if (node_anim) {

            //glm::mat4 transformation_matrix(1.0f);

            glm::mat4 translation_matrix(1.0f);
            glm::mat4 rotation_matrix(1.0f);
            glm::mat4 scaling_matrix(1.0f);

            aiVector3D translation;
            CalcInterpolatedPosition(translation, _animationTime, node_anim);

            translation_matrix = glm::translate(translation_matrix, glm::vec3(translation.x, translation.y, translation.z));

            aiQuaternion rotation;
            CalcInterpolatedRotation(rotation, _animationTime, node_anim);

            // Transpose the matrix after this.
            convert_aimatrix_to_glm(rotation_matrix, rotation.GetMatrix());
            //rotation_matrix = glm::transpose(rotation_matrix);

            aiVector3D scaling;
            CalcInterpolatedScaling(scaling, _animationTime, node_anim);
            scaling_matrix = glm::scale(scaling_matrix, glm::vec3(scaling.x, scaling.y, scaling.z));

            node_transformation = scaling_matrix * rotation_matrix * translation_matrix;
            //node_transformation = translation_matrix * rotation_matrix * scaling_matrix;

        }

        glm::mat4 global_transformation =  node_transformation * _parentTransform;

        if (boneMapping.find(node_name) != boneMapping.end()) {

            // Update the Global Transformation.
            auto bone_index = boneMapping[node_name];

            //boneInfoData[bone_index].finalTransformation = globalInverseTransform * global_transformation * boneInfoData[bone_index].boneOffset;
            boneInfoData[bone_index].finalTransformation = boneInfoData[bone_index].boneOffset * global_transformation * globalInverseTransform;
            //boneInfoData[bone_index].finalTransformation = globalInverseTransform;
        }

        for (auto i = 0; i < _node->mNumChildren; i++) {
            ReadNodeHierarchyAnimation(_animationTime, _node->mChildren[i], global_transformation);
        }

    }

My Current Output:

Current Output

I tried going through each matrix used in the code to check whether I should tranpose it or not. Whether I should change the matrix multiplication order or not. I could not find my issue.

If anyone can point out my mistakes here or direct me to a different tutorial that would help me load animations, that would be great.

Also, I see suggestions to use a basic model in the initial stages of learning this. But I was told Obj format doesn't support animations, and I have been using just Obj before this. Can I use any other formats that blender exports in a manner similar to MD5 as shown in this tutorial?


Solution

  • I built an animated scene a few years ago using Assimp library, basically following these tutorials. http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html and http://sourceforge.net/projects/assimp/forums/forum/817654/topic/3880745

    While I was using and old X format (blender can work with X, using an extension), I can definitely confirm you need to transpose the assimp animation matrices for use with GML.

    Regarding using other formats, you can use what whatever you like provided they are supported by Blender (import, Editing, Export) and by Assimp. Be prepared for a fair bit of trial and error when changing formats!

    Rather then me trying to understand your code, I will post the relevant fragments from my working system, that shows the calculation of bone matrices. Hopefully this will help you, as I remember having the same problem as you describe, and taking some time to track it down. Code is plain 'C'.

    You can see where the transposition takes place at the end of the code.

    // calculateAnimPose() calculates the bone transformations for a mesh at a particular time in an animation (in scene)
    // Each bone transformation is relative to the rest pose.
    void calculateAnimPose(aiMesh* mesh, const aiScene* scene, int animNum, float poseTime, mat4 *boneTransforms) {
    
        if(mesh->mNumBones == 0 || animNum < 0) {    // animNum = -1 for no animation
            boneTransforms[0] = mat4(1.0);           // so, just return a single identity matrix
            return;
        }
        if(scene->mNumAnimations <= (unsigned int)animNum)    
            failInt("No animation with number:", animNum);
    
        aiAnimation *anim = scene->mAnimations[animNum];  // animNum = 0 for the first animation
    
        // Set transforms from bone channels 
        for(unsigned int chanID=0; chanID < anim->mNumChannels; chanID++) {
            aiNodeAnim *channel = anim->mChannels[chanID];        
            aiVector3D curPosition;
            aiQuaternion curRotation;   // interpolation of scaling purposefully left out for simplicity.
    
            // find the node which the channel affects
            aiNode* targetNode = scene->mRootNode->FindNode( channel->mNodeName );
    
            // find current positionKey
            size_t posIndex = 0;
            for(posIndex=0; posIndex+1 < channel->mNumPositionKeys; posIndex++)
                if( channel->mPositionKeys[posIndex + 1].mTime > poseTime )
                    break;   // the next key lies in the future - so use the current key
    
            // This assumes that there is at least one key
            if(posIndex+1 == channel-> mNumPositionKeys)
                 curPosition = channel->mPositionKeys[posIndex].mValue;  
            else {
                float t0 = channel->mPositionKeys[posIndex].mTime;   // Interpolate position/translation
                float t1 = channel->mPositionKeys[posIndex+1].mTime;
                float weight1 = (poseTime-t0)/(t1-t0);  
    
                curPosition = channel->mPositionKeys[posIndex].mValue * (1.0f - weight1) + 
                              channel->mPositionKeys[posIndex+1].mValue * weight1;
            }
    
            // find current rotationKey
            size_t rotIndex = 0;
            for(rotIndex=0; rotIndex+1 < channel->mNumRotationKeys; rotIndex++)
                if( channel->mRotationKeys[rotIndex + 1].mTime > poseTime )
                    break;   // the next key lies in the future - so use the current key
    
            if(rotIndex+1 == channel-> mNumRotationKeys)
                curRotation = channel->mRotationKeys[rotIndex].mValue;
            else {
                float t0 = channel->mRotationKeys[rotIndex].mTime;   // Interpolate using quaternions
                float t1 = channel->mRotationKeys[rotIndex+1].mTime;
                float weight1 = (poseTime-t0)/(t1-t0); 
    
                aiQuaternion::Interpolate(curRotation, channel->mRotationKeys[rotIndex].mValue, 
                                          channel->mRotationKeys[rotIndex+1].mValue, weight1);
                curRotation = curRotation.Normalize();
            }
    
            aiMatrix4x4 trafo = aiMatrix4x4(curRotation.GetMatrix());             // now build a rotation matrix
            trafo.a4 = curPosition.x; trafo.b4 = curPosition.y; trafo.c4 = curPosition.z; // add the translation
            targetNode->mTransformation = trafo;  // assign this transformation to the node
        }
    
        // Calculate the total transformation for each bone relative to the rest pose
        for(unsigned int a=0; a<mesh->mNumBones; a++) { 
            const aiBone* bone = mesh->mBones[a];
            aiMatrix4x4 bTrans = bone->mOffsetMatrix;  // start with mesh-to-bone matrix to subtract rest pose
    
            // Find the bone, then loop through the nodes/bones on the path up to the root. 
            for(aiNode* node = scene->mRootNode->FindNode(bone->mName); node!=NULL; node=node->mParent)
                bTrans = node->mTransformation * bTrans;   // add each bone's current relative transformation
    
            boneTransforms[a] =  mat4(vec4(bTrans.a1, bTrans.a2, bTrans.a3, bTrans.a4),
                                      vec4(bTrans.b1, bTrans.b2, bTrans.b3, bTrans.b4),
                                      vec4(bTrans.c1, bTrans.c2, bTrans.c3, bTrans.c4), 
                                      vec4(bTrans.d1, bTrans.d2, bTrans.d3, bTrans.d4));   // Convert to mat4
        }
    }