/** * @class Lighting computation, based on a traditional FOV for multiple light sources and multiple passes. * @param {function} reflectivityCallback Callback to retrieve cell reflectivity (0..1) * @param {object} [options] * @param {int} [options.passes=1] Number of passes. 1 equals to simple FOV of all light sources, >1 means a *highly simplified* radiosity-like algorithm. * @param {int} [options.emissionThreshold=100] Cells with emissivity > threshold will be treated as light source in the next pass. * @param {int} [options.range=10] Max light range */ ROT.Lighting = function(reflectivityCallback, options) { this._reflectivityCallback = reflectivityCallback; this._options = { passes: 1, emissionThreshold: 100, range: 10 }; this._fov = null; this._lights = {}; this._reflectivityCache = {}; this._fovCache = {}; this.setOptions(options); } /** * Adjust options at runtime * @see ROT.Lighting * @param {object} [options] */ ROT.Lighting.prototype.setOptions = function(options) { for (var p in options) { this._options[p] = options[p]; } if (options.range) { this.reset(); } return this; } /** * Set the used Field-Of-View algo * @param {ROT.FOV} fov */ ROT.Lighting.prototype.setFOV = function(fov) { this._fov = fov; this._fovCache = {}; return this; } /** * Set (or remove) a light source * @param {int} x * @param {int} y * @param {null || string || number[3]} color */ ROT.Lighting.prototype.setLight = function(x, y, color) { var key = x+","+y; if (color) { this._lights[key] = (typeof(color) == "string" ? ROT.Color.fromString(color) : color); } else { delete this._lights[key]; } return this; } /** * Reset the pre-computed topology values. Call whenever the underlying map changes its light-passability. */ ROT.Lighting.prototype.reset = function() { this._reflectivityCache = {}; this._fovCache = {}; return this; } /** * Compute the lighting * @param {function} lightingCallback Will be called with (x, y, color) for every lit cell */ ROT.Lighting.prototype.compute = function(lightingCallback) { var doneCells = {}; var emittingCells = {}; var litCells = {}; for (var key in this._lights) { /* prepare emitters for first pass */ var light = this._lights[key]; if (!(key in emittingCells)) { emittingCells[key] = [0, 0, 0]; } ROT.Color.add_(emittingCells[key], light); } for (var i=0;i this._options.emissionThreshold) { result[key] = emission; } } return result; } /** * Compute one iteration from one cell * @param {int} x * @param {int} y * @param {number[]} color * @param {object} litCells Cell data to by updated */ ROT.Lighting.prototype._emitLightFromCell = function(x, y, color, litCells) { var key = x+","+y; if (key in this._fovCache) { var fov = this._fovCache[key]; } else { var fov = this._updateFOV(x, y); } for (var fovKey in fov) { var formFactor = fov[fovKey]; if (fovKey in litCells) { /* already lit */ var result = litCells[fovKey]; } else { /* newly lit */ var result = [0, 0, 0]; litCells[fovKey] = result; } for (var i=0;i<3;i++) { result[i] += Math.round(color[i]*formFactor); } /* add light color */ } return this; } /** * Compute FOV ("form factor") for a potential light source at [x,y] * @param {int} x * @param {int} y * @returns {object} */ ROT.Lighting.prototype._updateFOV = function(x, y) { var key1 = x+","+y; var cache = {}; this._fovCache[key1] = cache; var range = this._options.range; var cb = function(x, y, r, vis) { var key2 = x+","+y; var formFactor = vis * (1-r/range); if (formFactor == 0) { return; } cache[key2] = formFactor; } this._fov.compute(x, y, range, cb.bind(this)); return cache; }