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 Overview

Live Demo: Physics

Nightshade integrates Rapier3D for physics simulation, providing rigid body dynamics, collision detection, and character controllers.

Enabling Physics

Physics is enabled with the physics feature:

[dependencies]
nightshade = { git = "...", features = ["engine", "physics"] }

Physics World

The physics world is accessed through resources:

#![allow(unused)]
fn main() {
let physics = &mut world.resources.physics;

// Configuration
physics.gravity = Vec3::new(0.0, -9.81, 0.0);
physics.fixed_timestep = 1.0 / 60.0;
}

Core Concepts

Rigid Bodies

Objects that can move and be affected by forces:

  • Dynamic: Affected by gravity and forces
  • Kinematic: Moved by code, affects dynamic bodies
  • Fixed: Immovable, infinite mass

Colliders

Shapes used for collision detection:

  • Cuboid, Ball, Capsule, Cylinder
  • Triangle mesh, Heightfield
  • Compound shapes

Character Controllers

Kinematic bodies with special handling for player movement.

Quick Start

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

fn initialize(&mut self, world: &mut World) {
    spawn_cube_at(world, Vec3::new(0.0, -0.5, 0.0));

    for index in 0..10 {
        spawn_cube_at(world, Vec3::new(0.0, 2.0 + index as f32 * 1.5, 0.0));
    }
}
}

Physics Synchronization

Physics runs at a fixed timestep. Transforms are automatically synchronized:

  1. Game logic updates entity transforms
  2. Physics simulation steps (may run multiple times per frame)
  3. Physics transforms sync back to entities
  4. Interpolation smooths visual positions

Querying Physics

Picking (Raycasting)

The picking system casts rays from screen coordinates to find entities:

#![allow(unused)]
fn main() {
let (width, height) = world.resources.window.cached_viewport_size.unwrap_or((800, 600));
let screen_center = Vec2::new(width as f32 / 2.0, height as f32 / 2.0);

if let Some(hit) = pick_closest_entity(world, screen_center) {
    let hit_entity = hit.entity;
    let hit_distance = hit.distance;
    let hit_position = hit.world_position;
}
}

For precise trimesh-based raycasting (requires physics feature):

#![allow(unused)]
fn main() {
if let Some(hit) = pick_closest_entity_trimesh(world, screen_center) {
    let hit_entity = hit.entity;
    let hit_position = hit.world_position;
}
}

Physics Materials

Friction, restitution, and density are set directly on the ColliderComponent:

#![allow(unused)]
fn main() {
world.core.set_collider(entity, ColliderComponent::new_cuboid(0.5, 0.5, 0.5)
    .with_friction(0.5)
    .with_restitution(0.3)
    .with_density(1.0));
}

Debug Visualization

Enable physics debug drawing:

#![allow(unused)]
fn main() {
world.resources.physics.debug_draw = true;

// In run_systems
physics_debug_draw_system(world);
}

This renders:

  • Collider shapes (wireframe)
  • Contact points
  • Collision normals

Performance Tips

  1. Use simple collider shapes (boxes, spheres) when possible
  2. Disable collision between groups that don't need it
  3. Use compound colliders instead of many small colliders
  4. Set bodies to sleep when inactive
  5. Use appropriate fixed timestep (60 Hz is standard)

Joints

Connect bodies with joints for:

  • Doors (revolute)
  • Drawers (prismatic)
  • Ropes (rope/spring)
  • Chains (spherical)

See Physics Joints for details.