Box2D Physics & WebGL


Your browser does not support the canvas tag. This is a static example of what would be seen.

I’ve been meaning to spend some time looking into javascript physics engines, and the possibility we could connect them to WebGL for rendering, for a while. After some research I came to the conclusion that Box2DWeb was the physics engine most likely to yield interesting results, and most likely to let me build something more complex easily once I’d gotten to know the basics.

Box2DWeb is available from here and I found this short tutorial very useful when getting started.

I’ll quickly describe what I’ve done here…

First we have to pull a lot of stuff into the global namespace, otherwise we end up using very long names for everything and our code becomes fairly unreadable. Placing this block of code at the top of our script simplifies things somewhat.

  var b2Vec2 = Box2D.Common.Math.b2Vec2
    , b2BodyDef = Box2D.Dynamics.b2BodyDef
    , b2Body = Box2D.Dynamics.b2Body
    , b2FixtureDef = Box2D.Dynamics.b2FixtureDef
    , b2Fixture = Box2D.Dynamics.b2Fixture
    , b2World = Box2D.Dynamics.b2World
    , b2MassData = Box2D.Collision.Shapes.b2MassData
    , b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
    , b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
    , b2DebugDraw = Box2D.Dynamics.b2DebugDraw

The physics objects exist in a world, so the first thing any Box2D app needs to do is create the world. Making a world is fairly simple as it happens.

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

The first parameter when building a world is gravity which we set to 10 to approximate real world gravity.

Once we have a world we can create and add rigid bodies to it. In this simple example we have a fixed (static) box at the bottom of the scene and a dynamic ball at the top. The code for these two doesn’t look all that different as it happens. Each sets up a definition of a fixture and body and then issues calls to create them both. The box uses a different definition of the shape (fixture in Box2D terminology) and is flagged as a b2_staticBody rather than b2_dynamicBody meaning it cannot move.

For each rigid body we create we also get to add some user data, which can be anything we like. I’m going to use that to bind the objects to our WebGL rendering so we know how to draw the various bodies when the time comes. In this case I record the scale and a reference to the mesh we want to render, so we take a unit circle (radius = 1.0) and apply a scale of 0.1, matching the radius of the circle we ask Box2D to create.

This is code we use to create the ball in this example…

  function createBall() {
    var fixDef = new b2FixtureDef;
    var bodyDef = new b2BodyDef;

    fixDef.density = 1.0;
    fixDef.friction = 0.5;
    fixDef.restitution = 0.2;
         
    // *** create dynamic circle object ***
    bodyDef.type = b2Body.b2_dynamicBody;
    // user data
    bodyDef.userData = { };
    bodyDef.userData.scaleX = 0.1;
    bodyDef.userData.scaleY = 0.1;
    bodyDef.userData.mesh = circleMesh;
    // position.
    bodyDef.position.x = 0.0;
    bodyDef.position.y = -1.0;
    fixDef.shape = new b2CircleShape(bodyDef.userData.scaleX);
    // make-it!
    sphereBody = world.CreateBody(bodyDef);
    sphere = sphereBody.CreateFixture(fixDef);
  }

Bear in mind that we need to think about coordindate systems here. We want the coordinate systems of the rendering and physics to match. Our rendering is going to use untransformed (clip space) vertex positions, so we just use the same in Box2D, meaning the top and left edges of the canvas are -1.0 and the right and bottom are 1.0.

Now we need to ‘tick’ the world to trigger physics processing. This block of code does that for us, where my sample ensures a fixed update of 60hz so we can just hard code that.

  var frameRate = 1.0 / 60.0;
  world.Step(frameRate, 10, 10);
  world.ClearForces();

Finally we want to render the contents of the world. Box2D provides a mechanism for iterataing over the bodies in the world, getting the position and rotation of each, and querying for any user data. This already gives us all we need. If we have user data we can go ahead and draw the object. The position and rotation, when combined with the scale we stored in our user data give us all the information we need to build a matrix to represent to transformations the shader needs to apply in order to locate the vertices in the correct positions. Once we have that we can bind our matrix to a shader, grab the mesh from the user-data and issue a draw call.

  var obj = world.GetBodyList();
  while (obj) {
    var pos = obj.GetPosition();
    var angle = obj.GetAngle();
    var userData = obj.GetUserData();
    if (userData != null) {
      mat4.identity(modelMtx);
      mat4.translate(modelMtx, [pos.x, pos.y, 0.0], modelMtx);
      mat4.rotateZ(modelMtx, angle, modelMtx);
      mat4.scale(modelMtx, [userData.scaleX, userData.scaleY, 1.0], modelMtx);
      // set shader constant here...
      var mesh = userData.mesh;
      // draw mesh here...
    }
    obj = obj.GetNext();
  }

Leave a Reply

Your email address will not be published. Required fields are marked *