Jump to content
  • Advertisement
bzt

Need help with Assimp animation

Recommended Posts

Hi,

I'd like to convert two aiScenes. Scene A is loaded from an arbitrary model, which can have any node structure and any mTransformations. Scene B is a more digestable, strict one: it's mRootNode has the mTransformation of identity matrix, and has all the meshes. Skeletons are stored as mRootNode's children, without meshes, their mTransformation matrices converted for the new mRootNode.

I'm able to convert everything, except for aiNodeAnim objects. I convert all mesh vertices, for bone nodes I calculate mTransformation for world A, multiply it by parent's world matrix' inverse. For offsetmatrix in B, I multiply the parent node's world matrix in B with this, and take it's inverse. Everything seems to be fine. I use a slightly modified version of assview.c to visualize the bind-pose and the animation-pose for the first frame.

Now for the animation-pose, I get the referenced node, and replace it's mTransformation by the matrix composed from the first element  of mPositionKeys, mRotationKeys, mScalingKeys for each bone (channel). My first question is, how come that these sometimes reference a node which is not part of the skeleton (they have meshes, but no aiNodes)? I fix this by generating aiNodes in that node in A for the same node as bone and weight of 1.0, calculating the offsetmatrix from the node's mTranslation in A.

I repeat the step above for all channels, and I assume I should have the first frame's animation-pose in the node structure of A. Then I convert every mTransformation matrices from world A into world B, I decompose them into mPositionKeys,mRotationKeys,mScalingKeys for B.

The strange thing is, if I don't change the mTransformation with the aNodeAnim's data, then I can generate an animation-pose for B correctly. However if I recalculate mTransformation in A using aiNodeAnim's keys, then I get distorted results.

So in assview, bind-pose for A works, animation-pose for first frame of A works; bind-pose for B works, but first animation-pose of B only works if I leave the mTransformations of A as-is, and I don't replace them with composed aiNodeAnim keys. It is strange, because the assview.c does nothing special, it composes the mTransformation matrices exactly the same way as I'm trying to (and if I don't change those, the bind-pose A copied to animation-pose B aiNodeAnims correctly, meaning everything else I do is correct, I think).

What conversion do I miss for aiNodeAnim? Any help would be appreciated
bzt

Share this post


Link to post
Share on other sites
Advertisement

I've found the root cause of my problem... As it turned out, Assimp documentation is incorrect, and so are all the forum posts and tutorials (can you believe it?). They all say that the offsetMatrix is an inverse bind-pose matrix, that takes the vertices from mesh-space into bind-pose bone space.

THAT'S JUST ISN'T TRUE. For all who came after me, having the same issue and finds this topic, beware, mOffsetMatrix and inverse of the bone node's world mTranformation matrix ARE NOT the same, they are two completely different things. You simply can't calculate one from the other! The best you can do is forget about Assimp provided mTranformation on the bone node, and recalculate the bind-pose yourself from the aiBone offset matrix in the mesh.

I was lucky, I've found a model that proves this pretty clearly. minetest mobs_monster download it, and in the models directory you'll find mobs_spider.x which is a simple rigged and animated model. That has an offset matrix which is completely unrelated to the bind-pose node structure!

Cheers,
bzt

Share this post


Link to post
Share on other sites
15 minutes ago, bzt said:

As it turned out, Assimp documentation is incorrect, and so are all the forum posts and tutorials (can you believe it?).

I was unfortunate enough to start using glm math library shortly after they changed some default parameters from degree to radian. All the online tutorials used degree and happily compiled but didn't work.  After I gave up and came back several months later, I found out what the problem was. I really wanted to strangle that dumb happy bunny dude.

Share this post


Link to post
Share on other sites

Hi,

Thanks for your answer! I really don't understand what's the point in loading mTransformation and mOffsetMatrix separately, when they should be related. Anyway, as it turned out, my problem was with the assimp math library after all. When converting from A to B, one of the mTransformation matrices had the following rotation matrix in the left upper corner:
-1 0 0
0 -1 0
0 0 -1
Now decompose matrix returned quaternion 0, 0, 0, 0.7071069 for this. However aiQuaternion(0, 0, 0, 0.7071069).GetMatrix() returns
0 0 0
0 0 0
0 0 1
Which is not exactly the same...

Anyway, back to square one. I still have the same problem:
1. if I display only the mesh, everything is OK in B (meaning I have transformed the vertices from world A into world B correctly)
2. if I display the first frame of B with unchanged mTranslations in node tree A, everything is OK in B (meaning I'm converting the node tree into aiVectorKey and aiQuaternionKey records in B's mPositionKeys, mRotationKeys, mScalingKeys correctly, and I calculate the mOffetMatrix in each aiBone in B correctly)
3. if I recalculate the mTransformations in A using the first elements of mPositionKeys, mRotationKeys, mScalingKeys in A's aiAnimation before I convert the node tree into B's mPositionKeys, mRotationKeys, mScalingKeys, then everything goes sh*t.
4. if I use the first elements of mPositionKeys, mRotationKeys, mScalingKeys on A, and transform the vertices in the mesh with the bone's world matrix multiplied by the offset matrix in A, then the frame shows up correctly in B's mesh (assuming I'm displaying the B mesh only)

I still have no clue, what could I possibly miss, specially since I can display the first frame of A correctly if I don't convert it into B, and also if I manually convert the vertices in A for the first frame before I convert the whole thing into B. The only thing that bothers me, is one of the mPositionKeys, mRotationKeys, mScalingKeys channels refer to a node which is not part of the bone-hierarchy (that node has the mesh, and it is not referenced by any aiBone.mWeights records). How is this possible in a skeletal animation in the first place? And how to compensate for it? I'm starting to think that must be the reason, because I have eliminated all the other possibilities.

Why is it so hard to get a skeleton pose from assimp? Why aren't there any good, comprehensive tutorials on this? (Nope, ogldev's 38-skeletal-animation tutorial does not explain anything important, and it most definitely does not explain which matrices and transformation are in world space and which ones are in bone-relative space, and what nodes should be referenced by name from certain objects.) I seriously doubt I'm the first person trying to do this...

Please, I could use any help on this,
bzt

Share this post


Link to post
Share on other sites

If this helps, this is what I got. As you can see the movements are fine (in bone-space), it's just their global position and orientation that's off. This only happens if I recalculate mTranslation in A from mPositionKeys, mRotationKeys, mScalingKeys (which supposed to be bone node-relative if I my understanding is right). On the left you can see aiScene A, on the right aiScene B.

Thanks,
bzt

EDIT: it looks like I can't upload anim gifs. Sorry.

 

spider.gif

Edited by bzt

Share this post


Link to post
Share on other sites

Hi,

I think I finally cracked this case. It looks like when mRootNode->mTransformation is not the identity matrix, then assimp compensates by generating mesh transforms in skeletal animation... (or at least that particular format importer does).

So the final conversion matrix looks like this: B1 * B2 * B3 * O * M (where Bx are the bone node's mTransformation matrices, O is the mOffsetMatrix, and M is the messed up mTransformation referenced by aiNodeAnim).

I've found a really ugly workaround, but I really don't like it: for each aiNodeAnim that are not referencing a bone node, I look up the mWeights, and multiply the bone node's mTransformation by the transpose of the aiNodeAnim's mTransformation. So I get B1 * B2 * B3' * O, where B3' is B3 * Mt.

I use this when I convert Assimp structures into my own format. I've uploaded a few (free to use) models with skeletal animation. Check out if you'd like, the format can be loaded using an stb-style single header ANSI C/C++ SDK and uses MIT license. It is nothing like Assimp 😄, it is simple, easy to use and lightning fast. All the Assimp hacks (and this particular workaround) are implemented in the converter utility, m3dconv. https://gitlab.com/bztsrc/model3d

Cheers,
bzt

model3d.thumb.png.55ec36f695e05e933e42feee225c616e.png

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!