API Docs for: 0.1.0
Show:

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

/**
 * Viewport
 * 
 * This class is responsible for rendering the world on a canvas.
 * 
 * @class zerk.game.engine.viewport
 * @module zerk
 */
zerk.define({
	
	name: 'zerk.game.engine.viewport',
	require: [
		'zerk.game.engine.viewport.message'
	]
	
},{
	
	/**
	 * Game canvas
	 * 
	 * @property _canvas
	 * @type DOMElement
	 * @protected
	 */
	_canvas: null,
	
	/**
	 * Context of the main canvas
	 * 
	 * @property _context
	 * @type Object
	 * @protected
	 */
	_context: null,
	
	/**
	 * Buffer to render full bodies
	 * 
	 * @property _bufferBody
	 * @type DOMElement
	 * @protected
	 */
	_bufferBody: null,
	
	/**
	 * Context of the body buffer
	 * 
	 * @property _bufferBodyContext
	 * @type Object
	 * @protected
	 */
	_bufferBodyContext: null,
	
	/**
	 * Buffer for rendering of single fixtures
	 * 
	 * @property _bufferFixture
	 * @type DOMElement
	 * @protected
	 */
	_bufferFixture: null,
	
	/**
	 * Context of the fixture buffer
	 * 
	 * @property _bufferFixtureContext
	 * @type Object
	 * @protected
	 */
	_bufferFixtureContext: null,
	
	/**
	 * Rendering timer
	 * 
	 * @property _timer
	 * @type DOMTimer
	 * @protected
	 */
	_timer: null,
	
	/**
	 * Inidcates if the viewport is running
	 * 
	 * @property _running
	 * @type Boolean
	 * @protected
	 */
	_running: false,
	
	/**
	 * The total number of frames rendered
	 * 
	 * @property _frameCount
	 * @type Integer
	 * @protected
	 */
	_frameCount: 0,
	
	/**
	 * Counter used to calculate FPS
	 * 
	 * @property _fpsCounter
	 * @type Integer
	 * @protected
	 */
	_fpsCounter: 0,
	
	/**
	 * Stores the second of the last FPS measuring
	 * 
	 * @property _fpsSecond
	 * @type Integer
	 * @protected
	 */
	_fpsSecond: 0,
	
	/**
	 * The number of rendered frames per second
	 * 
	 * @property _fps
	 * @type Integer
	 * @protected
	 */
	_fps: 0,
	
	/**
	 * Zoom factor
	 * 
	 * @property _zoom
	 * @type Float
	 * @protected
	 */
	_zoom: 100,
	
	/*
	 * TODO Validate x,y,offsetX,offsetY to be named convenient
	 */
	
	/**
	 * Horizontal view position in pixels
	 * 
	 * @property _x
	 * @type Float
	 * @protected
	 */
	_x: 0,
	
	/**
	 * Vertical view position in pixels
	 * 
	 * @property _y
	 * @type Float
	 * @protected
	 */
	_y: 0,
	
	/**
	 * Horizontal view position in meters
	 * 
	 * @property _offsetX
	 * @type Float
	 * @protected
	 */
	_offsetX: 0,
	
	/**
	 * Vertical view position in meters
	 * 
	 * @property _offsetY
	 * @type Float
	 * @protected
	 */
	_offsetY: 0,
	
	/**
	 * Width of the viewport in pixels
	 * 
	 * @property _width
	 * @type Integer
	 * @protected
	 */
	_width: 0,
	
	/**
	 * Height of the viewport in pixels
	 * 
	 * @property _height
	 * @type Integer
	 * @protected
	 */
	_height: 0,
	
	/**
	 * Text messages
	 * 
	 * @property _messages
	 * @type Array
	 * @protected
	 */
	_messages: null,
	
	/**
	 * Class constructor
	 * 
	 * @method init
	 * @param {zerk.game.engine} engine Game engine
	 */
	init: function(engine) {
		
		this._engine=engine;
		
		this._messages=[];
		
		// Set default config values
		this._config={
			showBodyBuffer: false,
			showFixtureBuffer: false,
			showGrid: false,
			showFPS: false,
			showZoom: false,
			showPosition: false,
			showEntityBoundingBox: false,
			showEntityOriginIndicator: false,
			showFixtureBoundingBox: false,
			showBodyAngleIndicator: false,
			showWorldCenterIndicator: false,
			showViewCenterIndicator: false,
			trackPlayer: true,
			// How many 1m boxes will be drawn on the grid
			gridOuterWidth: 50,
			gridOuterHeight: 50
		};
		
		// Get a handle in the registry
		this._config=this._engine.registry.register('viewport',this._config);
		
		// Setup the game canvas
		
		this._canvas=this._engine.dom.getCanvasGame();
		
		if (!this._canvas.getContext) {
			
			this._log('Cannot create canvas 2d context');
			
		} else {
			
			this._context=this._canvas.getContext('2d');
			
		}
		
		// Setup the buffer for body rendering
		
		this._bufferBody=this._engine.dom.getCanvasBodyBuffer();
		
		if (this._config.showBodyBuffer) {
			
			this._bufferBody.style.display='';
			
		}
		
		if (!this._bufferBody.getContext) {
			
			this._log('Cannot create body buffer 2d context');
			
		} else {
			
			this._bufferBodyContext=this._bufferBody.getContext('2d');
			
		}
		
		// Setup the buffer for fixture rendering
		
		this._bufferFixture=this._engine.dom.getCanvasFixtureBuffer();
		
		if (this._config.showFixtureBuffer) {
			
			this._bufferFixture.style.display='';
			
		}
		
		if (!this._bufferFixture.getContext) {
			
			this._log('Cannot create fixture buffer 2d context');
			
		} else {
			
			this._bufferFixtureContext=this._bufferFixture.getContext('2d');
			
		}
		
		// Auto scale canvas to parent container size
		// this.autoScale();
		
		// Sync viwport with canvas size
		this._width=this._canvas.width;
		this._height=this._canvas.height;
		
		this._log(
			'Scale '
			+this._x+':'+this._y
			+' at '
			+this._width+'x'+this._height
		);
		
		// Setup crossbrowser requestAnimationFrame
		this._setupRequestAnimationFrame();
		
		var me=this;
		
		(function animloop() {
			
			requestAnimationFrame(animloop);
			
			me.render();
			
		})();
		
		this._log('Init');
		
	},
	
	/**
	 * Scale the viewport size to the game canvas size
	 * 
	 * @method autoScale
	 */
	autoScale: function() {
		
		var canvasParent=$('#canvas_game').parent();
		
		this._canvas.width=canvasParent.width();
		this._canvas.height=canvasParent.height();
		
	},
	
	/**
	 * Start the viewport
	 * 
	 * @method start
	 */
	start: function() {
		
		this._log('Started');
		
		this._running=true;
		
	},
	
	/**
	 * Stop the viewport
	 * 
	 * @method stop
	 */
	stop: function() {
		
		this._running=false;
		
		this._log('Stopped ('+this._frameCount+' Frames)');
		
		this.reset();
		
	},
	
	/**
	 * Reset the viewport
	 * 
	 * @method reset
	 */
	reset: function() {
		
		this._frameCount=0;
		
	},
	
	/**
	 * Clear the viewport
	 * 
	 * @method clear
	 */
	clear: function() {
		
		this._canvas.width=this._canvas.width;
		
	},
	
	/**
	 * Renders the current world state onto the game canvas
	 * 
	 * @method render
	 */
	render: function() {
		
		if (!this._running) return;
		
		this.clear();
		
		// Sync view with player entity
		if (this._config.trackPlayer) {
			
			if (this._engine.world!=null
			&& typeof this._engine.world._mapEntity.player!='undefined') {
				
				this._setViewPosition(
					this._engine.world._mapEntity.player.config.x,
					this._engine.world._mapEntity.player.config.y
				);
				
			}
			
		}
		
		if (this._config.showGrid) {
			this._debugDrawGrid();
		}
		
		if (this._config.showWorldCenterIndicator) {
			this._renderWorldCenterIndicator();
		}
		
		// Render all entities in the view area
		var entities=this._getEntitiesInViewport();
		
		for (var i=0;i<entities.length;i++) {
			this._renderEntity(entities[i]);
		}
		
		if (this._config.showViewCenterIndicator) {
			this._renderViewCenterIndicator();
		}
		
		this._renderMessages();
		
		this._renderDebugInfo();
		
		// Increase frame counter
		this._frameCount++;
		
		// Calculate FPS
		var date=new Date();
		var second=date.getSeconds();
		
		if (second!=this._fpsSecond) {
			this._fps=this._fpsCounter;
			this._fpsCounter=0;
		}
		
		this._fpsSecond=second;
		
		this._fpsCounter++;
		
	},
	
	/**
	 * Registers a message
	 * 
	 * @method registerMessage
	 * @param {zerk.game.engine.viewport.message} message
	 */
	registerMessage: function(message) {
		
		this._messages.push(message);
		
	},
	
	/**
	 * Unregisters a message
	 * 
	 * @method unregisterMessage
	 * @param {String} id Message id
	 */
	unregisterMessage: function(id) {
		
		var found=false;
		for (var i=0;i<this._messages.length;i++) {
			if (this._messages[i].id==id) {
				found=true;
				break;
			}
		}
		
		if (!found) return false;
		
		this._messages.splice(i,1);
		return true;
		
	},
	
	/**
	 * Removes all messages
	 * 
	 * @method clearMessages
	 */
	clearMessages: function() {
		
		this._messages=[];
		
	},
	
	/**
	 * Returns the horizontal view position in pixels
	 * 
	 * @method getX
	 * @return Float
	 */
	getX: function() {
		
		return this._x;
		
	},

	/**
	 * Returns the vertical view position in pixels
	 * 
	 * @method getY
	 * @return Float
	 */
	getY: function() {
		
		return this._y;
		
	},
	
	/**
	 * Sets the horizontal view position in pixels
	 * 
	 * @method setX
	 * @return Float
	 */
	setX: function(value) {
		
		this._x=value;
		
	},

	/**
	 * Sets the vertical view position in pixels
	 * 
	 * @method setY
	 * @return Float
	 */
	setY: function(value) {
		
		this._y=value;
		
	},
	
	/**
	 * Returns the horizontal view position in meters
	 * 
	 * @method getOffsetX
	 * @return Float
	 */
	getOffsetX: function() {
		
		return this._offsetX;
		
	},

	/**
	 * Returns the vertical view position in meters
	 * 
	 * @method getOffsetY
	 * @return Float
	 */
	getOffsetY: function() {
		
		return this._offsetY;
		
	},
	
	/**
	 * Returns the width of the viewport in pixels
	 * 
	 * @method getWidth
	 * @return Integer
	 */
	getWidth: function() {
		
		return this._width;
		
	},
	
	/**
	 * Returns the height of the viewport in pixels
	 * 
	 * @method getHeight
	 * @return Integer
	 */
	getHeight: function() {
		
		return this._height;
		
	},
	
	/**
	 * Scales given value by current zoom factor
	 * 
	 * @method toScaleX
	 * @param {Float} value Value
	 * @return {Float} Scaled value
	 */
	toScaleX: function(value) {
		
		return (value/100)*this._zoom;
		
	},
	
	/**
	 * Scales given value by current zoom factor
	 * 
	 * @method toScaleY
	 * @param {Float} value Value
	 * @return {Float} Scaled value
	 */
	toScaleY: function(value) {
		
		return (value/100)*this._zoom;
		
	},

	/**
	 * Un-scales given value by current zoom factor
	 * 
	 * @method fromScaleX
	 * @param {Float} value Value
	 * @return {Float} Scaled value
	 */
	fromScaleX: function(value) {
		
		return (value/this._zoom)*100;
		
	},

	/**
	 * Un-scales given value by current zoom factor
	 * 
	 * @method fromScaleY
	 * @param {Float} value Value
	 * @return {Float} Scaled value
	 */
	fromScaleY: function(value) {
		
		return (value/this._zoom)*100;
		
	},
	
	/**
	 * Increases current zoom factor by factor 10
	 * 
	 * @method zoomIn
	 */
	zoomIn: function() {
		
		this._zoom+=10;
		
	},

	/**
	 * Decreases current zoom factor by factor 10
	 * 
	 * @method zoomOut
	 */
	zoomOut: function() {
		
		this._zoom-=10;
		
	},
	
	/**
	 * Returns a list of all entities currently visible in the viewport
	 * 
	 * @method _getEntitiesInViewport
	 * @return {Array} Entity list
	 * @protected
	 */
	_getEntitiesInViewport: function() {
		
		var x1=this._engine.helper.toMeter(
			this._x+this.fromScaleX(0-(this._width/2))
		);
		
		var y1=this._engine.helper.toMeter(
			this._y+this.fromScaleY(0-(this._height/2))
		);
		
		var x2=this._engine.helper.toMeter(
			this._x+this.fromScaleX(this._width-(this._width/2))
		);
		
		var y2=this._engine.helper.toMeter(
			this._y+this.fromScaleY(this._height-(this._height/2))
		);
		
		return this._engine.physics.getEntitiesInArea(x1,y1,x2,y2);
		
	},
	
	/**
	 * Crossbrowser requestAnimationFrame
	 * 
	 * <a href="http://paulirish.com/2011/requestanimationframe-for-smart-animating/">
	 * http://paulirish.com/2011/requestanimationframe-for-smart-animating/</a>
	 * 
	 * <a href="http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating">
	 * http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating</a>
	 * 
	 * requestAnimationFrame polyfill by Erik Möller
	 * 
	 * fixes from Paul Irish and Tino Zijdel
	 * 
	 * @method _setupRequestAnimationFrame
	 * @protected
	 */
	_setupRequestAnimationFrame: function() {
		
		var lastTime=0;
		
		var vendors=[
			'ms',
			'moz',
			'webkit',
			'o'
		];
		
		for (var x=0; x<vendors.length && !window.requestAnimationFrame; ++x) {
			
			window.requestAnimationFrame
				=window[vendors[x]+'RequestAnimationFrame'];
			window.cancelAnimationFrame
				=window[vendors[x]+'CancelAnimationFrame']
				|| window[vendors[x]+'CancelRequestAnimationFrame'];
			
		}
		
		if (!window.requestAnimationFrame) {
			
			window.requestAnimationFrame=function(callback,element) {
				
				var currTime=new Date().getTime();
				var timeToCall=Math.max(0,16-(currTime-lastTime));
				var id=window.setTimeout(
					function() {
						
						callback(currTime+timeToCall); 
						
					},
					timeToCall
				);
				lastTime=currTime+timeToCall;
				return id;
				
			};
			
		}
	 
		if (!window.cancelAnimationFrame) {
			
			window.cancelAnimationFrame=function(id) {
				
				clearTimeout(id);
				
			};
			
		}
		
	},
	
	/**
	 * Removes messages that timed out
	 * 
	 * @method _clearMessages
	 * @protected
	 */
	_clearMessages: function() {
		
		do {
			
			for (var i=0;i<this._messages.length;i++) {
				
				if (this._messages[i].starttime==null) {
					
					this._messages[i].starttime=this._engine.getTime();
					
				} else if (this._messages[i].lifetime>0
				&& (this._engine.getTime()>=this._messages[i].starttime
				+this._messages[i].lifetime)) {
					
					this._messages.splice(i,1);
					break;
					
				}
				
			}
			
		}
		while (i<this._messages.length);
		
	},
	
	/**
	 * Renders messages
	 * 
	 * @method _renderMessages
	 * @protected
	 */
	_renderMessages: function() {
		
		this._clearMessages();
		
		for (var i=0;i<this._messages.length;i++) {
			
			this._renderText(
				this._messages[i].text,
				this._messages[i].x,
				this._messages[i].y,
				this._messages[i].font,
				this._messages[i].size,
				this._messages[i].color
			);
			
		}
		
	},
	
	/**
	 * Render text onto the game canvas
	 * 
	 * @method _renderText
	 * @param {String} text
	 * @param {Integer} x
	 * @param {Integer} y
	 * @param {String} font
	 * @param {Integer} size
	 * @param {String} color
	 * @protected
	 */
	_renderText: function(text,x,y,font,size,color) {
		
		if (typeof font=='undefined') {
			
			font='sans-serif';
			
		}
		
		if (typeof size=='undefined') {
			
			size=20;
			
		}
		
		if (typeof color=='undefined') {
			
			color='rgb(0,255,0)';
			
		}
		
		this._context.font=size+'pt '+font;
		this._context.fillStyle=color;
		
		var measure=this._context.measureText(text);
		
		this._context.fillText(
			text,
			(this._width/2)-(measure.width/2)+x,
			(this._height/2)+(size/2)+y
		);
		
	},
	
	/**
	 * Renders debug information onto the game canvas
	 * 
	 * @method _renderDebugInfo
	 * @protected
	 */
	_renderDebugInfo: function() {
		
		this._context.font="10pt sans-serif";
		this._context.fillStyle="rgb(0,255,0)";
		
		if (this._config.showFPS) {
			
			this._renderDebugInfoLine(this._fps+' FPS',1);
			
		}
		
		if (this._config.showZoom) {
			
			this._renderDebugInfoLine(
				this._engine.helper.formatPercent(this._zoom),
				2
			);
			
		}
		
		var offsetX=this._engine.helper.round(
			this._engine.helper.toMeter(this._x),
			1
		);
		var offsetY=this._engine.helper.round(
			this._engine.helper.toMeter(this._y),
			1
		);
		
		if (this._config.showPosition) {
			
			this._renderDebugInfoLine(offsetX+':'+offsetY,3);
			
		}
		
	},
	
	/**
	 * @method _getCanvasX
	 * @param {Float} meter
	 * @param {Integer} pixel
	 * @return {Integer}
	 * @protected
	 */
	/*
	 * TODO Document mehtod '_getCanvasX'
	 */
	_getCanvasX: function(meter,pixel) {
		
		if (typeof meter==='undefined') {
			
			meter=0;
			
		}
		
		if (typeof pixel==='undefined') {
			
			pixel=0;
			
		}
		
		var value=this.toScaleX(
			this._engine.helper.fromMeter(meter)+pixel-this._x
		);
		
		return ~~(0.5+value+(this._width/2));
		
	},
	
	/**
	 * @method _getCanvasY
	 * @param {Float} meter
	 * @param {Integer} pixel
	 * @return {Integer}
	 * @protected
	 */
	/*
	 * TODO Document mehtod '_getCanvasY'
	 */
	_getCanvasY: function(meter,pixel) {
		
		if (typeof meter==='undefined') {
			
			meter=0;
			
		}
		
		if (typeof pixel==='undefined') {
			
			pixel=0;
			
		}
		
		var value=this.toScaleX(
			this._engine.helper.fromMeter(meter)+pixel-this._y
		);
		
		return ~~(0.5+value+(this._height/2));
		
	},
	
	/**
	 * Set the view position to a given world position
	 * 
	 * @method _setViewPosition
	 * @param {Float} x
	 * @param {Float} y
	 * @protected
	 */
	_setViewPosition: function(x,y) {
		
		this._x=this._engine.helper.fromMeter(x);
		this._y=this._engine.helper.fromMeter(y);
		
	},
	
	/**
	 * Renders a line of debug information onto the game canvas
	 * 
	 * @method _renderDebugInfoLine
	 * @param {String} text Text to be rendered
	 * @param {Integer} line Row number
	 * @protected
	 */
	_renderDebugInfoLine: function(text,line) {
		
		var measure=this._context.measureText(text);
		this._context.fillText(text,this._width-measure.width-2,12*line);
		
	},
	
	/**
	 * Renders an entity onto the game canvas
	 * 
	 * @method _renderEntity 
	 * @param {zerk.game.engine.entity} entity Entity
	 * @protected
	 */
	_renderEntity: function(entity) {
		
		// Render all bodies of the entity
		
		for (var index in entity.bodies) {
			this._renderBody(entity,entity.bodies[index]);
		}
		
	},
	
	/**
	 * Renders a body onto the game canvas
	 * 
	 * @method _renderBody
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @protected
	 */
	_renderBody: function(entity,body) {
		
		var position=entity.config.bodies[body.key];
		
		var diagonal=
			Math.ceil(
				Math.sqrt(
					(this._engine.helper.fromMeter(body.width)
					*this._engine.helper.fromMeter(body.width))
					+(this._engine.helper.fromMeter(body.height)
					*this._engine.helper.fromMeter(body.height)
				)
			)
		);
		
		this._bufferBody.width=diagonal;
		this._bufferBody.height=diagonal;
		
		var x=this._bufferBody.width/2;
		var y=this._bufferBody.height/2;
		
		this._bufferBodyContext.translate(x,y);
		
		this._bufferBodyContext.rotate(position.angle);
		
		// Render all the fixtures of the body
		for (var i=0;i<body.fixtures.length;i++) {
			
			this._renderFixture(entity,body,body.fixtures[i]);
			
		}
		
		if (this._config.showEntityBoundingBox) {
			
			this._renderEntityBoundingBody(entity,body);
			
		}
		if (this._config.showBodyAngleIndicator) {
			
			this._renderBodyAngleIndicator(entity,body);
			
		}
		
		// Draw the buffer onto the display
		
		this._context.drawImage(
			this._bufferBody,
			0,
			0,
			diagonal,
			diagonal,
			this._getCanvasX(position.x,-(diagonal/2)),
			this._getCanvasY(position.y,-(diagonal/2)),
			this.toScaleX(diagonal),
			this.toScaleY(diagonal)
		);
		
		if (this._config.showEntityOriginIndicator) {
			
			this._renderEntityOriginIndicator(entity);
			
		}
		
	},
	
	/**
	 * Renders a fixture onto the body buffer canvas
	 * 
	 * @method _renderFixture
	 * @param {zerk.engine.entity.entity} entity Entity
	 * @param {zerk.engine.physics.body.body} body Body
	 * @param {zerk.engine.physics.fixture.fixture} fixture Fixture
	 * @protected
	 */
	_renderFixture: function(entity,body,fixture) {
		
		var diagonal=0;
		
		// Calculate buffer size
		
		if (fixture.shape=='circle') {
			
			diagonal=this._engine.helper.fromMeter(fixture.radius);
			
		} else {
			
			diagonal=Math.ceil(
				Math.sqrt(
					(this._engine.helper.fromMeter(fixture.width)
					*this._engine.helper.fromMeter(fixture.width))
					+(this._engine.helper.fromMeter(fixture.height)
					*this._engine.helper.fromMeter(fixture.height))
				)
			);
			
		}
		
		// Set buffer size
		this._bufferFixture.width=diagonal;
		this._bufferFixture.height=diagonal;
		
		// Translate the buffers coordinate systems origin to center
		this._bufferFixtureContext.translate(
			this._bufferFixture.width/2,
			this._bufferFixture.height/2
		);
		
		// Rotate the whole buffer before drawing
		this._bufferFixtureContext.rotate(fixture.angle);
		
		// Render all sprites of the fixture
		for (var i=0;i<fixture.sprites.length;i++) {
			
			this._renderSprite(entity,body,fixture,fixture.sprites[i]);
			
		}
		
		if (this._config.showFixtureBoundingBox) {
			
			this._renderFixtureBoundingShape(entity,body,fixture);
			
		}
		
		this._bufferBodyContext.drawImage(
			this._bufferFixture,
			0,
			0,
			diagonal,
			diagonal,
			this._engine.helper.fromMeter(fixture.x)-(diagonal/2),
			this._engine.helper.fromMeter(fixture.y)-(diagonal/2),
			diagonal,
			diagonal
		);
		
	},
	
	/**
	 * Renders a shape indicating the bounding area of a fixture
	 * 
	 * Delegate method for the fixture bounding shape methods
	 * 
	 * @method _renderFixtureBoundingShape
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @param {zerk.game.engine.physics.fixture} fixture Fixture
	 * @protected
	 */
	_renderFixtureBoundingShape: function(entity,body,fixture) {
		
		switch (fixture.shape) {
			case 'box':
				this._renderFixtureBoundingBox(entity,body,fixture);
				break;
			case 'circle':
				this._renderFixtureBoundingCircle(entity,body,fixture);
				break;
			case 'polygon':
				this._renderFixtureBoundingPolygon(entity,body,fixture);
				break;
		}
		
	},
	
	/**
	 * Renders the bounding area for a box shaped fixture
	 * 
	 * @method _renderFixtureBoundingShape
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @param {zerk.game.engine.physics.fixture} fixture Fixture
	 * @protected
	 */
	_renderFixtureBoundingBox: function(entity,body,fixture) {
		
		this._bufferFixtureContext.beginPath();
		
		if (fixture.isSensor) {
			
			this._bufferFixtureContext.strokeStyle="rgb(255,0,0)";
			
		} else {
			
			this._bufferFixtureContext.strokeStyle="rgb(0,255,0)";
			
		}
		
		this._bufferFixtureContext.strokeRect(
			this._engine.helper.fromMeter(-(fixture.width/2)),
			this._engine.helper.fromMeter(-(fixture.height/2)),
			this._engine.helper.fromMeter(fixture.width),
			this._engine.helper.fromMeter(fixture.height)
		);
		
	},
	
	/**
	 * Renders the bounding area for a circle shaped fixture
	 * 
	 * @method _renderFixtureBoundingCircle
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @param {zerk.game.engine.physics.fixture} fixture Fixture
	 * @protected
	 */
	_renderFixtureBoundingCircle: function(entity,body,fixture) {
		
		this._bufferBodyContext.beginPath();
		this._bufferBodyContext.strokeStyle="rgb(0,255,0)";
		this._bufferBodyContext.arc(
			0,
			0,
			this._engine.helper.fromMeter(fixture.radius),
			0,
			Math.PI*2,
			true
		);
		this._bufferBodyContext.closePath();
		this._bufferBodyContext.stroke();
		
	},
	
	/**
	 * Renders the bounding area for a polygon shaped fixture
	 * 
	 * @method _renderFixtureBoundingPolygon
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @param {zerk.game.engine.physics.fixture} fixture Fixture
	 * @protected
	 */
	_renderFixtureBoundingPolygon: function(entity,body,fixture) {
		
		this._bufferBodyContext.beginPath();
		this._bufferBodyContext.strokeStyle="rgb(0,255,0)";
		
		for (var i=0;i<fixture.vertices.length;i++) {
			
			if (i==0) {
				
				this._bufferBodyContext.moveTo(
					this._engine.helper.fromMeter(fixture.vertices[i][0]),
					this._engine.helper.fromMeter(fixture.vertices[i][1])
				);
				
			} else {
				
				this._bufferBodyContext.lineTo(
					this._engine.helper.fromMeter(fixture.vertices[i][0]),
					this._engine.helper.fromMeter(fixture.vertices[i][1])
				);
				
			}
			
		}
		
		this._bufferBodyContext.closePath();
		this._bufferBodyContext.stroke();
		
	},
	
	/**
	 * Renders a bounding box for a body
	 * 
	 * @method _renderEntityBoundingBody
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @protected
	 */
	_renderEntityBoundingBody: function(entity,body) {
		
		this._bufferBodyContext.beginPath();
		this._bufferBodyContext.strokeStyle="rgb(160,32,240)";
		this._bufferBodyContext.strokeRect(
			-(this._engine.helper.fromMeter(body.width)/2),
			-(this._engine.helper.fromMeter(body.height)/2),
			this._engine.helper.fromMeter(body.width),
			this._engine.helper.fromMeter(body.height)
		);
		
	},
	
	/**
	 * Render sprite
	 * 
	 * @method _renderSprite
	 * @param {zerk.game.engine.entity} entity
	 * @param {zerk.game.engine.physics.body} body
	 * @param {zerk.game.engine.physics.fixture} fixture
	 * @param {zerk.game.engine.viewport.sprite} sprite
	 * @protected
	 */
	_renderSprite: function(
		entity,
		body,
		fixture,
		sprite
	) {
		
		/*
		 * TODO Reactivate sprite rendering
		 */
		
	},
	
	/**
	 * Renders an angle indicator for each body of the entity
	 * 
	 * @method _renderBodyAngleIndicator
	 * @param {zerk.game.engine.entity} entity Entity
	 * @param {zerk.game.engine.physics.body} body Body
	 * @protected
	 */
	_renderBodyAngleIndicator: function(entity,body,fixture) {
		
		var x=0,y=0;
		
		this._bufferBodyContext.beginPath();
		this._bufferBodyContext.strokeStyle="rgb(255,0,0)";
		
		if (body.shape!='polygon') {
			
			x=this._engine.helper.fromMeter(-body.width/2);
			y=this._engine.helper.fromMeter(-body.height/2);
			
		}
		
		this._bufferBodyContext.moveTo(x+0,y+10);
		this._bufferBodyContext.lineTo(x+10,y+10);
		this._bufferBodyContext.moveTo(x+10,y+0);
		this._bufferBodyContext.lineTo(x+10,y+10);
		this._bufferBodyContext.stroke();
		
		/*
		 * TODO Create drawEntityName config value
		 */
		// Draw entity name
		/*
		 * this._bufferBodyContext.fillStyle="rgb(0,255,0)";
		 * this._bufferBodyContext.fillText(entity.name,x+15,y+8);
		 */
		
	},
	
	/**
	 * Renders the entity origin indicator
	 * 
	 * @method _renderEntityOriginIndicator
	 * @param {zerk.game.engine.entity} entity Entity
	 * @protected
	 */
	_renderEntityOriginIndicator: function(entity) {
		
		this._context.beginPath();
		this._context.strokeStyle="rgb(255,0,0)";
		
		this._context.moveTo(
			this._getCanvasX(entity.config.x-0.2),
			this._getCanvasY(entity.config.y)
		);
		this._context.lineTo(
			this._getCanvasX(entity.config.x+0.2),
			this._getCanvasY(entity.config.y)
		);
		this._context.moveTo(
			this._getCanvasX(entity.config.x),
			this._getCanvasY(entity.config.y-0.2)
		);
		this._context.lineTo(
			this._getCanvasX(entity.config.x),
			this._getCanvasY(entity.config.y+0.2)
		);
		
		this._context.stroke();
		
	},
	
	/**
	 * Renders the world center indicator
	 * 
	 * @method _renderWorldCenterIndicator
	 * @protected
	 */
	_renderWorldCenterIndicator: function() {
		
		this._context.beginPath();
		this._context.strokeStyle="rgb(0,0,255)";
		
		this._context.moveTo(this._getCanvasX(-0.5),this._getCanvasY());
		this._context.lineTo(this._getCanvasX(0.5),this._getCanvasY());
		this._context.moveTo(this._getCanvasX(),this._getCanvasY(-0.5));
		this._context.lineTo(this._getCanvasX(),this._getCanvasY(0.5));
		
		this._context.stroke();
		
	},
	
	/**
	 * Renders the view center indicator
	 * 
	 * @method _renderViewCenterIndicator
	 * @protected
	 */
	_renderViewCenterIndicator: function() {
		
		this._context.beginPath();
		this._context.strokeStyle="rgb(0,200,200)";
		
		this._context.moveTo((this._width/2)-10,(this._height/2));
		this._context.lineTo((this._width/2)+10,(this._height/2));
		this._context.moveTo((this._width/2),(this._height/2)-10);
		this._context.lineTo((this._width/2),(this._height/2)+10);
		
		this._context.stroke();
		
	},
	
	/**
	 * Renders the grid
	 * 
	 * @method _debugDrawGrid
	 * @protected
	 */
	/*
	 * TODO Rename method '_debugDrawGrid' more convenient
	 */
	_debugDrawGrid: function() {
		
		var coord=0;
		var width=this._config.gridOuterWidth;
		var height=this._config.gridOuterHeight;
		var offsetX=-(this._config.gridOuterWidth/2);
		var offsetY=-(this._config.gridOuterHeight/2);
		
		// Draw grid
		this._context.beginPath();
		this._context.strokeStyle="#111111";
		this._context.lineWidth=1;
		
		for (var i=1;i<=this._config.gridOuterWidth;i++) {
			
			coord=i*1;
			this._context.moveTo(
				this._getCanvasX(offsetX+coord),
				this._getCanvasY(offsetY)
			);
			this._context.lineTo(
				this._getCanvasX(offsetX+coord),
				this._getCanvasY(offsetY+height)
			);
			this._context.stroke();
			
		}
		
		for (var i=1;i<=this._config.gridOuterHeight;i++) {
			
			coord=i*1;
			this._context.moveTo(
				this._getCanvasX(offsetX),
				this._getCanvasY(offsetY+coord)
			);
			this._context.lineTo(
				this._getCanvasX(offsetX+width),
				this._getCanvasY(offsetY+coord)
			);
			this._context.stroke();
			
		}
		
		// Draw axis
		
		this._context.beginPath();
		this._context.strokeStyle="#111111";
		this._context
			.moveTo(this._getCanvasX(offsetX),this._getCanvasY(offsetY));
		this._context.lineTo(
			this._getCanvasX(offsetX+width),
			this._getCanvasY(offsetY)
		);
		this._context
			.moveTo(this._getCanvasX(offsetX),this._getCanvasY(offsetY));
		this._context.lineTo(
			this._getCanvasX(offsetX),
			this._getCanvasY(offsetY+height)
		);
		this._context.stroke();
		
	},
	
	/**
	 * Local log method
	 * 
	 * @method _log
	 * @param {String} msg Log message
	 * @protected
	 */
	_log: function(msg) {
		
		// Redirect to global log
		this._engine.debug.log(
			msg,this._engine.debug.GROUP_VIEWPORT
		);
		
	}
	
});