Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

requestAnimFrame can't give a constant frame rate, but my physics engine needs it

I use Box2D with WebGL. Box2D demands a constant frame rate (time steps for it's "world" updates).

function update(time) {//update of box2d world
     world.Step(
           1/60   // 1 / frame-rate
        ,  3      //velocity iterations
        ,  8       //position iterations
     );

But I've read that requestAnimFrame defined as below is the right way to go.

     requestAnimFrame = (function() {
     return window.requestAnimationFrame ||
     window.webkitRequestAnimationFrame ||
     window.mozRequestAnimationFrame ||
     window.oRequestAnimationFrame ||
     window.msRequestAnimationFrame ||
     function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
       window.setTimeout(callback, 1000/60);
     };
})();

requestAnimFrame doesn't give me a constant frame rate and so my Box2D's variables are going unsynchronized.

Is there a fix to this?

[EDIT]

John's (Cutch) solution when implemented looks like this:

function interpolate(dt) {
    var t = dt/time_step;
    body_coordinates = (1-t) * body_coordinates + t * next_body_coordinates;
}

var physicsDt = 0;
function tick() {
    var time_now = new Date().getTime();
    var dt = time_now - last_time; //Note that last_time is initialized priorly
    last_time = time_now;
    physicsDt += dt;
    clear_the_screen();
    requestAnimFrame(tick);
    drawEverything();
    if(physicsDt >= time_step) {
        update();
        physicsDt -= time_step;
    }
    interpolate(dt);
}

Note that my physics update function takes care that the next_attribues are set. And also, a physics update is called before this, to keep the physics world ahead by 1 frame.

The result

The animation is fairly smooth, except for those times when I can see some really bad jumps and random appearing micro-movements.

I thought the following issues were not addressed in the solution :

----> 1) dt may become larger than time_step : This would make dt/time_step greater than 1 which would ruin the interpolation equations.

When dt remains larger than time_step consistently, problems would increase. Is it possible to overcome the problem of the time gap becoming larger than time_step ?

I mean, even if we keep the world one frame ahead of the rendering, if time gaps consistently stay greater than time_step, it wouldn't take long pass that "ahead" frame.

----> 2) Imagine dt being lesser than time_step by 1 ms. Then, the world is not updated that one time. Now interpolation is done and the approximate position is found(1 ms behind where it should have been).

Lets say the next time no difference is seen between dt and time_step.

Now, no interpolation is done considering that dt and time_step are equal. So, the next that is drawn is the "ahead" frame in the world, right?(using those equations, with t = 1)

But accurately, the rendered world should be that 1ms behind which it was before. I mean, that 1ms by which it was behind the world frame should not vanish. But with t = 1, draws the physics world frame and forgets that 1ms.

Am I wrong about the code or the above 2 points?

I request you to clarify these issues.

[EDIT]

I asked the author of this webpage, for a way to efficiently draw many shapes, in the comments there.

I learnt to do it this way : I'm saving bufferData calls by keeping separate buffers for each shape and calling createBuffer, bindBuffer, bufferData only once during init.

Everytime I refresh the screen, I have to iterate over all the shapes and I have to call enableVertexAttribArray and vertexAttribPointer after binding the required shape's buffer(using bindBuffer).

My shapes don't change with time. There are just a variety of them (like polygons, circles, triangles) that stay from beginning to end.

like image 603
batman Avatar asked Oct 23 '12 18:10

batman


2 Answers

You must decouple your physics simulation stepping timing from your render vsync timing. The easiest solution is to do this:

frameCallback(dt) {
  physicsDt += dt;
  if (physicsDt > 16) {
    stepPhysics();
    physicsDt -= 16;
  }
  renderWorld();
  requestAnimFrame(frameCallback);
}

The biggest issue here is that sometimes you'll be rendering with an outdated physics world, for example, if physicsDt was 15 no simulation update will occur but your objects would have moved almost an entire frame by that point in time. You can work around this by keeping the physics 1 frame ahead of the rendering and linearly interpolating object positions in the renderer. Something like:

var t = dt/16.0;
framePosition = (1-t) * previousFramePositions + (t) * nextFramePositions;

That way your objects move smoothly even if you're rendering is out of sync with your physics simulation. Let me know if you have any questions.

John

like image 156
Cutch Avatar answered Sep 28 '22 16:09

Cutch


requestAnimFrame isn't meant to guarantee a constant frame rate – it's designed so that the browser only does the calculations for frames it actually draws.

If you want/have to calculate frames that aren't drawn, then it isn't the way to go.

like image 38
Rich Bradshaw Avatar answered Sep 28 '22 15:09

Rich Bradshaw