Physics Joints
Connect rigid bodies with constraints. Learn about fixed, spherical (ball), revolute (hinge), and prismatic (slider) joints with limits and motors.
The Joint component connects two rigid bodies with a constraint. Joints restrict relative motion between bodies, enabling ragdolls, vehicles, doors, chains, and other mechanical systems. Web Engine supports Fixed, Spherical, Revolute, and Prismatic joints with configurable limits and motors.
Joint Types#
Fixed (Type 0)
Locks two bodies together with no relative motion. Used for compound objects and attachments.
Spherical (Type 1)
Ball-and-socket joint allowing rotation in all directions. Used for ragdoll shoulders and hips.
Revolute (Type 2)
Hinge joint rotating around single axis. Used for doors, wheels, and rotating mechanisms.
Prismatic (Type 3)
Slider joint moving along single axis. Used for pistons, drawers, and sliding doors.
Component Properties#
| Property | Type | Default | Description |
|---|---|---|---|
| type | enum | 0 (Fixed) | Joint type: 0=Fixed, 1=Spherical, 2=Revolute, 3=Prismatic |
| connectedEntity | entity | 0 | Entity ID of the second body |
| anchor1 | vec3 | [0,0,0] | Local anchor point on first body |
| anchor2 | vec3 | [0,0,0] | Local anchor point on second body |
| axis1 | vec3 | [1,0,0] | Local rotation/slider axis on first body |
| axis2 | vec3 | [1,0,0] | Local rotation/slider axis on second body |
| min | float | -1 | Minimum angle/distance limit |
| max | float | 1 | Maximum angle/distance limit |
| stiffness | float | 0 | Spring stiffness (0=rigid) |
| damping | float | 0 | Spring damping (0=no damping) |
Fixed Joints (Type 0)#
Fixed joints lock two bodies together with zero relative motion. They maintain a fixed distance and orientation between the connected bodies.
// Fixed joint connecting two boxes// Entity A: First box (has Joint component)// Entity B: Second box // Joint component on Entity A:{ type: 0, // Fixed connectedEntity: entityB_id, anchor1: [0, 0, 0], // Center of first box anchor2: [0, 0, 0], // Center of second box // No axis needed for fixed joints}- Locks bodies together (zero relative motion)
- Maintains fixed distance and orientation
- Good for: compound objects, attachments, connected structures
- Example: attaching a weapon to a character's hand
Alternative to Fixed Joints
For static attachments, consider using parent-child hierarchy instead of joints. Fixed joints are best when you need dynamic bodies that remain connected during simulation.
Spherical Joints (Ball Socket) - Type 1#
Spherical joints allow rotation in all directions around a single point, like a ball-and-socket joint. Perfect for ragdoll shoulders, hips, and chains.
// Ragdoll shoulder joint{ type: 1, // Spherical connectedEntity: armEntity_id, anchor1: [0, 0.5, 0], // Top of torso (shoulder) anchor2: [0, -0.3, 0], // Top of arm // Spherical joints don't use axis} // Chain link (multiple spherical joints){ type: 1, // Spherical connectedEntity: nextLink_id, anchor1: [0, -0.5, 0], // Bottom of this link anchor2: [0, 0.5, 0], // Top of next link}- Rotates freely in all directions
- Single connection point
- Good for: ragdoll joints, chains, rope segments, pendulums
- Example: shoulder, hip, neck, wrecking ball
Spherical Joint Limits#
Spherical joints support cone limits to restrict rotation angle. The min and max properties define the cone angle in radians.
// Shoulder with 90-degree cone limit{ type: 1, // Spherical connectedEntity: armEntity_id, anchor1: [0, 0.5, 0], anchor2: [0, -0.3, 0], min: 0, max: Math.PI / 2, // 90 degrees}Revolute Joints (Hinge) - Type 2#
Revolute joints rotate around a single axis, like a door hinge or wheel axle. They support angle limits and motors for powered rotation.
// Door hinge (rotates around Y axis){ type: 2, // Revolute connectedEntity: doorFrame_id, anchor1: [-1, 0, 0], // Left edge of door anchor2: [0, 0, 0], // Door frame position axis1: [0, 1, 0], // Y axis (vertical hinge) axis2: [0, 1, 0], min: 0, // Fully closed max: Math.PI / 2, // 90 degrees open} // Wheel axle (free rotation){ type: 2, // Revolute connectedEntity: chassis_id, anchor1: [0, 0, 0], // Wheel center anchor2: [1, 0, 0], // Chassis attachment point axis1: [1, 0, 0], // X axis rotation axis2: [1, 0, 0], // No limits = free rotation}- Rotates around single axis
- Supports angle limits
- Can add motor for powered rotation
- Good for: doors, wheels, pendulums, rotating platforms
Angle Limits#
| Min | Max | Behavior | Use Case |
|---|---|---|---|
| No limits | No limits | Free 360° rotation | Wheels, fans, windmills |
| 0 | π/2 (90°) | Quarter turn | Door, hatch |
| -π/4 | π/4 (±45°) | Swing both ways | Pendulum, gate |
| 0 | π (180°) | Half rotation | Lid, laptop screen |
// Door that opens 90 degrees (one direction){ type: 2, // Revolute min: 0, max: Math.PI / 2, // 90 degrees} // Gate that swings both ways (±90 degrees){ type: 2, // Revolute min: -Math.PI / 2, // -90 degrees max: Math.PI / 2, // +90 degrees}Motors#
Add a motor to a revolute joint to create powered rotation. Use stiffness and damping to control motor strength and smoothness.
// Powered wheel (motor){ type: 2, // Revolute connectedEntity: chassis_id, axis1: [1, 0, 0], axis2: [1, 0, 0], stiffness: 1000, // Motor strength damping: 50, // Motor smoothness} // Script to control motorexport default (api) => { const motorSpeed = 10.0; // rad/s return (dt) => { if (api.input.forward) { // Set motor target velocity api.joint.targetVelocity = motorSpeed; } else if (api.input.backward) { api.joint.targetVelocity = -motorSpeed; } else { api.joint.targetVelocity = 0; } };};Prismatic Joints (Slider) - Type 3#
Prismatic joints allow linear motion along a single axis, like a drawer or piston. They support distance limits and motors for powered sliding.
// Sliding door (moves along X axis){ type: 3, // Prismatic connectedEntity: doorFrame_id, anchor1: [0, 0, 0], // Door center anchor2: [0, 0, 0], // Frame center axis1: [1, 0, 0], // Slide along X axis axis2: [1, 0, 0], min: 0, // Closed position max: 2.0, // Open position (2m slide)} // Piston (vertical motion){ type: 3, // Prismatic connectedEntity: cylinder_id, anchor1: [0, 0, 0], anchor2: [0, 0, 0], axis1: [0, 1, 0], // Slide along Y axis axis2: [0, 1, 0], min: -0.5, // Extended down max: 0.5, // Extended up}- Linear motion along single axis
- Supports distance limits
- Can add motor for powered sliding
- Good for: sliding doors, drawers, pistons, elevators
Spring Behavior#
Any joint type can have spring-like behavior using stiffnessand damping. Springs pull the joint back to a rest position.
// Soft spring (suspension){ type: 3, // Prismatic stiffness: 100, // Spring force damping: 10, // Resistance to motion} // Stiff spring (almost rigid){ type: 3, // Prismatic stiffness: 10000, damping: 100,} // No spring (pure constraint){ type: 3, // Prismatic stiffness: 0, damping: 0,}| Stiffness | Damping | Behavior | Use Case |
|---|---|---|---|
| 0 | 0 | Free motion (no spring) | Free joints |
| 100 | 10 | Soft spring | Suspension, wobble |
| 1000 | 50 | Medium spring | Door closer, recoil |
| 10000 | 100 | Stiff spring | Almost rigid, small flex |
Common Patterns#
Ragdoll#
// Ragdoll spine (spherical joints)// Torso -> Lower Torso{ type: 1, // Spherical connectedEntity: lowerTorso_id, anchor1: [0, -0.5, 0], // Bottom of torso anchor2: [0, 0.3, 0], // Top of lower torso min: 0, max: Math.PI / 4, // 45-degree bend} // Shoulder -> Arm (spherical with cone limit){ type: 1, // Spherical connectedEntity: arm_id, anchor1: [0.5, 0.4, 0], // Shoulder anchor2: [0, 0.3, 0], // Top of arm min: 0, max: Math.PI / 2, // 90-degree cone} // Elbow (revolute hinge){ type: 2, // Revolute connectedEntity: forearm_id, anchor1: [0, -0.3, 0], // Bottom of upper arm anchor2: [0, 0.25, 0], // Top of forearm axis1: [1, 0, 0], axis2: [1, 0, 0], min: 0, max: Math.PI * 0.75, // 135-degree bend}Automatic Door#
// Door component (revolute with motor){ type: 2, // Revolute connectedEntity: doorFrame_id, anchor1: [-1, 0, 0], // Hinge on left edge anchor2: [0, 0, 0], axis1: [0, 1, 0], // Vertical hinge axis2: [0, 1, 0], min: 0, max: Math.PI / 2, // 90-degree open stiffness: 500, // Motor strength damping: 25,} // Script to auto-open/closeexport default (api) => { let isOpen = false; const openAngle = Math.PI / 2; const closeAngle = 0; return async (dt) => { // Detect player nearby const nearby = await api.raycast( api.position, { x: 0, y: 0, z: 1 }, 2.0 ); if (nearby && nearby.eid === playerEntity) { isOpen = true; } else { isOpen = false; } // Set motor target api.joint.targetAngle = isOpen ? openAngle : closeAngle; };};Chain / Rope#
// Create chain with multiple spherical joints// Each link connects to the next with a spherical joint // Link 1 -> Link 2{ type: 1, // Spherical connectedEntity: link2_id, anchor1: [0, -0.5, 0], // Bottom of link 1 anchor2: [0, 0.5, 0], // Top of link 2} // Link 2 -> Link 3{ type: 1, // Spherical connectedEntity: link3_id, anchor1: [0, -0.5, 0], anchor2: [0, 0.5, 0],} // Continue for all links...Vehicle Suspension#
// Wheel suspension (prismatic with spring){ type: 3, // Prismatic connectedEntity: chassis_id, anchor1: [0, 0, 0], // Wheel center anchor2: [1, 0, 0], // Chassis attachment axis1: [0, 1, 0], // Vertical suspension axis2: [0, 1, 0], min: -0.2, // Compressed max: 0.2, // Extended stiffness: 1000, // Suspension stiffness damping: 50, // Suspension damping} // Wheel rotation (revolute around axle){ type: 2, // Revolute connectedEntity: suspensionBody_id, axis1: [1, 0, 0], // Axle rotation axis2: [1, 0, 0], // No limits = free rotation}Best Practices#
- Use anchor points to position joints correctly
- Set appropriate axis directions for revolute/prismatic joints
- Add limits to prevent unrealistic motion
- Use stiffness and damping for spring behavior
- Keep joint chains short (3-5 links) for stability
- Use fixed joints sparingly - parent-child hierarchy is often better
- Test joint stability with different body masses
- Avoid connecting static bodies with joints (use world anchors instead)
Joint Stability
Very long chains of joints (10+ links) can become unstable. Break them into smaller segments or use constraints instead of physical joints.
Debugging Joints#
// Debug joint in scriptexport default (api) => { return (dt) => { // Log joint angle (revolute) api.log(`Joint angle: ${api.joint.currentAngle}`); // Log joint distance (prismatic) api.log(`Joint distance: ${api.joint.currentDistance}`); // Check if limit reached if (api.joint.currentAngle >= api.joint.max) { api.log("Joint at max limit!"); } };};Visual Debugging
Enable physics debug rendering in the editor to visualize joint anchor points, axes, and limits. This helps identify configuration issues.