0
\$\begingroup\$

I'm trying to animate a skeletal mesh. The mesh loads with no problems and everything is set up correctly. My problem is that when I calculate the matrix for a given keyframe the mesh goes nuts.

Here is an image of the problem.

enter image description here

The mesh on the right is using the bone->transform as the final matrix. The one on the left is using the matrix calculated from keyframes. I don't know where I'm going wrong with this.

Here is my code:

The AnimationClip is where all keyframes are stored:

Matrix4 AnimationClip::GetTransform(Bone *bone, float deltaTime)
{
Channel* channel = channels[bone->name];
if (channel == nullptr)
    return bone->transform;
else
    return channel->Update(deltaTime);
}

///////////

Channel::Channel(aiNodeAnim* animNode)
{
name = string(animNode->mNodeName.data);

for (GLuint k = 0; k < animNode->mNumPositionKeys; k++)
{
    aiVectorKey vec = animNode->mPositionKeys[k];
    positions.push_back(
        Keyframe<Vector3>(
        (float)vec.mTime,
            Vector3(vec.mValue.x, vec.mValue.y, vec.mValue.z)));
}

for (GLuint k = 0; k < animNode->mNumScalingKeys; k++)
{
    aiVectorKey vec = animNode->mScalingKeys[k];
    scalings.push_back(
        Keyframe<Vector3>(
        (float)vec.mTime,
            Vector3(vec.mValue.x, vec.mValue.y, vec.mValue.z)));
}

for (GLuint k = 0; k < animNode->mNumRotationKeys; k++)
{
    aiQuatKey vec = animNode->mRotationKeys[k];
    rotations.push_back(
        Keyframe<Quaternion>(
        (float)vec.mTime,
            Quaternion(
                vec.mValue.x,
                vec.mValue.y,
                vec.mValue.z,
                vec.mValue.w)));
}
}

Matrix4 Channel::Update(float animTime)
{
return
    CalculatePosition(animTime) *
    CalculateRotation(animTime) *
    CalculateScaling(animTime);
}

Matrix4 Channel::CalculatePosition(float animationTime)
{
return 
    Matrix4::CreateTranslation(
        CalcInterpolatedPosition(animationTime));
}

Vector3 Channel::CalcInterpolatedPosition(float animationTime)
{
if (positions.size() == 1)
    return positions[0].value;

GLuint positionIndex = FindPosition(animationTime);
GLuint nextPositionIndex = (positionIndex + 1);

float deltaTime = 
    positions[nextPositionIndex].time - positions[positionIndex].time;
float factor = 
    (animationTime - (float)positions[positionIndex].time) / deltaTime;

Vector3 startPos = positions[positionIndex].value;
Vector3 endPos = positions[nextPositionIndex].value;

Vector3 delta = endPos - startPos;

return startPos + delta * factor;//Vector3::Lerp(startPos, endPos, factor);
}

GLuint Channel::FindPosition(float AnimationTime)
{
for (GLuint i = 0; i < positions.size() - 1; i++) {
    if (AnimationTime < (float)positions[i + 1].time)
        return i;
}
return 0;
}

And here is my SkinnedMesh code where I'm fetching the keyframes:

void SkinnedMesh::BoneTransform(
double delta, 
vector<Matrix4>& transforms,
AnimationClip *anim)
{
Matrix4 identity_matrix = Matrix4::Identity();

float ticksPerSecond = 
    anim->ticksPerSecond == 0 ? 25.0f : anim->ticksPerSecond;

double time_in_ticks = delta * ticksPerSecond;
float animation_time = 
    fmod((float)time_in_ticks, anim->duration);

UpdateTransforms(
    animation_time, 
    anim, 
    rootBone,
    identity_matrix);

transforms.resize(m_num_bones);

for (GLuint i = 0; i < m_num_bones; i++)
    transforms[i] = 
        m_bone_matrices[i].final_world_transform;
}

void SkinnedMesh::UpdateTransforms(
float p_animation_time,
AnimationClip *anim,
Bone *parentBone,
Matrix4& parentTransform)
{
Matrix4 boneTransform =// parentBone->transform;
    anim->GetTransform(parentBone, p_animation_time);

Matrix4 global_transform =
    parentTransform * boneTransform;

if (m_bone_mapping.find(parentBone->name) !=
    m_bone_mapping.end()) // true if node_name exist in bone_mapping
{
    GLuint bone_index = m_bone_mapping[parentBone->name];
    m_bone_matrices[bone_index].final_world_transform =
        m_global_inverse_transform *
        global_transform * 
        m_bone_matrices[bone_index].offset_matrix;
}

for (vector<Bone*>::iterator it =
    parentBone->children.begin();
    it != parentBone->children.end(); 
    it++) {
    UpdateTransforms(
        p_animation_time,
        anim,
        (*it), 
        global_transform);
}
}

void SkinnedMesh::Render(Shader* shader, AnimationClip *clip)
{
vector<Matrix4> transforms;
BoneTransform(
    (double)SDL_GetTicks() / 1000.0f,
    transforms,
    clip);

for (GLuint i = 0; i < transforms.size(); i++)
{
    GLfloat values[16];
    Matrix4::ValuePointer(transforms[i], values);

    string a = "jointTransforms[";
    a.append(to_string(i));
    a.append("]");

    glProgramUniformMatrix4fv(
        shader->GetID(),
        shader->GetUniformLocation(a.c_str()),
        1,
        GL_FALSE, 
        (const GLfloat*)values);
}

for (unsigned int i = 0; i < meshes.size(); i++)
    meshes[i]->Render(shader);
}

It seems like the matrices are all wrong and therefore deforming the mesh. Can you help me?

EDIT :

The mesh on the right has this code :

Matrix4 AnimationClip::GetTransform(Bone *bone, float deltaTime)
{
Channel* channel = channels[bone->name];
if (channel == nullptr)
return bone->transform;
else
return bone->transform;
}

EDIT 2 :

As daniel_1985 pointed out my matrices might have to be transposed but I'm in doubt…

This is how I'm converting Assimp matrices now

Matrix4 Matrix4::ToMatrix4(aiMatrix4x4 mat)
{
Matrix4 result;

result.row0 = Vector4(mat.a1, mat.b1, mat.c1, mat.d1);
result.row1 = Vector4(mat.a2, mat.b2, mat.c2, mat.d2);
result.row2 = Vector4(mat.a3, mat.b3, mat.c3, mat.d3);
result.row3 = Vector4(mat.a4, mat.b4, mat.c4, mat.d4);

return result;
}

This is the result if I set my skinned mesh to its bone->transform:

enter image description here

So what am I doing wrong? Is the matrix being correctly converted or not? If it is, why does my mesh get deformed like this?

I am building my bone hierarchy like this :

void SkinnedMesh::BuildBoneHierarchy(
aiNode* node, 
Bone* parentBone)
{
if (parentBone == nullptr)
{
    rootBone = new Bone();
    rootBone->name = string(node->mName.data);
    rootBone->transform =
        Matrix4::ToMatrix4(node->mTransformation);

    for(GLuint i = 0; i < node->mNumChildren; i++)
        BuildBoneHierarchy(
            node->mChildren[i], 
            rootBone);
}
else
{
    Bone* bone = new Bone();
    bone->name = string(node->mName.data);
    bone->transform =
        Matrix4::ToMatrix4(node->mTransformation);
    parentBone->children.push_back(bone);

    for (GLuint i = 0; i < node->mNumChildren; i++)
        BuildBoneHierarchy(
            node->mChildren[i],
            bone);
}
}

UPDATE :

So I've succesfully fixed my skinned mesh for its pose by calculating manually each bone inverseTransform manually like this :

void Bone::CalcInverseBindTransform(Matrix4 parentBindTransform)
{
Matrix4 bindTransform = parentBindTransform * transform;
inverseTransform = Matrix4::Invert(bindTransform);

for (vector<Bone*>::iterator iter = children.begin();
    iter != children.end();
    iter++)
    (*iter)->CalcInverseBindTransform(bindTransform);
}

In my UpdateTransforms I'm setting the mult like this:

GLuint bone_index = m_bone_mapping[parentBone->name];
    m_bone_matrices[bone_index].final_world_transform =
        m_global_inverse_transform *
        global_transform *
        parentBone->inverseTransform;

But when I animate it, it goes nuts. So what do I need to do now?

enter image description here

\$\endgroup\$
  • 1
    \$\begingroup\$ Assimp matrices are row-major and OpenGL expects column-major matrices. The issue you're experiencing might be caused by incorrect matrix data. In the glProgramUniformMatrix4fv() function, you have set the forth argument to GL_FALSE, indicating that you don't want to transpose the passed in matrix data. Try changing it to GL_TRUE, indicating that you indeed want the passed in matrix data to be transposed, effectively transposing it from row-major to column-major. \$\endgroup\$ – Daniel_1985 Jan 23 at 16:14
  • \$\begingroup\$ Thank you for the comment….See my edited post….. see if there is something wrong? \$\endgroup\$ – David Neves Jan 23 at 22:37
  • \$\begingroup\$ If you successfully fixed the issue, then please post an answer for it. Your current other question is only a duplicate of this one at the moment. \$\endgroup\$ – Vaillancourt Jan 24 at 18:50
1
\$\begingroup\$

Like daniel_1985 pointed out, I was wrongly converting the Assimp matrices. The convention above (ToMatrix4(aiMatrix4x4 …)) should fix things.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Don't forget to mark this answer as the correct one, if it helped you solve the problem. \$\endgroup\$ – Daniel_1985 Jan 29 at 14:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.