top of page

FK Animation Blending System

This is a demonstration of a project that I worked on in collaboration with my colleague Colin Brady. Together we constructed a forward-kinematics animation interpolation system from scratch. Beyond simple pose-to-pose animation, we also endeavored to incorporate more complicated multi-clip blending capabilities. Our main goal was to decouple the momentum of a character from the direction it faces. The clip blending structure we developed animates the proper locomotion for any combination of the character's forward-facing vector and forward-movement vector:

Our implementation mostly follows the hierarchy method prescribed by Jason Gregory of Naughty Dog in his book Game Engine Architecture. Every joint in the skeleton contains an index, the index of its parent, and its own set of transformation data. The hierarchy of joints is arranged in ascending order, with the joint at index 0 (the root joint) listed first. This ensures proper forward kinematics when traversing the list, as each joint will have inherited its parent's transformation before its own local transformation is applied.

​

The data format for the pose transformations is rather unique, since it is not the same set of data that you would see inside your animation software. In Maya for instance, the keyable channels for the position, rotation, and scale of every joint in a hierarchy will display the local transformation at every frame. Before being stored inside of our system's "pose" data structures, this transform data undergoes a modification:  the skeleton's "base pose" (in our case, the T-pose) is factored out of the local transformation. The result of this action is that each transformation in the pose definition describes how much the joint differs from its base pose, rather than how far it is from its parent.

​

The resulting pose data is a set of "delta" transformations rather than absolute local transformations, which is what we are truly interested in when solving animations. Essentially, a delta pose of (0, 0, 0) defines a joint's neutral position instead of the location of its parent. All of our blending operations take place between these sets of delta poses; once the final delta pose is reached, it is then concatenated with the base pose to put the transformations back into local space. The skeleton then goes through each joint, assumes the resulting position in FK order, and then moves on to the next frame.

​

The skeleton in our demo has a total of seven animations. The full blend tree that incorporates these clips is illustrated below:

The actual operators involved in the blending are not complicated. A switch node simply returns one of the two inputs. The lerp node sends the position and scale data through the lerp formula and runs the rotation values through a slerp (spherical linear interpolation) operation. The real complexity of this blend tree is in the interpolation parameters that dictate the weight of each clip in the final pose.

​

The simplest of the parameters is the moveSpeed variable, which is used to transition between the active (running and sidestepping) and stationary (idle and turning) animations. This parameter is derived from the movement vector (colored yellow in the demo) by clamping and normalizing the vector's magnitude. The other speed parameter, turnSpeed, is calculated by measuring the rate of change in the direction of the "look" vector (colored pink in the demo).

​

Two of the parameters are booleans used to switch between opposing animations. These parameters rely on both the movement and the look vectors. Unsurprisingly, the faceForward variable is used to tell if the character is running forward or backward. The key operation here is to calculate the dot product of the two vectors. Dot products are commonly used to determine how parallel two directions are, but thankfully, the dot product of two parallel vectors also tells whether the angle between them is acute or obtuse by being positive or negative. Hence, the sign of the dot product can be used to determine if the character's movement is pointing toward or away from its look vector.

​

Just by looking at the two vectors in the demo, one might think that the dot product can also be useful for determining the faceRight parameter. However, while the two possible arrangements of parallel vectors return distinct dot products, the dot product will be 0 for either perpendicular arrangement of the vectors. We use a different relationship between the two vectors for assigning faceRight.

 

The character's look vector, along with it's subsequent orientation, is actually determined by calculating a Frenet-Serret frame that faces a user-controlled target. The "binormal" vector of this frame is always perpendicular to the look vector. By using the dot product on the movement vector and the binormal component vector, the sign of the result is meaningful once again, and is equal to the value of the faceRight boolean. The absolute value of the same dot product is also the source of the sideStrength parameter, which is used to lerp between the forward/backward and sideways run animations.

​

None of the techniques in this project are groundbreaking or revolutionary. Rather, the purpose of creating it was so that Colin and I could deepen our understanding and demonstrate our competence in animation programming.

bottom of page