Skip to content

[Feature] Add foot IK with analytic two-bone solver - #1143

Open
miogds wants to merge 1 commit into
untoldengine:developfrom
miolabs:feature/animation_foot_ik_upstream
Open

[Feature] Add foot IK with analytic two-bone solver#1143
miogds wants to merge 1 commit into
untoldengine:developfrom
miolabs:feature/animation_foot_ik_upstream

Conversation

@miogds

@miogds miogds commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Next link in the animation chain after inertialized transitions (#1125) and root motion (#1135): foot IK. Clips are authored against a flat ground plane; on real terrain feet float or sink. With foot IK enabled — opt-in per entity, default off, zero cost when disabled — the ground beneath each configured ankle is sampled every frame and the hip and knee are adjusted with a closed-form two-bone solver so the foot lands on it.

API

setFootIKChains(entityId: player, chains: [
    FootIKChainDescriptor(hipPath: "root/Hips/LeftUpperLeg", kneePath: ".../LeftLowerLeg", anklePath: ".../LeftFoot"),
    FootIKChainDescriptor(hipPath: "root/Hips/RightUpperLeg", kneePath: ".../RightLowerLeg", anklePath: ".../RightFoot"),
])
setFootIKEnabled(entityId: player, enabled: true)
setFootIKGroundQuery(entityId: player, query: myHeightfieldQuery)   // optional override; nil restores the default
  • TwoBoneIK: Holden-style analytic solver — law-of-cosines bend plus aim rotation, expressed in each joint's local frame. Composition order is aim-then-bend in local post-multiplication (equivalent to bend-then-aim in world space); the wrong order is invisible whenever hint, chain, and target are coplanar, so the tests deliberately cover the non-coplanar case. Unreachable targets clamp to full extension; straight chains take the bend plane from a hint.
  • FootIK: chains are resolved once against the skeleton and cached; unknown joint paths are ignored. The ankle's authored height above the clip's ground plane is preserved above the real terrain, corrections beyond 0.5 m are treated as non-ground and ignored, and a collinear (straight-leg) pose falls back to the chain's configurable bendDirection.
  • Ground sampling defaults to a downward scene ray pick (octree picking) that rejects hits on the character itself or its descendants; games can override per entity with setFootIKGroundQuery (heightfield, navmesh, physics — anything).
  • Runs after root motion and transitions, before pose composition, so it corrects the pose that will actually be displayed. Leg joints are assumed unit-scale.

How-to guide: docs/API/UsingFootIK.md. Single commit cherry-picked onto current develop (7d54789).

Testing

AnimationFootIKTests (10 tests, all passing):

  • Two-bone solver: reaches a reachable target, clamps unreachable targets at full extension while pointing at the target, and solves a perfectly straight chain via the bend hint
  • Ankle lands on raised ground with its authored height above the clip's ground plane preserved; ground below full extension clamps at full reach
  • A ground sample far above the foot (beyond the 0.5 m limit) is rejected as non-ground; a missing ground sample leaves the pose untouched
  • Off by default — and verified to never sample the ground while disabled; enable/disable round-trip; unresolvable chain paths are dropped without effect
  • Placement tests drive the ground through a per-entity setFootIKGroundQuery injection, exercising the override path end to end

Root motion, inertialization, compiled-sampler, and policy suites still pass on this base. SwiftFormat lint clean.

Clips are authored against a flat ground plane; on real terrain feet
float or sink. Foot IK (opt-in per entity, default off) samples the
ground beneath each configured ankle every frame and adjusts the hip
and knee with a closed-form two-bone solver so the foot lands on it.

- TwoBoneIK: Holden-style analytic solver — law-of-cosines bend plus
  aim rotation, axes expressed in each joint's local frame. Composition
  order is aim-then-bend in local post-multiplication (equivalent to
  bend-then-aim in world space); the wrong order is invisible whenever
  hint, chain, and target are coplanar, so tests cover the
  non-coplanar case. Unreachable targets clamp to full extension;
  straight chains take the bend plane from a hint.
- FootIK: chains described by hip/knee/ankle joint paths
  (setFootIKChains), resolved once against the skeleton. The ankle's
  authored height above the clip's ground plane is preserved above the
  real terrain; corrections beyond 0.5 m are treated as non-ground and
  ignored. A collinear pose bend (straight leg) falls back to the
  chain's configurable bendDirection.
- Ground sampling defaults to a downward scene ray pick (octree
  picking) that rejects hits on the character itself or its
  descendants; games can override per entity with
  setFootIKGroundQuery (heightfield, navmesh, physics).
- Runs after root motion and transitions, before pose composition.
  Leg joints are assumed unit-scale.

Docs: docs/API/UsingFootIK.md
@miogds
miogds requested a review from untoldengine as a code owner August 12, 2026 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant