Saturday, September 24, 2011

Mouse Lock for HTML5 FPS games

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!




Good news, everyone!  Work is progressing on the Mouse Lock API, a new JavaScript API which will allow for playable "First Person Shooter" (aka FPS) games, and other  use cases, for HTML5 games.

Vince Scheib, Chrome engineer and veteran of the games development industry, has kicked off work back in June 2011 with an email to the public-webapps list.  A recent update from Vince, sent in Sept 22, 2011, hints at a work in progress implementation for Chrome.

The draft specification for Mouse Lock API is available for review.  It is proposed that the Web Events Working Group adopt the Mouse Lock spec.

Tracking progress for the spec is easy, with bugs available at W3C bug tracker, Chromium issue tracker, WebKit bugzilla, and the Mozilla bugzilla.

From the draft spec:

"""
The Mouse Lock API provides for input methods of applications based on the movement of the mouse, not just the absolute position of a cursor. A popular example is that of first person movement controls in three dimensional graphics applications such as games. Movement of the mouse is interpreted for rotation of the view-port, there is no limit to how far movement can go, and no mouse cursor is displayed.

Mouse Lock is related to Mouse Capture. Capture provides continued event delivery to a target element while a mouse is being dragged, but ceases when the mouse button is released. Mouse Lock differs by being persistent, not limited by screen boundaries, sending events regardless of mouse button state, hiding the cursor, and not releasing until an API call or specific release gesture by the user.
"""

Example code

x = document.getElementById("x");
x.addEventListener("click", mouseClick);
x.addEventListener("mousemove", mouseMove);

function mouseClick(e) {
    // Request that mouse be locked. 
    document.lockMouse(x);

    // If successful the mouseMove handler will continue 
    // to be called no matter how the mouse is moved.
    // The cursor will be hidden. No other elements or
    // system applications will be the target of mouse events.
}

function mouseMove(e) { 
    // .movementX/Y are valid normally as the mouse hovers
    // over an element. But when the mouse is locked the
    // mousemove event continues to fire whenever the mouse
    // moves.
    console.log(e.movementX + ", " + e.movementY); 
}

A call to lockMouse may trigger a user agent to prompt the user for permission.

Both movementX and movementY provide the change, or delta, in position of the mouse.  For example, movementX would equal eNow.screenX - ePrevious.screenX.

Summary

Adding Mouse Capture to the open web platform will enable first class FPS games. Along with WebGL and Fullscreen APIs, we'll see the emergence of a new class of web connected games.

I encourage you to follow along with the issues in the bug trackers and voice your support for Mouse Capture API!

Saturday, September 17, 2011

Box2D, Collision, Damage, 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!




After exploring Box2D and Impulses, which can make a body jump and move, we will turn our attention to detecting and dealing with body collisions.

Intro

Box2D of course knows when and how each body and fixture is contacting other bodies and fixtures, but so far we haven't reacted to these events nor done anything interesting. Luckily, there is an easy way to be notified when two bodies contact each other, when they stop contacting, and even how much impulse is felt by the bodies. We will explore these concepts in this blog post.

Can you feel it?

The b2ContactListener class from Box2D provides the four callbacks you can use to be notified of contact related events. These events are:
  • BeginContact - fired when two fixtures start contacting (aka touching) each other
  • EndContact - fired when two fixtures cease contact
  • PreSolve - fired before contact is resolved. you have the opportunity to override the contact here.
  • PostSolve - fired once the contact is resolved. the event also includes the impulse from the contact.
Note that all of the above events are fired during the world Step. This means you need to be very careful not to manipulate the world inside of these events, as the Box2D simulation isn't finished for the step, and you don't want to alter the world at this point.

EndContact can be fired outside of the Step, in the case of a body being removed from the world.

An example listener follows:

    var listener = new Box2D.Dynamics.b2ContactListener;
    listener.BeginContact = function(contact) {
        // console.log(contact.GetFixtureA().GetBody().GetUserData());
    }
    listener.EndContact = function(contact) {
        // console.log(contact.GetFixtureA().GetBody().GetUserData());
    }
    listener.PostSolve = function(contact, impulse) {
        
    }
    listener.PreSolve = function(contact, oldManifold) {

    }
    this.world.SetContactListener(listener);

Each of the events take a contact parameter, which has details of the contact, most of important are GetFixtureA() and GetFixtureB() which return the fixtures involved in the contact. From the fixtures you can reference the bodies. This implies that collisions occur between fixtures, not bodies.

How big was the impact?

You might be wondering, "Great, I see I can tell which two fixtures collided, but what was the force at the collision?" Knowing how hard or soft the collision was is important for lots of game logic.

Looking closely, you'll notice that Box2D doesn't hand you the force or impulse inside BeginContact(). You can of course calculate your own value for the magnitude of the collision, using linearVelocity and mass, both found on the body object. We might even do this in a future article.

Notice, though, that the PostSolve() method includes an impulse object, which includes an array of impulse values for the collision. This value is the easiest way to access the collision's magnitude.

If only things were so easy!

PostSolve() is called seemingly every time a body feels an impulse from another body. If object A hits object B which is connected to object C, then object C will feel the impulse from object A's initial hit (because the impulse "bumps" object B which "bumps" object C). This may or may not be what you want, depending on your game logic.

For example, if you have a ball rolling across the ground, this creates an impulse (albeit tiny) for every frame, and PostSolve() will be fired every frame. If you have other objects touching the ground, those objects will feel an impulse from the ground (which is "rumbled" by the ball rolling.)

If you are intending to calculate damage to an object from an impact, be very aware the PostSolve() event will be fired a LOT. If you are subtracting damage from an object's total "hit points" during every PostSolve() be sure you understand that your object will incur damage from basically every impulse.

You can of course create initial "hit points" high enough to withstand impulses from everywhere, or dismiss impulses below a threshold. Your needs will vary.

The important point to remember is that PostSolve() will be a called a LOT.

Example

For a simple example, I extended a previous example to add support for collisions. The below example will color objects black if they are feeling a contact (i.e. a PostSolve() event is fired) and will apply damage from the impulse.



You can view a stand alone version and see the source code.

I added a simple wrapper for the callback registration to my simple Box2D wrapper class:

bTest.prototype.addContactListener = function(callbacks) {
    var listener = new Box2D.Dynamics.b2ContactListener;
    if (callbacks.BeginContact) listener.BeginContact = function(contact) {
        callbacks.BeginContact(contact.GetFixtureA().GetBody().GetUserData(),
                               contact.GetFixtureB().GetBody().GetUserData());
    }
    if (callbacks.EndContact) listener.EndContact = function(contact) {
        callbacks.EndContact(contact.GetFixtureA().GetBody().GetUserData(),
                             contact.GetFixtureB().GetBody().GetUserData());
    }
    if (callbacks.PostSolve) listener.PostSolve = function(contact, impulse) {
        callbacks.PostSolve(contact.GetFixtureA().GetBody().GetUserData(),
                             contact.GetFixtureB().GetBody().GetUserData(),
                             impulse.normalImpulses[0]);
    }
    this.world.SetContactListener(listener);
}

I register a callback in the main page like this:

      box.addContactListener({
        BeginContact: function(idA, idB) {
        },
        
        PostSolve: function(idA, idB, impulse) {
          if (impulse < 0.1) return; // playing with thresholds
          var entityA = world[idA];
          var entityB = world[idB];
          entityA.hit(impulse, entityB);
          entityB.hit(impulse, entityA);
        }
      });

Note that the above code is not standard Box2D code, it is a simplification I've created for these demos.

I added a new method to Entity for the collision:

    Entity.prototype.hit = function(impulse, source) {
      this.isHit = true;
      if (this.strength) {
        this.strength -= impulse;
        if (this.strength <= 0) {
          this.dead = true
        }
      }
      
      //console.log(this.id + ", " + impulse + ", " + source.id + ", " + this.strength);
    }

For my purposes, entities may or may not have a strength (aka "hit points"). If my entity runs out of strength, it is "dead" and will be removed from the world:

    function update(animStart) {
      box.update();
      bodiesState = box.getState();
      
      var graveyard = [];
      
      for (var id in bodiesState) {
        var entity = world[id];
        
        if (entity && world[id].dead) {
          box.removeBody(id);
          graveyard.push(id);
        } else if (entity) {
          entity.update(bodiesState[id]);
        }
      }
      
      for (var i = 0; i < graveyard.length; i++) {
        delete world[graveyard[i]];
      }
    }

And my simple Box2D wrapper implements removeBody() as such:

bTest.prototype.removeBody = function(id) {
    this.world.DestroyBody(this.bodiesMap[id]);
}

Summary

The Box2D b2ContactListener provides four events for you to be notified when two fixtures contact/collide/touch. The PostSolve() method includes an impulse object which you can use to calculate damage or otherwise measure the intensity of the collision. However, PostSolve() is fired whenever a fixture "feels" another fixture, which means it will be fired quite a lot.

If you're at this point in your development, you'll probably notice that you're now involved with game tuning as much as raw coding. That is, with aspects like damage, the "feel" of the game now becomes quite important. It's not enough to figure out how the APIs work, you need to tune and tweak your game with quite a few "magic" values like damages, thresholds, etc.

Case in point is Angry Birds, which seems easy to copy until you start wanting to capture the essence of the game itself. That essence is wrapped up in many different small values that all add up to the "feel" of the game.

In other words... game balance is hard! :)

Next Steps

More details on Box2D contacts can be found in the Box2D manual. A Box2D collision tutorial from Alan Bishop covers PostSolve() but only deals with an instantaneous collisions and therefore doesn't run into the "PostSolve() gets called more frequently than you might think" issue. The Box2D forums feature a post on finding the maximum impulse from a collision.

Please do leave questions and comments below, and continue to let me know what else you'd like to see!

Thursday, September 15, 2011

Box2D, Impulse, and 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

So far we have been looking at worlds that interact merely from gravity, or motorized joints. The demos have been fairly calm. Time to add a bit more excitement with the introduction of Impulses. Using an impulse, we can immediately change a body's velocity.

Adding energy

Box2D has two seemingly similar ways to inject a little life into your bodies.
  1. ApplyImpulse : immediately change momentum. Think being hit by a bat.
  2. ApplyForce : change momentum over time. Think pushing something.
There's a lot of confusion over what the difference is between the two methods. If you need a quick obvious change, use ApplyImpulse. If you need to slowly change over time, use ApplyForce.

Example





The demo above has a stand alone version and the source code is available.

As you can see, we have the beginnings of an Angry Birds clone. An impulse is applied to the circle which propels it through the air, eventually crashing into innocent blocks.

The object configuration is straight forward:

    var initialState = [
      {id: "ground", x: ctx.canvas.width / 2 / SCALE, y: ctx.canvas.height / SCALE, halfHeight: 0.5, halfWidth: ctx.canvas.width / SCALE, color: 'yellow'},
      {id: "ball", x: 2, y: ctx.canvas.height / SCALE - 2, radius: 0.5},
      {id: "b1", x:17, y: ctx.canvas.height / SCALE - 1, halfHeight: 2, halfWidth: 0.10},
      {id: "b2", x:17, y: ctx.canvas.height / SCALE - 5, halfHeight: 0.25, halfWidth: 2}
    ];

Control your impulses

Both force and impulse require a vector, specifically a b2Vec2, for direction and magnitude. The following code is an easy way to create an impulse:

bTest.prototype.applyImpulse = function(bodyId, degrees, power) {
    var body = this.bodiesMap[bodyId];
    body.ApplyImpulse(new b2Vec2(Math.cos(degrees * (Math.PI / 180)) * power,
                                 Math.sin(degrees * (Math.PI / 180)) * power),
                                 body.GetWorldCenter());
}

To compute the vector for the impulse, the direction (in terms of degrees) and power (an arbitrary multiplier provided by me to test different powers) are combined.

The impulse is launched from the center of mass of the circle. The impulse can be launched from any point on the object, which creates a torque on the object.

Summary

Box2D allows you to add force and impulses to bodies. Adding an impulse is like hitting a body with a hard bat. Adding force over time is like pushing on a body.

Next Up

Please continue to follow along and learn about Box2D, Collision detection, and Damage.

Thanks for continuing to follow along. Your comments help, let me know what other topics you're looking for.

Box2D and Joints 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

Previously we looked at complex bodies with multiple shapes for Box2D. Using those techniques, we learned how to build concave bodies by associating two or more shapes to a body.

Another way to build more complex objects is to bind bodies together with joints. Joints constrain bodies together, for example by restricting ranges of motion or affecting motion of one body from another. Box2D supports many different types of joints, including pullings, gears, and ropes.

Box2D 2.1a (the version we've been using for all of these tutorials and demos) supports the following joint definitions:

  • Distance
  • Friction
  • Gear
  • Line
  • Mouse
  • Prismatic
  • Pulley
  • Revolute
  • Weld
Box2D 2.2 (the latest at the time of this writing, yet not yet ported to JavaScript) also supports:
  • all of the above
  • Wheel
  • Rope
Example

Let's start with a "revolute joint" which sounds like a fancy way of saying a joint that allows two bodies to revolve around a shared point. The below screenshot shows a sloped ground with a simple two wheel object rolling down.


See the below live example:



Visit the stand alone example of wheel joints with Box2D and check out the source code.

Explanation

Joints bind two bodies. As with many of Box2D's APIs, it all begins with a definition. For example, let's look at the definition of a Revolute joint:

    var joint = new b2RevoluteJointDef();
    joint.Initialize(body1, body2, body1.GetWorldCenter());
    this.world.CreateJoint(joint);

You can see the joint works with Bodies (not shapes or fixtures). The joint is created by the Box2D world.

Really weird stuff starts to happen when the anchor point for the joint is outside the body. And by really weird, I mean doesn't work. I've used the body.GetWorldCenter() to find the center of the body for the joint to bind to.

Notice that I'm only referencing the the center of one body. This is because a revolute joint specifies a single hinge point, and it assumes that the two bodies are already in the right position.

Revolute joints are quite configurable. Some examples of options for a revolute joint are:
  • enableLimit - whether the joint limits will be active
  • lowerAngle - angle for the lower limit
  • upperAngle - angle for the upper limit
  • enableMotor - whether the joint motor will be active, defaults to false
  • motorSpeed - the target speed of the joint motor. Positive for counter clockwise, negative for clockwise
  • maxMotorTorque - the maximum allowable torque the motor can use. A torque too weak won't be able to move the bodies.
Self powered

Using the motorSpeed and the maxMotorTorque we can build a self powered car.

bTest.prototype.addRevoluteJoint = function(body1Id, body2Id, params) {
    var body1 = this.bodiesMap[body1Id];
    var body2 = this.bodiesMap[body2Id];
    var joint = new b2RevoluteJointDef();
    joint.Initialize(body1, body2, body1.GetWorldCenter());
    if (params && params.motorSpeed) {
      joint.motorSpeed = params.motorSpeed;
      joint.maxMotorTorque = params.maxMotorTorque;
      joint.enableMotor = true;
    }
    this.world.CreateJoint(joint);
}


To show off this awesome hotrod, we'll flatten the ground and let her rip!




You can control the direction of the motor with the motorSpeed. A positive motorSpeed will travel counter-clockwise and a negative speed will travel clockwise (as seen above). The maxMotorTorque needs to be high enough to actually move your bodies.


Summary

Box2D joints bind two bodies together. Box2D has numerous joints like revolute, distance, and pulleys. Many joints have attributes like motor speeds, upper and lower angle bounds, and torque.

For more information on Box2D joints, the manual has examples and explanations of all the joint types. For more information on revolute joints specifically, I found this Box2d Revolute Joints tutorial helpful.

Next Up

Add some excitement to your Box2D simulations with Impulse.

Wednesday, September 14, 2011

Box2D with Complex and Concave Objects, 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

Previously in this Box2D for the JavaScript developer series, I covered how to build and simulate arbitrary polygons. Not entirely arbitrary, as I mentioned that you can only build convex objects. Turns out I was both right and wrong. Read on to learn how to build more complex and even concave bodies.


Example

The below screenshot has an example of a complex object. The green square + triangle is also concave.


Below is the live version:



The stand alone example for the above demo is available, as well as the source code for review.

Explanation

I'll start using more exact terminology now that we've covered the basics of getting objects onto the screen and interacting with each other. It's important to understand the specifics now that we're getting more complex.

A Body is the rigid chunk of matter.

A Shape is a geometrical object, such as a polygon, circle, square, etc. A Shape must be convex.

A Fixture binds a shape to a body.

Up to this point, we've had a one-to-one relationship between Body, Shape, and Fixture. Turns out that Box2D Bodies can have multiple shapes!  If you extrapolate what that means, you can see that we can build Bodies with complex shapes.


Code

With this more specific understanding and terminology, let's use it to build complex bodies (see, I used the right term there :)

I've extended our JSON shorthand object definitions to support one or more polygons for a body. (This is non-standard Box2D code, just something I put together to make it easier to play around.)

var initialState = {"0": {id: 0, x: 10, y: 5, radius: 2},
                    "1": {id: 1, x: 5, y: 5, polys: [
                      [{x: 0, y: 0}, {x: 1, y: 0}, {x: 0, y:2}] // triangle
                    ]},
                    "2": {id: 2, x: 9, y: 4, halfHeight: 1.5, halfWidth: 0.9},
                    "3": {id: 3, x: 4.5, y: 3, polys: [
                      [{x: 0, y: -2}, {x: 2, y: 0}, {x: 0, y:2}, {x:-0.5, y: 1.5}] // odd shape
                    ]},
                    "4": {id: 4, x: 10, y: 10, polys: [
                        [{x: -1, y: -1}, {x: 1, y: -1}, {x: 1, y: 1}, {x: -1, y: 1}], // box
                        [{x: 1, y: -1.5}, {x: 2, y: 0}, {x: 1, y: 1.5}]  // arrow
                    ], color: "green"}
};

Notice the last definition has two polygon definitions. The first has four points for a box, and the second has three points for a triangle. Just like arbitrary shapes, the points are defined relative to the position of the body as specified by the x,y coordinates.

To construct the Box2D objects from the above definition, I refactored the original code a bit to handle more than one Fixture and Shape per Body.

bTest.prototype.setBodies = function(bodyEntities) {
    this.bodyDef.type = b2Body.b2_dynamicBody;
    
    for(var id in bodyEntities) {
        var entity = bodyEntities[id];
        
        this.bodyDef.position.x = entity.x;
        this.bodyDef.position.y = entity.y;
        this.bodyDef.userData = entity.id;
        var body = this.world.CreateBody(this.bodyDef);
        
        if (entity.radius) {
            this.fixDef.shape = new b2CircleShape(entity.radius);
            body.CreateFixture(this.fixDef);
        } else if (entity.polys) {
            for (var j = 0; j < entity.polys.length; j++) {
                var points = entity.polys[j];
                var vecs = [];
                for (var i = 0; i < points.length; i++) {
                    var vec = new b2Vec2();
                    vec.Set(points[i].x, points[i].y);
                    vecs[i] = vec;
                }
                this.fixDef.shape = new b2PolygonShape;
                this.fixDef.shape.SetAsArray(vecs, vecs.length);
                body.CreateFixture(this.fixDef);
            }
        } else {
            this.fixDef.shape = new b2PolygonShape;
            this.fixDef.shape.SetAsBox(entity.halfWidth, entity.halfHeight);
            body.CreateFixture(this.fixDef);
        }
    }
    this.ready = true;
}

The HTML5 Canvas code didn't change too much. As you can see below, the canvas is translated and rotated once, and then all of the shapes are drawn with a series of moveTo() and lineTo() calls.

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 = this.color;

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

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

Summary

Box2D supports bodies with complex and even concave shapes by binding more than one Fixture and Shape to a Body.

Next Up

Did this help clear up shapes and bodies? What would you like to see next? Please leave comments below and let me know.

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!

Monday, September 12, 2011

Box2D, Web workers, Better performance

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!








WARNING: There is a bug with Chrome 15.0.874.5 dev (and probably earlier in the 15 dev channel series) that fires requestAnimationFrame twice per paint, which leads to very different results that you want. To see this correctly, use Canary or Chrome Stable.

(this post should have been called "Fixin' muh shit")

Following up on a recent post on Box2D, Web workers, and Page Visibility API, we will now take a closer look at performance with Box2D and Web workers. And we fixed my broken code.

Intro

I was initially pleased with the early work on Box2D and Web workers. Moving the physics simulation to the worker process makes sense, as it frees up our render loop. After a closer look, though, I could see hitching and jitters with the Web worker version, especially compared to the inline Box2D. Why was this happening?

Out of sync

After closer inspection, and some help from my awesome colleagues, we came to the conclusion that the workers were not synced with the renderer and everyone once in a while a frame would be skipped.

It turns out, and you probably knew this, that setInterval() and setTimeout() only handle integer time spans. This means a common call of setInterval(paint, 1000/60) will not in fact run at 16.6667 times per second (which just so happens to match the VSync of monitors and the requestAnimationFrame rate).

This means my requestAnimationFrame render loop is running at 60Hz while my worker thread was running at slightly more than 60Hz.  The paint thread and the update thread weren't in sync, and it showed.

How to verify

Chrome, it turns out, has a very nifty way to gain visibility into its render processes. Open a new tab and type about:gpu.  You'll find two tabs of interest.

The first tab lists your GPU configuration, including your graphics card, the current hardware accelerated functionalities (like WebGL or Canvas), and information about your driver.


This is handy information for verifying key components of your browser's graphics configuration.

The second tab available in about:gpu is where you really get to see how the sausage is made. And by sausage I clearly mean "getting stuff drawn onto the screen." Welcome to GPU Profiling!

GPU Tracing

You can record your browser's GPU calls for analysis, and drill down into how long the browser has spent on each call.

Below is an example of a trace from my MacBook Air:


You can see a highlighted WebViewImpl::animate call, which is the requestAnimationFrame call itself. This is where the browser is waiting for your particular code.  You can also see, in text at the bottom, a Duration of 12.237 ms for the highlighted animate call.

I've turned on the red vertical lines by clicking a call and pressing 'g'.  If you want the red line to appear at the end of the call, press shift-g.  These red lines mark a frequency of 16.66667 ms. Note that while these lines don't specifically mark the VSync cycle itself, you can use them to visualize one frame. If you never want to miss a frame, ensure that your animate method, and the supporting Chrome methods which follow (like the WebViewImpl::composite above) do not span these red vertical lines.

This was a wake up call for me. As you can see, even if my code was awesome and was under 16.6667 ms (the length of a frame), I may still end up hitching because the browser has more compositing and other tasks to perform.

Of this is is different if your Canvas happens to be hardware accelerated (doesn't mean it'll go faster, though I hope it does, just that it will be different).

The profiling doesn't look like the above when you start out. In fact, it looks like the below:


Exactly... huh?

The keyboard commands will come in very handy:

Keyboard shortcuts:
 w/s   : Zoom in/out
 a/d   : Pan left/right
 e     : Center on mouse g/G   : Shows grid at the start/end of the selected task

Using the w key, you can zoom in enough to see the pattern of frames.

We saw the hitches easily when we used the Tracing tool. The hitches show up as blank spots where a GPU call would be. You'll see call after call fairly regularly, and then some open space. Boom. Hitch.

Dev Tools to the rescue

After we used the Tracing tool to confirm our hitching, we wanted to find the culprit. Using another tool, the Timing tab in the Webkit Developer Tools, confirmed our suspicion.


The above screenshot is from the (mostly) fixed version, but you can even see a problem here. Notice the nice Paint and Function Call sequence (the Function Call is the worker's onmessage on the page, signaling the arrival of new state from Box2D). Normally everything is fine, Paint and Function Call, over and over. This is a good sequence, in lock step with one another.

Notice, though, the two Paint calls in a row. This means that Box2D took too long to send a message with the new state of the bodies, and the render loop just painted again. This is a small hitch, as the render loop painted the same scene twice.

(this reminds me, if the code doesn't receive an updated message from the worker, why should it paint? we can make our code smarter by only painting if it has received a message since the last time it painted. I'll go try that now...)

Success!

By not updating (admittedly a very quick op in the main thread) and not painting (costly) unless there's new state from Box2D via a message, I've seemingly made the main page much more consistent and less hitchy. I no longer see the double Paints or double Function Calls. Of course, YMMV.

Example

The stand alone example is too big to fit here, as I was upping my game and made a 1024x768 demo. I encourage you to check it out. You can run the example with the simulation in either a worker or inline so you can compare the performance and results. The source code is also available.

Summary

By using tools such as the GPU Tracing system and the Dev Tools Timeline, we can gain a better understanding of the browser's performance, call sequences, and insight into what's happening under the covers.

By syncing our app's worker process to compute physics simulations on demand instead of continually, we've greatly improved performance and reduced hitching. By only drawing when there's new information, we've reduced the amount of work the main thread has to perform as it waits for new state from the worker process. This seems to further reduce hitching.

Bonus, my calculating physics on demand, there's no need for the Page Visibility API to enable or disable the simulation in the worker.

Next Steps

I've gone too far down the rabbit hole here with frame rates and workers. We'll next turn our attention back to pure Box2D with a look at polygons. Stay tuned, and please leave your questions and thoughts in the comments below.

Saturday, September 10, 2011

Box2D, Web workers, and Page Visibility API

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!








UPDATE: The below code is not optimal. While this is interesting, I encourage you immediately follow up with the next post on a better way to do it.

(If you're not familiar with Web workers, I encourage to read the previous post first before diving into this post.)

Intro

A previous article discussed running Box2D in a Web worker. We found that, by putting the physics simulation into a separate process, we have better performance as we free up the main JavaScript thread to focus on rendering.

But we can do better.

I see it's not visible

It's great that your awesome physics based WebGL game is running at 60FPS. While I'm playing the game, that is. When I tab out to check how to make candied bacon ice cream (go ahead, I'll wait), there's no reason for my browser and CPU to grind away rendering 60FPS or simulating physics. As developers, we can provide for a better experience by pausing everything when the game is not visible.

Because we are good modern HTML5 game developers, we are already using requestAnimationFrame to schedule our draws. One of the benefits of this technique is allowing the browser to pause animation loops if the canvas, window, or tab is not visible. If the user can't see the element, no reason to keep animating. Not only does this save battery, but it effectively pauses the game which is a nice touch.

Or does it?

Workers sneakily keep working

Our Box2D in Web workers demo showed that you can run your physics simulation in a worker. While the render loop, powered by requestAnimationFrame, will pause if the tab is placed into the background, the worker will happily keep working and filling up that message queue with updates.

You can verify this by using Chrome's Task Manager. First, load up the original Box2D in a Web worker demo, then open the Task Manager (wrench icon, tools, task manager). Sort by CPU so you can see the tab running the simulation. Now, put that tab into the background, which pauses the animation and render loops. Notice, however, that CPU doesn't go to zero (on my machine it hovers around 10%)


You might be thinking, "wait a minute, why only 10%? why not more?" Good observation! It turns out that Chrome (and other browsers like Firefox) will clamp your setInterval and setTimeout calls to 1 second if your tab goes to the background. This is a good start, but it's still wasteful to run a physics simulation once a second if the user can't see or interact with it.

Not only is it wasteful, the simulation keeps right on simulating. The user doesn't have any chance to see the results or interact. It would be a bad experience if the user comes back to the tab to see the world completely changed.

Now I know I'm not visible

To fully pause the game, both the render loop and the physics simulation, we need to know if the tab or window is visible to the user. Enter the Page Visibility API!

The Page Visibility API provides a callback which signals to our application when the tab or window is visible to the user. For example, you can pause a video if the user hides the window.

Luckily, the API is very simple and straight forward. The host page, not the worker, but register for the callback:

    document.addEventListener('webkitvisibilitychange', function() {
      if (document.webkitHidden) {
        worker.postMessage({'cmd': 'hidden'});
        console.log('page now hidden, sent msg to worker');
      } else {
        worker.postMessage({'cmd': 'visible'});
        console.log('page now visible, sent msg to worker');
      }
    }, false);

You can see above that the main page listens to the webkitvisibilitychange event. Once fired, it looks at the document.webkitHidden flag. It then sends a message to the worker about the state change.

Over in the worker, we listen for the message and stop or start the loop accordingly:

var intervalId = setInterval(loop, 1000/30);

self.onmessage = function(e) {
    switch (e.data.cmd) {
        case 'visible':
            if (intervalId == null) {
                intervalId = setInterval(loop, 1000/30);
            }
            break;
        case 'hidden':
            clearInterval(intervalId);
            intervalId = null;
            break;
        case 'bodies':
            box.setBodies(e.data.msg);
            break;
    }
};

Demo

Try it out for yourself. Reload this page (or go to the stand alone version of the below example) and switch to another tab before the simulation finishes.

For the first test, load up the Task Manager to see zero CPU after the tab is hidden. For the second test, come back to this tab and notice how every object picks up right where it left off.

(Contrast this behavior with the original Box2D and Web worker demo, you'll see the bodies jump ahead when you return to the tab)



Grab the source code for the example above or check out the stand alone version.

Summary

Using a combination of requestAnimationFrame and Page Visibility API, you can control both the rendering and physics simulations when the game or app is no longer visible. This saves battery and provides a more consistent user experience when the user returns to your game or app.

Next Up

I took a closer look at the worker code and realized it had problems. Read on...

Friday, September 9, 2011

Box2D, Update Rate, and Paint Rate 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!




Last article we looked at running Box2D inside of a Web worker, with promising results. We will revisit Web workers in a future article. For now, let us return to a single thread.

Intro

After reviewing some of these recent Box2D posts, my colleague Boris Smus pointed out that I've missed an obvious example test. It would be interesting to see what happens if we twiddle two knobs: the update frequency and the draw frequency. I realized my previous articles don't address this experiment, so I present to you:

Moar Knobs

The example below now allows you to configure the number of bodies to simulate and render. I've bumped up the default to 100 for a more strenuous test.

You can now enable or disable individual simulations. Don't want to run side by side? No problem, turn one off to check performance.  (Note: we'll introduce Web workers with another article for a better side by side comparison)

I've modified a previous experiment to include a new knob: "How often we call world.step()" which is a long winded way to say "Update frequency".  This knob controls how often update() is called, which in our experiment, calls world.step().

The experiment still contains the previous knob for FPS which is draw() aka paint() frequency. This means we can draw more, or less, than we update, to see what effect it has on performance, both real and perceived.

Example


(The example below is available as a stand alone full side by side version.)



Try changing FPS to 30Hz and Time Step and world.step() freq to 60Hz. Or turn FPS up to 60 and turn Box2D down to 30. What do you see? How does it compare?

Results

Right (top) side:

  • FPS: 60
  • Box2D Time Step: 60
  • Adaptive: Off
  • Update: 60
  • Num Bodies: 100
  • Run Sim: On
Left (bottom) side:
  • FPS: 60
  • Box2D Time Step: 30
  • Adaptive: Off
  • Update: 30
  • Num Bodies: 100
  • Run Sim: On
For this simple 100 bodies simulation with the above configuration, the left (bottom) side seems to display a less smooth experience. Bodies however do end up approximately in the right place and approximately the right time.

I do see some Box2D correction as bodies seem to overlap sometimes in the Left/Bottom test.

What was surprising for me, though, was that the Left/Bottom side seemed to run faster. On my machine, the bodies on the Left didn't seem bogged down like the bodies on the Right. Bumping the number of bodies back down to 10 for both sides shows a matching speed, so it appears that running the physics simulation only half as much as your render loop helps the world performance maintain a constant speed.

Of course, we're running two simulations on the same thread in the browser, so we're affecting the performance of both simulations.

Open Source

Check out the source code and run the stand alone example.

Next Steps

Now that we have Moar Knobs and a more complex simulation, we will merge the Web worker example with this example for a better side by side test. By moving all Box2D simulations to separate processes, we will hopefully get a better sense of performance characteristics after we twiddle the knobs.

In the meantime, check out the next article on incorporating the Page Visibility API with Web workers and Box2D.

Thursday, September 8, 2011

Box2D and Web workers for JavaScript developers

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!








UPDATE: The below code is non-optimal. While it is interested, and I encourage you to read it for completeness, be sure to continue to a following post on fixes and analysis of this technique.

This is the fourth post in a series on Box2D for the JavaScript developer.

Intro

Our previous post on Box2D frame rates, world step rates, and adaptive rates showed how changing the FPS and world step frequency has an effect on performance and perception. The rendering and physics simulations were running in the same loop, which is typical for web programming.

However, nothing in the simulation requires it to be tied so closely to the render loop. What if we could separate the two loops (physics update loop and render loop) and run them on different threads?

Enter Web workers

Yay! for HTML5 because modern browsers can in fact spawn what is essentially a new process for long running, computationally intensive tasks. Web workers are designed to allow JavaScript to run independently of the main event loop for the page. This can keep your UI responsive for the user as the browser, in a separate process, is crunching on expensive algorithms which would otherwise lock up the browser.

Browser Support



Thanks to the awesome caniuse.com we can see that support for Web workers is pretty good, even the new Internet Explorer 10 will include it. For our purposes, those users without Web workers can either get Chrome Frame or you as the developer can gracefully degrade the application experience by placing the Box2D simulation into the render loop.

For users with modern browsers, we can provide an enhanced experience by using more than one CPU core.

Web worker details

Workers shouldn't be considered threads, as they are generally more heavy and should be considered more like processes. For example, you should only spawn a few workers per page.

Creating a new worker is straight forward:

var worker = new Worker('worker.js');

Notice how you need to supply a path to a script to be run by the worker. While you can't just point a worker to a function on your page, you can use the Blob Builder to construct JavaScript from the host page to hand to the worker. Generally, though, you will write your worker code as a separate file.

Restrictions

A worker can't access the DOM of your page. It can't add elements, respond to user events, and it can't write to console.log(. Best to think of a worker as a server for your client app.

Communicating with a worker

Use postMessage() and onmessage to send and receive messages between the host page and the worker. This should look familiar, it's how you communicate between different documents and iframes.

For example, receiving a message from the worker:

worker.onmessage = function (event) {
  document.getElementById('result').textContent = event.data;
};

Notice event.data which contains the object sent from the worker. I've noticed the event data can contain any object, not just simple strings or numbers. Data sent from worker to host page will be serialized and copied from producer to consumer.

To send a message, use postMessage():

postMessage(msg);

To be clear, the data sent via postMessage() will be copied and serialized. This implies the consumer or receiver of the event will get a copy of the message.

This event queue is unbounded, in Chrome at least. Your producer should be aware of its message sending rate to ensure it doesn't get too far ahead of the consumer. Unfortunately, the Web worker spec doesn't have the API to manage the queue. If your producer is generating a lot of messages (as we will below) you will need to add throttling.

Debugging workers

It is unfortunately that workers can't access console.log(). It is more difficult to debug a worker, but not impossible. Luckily, the Chrome Developer Tools can debug a worker, although it is not automatic.


In the Scripts tag in the Dev Tools, you'll see a Worker inspectors drop down on the right. By clicking the Debug checkbox, the Dev Tools will place the worker inside an iframe of the host page (recent versions of Chrome >= 15 should not be debugging workers natively). You can then step through the worker and debug it like normal.

Learn more

We barely scratched the surface for workers. I can recommend the Web worker spec and the Basics of Web Workers article from HTML5 Rocks.

Box2D and Workers

Box2D functions best if it can run at a consistent rate. By placing Box2D into a Web worker, we can shelter it from the browser's UI thread and let it run its loop independently.

Our strategy will be to use the host page to build the world and to perform all rendering via requestAnimationFrame. The worker will run the Box2D simulation at its own rate, and will post updates for all the objects in the world back to the host page to render.

This separation of concerns lets the Box2D world update at its optimal rate, while the rendering of the world can run as fast as possible.

Creating the world

We start by creating a simple object hierarchy of Entities, both Circles and Rectangles. We need these entities because the host application doesn't know anything about Box2D, nor should it.

    function Entity(id, x, y) {
      this.id = id;
      this.x = x;
      this.y = y;
      this.angle = 0;
    }
    
    Entity.prototype.update = function(state) {
      this.x = state.x;
      this.y = state.y;
      this.angle = state.a;
    }
    
    Entity.prototype.draw = function(ctx) {
      ctx.fillStyle = 'black';
      ctx.beginPath();
      ctx.arc(this.x * SCALE, this.y * SCALE, 2, 0, Math.PI * 2, true);
      ctx.closePath();
      ctx.fill();
    }
    
    function CircleEntity(id, x, y, radius) {
      Entity.call(this, id, x, y);
      this.radius = radius;
    }
    CircleEntity.prototype = new Entity();
    CircleEntity.prototype.constructor = CircleEntity;
    
    CircleEntity.prototype.draw = function(ctx) {
      ctx.fillStyle = 'blue';
      ctx.beginPath();
      ctx.arc(this.x * SCALE, this.y * SCALE, this.radius * SCALE, 0, Math.PI * 2, true);
      ctx.closePath();
      ctx.fill();
      
      Entity.prototype.draw.call(this, ctx);
    }
    
    function RectangleEntity(id, x, y, halfWidth, halfHeight) {
      Entity.call(this, id, x, y);
      this.halfWidth = halfWidth;
      this.halfHeight = halfHeight;
    }
    RectangleEntity.prototype = new Entity();
    RectangleEntity.prototype.constructor = RectangleEntity;
    
    RectangleEntity.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.fillRect((this.x-this.halfWidth) * SCALE,
                   (this.y-this.halfHeight) * SCALE,
                   (this.halfWidth*2) * SCALE,
                   (this.halfHeight*2) * SCALE);
      ctx.restore();
      
      Entity.prototype.draw.call(this, ctx);
    }

An entity has a position (x and y) and an angle. Box2D will provide these values.

We need to uniquely identify the entities so the Box2D worker and the host app can match up bodies and entities. Therefore, an ID is given to each entity.

Notice the entities know how to draw themselves. An entity also draws a small black dot in the center of the entity, which is very handy for visually debugging. We'll cover this in more detail later.

Next, we randomly create 150 entities for our world.

    var world = {};
    for (var i = 0; i < 150; i++) {
      world[i] = randomEntity(i);
    }

You will no doubt have something way better than this for your real app.

Creating the worker, populating the world

With our random assortment of entities, we are ready to start the Box2D simulation worker.

    var worker = new Worker('physics.js');
    worker.postMessage(world);

Note the postMessage(world) call which sends the world, and all 150 entities, to the worker. This is how the Box2D world receives its initial configuration.

Over in physics.js the worker receives the message:

// in physics.js worker
self.onmessage = function(e) {
    box.setBodies(e.data);
};

and it creates the Box2D bodies from the world entities:

bTest.prototype.setBodies = function(bodyEntities) {
    this.bodyDef.type = b2Body.b2_dynamicBody;
    for(var id in bodyEntities) {
        var entity = bodyEntities[id];
        if (entity.radius) {
            this.fixDef.shape = new b2CircleShape(entity.radius);
        } else {
            this.fixDef.shape = new b2PolygonShape;
            this.fixDef.shape.SetAsBox(entity.halfWidth, entity.halfHeight);
        }
       this.bodyDef.position.x = entity.x;
       this.bodyDef.position.y = entity.y;
       this.bodyDef.userData = entity.id;
       this.world.CreateBody(this.bodyDef).CreateFixture(this.fixDef);
    }
    this.ready = true;
}

Note that after the bodies are configured, the ready flag is set to true. This flag is required because the worker has already started its loop, and we want to signal when the bodies are set so the simulation can begin.

Looping

The worker starts the loop immediately, before any message is received:

var loop = function() {
    if (box.ready) box.update();
}

setInterval(loop, 1000/30);

As you can see, the loop is managed by setInterval to govern the frequency, which we have set to 30Hz.

Different frequencies

If you recall, the render loop on the host page is managed with requestAnimationFrame which should be running at 60Hz. This means we are rendering twice as fast as we are updating our physics simulation. The render loop simply draws whatever it knows about at the time of the frame.

Updating the canvas

The physics simulation is running in the worker, and updating the state of all the bodies. After each update, it needs to send a message to the host page with the new state of the objects.

bTest.prototype.sendUpdate = function() {
    var world = {};
    for (var b = this.world.GetBodyList(); b; b = b.m_next) {
        if (typeof b.GetUserData() !== 'undefined') {
            world[b.GetUserData()] = {x: b.GetPosition().x, y: b.GetPosition().y, a: b.GetAngle()};
        }
      }
    postMessage(world);
}

The sendUpdate function builds a temporary data structure to hold the x, y, and angle properties of all the objects. It only wants to inform the host page of bodies with IDs set in the UserData field. There is no need to send information about static bodies like the ground, as they don't change.

Note: a future enhancement will only send updated state for objects that are not at rest. If the object is sleeping, there's no need to send an update to the host page.

Remember that the world object is copied and serialized as it's sent via postMessage.

Updating entities

The host page receives the update messages from the worker and updates the state of all the entities.

    worker.onmessage = function(e) {
      for (var id in e.data) {
        world[id].update(e.data[id]);
      }
    };

You can see the use of the IDs as the order of the entities and bodies from the Box2D simulation are not maintained.

Render loop

In the host page, the render loop is running at its own 60Hz pace, drawing the objects to the canvas.

    (function loop() {
      ctx.clearRect(0, 0, 640, 480);
      for (var id in world) {
        var entity = world[id];
        entity.draw(ctx);
      }
      stats.update();
      requestAnimFrame(loop);
    })();

Yay! for polymorphism, as we let the implementation of Entity draw itself to the canvas.

Example

Now that we've walked through the code, let's see it in action:



Reload the page a few times to see it in action. Remember that the physics loop is running via a worker, and updating the host page at 30Hz with the new coordinates and angles for each of the entities. The rendering is governed by requestAnimationFrame on the host page, running at 60Hz.

Dangers

This is all well and good, but there is an added difficulty with this configuration. The Web worker, or producer, can generate too many messages for the host page, or consumer, to deal with. Although the queue is an unbounded vector in Chrome, at some point, if message sending rate isn't closely monitored, the browser will run out of memory.

The trick is to send messages just fast enough for the consumer to use them. Sending too many and you'll eventually run out of memory. Send too few and the perceived animation performance will suffer. If the consumer falls too far behind, it will want to catch up by pulling messages from the queue and throwing them out.

Web workers do not have APIs to manage the queue, so it's up to the application to build throttling logic and coordination between producers and consumers.

Summary

We've learned that we can separate our rendering and physics simulations by using Web workers. Placing Box2D into a worker allows it to run in a separate process, thus speeding up the rendering loop in the host page.

Future improvements include sending less updates from the worker when objects are asleep, and not rendering if all objects are asleep. Also, a necessary improvement would include update throttling so that the producer doesn't send more than the consumer can handle.

The full source code for this example is available and open source.

Visit the next stop on our Box2D tour: Twiddling the Update Freq and Draw Freq knobs.

Attend New Game for more HTML5 game nerdery

To learn more about JavaScript development for HTML5 games, I encourage you to join us at New Game.

New Game is the first conference in North America dedicated to HTML5 game development. Key developers and engineers from Google, Opera, Mozilla, Gamesalad, Spil, Subsonic, Mandreel, and others will present sessions on bringing modern games to the modern web browser. Keynotes from EA and Zynga will inspire and amaze. New Game is a conference by developers and for developers. Register today!

Disclaimer

I'm probably required to say that the views expressed in this blog are my own, and do not necessarily reflect those of my employer. Also, except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the BSD License.