Skip to main content

Class: LocalShadowScheduler

The L3 tier: decides which clustered lights earn a shadow slot (with eviction hysteresis so orbiting the camera does not thrash the atlas), owns the cached-static / dynamic-overlay face state machine per slot, and renders the faces the ShadowFrameLedger grants each frame.

A frame with zero shadow slots costs one integer compare in update and an early return in render.

Constructors​

constructor​

• new LocalShadowScheduler(registry, options): LocalShadowScheduler

Parameters​

NameTypeDescription
registryLightSourceRegistry-
optionsObject-
options.maxShadowedLightsnumber-
options.now?() => numberClock for the shadow fades; tests inject one.
options.shadowAtlasSizenumber-
options.shadowEvictionHysteresisObject-
options.shadowEvictionHysteresis.framesnumber-
options.shadowEvictionHysteresis.rationumber-
options.shadowFadeMs?number-
options.shadowSlotSizenumber-
options.temporalStability?boolean-

Returns​

LocalShadowScheduler

Properties​

atlas​

• Readonly atlas: LocalShadowAtlas


getIsOpaqueAt​

• getIsOpaqueAt: (vx: number, vy: number, vz: number) => boolean = null

getIsOpaqueAt hook, wired by the world; null skips mount awareness.

Type declaration​

▸ (vx, vy, vz): boolean

Parameters​
NameType
vxnumber
vynumber
vznumber
Returns​

boolean


getStaticCasterRoots​

• getStaticCasterRoots: (x: number, y: number, z: number, radius: number, out: Object3D<Object3DEventMap>[]) => void = null

Static caster collection hook, wired by the world: append every world root (chunk group) whose geometry can occlude a light at (x, y, z) with range radius to out. Null falls back to rendering the whole scene per face, which stays correct but pays full-graph traversal.

Type declaration​

▸ (x, y, z, radius, out): void

Parameters​
NameType
xnumber
ynumber
znumber
radiusnumber
outObject3D<Object3DEventMap>[]
Returns​

void


invalidationLog​

• Readonly invalidationLog: ShadowInvalidationEntry[] = []

Ring buffer of the most recent invalidations, for the debug HUD.


isBorrowingCasters​

• isBorrowingCasters: boolean = true

Draw an overlay face's casters from borrowed lists (on by default): the near entities in one call without reparenting them, and the slot's pools in one more, instead of moving each entity into a scratch scene and back (a childremoved/childadded pair on its real parent every time) and rendering each pool as its own root (a matrix walk and a render call per pool per face). Off restores the old path for A/B timing; the world flips it with the cascades' single caster pass.


onShadowDataChanged​

• onShadowDataChanged: () => void = null

Called whenever packed shadow texels must be rewritten.

Type declaration​

▸ (): void

Returns​

void

Accessors​

activeSlotCount​

• get activeSlotCount(): number

Returns​

number


slotCapacity​

• get slotCapacity(): number

Returns​

number

Methods​

dispose​

▸ dispose(): void

Returns​

void


estimateDynamicDemand​

▸ estimateDynamicDemand(entities?): number

Estimated dynamic face units this frame wants, for the ledger reservation taken before CSM renders its cascades. Mirrors exactly what render will draw through the dynamic tier — a moving light's pending world refreshes plus every slot's entity overlay faces — so an idle held light with no casters nearby reserves nothing and never squeezes the CSM far cascades or the static FIFO.

Parameters​

NameType
entities?Object3D<Object3DEventMap>[]

Returns​

number


invalidateAll​

▸ invalidateAll(cause): void

Parameters​

NameType
causeShadowInvalidationCause

Returns​

void


invalidateRegion​

▸ invalidateRegion(region): void

Public API: invalidate every cached map intersecting a world region.

Parameters​

NameType
regionObject
region.max[number, number, number]
region.min[number, number, number]

Returns​

void


notifyBlockEdit​

▸ notifyBlockEdit(vx, vy, vz): void

A voxel changed. Cached static maps of every slot whose range sphere intersects the edited voxel re-render through the FIFO; edits outside every range cost one AABB test per active slot.

Parameters​

NameType
vxnumber
vynumber
vznumber

Returns​

void


notifyChunkMeshed​

▸ notifyChunkMeshed(area): void

A chunk's mesh (re-)built. Cached maps that reach into the chunk baked whatever geometry existed at render time; streaming in late meshes must refresh them or lights shine through terrain that "wasn't there yet".

Parameters​

NameType
areaObject
area.maxHeightnumber
area.maxXnumber
area.maxZnumber
area.minXnumber
area.minZnumber

Returns​

void


onContextRestored​

▸ onContextRestored(): void

GPU context restored: atlas contents are gone; re-render lazily.

Returns​

void


recordForIndex​

▸ recordForIndex(index): ShadowTexelRecord

Texel provider for the clustered packer. The record is scratch reused by the next call (the packer runs every frame a shadow fades).

Parameters​

NameType
indexnumber

Returns​

ShadowTexelRecord


render​

▸ render(renderer, scene, ledger, entities, instancePools, skipShadowObjects, stats, poolBounds?): void

Render the faces the ledger grants. Order inside the local tier: moving-light refreshes and entity overlays (dynamic units, reserved) first, then the invalidated-static FIFO (free units only).

Parameters​

NameType
rendererWebGLRenderer
sceneScene<Object3DEventMap>
ledgerShadowFrameLedger
entitiesObject3D<Object3DEventMap>[]
instancePoolsGroup<Object3DEventMap>[]
skipShadowObjectsreadonly Object3D<Object3DEventMap>[]
statsLocalLightStats
poolBounds?readonly Box3[]

Returns​

void


resetCacheCounters​

▸ resetCacheCounters(): void

Restart the cache-hit measurement window (benchmark harnesses).

Returns​

void


setTemporalStability​

▸ setTemporalStability(isStable): void

On (default): shadows fade in once their faces exist and fade out before a slot changes hands, and an invalidated face keeps sampling its last map until the re-render lands. Off: the legacy behaviour, where an invalidation blanks every face of the light (fully lit) until the budgeted re-render, and a swap is instant.

Parameters​

NameType
isStableboolean

Returns​

void


setTierCaps​

▸ setTierCaps(maxShadowedLights, atlasSize, slotSize): void

Quality-tier change: new caps and atlas geometry. Every cached map is dropped (the atlas may have been reallocated at a new size).

Parameters​

NameType
maxShadowedLightsnumber
atlasSizenumber
slotSizenumber

Returns​

void


update​

▸ update(selectedIndices, selectedCount, cameraX, cameraY, cameraZ, stats, retainedIndices?, retainedCount?): void

Reconcile shadow slots against the clustered selection. Runs every frame; the scoring loop is O(clustered ≤ 255) and the whole pass is a no-op micro-loop when nothing shadow-requesting is selected.

Parameters​

NameTypeDefault valueDescription
selectedIndicesUint32Array<ArrayBufferLike>undefined-
selectedCountnumberundefined-
cameraXnumberundefined-
cameraYnumberundefined-
cameraZnumberundefined-
statsLocalLightStatsundefined-
retainedIndices?Uint32Array<ArrayBufferLike>undefinedLights still rendered while they fade out of the clustered set: a holder among them keeps its slot (and its shadow) until it is gone, but they never take or challenge for one.
retainedCountnumber0-

Returns​

void