Spaces:
Running
Running
/** | |
* @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; | |
} | |