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

Rigid Bodies

A rigid body is the chunk of state Rapier integrates each step. Position, orientation, linear and angular velocity, mass, the inertia tensor. The body type controls how it responds to forces. The collider attached to it controls what it collides with.

Body Types

Dynamic Bodies

Dynamic bodies are fully simulated. Gravity, applied forces, impulses, contacts all push them around.

#![allow(unused)]
fn main() {
let entity = world.spawn_entities(
    LOCAL_TRANSFORM | GLOBAL_TRANSFORM | RIGID_BODY | COLLIDER,
    1
)[0];

world.core.set_rigid_body(entity, RigidBodyComponent::new_dynamic());

world.core.set_collider(entity, ColliderComponent::new_ball(0.5));
}

Use dynamic for physics props, ragdolls, anything that should react to the world.

Kinematic Bodies

Kinematic bodies move by code, not by the integrator. Other dynamic bodies treat them as immovable walls, but the kinematic body itself ignores collision response.

#![allow(unused)]
fn main() {
world.core.set_rigid_body(entity, RigidBodyComponent::new_kinematic());
}

Drive a kinematic body by writing its transform directly each frame.

#![allow(unused)]
fn main() {
if let Some(transform) = world.core.get_local_transform_mut(kinematic_entity) {
    transform.translation.x += velocity.x * dt;
}
mark_local_transform_dirty(world, kinematic_entity);
}

The mark_local_transform_dirty call is required. Without it the transform sync system does not know the transform changed and the body will not move in Rapier.

Static Bodies

Static bodies never move. Floors, walls, environment geometry. Infinite mass, no integration cost.

#![allow(unused)]
fn main() {
world.core.set_rigid_body(entity, RigidBodyComponent::new_static());
}

Helper Functions

The physics module ships two helpers for the common case of spawning a dynamic primitive with a matching collider.

Spawning Physics Cubes

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

let cube = spawn_cube_at(world, Vec3::new(0.0, 5.0, 0.0));
}

Spawning Physics Spheres

#![allow(unused)]
fn main() {
let sphere = spawn_sphere_at(world, Vec3::new(0.0, 10.0, 0.0));
}

Both spawn a dynamic body with a unit-sized collider. Use the longer form when you need a custom size or material.

Applying Forces

Forces and impulses are not exposed through the component directly. Reach into Rapier through the handle on RigidBodyComponent.

#![allow(unused)]
fn main() {
fn apply_force(world: &mut World, entity: Entity, force: Vec3) {
    let Some(rb_component) = world.core.get_rigid_body(entity) else { return };
    let Some(handle) = rb_component.handle else { return };

    if let Some(rigid_body) = world.resources.physics.rigid_body_set.get_mut(handle.into()) {
        rigid_body.add_force(
            rapier3d::prelude::Vector::new(force.x, force.y, force.z),
            true,
        );
    }
}
}

The boolean is the wake flag. Sleeping bodies ignore forces unless explicitly woken, which is almost always what you want when applying force.

Applying Impulses

An impulse is an instantaneous change in velocity. Use it for explosions, jumps, hit reactions, anything that should not integrate over time.

#![allow(unused)]
fn main() {
fn apply_impulse(world: &mut World, entity: Entity, impulse: Vec3) {
    let Some(rb_component) = world.core.get_rigid_body(entity) else { return };
    let Some(handle) = rb_component.handle else { return };

    if let Some(rigid_body) = world.resources.physics.rigid_body_set.get_mut(handle.into()) {
        rigid_body.apply_impulse(
            rapier3d::prelude::Vector::new(impulse.x, impulse.y, impulse.z),
            true,
        );
    }
}
}

The difference from a force is units. Force is mass times acceleration, applied over the timestep. Impulse is mass times velocity, applied all at once.

Setting Velocity

#![allow(unused)]
fn main() {
fn set_velocity(world: &mut World, entity: Entity, velocity: Vec3) {
    let Some(rb_component) = world.core.get_rigid_body(entity) else { return };
    let Some(handle) = rb_component.handle else { return };

    if let Some(rigid_body) = world.resources.physics.rigid_body_set.get_mut(handle.into()) {
        rigid_body.set_linvel(
            rapier3d::prelude::Vector::new(velocity.x, velocity.y, velocity.z),
            true,
        );
    }
}
}

Setting velocity directly stomps whatever Rapier had. Useful for snapping a character to a target speed. Forces and impulses are better when you want the body to ease into motion.

Getting Velocity

#![allow(unused)]
fn main() {
fn get_velocity(world: &World, entity: Entity) -> Option<Vec3> {
    let rb_component = world.core.get_rigid_body(entity)?;
    let handle = rb_component.handle?;
    let rigid_body = world.resources.physics.rigid_body_set.get(handle.into())?;

    let vel = rigid_body.linvel();
    Some(Vec3::new(vel.x, vel.y, vel.z))
}
}

Mass Properties

#![allow(unused)]
fn main() {
fn set_mass(world: &mut World, entity: Entity, mass: f32) {
    let Some(rb_component) = world.core.get_rigid_body(entity) else { return };
    let Some(handle) = rb_component.handle else { return };

    if let Some(rigid_body) = world.resources.physics.rigid_body_set.get_mut(handle.into()) {
        rigid_body.set_additional_mass(mass, true);
    }
}
}

set_additional_mass adds to the mass Rapier computed from the collider's density and volume. Use density on the collider for typical materials. Use this for things like a hat that should not change the wearer's inertia.

Locking Axes

Some bodies should not be free to rotate or translate on every axis. A first-person character should not tip over. A 2.5D platformer body should not drift on Z.

#![allow(unused)]
fn main() {
if let Some(rigid_body) = world.resources.physics.rigid_body_set.get_mut(handle.into()) {
    rigid_body.lock_rotations(true, true);

    // rigid_body.lock_translations(true, true);
}
}

Damping

Damping is the air-drag-style velocity decay applied each step.

#![allow(unused)]
fn main() {
if let Some(rigid_body) = world.resources.physics.rigid_body_set.get_mut(handle.into()) {
    rigid_body.set_linear_damping(0.5);
    rigid_body.set_angular_damping(0.5);
}
}

Linear damping bleeds off translation, angular damping bleeds off spin. Values around 0.5 to 2.0 give a noticeable "thick air" feel. Zero is vacuum.

Sleeping

Rapier puts bodies to sleep when their velocity stays under a threshold for long enough. Sleeping bodies skip integration entirely. Wake them when you want them to react to something that does not generate a contact, like a teleport or a manual velocity change.

#![allow(unused)]
fn main() {
if let Some(rigid_body) = world.resources.physics.rigid_body_set.get_mut(handle.into()) {
    rigid_body.wake_up(true);
}
}