Add fixedUpdate method
For accurate physics simulation, the built-in fixedUpdate method is missing. Do you plan to add it?
fixedUpdate used to be part of the engine, but it was removed a long time ago. The implementation under the hood currently uses normal update, and runs multiple physics steps - each of them using a fixed dt, and so it's behaviour is the same as if using fixedUpdate.
What specific issues are you hitting?
fixedUpdate used to be part of the engine, but it was removed a long time ago. The implementation under the hood currently uses normal update, and runs multiple physics steps - each of them using a fixed dt, and so it's behaviour is the same as if using fixedUpdate.
What specific issues are you hitting?
For example, sometimes when working with physics the step does not correspond to the state of physics.
const defaultUpdateEventName = 'update';
const fixedUpdateEventName = 'fixed-update';
const physicsEngineFixedUpdateEventName = 'physics-fixed-update';
const physicsEngineUpdateEventName = 'physics-update';
....
const rb = this.app.systems.rigidbody;
rb .maxSubSteps = 0;
rb .fixedTimeStep = this.fixedTimeStep;
this.app.systems.off(defaultUpdateEventName, rb.onUpdate, rb);
this.app.systems.on(physicsEngineFixedUpdateEventName, rb.onUpdate, rb);
.....
onUpdate(deltaTime) {
this._dynamicTime += deltaTime;
this._internalTime += deltaTime;
const fixedTimeStep = this.fixedTimeStep;
if (this._internalTime >= fixedTimeStep) {
const numSimulationSubSteps = (this._internalTime / fixedTimeStep) | 0;
this._internalTime -= numSimulationSubSteps * fixedTimeStep;
for (let i = 0; i < numSimulationSubSteps; i++) {
this._fixedTime += fixedTimeStep;
this.app.systems.fire(fixedUpdateEventName, fixedTimeStep);
this.app.fire(fixedUpdateEventName, fixedTimeStep);
this.app.systems.fire(physicsEngineFixedUpdateEventName, fixedTimeStep);
this.app.fire(physicsEngineFixedUpdateEventName, fixedTimeStep);
}
}
// apply interpolation for items
this.app.systems.fire(physicsEngineUpdateEventName, deltaTime);
this.app.fire(physicsEngineUpdateEventName, deltaTime);
}
...
Also, when controlling a fixed step, there is the ability to add other physics engines that do not have a method similar to Ammo stepSimulation(dt, steps, fixedDt) under the hood.
the step does not correspond to the state of physics.
Could you elaborate more on this?
I agree with OP here, a fixedStep event/method is needed. No clue why it was removed when it was - I was not born that time yet. For any physics manipulation and predictability, you want to do that at fixed intervals, not at intervals that browser decides.
For example, applying impulse on a body will accumulate that command in Ammo until it steps. So, if you run in normal update method:
entity.rigidbody.applyImpulse(0, 1, 0);
It will look very different on different monitors.
You could say, yeah well, just use dt:
entity.rigidbody.applyImpulse(0, 1 * dt, 0);
That will still not solve it, as the error will accumulate over time, simply making it harder to see right away. You can measure the distance body traveled after some fixed seconds and they will not match by a large margin.
This all can be fixed with a fixed update method that runs at specified intervals. Browser doesn't actually have an ability to have a precise interval method, like system engines do (Unreal/Unity), but we can get pretty close to it anyway with tracking time. You then apply impulses/forces in a fixed update, while normal update is used for everything else.
the step does not correspond to the state of physics.
Could you elaborate more on this?
Currently, I am working on integrating Jolt into PlayCanvas with maximum compatibility for RigidBodySystem | RigidBodyComponent | CollisionSystem | CollisionComponent, ...
Since the Jolt engine lacks an interpolation method at the backend level, it is necessary to introduce a fixedUpdate method.
Together with LeXXik, we want to test Jolt's multithreading capabilities. If the performance with multithreading is excellent, then using the standard Jolt wrapper with a worker may not be necessary. This could provide a significant boost in game performance.
https://github.com/jrouwe/JoltPhysics.js/discussions/110
https://jrouwe.github.io/JoltPhysics.js/stress_test.html https://jrouwe.github.io/JoltPhysics.js/stress_test_threaded.html
This all can be fixed with a fixed update method that runs at specified intervals. Browser doesn't actually have an ability to have a precise interval method, like system engines do (Unreal/Unity), but we can get pretty close to it anyway with tracking time. You then apply impulses/forces in a fixed update, while normal update is used for everything else.
Also, updating physics every frame can be expensive.
but stepSimulation under the hood does multiple fixed step simulations - so the physics runs on fixed step.
This is similar to what we do under the hood for particles here to simulate at fixed step:
https://github.com/playcanvas/engine/blob/2e195072082fe19e8577bed0dd0df1eba98daee5/src/framework/components/particle-system/system.js#L243-L251
You can do this with Jolt as well?
but
stepSimulationunder the hood does multiple fixed step simulations - so the physics runs on fixed step. This is similar to what we do under the hood for particles here to simulate at fixed step:engine/src/framework/components/particle-system/system.js
Lines 243 to 251 in 2e19507
if (emitter.simTime >= emitter.fixedTimeStep) { numSteps = Math.floor(emitter.simTime / emitter.fixedTimeStep); emitter.simTime -= numSteps * emitter.fixedTimeStep; } if (numSteps) { numSteps = Math.min(numSteps, emitter.maxSubSteps); for (let i = 0; i < numSteps; i++) { emitter.addTime(emitter.fixedTimeStep, false); } You can do this with Jolt as well?
Yes, it is possible, but conceptually this method is not enough.
Yes, it is possible, but conceptually this method is not enough.
Please explain what is missing. You can modify any script that currently runs with random dt, to run at fixed step this way.
If the engine implements fixedUpdate, it will be done exactly like this, there is no other way.
Yes, it is possible, but conceptually this method is not enough.
Please explain what is missing. You can modify any script that currently runs with random dt, to run at fixed step this way.
If the engine implements
fixedUpdate, it will be done exactly like this, there is no other way.
What we lack is a standard implementation of fixed updates, which provides full control over the physics in the game. Such a mechanism is implemented in all major game engines, allowing for consistent and reliable physics calculations regardless of frame rate variations. Without this, achieving precise and stable physics behavior remains challenging.
but stepSimulation under the hood does multiple fixed step simulations - so the physics runs on fixed step.
No, not really. The engine steps Ammo every frame: https://github.com/playcanvas/engine/blob/2e195072082fe19e8577bed0dd0df1eba98daee5/src/framework/components/rigid-body/system.js#L1090
Internally, Ammo counts the time and steps the world, once fixed time has passed. Other physics engines don't do it, so you would have to actually step every frame. Regardless, the issue is still there.
Say, your browser runs at 60hz, Ammo runs at 1/60 fixed step. This will match the time so that script's update method will be called once before we step the physics world. Now, if the browser runs at 120hz, and Ammo still at fixed 1/60, then what happens is that Ammo will receive 2 commands to dynamicsWorld.stepSimulation before it will actually step the world. In this case, our script update method will still be called twice. And that will apply impulse twice as well (it is not! overwriting the previous applyMethod, it accumulates), resulting in different outcomes on different refresh rates of the monitor.
This is not really about what dt value we pass to a script's update method, it is about the frequency that method is called. It should match the rate Ammo steps the world.
but stepSimulation under the hood does multiple fixed step simulations - so the physics runs on fixed step.
No, not really. The engine steps Ammo every frame:
engine/src/framework/components/rigid-body/system.js
Line 1090 in 2e19507
this.dynamicsWorld.stepSimulation(dt, this.maxSubSteps, this.fixedTimeStep); Internally, Ammo counts the time and steps the world, once fixed time has passed. Other physics engines don't do it, so you would have to actually step every frame. Regardless, the issue is still there.
Say, your browser runs at 60hz, Ammo runs at 1/60 fixed step. This will match the time so that script's update method will be called once before we step the physics world. Now, if the browser runs at 120hz, and Ammo still at fixed 1/60, then what happens is that Ammo will receive 2 commands to
dynamicsWorld.stepSimulationbefore it will actually step the world. In this case, our script update method will still be called twice. And that will apply impulse twice as well (it is not! overwriting the previous applyMethod, it accumulates), resulting in different outcomes on different refresh rates of the monitor.This is not really about what dt value we pass to a script's update method, it is about the frequency that method is called. It should match the rate Ammo steps the world.
https://github.com/AlexAPPi/ammo.js/blob/1f3eaf1ee3a9e6c8423af3dc50ca1976fadf332a/bullet/src/BulletDynamics/Dynamics/btDiscreteDynamicsWorld.cpp#L405
If maxSubSteps = 0 then
//variable timestep
fixedTimeStep = timeStep;
m_localTime = m_latencyMotionStateInterpolation ? 0 : timeStep;
m_fixedTimeStep = 0;
if (btFuzzyZero(timeStep))
{
numSimulationSubSteps = 0;
maxSubSteps = 0;
} else
{
// WE HERE IF (timeStep = fixedTimeStep and maxSubSteps = 0)
numSimulationSubSteps = 1;
maxSubSteps = 1;
}
If maxSubSteps = 0 then
Let's not complicate the matter, so Martin can actually follow what we are talking about :D I'm speaking of the current default setup the engine is using.
Yep that makes sense. So you do not worry about the way ammo internally simulates, as that runs at fixed time step. But the problem is that you need to send updates to it (impulses ...) at the same time step, and that is not exposed in any way.
Agreed with this. This is a good reason to have fixedUpdate.
It would be nice if Ammo had a method to know when it actually stepped the world. We don't have access to the time it tracks, so we never know. We would have to track it ourselves in this case. I found that if we track it ourselves, then our time matches pretty well with Ammo's, even though there will be a slight discrepancy (Ammo is in 32 bit space, while we are tracking it in 64 bit), but it is so small that it can be neglected.