Inverse Kinematics

Overview

Inverse kinematics (IK) works backwards from where a bone should end up. Instead of rotating each joint by hand, you say "put the foot here" and AzureLib turns the thigh and shin so it gets there. Use it to make authored animation adapt to the world:

  • Feet that step onto slopes, stairs and ledges instead of floating or sinking.
  • Hands that stay on a weapon's grip, a ladder or a lever.
  • A head, neck and spine that turn to watch a target.
  • Tails and tentacles that reach for a point.

IK runs as a pose modifier, after your animation controllers. The animation still plays; IK adjusts the result each frame, and can be weighted, masked and faded per animatable like any other modifier.

Quick Start: Feet on Uneven Ground

public class ExampleEntityAnimator extends AzEntityAnimator<ExampleEntity> {
@Override
protected void registerPoseModifiers(AzPoseModifierRegistry<ExampleEntity> modifiers) {
modifiers.add("left_leg_ik", AzIkChain.<ExampleEntity>builder("left_thigh", "left_shin", "left_foot")
.target(AzIkTargets.ground(0.5F, 0.75F))
.constraint(AzBoneConstraints.hingeX("left_shin", 0, 150))
.build()).priority(50);
modifiers.add("right_leg_ik", AzIkChain.<ExampleEntity>builder("right_thigh", "right_shin", "right_foot")
.target(AzIkTargets.ground(0.5F, 0.75F))
.constraint(AzBoneConstraints.hingeX("right_shin", 0, 150))
.build()).priority(50);
}
}

Each frame, each leg:

  1. Takes the foot's position from the animation.
  2. Looks for the ground under that foot, from half a block above it to three quarters of a block below.
  3. Moves the foot by the difference between that ground and the ground the entity is standing on, so a lifted foot mid-step stays lifted.
  4. Bends the knee and swings the thigh so the foot lands there.

When the entity isn't on the ground, or no ground is found in range, the leg is left as animated.

IK Chains

AzIkChain.builder(...) takes the chain's bones in order from the root (hip, shoulder) to the tip (foot, hand). Each bone must be a descendant of the one before it, but doesn't have to be a direct child. If a bone is missing or the order is wrong, the chain is skipped for that model.

Builder methodWhat it sets
target(AzIkTarget)Where the effector should go each frame. Required
effector(x, y, z)The point that should reach the target, in Blockbench pixels from the last bone's pivot. Default: the pivot itself
solver(AzIkSolver)How to solve the chain. Default: two-bone for 2 or 3 bones, CCD otherwise
constraint(...)Joint limits the solver respects; see Bone Constraints

Effectors

The effector is the part of the chain that touches the target. For a chain of thigh, shin, foot, the default effector is the foot's pivot (the ankle). To put the sole on the ground instead, move the effector down:

AzIkChain.<ExampleEntity>builder("left_thigh", "left_shin", "left_foot")
.effector(0, -2, 0) // 2 pixels below the ankle.

For a two-bone chain without a foot bone (upper_arm, forearm), use the effector to mark the hand at the end of the forearm, for example effector(0, -10, 0).

Solvers

Two-Bone

AzIkSolvers.twoBone() is exact and cheap, for legs and arms. It bends the middle joint (knee, elbow) so the limb is the right length, then swings the upper bone to point at the target. If the target is out of reach, the limb straightens and points at it.

The knee bends in the same plane the animation put it in, so knees and elbows keep facing the way they were animated. When the limb is perfectly straight there is no plane to keep, so give it a pole: a point in model space (Blockbench pixels from the model's origin) the middle joint should turn towards:

.solver(AzIkSolvers.twoBone().pole(-2, 6, -20)) // Knee points forwards.

The solver uses the first two chain bones. A third bone, like the foot, only carries the effector; its own rotation isn't changed.

CCD

AzIkSolvers.ccd(iterations, tolerance) handles chains of any length, such as tails, tentacles and spines. Each pass turns every bone, tip first, to point the effector at the target, and stops once it's within tolerance pixels:

modifiers.add("tail_reach", AzIkChain.<ExampleEntity>builder("tail1", "tail2", "tail3", "tail4")
.effector(0, 0, 6)
.solver(AzIkSolvers.ccd(12, 0.25F))
.target(AzIkTargets.entityEyes(ExampleEntity::getGrabTarget))
.build());

8 to 16 iterations is usually enough. CCD tends to curl the tip first; constraints on the tip bones keep that natural.

Custom Solvers

A solver is a single method, so you can write your own:

AzIkSolver solver = (chain, effector, target, constraints) -> {
var tip = effector.position(chain, new Vector3f());
chain.aim(0, tip, target, 0.5F); // Turn the root halfway towards the target.
};

See AzBoneChain for what a chain can do.

Targets

AzIkTargets has the common targets. They return model-space positions, and can skip solving for a frame (leaving the limb as animated) when there's nothing to aim at.

TargetUse
ground(maxStepUp, maxStepDown)Foot placement, as in the quick start
world((entity, partialTick) -> Vec3)Any world position, such as a grip point or a ledge. Return null to skip
entityEyes(entity -> Entity)Another entity's eyes, for looking or reaching. Return null to skip
model(x, y, z)A fixed point in the model, in Blockbench pixels from the model's origin

A target is also a single method, so you can compute your own. It receives the effector's animated position, in model space, and writes the target into dest:

AzIkTarget<ExampleEntity> holdUp = (pose, effector, dest) -> {
dest.set(effector).add(0, 0.25F, 0); // A quarter block above where the animation put it.
return true;
};

Look-At

AzLookAt turns a chain of bones so the last one faces a target, spreading the turn along the chain so a spine, neck and head all share it:

modifiers.add("stare", AzLookAt.<ExampleEntity>builder("chest", "neck", "head")
.target(AzIkTargets.entityEyes(ExampleEntity::getSyncedTarget))
.constraint(
AzBoneConstraints.limit("head", -40, 40, -70, 70, 0, 0),
AzBoneConstraints.limit("neck", -20, 20, -30, 30, 0, 0)
)
.build());

The last bone's front is (0, 0, -1) in its own Blockbench axes, the front of a model facing north in Blockbench. If your head faces another way, set it with forward(x, y, z).

AzLookAt aims at any target you give it. To follow where the entity itself is looking, the simpler AzPoseModifiers.headLook is enough.

Fade it in and out like any modifier, for example when the mob gains or loses its target:

context().poseLayer().fadeWeight("stare", hasTarget ? 1 : 0, 8);

Bone Constraints

Constraints keep joints in a believable range. IK solvers apply them after every step, so a knee never bends backwards to reach a target.

ConstraintWhat it does
AzBoneConstraints.limit(bone, minX, maxX, minY, maxY, minZ, maxZ)Clamps each axis
AzBoneConstraints.hingeX(bone, min, max)Clamps X and holds Y and Z, for knees and elbows. hingeY and hingeZ work the same on the other axes

Angles are in degrees, relative to the bone's default pose, and use the signs of your exported animation file. Blockbench 5 shows X and Y rotation keyframes with the opposite sign to the exported file (Z is the same), so for X and Y, flip the sign of what Blockbench 5 shows: if a keyframe of [-90, 0, 0] in Blockbench 5 bends the knee the way it should go, hingeX("shin", 0, 150) allows that direction. In Blockbench 4 and earlier, keyframes show the same signs as the exported file, so that keyframe would read [90, 0, 0].

Constraints can also run on their own as a pose modifier, clamping whatever the animation and earlier modifiers produced:

modifiers.add("neck_limits", AzBoneConstraints.modifier(
AzBoneConstraints.limit("neck", -30, 30, -45, 45, -10, 10)
)).priority(100);

You can write your own by implementing AzBoneConstraint. It receives the bone's rotation (radians, in AzureLib's internal convention: X and Y have the opposite sign to the exported animation file, which is the sign Blockbench 5 shows) and its default rotation, and adjusts the rotation in place.

AzBoneChain

IK chains, look-at and spring bones are built on AzBoneChain, which you can use directly in your own modifiers:

private static final class ChainHolder {
AzBoneChain chain;
}
modifiers.add("point_up", pose -> {
var state = pose.state(ChainHolder::new);
if (state.chain == null) {
state.chain = AzBoneChain.resolve(pose, "shoulder", "arm");
}
var chain = state.chain;
if (chain == null) {
return;
}
chain.reset(); // Load this frame's pose.
var hand = chain.pointOnBone(1, 0, -12, 0, new Vector3f());
var sky = chain.joint(0, new Vector3f()).add(0, 4, 0);
chain.aim(0, hand, sky, 1); // Turn the shoulder so the hand points up.
chain.apply(); // Write the result, scaled by the weight.
});
MethodWhat it does
resolve(pose, bones...)Builds a chain for this animatable's model, or null if the bones don't form a chain. Keep the result in pose.state(...)
reset()Reloads the chain bones' rotations from the current pose
joint(index, dest)A chain bone's pivot in model space
pointOnBone(index, x, y, z, dest)A point fixed to a bone, in Blockbench pixels from its pivot
directionOnBone(index, x, y, z, dest)A direction fixed to a bone, in model space
aim(index, from, to, fraction)Turns a bone so the direction to from points at to
rotateAround(index, axis, angle)Turns a bone around a model-space axis through its pivot
constrain(index, constraint)Applies a constraint to a bone
apply()Writes every chain bone to the pose once, scaled by the modifier's weight

The chain works on its own copy of the rotations, so you can call aim as often as you like; nothing is rendered until apply().

Model Space

IK works in the model's own space: blocks, relative to the model's origin, before the entity's rotation and scale. Offsets you give AzureLib (effectors, poles, pointOnBone) are in Blockbench pixels, on Blockbench's axes. To convert world positions yourself, use AzEntityModelSpace:

var modelPoint = AzEntityModelSpace.DEFAULT.worldToModel(entity, partialTick, x, y, z, new Vector3f());
var worldPoint = AzEntityModelSpace.DEFAULT.modelToWorld(entity, partialTick, modelPoint);

It places the model the way the default entity renderer does: at the entity's position, turned by its body yaw and scaled by its scale attribute. If your renderer config scales the model with setScale, use AzEntityModelSpace.scaled(width, height) with the same values, and pass it to the targets that take one (AzIkTargets.ground(up, down, space), AzIkTargets.world(position, space)).

Troubleshooting

  1. The chain does nothing.

    • Check the bones are listed root first and that each one is under the one before it in Blockbench.
    • Check the target can resolve: ground needs the entity on the ground, entityEyes needs a synced target.
    • Make sure nothing masks the chain bones away from the modifier.
  2. The knee or elbow bends the wrong way.

    • Add a hinge constraint in the direction it should bend, or give the two-bone solver a pole in front of (or behind) the joint.
  3. The foot lands too high or too low.

    • Move the effector to the sole with effector(0, -n, 0).
    • If the renderer scales the model, pass AzEntityModelSpace.scaled(...) to ground.
  4. The head looks sideways or behind.

    • Set the head's front with forward(x, y, z) on AzLookAt.
  5. IK jitters on stairs.

    • Fade the modifier's weight while the entity is moving fast, or raise maxStepUp so the search starts above the step.