In part 1: I covered what Box2d is along with some of its application and a number of links to possible box2d resources. Now in this blog, I'm going to demonstrate a basic setup for box2d based simulations and the next one will cover the integration with KineticJS library.
This example was adapted from the Box2dWeb example.
Now let's break up the code and see how it works. For any box2d application, we start with defining the world with two parameters: gravity and sleep.
world = new b2World(
new b2Vec2(0, 10),
true
);
Here, we define gravity as a vector equal to 10units in y direction where (0,0) is top left and y is +ve downwards. And, sleep parameter tells box2d to not process the object if it is in equilibrium.
var scale = 20.0;
Scale
is another important declaration which maps the distances in Box2d world (in meters) to corresponding distance on our canvas (in pixels). So our code here defines 20m = 1px.
Next we see, how to define the ground for our world.
var fixDef = new b2FixtureDef;
fixDef.density = 2.0;
fixDef.friction = 0.9;
fixDef.restitution = 0.8;
var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_staticBody;
bodyDef.position.x = screenW/2/scale;
bodyDef.position.y = screenH/scale;
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(screenW/scale, 10/scale);
world.CreateBody(bodyDef).CreateFixture(fixDef);
In the above piece of code, we first define the properties (density, friction & restitution) of the ground. Then we define ground as a "static
" body (Box2d has 3 types of bodies, static, dynamic & kinematic, refer to manual for more details). A rectangular body in box2d is defined by its center, width & height. Thus, we position our ground's center at screenH(eight) {to place it at bottom} and half of screenW(idth) and divide it by scale to map to box2d coordinates. Finally, we define ground as a polygon shape and draw it as a box with width = screenW and height = 10px, and then create a body with these defined parameters, added to our world.
bodyDef.position.x = 0;
bodyDef.position.y = screenH/2/scale;
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(1/scale, screenH/scale);
world.CreateBody(bodyDef).CreateFixture(fixDef);
bodyDef.position.x = screenW/scale;
bodyDef.position.y = screenH/2/scale;
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(1/scale, screenH/scale);
world.CreateBody(bodyDef).CreateFixture(fixDef);
Similar to our ground, we define the left & right boundaries for our simulation space to keep the simulation objects within visible scope. This is optional and depends on the application. With the boundaries set, now let's add some objects to our world.
bodyDef.type = b2Body.b2_dynamicBody;
for(var i = 0; i < 10; ++i)
{
if(Math.random() < 0.5) {
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(Math.random() + 0.5, Math.random() + 0.5);
} else {
fixDef.shape = new b2CircleShape(Math.random() + 0.5);
}
bodyDef.position.x = Math.random() * screenW/scale;
bodyDef.position.y = Math.random() * screenH/scale/4;
world.CreateBody(bodyDef).CreateFixture(fixDef);
}
First important thing to note here is the change of body type to "dynamic
" as these are objects in our simulation space that interact with each other under laws of physics. The code above randomly adds rectangles and circles to the world placing them randomly across the canvas width and in top quarter of the canvas height.
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback, element){
window.setTimeout(callback, 1000 / 60);
};
})();
This is our callback for animation. It is widely suggested to use requestAnimFrame
instead of setTimeout
or setInterval
for better animation performance.
function update() {
world.Step(1 / 60, 3, 3);
if(drawDebug)
{
context.clearRect(0,0,screenW, screenH);
world.DrawDebugData();
drawCanvasObjects();
}
else
world.DrawDebugData();
world.ClearForces();
requestAnimFrame(update);
};
Next comes our update function. It first tells world
to step ahead in time, calculate the forces, their impact and resultant positions of the objects in the world. After that, it draws the world using DrawDebugData()
(defined below), a native renderer of box2d particularly useful in debugging application and testing interaction of various objects in simulation. if(drawDebug)
checks whether to overwrite the basic box2d rendering with HTML5 elements. In this demo, I simply fill the circles with "red
" color. Finally, we clear the forces and call our animation handler.
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(context);
debugDraw.SetDrawScale(scale);
debugDraw.SetFillAlpha(0.5);
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit | b2DebugDraw.e_centerOfMassBit);
world.SetDebugDraw(debugDraw);
debugDraw => defining DrawDebugData()<
function drawCanvasObjects()
{
var node = world.GetBodyList();
while(node)
{
var curr = node;
node = node.GetNext();
if(curr.GetType() == b2Body.b2_dynamicBody)
{
var shape = curr.GetFixtureList().GetShape();
if(shape.GetType() == circle.GetType())
{
var position = curr.GetPosition();
var canvasY = position.y*scale;
var canvasX = position.x*scale;
context.strokeStyle = "#000000";
context.fillStyle = "#FF0000";
context.beginPath();
context.arc(canvasX,canvasY,shape.GetRadius()*scale,0,Math.PI*2,true);
context.closePath();
context.stroke();
context.fill();
}
}
}
}
Function drawCanvasObjects()
gets the list of objects attached to the world and iterate over them checking if the object is a dynamic body. Then, it checks for the body type to be "circle
", in which case we retrieve the position of the body, maps it to canvas coordinates and then draw them filled red.
function keydown(e)
{
if (e.keyCode == 68)
{
drawDebug = !drawDebug;
}
}
window.addEventListener("load", init, true);
Finally, the handler to check key-press 'd
' and the event listener to "load
" event.
This completes the part 2. I hope it helps my fellow programmers who are starting/ planning to start developing next Angry Birds for all of us to enjoy over and over. I'll love to hear your suggestions!
Next in this series would be how to use KinecticJS library with box2d.