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\$
3
  • 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\$ Commented Jan 23, 2020 at 16:14
  • \$\begingroup\$ Thank you for the comment….See my edited post….. see if there is something wrong? \$\endgroup\$ Commented Jan 23, 2020 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
    Commented Jan 24, 2020 at 18:50

1 Answer 1

1
\$\begingroup\$

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

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

You must log in to answer this question.

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