Physics, audio and input
Jolt physics with colliders streamed from greedy quads, the character controller, rigid bodies, constraints, impacts and raycasts; the audio engine's buses, voices, 3D emitters, procedural synths and world filter; and keyboard, mouse and gamepad input.
Physics
physics/PhysicsWorld.hpp, rv::physics::PhysicsWorld: a thin, ECS-facing facade over Jolt Physics. Positions are in render space (world space minus the floating origin) and metres.
Static voxel collision
Static colliders come from the same greedy quads the renderer draws (see Meshing), so collision matches what you see:
auto body = physics.addChunkCollider(origin, chunkMesh, palette); // collidable materials only
physics.removeBody(body);
A streamed world builds them asynchronously as the player comes near (see Worlds), and a rebuilt collider replaces the old body only once it is ready. ignoreStaticWithin(body, lo, hi, seconds) lets a static body be passed through in a box for a while (a hole just dug, a door just opened), and setBodyOffset moves a body after an origin rebase.
The character
The player is a Jolt CharacterVirtual capsule that walks up voxel terraces by stair stepping:
physics.createCharacter(feet, /*height=*/1.8f, /*radius=*/0.3f);
rv::physics::CharacterState s = physics.updateCharacter(dt, desiredVelocity, jumpSpeed, /*flying=*/false);
// s.position, s.velocity, s.onGround, s.groundNormal
physics.teleportCharacter(feet);
setCharacterHeight (crouching) and setCharacterBody(height, radius, span) (a rider and their horse) change the shape when there is room, returning false when there is not; turnCharacter(yaw) turns a body with a span.
Rigid bodies
Free bodies are made from boxes, with a mass (inertia follows the boxes’ shape), friction, restitution, gravity factor and damping. LostVale uses them for voxel debris and felled trees.
rv::physics::DynamicBodyDesc d;
d.boxes = {{{0, 0.5f, 0}, {0.3f, 0.5f, 0.3f}}};
d.mass = 80.0f;
d.position = where;
auto body = physics.addDynamicBody(d);
physics.addImpulse(body, impulse, atPoint);
Also: addStaticBoxes, translateBody, setVelocity; kinematic meshes moved by the game (addKinematicMesh, moveKinematic, placeKinematic: a ship’s hull you walk on); actor bodies from point clouds (addActorBody, setActorShape); and constraints (hingeToWorld(body, point, axis, frictionTorque): a tree hinging on its stump).
Impacts and raycasts
drainImpacts() returns each hard contact once, as it begins: the body, what it struck, the point, the normal and the closing speed, for sounds, dust and damage. setImpactThreshold(minSpeed) filters the gentle ones. raycast(origin, dir, maxDist, outDist, outNormal) casts against everything.
step(dt) advances the world; the Application steps it after each onFixedUpdate.
Audio
audio/AudioEngine.hpp, rv::audio::AudioEngine, on miniaudio.
Buses
Every sound plays on a bus with its own volume (the player’s options): Music, Ambience, Effects, Footsteps, Creatures and Interface. The world buses pass through the world filter, a low-pass and an echo eased over time, for underwater muffling and cave and hall echo; music and the interface never do. A limiter keeps the mix from clipping.
audio.setBusVolume(rv::audio::Bus::Music, 0.6f, /*seconds=*/1.5f); // ducking uses a fade
audio.setWorldFilter(/*lowpassHz=*/800.0f, /*echoWet=*/0.3f, /*echoDecay=*/0.5f, /*seconds=*/0.4f);
audio.setListener(cameraPos, forward, up);
audio.update(dt); // once a frame
Sampled sounds
rv::audio::SoundId hit = audio.load("assets/audio/sfx/hit.ogg"); // decoded once
rv::audio::SoundId theme = audio.load("assets/audio/music/dawn.ogg", true); // streamed from disk
rv::audio::PlayParams p;
p.bus = rv::audio::Bus::Effects;
p.positional = true;
p.position = where; p.minDistance = 2.0f; p.maxDistance = 60.0f;
p.delay = 0.8f; // thunder after the flash
p.priority = 2; // higher survives voice stealing
rv::audio::Voice v = audio.play(hit, p);
WAV, FLAC, MP3 and Ogg Vorbis load on a loader thread; play before loading finishes starts as soon as the data arrives. Short sounds decode into memory once and every play is a cheap copy; long ones (music, ambience beds) stream. PlayParams also take volume, pitch, pan, looping, rolloff (inverse, linear, exponential), fade-in, a start offset and a sample-aligned start frame for layered music. Voices are pooled; a handle to a voice that finished or was stolen simply stops working. stop, fadeTo, setVolume, setPitch, setPosition, cursor, seek and stopAll(bus, fade) control them.
Procedural emitters
Synthesised in real time with no sample assets: wind (filtered pink noise with gusts and a resonant whistle), surf (brown-noise swells with a breaking hiss), birds (FM chirp phrases), crickets (gated sine pulse trains), leaves (a crackle modulated by the wind) and footsteps (filtered bursts shaped by the surface: grass, sand, stone, wood, water, leaves, gravel, snow, mud, dirt).
auto wind = audio.createEmitter(rv::audio::SynthKind::Wind, /*spatial=*/false, seed);
audio.setEmitterIntensity(wind, gust); // drives loudness and character
Devices and capture
The engine follows the system’s default output (and moves with it), or plays to a named one, falling back and returning as devices come and go. Constructed with a WAV path, it plays to no device: a clock thread pulls the mix in real time and writes it to the file, for tests.
Input
platform/Platform.hpp. The Window pumps SDL events into an InputState snapshot each frame, kept as an ECS resource:
- keys by scancode:
down,pressedandreleasedthis frame (key(),keyPressed()); - mouse buttons, position, delta and wheel; mouse capture for mouse look;
anyPressedkeys whether or not the game has focus (for a UI’s own shortcuts);- the gamepad: buttons by position (south, east, west, north, back, guide, start, sticks, shoulders, d-pad, with the triggers as buttons past a threshold), raw axes, its type (for button names and glyphs) and hot-plug events;
lastDevice, the kind of controller touched last, so prompts and menus can follow it.
window.rumble(low, high, ms) shakes the pad. For tests, a virtual gamepad goes through SDL like a real one (attachVirtualPad, setVirtualPadButton, setVirtualPadAxis).
Written from the engine's headers and source as of commit 02e81b2 (2026-11-01).