@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:
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.
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
})