2010-06-30

Emitting Physics Events with Box2d and Node

Node tries to make as much application logic as possible asynchronous and non-blocking. Most objects communicate by emitting "events" to which other objects can subscribe with listeners. The listeners handle the emitted events asynchronously. All if this is pretty well documented and examples can be found in the node source code itself, most of its included modules, and many applications that make use of the framework.

In sticking with this pattern for nodehockey, I wanted to make the physics simulation asynchronous with sending state updates to the clients. Here's the code (refactored for brevity):
var sys = require("sys"),
    events = require("events"),
    b2d    = require("./path/to/box2dnode");

var PhysicsSim = function () {
    events.EventEmitter.call(this);
    this.runIntervalId = null;

    // Initialize the simulator 
    var worldAABB = new b2d.b2AABB();
    worldAABB.lowerBound.Set(-1000, -1000);
    worldAABB.upperBound.Set( 1000,  1000);
    var gravity = new b2d.b2Vec2(0.0, -9.8);
    var doSleep = true;
    this.world = new b2d.b2World(worldAABB, gravity, doSleep);

    // Initialize physics entities
    // ...
}
sys.inherits(PhysicsSim, events.EventEmitter);
PhysicsSim.prototype.getState() = function () {
    // Get whatever state information we should return to clients
    // ...
    return state;
}
A new class is defined to handle all the physics simulation. The class is responsible for initializing the world (see the Box2D documentation for more on this) and setting up the entities to be simulated. It includes a method for retrieving the current state of the simulated world.

The important bit is setting the class up to be an event emitter, accomplished by the first line in the function (to call the "parent" constructor) and the sys.inherits call which will add all the prototype properties and functions. This will allow the simulator to broadcast events to which other objects can subscribe:
PhysicsSim.prototype.run = function () {
    this.pause();
    this.runIntervalId = setInterval(function (sim, t, i) {
            sim.world.Step(t, i);
            sim.emit("step", sim.getState());
        }, 50, this, 1.0/60.0, 10);

    this.emit("run");
}
PhysicsSim.prototype.pause = function () {
    if (this.runIntervalId != null) {
        clearInterval(this.runIntervalId);
        this.runIntervalId = null;
    }
    this.emit("pause");
}
Three events are defined here. Starting with the last two, "run" is broadcast whenever the simulation begins or is un-paused, and "pause" is broadcast whenever we halt the simulation.

The more interesting event is the "step" event, which is broadcast when the simulation runs another timestep. An interval timer is set up to run the simulation every 50 milliseconds. The simulator is passed in, as well as a timestep and a simulation interval.

In Box2D, the timestep determines how much "in world" time passes with each step. The smaller the value, the more incremental the calculation of the next state. In the above code, the timestep is 1/60th of a second.

The second argument is the simulation interval (the name is a bit misleading.) This is the number of passes in each step that the simulator will take when calculating collisions and movement. Every time a body in the simulated world moves, it has the chance to affect the other bodies. Since this can cascade infinitely within a step, the interval limits how many times the calculations will actually be run. Note that Box2D is smart enough to stop calculating if it doesn't need the full number of passes.

The interval's wait time (50 milliseconds) does not have to match the simulator's timestep. Increasing or decreasing the wait time will speed up or slow down the rate at which the simulator calculates the next step completely independently of how much "in world" time passes with each step, determined by the timestep. This can be useful for adding slow-motion or fast-forward effects to simulations and games.

Once the step is calculated, a "step" event is emitted with the new game state. This is the signal that will be used to let all the clients know that there is a new state they must handle. The following code demonstrates catching these events:
var sim = new PhysicsSim();
sim.addListener("run", function () {
        sys.puts("Simulation running");
    })
    .addListener("pause", function () {
        sys.puts("Simulation paused");
    })
    .addListener("step", function (state) {
        sys.puts("New simulation step: " + sys.inspect(state));
    });
sim.run();
setTimeout(function () {
    sim.pause();
}, 1000);
Running this code in a terminal will show the simulator starting, the first 20 steps of the simulation, then the simulator pausing.

Any number of listeners can be subscribed to these events, even after the simulation is running. Here's an example using websockets:
var ws = require("./path/to/websocket/library");
var sim = new PhysicsSim();
sim.run();

ws.createServer(function (connection) {
    connection.ready = false;
    connection.addListener("connect", function () {
            connection.ready = true;
        });
    sim.addListener("step", function (state) {
            if (connection.ready) {
                connection.write(JSON.stringify(state));
            }
        });
}).listen(8080);
When run, a simulator is created and begins stepping through the simulation. At this point, it is broadcasting "step" events, though there are no listeners.

A websocket server is created and starts listening for connections on port 8080. When it receives a new connection, it attaches a new listener to the simulator for that connection to receive step events, JSON serialize the state that is broadcast and send it to the websocket client.

The "connect" listener on the websocket connection is used to prevent sending step events to the client before the connection is fully initialized. Since all the event handling takes place asynchronously, it is very likely that a step event could be received and sent through the websocket before the socket is done connecting. The connect event on the websocket doesn't fire until the connection is complete, so at that point we can signal the step listener to start sending states to the client.

Since the simulator runs independently of the client connections, clients can connect, disconnect and reconnect at any time, and immediately see the same state as all other clients. Nothing needs to save the simulator state to replay to clients that connect at later points.

If a client does want to see what it has missed, a listener could be added to the step event that would save each state, then replay those states to the client faster than new states are broadcast. Once all the states are replayed, the client could then begin to receive states at the normal pace. This is an exercise left for the reader.

2010-06-29

Canvas Transforms and Origin Re-Mapping

The nodehockey client receives a set of coordinates from the server in units of meters. These physical coordinates need to be translated into pixel values on the canvas.

There are a few obstacles that need to be overcome. The first, is that there is no direct mapping of pixels to meters. My original solution was to take each coordinate and multiply it by a scaling factor, which was the height of the canvas in pixels divided by the height of the physical table in meters. To translate back when sending coordinates from the client to the server, I divided the canvas coordinates by the same scaling factor.

The second issue is that the canvas element places its origin at the top-left of the element, with positive y-values going down towards the bottom. The physical model uses the standard Cartesian scale, with the origin at the bottom-left and positive y-values going up. The original solution to that was to subtract the scaled y-coordinate from the canvas height for rendering, and do the opposite for sending mouse position back.

It turns out, canvas has a built-in method for dealing with these types of problems. Instead of manually scaling each coordinate, you can set a scaling factor on the canvas drawing context, and it will do the scaling for you:
var tableHeight = physical.height;
var canvasHeight = 400;
var scaleFactor = canvasHeight / tableHeight;

context.scale(scaleFactor, -scaleFactor);

The scale() method takes 2 arguments. The first is the multiplier on the x-axis, the second is on the y-axis. They are independent. The above code also reverses the canvas's y-axis, so that y-values move bottom to top.

There is still an issue with this solution. Since y-values are being translated into the negative range, they are drawn from the top of the canvas element upwards, hiding them from view. To solve this, a further transformation must be applied:
context.translate(0, -tableHeight);
The translate() method shifts all coordinates on the canvas by the amounts specified by the first (x-value) and second (y-value) arguments. In this case, it shifts all y-values down by the height of the physical table, which will be scaled to the height of the canvas element. Effectively, this moves to origin of the canvas to the bottom-left, meaning a straight translation of physical coordinates to pixel coordinates.

Translating from canvas pixels back to physical meters still requires a manual transformation:
function scaleToPhysical(coords) {
    scaled = {
        x : coords.x / scaleFactor,
        y : (canvasHeight - coords.y) / scaleFactor,
        r : coords.r / scaleFactor
    }
    return scaled;
}
Anyone know of a better way to do this?

Another note: the scaling applies to all line styles as well as coordinates. Therefore, it's helpful to know the width of an actual pixel in scaled terms. The following code will find the "physical" width of a single pixel:
var singlePixel = 1 / scaleFactor;

2010-06-28

Real-time Multi-player In-Browser Gaming: Air Hockey

I've been wanting to try my hand with nodejs for a while now, and have been trying to find time and a proper project. Recently, I was reading about the <canvas> HTML element, and started trying to think of a cool learning project for that as well. Joining the two technologies together to build a browser-based 2-player air hockey game seemed like just the balance of challenge and opportunity to learn that I was looking for.

The basic premise is to use canvas on the client side to render the state kept track of on the server side, with the server written in Javascript using nodejs. The client is also responsible for capturing player mouse movements and sending them to the server to update game state. Two players will be able to manipulate the same game board at the same time. Only the first two connections to the server are players; other connections can be made and will witness the same game as spectators.

To make things a bit more challenging, the two players should be allowed to be on different clients but still play in real-time. Websockets will allow the client and server to communicate without AJAX long-polling. A physics engine utilizing Box2D will keep track of the game state (actually, I'll be using my nodejs module port of Box2D box2dnode.)

I will be posting now and then on this project as I work on it, learn something interesting or change anything drastically. All source code for the project can be found at http://github.com/jadell/nodehockey. Note: you must have an HTML5 capable browser, like Chrome to run the client.

Blog Title Change

Apparently, there is already a group out there called Citizen Software. In order to not cause confusion or future legal hassles, I have renamed my blog to Everyman Software.

2010-06-21

box2dnode available

I finished my first pass at a Box2D port for nodejs. It is available at http://github.com/jadell/box2dnode.

The module was constructed fairly easily by combining the files from Jonas Wagner's Javascript port of Box2DFlash into one large file, then exporting all the classes using
exports.b2ClassName = b2ClassName;

Pretty simple. Next step is to figure out how to turn mouse movement into forces that act on the various entities on the game board.

2010-06-18

Nodejs and Physics

I was going to make a my first project in nodejs a simple airhockey game. It was going to be an opportunity to not just play with nodejs, but also HTMLS5's canvas tag and websockets. I got the canvas and websockets portion working, and even talking to a nodejs server (more on this stuff in a later post series.)

There was a hang-up halfway through the airhockey project. I found myself having to recreate a physics engine. While this sounds like an interesting project, it wasn't where I was hoping to go with the project I was working on. Instead, I set off on a search for a nodejs-based physics engine. No such luck.

Then I stumbled upon Box2d2. There is already a Javascript physics engine! It seems mainly built for browser use, but it's already written. So my new meta-project is to port Box2d2 into a nodejs module. A side-meta-project is to learn how to properly unit test nodejs modules and applications.

box2dnode is available at http://github.com/jadell/box2dnode.