Course outline
Physics Engines
In one line
A physics engine keeps its own world of bodies (things with mass and motion) and colliders (their shapes), applies gravity and contacts, and moves everything forward one step at a time. Rapier runs as WebAssembly: you must await RAPIER.init() before using it.
Think of it like this
A referee and a scoreboard. The players (three.js meshes) are what the crowd sees, but the referee (the physics engine) decides where the ball really is, who touched it, and what happens next. The scoreboard only shows the referee's decisions.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- physics engine
- A library that moves objects by forces, gravity and contacts, one time step at a time.
- rigid body
- A thing the engine moves: position, rotation, velocity and mass. It never bends.
- collider
- The shape of a body for collisions (ball, cuboid, cylinder, mesh). Without a body, it is fixed in place.
- step
- One move of the whole world forward by a fixed time, usually 1/60 s.
- WebAssembly
- A compact binary format browsers run alongside JavaScript, used for fast code written in languages like Rust.
- sleep
- A resting body the engine stops simulating until something wakes it.
Step by step
01Start the engine
import RAPIER from "@dimforge/rapier3d-compat", then await RAPIER.init(). The first example tries to make a world before that and prints the real error, then does it properly.
02Build a world
Gravity, one fixed collider for the floor (ColliderDesc.cuboid(5, 0.1, 5): half-sizes, so 10 × 0.2 × 10 m), one dynamic body with a ball collider. Note that cuboid sizes are half sizes.
03Step and watch
120 steps (two seconds), printing the ball's height every 20. It falls, bounces once (restitution 0.5) and rests.
04Draw it with three.js
The second example draws three crates on the stand. Each frame copies every body's translation() and rotation() into its mesh. The engine owns the pose; three.js only shows it (Topic 16.4).
Try it yourself
- 1
Make it the Moon
In the first example, change gravity to
y: -1.62. Gravity pulls about six times less, so the fall takes about 2.5 times longer (the time grows with the square root). Measured: at step 20 the ball is still aty = 1.909, and at step 120 it is still bouncing aty = 0.413instead of resting at 0.350. - 2
Stack them higher
In the second example, add a fourth crate
[0.1, 3, 0.3]to the list. Measured: it lands on the pile, tumbles off the edge of the stand and keeps falling (crate 4: y -12.41, asleep: false): the stand is the only floor in this world.
Code & diagrams
Rapier 0.21.0. The numbers are the same on every machine: the engine is deterministic for the same inputs and steps.
Console
(nothing was printed)Expected console output
world before init: TypeError: Cannot read properties of undefined (reading 'rawintegrationparameters_new')
rapier version: 0.21.0
timestep: 0.01666666753590107
step 20: y = 1.448
step 40: y = 0.399
step 60: y = 0.350
step 80: y = 0.350
step 100: y = 0.350
step 120: y = 0.350One physics step per frame for 240 frames, then it stops stepping. Watch the scene tree: the meshes are ordinary three.js objects.
Console
Run the scene to see its console.Expected console output
after 240 steps (4 s of physics):
crate 1: y 0.20, asleep: true
crate 2: y 0.20, asleep: true
crate 3: y 0.44, asleep: trueBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget the half-size
In the second example, write ColliderDesc.cuboid(0.4, 0.4, 0.4) for the crates (the full size).
Myth vs fact
Myth
three.js has physics built in.
Fact
three.js only draws. Physics comes from a separate engine (Rapier, cannon-es, Ammo, Jolt), and your code copies its results into the meshes.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
The
-compatbuild inlines its WebAssembly as base64 inside the JavaScript (so no bundler setup is needed); the non-compat@dimforge/rapier3dpackage loads a separate.wasmfile, which is smaller to download but needs your bundler to serve it. - ▸
Rapier is deterministic for the same version, inputs and step sequence (the example's numbers repeat on every run). That is what lets multiplayer games send inputs instead of positions, and what makes physics testable.
Remember this
- 1
The problem this solves: making things fall, stack, bounce and collide by hand means solving contact forces between every pair of objects, every frame, without them sinking into each other. That is decades of work. An engine does it, and you describe the world.
- 2
Bodies and colliders. A rigid body has a position, rotation, velocity and mass, and the engine moves it. A collider is a shape (ball, cuboid, cylinder, capsule, triangle mesh) attached to a body; it gives the body its size and, through its density, its mass. A collider with no body is fixed: floors, walls, the stand. Bodies can be dynamic (moved by forces), fixed, or kinematic (moved by your code, pushing dynamic bodies out of the way).
- 3
Rapier and its init. Rapier is written in Rust and compiled to WebAssembly (a fast binary format browsers run next to JavaScript). The
-compatpackage embeds the WebAssembly inside its JavaScript file, so it works with any bundler, at the cost of size. That code must be started first:await RAPIER.init(). Used before that, it fails withTypeError: Cannot read properties of undefined (reading 'rawintegrationparameters_new'), an unhelpful message that only means "not started yet". - 4
The step.
new RAPIER.World({ x: 0, y: -9.81, z: 0 })sets gravity in m/s².world.step()moves everything forward byworld.timestep, which is 1/60 s by default (it prints as 0.01666666753590107: Rapier stores it in a 32-bit float). Nothing moves between steps. The first example drops a ball (radius 0.25) on a floor whose top is at y = 0.1: after 60 steps it rests at y = 0.350, exactly 0.1 + 0.25. - 5
Sleep. A body that has stopped moving goes to sleep: the engine skips it until something touches it or you push it. In the second example all three crates are asleep after 240 steps. A resting pile of crates costs far less than the same pile in motion, because the sleeping ones are skipped.
Explain it without notes
What is the difference between a rigid body and a collider?
Why must you await RAPIER.init(), and what happens if you don't?
Practice
Add a second ball with restitution 0 next to the first, and print both heights.
Make the floor a fixed body explicitly, instead of a collider without a body.
Done when you can
You can create a Rapier world after init() and step it
You know bodies from colliders, and fixed from dynamic
You remember that cuboid takes half-sizes
You can copy a body's pose into a mesh