/** * 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 ].first.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 ); } } );