New Ardor3D Animation Features - Part 3

Parts 1 and 2 on the new Ardor3D Animation Features covered nearly everything you need to know, so we'll wrap it up by discussing a few loose ends.

First, let's talk about weights.  When you put together states and layers, you'll likely use a weight based blending to bring together multiple sources.  For state transitions, the weighting is mostly out of your hands as it is time-based, but for other types of weighting (such as the blending between two layers or a lerp node in a state's blend tree) you'll often find yourself wanting to directly modify it on the fly based on the current state of your game/application.  Again, you run into a problem here...  How to reach into an animation system and access that BlendTreeSource object or LayerBlender when it might be created and maintained outside of your control (e.g. such as by our JSON based "scripting" system.)

To address this, we've taken the actual weight value away from these types of objects and moved it to a map in the AnimationManager.  The blend object itself instead maintains a key used to pull the weight from the manager.  In this way, you merely have to know the key you are altering and have a link to the manager it lies under, letting your aritist/animator/tool tweak things to their heart's content.


Shifting gears, so far our animations have been all about moving skeleton joints around, but the animation system was actually built to handle any generic sample-based data set.  To take advantage of that, a new type of channel called a TriggerChannel was added to give you a String-based, trigger-callback system that can be plugged into existing animations or added as standalone clips.

Here's how a trigger channel works.  The channel is made up of multiple (nullable) String "samples" that change over time.  For example, it might have no sample at time index 0.  Then at 2 it might have a sample called "left_step" and at 4 a sample called "right_step" and at 5, null again.  As the animation this channel belongs to plays, the current sample String is collected.  The applier logic has access to this current string and can then act on it.

Our default applier logic acts on the string by executing callbacks associated with the given String.  The callback is only called on unique samples, so while in the current sample, it will only be called once.  Your callback logic can then be whatever you like... play a sound, a particle effect, trigger a different animation, etc.  In the case of the above left_step, right_step, it might be programmed to play footstep sounds on the appropriate time sequences.  If this was a channel in our walk state, then walking would also trigger the footstep sounds.

TriggerChannels are created/setup only in Java code currently, but they will soon be added to the JSON-based input system as well.

Here's an excerpt from AnimationDemoExample showing how you might put this together. The method shown makes a channel that has a "punch_fire" sample set at the mid point of the given source clip. The trigger callback adds (in the update queue) particles to the scene:

private void addFireballTrigger(final SimpleAnimationApplier applier, final 
AnimationClip punchClip) {
    final float max = punchClip.getMaxTimeIndex();
    final TriggerChannel triggerChannel = new TriggerChannel("punch_fire", new 
float[] { 0, max / 2, max / 2 + 0.25f }, new String[] { null, "fist_fire", null });
    punchClip.addChannel(triggerChannel);
    applier.addTriggerCallback("fist_fire", new TriggerCallback() {
        public void doTrigger() {
            GameTaskQueueManager.getManager(_canvas.getCanvasRenderer()
.getRenderContext()).update(
                new Callable() {
                    @Override
                    public Void call() throws Exception {
                        addParticles();
                        return null;
                    }
                });
            }
        });
    }

Here's a video of Ardor3D's AnimationDemoExample in action:



Finally, let's cap this series of posts off with a webstart of the AnimationDemoExample in Ardor3D. Note that I left it loading from the original Collada files, so it takes a few seconds to start. Also, it's been a while since I've packaged things for webstart, so bear with me if it doesn't work for some reason.

You'll need Java 6.0. (Mac users may need to save the jnlp to disk, then right click and open with Java Web Start.) Drag the scene with the left mouse to rotate the camera. WASD to move.

New Ardor3D Animation Features - Part 2

In the last post, we looked at how our new animation system adds animation states and a finite state machine to enable simpler handling of character animation.  This is good, but what if we need the character to be doing two things at once... say walking and pointing at something of interest.  We'd either need to have states for all possible combinations of behavior (yikes, and probably not possible), or we'd have to start modifying the solid states' blend trees, which is hairy and goes against what states were trying to accomplish anyhow.

Enter Ardor3D's AnimationLayer class.  Animation layering allows us to play multiple, unrelated animation states on the same skeleton by blending their outputs together.  In Ardor3D this blending is done through an AnimationBlender field in AnimationLayer.  The output of the layers is gathered by walking through the layers and blending each layer with the output of the layer (or layer's blender) immediately before it.  Right now we only have a lerp layer blender, but additive blending would be easy to implement as well.  Each layer implements the finite state machine talked about in the last post and is stored in a List in AnimationManager.

When putting the content and time line of your layers together you should pay special attention to the first or "base" animation layer.  To ensure your skeleton is properly posed, this layer should have an active state containing a complete skeletal pose for your character at all points in the time line.  (Note: You could split this responsibility across two or more layers, just make sure you are covered.)  Other layers in your manager may or may not have full pose information, or might only have an active state at certain points in the time line.  In our AnimationDemoExample we have a layer for walk/run, one for punch (which is only active when the punch button is pressed) and one for head turning (which may or may not be enabled.)


Now that we have our pieces laid out, how do we put them together in a way that is friendly to our development / art pipeline?  We could just go ahead and program our states in Java directly, but we might want to instead do it in a way that does not require recompiling classes and is perhaps even tool friendly.

To assist in this area, we've put together a JavaScript loading system.  Your layers, states, transitions and so forth are described using JSON and then converted to Java objects for populating an AnimationManager instance.  An example of this is displayed below:

// our base layer
var baseLayer = MANAGER.baseAnimationLayer;

// up and down movement state
var stateUD = {
    "name" : "up_down",
    "tree" : {
        "clip" : {
            "name" : "up_down_clip",
            "timeScale" : -1.5
        }
    },
    "transitions" : {
        "fade" : [
                 "-", "-", // start/end window
                 "fade", // type
                 "left_right", // target
                 0.5, // fade time
                 "SCurve5" // type
                 ],
        "cut" : [
                 "-", "-", // start/end window
                 "immediate", // type
                 "left_right" // target
                 ],
    },
    "endTransition" : [
                   "-", "-", // start/end window
                   "immediate", // type
                 "up_down" // target
                 ]
};

// add to layer
baseLayer.addSteadyState(_steadyState(stateUD));
In the above example, we grab the automatically provided base layer, create a JSON structure describing a state with a simple blend tree (with just one ClipSource node), two transitions ("fade" and "cut") and an endTransition (effectively a repeat).  We then add this state to our base layer.  The function _steadyState comes from functions.js, part of the ardor3d-animations project.  Note also that it is pretty easy to tweak fade times, time scales, and so forth.

In the next post, we'll look at how to set weights without knowing the exact structure of your layers/states. We'll also look at animation triggers and finally, we'll take a closer look at the demo.

New Ardor3D Animation Features - Part 1

Recently added to Ardor3D's animation project are several new features to give you more options and ease of use with your animated characters.

First it might be helpful to look at what we already had in our first feature set.  The first cut of animation system had an animation manager that held a blend tree and a skeleton pose to apply it to.  You would set a manager up per unique animated model and the blend tree allowed you to put together complex animations using one or more animation clips.

This arrangement gives you a lot of control over animation, but quickly becomes tedious in a real system.  As Jason Gregory puts it in his excellent book on Game Engine Architecture, such a system is similar to working directly in OpenGL or DirectX - powerful, but verbose and clunky.  Controlling what animation is playing and when... or adding multiple animations together (transitions, IK, computed animations, etc.) based on the current activities in the game/application means tracking and managing that blend tree at a very low level.  You can quickly lose control.

What you need instead is a system that already knows the possible "states" your animated character can be in, and understands how to transition between them.  That's the concept behind a "finite state machine" (FSM), which is at its core a set of possible states and logic describing how to move from one to the next.

The recently released additions to Ardor3D's animation system add these states under the abstract class, AbstractFiniteState and broken into two basic categories: steady states and transition states.  SteadyState objects describe the list of base states for the animation FSM.  If you had a character with a walk animation and a run animation, walk and run might each exist as separate SteadyState instances.  Transition states do exactly what they sound like - they transition from one state to another (or sometimes the beginning of the same state).  A transition for walk->run might take both steady states and linearly interpolate between the two until we were fully in "run", and thereupon transition to the run state directly.

In Ardor3D, steady states have a map of possible transitions accessed by a String key.  The keys can be anything you like, but often work best if you use uniform values across states.  For example, if the user indicates they would like to start running, it would be nice if, regardless of the current state, we could transition by simply saying .doTransition("run").  Steady states also have an optional "end transition" field, allowing you to dictate what action will be taken when the steady state reaches its end.

Ardor3D currently has four types of state transitions available, including fade, synchronized fade, frozen and immediate.  Fade and Synchronized fade are similar in that they play the start and end state during the transition and change over time, from 0 to 100% how much of each clip is included.  Fade will simply start the end state from the beginning at the start of the transition.  Sync fade will synchronize the start times of the two clips and then do the transition.  This is helpful for matching up clips that have similar phases.  Frozen transitions also work similarly, but freeze the start state at the beginning of the transition and blend in the end state over time.  Freeze transitions are often most useful for blending between two dissimilar poses.  All three of these types can use linear blending, a cubic s-curve or a quintic s-curve to blend between the two states during the transition.  The final transition type is very simple and just immediately cuts from one state to the next.  It is often useful as an end transition, acting as a "repeat" command.

Transitions have one more common trait that can be useful - time windows.  By default, if you ask for a state to do a particular transition, it will happen.  But lets say that you have a transition that just doesn't make sense at certain points in the animation.  For example, you may be throwing a punch and be asked to animate as if you yourself took a punch.  It might look okay to do so as you wind up for the punch or perhaps after the punch landed and your hand is coming back, but not as your punch is landing.  In such a case you could set up a time window, stating that this transition should only happen when the state is in a certain part of its life cycle.

There's a lot more to go over, but this is already getting long enough, so let's call it Part 1.  In the next part we'll go over animation layering and the JSON based input you can use for creating your layers and FSM.  For the impatient, check out AnimationDemoExample and its related js file and get your hands dirty now. :)