@threlte/rapier

Physics Framerate

Threlte’s <World> component has a framerate prop that can be used to control the framerate of the physics simulation.

requestAnimationFrame Timing

By default, the physics framerate is set to vary depending on the timestamp passed as the argument to the callback of requestAnimationFrame. Take this trivial example:

const render = (time: number) => {
  console.log(time)
  requestAnimationFrame(render)
}
requestAnimationFrame(render)

Unlucky for us, the time delta between frames varies depending on several factors such as the refresh rate of the monitor and the general load of your computer or application. When using a varying framerate, this time delta is used to advance the physics simulation and while you would think that this wouldn’t make much of a difference, computers are really bad at floating point arithmetic (console.log(0.1 + 0.2)): Realistically no two executions of the same physics simulation will yield the same result. In short: it’s not deterministic.

For most applications, this is still the best choice since it doesn’t require you to think about the aspects of handling different framerates for physics and rendering.

Sometimes, however, whether you’re developing a game or wish to have more control art directing the outcome, you want a physics simulation to always yield the same result, e.g. to be deterministic. In this case, you can set the framerate prop to a fixed number. A fixed framerate of 50 for example will step the physics simulation 50 times per second with a delta of exactly 1 / 50 = 0.02 seconds – no matter the timestamp passed to requestAnimationFrame.

The following example illustrates the difference:

Fixed Framerate

In order to efficiently use a fixed framerate, it’s important to understand how Threlte is able to advance the physics simulation in a deterministic fashion while keeping the visual output smooth and in sync.

First, let’s have a look at the timing of the requestAnimationFrame API:

Frame rate 1

In this example we’re looking at two invokations of requestAnimationFrame.

  • A receives a delta of 16.35ms
  • B receives a delta of 16.1ms

Threlte is making use of the scheduler’s ability to run the tasks of a stage multiple times per frame and with a fixed delta.

We’d like to advance the physics simulation 200 times per second, so 5ms per step.

Frame rate 2

In this example, the physics simulation is advanced 4 times as part of requestAnimationFrame A. The simulation then runs ahead of the visual output. After advancing the physics simulation, the visual state of the objects is rolled back from 20ms to 16.35ms while internally maintaining the physics state of after the 4th simulation step.

This cycle repeats in the next frame B: The simulation is advanced 3 times as part of requestAnimationFrame B in order to slightly run ahead of the render time and subsequently rolled back to match the time of the render.

The following example visualizes how the framerate correlates with the physics simulation running ahead of the visual output.

usePhysicsTask

Use the hook usePhysicsTask to interact with the physics simulation. Depending on your choice of framerate the provided callback will be called differently:

Varying Framerate

When you’re using a varying framerate for your physics simulation, the hook essentially behaves like a regular useTask with the benefit that it’s added to the physics stage automatically.

<World framerate="varying">
usePhysicsTask((delta) => {
  console.log(delta) // ~0.016
})

Fixed Framerate

When you’re using a fixed framerate for your physics simulation, the callback will be called with the fixed delta time between frames.

<World framerate={200}>
usePhysicsTask((delta) => {
  console.log(delta) // 0.005
})