lychees's picture
Upload 569 files
87b3b3a
/**
* @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.passes;i++) { /* main loop */
this._emitLight(emittingCells, litCells, doneCells);
if (i+1 == this._options.passes) { continue; } /* not for the last pass */
emittingCells = this._computeEmitters(litCells, doneCells);
}
for (var litKey in litCells) { /* let the user know what and how is lit */
var parts = litKey.split(",");
var x = parseInt(parts[0]);
var y = parseInt(parts[1]);
lightingCallback(x, y, litCells[litKey]);
}
return this;
}
/**
* Compute one iteration from all emitting cells
* @param {object} emittingCells These emit light
* @param {object} litCells Add projected light to these
* @param {object} doneCells These already emitted, forbid them from further calculations
*/
ROT.Lighting.prototype._emitLight = function(emittingCells, litCells, doneCells) {
for (var key in emittingCells) {
var parts = key.split(",");
var x = parseInt(parts[0]);
var y = parseInt(parts[1]);
this._emitLightFromCell(x, y, emittingCells[key], litCells);
doneCells[key] = 1;
}
return this;
}
/**
* Prepare a list of emitters for next pass
* @param {object} litCells
* @param {object} doneCells
* @returns {object}
*/
ROT.Lighting.prototype._computeEmitters = function(litCells, doneCells) {
var result = {};
for (var key in litCells) {
if (key in doneCells) { continue; } /* already emitted */
var color = litCells[key];
if (key in this._reflectivityCache) {
var reflectivity = this._reflectivityCache[key];
} else {
var parts = key.split(",");
var x = parseInt(parts[0]);
var y = parseInt(parts[1]);
var reflectivity = this._reflectivityCallback(x, y);
this._reflectivityCache[key] = reflectivity;
}
if (reflectivity == 0) { continue; } /* will not reflect at all */
/* compute emission color */
var emission = [];
var intensity = 0;
for (var i=0;i<3;i++) {
var part = Math.round(color[i]*reflectivity);
emission[i] = part;
intensity += part;
}
if (intensity > 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;
}