Inconsistent Physics Behavior in Babylon.js on Different FPS

Hi there!
I don’t know, is it a bug or a feature, but I have a question:

I have a Macbook Pro, M1 Pro, 16GB RAM

  1. https://playground.babylonjs.com/#E5URLZ#2
  2. Click on the Vortex button

The result when I did it directly in Macbook, with a full battery. All boxes fly upper than the cylinder

The result with external monitor 4k (or I can reproduce with low battery). Boxes dance in circles without flying high

So, I’m developing a multiplayer game, where I use physicsHelper. And depends on the laptop people have different behaviors. Yes, I know, it is fanny. If you want to win, then buy new laptop. It is a joke. How I can control it and make only one behavior?

Thanks!

1 Like

haha. It’s not a joke for apple. I’m sure if you ask’em, they would give you this as the only solution :zipper_mouth_face: :grin:
I didn’t dig into it and I’m not the best with havok physics either but I noticed you are using a time out here or there. May be you should check on the frameRate?

1 Like

For multiplayer games or simulations where consistent timing and physics are important, deterministic lockstep is often the answer.

Have you tried enabling that in the engine? That will force physics computations to occur at the same intervals no matter the frame rate (within reason, ofc)

3 Likes

@mawa Oh yes, it is true. 120fps and 60fps have a different behavior :smiley:

@jelster good point, thank you. At the moment I’m trying Advanced Animation Methods | Babylon.js Documentation
But didn’t help

Yeah, well I’m not sure about havok using this method. There’s nothing about it in the doc and I’m afraid I’m not qualified. Now just who is the havok physics guru in the forum? May be try cc @Cedric

Found!
Now works:

scene.enablePhysics(gravityVector, physicsPlugin);
scene.getPhysicsEngine().setTimeStep(1 / 20);

Example: https://playground.babylonjs.com/#E5URLZ#17

But it is funny, physicsPlugin has setTimeStep method too, but it doesn’t work and I a bit confused what it is doing

// don't work
physicsPlugin.setTimeStep(1/20)
scene.enablePhysics(gravityVector, physicsPlugin);

@mawa @jelster Thanks, you helped me to find a solution!

2 Likes

I wrong, it is was not a solution
Default timeStep 1/60 everywhere

As I see it, it is not Havok, but an Engine plugin.

Stack

in node_modules/@babylonjs/core/Engines/thinEngine.js:4777 we have a class ThinEngine

In this class QueueNewFrame return requestAnimationFrame. So on a Macbook monitor, it is 120fps, in external 60fps.

runRenderLoop() use requestAnimationFrame() for _renderLoop

Ok. Good. Let’s see on Engine.
class Engine extends ThinEngine and has _renderLoop too (redefine)

And for registering the next frame, use _queueNewFrame from class ThinEngine where return requestAnimationFrame
in the line:
this._frameHandler = this._queueNewFrame(this._boundRenderFunction, this.getHostWindow());

Instead requestAnimationFrame for physic, I found customAnimationFrameRequester

And I can say: it is working!

    this.#engine.customAnimationFrameRequester = {
      requestAnimationFrame(boundedRenderFunction: () => void) {
        setTimeout(boundedRenderFunction, 10);
      },
    };

Working example: https://playground.babylonjs.com/#E5URLZ#18

But it is strange. Nobody writes about this, I haven’t found similar examples. How do you synchronize physics for 60 and 120 fps?

Hey, I don’t know what is your multiplayer architecture but I’d guess that for the multiplayer physics you usually have authoritative server which sends the ‘truth’ to the clients and the clients are only rendering what server is sending them?

No, I use WebRTC, peer-to-peer between browsers. Without a central server. Just sharing coordinates. But it is not important. I make this project only for fun, because I want to learn about Babylon

Question more, why does physics depend on fps :smiley: And how synchronize behavior

At the moment I am experimenting with:

  1. Set requestAnimationFrame and make the limit with 60fps everywhere
  2. Changing physicsEngine.setTimeStep() depend of value from engine.getFps()

I didn’t find any solutions, so I make experiments :dizzy_face:

Ok, so the first working solution, is how to make 60fps:

    let lastTime = 0;
    let accumulatedTime = 0;
    const frameDuration = 16.666666666666668; // 60fps

    this.#engine.customAnimationFrameRequester = {
      requestAnimationFrame(boundedRenderFunction: () => void) {
        function frameRequestCallback(time: number) {
          accumulatedTime += time - lastTime;
          lastTime = time;

          if (accumulatedTime >= frameDuration) {
            accumulatedTime -= frameDuration;
            boundedRenderFunction();
            return;
          }
          requestAnimationFrame(frameRequestCallback);
        }

        requestAnimationFrame(frameRequestCallback);
      },
    };

But I don’t like this, because if for some reason fps goes down to 50, then physics again will be different

Ok, this solution is better (but you can use both)

in the loop:

scene.getPhysicsEngine()?.setTimeStep(1 / engine.getFps());

I think it should be in documentation (or maybe I’m just blind? :smiley:)

4 Likes

Glad you found your way around it. It’s always more efficient when people can solve their own topics :joy:
GJ and have a great day :sunglasses: