Files
Foundry-VTT-Docker/resources/app/client/pixi/layers/effects/illumination-effects.js

254 lines
7.4 KiB
JavaScript
Raw Permalink Normal View History

2025-01-04 00:34:03 +01:00
/**
* A CanvasLayer for displaying illumination visual effects
* @category - Canvas
*/
class CanvasIlluminationEffects extends CanvasLayer {
constructor() {
super();
this.#initialize();
}
/**
* The filter used to mask visual effects on this layer
* @type {VisualEffectsMaskingFilter}
*/
filter;
/**
* The container holding the lights.
* @type {PIXI.Container}
*/
lights = new PIXI.Container();
/**
* A minimalist texture that holds the background color.
* @type {PIXI.Texture}
*/
backgroundColorTexture;
/**
* The background color rgb array.
* @type {number[]}
*/
#backgroundColorRGB;
/**
* The base line mesh.
* @type {SpriteMesh}
*/
baselineMesh = new SpriteMesh();
/**
* The cached container holding the illumination meshes.
* @type {CachedContainer}
*/
darknessLevelMeshes = new DarknessLevelContainer();
/* -------------------------------------------- */
/**
* To know if dynamic darkness level is active on this scene.
* @returns {boolean}
*/
get hasDynamicDarknessLevel() {
return this.darknessLevelMeshes.children.length > 0;
}
/**
* The illumination render texture.
* @returns {PIXI.RenderTexture}
*/
get renderTexture() {
return this.darknessLevelMeshes.renderTexture;
}
/* -------------------------------------------- */
/**
* Initialize the layer.
*/
#initialize() {
// Configure background color texture
this.backgroundColorTexture = this._createBackgroundColorTexture();
// Configure the base line mesh
this.baselineMesh.setShaderClass(BaselineIlluminationSamplerShader);
this.baselineMesh.texture = this.darknessLevelMeshes.renderTexture;
// Add children
canvas.masks.addChild(this.darknessLevelMeshes); // Region meshes cached container
this.addChild(this.lights); // Light and vision illumination
// Add baseline rendering for light
const originalRender = this.lights.render;
const baseMesh = this.baselineMesh;
this.lights.render = renderer => {
baseMesh.render(renderer);
originalRender.call(this.lights, renderer);
};
// Configure
this.lights.sortableChildren = true;
}
/* -------------------------------------------- */
/**
* Set or retrieve the illumination background color.
* @param {number} color
*/
set backgroundColor(color) {
const cb = this.#backgroundColorRGB = Color.from(color).rgb;
if ( this.filter ) this.filter.uniforms.replacementColor = cb;
this.backgroundColorTexture.baseTexture.resource.data.set(cb);
this.backgroundColorTexture.baseTexture.resource.update();
}
/* -------------------------------------------- */
/**
* Clear illumination effects container
*/
clear() {
this.lights.removeChildren();
}
/* -------------------------------------------- */
/**
* Invalidate the cached container state to trigger a render pass.
* @param {boolean} [force=false] Force cached container invalidation?
*/
invalidateDarknessLevelContainer(force=false) {
// If global light is enabled, the darkness level texture is affecting the vision mask
if ( canvas.environment.globalLightSource.active ) canvas.masks.vision.renderDirty = true;
if ( !(this.hasDynamicDarknessLevel || force) ) return;
this.darknessLevelMeshes.renderDirty = true;
// Sort by adjusted darkness level in descending order such that the final darkness level
// at a point is the minimum of the adjusted darkness levels
const compare = (a, b) => b.shader.darknessLevel - a.shader.darknessLevel;
this.darknessLevelMeshes.children.sort(compare);
canvas.visibility.vision.light.global.meshes.children.sort(compare);
}
/* -------------------------------------------- */
/**
* Create the background color texture used by illumination point source meshes.
* 1x1 single pixel texture.
* @returns {PIXI.Texture} The background color texture.
* @protected
*/
_createBackgroundColorTexture() {
return PIXI.Texture.fromBuffer(new Float32Array(3), 1, 1, {
type: PIXI.TYPES.FLOAT,
format: PIXI.FORMATS.RGB,
wrapMode: PIXI.WRAP_MODES.CLAMP,
scaleMode: PIXI.SCALE_MODES.NEAREST,
mipmap: PIXI.MIPMAP_MODES.OFF
});
}
/* -------------------------------------------- */
/** @override */
render(renderer) {
// Prior blend mode is reinitialized. The first render into PointSourceMesh will use the background color texture.
PointSourceMesh._priorBlendMode = undefined;
PointSourceMesh._currentTexture = this.backgroundColorTexture;
super.render(renderer);
}
/* -------------------------------------------- */
/** @override */
async _draw(options) {
const maskingFilter = CONFIG.Canvas.visualEffectsMaskingFilter;
this.darknessLevel = canvas.darknessLevel;
this.filter = maskingFilter.create({
visionTexture: canvas.masks.vision.renderTexture,
darknessLevelTexture: canvas.effects.illumination.renderTexture,
mode: maskingFilter.FILTER_MODES.ILLUMINATION
});
this.filter.blendMode = PIXI.BLEND_MODES.MULTIPLY;
this.filterArea = canvas.app.renderer.screen;
this.filters = [this.filter];
canvas.effects.visualEffectsMaskingFilters.add(this.filter);
}
/* -------------------------------------------- */
/** @override */
async _tearDown(options) {
canvas.effects.visualEffectsMaskingFilters.delete(this.filter);
this.clear();
}
/* -------------------------------------------- */
/* Deprecations */
/* -------------------------------------------- */
/**
* @deprecated since v11
* @ignore
*/
updateGlobalLight() {
const msg = "CanvasIlluminationEffects#updateGlobalLight has been deprecated.";
foundry.utils.logCompatibilityWarning(msg, {since: 11, until: 13});
return false;
}
/**
* @deprecated since v12
* @ignore
*/
background() {
const msg = "CanvasIlluminationEffects#background is now obsolete.";
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
return null;
}
/**
* @deprecated since v12
* @ignore
*/
get globalLight() {
const msg = "CanvasIlluminationEffects#globalLight has been deprecated without replacement. Check the" +
"canvas.environment.globalLightSource.active instead.";
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
return canvas.environment.globalLightSource.active;
}
}
/**
* Cached container used for dynamic darkness level. Display objects (of any type) added to this cached container will
* contribute to computing the darkness level of the masked area. Only the red channel is utilized, which corresponds
* to the desired darkness level. Other channels are ignored.
*/
class DarknessLevelContainer extends CachedContainer {
constructor(...args) {
super(...args);
this.autoRender = false;
this.on("childAdded", this.#onChildChange);
this.on("childRemoved", this.#onChildChange);
}
/** @override */
static textureConfiguration = {
scaleMode: PIXI.SCALE_MODES.NEAREST,
format: PIXI.FORMATS.RED,
multisample: PIXI.MSAA_QUALITY.NONE,
mipmap: PIXI.MIPMAP_MODES.OFF
};
/**
* Called when a display object is added or removed from this container.
*/
#onChildChange() {
this.autoRender = this.children.length > 0;
this.renderDirty = true;
canvas.perception.update({refreshVisionSources: true, refreshLightSources: true});
}
}