Bend limbs and the torso along a Bézier spine #2

Merged
selimaj-dev merged 3 commits from limb-bending into master 2026-09-26 18:10:27 +00:00
Owner

Adds limb bending, the main reason for replacing player-animation-lib. It uses the same approach as the cloak renderer: a spine, rings of shared vertices along it, and faces drawn between the rings.

Core (no Minecraft)

  • Bezier: Bézier curves of any degree, with points and derivatives.
  • LimbBend: bends a limb along a single axis.
    • The spine is a cubic Bézier through the rigid "upper half, joint, lower half" shape. sharpness goes from a round curve to a tight joint (fixed at 0.5 for now).
    • The limb is cut into one piece per texture row (12 per limb), spaced evenly by arc length so the limb keeps its length.
    • Each ring moves as a unit, so neighbouring pieces share vertices and the mesh stays closed.
    • anchoredAtEnd keeps the far end in place, which the torso uses so the hips stay on the legs.
  • QuadSplitter: cuts vanilla faces at the rings and splits their UVs to match, so the texture is unchanged apart from the cuts.

Fabric

  • ModelPartMixin / BentCubeRenderer: any part with a bend is drawn by deforming vanilla's own cubes. Slim arms, sleeves, pants and armor all bend. Overlays and armor use the limb's spine, not their own inflated box, so they stay attached.
  • PoseApplier:
    • Bends are cleared on every setupAnim, because models are shared between entities.
    • The body bends from the hips, and the head and arms follow it: their pivots move with the bent torso and they turn with it.
  • Version differences: the only one so far is the vertex format, which changed in 1.21.9. It's handled with a Stonecutter branch.

Tester

  • New animations: flap, squat, bow and a rough twerk. The twerk is only a test pose and isn't close to other clients' twerks yet.
  • Screenshot test: ./gradlew :<mc>:runClientGameTest is a Fabric client gametest. It creates a world and screenshots each animation from several angles into versions/<mc>/build/run/clientGameTest/screenshots/.
  • Full Fabric API: the tester now runs with all of Fabric API, because single modules don't bring their dependencies, which differ between versions.

Testing

  • ./gradlew :core:test: 27 tests pass (Bézier, limb and torso bends, face splitting, animation timing).
  • buildAll builds all 8 versions.
  • The screenshot test passes on all 8 versions. I checked the screenshots from 1.21.4, 1.21.9 and 1.21.11: joints stay closed and textures stay intact.
  • The final twerk tweak (arching the lower back) was only compiled on 1.21.4 and 1.21.11 and checked by hand in game, not in the screenshot test.

Known gaps

  • Held items still attach to the unbent arm.
  • The cape doesn't follow the torso.
  • The head and arms follow the body's bend but not its rotation, so emotes have to add the rotation themselves. This is a proposed follow-up.
  • The unit tests may still change.

🤖 Generated with Claude Code

Adds limb bending, the main reason for replacing player-animation-lib. It uses the same approach as the cloak renderer: a spine, rings of shared vertices along it, and faces drawn between the rings. ## Core (no Minecraft) - **`Bezier`**: Bézier curves of any degree, with points and derivatives. - **`LimbBend`**: bends a limb along a single axis. - The spine is a cubic Bézier through the rigid "upper half, joint, lower half" shape. `sharpness` goes from a round curve to a tight joint (fixed at 0.5 for now). - The limb is cut into one piece per texture row (12 per limb), spaced evenly by arc length so the limb keeps its length. - Each ring moves as a unit, so neighbouring pieces share vertices and the mesh stays closed. - `anchoredAtEnd` keeps the far end in place, which the torso uses so the hips stay on the legs. - **`QuadSplitter`**: cuts vanilla faces at the rings and splits their UVs to match, so the texture is unchanged apart from the cuts. ## Fabric - **`ModelPartMixin` / `BentCubeRenderer`**: any part with a bend is drawn by deforming vanilla's own cubes. Slim arms, sleeves, pants and armor all bend. Overlays and armor use the limb's spine, not their own inflated box, so they stay attached. - **`PoseApplier`**: - Bends are cleared on every `setupAnim`, because models are shared between entities. - The body bends from the hips, and the head and arms follow it: their pivots move with the bent torso and they turn with it. - **Version differences**: the only one so far is the vertex format, which changed in 1.21.9. It's handled with a Stonecutter branch. ## Tester - **New animations**: `flap`, `squat`, `bow` and a rough `twerk`. The twerk is only a test pose and isn't close to other clients' twerks yet. - **Screenshot test**: `./gradlew :<mc>:runClientGameTest` is a Fabric client gametest. It creates a world and screenshots each animation from several angles into `versions/<mc>/build/run/clientGameTest/screenshots/`. - **Full Fabric API**: the tester now runs with all of Fabric API, because single modules don't bring their dependencies, which differ between versions. ## Testing - `./gradlew :core:test`: 27 tests pass (Bézier, limb and torso bends, face splitting, animation timing). - `buildAll` builds all 8 versions. - The screenshot test passes on all 8 versions. I checked the screenshots from 1.21.4, 1.21.9 and 1.21.11: joints stay closed and textures stay intact. - The final twerk tweak (arching the lower back) was only compiled on 1.21.4 and 1.21.11 and checked by hand in game, not in the screenshot test. ## Known gaps - Held items still attach to the unbent arm. - The cape doesn't follow the torso. - The head and arms follow the body's bend but not its rotation, so emotes have to add the rotation themselves. This is a proposed follow-up. - The unit tests may still change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
selimaj-dev added 3 commits 2026-09-26 18:04:10 +00:00
- core: Bezier (any degree, De Casteljau), LimbBend (cubic spine through
  the rigid upper/joint/lower shape, one ring per texture row spaced by
  arc length, rigid rings so pieces share vertices) and QuadSplitter
  (cuts faces at the rings, interpolating UVs).
- fabric: ModelPartMixin draws parts that have a bend with
  BentCubeRenderer, which deforms vanilla's own cubes. Overlays and armor
  use the limb's spine so they stay attached. Bends are reset every
  setupAnim because models are shared between entities.
- testmod: flap and squat animations, and a client gametest that
  screenshots each animation from the front and side
  (runClientGameTest). The tester runs with the full Fabric API because
  single modules don't bring their version-specific dependencies.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- LimbBend.anchoredAtEnd keeps the far end in place (the hips on the
  legs) and folds the other half, and angleAt/axis expose how far the
  limb has turned at a point.
- PoseApplier bends the body from the hips and carries the head and arms
  with it: their pivots follow the bent torso and they turn with its ring.
- Test animations: bow (torso only) and twerk. The gametest shoots the
  twerk at the top and bottom of its bounce, including from behind.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The torso tips forward around the hips and bends backwards, so the lower
back curves in and the chest comes up. The head and arms get the tilt
themselves, since they only follow the torso's bend.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
selimaj-dev merged commit 7016405e5f into master 2026-09-26 18:10:27 +00:00
selimaj-dev deleted branch limb-bending 2026-09-26 18:10:29 +00:00
Sign in to join this conversation.