The Summer of Code is finally here, and the first week is already almost over! I’m sorry that I haven’t updated the blog more, but I’ve been busy working on EMI’s animations this week and I’ve made some excellent progress.
Some background: right now there is a basic implementation of EMI animations in ResidualVM, but the implementation is still far from perfect. I’m currently focusing on two major issues that I’ve identified. Firstly, the current implementation prioritizes animations in the order in which they were started, so the animation that is started last always overrides any previously applied animation (although there are specialized workarounds for some cases). Secondly, unlike in the original game, all animation transitions are instant due to the lack of animation blending. In this post I’m focusing mainly on the prioritization of animations.
If play order shouldn’t matter, then how should it be chosen which animation takes precedence over another? We could get some ideas by looking at Grim Fandango, since it is based on an older version of the same engine. In Grim, each keyframe animation contains a priority field (actually two of them, but let’s keep it simple) that control the order in which animations are applied. A higher priority animation always takes precedence over a lower priority one.
One might ask what happens if two animations with the same priority play at the same time. The answer is they are blended together, and the result is an average between the two animations. Again, the result is the same regardless of the order in which the animations were applied. I’ll describe blending in more detail in a later post.
Knowing how the priorization of animations was done in Grim, the first thing I did of course was to try to find a similar priority field in EMI’s .animb animation data format. Perhaps unsurprisingly, it turns out there is one! However, the catch is that the priority field in EMI is bone-specific. In other words, an animation may specify a different priority value for each bone that the animation is affecting. (Note: EMI uses skeletal animation.)
For example, we could have an animation of Guybrush waving both of his arms. For the left hand the priority value could be 1, and for the right hand it could be 3. In addition, we could have a standing idle animation with a constant priority of 2 for all bones. Now, if both of these animations were applied at the same time, the result would be that Guybrush would wave his right arm, but the rest of his body would follow the standing idle animation.
Typical real priority values I’ve seen so far are 0 for base poses like standing idle, 1 for walk and run, 2 for holding an object in hands, and so on.
Without animation blending, adding support for the priority value is fairly straightforward. When applying the animation to the bones of the skeleton, we can keep track of the currently applied priority for each bone. If the animation’s priority is higher than the bone’s current priority, the animation replaces the current bone transform completely. Otherwise the animation is skipped. Using this method we can apply the animations in arbitrary order and the highest priority animation is always displayed. Of course this simplistic approach is still dependent on the order in which the animations are applied in the case where animation priorities are equal.
Things start to get more complicated once animation blending is added into the mix, though. With blending, a higher priority animation may not completely replace a lower priority one if it has a blend weight lower than 100%. In that case some of the lower priority animation will “show through”. In order to implement this properly animations can no longer be applied in arbitrary order. Instead, we need to calculate the transformation for each bone by applying the animations in descending priority order, taking the blend weights into account at each step. This is complicated by the fact that since priorities are bone-specific, the order in which animations should be applied may be different for each bone.
I’ll go into details on how I solved this in the next post. In the meantime you can check out my progress at https://github.com/Akz-/residual/commits/animations.