/**
* Renders a given map
*
* Copyright (C) 2012 Mike Gerwitz
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
/**
* Renders a map to a canvas
*/
ltjs.MapRender = Class( 'MapRender',
{
/**
* Tunnel bitmask
* @type {number}
*/
'private const _TMASK': 0x40,
/**
* 2d context to which map should be drawn
* @type {CanvasRenderingContext2d}
*/
'private _ctx': null,
/**
* 2d context to which masked game objects should be drawn
* @type {CanvasRenderingContext2d}
*/
'private _ctxObj': null,
/**
* Tile set to be rendered
* @type {Object}
*/
'private _tiles': {},
/**
* Maps each "game object" in the map to a tile id
* TODO: abstract
* @type {Array.}
*/
'private _objMap': [
'dirt', 'tup', 'base', 'water', 'block', 'mblock', 'brick',
'atup', 'atright', 'atdown', 'atleft', 'mirrorul', 'mirrorur',
'mirrordr', 'mirrordl', 'owup', 'owright', 'owdown', 'owleft',
'cblock', 'rmirrorul', 'rmirrorur', 'rmirrordr', 'rmirrordl',
'ice', 'thinice'
],
/**
* Colors used to identify tunnels
*
* These colors will be rendered in the background and will bleed through
* the transparent portions of the tile. We use explicit HEX codes rather
* than CSS color names because environments may vary the colors used.
*
* Taken from ColorList in LTANK2.C in the original sources. Note that there
* is an endianness difference.
*
* TODO: abstract
*
* @type {Array.}
*/
'private _tunnelColors': [
'#ff0000', '#00ff00', '#0000ff', '#00ffff', // r g b c
'#ffff00', '#ff00ff', '#ffffff', '#808080' // y m w b
],
/**
* Initialize renderer with a canvas context and a tile set
*
* An additional canvas of equal dimensions will be created and laid atop of
* the provided canvas to render masked game objects.
*
* @param {CanvasRenderingContext2d} ctx canvas 2d context
* @param {Object} tiles tile set to render
*/
__construct: function( ctx, tiles )
{
this._ctx = ctx;
this._tiles = tiles;
this._ctxObj = this._getObjCanvas();
},
/**
* Create the overlaying canvas for masked elements and return a 2d context
*
* @return {CanvasRenderingContext2d} 2d context for new canvas element
*/
'private _getObjCanvas': function()
{
var canvas = this._ctx.canvas,
canvas_obj = document.createElement( 'canvas' );
// mimic the dimensions and positions of the original canvas
canvas_obj.style.position = 'absolute';
canvas_obj.width = canvas.width;
canvas_obj.height = canvas.height;
canvas_obj.style.left = ( canvas.offsetLeft + 'px' );
canvas_obj.style.top = ( canvas.offsetTop + 'px' );
// append the new canvas to the DOM under the same parent
canvas.parentNode.appendChild( canvas_obj );
return canvas_obj.getContext( '2d' );
},
/**
* Render the provided map
*
* @param {Map} map map to render
*
* @return {MapRender} self
*/
'public render': function( map )
{
if ( !( Class.isA( ltjs.Map, map ) ) )
{
throw TypeError( 'Invalid Map provided' );
}
var objs = map.getObjects(),
size = map.getDimensions(),
i = objs.length,
tiles = this._tiles,
// get the width and height from one of the tiles
t = this._tiles.dirt.data,
w = t.width,
h = t.height;
// render each object (remember, we're dealing with columns, not rows;
// see Map.getObjects())
while ( i-- )
{
var oid = objs[ i ],
tid = this._objMap[ oid ],
x = ( Math.floor( i / size[ 0 ] ) * w ),
y = ( ( i % size[ 1 ] ) * h );
// tunnels are handled a bit differently than other objects
if ( this._isTunnel( oid ) )
{
this._renderTunnel( oid, x, y );
continue;
}
this._drawTile( tid, x, y );
}
return this;
},
/**
* Draw the tile identified by the given id
*
* The tile will be drawn to the appropriate canvas depending on whether or
* not it has been masked. If it does have a mask, it will be drawn to the
* overlaying canvas and the dirt tile will be drawn underneath it.
*
* @param {string} tid tile id
* @param {number} x left position
* @param {number} y top position
*
* @return {undefined}
*/
'private _drawTile': function( tid, x, y )
{
var tile = this._tiles[ tid ],
ctx = ( tile.masked ) ? this._ctxObj : this._ctx;
ctx.putImageData( this._tiles[ tid ].data, x, y );
if ( tile.masked )
{
this._ctx.putImageData( this._tiles.dirt.data, x, y );
}
},
/**
* Returns whether the given object id is a tunnel
*
* @param {number} oid object id
*
* @return {boolean} TRUE if object is a tunnel, otherwise false
*/
'private _isTunnel': function( oid )
{
return ( oid & this.__self.$( '_TMASK' ) );
},
/**
* Render the given tunnel
*
* The tunnel background color (which will peek through the mask) is
* rendered to the base canvas, whereas the tunnel tile itself is rendered
* on the overlaying canvas.
*
* @param {number} oid tunnel object id
* @param {number} x left position
* @param {number} y top position
*
* @return {undefined}
*/
'private _renderTunnel': function( oid, x, y )
{
// get the tunnel id by stripping off the tunnel bitmask and grab the
// associated color
var tunnel_id = ( ( +oid ^ this.__self.$( '_TMASK' ) ) / 2 ),
color = this._tunnelColors[ tunnel_id ] || 'black';
// fill tile with the appropriate background color for this tile
this._ctx.fillStyle = color;
this._ctx.fillRect( x, y, 32, 32 );
// render the tile to the overlay canvas
this._ctxObj.putImageData( this._tiles.tunnel.data, x, y );
}
} );