Sunday, September 2, 2012

Getting Started with Box2D and Three.js

‹prev | My Chain | next›

Up tonight, I am going to get started on a new Three.js game for Gaming JavaScript. I ran into a bit of a problem with the last game that used the Physijs physics engine to simulate physics. It worked just fine—except when running from the file system.

To minimize the dependencies, I have been trying to write games for kids such that they could make a simple web page that referenced all library code from a web site. That works fine for normal JavaScript, but no so much for web worker code. Physijs smartly uses a web worker for its work. Unfortunately that complicates things for me.

So tonight I hope to get started with Box2D.js and Three.js. For all I know, it could suffer from similar "problems".

I start by grabbing the download:
wget http://box2dweb.googlecode.com/files/Box2dWeb-2.1a.3.zip
I unzip and am ready to go:
➜  scripts git:(gh-pages) ✗ sha1sum Box2dWeb-2.1a.3.zip
315bb2b1c603f6dae2cf886b2ebc9ef242579ac6  Box2dWeb-2.1a.3.zip
➜  scripts git:(gh-pages) ✗ unzip -l Box2dWeb-2.1a.3.zip
Archive:  Box2dWeb-2.1a.3.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
   429833  2011-06-07 11:55   Box2dWeb-2.1.a.3.js
   224935  2011-06-07 11:45   Box2dWeb-2.1.a.3.min.js
     6764  2011-06-15 13:04   demo.html
     3030  2011-06-15 13:04   example.html
---------                     -------
   664562                     4 files
➜  scripts git:(gh-pages) ✗ unzip Box2dWeb-2.1a.3.zip
Archive:  Box2dWeb-2.1a.3.zip
  inflating: Box2dWeb-2.1.a.3.js
  inflating: Box2dWeb-2.1.a.3.min.js
  inflating: demo.html
  inflating: example.html   
I start my Three.js Javascript with a boring (non-physics) "player" in a blank scene:
function init() {
  renderer = initRenderer(0x87CEEB);

  scene = new THREE.Scene;

  player = new THREE.Mesh(
    new THREE.CubeGeometry(20, 50, 10),
    new THREE.MeshBasicMaterial({color: 0xB22222})
  );
  scene.add(player);

  camera = new THREE.PerspectiveCamera(
    75, window.innerWidth / window.innerHeight, 1, 10000
  );
  camera.position.z = 100;
  camera.lookAt(player.position);

  scene.add(camera);
}
The initRenderer() is just something that I wrote for the rafting game to initialize a WebGL Three.js renderer (if present) or a canvas renderer otherwise, with the specified background color. The rest is straight-forward Three.js—adding a firebrick red cube, representing the game player, to the scene as well as a camera that looks at the player.

I then start the physics, copying most of this straight from the Box2dWeb example file:
function startPhysics() {
  var b2Vec2 = Box2D.Common.Math.b2Vec2
    , b2World = Box2D.Dynamics.b2World
    , b2FixtureDef = Box2D.Dynamics.b2FixtureDef
    , b2BodyDef = Box2D.Dynamics.b2BodyDef
    , b2Body = Box2D.Dynamics.b2Body
    , b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape;

  world = new b2World(
    new b2Vec2(0, 10),    //gravity
    true                  //allow sleep
  );

  var fixDef = new b2FixtureDef;
  fixDef.density = 1.0;
  fixDef.friction = 0.5;
  fixDef.restitution = 0.2;

  var bodyDef = new b2BodyDef;

  //create ground
  bodyDef.type = b2Body.b2_staticBody;
  bodyDef.position.x = 9;
  bodyDef.position.y = 13;
  fixDef.shape = new b2PolygonShape;
  fixDef.shape.SetAsBox(10, 0.5);
  world.CreateBody(bodyDef).CreateFixture(fixDef);

  // player
  bodyDef.type = b2Body.b2_dynamicBody;
  fixDef.shape = new b2PolygonShape;
  fixDef.shape.SetAsBox(
    Math.random() + 0.1 //half width
    ,  Math.random() + 0.1 //half height
  );

  bodyDef.position.x = 0;
  bodyDef.position.y = 100;
  player_fixture = world.CreateBody(bodyDef).CreateFixture(fixDef);
}
Last up, I create a gameStep() function to periodically update my player's position as it falls:
function gameStep() {
  world.Step(
    1 / 60,   //frame-rate
    10,       //velocity iterations
    10       //position iterations
  );
  world.ClearForces();

  var body = player_fixture.GetBody().GetDefinition();
  player.position.x = body.position.x;
  player.position.y = body.position.y;

  setTimeout(gameStep, 1000 / 60); // process the game logic
                                   // at a target 60 FPS.
}
And that works. Only not quite. My player drop up instead of down. This is because the Box2D animation was written for canvas and its inverted y-axis. The fix is easy enough—I only need to mark physics as working down in the startPhysics() function:
function startPhysics() {
  // ...
  world = new b2World(
    new b2Vec2(0, -10),    //gravity
    true                  //allow sleep
  );
  // ...
}
With that, I have my "player" falling under the influence of gravity:



That will do for a stopping point tonight. Up tomorrow I will add movement controls to the player and reflect those changes in the Box2dWeb world. I also need to clean up that Box2DWeb code. I can't be inflicting that on kids.

Day #497

No comments:

Post a Comment