UNPKG

rot-js

Version:

A roguelike toolkit in JavaScript

150 lines (149 loc) 5.97 kB
import FOV from "./fov.js"; /** Octants used for translating recursive shadowcasting offsets */ const OCTANTS = [ [-1, 0, 0, 1], [0, -1, 1, 0], [0, -1, -1, 0], [-1, 0, 0, -1], [1, 0, 0, -1], [0, 1, -1, 0], [0, 1, 1, 0], [1, 0, 0, 1] ]; /** * @class Recursive shadowcasting algorithm * Currently only supports 4/8 topologies, not hexagonal. * Based on Peter Harkins' implementation of Björn Bergström's algorithm described here: http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting * @augments ROT.FOV */ export default class RecursiveShadowcasting extends FOV { /** * Compute visibility for a 360-degree circle * @param {int} x * @param {int} y * @param {int} R Maximum visibility radius * @param {function} callback */ compute(x, y, R, callback) { //You can always see your own tile callback(x, y, 0, 1); for (let i = 0; i < OCTANTS.length; i++) { this._renderOctant(x, y, OCTANTS[i], R, callback); } } /** * Compute visibility for a 180-degree arc * @param {int} x * @param {int} y * @param {int} R Maximum visibility radius * @param {int} dir Direction to look in (expressed in a ROT.DIRS value); * @param {function} callback */ compute180(x, y, R, dir, callback) { //You can always see your own tile callback(x, y, 0, 1); let previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 180 degrees let nextPreviousOctant = (dir - 2 + 8) % 8; //Need to retrieve the previous two octants to render a full 180 degrees let nextOctant = (dir + 1 + 8) % 8; //Need to grab to next octant to render a full 180 degrees this._renderOctant(x, y, OCTANTS[nextPreviousOctant], R, callback); this._renderOctant(x, y, OCTANTS[previousOctant], R, callback); this._renderOctant(x, y, OCTANTS[dir], R, callback); this._renderOctant(x, y, OCTANTS[nextOctant], R, callback); } ; /** * Compute visibility for a 90-degree arc * @param {int} x * @param {int} y * @param {int} R Maximum visibility radius * @param {int} dir Direction to look in (expressed in a ROT.DIRS value); * @param {function} callback */ compute90(x, y, R, dir, callback) { //You can always see your own tile callback(x, y, 0, 1); let previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 90 degrees this._renderOctant(x, y, OCTANTS[dir], R, callback); this._renderOctant(x, y, OCTANTS[previousOctant], R, callback); } /** * Render one octant (45-degree arc) of the viewshed * @param {int} x * @param {int} y * @param {int} octant Octant to be rendered * @param {int} R Maximum visibility radius * @param {function} callback */ _renderOctant(x, y, octant, R, callback) { //Radius incremented by 1 to provide same coverage area as other shadowcasting radiuses this._castVisibility(x, y, 1, 1.0, 0.0, R + 1, octant[0], octant[1], octant[2], octant[3], callback); } /** * Actually calculates the visibility * @param {int} startX The starting X coordinate * @param {int} startY The starting Y coordinate * @param {int} row The row to render * @param {float} visSlopeStart The slope to start at * @param {float} visSlopeEnd The slope to end at * @param {int} radius The radius to reach out to * @param {int} xx * @param {int} xy * @param {int} yx * @param {int} yy * @param {function} callback The callback to use when we hit a block that is visible */ _castVisibility(startX, startY, row, visSlopeStart, visSlopeEnd, radius, xx, xy, yx, yy, callback) { if (visSlopeStart < visSlopeEnd) { return; } for (let i = row; i <= radius; i++) { let dx = -i - 1; let dy = -i; let blocked = false; let newStart = 0; //'Row' could be column, names here assume octant 0 and would be flipped for half the octants while (dx <= 0) { dx += 1; //Translate from relative coordinates to map coordinates let mapX = startX + dx * xx + dy * xy; let mapY = startY + dx * yx + dy * yy; //Range of the row let slopeStart = (dx - 0.5) / (dy + 0.5); let slopeEnd = (dx + 0.5) / (dy - 0.5); //Ignore if not yet at left edge of Octant if (slopeEnd > visSlopeStart) { continue; } //Done if past right edge if (slopeStart < visSlopeEnd) { break; } //If it's in range, it's visible if ((dx * dx + dy * dy) < (radius * radius)) { callback(mapX, mapY, i, 1); } if (!blocked) { //If tile is a blocking tile, cast around it if (!this._lightPasses(mapX, mapY) && i < radius) { blocked = true; this._castVisibility(startX, startY, i + 1, visSlopeStart, slopeStart, radius, xx, xy, yx, yy, callback); newStart = slopeEnd; } } else { //Keep narrowing if scanning across a block if (!this._lightPasses(mapX, mapY)) { newStart = slopeEnd; continue; } //Block has ended blocked = false; visSlopeStart = newStart; } } if (blocked) { break; } } } }