UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

1,126 lines (1,123 loc) 37.9 kB
import { math } from '../../../core/math/math.js'; import { Vec4 } from '../../../core/math/vec4.js'; import { MASK_AFFECT_DYNAMIC, MASK_AFFECT_LIGHTMAPPED, MASK_BAKE } from '../../../scene/constants.js'; import { Asset } from '../../asset/asset.js'; import { Component } from '../component.js'; import { properties } from './data.js'; /** * @import { Color } from '../../../core/math/color.js' * @import { EventHandle } from '../../../core/event-handle.js' * @import { LightComponentData } from './data.js' * @import { Light } from '../../../scene/light.js' * @import { Texture } from '../../../platform/graphics/texture.js' * @import { Vec2 } from '../../../core/math/vec2.js' */ /** * The LightComponent enables an {@link Entity} to light the scene. There are three types of light: * * - `directional`: A global light that emits light in the direction of the negative y-axis of the * owner entity. Emulates light sources that appear to be infinitely far away such as the sun. The * owner entity's position is effectively ignored. * - `omni`: A local light that emits light in all directions from the owner entity's position. * Emulates candles, lamps, bulbs, etc. * - `spot`: A local light that emits light similarly to an omni light but is bounded by a cone * centered on the owner entity's negative y-axis. Emulates flashlights, spotlights, etc. * * You should never need to use the LightComponent constructor directly. To add an LightComponent * to an {@link Entity}, use {@link Entity#addComponent}: * * ```javascript * const entity = new pc.Entity(); * entity.addComponent('light', { * type: 'omni', * color: new pc.Color(1, 0, 0), * intensity: 2 * }); * ``` * * Once the LightComponent is added to the entity, you can access it via the {@link Entity#light} * property: * * ```javascript * entity.light.intensity = 3; // Set the intensity of the light * * console.log(entity.light.intensity); // Get the intensity of the light * ``` * * Relevant Engine API examples: * * - [Area Lights](https://playcanvas.github.io/#/graphics/area-lights) * - [Clustered Area Lights](https://playcanvas.github.io/#/graphics/clustered-area-lights) * - [Clustered Lighting](https://playcanvas.github.io/#/graphics/clustered-lighting) * - [Clustered Onmi Shadows](https://playcanvas.github.io/#/graphics/clustered-omni-shadows) * - [Clustered Spot Shadows](https://playcanvas.github.io/#/graphics/clustered-spot-shadows) * - [Lights](https://playcanvas.github.io/#/graphics/lights) * * @hideconstructor * @category Graphics */ class LightComponent extends Component { // TODO: Remove this override in upgrading component /** * @type {LightComponentData} * @ignore */ get data() { const record = this.system.store[this.entity.getGuid()]; return record ? record.data : null; } /** * Sets the enabled state of the component. * * @type {boolean} */ set enabled(arg) { this._setValue('enabled', arg, function(newValue, oldValue) { this.onSetEnabled(null, oldValue, newValue); }); } /** * Gets the enabled state of the component. * * @type {boolean} */ get enabled() { return this.data.enabled; } /** * @type {Light} * @ignore */ set light(arg) { this._setValue('light', arg); } /** * @type {Light} * @ignore */ get light() { return this.data.light; } /** * Sets the type of the light. Can be: * * - "directional": A light that is infinitely far away and lights the entire scene from one * direction. * - "omni": An omni-directional light that illuminates in all directions from the light source. * - "spot": An omni-directional light but is bounded by a cone. * * Defaults to "directional". * * @type {string} */ set type(arg) { this._setValue('type', arg, function(newValue, oldValue) { this.system.changeType(this, oldValue, newValue); this.refreshProperties(); }); } /** * Gets the type of the light. * * @type {string} */ get type() { return this.data.type; } /** * Sets the color of the light. The alpha component of the color is ignored. Defaults to white * (`[1, 1, 1]`). * * @type {Color}; */ set color(arg) { this._setValue('color', arg, function(newValue, oldValue) { this.light.setColor(newValue); }, true); } /** * Gets the color of the light. * * @type {Color}; */ get color() { return this.data.color; } /** * Sets the brightness of the light. Defaults to 1. * * @type {number} */ set intensity(arg) { this._setValue('intensity', arg, function(newValue, oldValue) { this.light.intensity = newValue; }); } /** * Gets the brightness of the light. * * @type {number} */ get intensity() { return this.data.intensity; } /** * Sets the physically-based luminance. Only used if `scene.physicalUnits` is true. Defaults to 0. * * @type {number} */ set luminance(arg) { this._setValue('luminance', arg, function(newValue, oldValue) { this.light.luminance = newValue; }); } /** * Gets the physically-based luminance. * * @type {number} */ get luminance() { return this.data.luminance; } /** * Sets the light source shape. Can be: * * - {@link LIGHTSHAPE_PUNCTUAL}: Infinitesimally small point. * - {@link LIGHTSHAPE_RECT}: Rectangle shape. * - {@link LIGHTSHAPE_DISK}: Disk shape. * - {@link LIGHTSHAPE_SPHERE}: Sphere shape. * * Defaults to pc.LIGHTSHAPE_PUNCTUAL. * * @type {number} */ set shape(arg) { this._setValue('shape', arg, function(newValue, oldValue) { this.light.shape = newValue; }); } /** * Gets the light source shape. * * @type {number} */ get shape() { return this.data.shape; } /** * Sets whether material specularity will be affected by this light. Ignored for lights other * than {@link LIGHTTYPE_DIRECTIONAL}. Defaults to true. * * @type {boolean} */ set affectSpecularity(arg) { this._setValue('affectSpecularity', arg, function(newValue, oldValue) { this.light.affectSpecularity = newValue; }); } /** * Gets whether material specularity will be affected by this light. * * @type {boolean} */ get affectSpecularity() { return this.data.affectSpecularity; } /** * Sets whether the light will cast shadows. Defaults to false. * * @type {boolean} */ set castShadows(arg) { this._setValue('castShadows', arg, function(newValue, oldValue) { this.light.castShadows = newValue; }); } /** * Gets whether the light will cast shadows. * * @type {boolean} */ get castShadows() { return this.data.castShadows; } /** * Sets the distance from the viewpoint beyond which shadows are no longer rendered. Affects * directional lights only. Defaults to 40. * * @type {number} */ set shadowDistance(arg) { this._setValue('shadowDistance', arg, function(newValue, oldValue) { this.light.shadowDistance = newValue; }); } /** * Gets the distance from the viewpoint beyond which shadows are no longer rendered. * * @type {number} */ get shadowDistance() { return this.data.shadowDistance; } /** * Sets the intensity of the shadow darkening. 0 having no effect and 1 meaning shadows are * entirely black. Defaults to 1. * * @type {number} */ set shadowIntensity(arg) { this._setValue('shadowIntensity', arg, function(newValue, oldValue) { this.light.shadowIntensity = newValue; }); } /** * Gets the intensity of the shadow darkening. * * @type {number} */ get shadowIntensity() { return this.data.shadowIntensity; } /** * Sets the size of the texture used for the shadow map. Valid sizes are 64, 128, 256, 512, * 1024, 2048. Defaults to 1024. * * @type {number} */ set shadowResolution(arg) { this._setValue('shadowResolution', arg, function(newValue, oldValue) { this.light.shadowResolution = newValue; }); } /** * Gets the size of the texture used for the shadow map. * * @type {number} */ get shadowResolution() { return this.data.shadowResolution; } /** * Set the depth bias for tuning the appearance of the shadow mapping generated by this light. Valid * range is 0 to 1. Defaults to 0.05. * * @type {number} */ set shadowBias(arg) { this._setValue('shadowBias', arg, function(newValue, oldValue) { this.light.shadowBias = -0.01 * math.clamp(newValue, 0, 1); }); } /** * Get the depth bias for tuning the appearance of the shadow mapping generated by this light. * * @type {number} */ get shadowBias() { return this.data.shadowBias; } /** * Sets the number of shadow cascades. Can be 1, 2, 3 or 4. Defaults to 1, representing no * cascades. * * @type {number} */ set numCascades(arg) { this._setValue('numCascades', arg, function(newValue, oldValue) { this.light.numCascades = math.clamp(Math.floor(newValue), 1, 4); }); } /** * Gets the number of shadow cascades. * * @type {number} */ get numCascades() { return this.data.numCascades; } /** * Sets the blend factor for cascaded shadow maps, defining the fraction of each cascade level * used for blending between adjacent cascades. The value should be between 0 and 1, with * a default of 0, which disables blending between cascades. * * @type {number} */ set cascadeBlend(value) { this._setValue('cascadeBlend', value, function(newValue, oldValue) { this.light.cascadeBlend = math.clamp(newValue, 0, 1); }); } /** * Gets the blend factor for cascaded shadow maps. * * @type {number} */ get cascadeBlend() { return this.data.cascadeBlend; } /** * Sets the number of samples used to bake this light into the lightmap. Defaults to 1. Maximum * value is 255. * * @type {number} */ set bakeNumSamples(arg) { this._setValue('bakeNumSamples', arg, function(newValue, oldValue) { this.light.bakeNumSamples = math.clamp(Math.floor(newValue), 1, 255); }); } /** * Gets the number of samples used to bake this light into the lightmap. * * @type {number} */ get bakeNumSamples() { return this.data.bakeNumSamples; } /** * Sets the penumbra angle in degrees, allowing for a soft shadow boundary. Defaults to 0. * Requires `bake` to be set to true and the light type is {@link LIGHTTYPE_DIRECTIONAL}. * * @type {number} */ set bakeArea(arg) { this._setValue('bakeArea', arg, function(newValue, oldValue) { this.light.bakeArea = math.clamp(newValue, 0, 180); }); } /** * Gets the penumbra angle in degrees. * * @type {number} */ get bakeArea() { return this.data.bakeArea; } /** * Sets the distribution of subdivision of the camera frustum for individual shadow cascades. * Only used if {@link LightComponent#numCascades} is larger than 1. Can be a value in range of * 0 and 1. Value of 0 represents a linear distribution, value of 1 represents a logarithmic * distribution. Defaults to 0.5. Larger value increases the resolution of the shadows in the * near distance. * * @type {number} */ set cascadeDistribution(arg) { this._setValue('cascadeDistribution', arg, function(newValue, oldValue) { this.light.cascadeDistribution = math.clamp(newValue, 0, 1); }); } /** * Gets the distribution of subdivision of the camera frustum for individual shadow cascades. * * @type {number} */ get cascadeDistribution() { return this.data.cascadeDistribution; } /** * Sets the normal offset depth bias. Valid range is 0 to 1. Defaults to 0. * * @type {number} */ set normalOffsetBias(arg) { this._setValue('normalOffsetBias', arg, function(newValue, oldValue) { this.light.normalOffsetBias = math.clamp(newValue, 0, 1); }); } /** * Gets the normal offset depth bias. * * @type {number} */ get normalOffsetBias() { return this.data.normalOffsetBias; } /** * Sets the range of the light. Affects omni and spot lights only. Defaults to 10. * * @type {number} */ set range(arg) { this._setValue('range', arg, function(newValue, oldValue) { this.light.attenuationEnd = newValue; }); } /** * Gets the range of the light. * * @type {number} */ get range() { return this.data.range; } /** * Sets the angle at which the spotlight cone starts to fade off. The angle is specified in * degrees. Affects spot lights only. Defaults to 40. * * @type {number} */ set innerConeAngle(arg) { this._setValue('innerConeAngle', arg, function(newValue, oldValue) { this.light.innerConeAngle = newValue; }); } /** * Gets the angle at which the spotlight cone starts to fade off. * * @type {number} */ get innerConeAngle() { return this.data.innerConeAngle; } /** * Sets the angle at which the spotlight cone has faded to nothing. The angle is specified in * degrees. Affects spot lights only. Defaults to 45. * * @type {number} */ set outerConeAngle(arg) { this._setValue('outerConeAngle', arg, function(newValue, oldValue) { this.light.outerConeAngle = newValue; }); } /** * Gets the angle at which the spotlight cone has faded to nothing. * * @type {number} */ get outerConeAngle() { return this.data.outerConeAngle; } /** * Sets the fall off mode for the light. This controls the rate at which a light attenuates * from its position. Can be: * * - {@link LIGHTFALLOFF_LINEAR}: Linear. * - {@link LIGHTFALLOFF_INVERSESQUARED}: Inverse squared. * * Affects omni and spot lights only. Defaults to {@link LIGHTFALLOFF_LINEAR}. * * @type {number} */ set falloffMode(arg) { this._setValue('falloffMode', arg, function(newValue, oldValue) { this.light.falloffMode = newValue; }); } /** * Gets the fall off mode for the light. * * @type {number} */ get falloffMode() { return this.data.falloffMode; } /** * Sets the type of shadows being rendered by this light. Can be: * * - {@link SHADOW_PCF1_32F} * - {@link SHADOW_PCF3_32F} * - {@link SHADOW_PCF5_32F} * - {@link SHADOW_PCF1_16F} * - {@link SHADOW_PCF3_16F} * - {@link SHADOW_PCF5_16F} * - {@link SHADOW_VSM_16F} * - {@link SHADOW_VSM_32F} * - {@link SHADOW_PCSS_32F} * * @type {number} */ set shadowType(arg) { this._setValue('shadowType', arg, function(newValue, oldValue) { this.light.shadowType = newValue; }); } /** * Gets the type of shadows being rendered by this light. * * @type {number} */ get shadowType() { return this.data.shadowType; } /** * Sets the number of samples used for blurring a variance shadow map. Only uneven numbers * work, even are incremented. Minimum value is 1, maximum is 25. Defaults to 11. * * @type {number} */ set vsmBlurSize(arg) { this._setValue('vsmBlurSize', arg, function(newValue, oldValue) { this.light.vsmBlurSize = newValue; }); } /** * Gets the number of samples used for blurring a variance shadow map. * * @type {number} */ get vsmBlurSize() { return this.data.vsmBlurSize; } /** * Sets the blurring mode for variance shadow maps. Can be: * * - {@link BLUR_BOX}: Box filter. * - {@link BLUR_GAUSSIAN}: Gaussian filter. May look smoother than box, but requires more samples. * * @type {number} */ set vsmBlurMode(arg) { this._setValue('vsmBlurMode', arg, function(newValue, oldValue) { this.light.vsmBlurMode = newValue; }); } /** * Gets the blurring mode for variance shadow maps. * * @type {number} */ get vsmBlurMode() { return this.data.vsmBlurMode; } /** * Sets the VSM bias value. * * @type {number} */ set vsmBias(arg) { this._setValue('vsmBias', arg, function(newValue, oldValue) { this.light.vsmBias = math.clamp(newValue, 0, 1); }); } /** * Gets the VSM bias value. * * @type {number} */ get vsmBias() { return this.data.vsmBias; } /** * Sets the texture asset to be used as the cookie for this light. Only spot and omni lights can * have cookies. Defaults to null. * * @type {number|null} */ set cookieAsset(arg) { this._setValue('cookieAsset', arg, function(newValue, oldValue) { if (this._cookieAssetId && (newValue instanceof Asset && newValue.id === this._cookieAssetId || newValue === this._cookieAssetId)) { return; } this.onCookieAssetRemove(); this._cookieAssetId = null; if (newValue instanceof Asset) { this.data.cookieAsset = newValue.id; this._cookieAssetId = newValue.id; this.onCookieAssetAdd(newValue); } else if (typeof newValue === 'number') { this._cookieAssetId = newValue; const asset = this.system.app.assets.get(newValue); if (asset) { this.onCookieAssetAdd(asset); } else { this._cookieAssetAdd = true; this.system.app.assets.on(`add:${this._cookieAssetId}`, this.onCookieAssetAdd, this); } } }); } /** * Gets the texture asset to be used as the cookie for this light. * * @type {number|null} */ get cookieAsset() { return this.data.cookieAsset; } /** * Sets the texture to be used as the cookie for this light. Only spot and omni lights can have * cookies. Defaults to null. * * @type {Texture|null} */ set cookie(arg) { this._setValue('cookie', arg, function(newValue, oldValue) { this.light.cookie = newValue; }); } /** * Gets the texture to be used as the cookie for this light. * * @type {Texture|null} */ get cookie() { return this.data.cookie; } /** * Sets the cookie texture intensity. Defaults to 1. * * @type {number} */ set cookieIntensity(arg) { this._setValue('cookieIntensity', arg, function(newValue, oldValue) { this.light.cookieIntensity = math.clamp(newValue, 0, 1); }); } /** * Gets the cookie texture intensity. * * @type {number} */ get cookieIntensity() { return this.data.cookieIntensity; } /** * Sets whether normal spotlight falloff is active when a cookie texture is set. When set to * false, a spotlight will work like a pure texture projector (only fading with distance). * Default is false. * * @type {boolean} */ set cookieFalloff(arg) { this._setValue('cookieFalloff', arg, function(newValue, oldValue) { this.light.cookieFalloff = newValue; }); } /** * Gets whether normal spotlight falloff is active when a cookie texture is set. * * @type {boolean} */ get cookieFalloff() { return this.data.cookieFalloff; } /** * Sets the color channels of the cookie texture to use. Can be "r", "g", "b", "a", "rgb". * * @type {string} */ set cookieChannel(arg) { this._setValue('cookieChannel', arg, function(newValue, oldValue) { this.light.cookieChannel = newValue; }); } /** * Gets the color channels of the cookie texture to use. * * @type {string} */ get cookieChannel() { return this.data.cookieChannel; } /** * Sets the angle for spotlight cookie rotation (in degrees). * * @type {number} */ set cookieAngle(arg) { this._setValue('cookieAngle', arg, function(newValue, oldValue) { if (newValue !== 0 || this.cookieScale !== null) { if (!this._cookieMatrix) this._cookieMatrix = new Vec4(); let scx = 1; let scy = 1; if (this.cookieScale) { scx = this.cookieScale.x; scy = this.cookieScale.y; } const c = Math.cos(newValue * math.DEG_TO_RAD); const s = Math.sin(newValue * math.DEG_TO_RAD); this._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy); this.light.cookieTransform = this._cookieMatrix; } else { this.light.cookieTransform = null; } }); } /** * Gets the angle for spotlight cookie rotation (in degrees). * * @type {number} */ get cookieAngle() { return this.data.cookieAngle; } /** * Sets the spotlight cookie scale. * * @type {Vec2|null} */ set cookieScale(arg) { this._setValue('cookieScale', arg, function(newValue, oldValue) { if (newValue !== null || this.cookieAngle !== 0) { if (!this._cookieMatrix) this._cookieMatrix = new Vec4(); const scx = newValue.x; const scy = newValue.y; const c = Math.cos(this.cookieAngle * math.DEG_TO_RAD); const s = Math.sin(this.cookieAngle * math.DEG_TO_RAD); this._cookieMatrix.set(c / scx, -s / scx, s / scy, c / scy); this.light.cookieTransform = this._cookieMatrix; } else { this.light.cookieTransform = null; } }, true); } /** * Gets the spotlight cookie scale. * * @type {Vec2|null} */ get cookieScale() { return this.data.cookieScale; } /** * Sets the spotlight cookie position offset. * * @type {Vec2|null} */ set cookieOffset(arg) { this._setValue('cookieOffset', arg, function(newValue, oldValue) { this.light.cookieOffset = newValue; }, true); } /** * Gets the spotlight cookie position offset. * * @type {Vec2|null} */ get cookieOffset() { return this.data.cookieOffset; } /** * Sets the shadow update model. This tells the renderer how often shadows must be updated for * this light. Can be: * * - {@link SHADOWUPDATE_NONE}: Don't render shadows. * - {@link SHADOWUPDATE_THISFRAME}: Render shadows only once (then automatically switches * to {@link SHADOWUPDATE_NONE}. * - {@link SHADOWUPDATE_REALTIME}: Render shadows every frame (default). * * @type {number} */ set shadowUpdateMode(arg) { this._setValue('shadowUpdateMode', arg, function(newValue, oldValue) { this.light.shadowUpdateMode = newValue; }, true); } /** * Gets the shadow update model. * * @type {number} */ get shadowUpdateMode() { return this.data.shadowUpdateMode; } /** * Sets the mask to determine which {@link MeshInstance}s are lit by this light. Defaults to 1. * * @type {number} */ set mask(arg) { this._setValue('mask', arg, function(newValue, oldValue) { this.light.mask = newValue; }); } /** * Gets the mask to determine which {@link MeshInstance}s are lit by this light. * * @type {number} */ get mask() { return this.data.mask; } /** * Sets whether the light will affect non-lightmapped objects. * * @type {boolean} */ set affectDynamic(arg) { this._setValue('affectDynamic', arg, function(newValue, oldValue) { if (newValue) { this.light.mask |= MASK_AFFECT_DYNAMIC; } else { this.light.mask &= ~MASK_AFFECT_DYNAMIC; } this.light.layersDirty(); }); } /** * Gets whether the light will affect non-lightmapped objects. * * @type {boolean} */ get affectDynamic() { return this.data.affectDynamic; } /** * Sets whether the light will affect lightmapped objects. * * @type {boolean} */ set affectLightmapped(arg) { this._setValue('affectLightmapped', arg, function(newValue, oldValue) { if (newValue) { this.light.mask |= MASK_AFFECT_LIGHTMAPPED; if (this.bake) this.light.mask &= ~MASK_BAKE; } else { this.light.mask &= ~MASK_AFFECT_LIGHTMAPPED; if (this.bake) this.light.mask |= MASK_BAKE; } }); } /** * Gets whether the light will affect lightmapped objects. * * @type {boolean} */ get affectLightmapped() { return this.data.affectLightmapped; } /** * Sets whether the light will be rendered into lightmaps. * * @type {boolean} */ set bake(arg) { this._setValue('bake', arg, function(newValue, oldValue) { if (newValue) { this.light.mask |= MASK_BAKE; if (this.affectLightmapped) this.light.mask &= ~MASK_AFFECT_LIGHTMAPPED; } else { this.light.mask &= ~MASK_BAKE; if (this.affectLightmapped) this.light.mask |= MASK_AFFECT_LIGHTMAPPED; } this.light.layersDirty(); }); } /** * Gets whether the light will be rendered into lightmaps. * * @type {boolean} */ get bake() { return this.data.bake; } /** * Sets whether the light's direction will contribute to directional lightmaps. The light must * be enabled and `bake` set to true. Be aware, that directional lightmap is an approximation * and can only hold single direction per pixel. Intersecting multiple lights with bakeDir=true * may lead to incorrect look of specular/bump-mapping in the area of intersection. The error * is not always visible though, and highly scene-dependent. * * @type {boolean} */ set bakeDir(arg) { this._setValue('bakeDir', arg, function(newValue, oldValue) { this.light.bakeDir = newValue; }); } /** * Gets whether the light's direction will contribute to directional lightmaps. * * @type {boolean} */ get bakeDir() { return this.data.bakeDir; } /** * Sets whether the light ever moves. This is an optimization hint. * * @type {boolean} */ set isStatic(arg) { this._setValue('isStatic', arg, function(newValue, oldValue) { this.light.isStatic = newValue; }); } /** * Gets whether the light ever moves. * * @type {boolean} */ get isStatic() { return this.data.isStatic; } /** * Sets the array of layer IDs ({@link Layer#id}) to which this light should belong. Don't * push/pop/splice or modify this array. If you want to change it, set a new one instead. * * @type {number[]} */ set layers(arg) { this._setValue('layers', arg, function(newValue, oldValue) { for(let i = 0; i < oldValue.length; i++){ const layer = this.system.app.scene.layers.getLayerById(oldValue[i]); if (!layer) continue; layer.removeLight(this); this.light.removeLayer(layer); } for(let i = 0; i < newValue.length; i++){ const layer = this.system.app.scene.layers.getLayerById(newValue[i]); if (!layer) continue; if (this.enabled && this.entity.enabled) { layer.addLight(this); this.light.addLayer(layer); } } }); } /** * Gets the array of layer IDs ({@link Layer#id}) to which this light should belong. * * @type {number[]} */ get layers() { return this.data.layers; } /** * Sets an array of SHADOWUPDATE_ settings per shadow cascade. Set to undefined if not used. * * @type {number[] | null} */ set shadowUpdateOverrides(values) { this.light.shadowUpdateOverrides = values; } /** * Gets an array of SHADOWUPDATE_ settings per shadow cascade. * * @type {number[] | null} */ get shadowUpdateOverrides() { return this.light.shadowUpdateOverrides; } /** * Sets the number of shadow samples used for soft shadows when the shadow type is * {@link SHADOW_PCSS_32F}. This value must be a positive whole number starting at 1. Higher * values result in smoother shadows but can significantly decrease performance. Defaults to 16. * * @type {number} */ set shadowSamples(value) { this.light.shadowSamples = value; } /** * Gets the number of shadow samples used for soft shadows. * * @type {number} */ get shadowSamples() { return this.light.shadowSamples; } /** * Sets the number of blocker samples used for soft shadows when the shadow type is * {@link SHADOW_PCSS_32F}. These samples are used to estimate the distance between the shadow * caster and the shadow receiver, which is then used for the estimation of contact hardening in * the shadow. This value must be a positive whole number starting at 0. Higher values improve * shadow quality by considering more occlusion points, but can decrease performance. When set * to 0, contact hardening is disabled and the shadow has constant softness. Defaults to 16. Note * that this values can be lower than shadowSamples to optimize performance, often without large * impact on quality. * * @type {number} */ set shadowBlockerSamples(value) { this.light.shadowBlockerSamples = value; } /** * Gets the number of blocker samples used for contact hardening shadows. * * @type {number} */ get shadowBlockerSamples() { return this.light.shadowBlockerSamples; } /** * Sets the size of penumbra for contact hardening shadows. For area lights, acts as a * multiplier with the dimensions of the area light. For punctual and directional lights it's * the area size of the light. Defaults to 1. * * @type {number} */ set penumbraSize(value) { this.light.penumbraSize = value; } /** * Gets the size of penumbra for contact hardening shadows. * * @type {number} */ get penumbraSize() { return this.light.penumbraSize; } /** * Sets the falloff rate for shadow penumbra for contact hardening shadows. This is a value larger * than or equal to 1. This parameter determines how quickly the shadow softens with distance. * Higher values result in a faster softening of the shadow, while lower values produce a more * gradual transition. Defaults to 1. * * @type {number} */ set penumbraFalloff(value) { this.light.penumbraFalloff = value; } /** * Gets the falloff rate for shadow penumbra for contact hardening shadows. * * @type {number} */ get penumbraFalloff() { return this.light.penumbraFalloff; } /** @ignore */ _setValue(name, value, setFunc, skipEqualsCheck) { const data = this.data; const oldValue = data[name]; if (!skipEqualsCheck && oldValue === value) return; data[name] = value; if (setFunc) setFunc.call(this, value, oldValue); } addLightToLayers() { for(let i = 0; i < this.layers.length; i++){ const layer = this.system.app.scene.layers.getLayerById(this.layers[i]); if (layer) { layer.addLight(this); this.light.addLayer(layer); } } } removeLightFromLayers() { for(let i = 0; i < this.layers.length; i++){ const layer = this.system.app.scene.layers.getLayerById(this.layers[i]); if (layer) { layer.removeLight(this); this.light.removeLayer(layer); } } } onLayersChanged(oldComp, newComp) { if (this.enabled && this.entity.enabled) { this.addLightToLayers(); } oldComp.off('add', this.onLayerAdded, this); oldComp.off('remove', this.onLayerRemoved, this); newComp.on('add', this.onLayerAdded, this); newComp.on('remove', this.onLayerRemoved, this); } onLayerAdded(layer) { const index = this.layers.indexOf(layer.id); if (index >= 0 && this.enabled && this.entity.enabled) { layer.addLight(this); this.light.addLayer(layer); } } onLayerRemoved(layer) { const index = this.layers.indexOf(layer.id); if (index >= 0) { layer.removeLight(this); this.light.removeLayer(layer); } } refreshProperties() { for(let i = 0; i < properties.length; i++){ const name = properties[i]; /* eslint-disable no-self-assign */ this[name] = this[name]; /* eslint-enable no-self-assign */ } if (this.enabled && this.entity.enabled) { this.onEnable(); } } onCookieAssetSet() { let forceLoad = false; if (this._cookieAsset.type === 'cubemap' && !this._cookieAsset.loadFaces) { this._cookieAsset.loadFaces = true; forceLoad = true; } if (!this._cookieAsset.resource || forceLoad) this.system.app.assets.load(this._cookieAsset); if (this._cookieAsset.resource) { this.onCookieAssetLoad(); } } onCookieAssetAdd(asset) { if (this._cookieAssetId !== asset.id) return; this._cookieAsset = asset; if (this.light.enabled) { this.onCookieAssetSet(); } this._cookieAsset.on('load', this.onCookieAssetLoad, this); this._cookieAsset.on('remove', this.onCookieAssetRemove, this); } onCookieAssetLoad() { if (!this._cookieAsset || !this._cookieAsset.resource) { return; } this.cookie = this._cookieAsset.resource; } onCookieAssetRemove() { if (!this._cookieAssetId) { return; } if (this._cookieAssetAdd) { this.system.app.assets.off(`add:${this._cookieAssetId}`, this.onCookieAssetAdd, this); this._cookieAssetAdd = false; } if (this._cookieAsset) { this._cookieAsset.off('load', this.onCookieAssetLoad, this); this._cookieAsset.off('remove', this.onCookieAssetRemove, this); this._cookieAsset = null; } this.cookie = null; } onEnable() { const scene = this.system.app.scene; const layers = scene.layers; this.light.enabled = true; this._evtLayersChanged = scene.on('set:layers', this.onLayersChanged, this); if (layers) { this._evtLayerAdded = layers.on('add', this.onLayerAdded, this); this._evtLayerRemoved = layers.on('remove', this.onLayerRemoved, this); } if (this.enabled && this.entity.enabled) { this.addLightToLayers(); } if (this._cookieAsset && !this.cookie) { this.onCookieAssetSet(); } } onDisable() { const scene = this.system.app.scene; const layers = scene.layers; this.light.enabled = false; this._evtLayersChanged?.off(); this._evtLayersChanged = null; if (layers) { this._evtLayerAdded?.off(); this._evtLayerAdded = null; this._evtLayerRemoved?.off(); this._evtLayerRemoved = null; } this.removeLightFromLayers(); } onRemove() { // remove from layers this.onDisable(); // destroy light node this.light.destroy(); // remove cookie asset events this.cookieAsset = null; } constructor(...args){ super(...args), /** * @type {EventHandle|null} * @private */ this._evtLayersChanged = null, /** * @type {EventHandle|null} * @private */ this._evtLayerAdded = null, /** * @type {EventHandle|null} * @private */ this._evtLayerRemoved = null, /** @private */ this._cookieAsset = null, /** @private */ this._cookieAssetId = null, /** @private */ this._cookieAssetAdd = false, /** @private */ this._cookieMatrix = null; } } export { LightComponent };