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:
- Pre-Physics -- Read ECS
Transform/Velocity, push updates into the Rapier world - Physics Step -- Rapier advances the simulation
- 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
| Type | Module | Purpose |
|---|---|---|
WorldPhysicsConfig | config | Per-world gravity, time step, solver settings |
RigidBodyHandle | components | Links entity to a Rapier rigid body |
ColliderHandle | components | Collision shape attached to a body |
PhysicsLayer | layers | Membership + filter bitmask |
TriggerZone | trigger | Sensor collider emitting enter/exit events |
CharacterController | character | Kinematic controller with ground detection |
PhysicsAuthority | authority | Server vs. client authority marker |
GrabSystem | vr::grab | Joint-based grab management |
ThrowDetector | vr::throw_detection | Velocity sampling for throws |
HapticFeedbackMapper | vr::haptic | Force-to-vibration mapping |
ManipulationState | vr::manipulation | Two-hand rotate and scale |