Skip to content

JointComponent: no way to read the current value of a constrained degree of freedom #9178

Description

@willeastcott

Description

JointComponent has 32 public getters and every one of them returns configuration — the limits, the motion modes, the stiffness, the motor settings. None returns the joint's current state. The only windows onto a live constraint are isBroken and constraint, the raw backend handle.

So there is no supported way to answer "what angle is this hinge at?", or "how far is this joint from its limit?". That is needed for more than debugging:

  • driving gameplay or UI from a joint — a door's openness, a lever or dial position, a suspension's travel
  • detecting that a joint is sitting against a limit, which behaves very differently from being free
  • verifying that authored limits are the ones actually in force, given that limits are measured from the relative pose at constraint creation rather than from any absolute frame

Why the workaround is not good enough

The alternative is to derive it from the two bodies' world transforms. That is easy to get subtly wrong, and I got it wrong.

Building a ragdoll, I wanted to know whether the torso joints were pinned against their limits. I computed the angle of the relative quaternion between each pair of bodies:

const q = parent.getRotation().clone().invert().mul(child.getRotation());
const degrees = 2 * Math.acos(Math.min(1, Math.abs(q.w))) * 180 / Math.PI;

For a hinge that is fine. For a ball joint it is not: it returns the total rotation, so swing and twist are conflated. I read 21.1° against a swingLimitY of 20 and concluded the joint was violating its limit, when the reading was a legitimate mix of swing well inside its limit and twist well inside a separate 25° one. That is the sort of wrong measurement that sends you tuning the wrong parameter — and there is nothing in the public surface to check it against.

Any accessor the component provides would be correct by construction, because the component already knows the joint frames and which degree of freedom is which.

This is also, I think, the cheapest way to make the sign and zero-point conventions discoverable — related to #9169, which is about documenting them. A value you can print teaches the convention faster than prose can.

What the backend can supply today

Mixed, which suggests doing this in two steps rather than one:

type primary DOF in the Ammo build?
hinge angle yes — getHingeAngle()
slider travel yes — getLinearPos()
6dof angular per axis yes — getAngle(axis)
6dof linear per axis no — getRelativePivotPosition is not exported
ball twist no — getTwistAngle is not exported
ball swing no — Bullet does not expose it directly

So hinge, slider and 6dof angular could be read straight from the constraint as things stand. Ball and 6dof linear would need either those symbols added to the Ammo build, or deriving from the bodies inside the component.

Deriving them has precedent: _isAnchorSeparated() already reconstructs joint geometry from the two bodies' world transforms plus the stored _anchorA, _anchorB and _axisA, including projecting along the slide axis for sliders. Everything needed to compute swing and twist against frame A is already held on the component.

Where it would live

PhysicsJoint (src/framework/physics/physics-joint.js) currently exposes updateLimits, updateMotor, updateSpring, setBreakImpulse and isBroken, with the Ammo implementation as per-type strategy objects in ammo-physics-joint.js. A state read fits that shape — though it does mean the null backend needs an answer for it too.

Possible shapes

Not attached to any of these; the naming question is really "how much surface for how much benefit":

  1. Per-type accessors mirroring the existing property names — hingeAngle, sliderPosition, twistAngle, swingAngle, and angularAngles/linearPositions for 6dof. Most readable at the call site, most surface, and each is meaningless on the wrong type.
  2. One accessor keyed by degree of freedom, e.g. getAngle(axis) / getPosition(axis) on the frame's X/Y/Z, so hinge and ball are just the X and Y/Z cases of the same thing. Smaller surface, and it reinforces that the joint frame is the coordinate system that matters.
  3. A single state object, returned or filled in place, holding whatever the joint type defines. Cheapest to add, least discoverable.

My inclination is (2), starting with hinge, slider and 6dof angular where the backend already answers, and adding ball once swing/twist can be computed or exported. Happy to put up a PR for whichever is preferred.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions