Recursive AI

Solve two-bone inverse kinematics in C#

onegame is a free, open-source game engine for .NET from Recursive AI. MIT or Apache-2.0, no editor required.

Inverse kinematics (IK) turns an arm or leg to put its end joint at a target. Supply the target and bend direction in model space: coordinates relative to the model’s root, before its placement in the game world.

Terminal
dotnet add package RecursiveAI.OneGame.Gltf

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

figure.glb, the skinned sample, is in the zip.

C#
using System.Numerics;
using OneGame.Gltf;
var rig = new Rig(
    GltfLoader.LoadFile("figure.glb"));
var pose = rig.CreatePose();
bool reached = TwoBoneIk.Solve(rig, pose,
    rig.Node("ThighR"), rig.Node("ShinR"),
    rig.Node("FootR"),
    target: new(0.1f, 0.3f, -0.3f),
    pole: new(0.1f, 0.5f, -1));
Console.WriteLine(reached);

Prints True: the target is within reach.

Text
True

Unzip the example, then dotnet run.

Full program

The scene-submission example also uses the host package:

Terminal
dotnet add package RecursiveAI.OneGame.Host
C#
using System.Numerics;
using OneGame.Core;
using OneGame.Gltf;
using OneGame.Host;
 
var rig = new Rig(GltfLoader.LoadFile("figure.glb"));
var pose = rig.CreatePose();
int upper = rig.Node("ThighR"), mid = rig.Node("ShinR"), end = rig.Node("FootR");
var pole = new Vector3(0.1f, 0.5f, -1);
foreach (var target in new[] { new Vector3(0.1f, 0.3f, -0.3f), new Vector3(0.1f, 0.3f, -3) })
{
    rig.Sample(rig.Clip("idle"), 0, pose);
    bool reached = TwoBoneIk.Solve(rig, pose, upper, mid, end, target, pole);
    var foot = rig.ComputeWorld(pose)[end].Translation;
    Console.WriteLine($"Reachable: {reached}; error: {Vector3.Distance(foot, target):F4}");
}
using var view = new SceneView3D();
view.Models.Add("hero", rig.Model);
view.Describer = (_, scene) => scene.Model("hero", Transform3.Identity, pose);
view.Prepare(new World(1, new EntityRegistry()), 0, Fixed.Zero);
Console.WriteLine($"Models submitted: {view.ListModels().Count}");
Text
Reachable: True; error: 0.0000
Reachable: False; error: 2.2014
Models submitted: 1

The full program samples the idle clip before each solve, reports the distance from the foot to each target, and submits the final pose to a scene. This console example builds the scene without opening a window.

Joints that are not a direct parent-child chain throw ArgumentException.

The fixture is the public repository’s code-generated box figure, redistributed under MIT or Apache-2.0 with both license texts in the zip. Its generator source shows the skeleton and animation inputs.

Variations

Aim outside the leg’s reach. Append this to the snippet at the top:

C#
bool far = TwoBoneIk.Solve(rig, pose,
    rig.Node("ThighR"), rig.Node("ShinR"),
    rig.Node("FootR"),
    new(0.1f, 0.3f, -3), new(0.1f, 0.5f, -1));
Console.WriteLine(far);

The additional line printed is:

Text
False

The short answer

Use TwoBoneIk.Solve on the final pose, with the upper, middle and end joint indices plus a target and pole point in model space. It modifies the upper and middle local rotations. Apply it after sampling and blending clips, then submit that corrected pose with scene.Model. Sampling a clip again afterward would replace the rotations you just solved.

The download uses a real skinned glTF fixture. The first target is reachable and the second is deliberately too far away. The scene receives the last solved pose, so changing the target visibly changes the pose when the same code is used in a game view. This is a limb correction, not a full-body solver.

How do I use two-bone inverse kinematics in C#?

Choose three directly connected joints: hip, knee and ankle, or shoulder, elbow and wrist. The example resolves ThighR, ShinR and FootR by name rather than assuming numeric indices from a particular exporter. A differently named skeleton requires different names. The method checks the parent relationships; it does not search through intermediate twist joints to build a chain for you.

Create the pose buffer once and reuse it. Sample the desired animation into it, finish any pose blends, then solve the limb. The solver reads the current joint positions from that pose, so a target should describe the pose’s model-space coordinates, not screen pixels. For a world-space target in a moved character, first transform it into the model’s root space. The fixture’s identity transform lets the example avoid that extra coordinate conversion.

The bones should have uniform scale, as the API documentation specifies. Use the returned value to handle the ordinary out-of-range case, but inspect the resulting end position when your gameplay depends on an exact contact. IK here changes presentation. A hit, collision or deterministic character position must not be inferred by writing the floating-point pose back into synced state.

What does the pole target do in inverse kinematics?

The target says where the foot or hand should go. The pole is another point in model space that selects the side toward which the middle joint bends. In this fixture, the pole has negative Z so the knee bends forward. It is a position, not a rotation or an angle limit.

A pole along the target direction cannot uniquely select the bend plane. The implementation then uses the existing bend when possible, or a perpendicular direction as a fallback. Choose a stable pole on the intended knee or elbow side rather than relying on this fallback for normal motion. The method provides no anatomical joint-limit configuration or full-body balance adjustment.

What happens when an IK target is out of reach?

For a target beyond the combined bone lengths, the solver points the limb toward it and returns false. The printed error remains nonzero because the bones do not stretch to meet the target. Invalid chain structure instead throws an exception: it is not the same condition as a valid chain aiming too far away.

There are also degenerate cases. A target at the upper joint or a near-zero-length bone returns false without a normal solve. For unequal bone lengths, do not treat true alone as a guarantee of exact contact inside the folded limb’s minimum radius: this implementation clamps the solve distance but its reachability flag checks the outer distance. The explicit foot-to-target distance in the full program is useful when selecting a fallback pose. The animation guide documents the supported two-bone operation; it does not provide retargeting or a general chain solver.

All onegame recipes