- END #header -->

Box2D JS – Physics in HTML5 & Javascript Guide

Box2D JS Hello World

I hate to do it, but I highly recommend the Google Chrome browser for this demo. In other browsers it may take a while to load and runs slower.

→ Click on the demo above or here, then right click to view source
→ Download the Box2D JS library
→ Download the concatenated version (~350KB)
→ Download the minified version (~170 KB)

The Overview

Some of you might remember the Box2DFlashAS3 demo I did a while ago. Well here’s its HTML5 counter-part, with the help of Box2D JS. Box2D is the Javascript port of the Box2DFlashAS3 library, which in turn is a port of the Box2D C++ library. Put simply, this library allows you to apply 2 dimensional physics to objects on your HTML5 canvas element. Just click anywhere on the demo above to see what I mean.

The code behind this is a condensed, easier-to-follow version of the demo code available by viewing the page source at the Box2D JS site. I personally found it a little tough to follow initially and really wanted to avoid the large amount of individual includes necessary to get it working. To that end I created the concatenated and minified versions downloadable above. So basically the includes necessary go from this:

 
<script src="lib/prototype-1.6.0.2.js">script> 
 
 
<script src='js/box2d/common/b2Settings.js'>script> 
<script src='js/box2d/common/math/b2Vec2.js'>script> 
<script src='js/box2d/common/math/b2Mat22.js'>script> 
<script src='js/box2d/common/math/b2Math.js'>script> 
<script src='js/box2d/collision/b2AABB.js'>script> 
<script src='js/box2d/collision/b2Bound.js'>script> 
<script src='js/box2d/collision/b2BoundValues.js'>script> 
<script src='js/box2d/collision/b2Pair.js'>script> 
<script src='js/box2d/collision/b2PairCallback.js'>script> 
<script src='js/box2d/collision/b2BufferedPair.js'>script> 
<script src='js/box2d/collision/b2PairManager.js'>script> 
<script src='js/box2d/collision/b2BroadPhase.js'>script> 
<script src='js/box2d/collision/b2Collision.js'>script> 
<script src='js/box2d/collision/Features.js'>script> 
<script src='js/box2d/collision/b2ContactID.js'>script> 
<script src='js/box2d/collision/b2ContactPoint.js'>script> 
<script src='js/box2d/collision/b2Distance.js'>script> 
<script src='js/box2d/collision/b2Manifold.js'>script> 
<script src='js/box2d/collision/b2OBB.js'>script> 
<script src='js/box2d/collision/b2Proxy.js'>script> 
<script src='js/box2d/collision/ClipVertex.js'>script> 
<script src='js/box2d/collision/shapes/b2Shape.js'>script> 
<script src='js/box2d/collision/shapes/b2ShapeDef.js'>script> 
<script src='js/box2d/collision/shapes/b2BoxDef.js'>script> 
<script src='js/box2d/collision/shapes/b2CircleDef.js'>script> 
<script src='js/box2d/collision/shapes/b2CircleShape.js'>script> 
<script src='js/box2d/collision/shapes/b2MassData.js'>script> 
<script src='js/box2d/collision/shapes/b2PolyDef.js'>script> 
<script src='js/box2d/collision/shapes/b2PolyShape.js'>script> 
<script src='js/box2d/dynamics/b2Body.js'>script> 
<script src='js/box2d/dynamics/b2BodyDef.js'>script> 
<script src='js/box2d/dynamics/b2CollisionFilter.js'>script> 
<script src='js/box2d/dynamics/b2Island.js'>script> 
<script src='js/box2d/dynamics/b2TimeStep.js'>script> 
<script src='js/box2d/dynamics/contacts/b2ContactNode.js'>script> 
<script src='js/box2d/dynamics/contacts/b2Contact.js'>script> 
<script src='js/box2d/dynamics/contacts/b2ContactConstraint.js'>script> 
<script src='js/box2d/dynamics/contacts/b2ContactConstraintPoint.js'>script> 
<script src='js/box2d/dynamics/contacts/b2ContactRegister.js'>script> 
<script src='js/box2d/dynamics/contacts/b2ContactSolver.js'>script> 
<script src='js/box2d/dynamics/contacts/b2CircleContact.js'>script> 
<script src='js/box2d/dynamics/contacts/b2Conservative.js'>script> 
<script src='js/box2d/dynamics/contacts/b2NullContact.js'>script> 
<script src='js/box2d/dynamics/contacts/b2PolyAndCircleContact.js'>script> 
<script src='js/box2d/dynamics/contacts/b2PolyContact.js'>script> 
<script src='js/box2d/dynamics/b2ContactManager.js'>script> 
<script src='js/box2d/dynamics/b2World.js'>script> 
<script src='js/box2d/dynamics/b2WorldListener.js'>script> 
<script src='js/box2d/dynamics/joints/b2JointNode.js'>script> 
<script src='js/box2d/dynamics/joints/b2Joint.js'>script> 
<script src='js/box2d/dynamics/joints/b2JointDef.js'>script> 
<script src='js/box2d/dynamics/joints/b2DistanceJoint.js'>script> 
<script src='js/box2d/dynamics/joints/b2DistanceJointDef.js'>script> 
<script src='js/box2d/dynamics/joints/b2Jacobian.js'>script> 
<script src='js/box2d/dynamics/joints/b2GearJoint.js'>script> 
<script src='js/box2d/dynamics/joints/b2GearJointDef.js'>script> 
<script src='js/box2d/dynamics/joints/b2MouseJoint.js'>script> 
<script src='js/box2d/dynamics/joints/b2MouseJointDef.js'>script> 
<script src='js/box2d/dynamics/joints/b2PrismaticJoint.js'>script> 
<script src='js/box2d/dynamics/joints/b2PrismaticJointDef.js'>script> 
<script src='js/box2d/dynamics/joints/b2PulleyJoint.js'>script> 
<script src='js/box2d/dynamics/joints/b2PulleyJointDef.js'>script> 
<script src='js/box2d/dynamics/joints/b2RevoluteJoint.js'>script> 
<script src='js/box2d/dynamics/joints/b2RevoluteJointDef.js'>script>

to this:

 
<script src="lib/prototype-1.6.0.2.js">script> 
<script src="box2djs.min.js">script>

You still need to download Box2D JS for its dependencies on Prototype and excanvas, but using my single file versions of the library will make getting started much cleaner and easier. OK, enough of the back story, on to the code.

The Code


    
        
        SavageLook.com - Box2D JS Hello World
        
        
        
 
        <script type="text/javascript">
        var world;
        var ctx;
        var canvasWidth;
        var canvasHeight;
        var canvasTop;
        var canvasLeft;
 
        function drawWorld(world, context) {
                for (var j = world.m_jointList; j; j = j.m_next) {
                        drawJoint(j, context);
                }
                for (var b = world.m_bodyList; b; b = b.m_next) {
                        for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {
                                drawShape(s, context);
                        }
                }
 
                ctx.font = 'bold 18px arial';
                ctx.textAlign = 'center';
                ctx.fillStyle = '#000000';
                ctx.fillText("Click the screen to add more objects", 400, 20);
                ctx.font = 'bold 14px arial';
                ctx.fillText("Performance will vary by browser", 400, 40);
 
        }
 
        function drawJoint(joint, context) {
                var b1 = joint.m_body1;
                var b2 = joint.m_body2;
                var x1 = b1.m_position;
                var x2 = b2.m_position;
                var p1 = joint.GetAnchor1();
                var p2 = joint.GetAnchor2();
                context.strokeStyle = '#00eeee';
                context.beginPath();
                switch (joint.m_type) {
                case b2Joint.e_distanceJoint:
                        context.moveTo(p1.x, p1.y);
                        context.lineTo(p2.x, p2.y);
                        break;
 
                case b2Joint.e_pulleyJoint:
                        // TODO
                        break;
 
                default:
                        if (b1 == world.m_groundBody) {
                                context.moveTo(p1.x, p1.y);
                                context.lineTo(x2.x, x2.y);
                        }
                        else if (b2 == world.m_groundBody) {
                                context.moveTo(p1.x, p1.y);
                                context.lineTo(x1.x, x1.y);
                        }
                        else {
                                context.moveTo(x1.x, x1.y);
                                context.lineTo(p1.x, p1.y);
                                context.lineTo(x2.x, x2.y);
                                context.lineTo(p2.x, p2.y);
                        }
                        break;
                }
                context.stroke();
        }
 
        function drawShape(shape, context) {
                context.strokeStyle = '#ffffff';
                if (shape.density == 1.0) {
                        context.fillStyle = "red";
                } else {
                        context.fillStyle = "black";
                }
                context.beginPath();
                switch (shape.m_type) {
                case b2Shape.e_circleShape:
                        {
                                var circle = shape;
                                var pos = circle.m_position;
                                var r = circle.m_radius;
                                var segments = 16.0;
                                var theta = 0.0;
                                var dtheta = 2.0 * Math.PI / segments;
 
                                // draw circle
                                context.moveTo(pos.x + r, pos.y);
                                for (var i = 0; i < segments; i++) {
                                        var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));
                                        var v = b2Math.AddVV(pos, d);
                                        context.lineTo(v.x, v.y);
                                        theta += dtheta;
                                }
                                context.lineTo(pos.x + r, pos.y);
 
                                // draw radius
                                context.moveTo(pos.x, pos.y);
                                var ax = circle.m_R.col1;
                                var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);
                                context.lineTo(pos2.x, pos2.y);
                        }
                        break;
                case b2Shape.e_polyShape:
                        {
                                var poly = shape;
                                var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));
                                context.moveTo(tV.x, tV.y);
                                for (var i = 0; i < poly.m_vertexCount; i++) {
                                        var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));
                                        context.lineTo(v.x, v.y);
                                }
                                context.lineTo(tV.x, tV.y);
                        }
                        break;
                }
                context.fill();
                context.stroke();
        }
 
        function createWorld() {
                var worldAABB = new b2AABB();
                worldAABB.minVertex.Set(-1000, -1000);
                worldAABB.maxVertex.Set(1000, 1000);
                var gravity = new b2Vec2(0, 300);
                var doSleep = true;
                world = new b2World(worldAABB, gravity, doSleep);
                createGround(world);
                return world;
        }             
 
        function createGround(world) {
                var groundSd = new b2BoxDef();
                groundSd.extents.Set(400, 30);
                groundSd.restitution = 0.0;
                var groundBd = new b2BodyDef();
                groundBd.AddShape(groundSd);
                groundBd.position.Set(400, 470);
                return world.CreateBody(groundBd);
        }
 
        function createBall(world, x, y) {
                var ballSd = new b2CircleDef();
                ballSd.density = 1.0;
                ballSd.radius = 20;
                ballSd.restitution = 0.5;
                ballSd.friction = 0.5;
                var ballBd = new b2BodyDef();
                ballBd.AddShape(ballSd);
                ballBd.position.Set(x,y);
                return world.CreateBody(ballBd);
        }
 
        function createHelloWorld() {
                // H
                createBox(world, 50, 420, 10, 20, false);
                createBox(world, 90, 420, 10, 20, false);
                createBox(world, 70, 395, 30, 5, false);
                createBox(world, 50, 370, 10, 20, false);
                createBox(world, 90, 370, 10, 20, false);
 
                // E
                createBox(world, 140, 435, 30, 5, false);
                createBox(world, 120, 420, 10, 10, false);
                createBox(world, 130, 405, 20, 5, false);
                createBox(world, 120, 390, 10, 10, false);
                createBox(world, 140, 375, 30, 5, true);
 
                // L
                createBox(world, 200, 435, 20, 5, false);
                createBox(world, 185, 400, 5, 30, false);
 
                // L
                createBox(world, 250, 435, 20, 5, false);
                createBox(world, 235, 400, 5, 30, false);
 
                // O
                createBox(world, 300, 435, 20, 5, false);
                createBox(world, 285, 405, 5, 25, false);
                createBox(world, 315, 405, 5, 25, false);
                createBox(world, 300, 375, 20, 5, false);
 
                // W
                createBox(world, 390, 435, 40, 5, false);
                createBox(world, 360, 390, 10, 40, false);
                createBox(world, 420, 390, 10, 40, false);
                createBox(world, 390, 415, 5, 15, false);
 
                // O
                createBox(world, 460, 435, 20, 5, false);
                createBox(world, 445, 405, 5, 25, false);
                createBox(world, 475, 405, 5, 25, false);
                createBox(world, 460, 375, 20, 5, false);
 
                // R
                createBox(world, 495, 410, 5, 30, false);
                createBox(world, 518, 425, 5, 15, false);
                createBox(world, 515, 405, 15, 5, false);
                createBox(world, 525, 390, 5, 10, false);
                createBox(world, 510, 375, 20, 5, false);
 
                // L
                createBox(world, 560, 435, 20, 5, false);
                createBox(world, 545, 400, 5, 30, false);
 
                // D
                createBox(world, 610, 435, 20, 5, false);
                createBox(world, 595, 405, 5, 25, false);
                createBox(world, 625, 405, 5, 25, false);
                createBox(world, 610, 375, 20, 5, false);
 
                // !
                createBox(world, 650, 430, 10, 10, false);
                createBox(world, 650, 380, 10, 40, false);
        }
 
        function createBox(world, x, y, width, height, fixed) {
                if (typeof(fixed) == 'undefined') fixed = true;
                var boxSd = new b2BoxDef();
                if (!fixed) boxSd.density = 1.0; 
                boxSd.restitution = 0.0;
                boxSd.friction = 1.0;
                boxSd.extents.Set(width, height);
                var boxBd = new b2BodyDef();
                boxBd.AddShape(boxSd);
                boxBd.position.Set(x,y);
                return world.CreateBody(boxBd);
        }
 
        function step(cnt) {
                var stepping = false;
                var timeStep = 1.0/60;
                var iteration = 1;
                world.Step(timeStep, iteration);
                ctx.clearRect(0, 0, canvasWidth, canvasHeight);
                drawWorld(world, ctx);
                setTimeout('step(' + (cnt || 0) + ')', 10);
        }
 
        // main entry point
        Event.observe(window, 'load', function() {
                world = createWorld();
                ctx = $('canvas').getContext('2d');
                var canvasElm = $('canvas');
                canvasWidth = parseInt(canvasElm.width);
                canvasHeight = parseInt(canvasElm.height);
                canvasTop = parseInt(canvasElm.style.top);
                canvasLeft = parseInt(canvasElm.style.left);
 
                createHelloWorld();
 
                Event.observe('canvas', 'click', function(e) {
                                if (Math.random() > 0.5) {
                                        //createBox(world, Event.pointerX(e), Event.pointerY(e), 10, 10, false);
                                        createBox(world, e.clientX, e.clientY, 10, 10, false);
                                } else {
                                        createBall(world, Event.pointerX(e), Event.pointerY(e));
                                }
                });
                step();
        });
        script>
    
    
        
    
 

The Breakdown

DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>SavageLook.com - Box2D JS Hello Worldtitle>
        
        <script src="lib/prototype-1.6.0.2.js">script>
        <script src="box2djs.min.js">script>

We start by including the scripts necessary to make Box2D JS work. In order, we need excanvas (included in the Box2D JS distribution) in order to account for the fact that all current released version of Internet Explorer do not support the HTML canvas element. Next we include the Prototype Javascript framework, also included with Box2D JS. Finally we include my minified version of the library. Now we can get started building physics into our canvas element.


<script type="text/javascript">
        var world;
        var ctx;
        var canvasWidth;
        var canvasHeight;
        var canvasTop;
        var canvasLeft;
 
        function drawWorld(world, context) {
                for (var j = world.m_jointList; j; j = j.m_next) {
                        drawJoint(j, context);
                }
                for (var b = world.m_bodyList; b; b = b.m_next) {
                        for (var s = b.GetShapeList(); s != null; s = s.GetNext()) {
                                drawShape(s, context);
                        }
                }
 
                ctx.font = 'bold 18px arial';
                ctx.textAlign = 'center';
                ctx.fillStyle = '#000000';
                ctx.fillText("Click the screen to add more objects", 400, 20);
                ctx.font = 'bold 14px arial';
                ctx.fillText("Performance will vary by browser", 400, 40);
 
        }

Here we declare our global variables that define the “world” the physics exist in and the context, dimensions, and position of the canvas element.

Also we have our drawWorld() function that will, as the name implies, draw the shapes and joints that compose the Box2D JS world. Each of these objects is iterated through and drawn individually. They are added to the world with the createBody() function. An important thing to note is that in the case of this demo, drawWorld() will be called with each “step”. Think of your canvas as an animation and each call to drawWorld() as a frame. Should be a simple concept for you Flash devs out there ;)


        function drawJoint(joint, context) {
                var b1 = joint.m_body1;
                var b2 = joint.m_body2;
                var x1 = b1.m_position;
                var x2 = b2.m_position;
                var p1 = joint.GetAnchor1();
                var p2 = joint.GetAnchor2();
                context.strokeStyle = '#00eeee';
                context.beginPath();
                switch (joint.m_type) {
                case b2Joint.e_distanceJoint:
                        context.moveTo(p1.x, p1.y);
                        context.lineTo(p2.x, p2.y);
                        break;
 
                case b2Joint.e_pulleyJoint:
                        // TODO
                        break;
 
                default:
                        if (b1 == world.m_groundBody) {
                                context.moveTo(p1.x, p1.y);
                                context.lineTo(x2.x, x2.y);
                        }
                        else if (b2 == world.m_groundBody) {
                                context.moveTo(p1.x, p1.y);
                                context.lineTo(x1.x, x1.y);
                        }
                        else {
                                context.moveTo(x1.x, x1.y);
                                context.lineTo(p1.x, p1.y);
                                context.lineTo(x2.x, x2.y);
                                context.lineTo(p2.x, p2.y);
                        }
                        break;
                }
                context.stroke();
        }
 
        function drawShape(shape, context) {
                context.strokeStyle = '#ffffff';
                if (shape.density == 1.0) {
                        context.fillStyle = "red";
                } else {
                        context.fillStyle = "black";
                }
                context.beginPath();
                switch (shape.m_type) {
                case b2Shape.e_circleShape:
                        {
                                var circle = shape;
                                var pos = circle.m_position;
                                var r = circle.m_radius;
                                var segments = 16.0;
                                var theta = 0.0;
                                var dtheta = 2.0 * Math.PI / segments;
 
                                // draw circle
                                context.moveTo(pos.x + r, pos.y);
                                for (var i = 0; i < segments; i++) {
                                        var d = new b2Vec2(r * Math.cos(theta), r * Math.sin(theta));
                                        var v = b2Math.AddVV(pos, d);
                                        context.lineTo(v.x, v.y);
                                        theta += dtheta;
                                }
                                context.lineTo(pos.x + r, pos.y);
 
                                // draw radius
                                context.moveTo(pos.x, pos.y);
                                var ax = circle.m_R.col1;
                                var pos2 = new b2Vec2(pos.x + r * ax.x, pos.y + r * ax.y);
                                context.lineTo(pos2.x, pos2.y);
                        }
                        break;
                case b2Shape.e_polyShape:
                        {
                                var poly = shape;
                                var tV = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[0]));
                                context.moveTo(tV.x, tV.y);
                                for (var i = 0; i < poly.m_vertexCount; i++) {
                                        var v = b2Math.AddVV(poly.m_position, b2Math.b2MulMV(poly.m_R, poly.m_vertices[i]));
                                        context.lineTo(v.x, v.y);
                                }
                                context.lineTo(tV.x, tV.y);
                        }
                        break;
                }
                context.fill();
                context.stroke();
        }

Above are the body drawing functions: drawJoint() and drawShape(). I’m not going to get into much detail here, but just know that these functions are responsible for taking the physics bodies and giving them a visual representation. They make calls to the canvas context 2D drawing API to create the shapes that give us our falling rectangles and circles. This is the simplest case and requires no external dependencies. In practical cases though, you will more likely find images or other more clever uses like this one:


        function createWorld() {
                var worldAABB = new b2AABB();
                worldAABB.minVertex.Set(-1000, -1000);
                worldAABB.maxVertex.Set(1000, 1000);
                var gravity = new b2Vec2(0, 300);
                var doSleep = true;
                world = new b2World(worldAABB, gravity, doSleep);
                createGround(world);
                return world;
        }

This is where the Box2D JS physics world is created. We define the bounds for the AABB physics with the minVertex and maxVertex properties and set the vector of gravity. After we create those we apply them to a newly created world and create the ground that will be the base of our scene.


        function createGround(world) {
                var groundSd = new b2BoxDef();
                groundSd.extents.Set(400, 30);
                groundSd.restitution = 0.0;
                var groundBd = new b2BodyDef();
                groundBd.AddShape(groundSd);
                groundBd.position.Set(400, 470);
                return world.CreateBody(groundBd);
        }
 
        function createBall(world, x, y) {
                var ballSd = new b2CircleDef();
                ballSd.density = 1.0;
                ballSd.radius = 20;
                ballSd.restitution = 0.5;
                ballSd.friction = 0.5;
                var ballBd = new b2BodyDef();
                ballBd.AddShape(ballSd);
                ballBd.position.Set(x,y);
                return world.CreateBody(ballBd);
        }
 
        function createBox(world, x, y, width, height, fixed) {
                if (typeof(fixed) == 'undefined') fixed = true;
                var boxSd = new b2BoxDef();
                if (!fixed) boxSd.density = 1.0; 
                boxSd.restitution = 0.0;
                boxSd.friction = 1.0;
                boxSd.extents.Set(width, height);
                var boxBd = new b2BodyDef();
                boxBd.AddShape(boxSd);
                boxBd.position.Set(x,y);
                return world.CreateBody(boxBd);
        }

Above we have the body create functions: createGround(), createBall(), and createBox(). Other than the obvious, let’s talk about a few things going on here. Each body is defined by a shape definition, and each shape definition has a number of properties that dictate how it will behave in the Box2D JS world (see the Box2D docs for details). Restitution, friction, and density affect how the shapes fall, move, and react.

The extents define the dimensions of the shapes, but probably not how you are accustomed. Extents represent the distance from one corner of the shape to its center. So a 100×100 box is actually defined by the extents shapeDef.extents.Set(50,50).

The shape definition is then used to define a body definition. The body is then positioned in the world. The positioning, like extents, is also based on the center of the body, not its corner. Finally, the newly defined body, based on the shape definition, is added the world with the CreateBody() function.


        function createHelloWorld() {
                // H
                createBox(world, 50, 420, 10, 20, false);
                createBox(world, 90, 420, 10, 20, false);
                createBox(world, 70, 395, 30, 5, false);
                createBox(world, 50, 370, 10, 20, false);
                createBox(world, 90, 370, 10, 20, false);
 
                // E
                createBox(world, 140, 435, 30, 5, false);
                createBox(world, 120, 420, 10, 10, false);
                createBox(world, 130, 405, 20, 5, false);
                createBox(world, 120, 390, 10, 10, false);
                createBox(world, 140, 375, 30, 5, true);
 
                // L
                createBox(world, 200, 435, 20, 5, false);
                createBox(world, 185, 400, 5, 30, false);
 
                // L
                createBox(world, 250, 435, 20, 5, false);
                createBox(world, 235, 400, 5, 30, false);
 
                // O
                createBox(world, 300, 435, 20, 5, false);
                createBox(world, 285, 405, 5, 25, false);
                createBox(world, 315, 405, 5, 25, false);
                createBox(world, 300, 375, 20, 5, false);
 
                // W
                createBox(world, 390, 435, 40, 5, false);
                createBox(world, 360, 390, 10, 40, false);
                createBox(world, 420, 390, 10, 40, false);
                createBox(world, 390, 415, 5, 15, false);
 
                // O
                createBox(world, 460, 435, 20, 5, false);
                createBox(world, 445, 405, 5, 25, false);
                createBox(world, 475, 405, 5, 25, false);
                createBox(world, 460, 375, 20, 5, false);
 
                // R
                createBox(world, 495, 410, 5, 30, false);
                createBox(world, 518, 425, 5, 15, false);
                createBox(world, 515, 405, 15, 5, false);
                createBox(world, 525, 390, 5, 10, false);
                createBox(world, 510, 375, 20, 5, false);
 
                // L
                createBox(world, 560, 435, 20, 5, false);
                createBox(world, 545, 400, 5, 30, false);
 
                // D
                createBox(world, 610, 435, 20, 5, false);
                createBox(world, 595, 405, 5, 25, false);
                createBox(world, 625, 405, 5, 25, false);
                createBox(world, 610, 375, 20, 5, false);
 
                // !
                createBox(world, 650, 430, 10, 10, false);
                createBox(world, 650, 380, 10, 40, false);
        }

Here’s my addition to the Box2D JS code. By using a series of stacked boxes I create the infamous programmer’s first message, “Hello World!” (minus the comma, sorry). No science or mystery here, just a lot of extents and positions for the boxes that compose my message. And yes, I did cheat on the “E” and make it fixed, but not even I can defy the laws of physics for the sake of a code demo.


        function step(cnt) {
                var stepping = false;
                var timeStep = 1.0/60;
                var iteration = 1;
                world.Step(timeStep, iteration);
                ctx.clearRect(0, 0, canvasWidth, canvasHeight);
                drawWorld(world, ctx);
                setTimeout('step(' + (cnt || 0) + ')', 10);
        }

The step() function is the what makes the whole thing work. step() is called over and over, at specified intervals, to create the animation of our scene on the canvas. The world’s Step() function is first called to apply one iteration of physics to our world’s bodies. Next we clear the visual representation of the scene so that it can be redrawn by our drawWorld() function. Finally we set the interval timer so that step() will be called again. Again, Flash devs will recognize this as a similar methodology as using the ENTER_FRAME event.


        // main entry point
        Event.observe(window, 'load', function() {
                world = createWorld();
                ctx = $('canvas').getContext('2d');
                var canvasElm = $('canvas');
                canvasWidth = parseInt(canvasElm.width);
                canvasHeight = parseInt(canvasElm.height);
                canvasTop = parseInt(canvasElm.style.top);
                canvasLeft = parseInt(canvasElm.style.left);
 
                createHelloWorld();
 
                Event.observe('canvas', 'click', function(e) {
                                if (Math.random() > 0.5) {
                                        //createBox(world, Event.pointerX(e), Event.pointerY(e), 10, 10, false);
                                        createBox(world, e.clientX, e.clientY, 10, 10, false);
                                } else {
                                        createBall(world, Event.pointerX(e), Event.pointerY(e));
                                }
                });
                step();
        });
        script>

And this is where we kick everything off. We use the Prototype event handling mechanism to wait until the window is loaded to start our code. We first create the Box2D JS world and get the 2D context, dimensions, and position of our canvas. After that I create the boxes that make up the Hello World message. To finish up we listen for mouse clicks so that we can add more falling objects to the scene, since what good is a meticulously stacked Hello World if you can’t turn it into a pile of rubble? The first step() is kicked off and our scene is ready to go!


    head>
    <body style="margin:0px;">
        <canvas id="canvas" width='800' height='500' style="background-color:#eeeeee;">canvas>
    body>
 html>

And to round out the breakdown, here’s the actual instance of the canvas element. Its a very simple container and allows for all the heavy lifting to be done in the Javascript. Just a reminder, the canvas element will NOT work in Internet Explorer unless you have the conditional check that includes excanvas if necessary, or if you happen to be test driving the IE9 beta.

The Summary

OK, well that turned out a lot longer and wordier than I was expecting, but 2D physics being utilized in HTML5 and Javascript is a simple topic. Hopefully, though, if you made it all the way through you have a much better understanding of how they all play together and how you will create Newtonian worlds of your own.

The difference in performance between different browsers is enough to drive you crazy. In my personal experience, Chrome > Firefox > IE. I know its early in the game and that IE 9 and Firefox 4 will likely be right up to par with Chrome, but its this lag and inconsistency across the board that has me spending most of my web development time in Flash and AS3.

Also, I had trouble making this work in all versions of IE, so if you run into any problems with a particular browser, please let me know.

NOTE: The concatenated version of Box2D JS is a convenience I offer so that you don’t have to do all the individual includes shown on the Box2D JS site’s demos. The minified version is that same concatenated version run through JSMin. Both versions are based on version 0.1.0 of Box2D JS. I am not a contributor to the Box2D JS project, just someone making it simpler to get into.

11 Responses to “Box2D JS – Physics in HTML5 & Javascript Guide”

  1. Bart Burkhardt says:

    I love this HelloWorld Box2d project, it’s a great tutorial, so thanks

    Do you know how I initiate an action when a collision occurs?
    For example, a sound or explosion animation

    Thanks, Bart

  2. Mark Robinson says:

    I can grasp placing objects, but is there any way to move a ball (player) on key press? I would appreciate any help

    Thanks

  3. Tyler says:

    Check out ImpactJS, They just integrated with Box2D and it’s pretty intuitive

  4. Sid says:

    Great guide man. But I was wondering why the top “box” on the letter E doesn’t collapse like there other letters.

    Comment out the first line under ‘E’ in createHelloWorld(), and you can see that it doesn’t move like it should.

    Thanks!

  5. @Sid: Its because I couldn’t find another way to create a stackable “E” without defying the laws of gravity ;) Perhaps there’s a way to move the center of gravity, but I haven’t dug that deep into it.

  6. Bibliotecas JavaScript: Motores de física | Chuso! says:

    [...] código en el que he basado este artículo está extraído de savagelook.com. Te recomiendo que entres y lo revises, ya que su ejemplo es más completo que la versión que yo [...]

  7. chuso says:

    Great post! I have make another post, with your sample code, but explaining how it works step by step in spanish. Of course, linking back to you.

  8. Yemi Bedu says:

    Hello,
    This will also work well in the chromium browser and give you less “who’s snooping on me” chills.

  9. LarsenSado says:

    Great great tutorial,
    thank you so much.
    i was playing a bit with your code and i was wondering if any of you know a way to set the dimension of the world (in wich the physic’s events occours) to fill the browser window..like the 100% property in the css for a div, or also something to center the world in the middle of the page, like the “margin:auto” property in css..
    thanks in advance
    take care

  10. craig says:

    very nice tutorial
    any idea how to make a character that moves like on http://speedballgame.com/

  11. Nagarajan says:

    I couldn’t find jquery version of this library…