Box2D and Polygons for JavaScript

Sponsor: Register today for New Game, the conference for HTML5 game developers. Learn from Mozilla, Opera, Google, Spil, Bocoup, Mandreel, Subsonic, Gamesalad, EA, Zynga, and others at this intimate and technically rich conference. Join us for two days of content from developers building HTML5 games today. Nov 1-2, 2011 in San Francisco. Register now!








Intro

Welcome back to another installment of Box2D for the JavaScript programmer. We will pause our exploration of running Box2D in a Web worker for a while and get back to the fundamentals. Today we look at Polygons, how to specify them, and how to render them.

The shape of things

Box2D supports both circles and squares/rectangles, and we've seen examples of both from our original Box2D Orientation article.

For quick review, specifying a circle requires the radius and the position:

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

this.bodyDef = new b2BodyDef;
this.bodyDef.type = b2Body.b2_dynamicBody;
this.fixDef.shape = new b2CircleShape(radius);

this.bodyDef.position.x = x;
this.bodyDef.position.y = y;
this.world.CreateBody(this.bodyDef).CreateFixture(this.fixDef);

Remember, the x and y coordinates are using Box2D's meters, and should not be pixels on your canvas.

Specifying a rectangle or square is fairly similar:

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

this.bodyDef = new b2BodyDef;
this.bodyDef.type = b2Body.b2_dynamicBody;
this.fixDef.shape = new b2PolygonShape;
this.fixDef.shape.SetAsBox(halfWidth, halfHeight);

this.bodyDef.position.x = x;
this.bodyDef.position.y = y;
this.world.CreateBody(this.bodyDef).CreateFixture(this.fixDef);

You can see the reference to bPolygonShape which is a hint that Box2D can simulate other types of polygons.

Constraints and definitions

What does Box2D mean by Polygon? Referring to the manual:
Polygon shapes are solid convex polygons. A polygon is convex when all line segments connecting two points in the interior do not cross any edge of the polygon. Polygons are solid and never hollow. A polygon must have 3 or more vertices.
See the below illustration for the difference between a convex and concave polygons:
You can simulate a concave polygon by binding together more than one convex polygons, and we'll look at an example of that in another article.

Out of the box (pun not intended), Box2D supports polygons with up to eight vertices.

Specification

Specifying a polygon is harder than specifying a square or rectangle, so kudus to Box2D for providing helper methods for those common cases.

The bodyDef still has a position, however with a polygon that position is not considered the center of the object. (This is different than the circle or square, so do be aware of this difference.) The position for a polygon is the point from which the vertices are relative to.  For example, a vertex of 0,0 is the same as the x,y position of the polygon. A vertex of 1,1 is one unit to the right and one unit down from the x,y position of the object.

The manual will tell you that the vertices must be specified in CCW, or counter-clockwise, order for "right handed coordinate systems." However, as far as I can tell, this is the opposite of the HTML5 Canvas. When specifying your polygon coordinates for HTML5 canvas, you should specify in clockwise order. (please correct in the comments if this is wrong)

The code for a triangle polygon is as follows:

this.fixDef.density = 1.0;
this.fixDef.friction = 0.5;
this.fixDef.restitution = 0.2;

this.bodyDef = new b2BodyDef;
this.bodyDef.type = b2Body.b2_dynamicBody;
this.fixDef.shape = new b2PolygonShape;

// entity.points == [{x: 0, y: 0}, {x: 1, y: 0}, {x: 0, y:2}]
var points = [];

for (var i = 0; i < entity.points.length; i++) {
    var vec = new b2Vec2();
    vec.Set(entity.points[i].x, entity.points[i].y);
    points[i] = vec;
}

this.fixDef.shape.SetAsArray(points, points.length);

this.bodyDef.position.x = x;
this.bodyDef.position.y = y;
this.world.CreateBody(this.bodyDef).CreateFixture(this.fixDef);

The follow screenshot shows the triangle (amongst other shapes):

Drawing the polygon

Using HTML5 Canvas we can draw any Box2D polygon:

PolygonEntity.prototype.draw = function(ctx) {
  ctx.save();
  ctx.translate(this.x * SCALE, this.y * SCALE);
  ctx.rotate(this.angle);
  ctx.translate(-(this.x) * SCALE, -(this.y) * SCALE);
  ctx.fillStyle = 'red';

  ctx.beginPath();
  ctx.moveTo((this.x + this.points[0].x) * SCALE, (this.y + this.points[0].y) * SCALE);
  for (var i = 1; i < this.points.length; i++) {
     ctx.lineTo((this.points[i].x + this.x) * SCALE, (this.points[i].y + this.y) * SCALE);
  }
  ctx.lineTo((this.x + this points[0].x) * SCALE, (this.y + this.points[0].y) * SCALE);
  ctx.closePath();
  ctx.fill();
  ctx.stroke();

  ctx.restore();

  Entity.prototype.draw.call(this, ctx);
}

Notice how in the above code, we first moveTo() to the position of the object, and then draw lineTo() to the vertices of the object, and then connect the last vertex to the original position.

The only properties of the object that change are the position and the angle, the vertices remain static (as they are essentially offsets from the position).

Center of mass

Finding the center of mass for a circle or rectangle in Box2D is easy, as it's just the position (via GetPosition() on the bodyDef.) The position for a polygon is not the center of mass, more like a simple reference point for the vertices of the polygon.

Finding the "center" of a polygon requires bodyDef.GetWorldCenter(). You can see a yellow dot for the center of mass of each of the objects in the example below.

Example

The embedded example below displays the triangle, with other objects like a circle, rectangle, and a four sides polygon.



You can check out the stand alone example for Box2D polygons. The source code is also available.

Summary

Box2D supports circles and squares and rectangles, as well as more complex polygons. The polygons must be convex, have no more than eight vertices, and the vertices must be specified in a specific order (seemingly clockwise for HTML5 canvas).

Next up is how to build and render complex bodies with Box2D, even convex bodies!

Please let me know what you think in the comments below. Thanks!

Popular posts from this blog

Lists and arrays in Dart

Converting Array to List in Scala

Null-aware operators in Dart