Skip to content

Java Library Concepts

jellymods edited this page Sep 19, 2026 · 1 revision

Concepts

This is mostly for internal housekeeping and documentation, and much of it only really matters if you're writing a model by hand. The Blockbench exporter should abstract most of the technical details mentioned here.

MarionettePart one segment: a Forge PartEntity with a length, a direction, and a hitbox
Limb an ordered chain of parts + the animator that drives it
FabrikAnimator solver which decides where each part ends up
Marionette the interface your entity implements to own a list of limbs

MarionettePart

A MarionettePart is a entity in the world with a real hitbox, so a tentacle can be hit, collided with and hurt along its length.

Its length is fixed at construction and its position is the center of the segment, not either end. The two ends are derived:

getRootPos()  // position - direction * length/2, the parent-side end
getEndPos()   // position + direction * length/2, the tip end
setRootPos(v) // move so the parent-side end lands on v
setEndPos(v)  // move so the tip end lands on v

direction is a unit vector; setPartDirection normalizes for you. Everything is in blocks.

Positions are double-buffered: setPartPos stores a pending position that position() reports immediately, and tick() commits it and syncs the previous-position fields so rendering is interpolated. The solver calls tick() on every part at the end of a solve, so you normally never should.

Limb

Limb<MarionettePart<OctopusEntity>> tentacle = Limb.builder(this)
        .segments(4, 16f / 16, 16f / 16, 16f / 16)   // count, sizeXZ, sizeY, length
        .segments(4, 12f / 16, 12f / 16, 12f / 16)   // taper by chaining calls
        .segments(7, 10f / 16, 10f / 16, 10f / 16)
        .build();

Index 0 is the root end, attached to the entity; the last index is the tip that reaches for the target. Part order has to match the model's segment-name array.

Solver

Call setFabrikTarget(...) in your entity's tick(), then tickMarionette(). Each solve runs a backward pass (walk from the root, pinning each segment's root end to the previous segment's tip) and a forward pass (walk from the tip, pinning each segment's tip to the next segment's root), repeating until the tip is within 0.01 blocks of the target or 100 iterations are up.

followRootOnly(true) drops the forward pass: segments only follow the root, dragging along behind it. Use this for worms, tails, anything that doesn't have a set root position.

Coordinate conventions

As with everything in this page, it only really matters when you're writing a model by hand. the Blockbench exporter already applies all of them.

  • A segment's model part is positioned relative to the entity, in model units (16 per block), with Y flipped: the rest position of every part is PartPose.offset(0, 24, 0).
  • setupAnim overwrites every part's position and rotation each frame, so the pose baked into your PartDefinitions is never seen. Author geometry around the segment's centre and leave the pose at the default.
  • Segment geometry points down +Z. The renderer rotates the whole model 180° about Y so that matches Blockbench's forward.

Clone this wiki locally