playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
582 lines (581 loc) • 14.5 kB
JavaScript
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
};