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.
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
:
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?