Skip to content

TransformationMachine

David Jewsbury edited this page Jan 26, 2015 · 5 revisions

#Transformation Machine

Animation playback has a simple goal:

  1. Read in data that has been authored by an artist
  2. Then, we need some state information, representing some animation state for "now"
  3. With this state information, produce a linear list of local-to-model transforms
  • these transforms will be used for bones and rigid nodes in later parts of the pipeline

Sometimes animation playback might produce colour values or properties values as well. But let's focus on transformation information for now.

Normally, we might view this process involving a hierarchy of bones.

Here is a diagram of a very simple animation process: HierarchicalAnimation

Notice that artist authored data + state -> shader constant array

##Triptych of classes

In XLE, there are 3 important classes that work together in the animation process:

  • the skeleton (called the transformation machine)
  • the animation data itself (called an animation set)
  • the model

Each part works separately from the others, but they can interface with each other using a "binding" object. Of particular interest is the "transformation machine." This is called a "machine" because it works like a black-box, taking as input a limited animation information and producing new information.

AnimationBindings

Above, we can see how the three objects interaction. The advantage of this system is we can implement each class however we like, independently from the other classes.

In particular, or TransformationMachine has a very simple and clear interface: it takes in the state of certain animate-able parameters, and output transforms. Any implementation that meets that interface will work.

XLE's native implementation, of the TransformationMachine is very simple, but also a little unusual. Recall that the transformation machine represents a skeleton, and a skeleton is basically a tree of nodes. But XLE's transformation machine doesn't have any tree based data structures internally.

Instead, the skeleton has been "compiled" into a linear stream of commands (many of which are push & pop commands).

##Skeleton node traversal

Normally when we traverse the skeleton tree, might might look a little like this recursive algorithm:

ProcessNode(node, workingStack, animState)
{
	auto localToParent = node.CalculateLocalToParent(animState);
	workingStack.Push(localToParent);
    workingState.WriteMarker(node.GetName());

	for (child : node.Children()) {
		ProcessNode(child, workingState, animState);
	}
	workingStack.Pop()
}

Imagine we had a tree that looked like:

  • parent
  • child0
  • child1
  • sibling
  • nephew

This would result in the following commands begin executed:

workingStack.Push(parent.CalculateLocalToParent(animState));
workingStack.WriteMarker(parent.GetName());

workingStack.Push(child0.CalculateLocalToParent(animState));
workingStack.WriteMarker(child0.GetName());
workingStack.Pop();

workingStack.Push(child1.CalculateLocalToParent(animState));
workingStack.WriteMarker(child1.GetName());
workingStack.Pop();
workingStack.Pop();

workingStack.Push(sibling.CalculateLocalToParent(animState));
workingStack.WriteMarker(sibling.GetName());

workingStack.Push(nephew.CalculateLocalToParent(animState));
workingStack.WriteMarker(nephew.GetName());
workingStack.Pop();
workingStack.Pop();

For any given hierarchy, we can pre-compile those commands. This just becomes another way to represent the same data.

And the "transformation machine" becomes (in effect) just a tiny virtual machine running a basic stack-based instruction set.

There are some advantages to doing it this way:

  • some nodes don't have a local-to-parent and some nodes don't need to write a marker. This method works well when we need to skip steps (without any extra if's)
  • We can optimise the Push()es and Pop()s to remove redundant steps. In the command-based form, it's easy to find unnecessary stuff
  • it's very easy to incorporate different ways to build the local-to-parent
  • eg, one node could use a combination of axis-angle pairs, another could combine quaternions. This is useful if we want to use non-quaternion animation parameters for some nodes.
  • Everything is linear, so it's easy to load and save

However, there are some disadvantages:

  • it's more difficult to find the transformation for just a single node. If we wanted to find the chain of nodes that ends in a particular leaf, it requires more work
  • it's difficult to change the tree at runtime. Adding and removing nodes is not straight-forward.
  • this follows the XLE philosophy that artists-generated assets should generally be static (except pre-arranged animate-able properties)