How to rotate a skinned model's bones in c++ using assimp?
Asked Answered
C

1

6

I could load an animation of a skinned model using assimp by interpolating between key-frames. Now, I have been trying to orient or position bones from a user defined transformation matrix instead of just loading it from an animation file. Like, rotating an arm by a certain angle in which the angle would be specified by the user. I loaded the model in it's bind pose as:

void recursion(aiNode* gNode)
{
 std::string gNodeName(gNode->mName.data);
 if(boneMapping.find(gNodeName) != boneMapping.end())
 {
  //if node corresponds to a bone,
  Matrix4f boneMatrix = IdentityMatrix;
  aiNode* tempNode = gNode;
     //find combined transform of a bone
     while(tempNode != NULL)
     {
        Matrix4f NodeMatrix(tempNode->mTransformation);
        boneMatrix =  NodeMatrix * boneMatrix;
        tempNode = tempNode->mParent;
     }

     pBoneData[boneId].FinalTransform = GlobalInverseTransform * boneMatrix * pBoneData[boneId].OffsetMatrix;
  }
 for(int i = 0; i < gNode->mNumChildren; i++)
 { //repeat this process for child nodes
    recursion(gNode->mChildren[i]);
 }
}

To orient one of the meshes of a model using transformation matrix, I tried searching for name of the bone that corresponds to the parent-bone of the mesh and then replace it's node matrix with the required matrix. However, that didn't work at all as it deformed a bone at a different mesh.

enter image description here

The model to the right is in T pose and I intended to modify it by rotating the bone at the neck by a 45 deg angle, but it remained the same and the leg portion got deformed as seen in model to the left. So, any links to existing articles or answers could be really helpful.

Curnin answered 21/3, 2015 at 15:13 Comment(2)
'boneId' is boneIndex for the bone, if anyone was wonderingCurnin
Any luck with it? Please mark the question as answered if it worked.Cedeno
C
11

I think the order of the multiplication matrix is wrong in this line:

boneMatrix =  NodeMatrix * boneMatrix;

it should be:

boneMatrix = boneMatrix * NodeMatrix;

In OpenGL the order of multiplication is reversed.

Although this approach doesn't sound right to me:

while(tempNode != NULL)
{
    Matrix4f NodeMatrix(tempNode->mTransformation);
    boneMatrix =  NodeMatrix * boneMatrix;
    tempNode = tempNode->mParent;
}

you are multiplying your local transformation by the bone matrix in bone space and not in world space. This is why your character appears deformed.

I solved this problem for my animation project, using assimp and OpenGL. I used a slightly different approach by saving all assimp information into my own data structure. (skeleton and bones). By the look of your code and the data structure you are using I guess you are basing your code upon this tutorial.

Here is the code I used, I'm sure you can use it to compare with your implementation:

glm::mat4 getParentTransform()
{
    if (this->parent)
        return parent->globalTransform;
    else 
        return glm::mat4(1.0f);
}

void updateSkeleton(Bone* bone = NULL)
{ 
    bone->globalTransform =  bone->getParentTransform() // This retrieve the transformation one level above in the tree
    * bone->transform //bone->transform is the assimp matrix assimp_node->mTransformation
    * bone->localTransform;  //this is your T * R matrix

    bone->finalTransform = inverseGlobal // which is scene->mRootNode->mTransformation from assimp
        * bone->globalTransform  //defined above
        * bone->boneOffset;  //which is ai_mesh->mBones[i]->mOffsetMatrix


    for (int i = 0; i < bone->children.size(); i++) {
        updateSkeleton (&bone->children[i]);
    }
}

EDIT:

For matrix conversion from Assimp to glm I used this function:

inline glm::mat4 aiMatrix4x4ToGlm(const aiMatrix4x4* from)
{
    glm::mat4 to;


    to[0][0] = (GLfloat)from->a1; to[0][1] = (GLfloat)from->b1;  to[0][2] = (GLfloat)from->c1; to[0][3] = (GLfloat)from->d1;
    to[1][0] = (GLfloat)from->a2; to[1][1] = (GLfloat)from->b2;  to[1][2] = (GLfloat)from->c2; to[1][3] = (GLfloat)from->d2;
    to[2][0] = (GLfloat)from->a3; to[2][1] = (GLfloat)from->b3;  to[2][2] = (GLfloat)from->c3; to[2][3] = (GLfloat)from->d3;
    to[3][0] = (GLfloat)from->a4; to[3][1] = (GLfloat)from->b4;  to[3][2] = (GLfloat)from->c4; to[3][3] = (GLfloat)from->d4;

    return to;
}

Hope it helps.

Cedeno answered 21/3, 2015 at 15:37 Comment(7)
Thanks for the reply. Yeah, I followed the tutorial you mentioned above and it uses a similar method as you have. But assimp matrices are row major matrices while opengl uses column major matrix. have you done conversion somewhere in between to make all the matrices of same type?Curnin
yeah I did, I will edit my answer adding the conversion matrix from assimp, so you can compare itCedeno
just to be sure, you've changed node->mTranformation to glm::mat4 right?Curnin
yeah, in order to use glm I converted the assimp matrices. Although you are using the same approach converting matrices into Matrix4fCedeno
also, would't it transform bones from the current 'bone' till the bottom of the hierarchy. For example, if 'bone' was somewhere close to root bone, wouldn`t it transform all the bones lying below it in the heirarchy.Curnin
that's the point of having a skeleton represented as a tree. Once you applied a transformation to a bone , this must reflect on all the children in the hierarchy of that boneCedeno
Let us continue this discussion in chat.Curnin

© 2022 - 2024 — McMap. All rights reserved.