playcanvas
Version:
PlayCanvas WebGL game engine
821 lines (818 loc) • 25 kB
JavaScript
import { math } from '../core/math/math.js';
import { Color } from '../core/math/color.js';
import { Mat4 } from '../core/math/mat4.js';
import { Vec2 } from '../core/math/vec2.js';
import { Vec3 } from '../core/math/vec3.js';
import { Vec4 } from '../core/math/vec4.js';
import { LIGHTTYPE_DIRECTIONAL, MASK_AFFECT_DYNAMIC, LIGHTFALLOFF_LINEAR, SHADOW_PCF3_32F, BLUR_GAUSSIAN, LIGHTSHAPE_PUNCTUAL, SHADOWUPDATE_REALTIME, SHADOWUPDATE_NONE, SHADOWUPDATE_THISFRAME, LIGHTTYPE_OMNI, shadowTypeInfo, SHADOW_PCSS_32F, SHADOW_PCF1_32F, SHADOW_PCF1_16F, SHADOW_PCF3_16F, SHADOW_VSM_32F, SHADOW_VSM_16F, MASK_BAKE, LIGHTTYPE_SPOT, MASK_AFFECT_LIGHTMAPPED, LIGHT_COLOR_DIVIDER } from './constants.js';
import { ShadowRenderer } from './renderer/shadow-renderer.js';
import { DepthState } from '../platform/graphics/depth-state.js';
import { FloatPacking } from '../core/math/float-packing.js';
const tmpVec = new Vec3();
const tmpBiases = {
bias: 0,
normalBias: 0
};
const tmpColor = new Color();
const chanId = {
r: 0,
g: 1,
b: 2,
a: 3
};
const lightTypes = {
'directional': LIGHTTYPE_DIRECTIONAL,
'omni': LIGHTTYPE_OMNI,
'point': LIGHTTYPE_OMNI,
'spot': LIGHTTYPE_SPOT
};
const directionalCascades = [
[
new Vec4(0, 0, 1, 1)
],
[
new Vec4(0, 0, 0.5, 0.5),
new Vec4(0, 0.5, 0.5, 0.5)
],
[
new Vec4(0, 0, 0.5, 0.5),
new Vec4(0, 0.5, 0.5, 0.5),
new Vec4(0.5, 0, 0.5, 0.5)
],
[
new Vec4(0, 0, 0.5, 0.5),
new Vec4(0, 0.5, 0.5, 0.5),
new Vec4(0.5, 0, 0.5, 0.5),
new Vec4(0.5, 0.5, 0.5, 0.5)
]
];
const channelMap = {
'rrr': 0b0001,
'ggg': 0b0010,
'bbb': 0b0100,
'aaa': 0b1000,
'rgb': 0b0111
};
let id = 0;
class LightRenderData {
constructor(camera, face, light){
this.light = light;
this.camera = camera;
this.shadowCamera = ShadowRenderer.createShadowCamera(light._shadowType, light._type, face);
this.shadowMatrix = new Mat4();
this.shadowViewport = new Vec4(0, 0, 1, 1);
this.shadowScissor = new Vec4(0, 0, 1, 1);
this.projectionCompensation = 0;
this.face = face;
this.visibleCasters = [];
this.viewBindGroups = [];
}
destroy() {
this.viewBindGroups.forEach((bg)=>{
bg.defaultUniformBuffer.destroy();
bg.destroy();
});
this.viewBindGroups.length = 0;
}
get shadowBuffer() {
const rt = this.shadowCamera.renderTarget;
if (rt) {
return this.light._isPcf ? rt.depthBuffer : rt.colorBuffer;
}
return null;
}
}
class Light {
constructor(graphicsDevice, clusteredLighting){
this.layers = new Set();
this.shadowDepthState = DepthState.DEFAULT.clone();
this.clusteredFlags = 0;
this.clusteredData = new Uint32Array(3);
this.clusteredData16 = new Uint16Array(this.clusteredData.buffer);
this._evtDeviceRestored = null;
this.device = graphicsDevice;
this.clusteredLighting = clusteredLighting;
this.id = id++;
this._evtDeviceRestored = graphicsDevice.on('devicerestored', this.onDeviceRestored, this);
this._type = LIGHTTYPE_DIRECTIONAL;
this._color = new Color(0.8, 0.8, 0.8);
this._intensity = 1;
this._affectSpecularity = true;
this._luminance = 0;
this._castShadows = false;
this._enabled = false;
this._mask = MASK_AFFECT_DYNAMIC;
this.isStatic = false;
this.key = 0;
this.bakeDir = true;
this.bakeNumSamples = 1;
this.bakeArea = 0;
this.attenuationStart = 10;
this.attenuationEnd = 10;
this._falloffMode = LIGHTFALLOFF_LINEAR;
this._shadowType = SHADOW_PCF3_32F;
this._vsmBlurSize = 11;
this.vsmBlurMode = BLUR_GAUSSIAN;
this.vsmBias = 0.01 * 0.25;
this._cookie = null;
this.cookieIntensity = 1;
this._cookieFalloff = true;
this._cookieChannel = 'rgb';
this._cookieTransform = null;
this._cookieTransformUniform = new Float32Array(4);
this._cookieOffset = null;
this._cookieOffsetUniform = new Float32Array(2);
this._cookieTransformSet = false;
this._cookieOffsetSet = false;
this._innerConeAngle = 40;
this._outerConeAngle = 45;
this.cascades = null;
this._shadowMatrixPalette = null;
this._shadowCascadeDistances = null;
this.numCascades = 1;
this._cascadeBlend = 0;
this.cascadeDistribution = 0.5;
this._shape = LIGHTSHAPE_PUNCTUAL;
this._colorLinear = new Float32Array(3);
this._updateLinearColor();
this._position = new Vec3(0, 0, 0);
this._direction = new Vec3(0, 0, 0);
this._innerConeAngleCos = Math.cos(this._innerConeAngle * math.DEG_TO_RAD);
this._updateOuterAngle(this._outerConeAngle);
this._usePhysicalUnits = undefined;
this._shadowMap = null;
this._shadowRenderParams = [];
this._shadowCameraParams = [];
this.shadowDistance = 40;
this._shadowResolution = 1024;
this._shadowBias = -5e-4;
this._shadowIntensity = 1.0;
this._normalOffsetBias = 0.0;
this.shadowUpdateMode = SHADOWUPDATE_REALTIME;
this.shadowUpdateOverrides = null;
this._isVsm = false;
this._isPcf = true;
this._softShadowParams = new Float32Array(4);
this.shadowSamples = 16;
this.shadowBlockerSamples = 16;
this.penumbraSize = 1.0;
this.penumbraFalloff = 1.0;
this._cookieMatrix = null;
this._atlasViewport = null;
this.atlasViewportAllocated = false;
this.atlasVersion = 0;
this.atlasSlotIndex = 0;
this.atlasSlotUpdated = false;
this._node = null;
this._renderData = [];
this.visibleThisFrame = false;
this.maxScreenSize = 0;
this._updateShadowBias();
}
destroy() {
this._evtDeviceRestored?.off();
this._evtDeviceRestored = null;
this._destroyShadowMap();
this.releaseRenderData();
this._renderData = null;
}
onDeviceRestored() {
if (this.shadowUpdateMode === SHADOWUPDATE_NONE) {
this.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
}
}
releaseRenderData() {
if (this._renderData) {
for(let i = 0; i < this._renderData.length; i++){
this._renderData[i].destroy();
}
this._renderData.length = 0;
}
}
addLayer(layer) {
this.layers.add(layer);
}
removeLayer(layer) {
this.layers.delete(layer);
}
set shadowSamples(value) {
this._softShadowParams[0] = value;
}
get shadowSamples() {
return this._softShadowParams[0];
}
set shadowBlockerSamples(value) {
this._softShadowParams[1] = value;
}
get shadowBlockerSamples() {
return this._softShadowParams[1];
}
set shadowBias(value) {
if (this._shadowBias !== value) {
this._shadowBias = value;
this._updateShadowBias();
}
}
get shadowBias() {
return this._shadowBias;
}
set numCascades(value) {
if (!this.cascades || this.numCascades !== value) {
this.cascades = directionalCascades[value - 1];
this._shadowMatrixPalette = new Float32Array(4 * 16);
this._shadowCascadeDistances = new Float32Array(4);
this._destroyShadowMap();
this.updateKey();
}
}
get numCascades() {
return this.cascades.length;
}
set cascadeBlend(value) {
if (this._cascadeBlend !== value) {
this._cascadeBlend = value;
this.updateKey();
}
}
get cascadeBlend() {
return this._cascadeBlend;
}
set shadowMap(shadowMap) {
if (this._shadowMap !== shadowMap) {
this._destroyShadowMap();
this._shadowMap = shadowMap;
}
}
get shadowMap() {
return this._shadowMap;
}
set mask(value) {
if (this._mask !== value) {
this._mask = value;
this.updateKey();
this.updateClusteredFlags();
}
}
get mask() {
return this._mask;
}
get numShadowFaces() {
const type = this._type;
if (type === LIGHTTYPE_DIRECTIONAL) {
return this.numCascades;
} else if (type === LIGHTTYPE_OMNI) {
return 6;
}
return 1;
}
set type(value) {
if (this._type === value) {
return;
}
this._type = value;
this._destroyShadowMap();
this._updateShadowBias();
this.updateKey();
this.updateClusteredFlags();
const stype = this._shadowType;
this._shadowType = null;
this.shadowUpdateOverrides = null;
this.shadowType = stype;
}
get type() {
return this._type;
}
set shape(value) {
if (this._shape === value) {
return;
}
this._shape = value;
this._destroyShadowMap();
this.updateKey();
this.updateClusteredFlags();
const stype = this._shadowType;
this._shadowType = null;
this.shadowType = stype;
}
get shape() {
return this._shape;
}
set usePhysicalUnits(value) {
if (this._usePhysicalUnits !== value) {
this._usePhysicalUnits = value;
this._updateLinearColor();
}
}
get usePhysicalUnits() {
return this._usePhysicalUnits;
}
set shadowType(value) {
if (this._shadowType === value) {
return;
}
let shadowInfo = shadowTypeInfo.get(value);
if (!shadowInfo) {
value = SHADOW_PCF3_32F;
}
const device = this.device;
if (value === SHADOW_PCSS_32F && (!device.textureFloatRenderable || !device.textureFloatFilterable)) {
value = SHADOW_PCF3_32F;
}
if (this._type === LIGHTTYPE_OMNI && value !== SHADOW_PCF1_32F && value !== SHADOW_PCF3_32F && value !== SHADOW_PCF1_16F && value !== SHADOW_PCF3_16F && value !== SHADOW_PCSS_32F) {
value = SHADOW_PCF3_32F;
}
if (value === SHADOW_VSM_32F && (!device.textureFloatRenderable || !device.textureFloatFilterable)) {
value = SHADOW_VSM_16F;
}
if (value === SHADOW_VSM_16F && !device.textureHalfFloatRenderable) {
value = SHADOW_PCF3_32F;
}
shadowInfo = shadowTypeInfo.get(value);
this._isVsm = shadowInfo?.vsm ?? false;
this._isPcf = shadowInfo?.pcf ?? false;
this._shadowType = value;
this._destroyShadowMap();
this.updateKey();
}
get shadowType() {
return this._shadowType;
}
set enabled(value) {
if (this._enabled !== value) {
this._enabled = value;
this.layersDirty();
}
}
get enabled() {
return this._enabled;
}
set castShadows(value) {
if (this._castShadows !== value) {
this._castShadows = value;
this._destroyShadowMap();
this.layersDirty();
this.updateKey();
}
}
get castShadows() {
return this._castShadows && this._mask !== MASK_BAKE && this._mask !== 0;
}
set shadowIntensity(value) {
if (this._shadowIntensity !== value) {
this._shadowIntensity = value;
this.updateKey();
}
}
get shadowIntensity() {
return this._shadowIntensity;
}
get bakeShadows() {
return this._castShadows && this._mask === MASK_BAKE;
}
set shadowResolution(value) {
if (this._shadowResolution !== value) {
if (this._type === LIGHTTYPE_OMNI) {
value = Math.min(value, this.device.maxCubeMapSize);
} else {
value = Math.min(value, this.device.maxTextureSize);
}
this._shadowResolution = value;
this._destroyShadowMap();
}
}
get shadowResolution() {
return this._shadowResolution;
}
set vsmBlurSize(value) {
if (this._vsmBlurSize === value) {
return;
}
if (value % 2 === 0) value++;
this._vsmBlurSize = value;
}
get vsmBlurSize() {
return this._vsmBlurSize;
}
set normalOffsetBias(value) {
if (this._normalOffsetBias !== value) {
const dirty = !this._normalOffsetBias && value || this._normalOffsetBias && !value;
this._normalOffsetBias = value;
if (dirty) {
this.updateKey();
}
}
}
get normalOffsetBias() {
return this._normalOffsetBias;
}
set falloffMode(value) {
if (this._falloffMode === value) {
return;
}
this._falloffMode = value;
this.updateKey();
this.updateClusteredFlags();
}
get falloffMode() {
return this._falloffMode;
}
set innerConeAngle(value) {
if (this._innerConeAngle === value) {
return;
}
this._innerConeAngle = value;
this._innerConeAngleCos = Math.cos(value * math.DEG_TO_RAD);
this.updateClusterData(false, true);
if (this._usePhysicalUnits) {
this._updateLinearColor();
}
}
get innerConeAngle() {
return this._innerConeAngle;
}
set outerConeAngle(value) {
if (this._outerConeAngle === value) {
return;
}
this._outerConeAngle = value;
this._updateOuterAngle(value);
if (this._usePhysicalUnits) {
this._updateLinearColor();
}
}
get outerConeAngle() {
return this._outerConeAngle;
}
set penumbraSize(value) {
this._penumbraSize = value;
this._softShadowParams[2] = value;
}
get penumbraSize() {
return this._penumbraSize;
}
set penumbraFalloff(value) {
this._softShadowParams[3] = value;
}
get penumbraFalloff() {
return this._softShadowParams[3];
}
_updateOuterAngle(angle) {
const radAngle = angle * math.DEG_TO_RAD;
this._outerConeAngleCos = Math.cos(radAngle);
this._outerConeAngleSin = Math.sin(radAngle);
this.updateClusterData(false, true);
}
set intensity(value) {
if (this._intensity !== value) {
this._intensity = value;
this._updateLinearColor();
}
}
get intensity() {
return this._intensity;
}
set affectSpecularity(value) {
if (this._type === LIGHTTYPE_DIRECTIONAL) {
this._affectSpecularity = value;
this.updateKey();
}
}
get affectSpecularity() {
return this._affectSpecularity;
}
set luminance(value) {
if (this._luminance !== value) {
this._luminance = value;
this._updateLinearColor();
}
}
get luminance() {
return this._luminance;
}
get cookieMatrix() {
if (!this._cookieMatrix) {
this._cookieMatrix = new Mat4();
}
return this._cookieMatrix;
}
get atlasViewport() {
if (!this._atlasViewport) {
this._atlasViewport = new Vec4(0, 0, 1, 1);
}
return this._atlasViewport;
}
set cookie(value) {
if (this._cookie === value) {
return;
}
this._cookie = value;
this.updateKey();
}
get cookie() {
return this._cookie;
}
set cookieFalloff(value) {
if (this._cookieFalloff === value) {
return;
}
this._cookieFalloff = value;
this.updateKey();
}
get cookieFalloff() {
return this._cookieFalloff;
}
set cookieChannel(value) {
if (this._cookieChannel === value) {
return;
}
if (value.length < 3) {
const chr = value.charAt(value.length - 1);
const addLen = 3 - value.length;
for(let i = 0; i < addLen; i++){
value += chr;
}
}
this._cookieChannel = value;
this.updateKey();
this.updateClusteredFlags();
}
get cookieChannel() {
return this._cookieChannel;
}
set cookieTransform(value) {
if (this._cookieTransform === value) {
return;
}
this._cookieTransform = value;
this._cookieTransformSet = !!value;
if (value && !this._cookieOffset) {
this.cookieOffset = new Vec2();
this._cookieOffsetSet = false;
}
this.updateKey();
}
get cookieTransform() {
return this._cookieTransform;
}
set cookieOffset(value) {
if (this._cookieOffset === value) {
return;
}
const xformNew = !!(this._cookieTransformSet || value);
if (xformNew && !value && this._cookieOffset) {
this._cookieOffset.set(0, 0);
} else {
this._cookieOffset = value;
}
this._cookieOffsetSet = !!value;
if (value && !this._cookieTransform) {
this.cookieTransform = new Vec4(1, 1, 0, 0);
this._cookieTransformSet = false;
}
this.updateKey();
}
get cookieOffset() {
return this._cookieOffset;
}
beginFrame() {
this.visibleThisFrame = this._type === LIGHTTYPE_DIRECTIONAL && this._enabled;
this.maxScreenSize = 0;
this.atlasViewportAllocated = false;
this.atlasSlotUpdated = false;
}
_destroyShadowMap() {
this.releaseRenderData();
if (this._shadowMap) {
if (!this._shadowMap.cached) {
this._shadowMap.destroy();
}
this._shadowMap = null;
}
if (this.shadowUpdateMode === SHADOWUPDATE_NONE) {
this.shadowUpdateMode = SHADOWUPDATE_THISFRAME;
}
if (this.shadowUpdateOverrides) {
for(let i = 0; i < this.shadowUpdateOverrides.length; i++){
if (this.shadowUpdateOverrides[i] === SHADOWUPDATE_NONE) {
this.shadowUpdateOverrides[i] = SHADOWUPDATE_THISFRAME;
}
}
}
}
getRenderData(camera, face) {
for(let i = 0; i < this._renderData.length; i++){
const current = this._renderData[i];
if (current.camera === camera && current.face === face) {
return current;
}
}
const rd = new LightRenderData(camera, face, this);
this._renderData.push(rd);
return rd;
}
clone() {
const clone = new Light(this.device, this.clusteredLighting);
clone.type = this._type;
clone.setColor(this._color);
clone.intensity = this._intensity;
clone.affectSpecularity = this._affectSpecularity;
clone.luminance = this._luminance;
clone.castShadows = this.castShadows;
clone._enabled = this._enabled;
clone.attenuationStart = this.attenuationStart;
clone.attenuationEnd = this.attenuationEnd;
clone.falloffMode = this._falloffMode;
clone.shadowType = this._shadowType;
clone.vsmBlurSize = this._vsmBlurSize;
clone.vsmBlurMode = this.vsmBlurMode;
clone.vsmBias = this.vsmBias;
clone.shadowUpdateMode = this.shadowUpdateMode;
clone.mask = this.mask;
if (this.shadowUpdateOverrides) {
clone.shadowUpdateOverrides = this.shadowUpdateOverrides.slice();
}
clone.innerConeAngle = this._innerConeAngle;
clone.outerConeAngle = this._outerConeAngle;
clone.numCascades = this.numCascades;
clone.cascadeDistribution = this.cascadeDistribution;
clone.cascadeBlend = this._cascadeBlend;
clone.shape = this._shape;
clone.shadowDepthState.copy(this.shadowDepthState);
clone.shadowBias = this.shadowBias;
clone.normalOffsetBias = this._normalOffsetBias;
clone.shadowResolution = this._shadowResolution;
clone.shadowDistance = this.shadowDistance;
clone.shadowIntensity = this.shadowIntensity;
clone.shadowSamples = this.shadowSamples;
clone.shadowBlockerSamples = this.shadowBlockerSamples;
clone.penumbraSize = this.penumbraSize;
clone.penumbraFalloff = this.penumbraFalloff;
return clone;
}
static getLightUnitConversion(type, outerAngle = Math.PI / 4, innerAngle = 0) {
switch(type){
case LIGHTTYPE_SPOT:
{
const falloffEnd = Math.cos(outerAngle);
const falloffStart = Math.cos(innerAngle);
return 2 * Math.PI * (1 - falloffStart + (falloffStart - falloffEnd) / 2.0);
}
case LIGHTTYPE_OMNI:
return 4 * Math.PI;
case LIGHTTYPE_DIRECTIONAL:
return 1;
}
}
_getUniformBiasValues(lightRenderData) {
const farClip = lightRenderData.shadowCamera._farClip;
switch(this._type){
case LIGHTTYPE_OMNI:
tmpBiases.bias = this.shadowBias;
tmpBiases.normalBias = this._normalOffsetBias;
break;
case LIGHTTYPE_SPOT:
if (this._isVsm) {
tmpBiases.bias = -1e-5 * 20;
} else {
tmpBiases.bias = this.shadowBias * 20;
}
tmpBiases.normalBias = this._isVsm ? this.vsmBias / (this.attenuationEnd / 7.0) : this._normalOffsetBias;
break;
case LIGHTTYPE_DIRECTIONAL:
if (this._isVsm) {
tmpBiases.bias = -1e-5 * 20;
} else {
tmpBiases.bias = this.shadowBias / farClip * 100;
}
tmpBiases.normalBias = this._isVsm ? this.vsmBias / (farClip / 7.0) : this._normalOffsetBias;
break;
}
return tmpBiases;
}
getColor() {
return this._color;
}
getBoundingSphere(sphere) {
if (this._type === LIGHTTYPE_SPOT) {
const size = this.attenuationEnd;
const angle = this._outerConeAngle;
const cosAngle = this._outerConeAngleCos;
const node = this._node;
tmpVec.copy(node.up);
if (angle > 45) {
sphere.radius = size * this._outerConeAngleSin;
tmpVec.mulScalar(-size * cosAngle);
} else {
sphere.radius = size / (2 * cosAngle);
tmpVec.mulScalar(-sphere.radius);
}
sphere.center.add2(node.getPosition(), tmpVec);
} else if (this._type === LIGHTTYPE_OMNI) {
sphere.center = this._node.getPosition();
sphere.radius = this.attenuationEnd;
}
}
getBoundingBox(box) {
if (this._type === LIGHTTYPE_SPOT) {
const range = this.attenuationEnd;
const angle = this._outerConeAngle;
const node = this._node;
const scl = Math.abs(Math.sin(angle * math.DEG_TO_RAD) * range);
box.center.set(0, -range * 0.5, 0);
box.halfExtents.set(scl, range * 0.5, scl);
box.setFromTransformedAabb(box, node.getWorldTransform(), true);
} else if (this._type === LIGHTTYPE_OMNI) {
box.center.copy(this._node.getPosition());
box.halfExtents.set(this.attenuationEnd, this.attenuationEnd, this.attenuationEnd);
}
}
_updateShadowBias() {
if (this._type === LIGHTTYPE_OMNI && !this.clusteredLighting) {
this.shadowDepthState.depthBias = 0;
this.shadowDepthState.depthBiasSlope = 0;
} else {
const bias = this.shadowBias * -1e3;
this.shadowDepthState.depthBias = bias;
this.shadowDepthState.depthBiasSlope = bias;
}
}
_updateLinearColor() {
let intensity = this._intensity;
if (this._usePhysicalUnits) {
intensity = this._luminance / Light.getLightUnitConversion(this._type, this._outerConeAngle * math.DEG_TO_RAD, this._innerConeAngle * math.DEG_TO_RAD);
}
const color = this._color;
const colorLinear = this._colorLinear;
if (intensity >= 1) {
tmpColor.linear(color).mulScalar(intensity);
} else {
tmpColor.copy(color).mulScalar(intensity).linear();
}
colorLinear[0] = tmpColor.r;
colorLinear[1] = tmpColor.g;
colorLinear[2] = tmpColor.b;
this.updateClusterData(true);
}
setColor() {
if (arguments.length === 1) {
this._color.set(arguments[0].r, arguments[0].g, arguments[0].b);
} else if (arguments.length === 3) {
this._color.set(arguments[0], arguments[1], arguments[2]);
}
this._updateLinearColor();
}
layersDirty() {
this.layers.forEach((layer)=>{
if (layer.hasLight(this)) {
layer.markLightsDirty();
}
});
}
updateKey() {
let key = this._type << 29 | this._shadowType << 25 | this._falloffMode << 23 | (this._normalOffsetBias !== 0.0 ? 1 : 0) << 22 | (this._cookie ? 1 : 0) << 21 | (this._cookieFalloff ? 1 : 0) << 20 | chanId[this._cookieChannel.charAt(0)] << 18 | (this._cookieTransform ? 1 : 0) << 12 | this._shape << 10 | (this.numCascades > 0 ? 1 : 0) << 9 | (this._cascadeBlend > 0 ? 1 : 0) << 8 | (this.affectSpecularity ? 1 : 0) << 7 | this.mask << 6 | (this._castShadows ? 1 : 0) << 3;
if (this._cookieChannel.length === 3) {
key |= chanId[this._cookieChannel.charAt(1)] << 16;
key |= chanId[this._cookieChannel.charAt(2)] << 14;
}
if (key !== this.key) {
this.layersDirty();
}
this.key = key;
}
updateClusteredFlags() {
const isDynamic = !!(this.mask & MASK_AFFECT_DYNAMIC);
const isLightmapped = !!(this.mask & MASK_AFFECT_LIGHTMAPPED);
this.clusteredFlags = (this.type === LIGHTTYPE_SPOT ? 1 : 0) << 30 | (this._shape & 0x3) << 28 | (this._falloffMode & 0x1) << 27 | (channelMap[this._cookieChannel] ?? 0) << 23 | (isDynamic ? 1 : 0) << 22 | (isLightmapped ? 1 : 0) << 21;
}
getClusteredFlags(castShadows, useCookie) {
return this.clusteredFlags | ((castShadows ? Math.floor(this.shadowIntensity * 255) : 0) & 0xFF) << 0 | ((useCookie ? Math.floor(this.cookieIntensity * 255) : 0) & 0xFF) << 8;
}
updateClusterData(updateColor, updateAngles) {
const { clusteredData16 } = this;
const float2Half = FloatPacking.float2Half;
if (updateColor) {
clusteredData16[0] = float2Half(math.clamp(this._colorLinear[0] / LIGHT_COLOR_DIVIDER, 0, 65504));
clusteredData16[1] = float2Half(math.clamp(this._colorLinear[1] / LIGHT_COLOR_DIVIDER, 0, 65504));
clusteredData16[2] = float2Half(math.clamp(this._colorLinear[2] / LIGHT_COLOR_DIVIDER, 0, 65504));
}
if (updateAngles) {
const cosThreshold = 0.5;
let flags = 0;
const angleShrinkFactor = 0.99;
let innerCos = Math.cos(this._innerConeAngle * angleShrinkFactor * math.DEG_TO_RAD);
if (innerCos > cosThreshold) {
innerCos = 1.0 - innerCos;
flags |= 1;
}
let outerCos = Math.cos(this._outerConeAngle * angleShrinkFactor * math.DEG_TO_RAD);
if (outerCos > cosThreshold) {
outerCos = 1.0 - outerCos;
flags |= 2;
}
clusteredData16[3] = flags;
clusteredData16[4] = float2Half(innerCos);
clusteredData16[5] = float2Half(outerCos);
}
}
}
export { Light, lightTypes };