Search code examples
javascriptgame-physics

Most performant way to call update loop of a javaScript physics engine


I've written my own HTML5 canvas - javaScript based physics engine to simulate a number of points connected by springs. The current general structure of the program is

function init(){
// A bunch of event listeners
renderer();
physics();
}


var frameTime = 1;

function physics(){
  // iterate the physics
  parts.update();
  setTimeout(physics, frameTime);
}

// render loop
function renderer(){
  // draws a rectangle over the last frame
  drawBackground();
  // renders the objects themselves
  parts.draw();
  // update the timeout according to an onscreen slider
  frameTime = Math.ceil(101 - speed_r.value) / 2;
  setTimeout(renderer, 15);
}

The rationale behind the 2 different loops is that the human eye only needs to see 60fps, but doing more updates per second yields better physics.

I've since done more research, and found that the standard way to render animations with javaScript is to call requestAnimationFrame(), which as I understand it has the advantage of not rendering while the tab is deselected, improving battery life. However, due to the dual loop structure, the physics will continue to be calculated and will probably outweigh the renderer overhead.

The question is: What is the most performant and ideally most efficient way to achieve this?


Solution

  • To sync your physics simulation with the wall clock, and render the animation smoothly, you need to use a fixed time step and interpolation. Read this excellent article (see also: archive.org) about both subjects.

    Using requestAnimationFrame is a good idea to save battery (it will lower the frame rate if the battery is low on most devices). You can use it for both the physics and rendering loop. What you have to do is compute the time elapsed since the last frame and then use zero or many fixed steps to keep the physics loop in sync with the current (wall-clock) time. This is how all real-time physics engines work, including Box2D and Bullet Physics.

    I made a complete JSFiddle using HTML5 Canvas and JavaScript that implements what you need, based on the article mentioned above. See the code below or open it on JSFiddle.

    The integrate function is where you update your physics. In the code it is used to step a spring simulation forward.

    var t = 0;
    var dt = 0.01;
    
    var currentTime;
    var accumulator = 0;
    
    var previousState = { x: 100, v: 0 };
    var currentState = { x: 100, v: 0 };
    
    var canvas = document.getElementById("myCanvas");
    var ctx = canvas.getContext("2d");
    
    // start animation loop
    requestAnimationFrame(animate);
    
    function animate(newTime){
      requestAnimationFrame(animate);
    
      if (currentTime) {
        var frameTime = newTime - currentTime;
        if ( frameTime > 250 )
            frameTime = 250;
        accumulator += frameTime;
    
        while ( accumulator >= dt )
        {
            previousState = currentState;
            currentState = integrate( currentState, t, dt );
            t += dt;
            accumulator -= dt;
        }
    
        var alpha = accumulator / dt;
        var interpolatedPosition = currentState.x * alpha + previousState.x * (1 - alpha);
    
        render( interpolatedPosition );
      }
    
      currentTime = newTime;
    }
    
    // Move simulation forward
    function integrate(state, time, fixedDeltaTime){
      var fixedDeltaTimeSeconds = fixedDeltaTime / 1000;
      var f = (200 - state.x) * 3;
      var v = state.v + f * fixedDeltaTimeSeconds;
      var x = state.x + v * fixedDeltaTimeSeconds;
      return { x: x, v: v };
    }
    
    // Render the scene
    function render(position){
      // Clear
      ctx.fillStyle = 'white';
      ctx.fillRect(0,0,canvas.width,canvas.height);
    
      // Draw circle
      ctx.fillStyle = 'black';
      ctx.beginPath();
      ctx.arc(position,100,50,0,2*Math.PI);
      ctx.closePath();
      ctx.fill();
    }