rebirth-ik
Inverse kinematics in safe Rust. FABRIK for whole bone trees, closed-form one/two-bone solvers, joint limits, no_std, and a WebAssembly build you can play with right now.
▶ Live demo — a tentacle, a humanoid with 5 effectors, a procedural walker, an analytic knee, and 400 chains solved every frame, all running as WebAssembly.
Why "rebirth"?
This crate is a from-scratch Rust rewrite of TheComet/ik — a C library by Alex Murray, MIT licensed, last touched in 2018. The original had some genuinely good ideas, most of all how it turns an arbitrary bone tree plus a few effectors into a forest of chains that can be solved independently and stitched back together. It also never got finished: its FABRIK unit test is a stub (ASSERT_TRUE(0)), the multi-segment solver is empty, the hinge/cone constraints do nothing, and the convergence check is inverted.
rebirth-ik keeps the ideas, credits the author, and finishes the job in safe Rust:
- the chain decomposition is ported faithfully — the scenarios from the original's (commented-out) test-suite run as real tests here;
- FABRIK is implemented end to end for trees with any number of effectors;
- constraints actually constrain; the analytic solvers handle the degenerate cases instead of dividing by zero.
Thank you, Alex. The MIT license of the original is preserved in LICENSE.
Features
| FABRIK on bone trees | Any number of effectors, shared sub-bases, effectors in the middle of a chain, per-effector chain_length, weight (with optional NLERP blending), early exit with tolerance. |
| Joint rotations | Optional reconstruction of local rotations from the solved positions (with quaternion averaging where chains meet). |
| Constraints | Cone, Hinge (around a world axis, with min/max) and Stiff, enforced in the backward pass so the solved pose always respects them. |
| Analytic solvers | one_bone and two_bone (law of cosines) with a pole vector for knees/elbows; no NaN on collinear input. |
| Zero-allocation chain solver | solve_chain(&mut [Vec3], &[f32], target, …) — no skeleton, no heap, no_std. The hot path for ropes, tentacles, crowds. |
| Parallel batches | solve_chains_par (feature rayon) for thousands of independent chains. |
#![forbid(unsafe_code)] |
No unsafe anywhere in the core. |
no_std |
Builds for bare-metal targets (CI builds thumbv7em-none-eabihf) with the libm feature. |
| WebAssembly | Ready-made wasm-bindgen bindings and the demo in web/. |
Quick start
[]
= "0.1"
A skeleton with effectors
use *;
// a three-bone arm
let mut skel = new;
let root = skel.add_root;
let a = skel.add_child;
let b = skel.add_child;
let hand = skel.add_child;
// where should the hand go? (chain_length 0 = all the way to the root)
skel.set_effector;
let mut solver = new;
let info = solver.solve; // writes solved *local* transforms back
assert!;
println!;
Call solver.rebuild(&skel) whenever you add or remove effectors or change a chain_length; moving targets needs no rebuild.
Several effectors on one tree
// `skel`, `head`, `l_hand`, `r_hand` come from your own skeleton
skel.set_effector;
skel.set_effector; // shoulder → hand
skel.set_effector; // half-blended
Joint limits and rotations
// bend the knee only about the X axis, from straight (0) to 2.4 rad
skel.set_constraint;
let cfg = Config ;
One chain, no allocation
use ;
let mut points: = .map.collect;
let mut lengths = ;
segment_lengths;
let r = solve_chain;
assert!;
Two bones with a knee
use ;
let = ;
let = two_bone;
Performance
Measured with Criterion on an Intel i5-12400F (6 cores / 12 threads), Rust 1.99, --release with lto = "fat", f32. Every case runs a fixed 20 sweeps (no early exit), so the numbers are the full cost of a solve.
| Scenario | Time |
|---|---|
| Single chain, 5 bones | 0.90 µs |
| Single chain, 10 bones | 3.7 µs |
| Single chain, 20 bones | 9.0 µs |
| Single chain, 50 bones | 23 µs |
| Single chain, 200 bones | 94 µs |
| Humanoid tree, 5 effectors (positions only) | 7.3 µs |
| Humanoid tree, 5 effectors (with joint rotations) | 7.6 µs |
| Two-bone analytic | 11.7 ns |
| Crowd of 100 000 ten-bone chains, one thread | 376 ms (0.27 M chains/s) |
Crowd of 100 000 ten-bone chains, rayon, 12 threads |
39 ms (2.5 M chains/s) — ×9.6 |
| Same crowd in WebAssembly (Chrome, one thread) | ≈ 410 ms (0.24 M chains/s) — within ~15 % of native |
Reproduce: cargo bench -p rebirth-ik --features rayon. You can also run the browser benchmark on the live demo.
No head-to-head with the original C library is published here. Its FABRIK solver is unfinished (see above) and always runs its full iteration budget without checking convergence, so a speed comparison would compare different things. If you want to contribute a fair harness, see CONTRIBUTING. SIMD: the hot loops are written so the compiler can auto-vectorize them; there is no nightly-only
std::simdcode, so the crate builds on stable.
no_std and embedded
= { = "0.1", = false, = ["libm"] }
The Skeleton/Fabrik types need alloc; the slice-based solve_chain and the analytic solvers need nothing. CI cross-builds thumbv7em-none-eabihf (Cortex-M4F).
WebAssembly
The demo page (web/index.html) is a single file; GitHub Actions builds and deploys it to GitHub Pages on every push to main.
Known limitations
- A perfectly collinear chain whose tip is pulled straight back on itself cannot fold — a classic FABRIK singularity. Give the bones a tiny bend (real skeletons always have one).
- A fully stretched target (at exactly the chain's maximum reach) converges slowly; the solver stops at the iteration limit with a small residual.
- A chain whose base hangs below another solved chain is solved relative to the rest pose of that base.
Hingeuses a fixed world-space axis; it is ideal for planar chains and limbs, not for arbitrary twisted joints.- Constraints are applied in the backward pass only; if the limits make a target unreachable the effector stops short.
Roadmap
- C FFI crate with a generated header
- Python bindings (
pyo3) and Node.js bindings (napi-rs) - Structure-of-arrays batch solver for SIMD-friendly crowds
- Twist limits and pole vectors for tree chains
- Fair benchmark harness against the original C implementation
Credits & license
- Original design and C implementation: Alex Murray — TheComet/ik, MIT, © 2017.
- FABRIK: Aristidou & Lasenby, FABRIK: A fast, iterative solver for the Inverse Kinematics problem, Graphical Models 73 (2011).
- This crate: MIT, see LICENSE. Contributions are welcome under the same license.