HTML5: Combining physics engine (box2dWeb) with DeviceOrientation

When the first smartphones came out, they all had the traditional labyrinth applications. Application where you had to move a ball around a certain path by tilting and moving your phone around. There is also an HTML5 specification that allows you to do this directly from your browser. This would allow us to create all kind of cool games that run directly from the browser. In this article I'll show you my first experiments with this API in combination with a javascript based physics engine. The goal is to create a simple HTML5 application where you can move a number of balls around using the motion sensors in your device. What we're aiming for is this:

Physics using box2dweb

You can test this for yourself here: "Box2dWeb and deviceorientation demo".

Before I start with the explanation, a quick note on device compatibility. My initial idea was to write this for my tablet to play around a bit with the various sensors, HTML5 apis etc. that are available. The problem however is, that at the moment the box2dweb.js library is much too heavy for mobile development. I've alo tried other physics javascript libraries out there, but they all result in a framerate of about 2 to 3 frames per second. I'm currently looking into offloading the physics engine to a serverside backend (just as I did in the facedetection example). So I tested and changed this example to work with the sensors from my MacBook Pro.

Accessing device sensors

Accessing a device's sensors is very easy. The specification defines that you have to register for a specific event, and you'll receive a callback at a specific interval like this:

      window.addEventListener("deviceorientation", function(event) {
          // process event.alpha, event.beta and event.gamma
      }, true);
</javscript>
 
The event you receive contains the following information:
 
<javascript>
     {alpha: 90,        // represents position on the z-axis
       beta: 0,          // represents position on the x-axis
       gamma: 0};    // represents position on the y-axis

For my macbook this results in the following set of events:

absolute: null
alpha: null
beta: 2.2814938370474303
bubbles: false
cancelBubble: false
cancelable: false
clipboardData: undefined
currentTarget: DOMWindow
defaultPrevented: false
eventPhase: 0
gamma: 1.3697507397371704
returnValue: true
srcElement: DOMWindow
target: DOMWindow
timeStamp: 1336114836578
type: "deviceorientation"

As you can see, besides a lot of other info, we receive an alpha, beta and a gamma. For my macbook I never receive an alpha value. For this demo I want to roll all the balls, from the image, to the left when I tilt my laptop to the left and they should roll to the right when I tilt my laptop to the right. The same goes for when I tilt my laptop backwards, the balls should roll to the top and when I tilt my laptop downwards the balls should roll to the bottom.

I use the following listener for this:

// initialize the device orientation and set the callback
function initOrientation() {
    if (window.DeviceOrientationEvent) {
        console.log("DeviceOrientation is supported");
        window.addEventListener('deviceorientation', function (eventData) {
 
            var LR = eventData.gamma; // used as x gravity
            var FB = eventData.beta;  // used as y gravity
 
            var newXGravity = Math.round(LR / 2);
            var newYGravity = Math.round(FB / 2);
 
            if (newXGravity != world.m_gravity.x || newYGravity != world.m_gravity.y ) {
 
                // set new gravity
                world.m_gravity.x = newXGravity
                world.m_gravity.y = newYGravity
 
                // wakeup all the bodies when the gravity changes
                for (var body = world.m_bodyList; body; body = body.m_next) {
                    body.SetAwake(true);
                }
            }
        }, false);
    } else {
        alert("Not supported");
    }
}

In this listing I start listening for the event and retrieve the new X and the new Y gravity based on the tilt of the laptop. When the tilt has changed, I set the new gravity of the physics world and wake up any sleeping bodies (see below for more info on this). And that's all you need to do.

Setup the physics engine

Next lets look at the physics engine. I've use box2dweb, which is an javascript implementation of the box2d javascript engine. I won't go into too much detail here, but the comments in the code should explain what is happening.

function initWorld() {
    var b2Vec2 = Box2D.Common.Math.b2Vec2
        , b2AABB = Box2D.Collision.b2AABB
        , b2BodyDef = Box2D.Dynamics.b2BodyDef
        , b2Body = Box2D.Dynamics.b2Body
        , b2FixtureDef = Box2D.Dynamics.b2FixtureDef
        , b2Fixture = Box2D.Dynamics.b2Fixture
        , b2World = Box2D.Dynamics.b2World
        , b2MassData = Box2D.Collision.Shapes.b2MassData
        , b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
        , b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
        , b2DebugDraw = Box2D.Dynamics.b2DebugDraw
        , b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef;
 
    // setup the world
    world = new b2World(
        new b2Vec2(0, 10)    //gravity
        , true               //allow sleep
    );
 
    // define the borders
    var fixDef = new b2FixtureDef;
    fixDef.density = 0.1;
    fixDef.friction = 0.3;
    fixDef.restitution = 0.2;
 
    var bodyDef = new b2BodyDef;
    //create enclosure
    bodyDef.type = b2Body.b2_staticBody;
    fixDef.shape = new b2PolygonShape;
    fixDef.shape.SetAsBox(width / 10, 2);
 
    // draw lower bound
    bodyDef.position.Set(0, height / scale);
    world.CreateBody(bodyDef).CreateFixture(fixDef);
 
    // draw upper bound
    bodyDef.position.Set(0, 0);
    world.CreateBody(bodyDef).CreateFixture(fixDef);
 
    // draw left bound
    fixDef.shape.SetAsBox(2, height);
    bodyDef.position.Set(0, 0);
    world.CreateBody(bodyDef).CreateFixture(fixDef);
 
    // draw right bound
    bodyDef.position.Set(width / scale, 0);
    world.CreateBody(bodyDef).CreateFixture(fixDef);
 
    //create 100 objects
    bodyDef.type = b2Body.b2_dynamicBody;
    for (var i = 0; i < 100; ++i) {
        fixDef.shape = new b2CircleShape(1);
        bodyDef.position.x = (Math.random() * width) / scale;
        bodyDef.position.y = (Math.random() * height) / scale;
        world.CreateBody(bodyDef).CreateFixture(fixDef);
    }
 
    //setup debug draw
    var debugDraw = new b2DebugDraw();
    debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
    debugDraw.SetDrawScale(10.0);
    debugDraw.SetFillAlpha(1);
    debugDraw.SetLineThickness(1.0);
    debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
    world.SetDebugDraw(debugDraw);
 
    // start drawing
    window.setInterval(update, 1000 / 100);
}
 
function update() {
    world.Step(1 / 60, 8, 3);
    world.DrawDebugData();
    world.ClearForces();
}

Wrapping up

Accessing the device orientation API is actually pretty easy. Using the information in a useful way is a whole other story. It really is too bad though that there isn't a performant javascript physics engine yet for mobile devices. On the other hand, with the speed the power of smartphones and tablets is increasing time might solve this.