Thursday, September 6, 2012

Converting from Box2d to Physijs

‹prev | My Chain | next›

If I have learned one thing from my efforts at getting Box2dWeb integrated with Three.js these last few days, it is that I much prefer Physijs—especially as a library for teaching kids in Gaming JavaScript. The impetus for investigating Box2dWeb was primarily the difficulty at getting Physijs working locally (i.e. with file:// web pages). But, with the code editor introduced by Ricardo Cabello Miguel (aka Mr Doob) that is no longer a concern (I think).

So tonight, I set out to convert my still simple jumping game to use Physijs. And to try it out on the Code Editor.

The conversion starts with the init() function. With Box2d, the init() function was solely responsible for drawing Three.js shapes. A subsequent startPhysics() function call would then attach Box2D physics. With Physijs, I open init() by creating a Physijs scene, which combines physics engine and rendering engine:
  scene = new Physijs.Scene;
Then I define physics objects with Three.js features. The ground, so that my player does not fall down into infinity:
  var ground = new Physijs.PlaneMesh(
    new THREE.PlaneGeometry(1e6, 1e6),
    new THREE.MeshBasicMaterial({color: 0x7CFC00})
  );
  scene.add(ground);
My player:
  player = new Physijs.BoxMesh(
    new THREE.CubeGeometry(20, 50, 1),
    new THREE.MeshNormalMaterial({color: 0xB22222})
  );
  player.position.x = -50;
  player.position.y = 25;
  scene.add(player);
And the fruit that my player is going to try to grab:
  fruit = new Physijs.ConvexMesh(
    new THREE.CylinderGeometry(20, 20, 1, 24),
    new THREE.MeshNormalMaterial({color: 0xB22222})
  );
  fruit.rotation.x = Math.PI/2;
  fruit.position.x = 50;
  fruit.position.y = 20;
  scene.add(fruit);
(again, what kid wouldn't want to play a healthy food game?)

The rest of init() adds the camera, just as it did with Box2d:
  camera = new THREE.PerspectiveCamera(
    75, window.innerWidth / window.innerHeight, 1, 10000
  );
  camera.position.z = 100;
  camera.position.y = 50;
//  camera.lookAt(player.position);

  scene.add(camera);
In Box2d, I did not worry about shapes falling forward or backward. Box2d gets its name from being two dimensional, after all. I did have to prevent the player from rotating after impact. In the Physijs equivalent, I have to worry about both:


So I need the equivalent Physijs prevention of rotation and I need to prevent the player from falling forward or backward. This is what the __dirtyRotation and __dirtyPosition attributes are for:
function animate() {
  requestAnimationFrame(animate);

  player.position.z = 0;
  player.__dirtyPosition = true;
  player.rotation.set(0,0,0);
  player.__dirtyRotation = true;

  scene.simulate(); // run physics
  renderer.render(scene, camera);
}
Physijs recognizes these flags as a signal to retain the values explicitly set. Both __dirty flags need to be set before each scene.simulate()—otherwise Physijs will revert back to real physics.

Next up, I need to move my player. I can re-use the same keypress listener that I have on document. I only need to change the subsequent move() function. Instead of Box2d ApplyForce() and ApplyImpulse() methods, I call the Physijs equivalents of applyCentralForce() and applyCentralImpulse():
function move(x, y) {
  var method = (y>0) ? 'applyCentralForce' : 'applyCentralImpulse';

  player[method](
    new THREE.Vector3(x, y, 0)
  );
}
Last up, I need to register collisions. In Box2d, I had to listen for all collisions in the Box2d world, then filter the ones that actually mattered. In Physijs, I can simply listen for collisions on my player. If my player has hit fruit, then score!
  player.addEventListener('collision', function(object) {
    if (object === fruit) {
      console.log(object);
      score += 10;
      writeScoreBoard("Score: " + score);
    }
  });
With that, I can collide with fruit to rack up the points:


That will do for a stopping point tonight. I will explore adding this to Mr Doob's Code Editor tomorrow. Hopefully that will work, because it really is much nicer working with Physijs than Box2d when drawing with Three.js.


Day #501

3 comments:

  1. Much better way to restrict movement & rotation:

    mesh.setAngularFactor(new THREE.Vector3( 0, 0, 1 )); // only rotate on Z axis
    mesh.setLinearFactor(new THREE.Vector3( 1, 1, 0 )); // only move on X and Y axis

    The __dirtyPosition / __dirtyRotation properties should be avoided as much as possible because they can introduce some unintended side effects when the scene is synchronized.

    ReplyDelete
    Replies
    1. Note that the set*Factor functions should be called after adding the mesh to the scene.

      Delete
    2. Ah, nice. That does work: Code Editor sample. I was seeing the pieces bounce unexpectedly at times with the __dirty flag, but had not seen it enough to mention yet. Hopefully that explains it. Regardless, this feels like a better solution.

      Big thanks for the pointer :)

      Delete