Initial
This commit is contained in:
253
resources/app/client/pixi/layers/effects/illumination-effects.js
Normal file
253
resources/app/client/pixi/layers/effects/illumination-effects.js
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* 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});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user