I have loaded in a model to my C++ OpenGL game. It is a COLLADA file type that I have loaded, and I setup an animation under blender for the file. The problem is I don't know how to animate the model. The Assimp documentation didn't really help me out. Their source code didn't use animations, and I can't seem to find anywhere online that someone explains how to animate your loaded model... I'm sorta wondering if someone could link me to a helpful website, or maybe just help me out, so that maybe I will understand how to do animations with assimp.
Disclaimer: I use AssimpNet which is a managed wrapper around Assimp, though the object graph used by Assimp remains unchanged so the process is equivalent no matter the platform.
I personally think Assimp is a wonderful idea, and has saved me countless hours, but translating their graph to your engine is writing an importer and learning everything required to do so.
I am assume you are referring to Skeletal animation, rather than blendshapes or just individual nodes (though the latter I'll mention anyway).
As you'll know, Assimp collects entities in your source file in a tree of
nodes. These nodes can have things like
meshes attached to them, to identify visual geometry, or they can be 'abstract' nodes which exist to identify the world transforms of things like lights, and in your case, bones. Many file formats will 'flatten' the hierarchy so you will have a model with a single level of children, each one with a mesh. COLLADA is not one of those, and therefore its a little more complicated because if you want your imported model to behave correctly, you need to preserve its structure.
Take the above diagram. The dashed boxes are Node objects, some have meshes but all exist within the graph eminating from the Root Node which you find in the scene object.
Importing the Animations
Importing the animation data is the simplest part of the process. The
aiScene object will have a member
aiAnimations which is a list of
aiAnimation objects. Each
aiAnimation will have a list of
aiNodeAnim* objects, in the
Essentially, each channel controls a single node, and contains a list of keyframes, each with a scale, a rotation, and a translation value for this node at a specific point in time. Animation tracks identify nodes to control via their name.
(It is worth nothing at this point, that some exporters (such as 3DSMax) combine animations in odd ways, resulting in a single animation containing tracks for unrelated nodes, so if you are not animating a skinned mesh, make sure you check all the animation objects!)
So, say you are animating "Box". You have already imported the node 'Box' with its geometry and you have an item in your world with a transform. To animate the box, get the animation from
aiAnimations and iterate over each
aiNodeAnim (channel) until you find one with the mNodeName matching "Box". Then, iterate over each of the Position, Scale, and Rotation keyframes in the
mRotationKeys members, transforming the value (Vectors or Quaternions, and the time offset) into whatever structure your application stores animation data in.
Then just pick a keyframe/interpolate between them as usual and apply the transform specified by the frame to that nodes local transform and see it move!
Extracting skeletal animation is an identical process, since in Assimp bones are just other nodes. The skinning data is slightly trickier. To animate a skinned model you need three things:
The transforms for each bone from the animation (which we get above)
The hierarchy of the skeleton
The skinning transforms for each bone (a.k.a inverse bind pose transforms) which we get from the mesh objects.
The first thing to do, though, is find the skeleton.
Finding the Skeleton
The example diagram above is what you will find in every COLLADA DOM I have seen so far. That is, the skeleton will exist as a separate tree of nodes, sibling to the 'visual' geometry just under the root node.
Assimp makes no distinction between 'bone nodes' and 'nodes', which means your importer will have to engage in some guesswork to pick the right starting node before reading in the whole tree.
The reason we need to find the skeleton is because, as can be seem from the diagram, each bone is parented to each other bone; the transforms extracted from the animation tracks/channels need to be combined with the transforms from other tracks to move the vertices correctly.
The way I do it is this:
Go over all the meshes and identify all the bones that they use, then for each bone, I look for a node in the object graph under root with the same name. I check how far down in the tree from root this bone is. The bone with the lowest value (degree of separation) is the root of the skeleton.
Once you have this, it is a case of reading in this node and each of its children, storing the transform of the node, and its parent node's name. Using this information, you can look up the parent for each bone.
This is the trickiest bit, extracting the bones and weights from each mesh. The member you are looking for this time in
mBones and it will be a member of
aiMesh. It contains two things of interest:
mOffsetMatrix- this is the skinning transform, or Inverse Bind Pose transform - you want to store this for each bone along with the bones original transform and the name of its parent.
mWeights- these are the vertex weights - and they are, well, sort of backwards!
Each aiVertexWeight object contains mVertexId (which identifies the vertex) and mWeight, which is the weighting of the bone. The mVertexId is the index of the vertex in the 'vertex array' that serves the mesh. What that means is that you have to define a 'master list' of bone names, so that you can assign a number to each bone and have it refer consistently to the same bone. Then you need to look up that number for the name of the aiBone object which contains the vertex weight. Now you can update the vertex in your mesh with the bone ids and weights.
(You see what I mean about backwards, the weight contains the vertex id, while its parent contains the bone - although it makes it easier to write importers)
Now you should have, for non-skinned items, a list of keyframes which define transforms of a node (identified by the name) at specific times. Which is all you need to animate a node (just update its local transform).
If you have a skinned mesh then you should have:
a. A list of bones in a specific order
b. A list of bone ids and their weights, for each vertex in the mesh, ready to be packed into your vertex data array.
c. A list of skinning/inversebindpose transforms, one for each bone in the list in (a)
d. The parent for each bone in (a)
e. The bind pose transform for each bone in (a) (same place you got (d), or just take the inverse of the skinning transform)
This is the "standard dataset" for skeletal animation, and if you know about skeletal animation then it's all you need! If not, that best be a different question since it's quite a large area ;)
I hope that has helped, or at the very least not added to the confusion. Good luck!
(*Animations also contain mMeshAnim which allow for animating the mesh directly, I assume you are not talking about this just yet)
I was waiting for a more experienced answer, but as nobody seems to have anything to help you, I will give you the informations I found.
Obviously there is a lack of documentation about Assimp animations and except if you dig into the Assimp code directly it seems that there is no available informations about this precise point.
But I did found this forum thread where you can see some interesting code portions wrote in order to import and use animations.
The other potentially useful information I found is this blog entry where you can find an Open Asset Import Library animation loader with the code which is free for use for anyone for any purpose (as wrote at the end of the entry).
I hope this could help...