Monday, July 29, 2013

Setting up Ruby on Rails

Found this amazing installer, that gets you going instantly
http://railsinstaller.org/en

And Remember,
- Ruby is the language
 - RubyGems is the packaging system
- Rails is a web application development framework written in the Ruby language

A fun place to start learning Ruby.
http://tryruby.org/

Friday, May 31, 2013

Screenshot using nodejs and phantomjs

In the ever evolving javascript space the best way to connect the pieces is thru solving problems. The problem I am trying to solve here is "provide users the ability to request screenshots on a remote machine.  Use a  node.js server that allows you to enter a URL and get screenshots back from the  browser" 

Installation and System Setup

Download nodejs installer from http://nodejs.org/download/
After installation open the command prompt and type
C:\>node --version //prints the version number
C:\>npm -version //npm is a NodeJS package manager. it's used to install node programs.
The next step would be to install all the dependencies for the project, -g flag is to install the module globally.
C:\>npm install -g socket.io  
C:\>npm install -g phantomjs  
C:\>npm install -g node-static  
C:\>npm ls    //lists out all the modules installed

The project contains 3 pieces,
nodejs_example1.js - is the server code that defines how the server talks to the client.
nodejs_example1.html -  is the html page loaded in the client browser.
phantomjs_example1.js - contains the phantomjs code to take the screenshot

nodejs_example1.js
var http = require('http')
  , io = require('socket.io')
  , fs = require('fs')
  , os = require('os') 
  , path = require('path')
  , childProcess = require('child_process')
  , phantomjs = require('phantomjs');
var server = http.createServer(onCreateHandler);
var socket = io.listen(server);
server.listen(8090);
var sessionid;
function onCreateHandler(req, res) {
  fs.readFile(__dirname + '/nodejs_example1.html',
  function (err, data) {
    if (err) {
      res.writeHead(500);
      return res.end('Error loading nodejs_example1.html');
    }
    res.writeHead(200);
    res.end(data);
  });
}
socket.on('connection', function (socket) {
  sessionid = genRandNum();
  socket.emit('session id', { id:  sessionid});
  socket.on('capture screen event', function (id, url) {
    if(sessionid == id){
         captureScreenShot(url);
    }else{
         console.log("Invalid Session ID");
    }
  });
});
function captureScreenShot(data){
  var binPath = phantomjs.path;
  output = os.arch() + os.platform() +  "_" + Math.round(os.uptime()) + '.png';
  var childArgs = [
          path.join(__dirname, 'phantomjs_example1.js'),
          data,
          output]
  console.log(childArgs);
  childProcess.execFile(binPath, childArgs, function(err, stdout, stderr) {
        // handle results
  }) 
}
function genRandNum() {
    return Math.floor((Math.random()*10)+1); //Return a random number between 1 and 10
}

nodejs_example1.html
<html>
<head>
<script type="text/javascript" src="http://localhost:8090/socket.io/socket.io.js"></script> 
<script type="text/javascript" src="http://localhost:8090/phantomjs/phantomjs.js"></script> 
<script type="text/javascript" src="http://localhost:8090/phantom/phantom.js"></script> 
<script>
  var socket = io.connect('http://localhost');
  var sessionid = 0;
  socket.on('session id', function (data) {
    sessionid = data.id;
  });
  function captureScreenShot(){
    socket.emit('capture screen event', sessionid, document.getElementById("inputurl").value );
  }
</script>
</head>
<body>
 url for screenshots <input type="text" id="inputurl" value="https://www.google.com/"><br>
 <input type="submit" value="Get Screen Shot" onclick="captureScreenShot()">
</body>
</html>

phantomjs_example1.js
var system = require('system');
var page = require('webpage').create();

address = system.args[1]; 
output = system.args[2];
//debug_address = 'https://www.google.com/'; 
//debug_output = 'hello.png';

 page.open(address, function (status) {
  console.log(address);
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit();
        } else {
            window.setTimeout(function () {
                page.render(output);
                phantom.exit();
            }, 200);
        }
    });
To Run the code, (note all three files must be in the same folder) start your server as follows,
C:\>node nodejs_example1.js
Open the bowser and load the following page "http://localhost:8090/", Click on the "Get ScreenShot" button, the screenshot gets stored in your local folder.

The next blog would be about sending the screenshot to the server.

Friday, July 27, 2012

Drawing Line in the Box2D world

I wanted to draw a line in the Box2D world using the mouse click and Drag. This would be helpful in games where I need to Dynamically draw a line to cut a joint or body.

Step 1: Add EventListeners for the mouse events
document.addEventListener("mousedown", handleMouseDown, true);
document.addEventListener("mouseup", handleMouseUp, true);
document.addEventListener("mousemove", handleMouseMove, true);
Step 2: Event Handlers
//Start Drag when the mouse click happens and save the start position
var mouseX, mouseY, dragMode = false, line = null;
function handleMouseDown(e) { 
    mouseX = (e.clientX - canvas.offsetLeft) / SCALE;
    mouseY = (e.clientY - canvas.offsetTop) / SCALE;
    dragMode = true;
}
function handleMouseUp(e) { 
 dragMode = false;
 currentMouseX = (e.clientX - canvas.offsetLeft) / SCALE;
 currentMouseY = (e.clientY - canvas.offsetTop) / SCALE; 
 var start = new b2Vec2(mouseX, mouseY);
 var end = new b2Vec2(currentMouseX, currentMouseY);
 line = drawline(line, start, end);
 
}
function handleMouseMove(e) {
 if(dragMode)
 {
  currentMouseX = (e.clientX - canvas.offsetLeft) / SCALE;
  currentMouseY = (e.clientY - canvas.offsetTop) / SCALE; 
  var start = new b2Vec2(mouseX, mouseY);
  var end = new b2Vec2(currentMouseX, currentMouseY);
  line = drawline(line, start, end);  
 }
}
function keyset(evt){
}
Step 3: updating the to redraw the new line
function update() { 
    ....
    var lineCount = 0;
    for (b = world.GetBodyList() ; b; b = b.GetNext()) { 
       if(b.GetUserData() != null){
           if( b.GetUserData().name == "Line"){
               updateDrawLine(b);
               lineCount++;
            if(dragMode && lineCount > 1) objScheduledForRemoval[++index] = b;
        }
    ....

In the above code snippet, I am drawing a new line and deleting the old one as my mouse gets dragged. The issue I faced while doing this is if I hover over the same point without moving then the line disappears to fix this I have added the lineCount parameter, to make sure atleast one line is visible to the user at all times during the drag. On a side note, Box2D doesn't like it when you attempt to remove a body from the world when the world is being stepped. Hence I am adding the objects to be removed to a list and removing them later.

//------------------------------- Removing objects from the world
window.setInterval(removeObjScheduledForRemoval, 1000/90);
var objScheduledForRemoval = Array();
var index = -1;
function removeObjScheduledForRemoval()
{
 for(var i = 0; i <= index; i++){
  console.log("here",i);
  world.DestroyBody(objScheduledForRemoval[i]); 
  objScheduledForRemoval[i] = null; 
 }
 objScheduledForRemoval = Array();
 index = -1;
}

Step 4: Drawing the line in the Box2D world.

function drawline(b,start,end) {
    var fixDef = new b2FixtureDef;
  fixDef.shape = new b2PolygonShape;
  fixDef.density = 1.0;
  fixDef.friction = 0.5;
  fixDef.restitution = .5;
  fixDef.shape.SetAsArray([
       start,
       end],2
  );
  var bodyDef = new b2BodyDef;
  bodyDef.type = b2Body.b2_staticBody;
  bodyDef.position.Set(0,0);
  rbData = new Object();
  rbData.name = "Line";
  rbData.start = start;
  rbData.end = end;
  bodyDef.userData = rbData;
  var line = world.CreateBody(bodyDef).CreateFixture(fixDef);
  return line;
}
Step 4: Mapping the line to the canvas.
function  updateDrawLine(b){
 start = b.GetUserData().start;
 end = b.GetUserData().end;
 context.save(); 
 context.beginPath();
 context.moveTo(start.x * SCALE,start.y * SCALE);
 context.lineTo(end.x * SCALE,end.y *SCALE);
 context.stroke();
 context.restore();
}

Handling Mouse Click in Box2D

Often in a game you would want to know the b2Body on which the mouse click occurred .

The co-ordinates for the mouse click can be obtained as follows,
var mouseX, mouseY;
function handleMouseDown(e) {
    mouseX = (e.clientX - canvas.offsetLeft) / SCALE;
    mouseY = (e.clientY - canvas.offsetTop) / SCALE;
    getBodyAtMouse();
}

The b2Body at the Mouse Click can be obtained as follows,
var selectedBody;
function getBodyAtMouse() {
mousePVec = new b2Vec2(mouseX, mouseY);
    var aabb = new b2AABB();
    aabb.lowerBound.Set(mouseX - 0.001, mouseY - 0.001);
    aabb.upperBound.Set(mouseX + 0.001, mouseY + 0.001);
    selectedBody = null;
    world.QueryAABB(getBodyCB, aabb);
    if(selectedBody!= null && selectedBody.GetUserData() != null)
              console.log(selectedBody.GetUserData().name);
    return selectedBody;
}

b2AABB() returns an axis aligned bounding box defined by the upper and lower bounds. I have set the upper and lower bound of the box to the mouse click points. The AABB query returns all fixtures overlapping in the given the rectangular area, the callback function is called for each of the fixture in the bounding box. The callback is usually used to create a list of fixtures/body for further processing.

function getBodyCB(fixture) {
if(fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), mousePVec)){
                  selectedBody = fixture.GetBody();
    }
}

Thursday, July 26, 2012

Adding sound to the Box2D world

This is a continuation from the prev post, and it gives the code flow for "Let there be sound".

Add audio tag to your html page,
.....
<body onLoad="init();">
      <canvas id="canvas" width="600" height="400" style="background-color:#333333;" ></canvas>
      <audio id="audio" src="media/misc003.mp3">
 </audio>
</body>
.....

Initializing the audio object in the js file
initWorld.js
    audio = document.getElementById("audio");

Event handling for the audio object can be added as follows
    audio.addEventListener("playing", start, false);
    audio.addEventListener("pause", stop, false);
    audio.addEventListener("ended", stop, false);
    function start() {
    }
    function stop() {
    }

playing sound in the Detect collision loop
....
if((body1.GetUserData().name == "Bat" && body2.GetUserData().name == "Ball") ||(body1.GetUserData().name == "Ball" && body2.GetUserData().name == "Bat")){
audio.play();
...
}
....

other methods that used for audio manipulation within js
Alternative, to play audio from JavaScript you can simply do this:
       var audio = new Audio("song.mp3");
       audio.play();
To pause
        audioElement.pause();
Volume Controller
        audioElement.volume=0
Play at exactly 20 seconds in the song
        audioElement.currentTime=35;
        audioElement.play();

Tad dam we are done, this plays the audio file everytime there is a collision between the bat and the ball. 

Reference
HTML5 Audio Tag
https://developer.mozilla.org/en/Using_audio_and_video_in_Firefox
http://www.webkit.org/blog/140/html5-media-support/
HTML5 Supported Audio Formats

Wednesday, July 25, 2012

BananaMonkey

Hi guys sharing the completed BananaMonkey mini project here.
p.s. Sorry about the indentation in the code, the editor screws it up :( 

index.html

<html>
   <head>
      <title>Box2dWeb</title>
   </head>
   <style type="text/css" media="screen">
    canvas, img { display:block; margin:1em auto; border:1px solid black; }
    canvas { background:url(image/BG2.jpg) }
  </style>

 <body onLoad="init();">
      <canvas id="canvas" width="600" height="400" style="background-color:#333333;" ></canvas>
   </body>
   <script type="text/javascript" src="libs/Box2dWeb-2.1.a.3.min.js"></script>
<script type="text/javascript" src="js/initWorld.js"></script>
<script type="text/javascript" src="js/baseclass.js"></script>
<script type="text/javascript" src="js/gameDemo.js"></script>
</html>

initWorld.js
- initializes the world
- handles events
- handles collision
- redraws world


var world;
var b2Vec2 = Box2D.Common.Math.b2Vec2
        ,      b2BodyDef = Box2D.Dynamics.b2BodyDef
        ,      b2Body = Box2D.Dynamics.b2Body
        ,      b2FixtureDef = Box2D.Dynamics.b2FixtureDef
        ,      b2World = Box2D.Dynamics.b2World
        ,      b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
        ,      b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
        ,      b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef
        ,      b2DistanceJointDef = Box2D.Dynamics.Joints.b2DistanceJointDef
        ,      b2RopeJointDef = Box2D.Dynamics.Joints.b2RopeJointDef
        ,      b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef
        ,      b2DebugDraw = Box2D.Dynamics.b2DebugDraw
        ,      b2Fixture = Box2D.Dynamics.b2Fixture
        ,      b2AABB = Box2D.Collision.b2AABB
        ,      b2Color = Box2D.Common.b2Color;
var SCALE = 30;
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
function init() {
world = new b2World(new b2Vec2(0,10), true);
var debugDraw = new b2DebugDraw();
        debugDraw.SetSprite ( document.getElementById ("canvas").getContext ("2d"));
        debugDraw.SetDrawScale(SCALE);     //define scale
        debugDraw.SetFillAlpha(0.3);    //define transparency
        debugDraw.SetLineThickness(1.0);
        debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
        world.SetDebugDraw(debugDraw);
initGame();
window.setInterval(update,1000/60);
}
window.setInterval(removeObjScheduledForRemoval, 1000/90);
document.body.addEventListener("keydown", keyset, false);
var moveLeft, moveRight;
function keyset(evt){
//37: "left", 39: "right"
if(evt.keyCode == 37){
moveLeft = 1;
moveRight = 0;
}
if(evt.keyCode == 39){
moveLeft = 0;
moveRight = 1;
}  
}
var bricksScheduledForRemoval = Array();
var index = -1;
function removeObjScheduledForRemoval()
{
for(var i = 0; i <= index; i++){
world.DestroyBody(bricksScheduledForRemoval[i]);
bricksScheduledForRemoval[i] = null;
}
bricksScheduledForRemoval = Array();
index = -1;
}
function update() {
world.Step(1 / 60, 10, 10);
        world.ClearForces();
        context.clearRect(0, 0, canvas.width, canvas.height);
world.DrawDebugData(); // comment to disable debugdraw

//detect collision
for (var c = world.GetContactList() ; c ; c = c.GetNext()){
var body1 = c.GetFixtureA().GetBody();
      var body2 = c.GetFixtureB().GetBody();
if((body1.GetUserData().name == "Bat" && body2.GetUserData().name == "Ball") ||(body1.GetUserData().name == "Ball" && body2.GetUserData().name == "Bat")){
var ImpulseVec = new b2Vec2((Math.random() * 3), - (Math.random() * 5));
var point = new b2Vec2((body2.GetWorldCenter().x),(body2.GetWorldCenter().y));
body2.ApplyImpulse(ImpulseVec, point);
}
for (var i = 0; i < 3 ; i = i + 1)
{
  for(var j = 0; j <= 9 ; j = j + 1)
  {
  if(body1.GetUserData().name == ("Brick" + j + i) && body2.GetUserData().name == "Ball") {
bricksScheduledForRemoval[++index] = body1;
}else if(body1.GetUserData().name == "Ball" && body2.GetUserData().name == ("Brick" + j + i)){
bricksScheduledForRemoval[++index] = body2;
}
    }
}
}

// world redraw
for (b = world.GetBodyList() ; b; b = b.GetNext()) {
// draw the static world objects
if (b.GetType() == b2Body.b2_staticBody && b.GetUserData() != null){
updateSkin(b);
}
if (b.GetType() == b2Body.b2_dynamicBody && b.GetUserData() != null) {
if(b.GetUserData().name == "Bat"){
if(moveLeft){
moveLeft = 0;
var ImpulseVec = new b2Vec2(-30,0);
var point = new b2Vec2((b.GetWorldCenter().x ), (b.GetWorldCenter().y ));
b.ApplyImpulse(ImpulseVec, point);
}
if(moveRight){
moveLeft = 0;
var ImpulseVec = new b2Vec2(30,0);
var point = new b2Vec2((b.GetWorldCenter().x ), (b.GetWorldCenter().y ));
b.ApplyImpulse(ImpulseVec, point);
}
}
updateSkin(b);
}
}
}

baseclass.js
- Generic class for creation Box2D objects
- handles Box2D world to Canvas mapping


function createRigidBody(propList){
var fixDef = new b2FixtureDef;
        fixDef.density = propList.density;
        fixDef.friction = propList.friction;
        fixDef.restitution = propList.restitution;
        var bodyDef = new b2BodyDef;
if(propList.type == "StaticBody"){
    bodyDef.type = b2Body.b2_staticBody;
}
if(propList.type == "DynamicBody"){
bodyDef.type = b2Body.b2_dynamicBody;
}
if(propList.shape == "Box"){
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(propList.width, propList.height);
}
if(propList.shape == "Sphere"){
fixDef.shape = new b2CircleShape(propList.width)
}
bodyDef.position.x = propList.x;
bodyDef.position.y = propList.y;
rbData = new Object();
rbData.name = propList.name;
rbData.width = propList.width;
rbData.height = propList.height;
rbData.skin = propList.skin;
bodyDef.userData = rbData;
var rigidbody = world.CreateBody(bodyDef).CreateFixture(fixDef);
return rigidbody;
}

function updateSkin(b)
{
var pos = b.GetPosition();
context.save();
context.translate(b.GetWorldCenter().x * SCALE,b.GetWorldCenter().y * SCALE);
context.rotate(b.GetAngle());
width = b.GetUserData().width * SCALE;
height = b.GetUserData().height * SCALE;
context.drawImage(b.GetUserData().skin, -width, -height, (width)*2, (height)*2);


    context.restore();
}

gameDemo.js
- Creationg of the Game objects



function initGame(){
initBox();
initBat();
initBricks();
initBall();
}
function initGame2(){
initBox();
initPendullum();
initBasket();
}
function initBox(){
propertyList = new Object();
propertyList.density = 1.0;
propertyList.friction = 0.5;
propertyList.restitution = 0.1;
propertyList.x = 10;
propertyList.y = 14;
propertyList.width = 10;
propertyList.height = 1;
propertyList.type = "StaticBody";
propertyList.shape = "Box"
var image = new Image();
image.src = "image/border.png";
propertyList.skin = image;
propertyList.name = "Ground";
ground = createRigidBody(propertyList);
propertyList.x = 10;
propertyList.y = 0;
propertyList.width = 10;
propertyList.height = 0.3;
propertyList.name = "Roof";
roof = createRigidBody(propertyList);
propertyList.x = 0;
propertyList.y = 0;
propertyList.width = 0.4;
propertyList.height = 14;
propertyList.name = "rwall";
rwall = createRigidBody(propertyList);
propertyList.x = 20;
propertyList.y = 0;
propertyList.name = "lwall";
lwall = createRigidBody(propertyList);
}

function initBat(){
//create Basket
propertyList = new Object();
propertyList.density = 10.0;
propertyList.friction = 0.5;
propertyList.restitution = 0.2;
propertyList.x = 10;
propertyList.y = 13;
propertyList.width = 1.5;
propertyList.height = .8;
propertyList.type = "DynamicBody";
propertyList.shape = "Box"
var image = new Image();
image.src = "image/moneky.png";
propertyList.skin = image;
propertyList.name = "Bat";
bat = createRigidBody(propertyList);
}
function initBall(){
propertyList = new Object();
propertyList.density = 1.0;
propertyList.friction = 0.5;
propertyList.restitution = 0.1;
propertyList.x = 10.0;
propertyList.y = 5.0;
propertyList.width = 0.5;
propertyList.height = 0.5;
propertyList.type = "DynamicBody";
propertyList.shape = "Sphere"
var image = new Image();
image.src = "image/ball.png";
propertyList.skin = image;
propertyList.name = "Ball";
pendullumball = createRigidBody(propertyList);
}

function initBricks() {
propertyList = new Object();
propertyList.density = 10.0;
propertyList.friction = 0.5;
propertyList.restitution = 0.2;
propertyList.width = 0.7;
propertyList.height = 0.5;
propertyList.type = "StaticBody";
propertyList.shape = "Box"
var image = new Image();
image.src = "image/banana.png";
propertyList.skin = image;
var x = 1, y = 1;
for (var i = 0; i < 3 ; i = i + 1)
{
for(var j = 0; j <= 9 ; j = j + 1)
{
propertyList.x = x;
propertyList.y = y;
propertyList.name = "Brick" + j + i;
createRigidBody(propertyList);
x = x + 2;
}
y = y + 0.9;
x = 1;
}
}

Yaeeee my short term project is completed. Now on we march to bigger goals :D


The game doesn't end when the monkey drops the ball since i suck at playing it :)

Reference -
Removing bodies safely

Saturday, July 21, 2012

Box2D - Revolute joint Skimming

Joins are such buggers, I have made a simple example for Revolute joint.
Joints are used to constraint two bodies together in various fashions. Revolute joint is a one degree joint, an anchor point around which the bodies rotate like a door hinge or a pendulum.
Joint definitions are used to construct the joints.
               e.g.,  jointDef.Initialize(box1, box2, refPoint);
here, refPoint is the anchor point where the bodies, box1 and box2 are joined.
Also,
localAnchor1, localAnchor2 - Anchor is a point on each body. It is the location around which the bodies must interact. Depending on the joint type, this point will be the center of rotation or the locations to keep a certain distance apart. In Revolute joint it is the rotation angle.

referenceAngle - The reference angle allows you to say what the 'joint angle' between the bodies is at the time of creation. In  Revolute joint the body2 angle minus body1 angle in the reference state (radians).
enableLimit, lowerAngle, upperAngle - Are the joint limits. It places a restriction on how far the bodies can rotate or slide.
enableMotor, maxMotorTorque,  motorSpeed - This enables you to apply torque or angular impulse to rotate to control the movement of the bodies. 
<html>
   <head>
      <title>Box2dWeb</title>
   </head>
   <body>
      <canvas id="canvas" width="600" height="400" style="background-color:#333333;" ></canvas>
   </body>
   <script type="text/javascript" src="libs/Box2dWeb-2.1.a.3.min.js"></script>
   <script type="text/javascript">
        var    b2Vec2 = Box2D.Common.Math.b2Vec2
        ,      b2BodyDef = Box2D.Dynamics.b2BodyDef
        ,      b2Body = Box2D.Dynamics.b2Body
        ,      b2FixtureDef = Box2D.Dynamics.b2FixtureDef
        ,      b2World = Box2D.Dynamics.b2World
        ,      b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
        ,      b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
        ,      b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef
        ,      b2DistanceJointDef = Box2D.Dynamics.Joints.b2DistanceJointDef
        ,      b2RopeJointDef = Box2D.Dynamics.Joints.b2RopeJointDef
        ,      b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef
        ,      b2DebugDraw = Box2D.Dynamics.b2DebugDraw
        ,      b2Fixture = Box2D.Dynamics.b2Fixture
        ,      b2AABB = Box2D.Collision.b2AABB
        ,      b2Color = Box2D.Common.b2Color;

        var world = new b2World(new b2Vec2(0,10), true);

//create the ground
var groundDef = new b2BodyDef;
        groundDef.type = b2Body.b2_staticBody;
        groundDef.position.Set(10,14);
        var fixDef1 = new b2FixtureDef;
        fixDef1.shape = new b2PolygonShape;
        fixDef1.shape.SetAsBox(10,1);
        var ground = world.CreateBody(groundDef);
        ground.CreateFixture(fixDef1);

        //box1
        var box1Def = new b2BodyDef;
        box1Def.type = b2Body.b2_staticBody;
        box1Def.position.Set(10,5);
        var fixDef2 = new b2FixtureDef;
        fixDef2.density = 10.0;
        fixDef2.friction = 0.5;
        fixDef2.restitution = .5;
        fixDef2.shape = new b2PolygonShape;
        fixDef2.shape.SetAsBox(.3,.3);
        var box1 = world.CreateBody(box1Def);
        box1.CreateFixture(fixDef2);

        //box2
        var box2Def = new b2BodyDef;
        box2Def.type = b2Body.b2_dynamicBody;
        box2Def.position.Set(5,10);
        var fixDef3 = new b2FixtureDef;
        fixDef3.density = 10.0;
        fixDef3.friction = 0.5;
        fixDef3.restitution = .2;
        fixDef3.shape = new b2PolygonShape;
        fixDef3.shape.SetAsBox(.3,.3);
        var box2 = world.CreateBody(box2Def);
        box2.CreateFixture(fixDef3);

        //Revolute joint
  var jointDef = new b2RevoluteJointDef();
jointDef.Initialize(box1, box2, box1.GetWorldCenter());
        world.CreateJoint(jointDef);

        var debugDraw = new b2DebugDraw();
        debugDraw.SetSprite ( document.getElementById ("canvas").getContext ("2d"));
        debugDraw.SetDrawScale(30);     //define scale
        debugDraw.SetFillAlpha(0.3);    //define transparency
        debugDraw.SetLineThickness(1.0);
        debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
        world.SetDebugDraw(debugDraw);

        window.setInterval(update,1000/60);
        function update() {
            world.Step(1 / 60, 10, 10);
            world.DrawDebugData();
            world.ClearForces();
        };
   </script>
</html>
A joint with respect to a reference point, can be made as follows
        // Revolute joint
var refPoint = new b2Vec2(5,5);
var jointDef = new b2RevoluteJointDef();
jointDef.Initialize(box1, box2, refPoint);
        world.CreateJoint(jointDef);

Reference,
http://www.iforce2d.net/b2dtut/joints-revolute
http://www.box2dflash.org/docs/2.0.2/reference/Box2D/Dynamics/Joints/b2RevoluteJointDef.html
http://www.emanueleferonato.com/2009/04/06/two-ways-to-make-box2d-cars/

Thursday, July 19, 2012

Box2D collision

This is a continuation from my previous post, I am still on my quest to create a Pingpong game, I will assume that we have all the Images with the physics object rendered in the world, now in order to make it into a game, there are two things,
1. Event Handlers
2. Collision Detection

Event Handlers
I have added my EventListener to the document Class. Every time an event occurs I record it in the global variables moveLeft, moveRight, this is done in keyset(). I use these variable states in the GameLoop to update the Box2D objects.
Alternatively you can use the js-hotkeys jQuery plugin for hooking the keyboard events.


var moveLeft, moveRight;
document.addEventListener("mousedown", handleMouseClick, true);
document.body.addEventListener("keydown", keyset, false);
function handleMouseClick(evt) {
}
function keyset(evt){
        //37: "left", 39: "right"
if(evt.keyCode == 37){
moveLeft = 1;
moveRight = 0;
}
if(evt.keyCode == 39){
moveLeft = 0;
moveRight = 1;
}  
}

Collision Detection
I am doing Collision Detection in the gameLoop, There are alternative ways of doing this, 2weeks into Box2D, this seems the easiest way to do it right now, will post alternatives soon.

The code detects collision between "Ball" and "Bat" and apply's a negative Y force on the ball to make it bounce off the bat in the opposite direction.
In Box2D it's common to think of bodies colliding with each other, but in reality its the fixtures which are used to detect when a collision occurs. Information about a collision is contained in the b2Contact object.

You can look at all the contacts in the world anytime by checking the world's contact list and this is the method that i have used in the following code.

...
//detect collision
 for (var c = world.GetContactList() ; c ; c = c.GetNext()){
var body1 = c.GetFixtureA().GetBody();
               var body2 = c.GetFixtureB().GetBody();
if(body1.GetUserData().name == "Bat" && body2.GetUserData().name == "Ball"){
var force = new b2Vec2(200,-(Math.random())*200000);
var point = new b2Vec2(body2.GetWorldCenter().x * SCALE,body2.GetWorldCenter().y * SCALE);
body2.ApplyForce(force, point);
}
if(body1.GetUserData().name == "Ball" && body2.GetUserData().name == "Bat"){
var force = new b2Vec2(200,-(Math.random())*200000);
var point = new b2Vec2(body1.GetWorldCenter().x * SCALE,body1.GetWorldCenter().y * SCALE);
body1.ApplyForce(force, point);
}
}
....

Alternatively you can look at the contacts for a single body as follows
for (var edge = b.GetContactList() ; edge  ; edge  = edge .GetNext()){

...
}
The other method for collision detection is using the Contact Listeners(will go into this in another blog)

Putting the GameLoop Together
In the game loop Iam taking care of
1. Redrawing the canvas
2. Event Handling, if moveLeft is set, then apply force on the "Bat" object to move it along the x-axis to the left, similarly in the opposite direction if the moveRight is set.
3. Collision Detection to make the ball bounce of the bat when it hits it. 


function gameLoop() {
        world.Step(1 / 60, 8, 3);
        world.ClearForces();
        context.clearRect(0, 0, canvas.width, canvas.height);
world.DrawDebugData();
//detect collision
 for (var c = world.GetContactList() ; c ; c = c.GetNext()){
var body1 = c.GetFixtureA().GetBody();
              var body2 = c.GetFixtureB().GetBody();
if(body1.GetUserData().name == "Bat" && body2.GetUserData().name == "Ball"){
var force = new b2Vec2(200,-(Math.random())*200000);
var point = new b2Vec2(body2.GetWorldCenter().x * SCALE,body2.GetWorldCenter().y * SCALE);
body2.ApplyForce(force, point);
}
if(body1.GetUserData().name == "Ball" && body2.GetUserData().name == "Bat"){
var force = new b2Vec2(200,-(Math.random())*200000);
var point = new b2Vec2(body1.GetWorldCenter().x * SCALE,body1.GetWorldCenter().y * SCALE);
body1.ApplyForce(force, point);
}
}
for (b = world.GetBodyList() ; b; b = b.GetNext()) {
// draw the static world objects
if (b.GetType() == b2Body.b2_staticBody && b.GetUserData() != null){
updateSkin(b);
}
if (b.GetType() == b2Body.b2_dynamicBody && b.GetUserData() != null) {
if(b.GetUserData().name == "Bat"){
if(moveLeft){
moveLeft = 0;
var force = new b2Vec2(-200000,0);
var point = new b2Vec2(b.GetWorldCenter().x * SCALE,b.GetWorldCenter().y * SCALE);
b.ApplyForce(force, point);
}
if(moveRight){
moveRight = 0;
var force = new b2Vec2(200000,0);
var point = new b2Vec2(b.GetWorldCenter().x * SCALE,b.GetWorldCenter().y * SCALE);
b.ApplyForce(force, point);
}
}
updateSkin(b);
}
}
}

I realize I have too many things happening in my game loop and the same logic might not work in a game with too many objects. right now "Trial and Error" baby :)

Box2D - Creation of b2Body Objects

I am writing a pingpong game. I realized I was writing lots of repetitive code for creation of the Box2D body. I have tried to simplify it and  created a skeletal method called  createRigidBody(propList) that can be used to create Dynamic/Static, Box/Circle b2Body Objects. I am planning to add Polygons to it soon. The final idea is to make a RigidBody Class that will take care of all the permutations and combinations of creating a RigidBody in Box2D.

function createRigidBody(propList){
var fixDef = new b2FixtureDef;
fixDef.density = propList.density;
        fixDef.friction = propList.friction;
        fixDef.restitution = propList.restitution;
        var bodyDef = new b2BodyDef;
if(propList.type == "StaticBody"){
    bodyDef.type = b2Body.b2_staticBody;
}
if(propList.type == "DynamicBody"){
bodyDef.type = b2Body.b2_dynamicBody;
}
if(propList.shape == "Box"){
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(propList.width, propList.height);
}
if(propList.shape == "Sphere"){
fixDef.shape = new b2CircleShape(propList.width)
}
bodyDef.position.x = propList.x;
bodyDef.position.y = propList.y;
rbData = new Object();
rbData.name = propList.name;
rbData.width = propList.width;
rbData.height = propList.height;
rbData.skin = propList.skin;
bodyDef.userData = rbData;
world.CreateBody(bodyDef).CreateFixture(fixDef);
}


There are three parts to a b2Body
1. Fixture Definition - It defines the attributes of the object, such as density, friction, and restitution (bounciness).
2. Body Definition - It defines where in the world the object is, and if it is dynamic (reacts to things) or static.
3. Shape - it defines the actual 2D geometrical object, such as a circle/box or polygon. Right now this code handles only box/circle

Rendering the Box2D object on the Canvas


The Box2D object can be drawn on the canvas using the Debug2draw method like in the previous example, but that is only for debugging purpose. For gaming we need to render images on the canvas.
When creating the b2Body, i have attached the skin(image to be rendered) information to it for housekeeping purpose.
rbData.skin = propList.skin;   
..
bodyDef.userData = rbData;  

I am using the updateskin(b) method to redraw the canvas for each step in the game world. When Dynamic physics bodies move in the world the canvas has to be updated to make sure that the image is in the exact same position and of same size as the b2Body.


function updateSkin(b)
{
var pos = b.GetPosition();
context.save();
console.log(b.GetWorldCenter().x * SCALE, b.GetWorldCenter().y *SCALE);
context.translate(b.GetWorldCenter().x * SCALE,b.GetWorldCenter().y * SCALE);
context.rotate(b.GetAngle());
width = b.GetUserData().width * SCALE;
height = b.GetUserData().height * SCALE;
context.drawImage(b.GetUserData().skin, -width, -height, (width)*2, (height)*2);
        context.restore();
}
The updateSkin(b) can be a little tricky since the Canvas and the Box2D world have different co-ordinate systems. Also Box2D uses MKS (meters, kilograms, and seconds) units and radians for angles so you have to come up with a conversion factor for mapping from Box2D to Canvas.




Putting it to Together


Now using the above methods, you can create an Box2D object and render it on screen as follows,

function initGround() {
propertyList = new Object();
propertyList.density = 1.0;
propertyList.friction = 0.5;
propertyList.restitution = 0.2;
propertyList.x = 200;
propertyList.y = 390;
propertyList.width = 350;
propertyList.height = 10;
propertyList.type = "StaticBody";
propertyList.shape = "Box"
var image = new Image();
image.src = "image/brick.png";
propertyList.skin = image;
propertyList.name = "Ground";
createRigidBody(propertyList);
}

function gameLoop() {
    world.Step(1 / 60, 8, 3);
    world.ClearForces();
    context.clearRect(0, 0, canvas.width, canvas.height);
    for (b = world.GetBodyList() ; b; b = b.GetNext()) {
// draw the static world objects
 if (b.GetType() == b2Body.b2_staticBody && b.GetUserData() != null){
updateSkin(b);
}
if (b.GetType() == b2Body.b2_dynamicBody && b.GetUserData() != null) {
updateSkin(b);
}
    }
}


Monday, July 16, 2012

Getting started with Box2D

Was playing with idea of using physics in my game, and found this amazing open source physics gaming engine called Box2D. They have ports of the game engine for javascript and AS3. The only problem being there isn't much documentation out there for js.
I will be using the Box2D-Web port of Box 2D since this version is kept up to date.

here is the code,

    var world;
    var   b2Vec2 = Box2D.Common.Math.b2Vec2
             ,    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
            ,   b2Vec2 = Box2D.Common.Math.b2Vec2
            ;
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var mouseX, mouseY, isMouseDown; 
    function init() {
        world = new b2World(
               new b2Vec2(0, 10)    //gravity
            ,  true                 //allow sleep
        );
        setupWorld() ;
        //setup debug draw
        var debugDraw = new b2DebugDraw();
            debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
            debugDraw.SetDrawScale(1.0);
            debugDraw.SetFillAlpha(0.3);
            debugDraw.SetLineThickness(1.0);
            debugDraw.SetFlags(b2DebugDraw.e_shapeBit  |  b2DebugDraw.e_jointBit);       
        world.SetDebugDraw(debugDraw);
        window.setInterval(update, 1000 / 60);
      };
      function setupWorld() {
            setupGround();
           addBouncingBall();
      }
      function setupGround() {
           var fixDef = new b2FixtureDef;
         fixDef.density = 1.0;
         fixDef.friction = 0.5;
         fixDef.restitution = 0.2;
         var bodyDef = new b2BodyDef;
         //create ground
         bodyDef.type = b2Body.b2_staticBody;
         bodyDef.position.x = 300;
         bodyDef.position.y = 400;
         fixDef.shape = new b2PolygonShape;
         fixDef.shape.SetAsBox(290, 10);
         world.CreateBody(bodyDef).CreateFixture(fixDef);
      }
      function addBouncingBall() {     
         var fixDef = new b2FixtureDef;
         fixDef.density = 1.0;
         fixDef.friction = 1.0;
         fixDef.restitution = 0.1;
         var bBallbodyDef = new b2BodyDef;
         bBallbodyDef.type = b2Body.b2_dynamicBody;
         fixDef.shape = new b2CircleShape(Math.random() + 30);
         bBallbodyDef.position.x = Math.random() * 300;
         bBallbodyDef.position.y = Math.random() * 300;   
         obj = new Object();
         obj.val = "Ball";
         bBallbodyDef.userData = obj;
         bBallBody = world.CreateBody(bBallbodyDef).CreateFixture(fixDef);
      };
      document.addEventListener("mousedown", handleMouseMove, true);
      function handleMouseMove(e) {
          isMouseDown = true;
          mouseX = e.clientX;
          mouseY = e.clientY;
      };
      function redraw_world(){
          if(isMouseDown)
          {
              for (b = world.GetBodyList() ; b; b = b.GetNext()) {
                  if( b.GetUserData() != null)
                  {
                      if(b.GetUserData().val == "Ball")
                      {
                            var force = new b2Vec2(4000,4000);
                            var point = new b2Vec2(100,100);
                            b.ApplyForce(force, point);      
                      }
                  }           
              }
          isMouseDown = false;
           }
      };
      function update() {
          redraw_world();
           world.Step(1 / 2, 5, 5);
           world.DrawDebugData();
           world.ClearForces();
      };

Most of the code above is from online tutorials and I am sure you have come across it already if you are interested in Box2D, check the links in the Reference if you haven't. Well the bummers in this code was figuring out which body should be moved. Online there are a zillion questions posted on usage of GetUserData()/SetUserData(), even i posted one, but no definite answer for js

Initially i was doing it as
function addBouncingBall(){
     ....
     bBallbodyDef.userData = obj;
     bBallBody = world.CreateBody(bBallbodyDef).CreateFixture(fixDef);  // note: bBallBody is declared global
     bBallBody.SetUserData(obj);
    ....

So
redraw_world()
{
      ...  
      console.log(bBallBody.GetsUserData().val);  // Was on the money, and it printed "Ball" to the console
      for (b = world.GetBodyList() ; b; b = b.GetNext()) {
          ...
          console.log(b.GetsUserData());  //returned null everytime.
      ...
 }

Finally after much Googling and ogling, found out that the userdata can be assigned to the bodydef as follows, and that solved most  of the problems,
bBallbodyDef.userData = obj;

There is lack of documentation for javascript port of Box2D, but you can refer the documentation for Box2dFlash. They are almost the same.


Resources:
Box2DWeb