Procedurally Animated Creatures using FABRIK

By Jonas Jansen
500807120

Foreword

This article will explore the techniques and theory behind procedural animation and creating code based animations for games. Inspiration was drawn from creators such as Codeer and Makan Gilani as well as the Unity Prototype Series.

Table of contents

  1. Animating the limbs
    1. Basic concepts
    2. Forward & Inverse Kinematics
    3. Algorithms for Inverse Kinematics
    4. Poletargets
  2. Procedural walking
    1. Simple controller
    2. Adding limbs
    3. Stepping
  3. Results
  4. Future
  5. Sources


1. Animating the limbs

The first part of the animation we should recreate is the movement of the limbs. One end of these limbs will be attatched to the body, while the other end will be placed on the terrain or moving towards a new point on the terrain while walking. In the animation we want the limbs to move naturally from one position to another. Where moving the end of a limb will be simple, for example by making it follow a target, the rest of the limb will pose a problem.
Since we want don’t want the limbs deforming during the animation, the distance between joints should stay consistent. Unfortunately this means we will have to the movement of each joint whenever we move the foot of the creature. To fix this we will need a system that determines where the joints should move based on the movement of the foot.

1.1. Basic concepts

To be able to understand how the limbs are going to be moved, it’s important to first list and understand the different parts of the limb that will have to be dealt with. From here on limbs will be referred to as a ‘chain’ since we will treat it as a chain of bones. A chain consists of all the components spanning from its root (1) to its effector (3).

Chain concepts (Figure 1.)

  1. Root: The base of the chain; its position will be fixed to the body of the creature. Also the first joint in the chain. (1.)
  2. Joint: The start (or end) of a bone, also the point where the chain can kink/bend. (2a & b.)
  3. Effector: The end of the chain; its position will be able to move around and follow a target. Also the final joint in the chain. (3.)
  4. Bone: The length between two successive joints, this length is constant. (4a, b & c.)
  5. Ancestor: All joints further up the chain in the direction of the root. (for example; 2a‘s sole ancestor is the root, while 2b‘s ancestors are both 2a and the root.)
  6. Descendant: All joints further down the chain in the direction of the effector. (for example; 2a‘s descendants are both 2b and the effector, while 2b‘s sole descendant is the effector.)
  7. Parent: A joint’s direct ancestor.
  8. Child: A joint’s direct descendant.
Figure 1: A limb/chain

1.2. Forward & Inverse Kinematics

Kinematics is a field of physics that’s used to geometrically describe the motion of points.
Forward Kinematics (FK) is used in animation to determine the position of a joint’s descendants based on the joint moving or rotating.
Inverse Kinematics (IK) is used in animation to determine the position of the effector’s ancestors based on the effector’s movement or rotation.

1.3. Algorithms for Inverse Kinematics

The two algorithms I chose to research for this project are Cyclic Coordinate Descent (CCD) and Forward And Backward Reaching Inverse Kinematics (FABRIK). Both algorithms are heuristic approaches that iteratively move closer and closer to a solution. The solution in this case is the effector reaching a target.

CCD IK

Cyclic Coordinate Descent or CCD is an algorithm that iteratively rotates all joints in a chain to move the effector closer to the target. The algorithm loops through all joints, excluding the effector, from the joint furthest down in the chain to the root.
Step 1: For each joint two vectors are calculated (a):

  • The vector between the joint’s position and the effector
  • The vector between the joint’s position and the target

Step 2: Using these vectors a rotation is calculated to get the joint as close to the target as possible. This rotation is then applied to the joint (b, c & d).

When all the joints in the chain have been rotated the algorithm has finished one iteration. If the effector is not close enough to the target the algorithm will repeat (e & f) untileffector is close enough to the target or the maximum amount of iterations has been reached.

One of the disadvantages of CCD is that it’s fairly biased towards the end of the chain. This can result in the chain being positioned in unnatural looking poses.

Figure 2: Cyclic Coordinate Descent Inverse Kinematics (Kinematics (Advanced Methods in Computer Graphics) Part 4)

FABRIK

Forward and Backward Reaching Kinematics or FABRIK propagates through the joints in the chain twice per iteration; first it moves from the effector to the root (forward) and then from the root to the effector (backward).

Step 0: In the example (figure 2.) we start out with the original chain (a) and the target t.

Step 1: During the forward pass the position of the chain’s effector will be set to the target position (b). After which the joint’s ancestor’s new positions will be calculated.

Step 2: To calculate this new position the vector between the joints child’s position and it’s old position will be normalized to represent the direction the new position will be in. This normalized vector will be multiplied by the corresponding bone’s length to find the new position (c).

Step 3: Now we repeat step 2 for all joints up to and including the root (d). A problem arises here as the root, which position should stay the same, has moved. To fix this we will now repeat the process we just executed in reverse.

Step 4: To start the backward pass the position of the root wil be returned to it’s original position (e).

Step 5: To finish the iteration step 2 will be executed in reverse by using the joint’s child instead of parent to calculate the vector that represents the direction of the new joint position (f).

Figure 3: Forward and Backward Reaching Inverse Kinematics (Aristidou & Lasenby, 2011, p. 251)

1.4. Poletargets

To make the limbs feel more natural we will add a poletarget. This poletarget will determine the direction the limbs bend towards. To achieve this we will loop through all of the joints in the chain except for the root and effector, since these have fixed positions, and execute the following steps:

First we create a plane. This plane will be positioned on the position of the parent of the current joint. To create this plane we need a point, in the case the position of the parent, and a normal. The normal for the plane will be the vector that starts in the parent and ends in the joint’s child. To calculate this normal we subtract the child’s position from the parent’s position.

The next step is to project the positions of both the poletarget and curent joint onto this plane. We do this by calculating the closest points to these positions located on the plane.

Using these projected positions we can create two new vectors located in the plane. The first starting in the parents position and ending on the projected joint position and the second starting in the parent’s position and ending in the projected pole position.

Finally, we calculate the angle between these two vectors, using the normal as the rotation axis, and rotating the joint around the normal by this angle.

//Create a plane positioned in the previous joint with the vector between the previous and next joint 
Plane plane = new Plane(positions[i + 1- positions[i - 1], positions[i - 1]);
 
//Project the position of the poleTarget onto the plane
Vector3 poleProjection = rotationPlane.ClosestPointOnPlane(polePos);
 
//Project the position of the current joint onto the plane
Vector3 jointProjection = rotationPlane.ClosestPointOnPlane(positions[i]);
 
//Calculate the rotation (using the plane's normal as rotation-axis) between the two projected points
float poleDeltaRotation = Vector3.SignedAngle(jointProjection - positions[i - 1], poleProjection - positions[i - 1], plane.normal);
 
//Rotate the current joint using the previously calculated angle around the plane's normal
positions[i] = Quaternion.AngleAxis(poleDeltaRotation, rotationPlane.normal) * (positions[i] - positions[i - 1]) + positions[i - 1];

2. Procedural walking

2.1. Simple controller

To move the creature we’ll start out by creating a base controller. The only functionality this controller has is updating the input and translating this to movement. We take the vertical input (up & down arrow keys) and use this to determine a forward velocity and the horizontal input (left & right arrow keys) to rotate the creature left and right. Using a simple cube as the creature’s body we can fly around.

protected virtual void Move(InputData input)
{
    float forwardVelocity = input.zAxisInput * movementSpeed * Time.fixedDeltaTime;
    float rotationVelocity = input.xAxisInput * rotationSpeed * Time.fixedDeltaTime;
    Quaternion newRotation = Quaternion.Euler(0rotationVelocity0* transform.rotation;
 
    transform.position += transform.forward * forwardVelocity;
    transform.rotation = newRotation;
}

While this simple controller is perfect for moving the body over flat surfaces, it doesn’t follow terrain that has dips or bumps. To make sure the creature sticks to the surface it’s traversing we will update its height and rotation based on the terrain directly below it.

Physics.Raycast(transform.position, Vector3.down, out RaycastHit hit5f, groudLayer);
Vector3 newPosition = hit.point + Vector3.up * height;
Quaternion newRotation = Quaternion.FromToRotation(transform.up, hit.normal);
newRotation *= transform.rotation;
 
transform.position = Vector3.MoveTowards(transform.position, newPositionTime.deltaTime);
transform.rotation = Quaternion.Slerp(transform.rotation, newRotationTime.deltaTime);

2.2. Adding limbs

Now it’s time to create a limb for the creature. Let’s start by creating a chain of joints with the root attached to the base controller and the effector planted on the floor. As you can see the chain readjusts whenever the main body moves.

2.3. Stepping

Now that we have added limbs we’ll have to start moving them. Using the IK algorithm we can make them follow a target, but we still need to move this target to make the creature walk. While moving the target should be easy, we still have to tackle the problems of when to move, which limb’s target to move and how to move the target.

Physics.Raycast(projectionOrigin-transform.up, out RaycastHit hit5f, groundLayer);
Vector3 projectedStep = hit.point;

When to step

There’s a lot of ways we coud go about choosing when to make the creature step. The one I used for this prototype is to predict the step for each of the creature’s steps and to start the step whenever the distance between this predicted position and the creature’s foot’s position is a certain length. Predicting the step is done by casting a ray down from the ‘neutral’ foot position and taking the point where this ray collides with the surface the creature is walking on.

Step pattern

Moving both legs on the same side of the creature at the same time would look unnatural. To fix this we should make sure only one of the legs on each side of the creature are moving at any given time. This can be done by tracking which legs are moving and on which side of the creature they are located.

Figure 4: The base controller
Figure 5: Adding a limb
Figure 6: First steps
Figure 7: walking with multiple limbs

Step height

The creatures feet (the chains effector) are moving towards their targets but these steps are still looking unnatural. Right now the feet are dragging along the floor to their new position instead of moving there in an arch. To achieve this we will use the shape of the first half of a sine wave. To calculate this we need 3 values:

  • The starting position of the step
  • The end position of the step
  • The current position of the effector

Using these 3 values we can calculate how far along the step we are by comparing the distance between the starting position and the end position (while ignoring their height) to the distance between the current position and the end position. Using the InverseLerp function we get a value that tells us how far along the step we are. With this value we can calculate where we are along the first half of the sine wave by taking the sine of Pi multiplied by the stepprogress. We can now multiply this value by the height we want the step to reach at its maximum and add this value to the step. When the stepprogress gets close enough to one or the target reaches close enough to the new position it will stop moving.

Figure 8: A sine wave (BioMath: Trigonometric Functions, z.d.)
Vector3 GetStepArch()
{
    stepCurrent = new Vector2(target.position.x, target.position.z);
    float stepProgress = Mathf.InverseLerp(0Vector2.Distance(stepFrom, stepTo), Vector2.Distance(stepFrom, stepCurrent));
    Vector3 step = Vector3.Lerp(lastRestingPosition, newTargetPosition, Time.fixedDeltaTime * stepSpeed) - lastRestingPosition;
    Vector3 heightStep = new Vector3(0Mathf.Sin(Mathf.PI * stepProgress* stepHeight, 0);
    step += heightStep;
    step -= lastHeightStep;
    lastHeightStep = heightStep;
 
    if (stepProgress > .99f || Vector3.Distance(target.transform.position, newTargetPosition) < 0.01f)
    {
        StopMoving();
    }
 
    return step;
}

3. Results

Combining all the previously described techniques gives a huge variety of different creatures that can be created like the simple four-legged example shown here.

Figure 9: Four-legged spider-like creature

Combining the procedural animation with other systems, such as a system that makes different body segments follow each other, is also a possiblity. Shown below is an example of one such creature.

Figure 10: Six-legged centipede-like creature

4. Future

Expanding upon the prototype created during this research I would like to create a more complete framework for procedural animation. Some features that would be interesting to explore further:

  • Body height & rotation based on the creatures feet
  • Being able to walk on steep incline/decline terrain and traversing terrain with 90+ degree angles
  • Procedurally generating animations other than walking/moving
  • Animating rigged 3D models

5. Sources

  • Aristidou, A., & Lasenby, J. (2011). FABRIK: A fast, iterative solver for the Inverse Kinematics problem. Graphical Models, 73(5), 243–260. https://doi.org/10.1016/j.gmod.2011.05.003
  • BioMath: Trigonometric Functions. (z.d.). http://www.biology.arizona.edu/. http://www.biology.arizona.edu/biomath/tutorials/trigonometric/graphtrigfunctions.html
  • C# Inverse Kinematics in Unity 🎓. (2019, 11 mei). [Video]. YouTube. https://www.youtube.com/watch?v=qqOAzn05fvk&t=491s&ab_channel=DitzelGames
  • FABRIK (Inverse kinematics). (2016, 2 juni). [Video]. YouTube. https://www.youtube.com/watch?v=UNoX65PRehA&ab_channel=EgoMoose
  • Juckett, R. (2020, 11 februari). Cyclic Coordinate Descent in 2D. RyanJuckett.Com. https://www.ryanjuckett.com/cyclic-coordinate-descent-in-2d/
  • Kinematics (Advanced Methods in Computer Graphics) Part 4. Http://What-When-How.Com/. http://what-when-how.com/advanced-methods-in-computer-graphics/kinematics-advanced-methods-in-computer-graphics-part-4/
  • Knightly, R. (2018, 2 juni). Ground Hugging Vehicles in Unity 3D – The Floating Point. Medium. https://medium.com/thefloatingpoint/ground-hugging-vehicles-in-unity-3d-50115f421005
  • Unite Berlin 2018 – An Introduction to CCD IK and How to use it. (2018, 12 juli). [Video]. YouTube. https://www.youtube.com/watch?v=MA1nT9RAF3k&ab_channel=Unity
  • Ye, R. (2019, 21 maart). Inverse Kinematics for Game Programming. Medium. https://medium.com/@turtle50vp/inverse-kinematics-for-game-programming-5f9a408e24b2
  • Zucconi, A. (2017, 10 april). Inverse Kinematics for Robotic Arms. https://www.alanzucconi.com/2017/04/10/robotic-arms/

Related Posts