UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

582 lines (581 loc) 14.5 kB
import { math } from "../../../core/math/math.js"; import { Vec4 } from "../../../core/math/vec4.js"; import { LAYERID_WORLD, MASK_AFFECT_LIGHTMAPPED, MASK_AFFECT_DYNAMIC, MASK_BAKE } from "../../../scene/constants.js"; import { Light, lightTypes } from "../../../scene/light.js"; import { Asset } from "../../asset/asset.js"; import { Component } from "../component.js"; const _properties = [ "type", "color", "intensity", "luminance", "shape", "affectSpecularity", "castShadows", "shadowDistance", "shadowIntensity", "shadowResolution", "shadowBias", "numCascades", "cascadeBlend", "bakeNumSamples", "bakeArea", "cascadeDistribution", "normalOffsetBias", "range", "innerConeAngle", "outerConeAngle", "falloffMode", "shadowType", "vsmBlurSize", "vsmBlurMode", "vsmBias", "cookieAsset", "cookie", "cookieIntensity", "cookieFalloff", "cookieChannel", "cookieAngle", "cookieScale", "cookieOffset", "shadowUpdateMode", "mask", "affectDynamic", "affectLightmapped", "bake", "bakeDir", "isStatic", "layers", "penumbraSize", "penumbraFalloff", "shadowSamples", "shadowBlockerSamples" ]; class LightComponent extends Component { _light; _evtLayersChanged = null; _evtLayerAdded = null; _evtLayerRemoved = null; _cookieAsset = null; _cookieAssetId = null; _cookieAssetAdd = false; _cookieMatrix = null; _shadowBias = 0.05; _cookieAngle = 0; _cookieScale = null; _castShadows = false; _affectSpecularity = true; _affectDynamic = true; _affectLightmapped = false; _bake = false; _layers = [LAYERID_WORLD]; _type = "directional"; constructor(system, entity) { super(system, entity); this._light = new Light(system.app.graphicsDevice, system.app.scene.clusteredLightingEnabled); this._light._node = entity; this._light.setColor(1, 1, 1); } get light() { return this._light; } set type(value) { if (this._type === value) return; this.removeLightFromLayers(); this._type = value; this._light.type = lightTypes[value]; this.refreshProperties(); } get type() { return this._type; } set color(value) { this._light.setColor(value); } get color() { return this._light.getColor(); } set intensity(value) { this._light.intensity = value; } get intensity() { return this._light.intensity; } set luminance(value) { this._light.luminance = value; } get luminance() { return this._light.luminance; } set shape(value) { this._light.shape = value; } get shape() { return this._light.shape; } set affectSpecularity(value) { this._affectSpecularity = value; this._light.affectSpecularity = value; } get affectSpecularity() { return this._affectSpecularity; } set castShadows(value) { this._castShadows = value; this._light.castShadows = value; } get castShadows() { return this._castShadows; } set shadowDistance(value) { this._light.shadowDistance = value; } get shadowDistance() { return this._light.shadowDistance; } set shadowIntensity(value) { this._light.shadowIntensity = value; } get shadowIntensity() { return this._light.shadowIntensity; } set shadowResolution(value) { this._light.shadowResolution = value; } get shadowResolution() { return this._light.shadowResolution; } set shadowBias(value) { this._shadowBias = value; this._light.shadowBias = -0.01 * math.clamp(value, 0, 1); } get shadowBias() { return this._shadowBias; } set numCascades(value) { this._light.numCascades = math.clamp(Math.floor(value), 1, 4); } get numCascades() { return this._light.numCascades; } set cascadeBlend(value) { this._light.cascadeBlend = math.clamp(value, 0, 1); } get cascadeBlend() { return this._light.cascadeBlend; } set bakeNumSamples(value) { this._light.bakeNumSamples = math.clamp(Math.floor(value), 1, 255); } get bakeNumSamples() { return this._light.bakeNumSamples; } set bakeArea(value) { this._light.bakeArea = math.clamp(value, 0, 180); } get bakeArea() { return this._light.bakeArea; } set cascadeDistribution(value) { this._light.cascadeDistribution = math.clamp(value, 0, 1); } get cascadeDistribution() { return this._light.cascadeDistribution; } set normalOffsetBias(value) { this._light.normalOffsetBias = math.clamp(value, 0, 1); } get normalOffsetBias() { return this._light.normalOffsetBias; } set range(value) { this._light.attenuationEnd = value; } get range() { return this._light.attenuationEnd; } set innerConeAngle(value) { this._light.innerConeAngle = value; } get innerConeAngle() { return this._light.innerConeAngle; } set outerConeAngle(value) { this._light.outerConeAngle = value; } get outerConeAngle() { return this._light.outerConeAngle; } set falloffMode(value) { this._light.falloffMode = value; } get falloffMode() { return this._light.falloffMode; } set shadowType(value) { this._light.shadowType = value; } get shadowType() { return this._light.shadowType; } set vsmBlurSize(value) { this._light.vsmBlurSize = value; } get vsmBlurSize() { return this._light.vsmBlurSize; } set vsmBlurMode(value) { this._light.vsmBlurMode = value; } get vsmBlurMode() { return this._light.vsmBlurMode; } set vsmBias(value) { this._light.vsmBias = math.clamp(value, 0, 1); } get vsmBias() { return this._light.vsmBias; } set cookieAsset(value) { if (this._cookieAssetId && (value instanceof Asset && value.id === this._cookieAssetId || value === this._cookieAssetId)) { return; } this.onCookieAssetRemove(); this._cookieAssetId = null; if (value instanceof Asset) { this._cookieAssetId = value.id; this.onCookieAssetAdd(value); } else if (typeof value === "number") { this._cookieAssetId = value; const asset = this.system.app.assets.get(value); if (asset) { this.onCookieAssetAdd(asset); } else { this._cookieAssetAdd = true; this.system.app.assets.on(`add:${this._cookieAssetId}`, this.onCookieAssetAdd, this); } } } get cookieAsset() { return this._cookieAssetId; } set cookie(value) { this._light.cookie = value; } get cookie() { return this._light.cookie; } set cookieIntensity(value) { this._light.cookieIntensity = math.clamp(value, 0, 1); } get cookieIntensity() { return this._light.cookieIntensity; } set cookieFalloff(value) { this._light.cookieFalloff = value; } get cookieFalloff() { return this._light.cookieFalloff; } set cookieChannel(value) { this._light.cookieChannel = value; } get cookieChannel() { return this._light.cookieChannel; } set cookieAngle(value) { if (this._cookieAngle === value) return; this._cookieAngle = value; if (value !== 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(value * math.DEG_TO_RAD); const s = Math.sin(value * 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; } } get cookieAngle() { return this._cookieAngle; } set cookieScale(value) { this._cookieScale = value; if (value !== null || this._cookieAngle !== 0) { if (!this._cookieMatrix) this._cookieMatrix = new Vec4(); const scx = value ? value.x : 1; const scy = value ? value.y : 1; 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; } } get cookieScale() { return this._cookieScale; } set cookieOffset(value) { this._light.cookieOffset = value; } get cookieOffset() { return this._light.cookieOffset; } set shadowUpdateMode(value) { this._light.shadowUpdateMode = value; } get shadowUpdateMode() { return this._light.shadowUpdateMode; } set mask(value) { this._light.mask = value; } get mask() { return this._light.mask; } set affectDynamic(value) { if (this._affectDynamic === value) return; this._affectDynamic = value; if (value) { this._light.mask |= MASK_AFFECT_DYNAMIC; } else { this._light.mask &= ~MASK_AFFECT_DYNAMIC; } this._light.layersDirty(); } get affectDynamic() { return this._affectDynamic; } set affectLightmapped(value) { if (this._affectLightmapped === value) return; this._affectLightmapped = value; if (value) { 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; } } get affectLightmapped() { return this._affectLightmapped; } set bake(value) { if (this._bake === value) return; this._bake = value; if (value) { 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(); } get bake() { return this._bake; } set bakeDir(value) { this._light.bakeDir = value; } get bakeDir() { return this._light.bakeDir; } set isStatic(value) { this._light.isStatic = value; } get isStatic() { return this._light.isStatic; } set layers(value) { const oldValue = this._layers; 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); } this._layers = value; for (let i = 0; i < value.length; i++) { const layer = this.system.app.scene.layers.getLayerById(value[i]); if (!layer) continue; if (this.enabled && this.entity.enabled) { layer.addLight(this); this._light.addLayer(layer); } } } get layers() { return this._layers; } set shadowUpdateOverrides(values) { this._light.shadowUpdateOverrides = values; } get shadowUpdateOverrides() { return this._light.shadowUpdateOverrides; } set shadowSamples(value) { this._light.shadowSamples = value; } get shadowSamples() { return this._light.shadowSamples; } set shadowBlockerSamples(value) { this._light.shadowBlockerSamples = value; } get shadowBlockerSamples() { return this._light.shadowBlockerSamples; } set penumbraSize(value) { this._light.penumbraSize = value; } get penumbraSize() { return this._light.penumbraSize; } set penumbraFalloff(value) { this._light.penumbraFalloff = value; } get penumbraFalloff() { return this._light.penumbraFalloff; } 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]; this[name] = this[name]; } 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?.off(); this._evtLayersChanged = scene.on("set:layers", this.onLayersChanged, this); if (layers) { this._evtLayerAdded?.off(); this._evtLayerAdded = layers.on("add", this.onLayerAdded, this); this._evtLayerRemoved?.off(); 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() { this.onDisable(); this._light.destroy(); this.cookieAsset = null; } } export { LightComponent, _properties };