Core Engine

aether-physics

Rapier3D physics integration

The aether-physics crate integrates the Rapier3D physics engine with Aether's ECS. It provides rigid body dynamics, collision detection, joints, and VR-specific interaction physics including grab, throw, hand collision, and haptic feedback.

Overview

Physics simulation runs as an ECS system, stepping the Rapier world each tick and synchronizing results back to entity components. The crate handles:

  • Rigid body dynamics with configurable mass, damping, and gravity.
  • Colliders in standard shapes (box, sphere, capsule, convex hull, trimesh) with collision layers and filters.
  • Joints for constraining bodies (fixed, revolute, prismatic, spherical).
  • Triggers for detecting overlap without generating contact forces.
  • VR interaction for physically plausible hand interactions in virtual reality.

Key Types

PhysicsWorld

Wraps the Rapier physics pipeline and manages the simulation state. Typically accessed through the ECS as a resource.

use aether_physics::PhysicsWorld;

let mut physics = PhysicsWorld::new();
physics.set_gravity([0.0, -9.81, 0.0]);

RigidBody

Defines how an entity participates in physics simulation. Variants include dynamic, kinematic, and static.

use aether_physics::{RigidBody, RigidBodyType};

let body = RigidBody::new(RigidBodyType::Dynamic)
    .with_mass(2.0)
    .with_linear_damping(0.1)
    .with_angular_damping(0.05);

world.insert(entity, body);

Collider

Defines the shape used for collision detection. Attach one or more colliders to an entity with a rigid body.

use aether_physics::{Collider, ColliderShape};

// Box collider with half-extents
let collider = Collider::new(ColliderShape::Box {
    half_extents: [0.5, 0.5, 0.5],
})
.with_friction(0.6)
.with_restitution(0.3)
.with_collision_layer(CollisionLayer::DEFAULT);

world.insert(entity, collider);

Joint

Constrains two rigid bodies together. Useful for doors, chains, ragdolls, and mechanical assemblies.

use aether_physics::{Joint, JointType};

let hinge = Joint::new(JointType::Revolute {
    axis: [0.0, 1.0, 0.0],
    limits: Some((-1.57, 1.57)),
})
.with_body_a(door_frame)
.with_body_b(door_panel);

physics.create_joint(hinge);

VR Interaction Features

The crate includes a dedicated VR interaction layer built on top of the core physics simulation.

Grab

Entities tagged with Grabbable can be picked up by VR hand controllers. When a grab is initiated, a temporary joint is created between the hand and the object, allowing natural physical manipulation.

use aether_physics::{Grabbable, GrabSettings};

world.insert(entity, Grabbable {
    settings: GrabSettings {
        snap_to_hand: true,
        two_hand_mode: TwoHandMode::ScaleAndRotate,
        ..Default::default()
    },
});

Throw

When a grabbed object is released, its velocity is computed from the hand's recent motion history. This produces natural-feeling throws with accurate direction and force.

use aether_physics::ThrowSettings;

world.insert(entity, Grabbable {
    settings: GrabSettings {
        throw: ThrowSettings {
            velocity_samples: 5,
            max_speed: 20.0,
            ..Default::default()
        },
        ..Default::default()
    },
});

Hand Collision

Hand colliders track the VR controller positions and generate contact events when the player's hands interact with the environment. This enables pushing buttons, flipping switches, and touching surfaces.

use aether_physics::HandCollider;

// Hand colliders are typically set up by the input system automatically.
// Custom collision responses can be registered through the event bus:
for event in world.events().read::<HandContactEvent>() {
    if event.other_entity == button_entity {
        activate_button(event.other_entity);
    }
}

Haptics

Contact events can trigger haptic feedback on the VR controller, providing tactile response when interacting with objects.

use aether_physics::HapticFeedback;

world.insert(entity, HapticFeedback {
    on_contact: HapticPulse {
        intensity: 0.5,
        duration_ms: 50,
    },
    on_grab: HapticPulse {
        intensity: 0.8,
        duration_ms: 100,
    },
});

Basic Usage

A minimal physics setup that creates a ground plane and a falling box:

use aether_ecs::World;
use aether_physics::{
    PhysicsPlugin, RigidBody, RigidBodyType, Collider, ColliderShape,
};

let mut world = World::new();
world.add_plugin(PhysicsPlugin::default());

// Static ground plane
let ground = world.spawn();
world.insert(ground, RigidBody::new(RigidBodyType::Static));
world.insert(ground, Collider::new(ColliderShape::Box {
    half_extents: [50.0, 0.1, 50.0],
}));

// Dynamic box that falls under gravity
let cube = world.spawn();
world.insert(cube, RigidBody::new(RigidBodyType::Dynamic).with_mass(1.0));
world.insert(cube, Collider::new(ColliderShape::Box {
    half_extents: [0.5, 0.5, 0.5],
}));
world.insert(cube, Position { x: 0.0, y: 10.0, z: 0.0 });

// Step the simulation
world.tick();