Command Palette

Search for a command to run...

Hectal
Course outline
Topic 16.4Advanced ~18 min · step 4 of 6

Syncing Physics and Rendering

In one line

The physics world and the three.js scene are two separate copies of the world. After every step, copy each body's position and rotation into its mesh, and never move the mesh by hand: the body owns the transform. In React Three Fiber, @react-three/rapier does this for you.

0/6 · 0%

Think of it like this

A puppet and its shadow on the wall. You move the puppet; the shadow follows. If you paint a new shadow on the wall instead, it no longer matches the puppet, and the next time the puppet moves, your painting is gone.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

sync
Copying each body's position and rotation into its mesh after a physics step.
owner
The one place allowed to change an object's transform; for a dynamic body, the physics engine.
kinematic body
A body your code moves by setting its next position; it pushes dynamic bodies but isn't pushed back.
teleport
Moving a body instantly with setTranslation, without a path the engine can check.

Step by step

01Compare three ways to sync

Run the first example. Each line slides the same crate for 90 steps and measures how far the mesh is from its body, and how far each has turned.

02Keep one owner

For each object, decide: body-owned (dynamic: copy body to mesh) or code-owned (kinematic: your code sets the next position; the engine follows). Never both.

03Let the library do it

The second example is the same idea in R3F: two crates fall onto a fixed slab; onSleep fires when each one comes to rest.

04Read the warnings

Both warnings come from libraries, not your code. Note them, check the release notes when you upgrade, and don't hide them.

Try it yourself

  1. 1

    Draw the colliders

    In the second example, write <Physics debug>. Coloured lines trace the edges of each collider, drawn on top of the meshes: the quickest way to see a collider that doesn't match its mesh.

  2. 2

    Slow the world

    In the second example, write <Physics gravity={[0, -1.62, 0]}>. The crates drop like on the Moon, noticeably slower, and the two "fell asleep" lines still appear (measured: both within 9 s).

Code & diagrams

Copy position and rotation, or not New tab
idle

Console

Run the scene to see its console.

Expected console output

copy position and rotation: mesh 0.00 m from the body, turned 29° (body 29°)
copy position only:         mesh 0.00 m from the body, turned 0° (body 29°)
mesh moved by hand too:     mesh 0.50 m from the body, turned 29° (body 29°)
Physics in React Three Fiber New tab

@react-three/rapier 2.2.0 (built against Rapier 0.19.2) running on the course's Rapier 0.21.0: that mismatch is why the init warning appears. `onSleep` fires once per crate when it comes to rest.

idle

Console

Run the scene to see its console.

Expected console output

THREE.Clock: This module has been deprecated. Please use THREE.Timer instead.
using deprecated parameters for the initialization function; pass a single object instead
the crate that started at y 1 fell asleep
the crate that started at y 2 fell asleep

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Teleport into a wall

Zero gravity, a fixed wall 0.2 m thick at x = 1 (faces at 0.9 and 1.1), and a 0.4 m dynamic box. Teleport the box with setTranslation to x = 0.9 (half inside the wall), x = 0.8, or x = 1.0 (its centre in the middle of the wall), and step.

terminal
$ Step the world 30 times for each
── what you'll see ──
x 0.8: after 1, 5, 10, 20, 30 steps x = 0.775, 0.724, 0.700, 0.661, 0.621 | speed -0.24
x 0.9: after 1, 5, 10, 20, 30 steps x = 0.854, 0.749, 0.704, 0.634, 0.565 | speed -0.42
x 1.0: after 1, 5, 10, 20, 30 steps x = 1.047, 1.210, 1.280, 1.347, 1.413 | speed +0.40
Measured in Rapier 0.21.0. A box at x 0.7 just touches the wall's face. The first two are shoved back out over many steps and overshoot (the box's front is at x + 0.2, so x 0.7 means just touching), carrying on with the speed they gained; the last one is shoved out of the far side of the wall.

Myth vs fact

Myth

You can move physics objects by setting mesh.position.

Fact

The mesh is a picture of the body. The next sync puts it back, and the body never moved. Move bodies, then copy.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    For many bodies, copy poses into an InstancedMesh with setMatrixAt instead of a mesh per body: one draw call for a thousand crates (Topic 17.2).

  • ▸

    With interpolation (Topic 16.2), keep the previous pose of each body and draw lerp(previous, current, alpha); @react-three/rapier's <Physics> has an interpolate prop for the same reason.

Remember this

  1. 1

    The problem this solves: physics moves bodies, but you see meshes. If the two get out of step, the picture lies: a crate looks upright while its body lies on its side, or a mesh moved by code walks through a wall that its body cannot pass.

  2. 2

    Copy both, every step. mesh.position.copy(body.translation()) and mesh.quaternion.copy(body.rotation()). The first example slides a crate down a slope: copying both keeps the mesh exactly on the body; copying only the position leaves the mesh at 0° while the body has turned 29°. Rapier gives positions and rotations as plain { x, y, z } and { x, y, z, w } objects, which three.js's copy accepts.

  3. 3

    Who owns the transform. For dynamic bodies, the body owns it; the mesh only shows it. Moving the mesh by hand is undone at the next copy and, until then, the picture is wrong (the example's third line: 0.50 m away). To move a dynamic body, apply a force or impulse, or set its velocity. To place it once (a reset), body.setTranslation() and setRotation(). For things your code moves (a platform), use a kinematic body with setNextKinematicTranslation(): it pushes dynamic bodies out of its way.

  4. 4

    Teleports. setTranslation jumps without a path, so it can put a body inside another. Measured: a 0.4 m box teleported half inside a fixed wall is pushed back out over many steps, not at once (about 10 steps to get out, and then it overshoots with the speed it gained), and one teleported to the middle of a 0.2 m wall pops out of the far side. Teleport only into free space, as the build's reset does (back to the start).

  5. 5

    In React Three Fiber. @react-three/rapier wraps Rapier in components: <Physics> creates and steps the world (with its own fixed-step loop), and <RigidBody> makes a body whose collider is generated from its child meshes (colliders="cuboid") and copies the pose to them each frame. The second example prints two real warnings: R3F 9.8.2's THREE.Clock deprecation (Topic 11.1), and Rapier's own using deprecated parameters for the initialization function; pass a single object instead, because this version of the library calls init() the older way.

Explain it without notes

01

Who should own an object's transform in a physics scene, and what goes wrong otherwise?

02

How do you move a dynamic body, a kinematic body and a fixed one?

Practice

01

Write syncAll(pairs) that copies every body's pose into its mesh.

02

Make a moving platform: a kinematic body that slides back and forth 1 m, carrying a crate.

Done when you can

  • You copy position and rotation after each step

  • You keep one owner per object

  • You move dynamic bodies with forces or velocities, not by moving meshes

  • You can use @react-three/rapier and read its warnings