Thursday, January 31, 2013

On Demand Physics with Physijs

‹prev | My Chain | next›

I have noticed that running the Physijs physics engine in my Three.js games is a fine way to test the fan on my MacBook Air (running linux). Now that I am quite sure that my fan is in good working order, I would prefer it if my fan were not running all of the time.

So I start by removing the call to scene.simulate from the usual Three.js animate() function:
  var pause = false;
  function animate() {
    if (pause) return;
    scene.simulate(); // run physics

    renderer.render(scene, camera);
For smooth physics, the physics engine only needs to run at 60 frames per second, not whatever rate the animation is currently running at. So I start a separate function for this game logic:
  function gameStep() {
    scene.simulate(); // run physics

    // when moving, process the game logic at a target 60 FPS
    setTimeout(gameStep, 1000/60);
Unfortunately, this has little to no effect. The animation must be running at about that rate since my fan is still being exercised. It seems that an alternate strategy will be required.

Instead, I try to stop the physics engine whenever my player is no longer in motion. Sure the fan might be taxed when I am actively moving my player, but it should quickly go back to normal when I am resting. In the move() function that is invoked in response to keyboard activity, I add a call to a new startPhysics() function:
  function move(x) {
    // ...
      new THREE.Vector3(v_x + x, v_y, 0)
The order of this winds up being important. Specifically, bad things happen when I setLinearVelocity() before the physics engine is running. That might seem intuitive, but I had expected whatever linear velocity was applied to be applied on the next call to scene.simulate()—regardless of whether or not the physics engine was already active.

Anyhow, the startPhysics() function is responsible for two things: starting the engine (naturally) and starting a timer that watches for then end of physics movement:
  function startPhysics() {
    if (game_step_timer) return;
The guard clause in this function is checking to see if the gameStep() function is already simulating physics. Since I need that timer variable in this function, it now needs to be defined and set in the gameStep() function:
  var game_step_timer;
  function gameStep() {
    scene.simulate(); // run physics

    // when moving, process the game logic at a target 60 FPS
    game_step_timer = setTimeout(gameStep, 1000/60);
That starts the physics engine and, thanks to a setTimeout() call, it continues to processes all of the necessary physics in the scene 60 times per second.

To stop the physics engine, I need only clear that timeout. This is what the waitForStop() function does:
  function waitForStop(just_started) {
    if (just_started) return setTimeout(waitForStop, 10*1000);
    if (player.getLinearVelocity().length() > 0) return setTimeout(waitForStop, 2*1000);

    game_step_timer = null;
Since it is unlikely that physics will cease to be a factor immediately after the first call, the first guard clause will wait for 10 seconds until checking to see if physics can stop. Not only is it nice to have this 10 second grace period, it turns out to be required. Event though waitForStop() is invoked after the physics engine is running, oftentimes I get a velocity of zero. This grace period also ensures that the physics engine has ample time to get things moving.

After the starting grace period, I check to see if physics is no longer needed every two seconds. Finally, once nothing is moving under the influence of physics, I clear the game_step_timer that would otherwise be updating physics 60 times a second.

And that seems to do the trick. The game is still running, but I no longer hear the fan as I am writing in a different desktop. If I switch back to the game, the player is immediately responsive to keyboard input and moves fluidly.

All of this seems too much to introduce to kids in 3D Game Programming for Kids. So, unless I can think of an easier way to do this, I will mentally file this under "good to know" and move back to useful stuff for kids tomorrow.

Day #647

No comments:

Post a Comment