Physics setTimeStep. Why does it slow things down?

I’ve written a little app that uses Babylon and the cannon physics engine to simulate a rocket launch. I have real world engine thrust data as an array of force values over time spaced 1ms apart.
In my app, I have a “beforePhysics” Observable which runs before every physics tick. I’ve set the physics engine to use a timestep of 1 ms so that at each physics tick, I increment my force array index by one and apply that force. Unfortunately, having a timestep of 1ms makes the whole simulation super slow. Not only is it slow but the rocket flies way higher than it realistically should. I’m confused because usually when numerically integrating and doing this sort of problem in excel, you use a certain timestep for each delta t when integrating. The smaller the timestep, the more accurate your solution. In this case, the solution looks to be worse and also plays back as if in slow motion.

Is there a way to fix this?

pinging @Cedric

It looks like setting timeStep doesn’t actually change how quickly the physics step happens? I use an onBeforePhysics observable and no matter what I change the timestep to it gets triggered about every 10-20 ms.

Hi Adam

To achieve what you want, something is missing in the engine. We need to add substeps: Now, for each rendering, we call the engine tick function once with a different time scale factor. This is not enough. For each frame, we should be able to run the tick function multiple times (and notify the observer properly).
For example, if we want a 1KHz at 60Hz, we would set the substep to 1000/60 = 16.6666
That means, we have to accumulate time.
16 sub steps for 1 rendering frame
16 sub steps for the second frame
17 sub steps for the 3rd
16 for the 4th
and so on

I’ll do the change monday. But there is one more issue.
The physics engine is a game physics engine. It works with 32bits float and the computation is done prilimarly for games and effects. It always favor fast and approximate results over slower and more realistic one.

If you mix big forces with mass bodies (or the inverse), you will end up having bad results. For ‘exact’ results, I suggest you to compute the physics in a separate function.

Anyway, I’ll release the substeps and you’ll be able to test it very soon :slight_smile:

2 Likes

Thanks! I’ve written my own rocket physics numerical integration function and I can use that to test it out.

I guess the problem with doing something 16 times before the frame gets rendered is that if you’ve got something running at 60fps so 16.66 ms per frame, you’ve got to do your physics sim calc every 1ms. I’m not sure if that’s too fast to do?

I think the idea there is to divide physics calcs into sub-ms time-to-completion parcels.

Hi !

The PR is here:

And the documentation here:

Please, let me know if something is not clear in the documentation. The concept is pretty simple but sometimes, the difficulty is in the explanation :slight_smile:

2 Likes

Merged!

1 Like

This is great! Thanks Cedric!

I’ve noticed if I use the Cannon plugin vs Ammo, the sim behaves way differently. In your playground example, using Cannon makes the box super sensitive to gravity changes. Is there something that needs to be changed for Cannon to make it work well?

I added your change to my rocket code and it works alright although now I’m not sure if its correct vs my manual calculations done.

In my code that implements the babylon physics engine I do this in the beforePhysics observable:

scene.onBeforePhysicsObservable.add(() => {
    const timeDiff = Date.now() - startTime;
    //camera.setPosition(cone.getAbsolutePosition());
    count += 1;
    const i = count;
    const rocketVelocity = cone.physicsImposter.getLinearVelocity().y;
    const rocketPosition = cone.getAbsolutePosition();
    const forceDirection = new BABYLON.Vector3(0, 1, 0);
    const forceMagnitude = slicedRocketData[i] < 1 ? 0 : slicedRocketData[i];
    const contactLocalRefPoint = BABYLON.Vector3.Zero();

    let dragDirection = new BABYLON.Vector3(0, -1, 0);
    if (rocketVelocity < 0) {
      dragDirection = new BABYLON.Vector3(0, 1, 0);
    }
    const dragMagnitude = CD * rho * (1 / 2) * rocketVelocity ** 2;
    particleSystem.emitRate = 300 * forceMagnitude;

    cone.physicsImposter.applyForce(forceDirection.scale(forceMagnitude),
      rocketPosition.add(contactLocalRefPoint));
    cone.physicsImposter.applyForce(dragDirection.scale(dragMagnitude),
      rocketPosition.add(contactLocalRefPoint));

    if (rocketPosition.y > maxPos) {
      maxPos = rocketPosition.y;
      maxHeightElement.innerText = `Max Height = ${maxPos} m`;
    }

In my manually done calculations, I do this. I had an array of rocket masses as the rocket mass changes over the burn time but for this test I kept each value constantly the same mass as the babylon physics code has. A bunch of the code below is there to check if the rocket is above ground so it doesn’t start going down as gravity is negative before the burn happens. In this case, timeStep is set to 1/1000 just like in the babylon example where it’s 1ms. Thrust sample Rate is 1/1000

for (let i = 0; i < maxTimeSeconds / timeStep; i += 1){

    //console.log(`Height: ${height[i]}  Velocity: ${velocity[i]}  Thrust: ${smoothRocketData[i]}`)

  
    if (height[i] <= 0) {
      height[i] = 0;
      gravityForce = 0;
      dragForce[i] = 0;
      velocity[i] = 0;
      accel[i] = 0;
    }
    dragForce[i] = Math.sign(velocity[i]) * CD * rho * velocity[i] ** 2 / 2;
    let gravityForce = rocketMasses[i] * g;
    forceSum[i] = smoothRocketData[Math.round(i * 1 / thrustSampleRate * timeStep)]
     - gravityForce  - dragForce[i];
    accel[i] = forceSum[i] / rocketMasses[i];
    velocity[i+1] = velocity[i] + accel[i] * timeStep ;
    height[i+1] = height[i] + velocity[i] * timeStep + (1/2) * accel[i] * timeStep ** 2;


    // Check if rocket hit the ground.
    if (height[i] === 0 && liftoffFlag === true){
      endStep = i + tailSteps;
      break
    }
    // Check if rocket has lift off. Next time height = 0 . it will be used to say rocket has hit ground.
    if (height[i] > 0.1){
      liftoffFlag = true;
    }
  }

The manual solution gives a max height that is 20 meters higher than the physics plugin way.

Here are demos of both:

Babylon Physics Method:
http://adam.teaches.engineering/Kerbciniak-Space-Program

Manual method:
http://adam.teaches.engineering/rocketProgram

Cannonjs engine might not be as efficient as Ammojs for small time step. I’d use Ammojs for better results. I believe the resolution code is better.

Can you try to set the velocity instead of applying forces in onBeforePhysicsObservable?

I ran the Babylon example by setting the linear velocity directly using the velocity my manual calculations used and that works great. Max height is the same for both. Now I wonder why the forces don’t work?

EDIT: Alright I figured it out and it was all an error on my part. In my Babylon example, I was assuming every force less than 1 was 0 because there is a slight bias force that’s always there in the data. I changed that to include forces above 0.1 and it gives me the correct height now!

2 Likes