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:
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:
{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.