API Docs for: 0.1.0
Show:

File: src/zerk/class/game/engine/physics.js

/**
 * Physics engine interface
 * 
 * Box2dWeb implementation.
 * 
 * @class zerk.game.engine.physics
 * @extends zerk.observable
 * @module zerk
 */
/*
 * TODO Give the Box2dWeb related properties their real types 
 */
zerk.define({
	
	name: 'zerk.game.engine.physics',
	extend: 'zerk.observable',
	require: [
		'zerk.game.engine.physics.body',
		'zerk.game.engine.physics.fixture.circle',
		'zerk.game.engine.physics.fixture.polygon',
		'zerk.game.engine.physics.fixture.rectangle',
		'zerk.game.engine.physics.joint.distance',
		'zerk.game.engine.physics.joint.revolute'
	]
	
},{
	
	/**
	 * Physics configuration
	 * 
	 * @property _config
	 * @type Object 
	 * @protected
	 */
	_config: null,
	
	/**
	 * Game engine
	 * 
	 * @property _engine
	 * @type zerk.game.engine
	 * @protected
	 */
	_engine: null,
	
	/**
	 * Box2D world
	 * 
	 * @property _world
	 * @type Box2D.Dynamics.b2World
	 * @protected
	 */
	_world: null,
	
	/**
	 * Handle for the mouse joint
	 * 
	 * @property _mouseJoint
	 * @type Object
	 * @protected
	 */
	_mouseJoint: null,
	
	/**
	 * Stores a vector for the mouse joint function
	 * 
	 * @property _mouseJointVec
	 * @type Object
	 * @protected
	 */
	_mouseJointVec: null,
	
	/**
	 * Stores the last selected body
	 * 
	 * @property _selectedBody
	 * @type Object
	 * @protected
	 */
	_selectedBody: null,
	
	/**
	 * List of bodies to be destroyed after current simulation tick
	 * 
	 * @property _destroyBodyList
	 * @type Array
	 * @protected
	 */
	_destroyBodyList: null,
	
	/**
	 * List of joints to be destroyed after current simulation tick
	 * 
	 * @property __destroyJointList
	 * @type Array
	 * @protected
	 */
	_destroyJointList: null,
	
	/**
	 * Shortcut for Box2D.Common.Math.b2Vec2
	 * 
	 * @property _b2Vec2
	 * @type Object
	 * @protected
	 */
	_b2Vec2: Box2D.Common.Math.b2Vec2,
	
	/**
	 * Shortcut for Box2D.Collision.b2AABB
	 * 
	 * @property _b2AABB
	 * @type Object
	 * @protected
	 */
	_b2AABB: Box2D.Collision.b2AABB,
	
	/**
	 * Shortcut for Box2D.Dynamics.b2BodyDef
	 * 
	 * @property _b2BodyDef
	 * @type Object
	 * @protected
	 */
	_b2BodyDef: Box2D.Dynamics.b2BodyDef,
	
	/**
	 * Shortcut for Box2D.Dynamics.b2Body
	 * 
	 * @property _b2Body
	 * @type Object
	 * @protected
	 */
	_b2Body: Box2D.Dynamics.b2Body,
	
	/**
	 * Shortcut for Box2D.Dynamics.b2FixtureDef
	 * 
	 * @property _b2FixtureDef
	 * @type Object
	 * @protected
	 */
	_b2FixtureDef: Box2D.Dynamics.b2FixtureDef,
	
	/**
	 * Shortcut for Box2D.Dynamics.b2World
	 * 
	 * @property _b2World
	 * @type Object
	 * @protected
	 */
	_b2World: Box2D.Dynamics.b2World,
	
	/**
	 * Shortcut for Box2D.Collision.Shapes.b2PolygonShape
	 * 
	 * @property _b2PolygonShape
	 * @type Object
	 * @protected
	 */
	_b2PolygonShape: Box2D.Collision.Shapes.b2PolygonShape,
	
	/**
	 * Shortcut for Box2D.Collision.Shapes.b2CircleShape
	 * 
	 * @property _b2CircleShape
	 * @type Object
	 * @protected
	 */
	_b2CircleShape: Box2D.Collision.Shapes.b2CircleShape,
	
	/**
	 * Shortcut for Box2D.Dynamics.b2DebugDraw
	 * 
	 * @property _b2DebugDraw
	 * @type Object
	 * @protected
	 */
	_b2DebugDraw: Box2D.Dynamics.b2DebugDraw,
	
	/**
	 * Shortcut for Box2D.Dynamics.Joints.b2MouseJointDef
	 * 
	 * @property _b2MouseJointDef
	 * @type Object
	 * @protected
	 */
	_b2MouseJointDef: Box2D.Dynamics.Joints.b2MouseJointDef,
	
	/**
	 * Shortcut for Box2D.Dynamics.Joints.b2RevoluteJointDef
	 * 
	 * @property _b2RevoluteJointDef
	 * @type Object
	 * @protected
	 */
	_b2RevoluteJointDef: Box2D.Dynamics.Joints.b2RevoluteJointDef,
	
	/**
	 * Shortcut for Box2D.Dynamics.Joints.b2DistanceJointDef
	 * 
	 * @property _b2DistanceJointDef
	 * @type Object
	 * @protected
	 */
	_b2DistanceJointDef: Box2D.Dynamics.Joints.b2DistanceJointDef,
	
	/**
	 * Shortcut for Box2D.Dynamics.b2ContactListener
	 * 
	 * @property _b2ContactListener
	 * @type Object
	 * @protected
	 */
	_b2ContactListener: Box2D.Dynamics.b2ContactListener,
	
	/**
	 * Class constructor
	 * 
	 * @method init
	 * @param {zerk.game.engine} engine Game engine
	 */
	init: function(engine) {
		
		zerk.parent('zerk.game.engine.physics').init.apply(
			this,
			arguments
		);
		
		this._engine=engine;
		
		this._destroyBodyList=[];
			
		this._destroyJointList=[];
		
		// Set default config values
		this._config={
			gravityX: 0,
			gravityY: 30,
			debugDraw: false
		};
		
		// Get a handle in the registry
		this._config=this._engine.registry.register(
			'physics',
			this._config
		);
		
		this._world=new this._b2World(
			new this._b2Vec2(
				this._config.gravityX,
				this._config.gravityY
			), // Setup gravity
			true // allow sleep
		);
		
		// Box2D debug draw
		
		if (this._config.debugDraw) {
			
			this.canvas=this._engine.dom.getCanvasPhysicsDebug();
			
			/*
			 * TODO Sync the view position with the game viewport
			 * 
			 * this.canvas.getContext("2d").translate(200,200);
			 */
			
			var debugDraw = new this._b2DebugDraw();
			debugDraw.SetSprite(this.canvas.getContext("2d"));
			debugDraw.SetDrawScale(30.0);
			debugDraw.SetFillAlpha(0.5);
			debugDraw.SetLineThickness(1.0);
			debugDraw.SetFlags(
				this._b2DebugDraw.e_shapeBit | this._b2DebugDraw.e_jointBit
			);
			
			this._world.SetDebugDraw(debugDraw);
			
		}
		
		this._addContactListener();
		
		this._log('Init');
		
	},
	
	/**
	 * Physics engine tick
	 * 
	 * @method tick
	 */
	tick: function() {
		
		this._processMouseJoint();
		
		/*
		 * TODO Make world step settings configurable
		 */
		
		// World step 
		this._world.Step(
			1/60, // Frame rate
			10, // Velocity iterations
			10 // Position iterations
		);
		
		if (this._config.debugDraw) {
			this._world.DrawDebugData();
		}
		
		this._world.ClearForces();
		
		this._cleanup();
		
	},
	
	/**
	 * Destroy bodies and joints schedules to be removed from the world
	 * 
	 * @method _cleanup
	 * @protected
	 */
	_cleanup: function() {
		
		for (var i=0;i<this._destroyJointList.length;i++) {
			
			this._destroyJoint(this._destroyJointList[i]);
			this._destroyJointList[i]=null;
			
		}
		
		this._destroyJointList=[];
		
		for (var i=0;i<this._destroyBodyList.length;i++) {
			
			this._destroyBody(this._destroyBodyList[i]);
			this._destroyBody[i]=null;
			
		}
		
		this._destroyBodyList=[];
		
	},
	
	/**
	 * Schedules a body to be destroyed after current simulation tick
	 * 
	 * @method scheduleDestroyBody
	 * @param {zerk.game.engine.entity} entity
	 * @param {zerk.game.engine.physics.body} body
	 */
	scheduleDestroyBody: function(entity,body) {
		
		this._destroyBodyList.push(body.physicsHandle);
		
	},
	
	/**
	 * Schedules a join to be destroyed after current simulation tick
	 * 
	 * @method scheduleDestroyJoint
	 * @param {zerk.game.engine.entity} entity
	 * @param {zerk.game.engine.physics.join} body
	 */
	scheduleDestroyJoint: function(entity,joint) {
		
		this._destroyJointList.push(joint.physicsHandle);
		
	},
	
	/**
	 * Destroys a body
	 * 
	 * @method _destroyBody
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @protected
	 */
	_destroyBody: function(physicsHandle) {
		
		if (this._world.IsLocked()) {
			
			console.error('Cannot destroy body, world is locked');
			
		}
		
		this._world.DestroyBody(physicsHandle);
		
	},
	
	/**
	 * Destroys a joint
	 * 
	 * @method _destroyJoint
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.joint} joint Joint
	 * @protected
	 */
	_destroyJoint: function(physicsHandle) {

		if (this._world.IsLocked()) {
			
			console.error('Cannot destroy joint, world is locked');
			
		}
		
		this._world.DestroyJoint(physicsHandle);
		
	},
	
	/**
	 * Create the physics engine representation for an entity
	 * 
	 * @method createPhysics
	 * @param {zerk.game.engine.entity} entity Entity
	 */
	createPhysics: function(entity) {
		
		for (var i=0;i<entity.bodies.length;i++) {
			
			this._createBody(entity,entity.bodies[i]);
			
		}
		
		for (var i=0;i<entity.joints.length;i++) {
			
			this._createJoint(entity,entity.joints[i]);
			
		}
		
	},
	
	/**
	 * Removes the physics engine representation for an entity
	 * 
	 * @method removePhysics
	 * @param {zerk.game.engine.entity} entity Entity
	 */
	removePhysics: function(entity) {
		
		for (var i=0;i<entity.joints.length;i++) {
			
			this.scheduleDestroyJoint(entity,entity.joints[i]);
			
		}
		
		for (var i=0;i<entity.bodies.length;i++) {
			
			this.scheduleDestroyBody(entity,entity.bodies[i]);
			
		}
		
	},
	
	/**
	 * Returns body at current cursor position if present
	 * 
	 * @method getBodyAtMouse
	 * @return {Object} Box2D body
	 */
	getBodyAtMouse: function() {
		
		this._mouseJointVec=new this._b2Vec2(
			this._engine.control.mouse.mouseX,
			this._engine.control.mouse.mouseY
		);
		
		var aabb=new this._b2AABB();
		
		aabb.lowerBound.Set(
			this._engine.control.mouse.mouseX-0.001,
			this._engine.control.mouse.mouseY-0.001
		);
		
		aabb.upperBound.Set(
			this._engine.control.mouse.mouseX+0.001,
			this._engine.control.mouse.mouseY+0.001
		);
		
		this._selectedBody=null;
		
		this._world.QueryAABB(this._getBodyAtMouseCallback,aabb);
		
		return this._selectedBody;
		
	},
	
	/**
	 * Returns an array of entities inside given area
	 * 
	 * @method getEntitiesInArea
	 * @param {Float} x1
	 * @param {Float} y1
	 * @param {Float} x2
	 * @param {Float} y2
	 * @return {Array} Entities inside given area
	 */
	getEntitiesInArea: function(x1,y1,x2,y2) {
		
		var aabb=new this._b2AABB();
		
		aabb.lowerBound.Set(x1,y1);
		aabb.upperBound.Set(x2,y2);
		
		var entitiesById={};
		
		this._world.QueryAABB(
			function (fixture) {
				
				var entity=fixture.GetBody().GetUserData()._entity;
				
				if (typeof entitiesById[entity.config.id]
				=='undefined') {
					
					entitiesById[entity.config.id]=entity;
					
				}
				
				return true;
				
			},
			aabb
		);
		
		var result=[];
		
		for (var id in entitiesById) {
			
			if (this._engine.world!=null) {
				
				if (this._engine.world.getEntityById(id)!=null) {
					
					result.push(entitiesById[id]);
					
				}
				
			}
			
		}
		
		return result;
	},
	
	/**
	 * Synchronize the physic simulation properties of an entity
	 * 
	 * @method syncEntityPhysics
	 * @param {zerk.game.engine.entity} entity Entity
	 */
	syncEntityPhysics: function(entity) {
		
		for (var index in entity.bodies) {
			
			if (entity.bodies[index].physicsHandle) {
				
				entity.config.bodies[entity.bodies[index].key].x=
					entity.bodies[index].physicsHandle.GetPosition().x;
					
				entity.config.bodies[entity.bodies[index].key].y=
					entity.bodies[index].physicsHandle.GetPosition().y;
					
				entity.config.bodies[entity.bodies[index].key].angle=
					entity.bodies[index].physicsHandle.GetAngle();
				
				if (entity.bodies[index].origin) {
					
					entity.config.x=
						entity.bodies[index].physicsHandle.GetPosition().x;
						
					entity.config.y=
						entity.bodies[index].physicsHandle.GetPosition().y;
					
				}
				
			}
			
		}
		
	},
	
	/**
	 * Dumps elements currently contained in physics simulation to console
	 * 
	 * @method dumpPhysicsData
	 */
	dumpPhysicsData: function() {
		
		var body=this._world.GetBodyList();
		
		console.log('--- Elements in physics simulation ---');
		
		while (body!=null) {
			
			var zerkBody=body.GetUserData();
			
			if (zerkBody) {
				
				console.log('BODY: '+zerkBody._entity.config.id);
				
			} else {
				
				console.log('UNKNOWN BODY',body);
				
			}
			
			body=body.GetNext();
			
		}
		
	},
	
	/**
	 * Contact begin event handler
	 * 
	 * @method _onContactBegin
	 * @param {Object} contact Contact information
	 * @protected
	 */
	_onContactBegin: function(contact) {
		
		var sourceFixture=contact.GetFixtureA().GetUserData();
		var targetFixture=contact.GetFixtureB().GetUserData();
		
		var sourceEntity=sourceFixture.body._entity;
		var targetEntity=targetFixture.body._entity;
		
		if (this._engine.world!=null) {
			
			/**
			 * Fires when two fixtures start colliding
			 * 
			 * @event contactbegin
			 * @param {zerk.game.engine.physics.fixture} sourceFixture Source fixture
			 * @param {zerk.game.engine.physics.fixture} targetFixture Target fixture
			 * @return {Boolean} Return false to Cancel bubble
			 */
			this.fireEvent(
				'contactbegin',
				sourceFixture,
				targetFixture
			);
			
			// Trigger entity events
			
			if (sourceEntity!=null) {
				
				sourceEntity.fireEvent(
					'contactbegin',
					sourceFixture,
					targetFixture
				);
				
			}
			
			if (targetEntity!=null) {
				
				targetEntity.fireEvent(
					'contactbegin',
					sourceFixture,
					targetFixture
				);
				
			}
			
		}
		
	},
	
	/**
	 * Contact end event handler
	 * 
	 * @method _onContactEnd
	 * @param {Object} contact Contact information
	 * @protected
	 */
	_onContactEnd: function(contact) {
		
		var sourceFixture=contact.GetFixtureA().GetUserData();
		var targetFixture=contact.GetFixtureB().GetUserData();
		
		var sourceEntity=sourceFixture.body._entity;
		var targetEntity=targetFixture.body._entity;
		
		if (this._engine.world!=null) {
			
			/**
			 * Fires when two fixtures stop colliding
			 * 
			 * @event contactend
			 * @param {zerk.game.engine.physics.fixture} sourceFixture Source 
			 * fixture
			 * @param {zerk.game.engine.physics.fixture} targetFixture Target 
			 * fixture
			 * @return {Boolean} Return false to Cancel bubble
			 */
			this.fireEvent(
				'contactend',
				sourceFixture,
				targetFixture
			);
			
			// Trigger entity events
			
			if (sourceEntity!=null) {
				
				sourceEntity.fireEvent(
					'contactend',
					sourceFixture,
					targetFixture
				);
				
			}
			
			if (targetEntity!=null) {
				
				targetEntity.fireEvent(
					'contactend',
					sourceFixture,
					targetFixture
				);
				
			}
			
		}
		
	},
	
	/**
	 * Contact post solve event handler
	 * 
	 * @method _onContactPostSolve
	 * @param {Object} contact Contact information
	 * @param {Object} impulse Impulse information
	 * @protected
	 */
	_onContactPostSolve: function(contact,impulse) {
		
		var sourceFixture=contact.GetFixtureA().GetUserData();
		var targetFixture=contact.GetFixtureB().GetUserData();
		
		var sourceEntity=sourceFixture.body._entity;
		var targetEntity=targetFixture.body._entity;
		
		if (this._engine.world!=null) {
			
			/**
			 * Fires before two fixtures start colliding
			 * 
			 * @event contactpostsolve
			 * @param {zerk.game.engine.physics.fixture} sourceFixture Source fixture
			 * @param {zerk.game.engine.physics.fixture} targetFixture Target fixture
			 * @param {Object} impulse
			 * @return {Boolean} Return false to Cancel bubble
			 */
			this.fireEvent(
				'contactpostsolve',
				sourceFixture,
				targetFixture,
				impulse
			);
			
			// Trigger entity events
			
			if (sourceEntity!=null) {
				
				sourceEntity.fireEvent(
					'contactpostsolve',
					sourceFixture,
					targetFixture,
					impulse
				);
				
			}
			
			if (targetEntity!=null) {
				
				targetEntity.fireEvent(
					'contactpostsolve',
					sourceFixture,
					targetFixture,
					impulse
				);
				
			}
			
		}
		
	},
	
	/**
	 * Contact pre solve event handler
	 * 
	 * @method _onContactPreSolve
	 * @param {Object} contact Contact information
	 * @param {Object} oldManifold
	 * @protected
	 */
	_onContactPreSolve: function(contact,oldManifold) {
		
		/*
		 * TODO Implement ContactPreSolve event
		 */
		
	},
	
	/**
	 * Registers the contact listener event handlers
	 * 
	 * @method _addContactListener
	 * @protected
	 */
	_addContactListener: function() {
		
		var listener=new this._b2ContactListener();
		
		var me=this;
		
		listener.BeginContact=function(contact) {
			
			me._onContactBegin(contact);
			
		};
		
		listener.EndContact=function(contact) {
			
			me._onContactEnd(contact);
			
		};
		
		listener.PostSolve=function(contact,impulse) {
			
			me._onContactPostSolve(contact,impulse);
			
		};
		
		listener.PreSolve=function(contact,oldManifold) {
			
			me._onContactPreSolve(contact,oldManifold);
			
		};
		
		this._world.SetContactListener(listener);
		
	},
	
	/**
	 * Creates a body
	 * 
	 * @method _createBody
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @return {Object} Body handle
	 * @protected
	 */
	_createBody: function(entity,body) {
		
		var bodyDef=new this._b2BodyDef;
		
		if (typeof body.moveable!='undefined' && body.moveable) {
			
			bodyDef.type=this._b2Body.b2_dynamicBody;
			
		} else if (typeof body.kinematic!='undefined' && body.kinematic) {
			
			bodyDef.type=this._b2Body.b2_kinematicBody;
			
		} else {
			
			bodyDef.type=this._b2Body.b2_staticBody;
			
		}
		
		var position=entity.config.bodies[body.key];
		
		bodyDef.position.x=position.x;
		bodyDef.position.y=position.y;
		bodyDef.angle=position.angle*Math.PI*2;
		bodyDef.fixedRotation=body.fixedRotation;
		bodyDef.userData=body;
		
		body.physicsHandle=this._world.CreateBody(bodyDef);
		
		// Create all fixtures of the body
		
		for (var index in body.fixtures) {
			
			body.physicsHandle.CreateFixture(
				this._createFixture(
					entity,
					body,
					body.fixtures[index]
				)
			);
			
		}
		
		return body.physicsHandle;
	},
	
	/**
	 * Delegate method for fixture creation
	 * 
	 * @method _createFixture
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body 
	 * @param {zerk.game.engine.physics.fixture} fixture Fixture
	 * @return {Object} Fixture handle
	 * @protected
	 */
	_createFixture: function(entity,body,fixture) {
		
		switch (fixture.shape) {
			case 'box':
				return this._createFixtureRectangle(
					entity,
					body,
					fixture
				);
				break;
			case 'circle':
				return this._createFixtureCircle(
					entity,
					body,
					fixture
				);
				break;
			case 'polygon':
				return this._createFixturePolygon(
					entity,
					body,
					fixture
				);
		}
		
	},
	
	/**
	 * Creates a rectangle shaped fixture
	 * 
	 * @method _createFixtureRectangle
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @param {zerk.game.engine.physics.fixture} fixture Fixture
	 * @return {Object} Fixture handle
	 * @protected
	 */
	_createFixtureRectangle: function(
		entity,
		body,
		fixture
	) {
		
		var fixDef=new this._b2FixtureDef;
		
		if (fixture.categoryBits!=null) {
			
			fixDef.filter.categoryBits=fixture.categoryBits;
			
		}
		if (fixture.maskBits!=null) {
			
			fixDef.filter.maskBits=fixture.maskBits;
			
		}
		
		fixDef.density=fixture.density;
		fixDef.friction=fixture.friction;
		fixDef.restitution=fixture.restitution;
		fixDef.isSensor=fixture.isSensor;
		fixDef.userData=fixture;
		
		fixDef.shape=new this._b2PolygonShape;
		
		var center=new this._b2Vec2(fixture.x, fixture.y);
		
		fixDef.shape.SetAsOrientedBox(
			fixture.width/2,
			fixture.height/2,
			center,
			fixture.angle
		);
		
		return fixDef;
		
	},
	
	/**
	 * Creates a circle shaped fixture
	 * 
	 * @method _createFixtureCircle
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @param {zerk.game.engine.physics.fixture} fixture Fixture
	 * @return {Object} Fixture handle
	 * @protected
	 */
	_createFixtureCircle: function(
		entity,
		body,
		fixture
	) {
		
		var fixDef=new this._b2FixtureDef;
		
		if (fixture.categoryBits!=null) {
			
			fixDef.filter.categoryBits=fixture.categoryBits;
			
		}
		if (fixture.maskBits!=null) {
			
			fixDef.filter.maskBits=fixture.maskBits;
			
		}
		
		fixDef.density=fixture.density;
		fixDef.friction=fixture.friction;
		fixDef.restitution=fixture.restitution;
		fixDef.isSensor=fixture.isSensor;
		fixDef.userData=fixture;
		
		fixDef.shape=new this._b2CircleShape(
			fixture.radius
		);
		
		fixDef.shape.SetLocalPosition(
			new this._b2Vec2(fixture.x, fixture.y)
		);
		
		return fixDef;
		
	},
	
	/**
	 * Creates a polygon shaped fixture
	 * 
	 * @method _createFixturePolygon
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @param {zerk.game.engine.physics.fixture} fixture Fixture
	 * @return {Object} Fixture handle
	 * @protected
	 */
	_createFixturePolygon: function(
		entity,
		body,
		fixture
	) {
		
		var fixDef=new this._b2FixtureDef;
		
		if (fixture.categoryBits!=null) {
			
			fixDef.filter.categoryBits=fixture.categoryBits;
			
		}
		if (fixture.maskBits!=null) {
			
			fixDef.filter.maskBits=fixture.maskBits;
			
		}
		
		fixDef.density=fixture.density;
		fixDef.friction=fixture.friction;
		fixDef.restitution=fixture.restitution;
		fixDef.isSensor=fixture.isSensor;
		fixDef.userData=fixture;
		
		fixDef.shape=new this._b2PolygonShape;
		
		// Create array of vertice instances
		
		var arr=[];
		
		for (var i=0;i<fixture.vertices.length;i++) {
			arr.push(
				new this._b2Vec2(
					fixture.vertices[i][0],
					fixture.vertices[i][1]
				)
			);
		}
		
		fixDef.shape.SetAsArray(arr, arr.length);
		
		return fixDef;
		
	},
	
	/**
	 * Delgate method for joint creation
	 * 
	 * @method _createJoint
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.joint} joint Joint
	 * @return {Object} Joint handle
	 * @protected
	 */
	_createJoint: function(entity,joint) {
		
		switch (joint.type) {
			case 'distance':
				return this._createJointDistance(entity,joint);
			case 'revolute':
				return this._createJointRevolute(entity,joint);
		}
		
	},
	
	/**
	 * Creates a distance joint
	 * 
	 * @method _createJointDistance
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.joint} joint Joint
	 * @return {Object} Joint handle
	 * @protected
	 */
	_createJointDistance: function(entity,joint) {
		
		var bodySource=entity.getBody(joint.source).physicsHandle;
		var bodyTarget=entity.getBody(joint.target).physicsHandle;
		
		var jointDef=new this._b2DistanceJointDef();
		
		jointDef.collideConnected=joint.collideConnected;
		jointDef.frequencyHz=joint.frequencyHz;
		jointDef.dampingRatio=joint.dampingRatio;
		
		// Create anchor vectors
		var anchorSource=bodySource.GetWorldCenter();
		var anchorTarget=bodyTarget.GetWorldCenter();
		
		jointDef.Initialize(
			bodySource,
			bodyTarget,
			anchorSource,
			anchorTarget
		);
		
		// Set local anchors
		jointDef.localAnchorA.Set(
			joint.anchorSourceX,
			joint.anchorSourceY
		);
		jointDef.localAnchorB.Set(
			joint.anchorTargetX,
			joint.anchorTargetY
		);
		
		return this._world.CreateJoint(jointDef);
		
	},
	
	/**
	 * Creates a revolute join
	 * 
	 * @method _createJointRevolute
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.joint} joint Joint
	 * @return {Object} Joint handle
	 * @protected
	 */
	_createJointRevolute: function(entity,joint) {
		
		var bodySource=entity.getBody(joint.source).physicsHandle;
		var bodyTarget=entity.getBody(joint.target).physicsHandle;
		
		var jointDef=new this._b2RevoluteJointDef();
		
		jointDef.enableLimit=joint.enableLimit;
		jointDef.lowerAngle=joint.lowerAngle;
		jointDef.upperAngle=joint.upperAngle;
		jointDef.enableMotor=joint.enableMotor;
		jointDef.motorSpeed=joint.motorSpeed;
		jointDef.maxMotorTorque=joint.maxMotorTorque;
		
		var anchorSource=bodySource.GetWorldCenter();
		anchorSource.x+=joint.anchorSourceX;
		anchorSource.y+=joint.anchorSourceY;
		
		jointDef.Initialize(
			bodySource,
			bodyTarget,
			anchorSource
		);
		
		return this._world.CreateJoint(jointDef);
		
	},
	
	/**
	 * Creates a mouse joint
	 * 
	 * @method _createJointMouse
	 * @param {Object} body Box2dWeb body
	 * @param {Float} targetX Horizontal position
	 * @param {Float} targetY Vertical position
	 * @return {Object} Joint handle
	 * @protected
	 */
	_createJointMouse: function(body,targetX,targetY) {
		
		var jointDef=new this._b2MouseJointDef();
		jointDef.bodyA=this._world.GetGroundBody();
		jointDef.bodyB=body;
		jointDef.target.Set(targetX,targetY);
		jointDef.collideConnected=true;
		jointDef.maxForce=300.0*body.GetMass();
		
		var joint=this._world.CreateJoint(jointDef);
		
		body.SetAwake(true);
		
		return joint;
		
	},
	
	/**
	 * Process mouse joint
	 * 
	 * @method _processMouseJoint
	 * @protected
	 */
	_processMouseJoint: function() {
		
		if (!this._engine.registry.getValue('control.mouse.enableJoint')) {
			
			return;
			
		}
		
		if (this._engine.control.mouse.mouseLeftDown && (!this._mouseJoint)) {
			
			var body=this.getBodyAtMouse();
			
			if (body) {
				
				this._mouseJoint=this._createJointMouse(
					body,
					this._engine.control.mouse.mouseX,
					this._engine.control.mouse.mouseY
				);
				
			}
			
		}
		
		if (this._mouseJoint) {
			
			if (this._engine.control.mouse.mouseLeftDown) {
				
				this._mouseJoint.SetTarget(
					new this._b2Vec2(
						this._engine.control.mouse.mouseX,
						this._engine.control.mouse.mouseY
					)
				);
				
			} else {
				
				this._destroyJoint(this._mouseJoint);
				this._mouseJoint=null;
				
			}
			
		}
		
	},
	
	/**
	 * Callbak method for internal processing of getBodyAtMouse
	 * 
	 * @method _getBodyAtMouseCallback
	 * @param {Object} fixture Box2D fixture
	 * @return {Boolean}
	 * @protected
	 */
	_getBodyAtMouseCallback: function(fixture) {
		
		/*
		 * TODO Remove global call zerk.game.engine.physics.*
		 */
		
		if (fixture.GetBody().GetType()
		!= zerk.game.engine.physics._b2Body.b2_staticBody) {
			
			if (fixture.GetShape().TestPoint(
				fixture.GetBody().GetTransform(),
				zerk.game.engine.physics._mouseJointVec
			)) {
				
				zerk.game.engine.physics._selectedBody=fixture.GetBody();
				return false;
				
			}
			
		}
		return true;
		
	},
	
	/**
	 * Local log method
	 * 
	 * @method _log
	 * @param {String} msg Log message
	 * @protected
	 */
	_log: function(msg) {
		
		this._engine.debug.log(
			msg,
			this._engine.debug.GROUP_PHYSICS
		);
		
	}
	
});