最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin3浏览0评论

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?

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?

Share Improve this question asked Aug 19, 2016 at 15:32 catalogue_numbercatalogue_number 3352 silver badges14 bronze badges 3
  • 2 No way for you to know until you actually benchmark it. This question is missing benchmarks and the hotpath. – George Stocker Commented Aug 19, 2016 at 15:38
  • So, have you actually tried rewriting your renderer loop to use requestAnimationFrame, and did it work as well / better than your current code? (Also, if you're trying to keep your physics timestep fixed, why not use setInterval instead of setTimeout? And if you're worried about wasting battery life, consider listening to visibilitychange events and pausing your game entirely when the tab is hidden.) – Ilmari Karonen Commented Aug 19, 2016 at 15:39
  • As you may have noticed, requestAnimationFrame has one argument, which is a DOMHighResTimeStamp. Use it to calculate the time elapsed between the last rendered frame and the current one. I used that argument in an old project to calculate the position of the box relatively to the time elapsed, instaed of using the timeout set by you (setTimeout isn't good in animations). As far as I know, it runs very smoothly in this way – fedetibaldo Commented Aug 19, 2016 at 15:43
Add a ment  | 

2 Answers 2

Reset to default 6

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) 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 pute 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 plete 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();
}

I think I'd look at putting the physics part in a web worker, and having it post updates to the main UI thread, which renders them on a requestAnimationFrame callback. That allows the physics code to run constantly (you don't even need setTimeout looping; although having it yield periodically so it can access messages from the front end — not least "stop"! — would be a good idea), while only updating the display as often as actually needed.

2018 update: As of ES2018, the worker and the main thread could share memory via a SharedArrayBuffer and the features of the Atomics object. Rather than having to break the work up so the worker could process messages, it could just check a location in the shared memory for flags (for instance, the flag saying it needs to stop). The worker could even be suspended right in the middle of the calculation (even in the middle of a standard loop, such as a for or while) and then resumed via Atomics.wait and Atomics.notify.

发布评论

评论列表(0)

  1. 暂无评论