254 lines
7.4 KiB
JavaScript
254 lines
7.4 KiB
JavaScript
|
|
/**
|
||
|
|
* 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});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|