Skeletal animation

The AnimatedModel component renders GPU-skinned geometry and is capable of skeletal animation. When a model is assigned to it using SetModel(), it creates a bone node hierarchy under its scene node, and these bone nodes can be moved and rotated to animate.

There are two ways to play skeletal animations:

Note that AnimationController does not by default stop non-looping animations automatically once they reach the end, so their final pose will stay in effect. Rather they must either be stopped manually, or the SetAutoFade() function can be used to make them automatically fade out once reaching the end.

Animation blending

Animation blending uses the concept of numbered layers. Layer numbers are unsigned 8-bit integers, and the active AnimationStates on each layer are processed in order from the lowest layer to the highest. As animations are applied by lerp-blending between absolute bone transforms, the effect is that the higher layer numbers have higher priority, as they will remain in effect last.

By default an Animation is played back by using all the available bone tracks. However an animation can be only partially applied by setting a start bone, see SetStartBone(). Once set, the bone tracks will be applied hierarchically starting from the start bone. For example, to apply an animation only to a bipedal character's upper body, which is typically parented to the spine bone, one could set the spine as the start bone.

Animation triggers

Animations can be accompanied with trigger data that contains timestamped Variant data to be interpreted by the application. This trigger data is in XML format next to the animation file itself. When an animation contains triggers, the AnimatedModel's scene node sends the E_ANIMATIONTRIGGER event each time a trigger point is crossed. The event data contains the timestamp, the animation name, and the variant data. Triggers will fire when the animation is advanced using AddTime(), but not when setting the absolute animation time position.

The trigger data definition is below. Either normalized (0 = animation start, 1 = animation end) or non-normalized (time in seconds) timestamps can be used. See bin/Data/Models/Ninja_Walk.xml and bin/Data/Models/Ninja_Stealth.xml for examples; NinjaSnowWar implements footstep particle effects using animation triggers.

<animation>
<trigger time="t" normalizedtime="t" type="Int|Bool|Float|String..." value="x" />
<trigger ... />
</animation>

Manual bone control

By default an AnimatedModel's bone nodes are reset on each frame, after which all active animation states are applied to the bones. This mechanism can be turned off per-bone basis to allow manual bone control. To do this, query a bone from the AnimatedModel's skeleton and set its animated_ member variable to false. For example:

Bone* headBone = model->GetSkeleton().GetBone("Bip01_Head");
if (headBone)
headBone->animated_ = false;

Combined skinned models

To create a combined skinned model from many parts (for example body + clothes), several AnimatedModel components can be created to the same scene node. These will then share the same bone nodes. The component that was first created will be the "master" model which drives the animations; the rest of the models will just skin themselves using the same bones. For this to work, all parts must have been authored from a compatible skeleton, with the same bone names. The master model should have all the bones required by the combined whole (for example a full biped), while the other models may omit unnecessary bones. Note that if the parts contain compatible vertex morphs (matching names), the vertex morph weights will also be controlled by the master model and copied to the rest.

Node animations

Animations can also be applied outside of an AnimatedModel's bone hierarchy, to control the transforms of named nodes in the scene. The AssetImporter utility will automatically save node animations in both model or scene modes to the output file directory.

Like with skeletal animations, there are two ways to play back node animations:

  • Instantiate an AnimationState yourself, using the constructor which takes a root scene node (animated nodes are searched for as children of this node) and an animation pointer. You need to manually advance its time position, and then call Apply() to apply to the scene nodes.
  • Create an AnimationController component to the root scene node of the animation. This node should not contain an AnimatedModel component. Use the AnimationController to play back the animation just like you would play back a skeletal animation.

Node animations do not support blending, as there is no initial pose to blend from. Instead they are always played back with full weight. Note that the scene node names in the animation and in the scene must match exactly, otherwise the animation will not play.