ig.module( 'impact.background-map' ) .requires( 'impact.map', 'impact.image' ) .defines(function(){ "use strict"; ig.BackgroundMap = ig.Map.extend({ tiles: null, scroll: {x: 0, y:0}, distance: 1, repeat: false, tilesetName: '', foreground: false, enabled: true, preRender: false, preRenderedChunks: null, chunkSize: 512, debugChunks: false, anims: {}, init: function( tilesize, data, tileset ) { this.parent( tilesize, data ); this.setTileset( tileset ); }, setTileset: function( tileset ) { this.tilesetName = tileset instanceof ig.Image ? tileset.path : tileset; this.tiles = new ig.Image( this.tilesetName ); this.preRenderedChunks = null; }, setScreenPos: function( x, y ) { this.scroll.x = x / this.distance; this.scroll.y = y / this.distance; }, preRenderMapToChunks: function() { var totalWidth = this.width * this.tilesize * ig.system.scale, totalHeight = this.height * this.tilesize * ig.system.scale; var chunkCols = Math.ceil(totalWidth / this.chunkSize), chunkRows = Math.ceil(totalHeight / this.chunkSize); this.preRenderedChunks = []; for( var y = 0; y < chunkRows; y++ ) { this.preRenderedChunks[y] = []; for( var x = 0; x < chunkCols; x++ ) { var chunkWidth = (x == chunkCols-1) ? totalWidth - x * this.chunkSize : this.chunkSize; var chunkHeight = (y == chunkRows-1) ? totalHeight - y * this.chunkSize : this.chunkSize; this.preRenderedChunks[y][x] = this.preRenderChunk( x, y, chunkWidth, chunkHeight ); } } }, preRenderChunk: function( cx, cy, w, h ) { var tw = w / this.tilesize / ig.system.scale + 1, th = h / this.tilesize / ig.system.scale + 1; var nx = (cx * this.chunkSize / ig.system.scale) % this.tilesize, ny = (cy * this.chunkSize / ig.system.scale) % this.tilesize; var tx = Math.floor(cx * this.chunkSize / this.tilesize / ig.system.scale), ty = Math.floor(cy * this.chunkSize / this.tilesize / ig.system.scale); var chunk = ig.$new( 'canvas'); chunk.width = w; chunk.height = h; var oldContext = ig.system.context; ig.system.context = chunk.getContext("2d"); for( var x = 0; x < tw; x++ ) { for( var y = 0; y < th; y++ ) { if( x + tx < this.width && y + ty < this.height ) { var tile = this.data[y+ty][x+tx]; if( tile ) { this.tiles.drawTile( x * this.tilesize - nx, y * this.tilesize - ny, tile - 1, this.tilesize ); } } } } ig.system.context = oldContext; return chunk; }, draw: function() { if( !this.tiles.loaded || !this.enabled ) { return; } if( this.preRender ) { this.drawPreRendered(); } else { this.drawTiled(); } }, drawPreRendered: function() { if( !this.preRenderedChunks ) { this.preRenderMapToChunks(); } var dx = ig.system.getDrawPos(this.scroll.x), dy = ig.system.getDrawPos(this.scroll.y); if( this.repeat ) { dx %= this.width * this.tilesize * ig.system.scale; dy %= this.height * this.tilesize * ig.system.scale; } var minChunkX = Math.max( Math.floor(dx / this.chunkSize), 0 ), minChunkY = Math.max( Math.floor(dy / this.chunkSize), 0 ), maxChunkX = Math.ceil((dx+ig.system.realWidth) / this.chunkSize), maxChunkY = Math.ceil((dy+ig.system.realHeight) / this.chunkSize), maxRealChunkX = this.preRenderedChunks[0].length, maxRealChunkY = this.preRenderedChunks.length; if( !this.repeat ) { maxChunkX = Math.min( maxChunkX, maxRealChunkX ); maxChunkY = Math.min( maxChunkY, maxRealChunkY ); } var nudgeY = 0; for( var cy = minChunkY; cy < maxChunkY; cy++ ) { var nudgeX = 0; for( var cx = minChunkX; cx < maxChunkX; cx++ ) { var chunk = this.preRenderedChunks[cy % maxRealChunkY][cx % maxRealChunkX]; var x = -dx + cx * this.chunkSize - nudgeX; var y = -dy + cy * this.chunkSize - nudgeY; ig.system.context.drawImage( chunk, x, y); ig.Image.drawCount++; if( this.debugChunks ) { ig.system.context.strokeStyle = '#f0f'; ig.system.context.strokeRect( x, y, this.chunkSize, this.chunkSize ); } // If we repeat in X and this chunks width wasn't the full chunk size // and the screen is not already filled, we need to draw anohter chunk // AND nudge it to be flush with the last chunk if( this.repeat && chunk.width < this.chunkSize && x + chunk.width < ig.system.realWidth ) { nudgeX = this.chunkSize - chunk.width; maxChunkX++; } } // Same as above, but for Y if( this.repeat && chunk.height < this.chunkSize && y + chunk.height < ig.system.realHeight ) { nudgeY = this.chunkSize - chunk.height; maxChunkY++; } } }, drawTiled: function() { var tile = 0, anim = null, tileOffsetX = (this.scroll.x / this.tilesize).toInt(), tileOffsetY = (this.scroll.y / this.tilesize).toInt(), pxOffsetX = this.scroll.x % this.tilesize, pxOffsetY = this.scroll.y % this.tilesize, pxMinX = -pxOffsetX - this.tilesize, pxMinY = -pxOffsetY - this.tilesize, pxMaxX = ig.system.width + this.tilesize - pxOffsetX, pxMaxY = ig.system.height + this.tilesize - pxOffsetY; // FIXME: could be sped up for non-repeated maps: restrict the for loops // to the map size instead of to the screen size and skip the 'repeat' // checks inside the loop. for( var mapY = -1, pxY = pxMinY; pxY < pxMaxY; mapY++, pxY += this.tilesize) { var tileY = mapY + tileOffsetY; // Repeat Y? if( tileY >= this.height || tileY < 0 ) { if( !this.repeat ) { continue; } tileY = tileY > 0 ? tileY % this.height : ((tileY+1) % this.height) + this.height - 1; } for( var mapX = -1, pxX = pxMinX; pxX < pxMaxX; mapX++, pxX += this.tilesize ) { var tileX = mapX + tileOffsetX; // Repeat X? if( tileX >= this.width || tileX < 0 ) { if( !this.repeat ) { continue; } tileX = tileX > 0 ? tileX % this.width : ((tileX+1) % this.width) + this.width - 1; } // Draw! if( (tile = this.data[tileY][tileX]) ) { if( (anim = this.anims[tile-1]) ) { anim.draw( pxX, pxY ); } else { this.tiles.drawTile( pxX, pxY, tile-1, this.tilesize ); } } } // end for x } // end for y } }); });