Inverse Kinematics

Overview

IK (Inverse kinematics) can be useful in many situations ranging from procedural animation to small adjustments of animation. Simply put, IK is used when you want to position the tips of a hierarchichal structure at a known location and need to calculate all of the rotations of the parent joints to achieve this.

Examples include: Moving a hand to pick up an object, or adjusting a foot so it's always touching the ground, regardless of incline.

Terminology

It is helpful to know some of the terminology used when talking about inverse kinematics, so you can better understand what's being said.

  • The upper-most node in a solver's tree is known as the base node or root node.
  • Nodes which need to be moved to a specific target location (example: The hand of a human) are called an end effector.
  • Joints from which multiple children branch off from are called a sub-base node.
  • IK solvers work most efficiently on single "strings" of nodes, which are referred to as chains.
  • The entire structure (in the case of a human with two hands) is called a chain tree.

End Effectors

Effectors are used to set the target position and rotation of a node. You can create one by attaching the IKEffector component to a node.

Node* handNode = modelNode->GetChild("Hand.R", true);
IKEffector* effector = handNode->CreateComponent<IKEffector>(); // C++
IKEffector@ effector = handNode.CreateComponent("IKEffector"); // AngelScript
local effector = handNode:CreateComponent("IKEffector") -- Lua

You can then give the effector a target position (for example, the position of an apple) using IKEffector::SetTargetPosition or you can tell the effector to automatically track the position of a node with IKEffector::SetTargetNode.

effector->SetTargetPosition(appleNode->GetWorldPosition()); // C++
effector.targetPosition = appleNode.worldPosition; // AngelScript
effector.targetPosition = appleNode.worldPosition -- Lua

If enabled, you can also tell the effector to try and match a target rotation using IKEffector::SetTargetRotation. This is useful if you want your hand to point in a particular direction in relation to the apple (this feature needs to be enabled in the solver, which is discussed futher below).

The target position and rotation are both set in global space.

Another important parameter is the chain length, IKEffector::SetChainLength. A chain length of 1 means a single segment or "bone" is affected. Arms and legs typically use a value of 2 (because you only want to solve for the arm and not the entire body). The default value is 0, which means all nodes right down to the base node are affected.

effector->SetChainLength(2); // Humans have two bones in their arms
effector.chainLength = 2; // AngelScript
effector.chainLength = 2 -- Lua

Effectors have a weight parameter (use IKEffector::SetWeight) indicating how much influence they have on the tree to be solved. You can make use of this to smoothly transition in and out of IK solutions. This will be required for when your character begins picking up an object and you want to smoothly switch from animation to IK.

effector->SetWeight(SomeSplineFunction()); // C++
effector.weight = SomeSplineFunction(); // AngelScript
effector.weight = SomeSplineFunction(); -- Lua

If you've played around with the weight parameter, you may have noticed that it causes a linear interpolation of the target position. This can look bad on organic creatures, especially when the solved tree is far apart from the original tree. You might consider enabling nlerp, which causes the weight to rotate around the next sub-base joint. This feature can be enabled with IKEffector::SetFeature.

effector->SetFeature(IKEffector::WEIGHT_NLERP, true); // C++
effector.WEIGHT_NLERP = true; // AngelScript
effector.WEIGHT_NLERP = true -- Lua

Note that the script bindings work a little differently in this regard. The features can be enabled/disabled directly on the effector object as attributes rather than having to call SetFeature. This is for convenience (but may be changed in the future due to script API inconsistency).

Solvers

Solvers are responsible for calculating a solution based on the attached effectors and their settings.

Note that solvers will only ever affect nodes that are in their subtree. This means that if you attach an IKEffector to a node that is a parent of the IKSolver node, then the solver will ignore this effector.

You can create a solver by attaching an IKSolver component to a node:

IKSolver* solver = modelNode->CreateComponent<IKSolver>(); // C++
IKSolver@ solver = modelNode.CreateComponent("IKSolver"); // AngelScript
local solver = modelNode:CreateComponent("IKSolver") -- Lua

The first thing you'll want to do is select the appropriate algorithm. As of this writing, there are 3 algorithms to choose from, and you should favour them in the order listed here.

  • ONE_BONE: A specialized solver designed to point an object at a target position (such as eyes, heads, etc.)
  • TWO_BONE: A specialized solver that calculates a direct solution using trigonometry, specifically for two-bone structures (arms, legs)
  • FABRIK: A generic solver capable of solving anything you can throw at it. It uses an iterative algorithm and is thus a bit slower than the two specialized algorithms. Should be used for structures with 3 or more bones, or structures with multiple end effectors.

You can set the algorithm using:

solver->SetAlgorithm(IKSolver::FABRIK); // C++
solver.algorithm = IKAlgorithm::FABRIK; // AngelScript
solver.algorithm = IKSolver.FABRIK -- Lua

If you chose an iterative algorithm, then you might also want to tweak the maximum number of iterations and the tolerance. FABRIK converges very quickly and works well with 20 or less iterations. Sometimes you can even get away with just 5 iterations. The tolerance specifies the maximum distance an end effector is allowed to be from its target. Obviously, the smaller you set the tolerance, the more iterations will be required. Good starting values for tolerance are about 100th the size of the chain you're solving (e.g. if your chain is 2 units long, then set the tolerance to 0.02).

solver->SetMaximumIterations(20); // Good starting value for FABRIK
solver->SetTolerance(0.02); // Good value is 100th of your chain length.
solver.maximumIterations = 20; // AngelScript
solver.tolerance = 0.02;
solver.maximumIterations = 20 -- Lua
solver.tolerance = 0.02

Note that these settings do nothing if you have selected a direct solver (such as TWO_BONE or ONE_BONE).

Solver Features

There are a number of features that can be enabled/disabled on the solver, all of which can be toggled by using IKSolver::SetFeature and checked with IKSolver::GetFeature. You can always look at the documentation of IKSolver::Feature for a detailed description of each feature.

AUTO_SOLVE

By default, the solver will be in auto solve mode. This means that it will automatically perform its calculations for you in response to E_SCENEDRAWABLEUPDATEFINISHED. All you have to do is create your effectors, set their target positions, and let the solver handle the rest. You can override this behaviour by disabling the AUTO_SOLVE feature, in which case you will have to call IKSolver::Solve manually for it to do anything. This may be desired if you want to "hook in" right between when the animation has updated, but before inverse kinematics is calculated.

solver->SetFeature(IKSolver::AUTO_SOLVE, false); // C++
solver.AUTO_SOLVE = false; // AngelScript
solver.AUTO_SOLVE = false -- Lua

And here's how you manually invoke the solver.

// C++
void MyLogic::Setup()
{
SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(MyLogic, HandleSceneDrawableUpdateFinished));
}
void MyLogic::HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData)
{
GetComponent<IKSolver>()->Solve();
}
// AngelScript
void Setup()
{
SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished")
}
void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData)
{
GetComponent("IKSolver").Solve()
}
// Lua
function Setup()
SubscribeToEvent("SceneDrawableUpdateFinished", "HandleSceneDrawableUpdateFinished")
end
function HandleSceneDrawableUpdateFinished(eventType, eventData)
GetComponent("IKSolver"):Solve()
end

IKSolver::JOINT_ROTATIONS

solver->SetFeature(IKSolver::JOINT_ROTATIONS, false); // C++
solver.JOINT_ROTATIONS = false; // AngelScript
solver.JOINT_ROTATIONS = false -- Lua

This is should be enabled if you are using IK on skinned models (or otherwise node structures that need rotations). If you don't care about node rotations, you can disable this feature and get a small performance boost.

When disabled, all nodes will simply keep their original orientation in the world, only their positions will change.

The solver calculates joint rotations after the solution has converged by comparing the solved tree with the original tree as a way to compute delta angles. These are then multiplied by the original rotations to obtain the final joint rotations.

TARGET_ROTATIONS

solver->SetFeature(IKSolver::TARGET_ROTATIONS, false); // C++
solver.TARGET_ROTATIONS = false; // AngelScript
solver.TARGET_ROTATIONS = false -- Lua

Enabling this will cause the orientation of the effector node (IKEffector::SetTargetRotation) to be considered during solving. This means that the effector node will try to match the rotation of the target as best as possible. If the target is out of reach or just within reach, the chain will reach out and start to ignore the target rotation in favour of reaching its target.

Disabling this feature causes IKEffector::SetTargetRotation to have no effect.

UPDATE_ORIGINAL_POSE, UPDATE_ACTIVE_POSE, and USE_ORIGINAL_POSE

These options can be quite confusing to understand.

The solver actually stores two trees, not one. There is an active tree, which is kind of like the "workbench". The solver uses the active tree for its initial condition but also writes the solution back into the active tree (i.e. the tree is solved in-place, rather than cloning).

Then there is the original tree, which is set once during creation and then never changed (at least not by default).

You can control which tree the solver should use for its initial condition. If you enable USE_ORIGINAL_POSE, then the solver will first copy all positions/rotations from the original tree into the active tree before solving. Thus, the solution will tend to "snap back" into its original configuration if it can.

If you disable USE_ORIGINAL_POSE, then the solver will use the active tree instead. The active tree will contain whatever pose was solved last. Thus, the solution will tend to be more "continuous".

Very important: Note that the active tree is NOT updated by Urho3D unless you enable UPDATE_ACTIVE_POSE (this is enabled by default). If UPDATE_ACTIVE_POSE is disabled, then any nodes that have moved outside of IKSolver's control will effectively be ignored. Thus, if your model is animated, you very likely want this enabled.

UPDATE_ORIGINAL_POSE isn't really required, but is here for debugging purposes. You can update the original pose either by enabling this feature or by explicitely calling IKSolver::ApplySceneToOriginalPose.

CONSTRAINTS

This feature is not yet implemented and is planned for a future release.

@ WEIGHT_NLERP
Definition: IKEffector.h:54
@ AUTO_SOLVE
Definition: IKSolver.h:165
@ TARGET_ROTATIONS
When enabled, the effector will try to match the target's rotation as well as the effectors position....
Definition: IKSolver.h:85
@ JOINT_ROTATIONS
Should be enabled if your model uses skinning or if you are generally interested in correct joint rot...
Definition: IKSolver.h:74