Physics Engine

Understand how aether-physics integrates Rapier3D with the ECS for rigid bodies, colliders, collision layers, VR hand interaction, and server-authoritative simulation.

Overview

The aether-physics crate wraps Rapier3D as the physics backend and integrates with aether-ecs, exposing rigid bodies, colliders, joints, collision layers, trigger zones, and character controllers as ECS components. Physics runs server-authoritatively with optional client-side prediction. VR interaction primitives -- grabbing, throwing, haptic feedback -- are built on top of the core layer.

Physics Pipeline

Each tick runs three phases synchronized with the ECS stage pipeline:

  1. Pre-Physics -- Read ECS Transform/Velocity, push updates into the Rapier world
  2. Physics Step -- Rapier advances the simulation
  3. Post-Physics -- Read Rapier results, write back to ECS, collect collision/trigger events
use aether_physics::WorldPhysicsConfig;

let config = WorldPhysicsConfig {
    gravity: [0.0, -9.81, 0.0],
    time_step: 1.0 / 60.0,
    max_velocity: 100.0,
    enable_ccd: false,
    solver_iterations: 4,
};

Each world can have its own config -- earth-like gravity, zero-G, or underwater buoyancy.

Rigid Bodies and Colliders

Three body types are available via RigidBodyHandle: Dynamic (affected by forces), Kinematic (moved by code), and Static (immovable). Colliders define collision shapes:

use aether_physics::components::{RigidBodyHandle, BodyType, ColliderHandle, ColliderShape};

world.add_component(entity, RigidBodyHandle::new(BodyType::Dynamic));
world.add_component(entity, ColliderHandle::new(
    ColliderShape::Box { half_extents: [0.5, 0.5, 0.5] }
));
// Also: ColliderShape::Sphere { radius }, ColliderShape::Capsule { half_height, radius }

Colliders flagged as sensors detect overlaps without producing physical contact.

Collision Layers

A 16-bit membership + 16-bit filter bitmask controls which objects collide. Two colliders interact only when (A.membership & B.filter) != 0 && (B.membership & A.filter) != 0.

use aether_physics::layers::PhysicsLayer;

let player_layer = PhysicsLayer {
    membership: 1 << 1,                       // Layer 1: Players
    filter: (1 << 0) | (1 << 2) | (1 << 3),  // Default + Props + Terrain
};

Built-in layers: 0 (Default), 1 (Players), 2 (Props), 3 (Terrain), 4 (Triggers), 5-15 (user-defined).

Trigger Zones

Sensor colliders that generate enter/exit events without physical response, used for scripted gameplay areas:

for event in trigger_events.read() {
    match event {
        TriggerEvent::Enter { zone, entity } => { /* entity entered zone */ }
        TriggerEvent::Exit { zone, entity } => { /* entity exited zone */ }
    }
}

Character Controller

A kinematic body with ground detection (downward raycast), step climbing, and slope limiting:

use aether_physics::character::CharacterController;

let controller = CharacterController {
    grounded: false,
    step_height: 0.3,
    max_slope_angle: 45.0_f32.to_radians(),
    locomotion_mode: LocomotionMode::Smooth,
};

VR Interaction

Grab System

Attaches hand entities to physics objects using joint types -- Fixed (locked), Spring (follows with lag), or Hinge (rotates on axis):

use aether_physics::vr::grab::{GrabSystem, GrabJointKind, GrabConstraint};

let mut grabs = GrabSystem::new();
grabs.try_grab(hand, target, contact_point, GrabConstraint {
    joint_kind: GrabJointKind::Spring { stiffness: 5000.0, damping: 100.0 },
    break_force: 500.0,
    ..Default::default()
});
let release_velocity = grabs.release(hand);

Throw Detection

Tracks hand velocity over recent frames via a ring buffer and estimates release velocity with weighted averaging:

use aether_physics::vr::throw_detection::ThrowDetector;

let mut detector = ThrowDetector::new(10);
detector.record_sample(hand_velocity, angular_velocity, timestamp);
let throw = detector.estimate_release_velocity();

Haptic Feedback

Maps collision forces to controller vibration through configurable curves (Linear, Quadratic, or Custom):

use aether_physics::vr::haptic::{HapticFeedbackMapper, HapticFeedbackConfig, HapticCurve};

let mapper = HapticFeedbackMapper::new(HapticFeedbackConfig {
    curve: HapticCurve::Quadratic,
    min_force: 0.1,
    max_force: 50.0,
    duration_secs: 0.05,
});
let haptic = mapper.map_collision_force(force_magnitude);
// haptic.intensity: [0..1], ready for the controller

Object Manipulation

Two-hand manipulation for rotation and scaling of held objects:

use aether_physics::vr::manipulation::ManipulationState;
let result = manipulation.update_two_hands(left_transform, right_transform);
// result.rotation: quaternion delta, result.scale_factor: e.g. 1.2

Server-Authoritative Model

The server runs authoritative physics. Clients predict locally with the same Rapier config and reconcile on server state receipt. PhysicsAuthority marks who controls each body:

use aether_physics::authority::PhysicsAuthority;
world.add_component(npc, PhysicsAuthority::Server);
world.add_component(avatar, PhysicsAuthority::Client(42));

Key Types Reference

TypeModulePurpose
WorldPhysicsConfigconfigPer-world gravity, time step, solver settings
RigidBodyHandlecomponentsLinks entity to a Rapier rigid body
ColliderHandlecomponentsCollision shape attached to a body
PhysicsLayerlayersMembership + filter bitmask
TriggerZonetriggerSensor collider emitting enter/exit events
CharacterControllercharacterKinematic controller with ground detection
PhysicsAuthorityauthorityServer vs. client authority marker
GrabSystemvr::grabJoint-based grab management
ThrowDetectorvr::throw_detectionVelocity sampling for throws
HapticFeedbackMappervr::hapticForce-to-vibration mapping
ManipulationStatevr::manipulationTwo-hand rotate and scale