I used to have a personal blog where I posted articles about technical issues I was working with at the time. Rather than just let them sit collecting dust, I’ve decided to re-publish some of them here under a special category.
I hope you find them interesting! And without further ado, here is the first:
Originally posted Friday, May 13, 2011 at 10:24pm
I finally have multiple-bone skinning working! I think I can summarize the last few days as “Wow, that was a pain”. On the surface, a hierarchical animation system sounds easy: “Just go down the tree and accumulate matrices as you go”, but as is usually the case, the devil is indeed in the details.
I would love to make this blog post a full tutorial on skinning, but unfortunately I still don’t completely understand it myself. I slapped in some foreign code for nasty jobs so that I could just focus on getting the bones to animate and stay attached to the skin. Also because the code was written by accumulating many tests, it could be improved quite a bit.
Here are some of what I will affectionately call “the devil’s details”. Hopefully these will help someone who is learning to write a skinning system. Keep in mind that some of these might only apply to the Milkshape3D model format.
Not all Vertices will be Attached to a Bone
While this might not be the case in most animations, you should make sure that your system won’t explode when this situation is encountered. One of the animations I tested my code on was a brick hurtling towards a brick wall, bouncing off the wall and falling onto the ground. The wall was part of the animation, but was not attached to a bone.
Keyframes are Defined per-Bone, not Globally
Rather than having a list of keyframes which each have a list of the bones that they affect, each bone has a list of keyframes.
Calculating Keyframe Indices; not as Easy as You Might Think
The bones are animated by interpolating between two keyframes, keyframen-1 and keyframen, so you need to figure out which two keyframes the current animation time (abbreviated as t) is in between. You also need to gracefully handle the situation where t can be either before the joint’s first keyframe (keyframef) or after its last keyframe (keyframel).
Currently, my code finds these two keyframes as follows (given that keyframes list is a list of all the joint’s keyframes, and it is not empty):
- Step through keyframes list chronologically until a keyframe is found with a timestamp after t or the end of keyframes list is reached.
- If the end of keyframes list was reached, let keyframen equal keyframel. Otherwise, let keyframen equal the keyframe that was found.
- If keyframen is equal to keyframef, let keyframen-1 equal keyframef. Otherwise, let keyframen-1 equal the first keyframe chronologically before keyframen.
There are two cases where keyframen-1 will equal keyframen: If t is before keyframef, or if t is after keyframel. If keyframen-1 equals keyframen, interpolation is not required.
Keyframe Positions and Orientations are Relative to the Bone’s Original Position and Orientation
This was one of the big ones for me. I did not figure this out until well into development, so I had to basically rewrite the skinning system. This means that in order to transform a point to where it should be for the given keyframe, the point must be in the same coordinate space as the bone.
Convert Orientations from RH to LH, not just Positions!
This was the big one. Because of the previous “detail”, things can become really out of sorts if you get rotation wrong. If a parent joint is in a certain orientation, all movement is going to depend on that orientation being correct. If the orientation is wrong, the vertices attached to the joint will be in the wrong spot. This problem gets worse and worse the further down the hierarchy you go. As far as converting the orientations goes, I’m not entirely sure how it works. What ended up working for me was to invert z-axis positions and z-axis rotations, but I have read elsewhere that only the x and y axis rotations should be inverted when z-axis positions are inverted. I’ll have to do more research into this, but it is working fine for now!
Bone Matrix, what is it?
This had me confused for a while. Basically, the bone matrix needs to transform a vertex in model space (which is attached to the bone) into the coordinate space of the bone (without any animations), apply the keyframe transformation, then transform back into model space.