Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Physics Joints

A joint is a constraint between two rigid bodies. The constraint solver enforces the relationship every step. Different joint types constrain different degrees of freedom. A fixed joint constrains all six (three translation, three rotation). A revolute joint constrains five and leaves one rotation axis free. A prismatic joint constrains five and leaves one translation axis free.

Joint Types

JointDescriptionUse Cases
FixedRigid connectionWelded objects
SphericalBall-and-socketPendulums, ragdolls
RevoluteHingeDoors, wheels
PrismaticSliderDrawers, pistons
RopeMax distanceRopes, chains
SpringElasticSuspension, bouncy connections

Fixed Joint

A fixed joint welds two bodies into one. They move and rotate together as if they were a single body, but mass and collision stay separate.

#![allow(unused)]
fn main() {
use nightshade::ecs::physics::joints::*;

create_fixed_joint(
    world,
    body_a,
    body_b,
    FixedJoint::new()
        .with_local_anchor1(Vec3::new(0.5, 0.0, 0.0))
        .with_local_anchor2(Vec3::new(-0.5, 0.0, 0.0)),
);
}

The two local anchors are the attachment points in each body's local space. The solver keeps those points coincident.

Spherical Joint (Ball-and-Socket)

A spherical joint locks position but allows all three rotation axes. The two bodies pivot freely around the joint point.

#![allow(unused)]
fn main() {
let anchor = spawn_cube_at(world, Vec3::new(0.0, 5.0, 0.0));
let ball = spawn_sphere_at(world, Vec3::new(0.0, 3.0, 0.0));

create_spherical_joint(
    world,
    anchor,
    ball,
    SphericalJoint::new()
        .with_local_anchor1(Vec3::new(0.0, -0.15, 0.0))
        .with_local_anchor2(Vec3::new(0.0, 1.0, 0.0)),
);
}

The example above is a pendulum. The anchor is fixed, the ball swings.

Revolute Joint (Hinge)

A revolute joint allows rotation around a single axis. Everything else is constrained.

#![allow(unused)]
fn main() {
let door_frame = spawn_cube_at(world, Vec3::zeros());
let door = spawn_cube_at(world, Vec3::new(0.5, 1.0, 0.0));

create_revolute_joint(
    world,
    door_frame,
    door,
    RevoluteJoint::new(JointAxisDirection::Y)
        .with_local_anchor1(Vec3::new(0.0, 0.0, 0.0))
        .with_local_anchor2(Vec3::new(-0.5, 0.0, 0.0))
        .with_limits(JointLimits::new(-1.5, 1.5)),
);
}

The Y-axis revolute joint above is a door hinge. The limits (in radians) prevent it from swinging through the wall behind it.

Adding Motor

A joint motor drives the joint toward a target position or velocity. For a position motor on the door, that turns the hinge into something that can open and close on command.

#![allow(unused)]
fn main() {
RevoluteJoint::new(JointAxisDirection::Y)
    .with_motor(JointMotor::position(0.0, 5.0, 100.0))
}

The three arguments are target position, stiffness, and damping. Stiffness controls how hard the motor pushes toward the target. Damping resists the resulting velocity.

Prismatic Joint (Slider)

A prismatic joint allows translation along a single axis and locks everything else.

#![allow(unused)]
fn main() {
let cabinet = spawn_cube_at(world, Vec3::zeros());
let drawer = spawn_cube_at(world, Vec3::new(0.0, 0.0, 0.5));

create_prismatic_joint(
    world,
    cabinet,
    drawer,
    PrismaticJoint::new(JointAxisDirection::Z)
        .with_local_anchor1(Vec3::new(0.0, 0.0, 0.0))
        .with_local_anchor2(Vec3::new(0.0, 0.0, -0.5))
        .with_limits(JointLimits::new(0.0, 0.8)),
);
}

The limits are in meters. 0 is fully closed, 0.8 is fully open.

Rope Joint

A rope joint enforces a maximum distance between two bodies. Within that distance the bodies move freely. At the limit they stop separating.

#![allow(unused)]
fn main() {
let ceiling = spawn_cube_at(world, Vec3::zeros());
let weight = spawn_sphere_at(world, Vec3::new(0.0, 0.0, 0.0));

create_rope_joint(
    world,
    ceiling,
    weight,
    RopeJoint::new(2.0)
        .with_local_anchor1(Vec3::new(0.0, -0.15, 0.0))
        .with_local_anchor2(Vec3::new(0.0, 0.0, 0.0)),
);
}

Use rope joints in series for a chain. Each link is a small body connected to the next by a rope joint with a short max distance.

Spring Joint

A spring joint applies a Hooke's-law restoring force toward a rest length. The body oscillates around that length and damping bleeds off the oscillation over time.

#![allow(unused)]
fn main() {
let anchor = spawn_cube_at(world, Vec3::zeros());
let bob = spawn_sphere_at(world, Vec3::new(0.0, -2.0, 0.0));

create_spring_joint(
    world,
    anchor,
    bob,
    SpringJoint::new(1.5, 50.0, 2.0)
        .with_local_anchor1(Vec3::new(0.0, -0.15, 0.0))
        .with_local_anchor2(Vec3::new(0.0, 0.2, 0.0)),
);
}

Arguments are rest length, stiffness, damping. Higher stiffness gives a stiffer spring. Higher damping settles the bounce faster.

Joint Limits

Limits clamp the free degree of freedom to a range.

#![allow(unused)]
fn main() {
RevoluteJoint::new(JointAxisDirection::Z)
    .with_limits(JointLimits::new(-1.57, 1.57))

PrismaticJoint::new(JointAxisDirection::X)
    .with_limits(JointLimits::new(-2.0, 2.0))
}

Revolute limits are radians. Prismatic limits are meters. Without limits the joint runs to its full mechanical range, which for a revolute is unbounded rotation and for a prismatic is unbounded translation.

Breaking Joints

Joints can break under excessive force. Set a maximum impulse and Rapier will dissolve the joint when contact forces push past it.

#![allow(unused)]
fn main() {
if let Some(joint) = world.resources.physics.get_joint_mut(joint_handle) {
    joint.set_max_force(1000.0);
}
}

Chain Example

A chain is a string of spherical joints. Each link's bottom anchor is the next link's top anchor.

#![allow(unused)]
fn main() {
fn create_chain(world: &mut World, start: Vec3, links: usize) {
    let mut previous = spawn_cube_at(world, start);

    for index in 0..links {
        let position = start - Vec3::new(0.0, (index + 1) as f32 * 0.5, 0.0);
        let link = spawn_sphere_at(world, position);

        create_spherical_joint(
            world,
            previous,
            link,
            SphericalJoint::new()
                .with_local_anchor1(Vec3::new(0.0, -0.2, 0.0))
                .with_local_anchor2(Vec3::new(0.0, 0.2, 0.0)),
        );

        previous = link;
    }
}
}

The first link anchors to the spawn cube. Each subsequent link anchors below the previous one.

Interactive Door Example

A real door is a revolute joint with locked rotations on every axis except the hinge axis. Locking rotations on the body itself, in addition to the joint constraint, makes the door behave like a door instead of a wobbling flap.

#![allow(unused)]
fn main() {
fn spawn_interactive_door(world: &mut World, position: Vec3) -> Entity {
    let frame = spawn_cube_at(world, position);

    let door = spawn_cube_at(world, position + Vec3::new(0.5, 0.0, 0.0));

    if let Some(rb) = world.core.get_rigid_body(door) {
        if let Some(handle) = rb.handle {
            if let Some(body) = world.resources.physics.rigid_body_set.get_mut(handle.into()) {
                body.lock_rotations(true, true);
            }
        }
    }

    create_revolute_joint(
        world,
        frame,
        door,
        RevoluteJoint::new(JointAxisDirection::Y)
            .with_local_anchor1(Vec3::new(0.05, 0.0, 0.0))
            .with_local_anchor2(Vec3::new(-0.5, 0.0, 0.0))
            .with_limits(JointLimits::new(-2.0, 2.0)),
    );

    door
}
}

The door body keeps the dynamic mass that gives it momentum when pushed. The joint limits define how far it swings open.