Skip to main content

Joints

Alpha

The joint component is in alpha, so its behavior and API may change. It is available from code on every surface and as a tag in Web Components, but the Editor does not yet have an inspector for it and PlayCanvas React has no component for it.

Joints constrain the relative motion of two rigid bodies: a door swinging on its hinge, a drawer sliding on its runner, a chain of links, a crate bobbing on a spring, or a tower of blocks welded together until something hits it hard enough to break the weld.

Joints

How Joints Work

A joint is a JointComponent on its own entity. That entity is not constrained itself. Its world transform defines the joint frame: the position is the anchor point the constraint operates about, and the local X axis is the primary axis. A hinge rotates about X, a slider translates along X and a ball joint twists about X. To aim a joint, rotate the joint entity.

The two bodies being constrained are set with entityA and entityB, both of which need a rigidbody component. Leave entityB empty to constrain entityA to a fixed point in world space, which is how you hang something from nothing. A common arrangement is to parent the joint entity to entityA at the pivot point, so the pivot moves with the part it belongs to.

Two details catch people out:

  • Frames are captured once, when the constraint is created. That usually happens when the component is enabled and both bodies are in the simulation. Moving the joint entity afterwards changes nothing until you call refreshFrames() on the component, which re-captures the frames from the current transforms. Entity scale is ignored throughout, as it is for rigid bodies.
  • Limits are measured from the starting pose, not from the joint entity. The two joint frames coincide at the moment the constraint is created, so every degree of freedom reads zero there. Hinge limits of 0 to 110 degrees mean "from wherever the body started, up to 110 degrees further", not an absolute angle.

Joint Types

The type property selects the constraint and decides which of the other properties matter:

TypeConstantConstrainsProperties it uses
fixedJOINTTYPE_FIXEDLocks the bodies rigidly togethernone
ballJOINTTYPE_BALLBall and socket: free rotation about the anchor, with optional swing and twist limitsswingLimitY, swingLimitZ, twistLimit
hingeJOINTTYPE_HINGERotation about the frame's X axis, with optional limits and a motorlimits, motorSpeed, maxMotorForce
sliderJOINTTYPE_SLIDERTranslation along the frame's X axis, with optional limits and a motorlimits, motorSpeed, maxMotorForce
6dofJOINTTYPE_6DOFEach linear and angular axis independently locked, limited or free, with optional springslinearMotionX/Y/Z, angularMotionX/Y/Z, linearLimitsX/Y/Z, angularLimitsX/Y/Z, linearStiffness, angularStiffness, linearDamping, angularDamping, linearEquilibrium, angularEquilibrium

Every type also honors enableCollision, which decides whether the two bodies still collide with each other (off by default), and breakImpulse.

For a 6dof joint each axis starts MOTION_LOCKED, so a joint with nothing else set behaves like a fixed one. Open the axes you want with MOTION_FREE or MOTION_LIMITED, bound the limited ones with the matching limits, and set a stiffness above zero on an axis to turn it into a spring that pulls towards its equilibrium value.

Adding a Joint

This door hinge constrains a dynamic door to a static frame. The joint entity sits on the hinge line, rotated so that its local X axis points up, and limits of 0 to 110 degrees let the door open one way only:

const hinge = new pc.Entity('hinge');
hinge.setPosition(1, 1, 0);
hinge.setEulerAngles(0, 0, 90); // the local X axis now points up
hinge.addComponent('joint', {
type: pc.JOINTTYPE_HINGE,
entityA: door,
entityB: doorFrame,
enableLimits: true,
limits: new pc.Vec2(0, 110)
});
app.root.addChild(hinge);

pc.Application registers the joint component system for you. If you build from pc.AppBase, add pc.JointComponentSystem to AppOptions.componentSystems alongside the collision and rigid body systems.

Motors, Limits and Breaking

Limits are opt-in twice over: they need enableLimits set and a limit value. A hinge with limits set but enableLimits left false swings freely.

Hinges and sliders have a motor. It is engaged while maxMotorForce is greater than zero and drives the joint at motorSpeed, in degrees per second for a hinge and units per second for a slider. A motor with limits enabled stalls against the stop.

// A windmill: spin the rotor about the hinge axis at 90 degrees per second
hinge.joint.motorSpeed = 90;
hinge.joint.maxMotorForce = 100;

Any joint can break. Set breakImpulse to the impulse above which the constraint gives way; it defaults to Infinity, meaning it never breaks. When it does, the component fires a break event, isBroken becomes true and the bodies move independently. Calling refreshFrames() re-attaches the joint from the bodies' current transforms.

const weld = jointEntity.joint;
weld.breakImpulse = 60;
weld.on('break', () => {
console.log('The weld snapped');
});
note

Breaking cannot be detected for 6dof joints on ammo.js builds that do not expose constraint state, so the break event does not fire for them. The other joint types are unaffected.

See Also

  • JointComponent - API reference for every joint property
  • <pc-joint> - The same joints in Web Components, with a live example of each type
  • Ragdoll - Engine example that builds a character from ball and hinge joints
  • Rigid Bodies - The bodies every joint constrains