@voluma/vlam 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/budget-governor.d.ts +4 -4
- package/dist/camera-budget-governor.d.ts +11 -11
- package/dist/chunk-cache-budget.d.ts +5 -5
- package/dist/chunk-fetch-scheduler.d.ts +4 -4
- package/dist/chunk-loader-DlNwggfG.js +1859 -0
- package/dist/chunk-loader-DlNwggfG.js.map +1 -0
- package/dist/chunk-loader.d.ts +2 -2
- package/dist/compute-sorter.d.ts +1 -1
- package/dist/effects.d.ts +11 -10
- package/dist/effects.js.map +1 -1
- package/dist/formats/ksplat/index.d.ts +1 -1
- package/dist/formats/lcc/collision-mesh.d.ts +2 -2
- package/dist/formats/lcc.js +17 -16
- package/dist/formats/lcc.js.map +1 -1
- package/dist/formats/ply/index.d.ts +1 -1
- package/dist/formats/ply/parse-splat-ply.d.ts +2 -2
- package/dist/formats/ply.js +1 -1
- package/dist/formats/ply.js.map +1 -1
- package/dist/formats/rad/index.d.ts +1 -1
- package/dist/formats/rad/parse-rad.d.ts +1 -1
- package/dist/formats/rad/rad.d.ts +2 -2
- package/dist/formats/rad.js +3 -3
- package/dist/formats/rad.js.map +1 -1
- package/dist/formats/sog/index.d.ts +1 -1
- package/dist/formats/sog/parse-sog.d.ts +4 -4
- package/dist/formats/sog.js.map +1 -1
- package/dist/formats/splat/index.d.ts +1 -1
- package/dist/formats/spz/index.d.ts +1 -1
- package/dist/index.d.ts +16 -25
- package/dist/index.js +253 -9047
- package/dist/index.js.map +1 -1
- package/dist/{load-scene.d.ts → load-splat-data.d.ts} +15 -13
- package/dist/loaders.d.ts +15 -0
- package/dist/loaders.js +26 -0
- package/dist/loaders.js.map +1 -0
- package/dist/loading-wLo8vRbA.js.map +1 -1
- package/dist/loading.d.ts +8 -8
- package/dist/{splat-mesh-types-CTN_duw2.js → lod-scheduler-B0a_uBlv.js} +133 -140
- package/dist/lod-scheduler-B0a_uBlv.js.map +1 -0
- package/dist/lod-scheduler.d.ts +1 -1
- package/dist/lod-source.d.ts +5 -5
- package/dist/{splat-scene.d.ts → merged-splat-mesh.d.ts} +14 -15
- package/dist/orientation.d.ts +7 -7
- package/dist/{ply-header-BwgExCn-.js → ply-header-B9WllbPg.js} +3 -3
- package/dist/{ply-header-BwgExCn-.js.map → ply-header-B9WllbPg.js.map} +1 -1
- package/dist/radix-sorter-R0FCSqKu.js +178 -0
- package/dist/radix-sorter-R0FCSqKu.js.map +1 -0
- package/dist/relighting-CSQF3UcL.js +969 -0
- package/dist/relighting-CSQF3UcL.js.map +1 -0
- package/dist/relighting.d.ts +1 -1
- package/dist/selection.d.ts +13 -0
- package/dist/selection.js +137 -0
- package/dist/selection.js.map +1 -0
- package/dist/source-transform.d.ts +4 -4
- package/dist/{splat-budget-DXew_86c.js → splat-budget-DuVZspPQ.js} +7 -7
- package/dist/{splat-budget-DXew_86c.js.map → splat-budget-DuVZspPQ.js.map} +1 -1
- package/dist/splat-budget.d.ts +1 -1
- package/dist/splat-data-BZM5dhhx.js.map +1 -1
- package/dist/splat-data.d.ts +4 -4
- package/dist/splat-mesh-aQdcWA6v.js +1942 -0
- package/dist/splat-mesh-aQdcWA6v.js.map +1 -0
- package/dist/splat-mesh-material.d.ts +2 -2
- package/dist/splat-mesh-types-BZIko-_9.js +18 -0
- package/dist/splat-mesh-types-BZIko-_9.js.map +1 -0
- package/dist/splat-mesh-types.d.ts +14 -8
- package/dist/splat-mesh.d.ts +4 -4
- package/dist/splat-modifier.d.ts +5 -5
- package/dist/splat-partition.d.ts +1 -1
- package/dist/static-lod-entry.d.ts +12 -0
- package/dist/static-lod-splat-mesh.d.ts +2 -2
- package/dist/{static-lod-worker-D2fyqQZO.js → static-lod-worker-DktKnJkR.js} +46 -46
- package/dist/{static-lod-worker-D2fyqQZO.js.map → static-lod-worker-DktKnJkR.js.map} +1 -1
- package/dist/static-lod.js +148 -0
- package/dist/static-lod.js.map +1 -0
- package/dist/streamed-splat-mesh.d.ts +21 -27
- package/dist/streaming.d.ts +23 -0
- package/dist/streaming.js +2877 -0
- package/dist/streaming.js.map +1 -0
- package/dist/{unified-splat-renderer.d.ts → unified-splat-mesh.d.ts} +10 -10
- package/dist/unified-work-buffer.d.ts +66 -0
- package/dist/unified.d.ts +11 -0
- package/dist/unified.js +738 -0
- package/dist/unified.js.map +1 -0
- package/dist/webgpu-limits-Y3CI_6_6.js +43 -0
- package/dist/webgpu-limits-Y3CI_6_6.js.map +1 -0
- package/dist/webgpu-limits.d.ts +0 -59
- package/dist/work-buffer-gather.d.ts +1 -1
- package/package.json +24 -4
- package/dist/splat-mesh-types-CTN_duw2.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-loader-DlNwggfG.js","sources":["../src/lib/chunk-loader.ts"],"sourcesContent":["import type { SplatData } from './splat-data';\nimport type {\n LoadWorkerRequest,\n LoadWorkerResponse,\n LoadWorkerSource,\n RadChunkRangeRequest,\n} from './load-worker-protocol';\nimport type { LccChunkParams } from './formats/lcc/parse-lcc';\nimport {\n createAbortError,\n deserializeSplatLoadError,\n resolveSplatUrl,\n SplatLoadError,\n splatFormatForExtension,\n splatNameExtension,\n splatUrlExtension,\n type ChunkFileFormat,\n type SplatDataFileLoadOptions,\n type SplatFormat,\n type SplatInputOptions,\n type SplatDataFormat,\n type SplatProgressCallback,\n} from './loading';\n// Inlined worker (blob URL): survives library bundling in any consumer\n// setup, unlike an asset file referenced via `new URL(...)`.\nimport LoadWorker from './load-worker?worker&inline';\n\n/**\n * Formats served by the one-shot worker rather than the streaming one.\n *\n * Both workers are inlined, so their parsers are string literals no bundler can\n * tree-shake. Keeping these three behind a dynamic import is what moves SPZ's\n * ~39 KB ZSTD wasm blob out of the main entry and into a chunk that loads only\n * when one of these formats is actually opened.\n */\nconst ONE_SHOT_FORMATS = new Set<ChunkFileFormat>(['spz', 'splat', 'ksplat']);\n\n/**\n * One worker plus the requests in flight on it.\n *\n * `ChunkLoader` runs two of these - see {@link ChunkLoader} - so the request\n * bookkeeping, cancellation and terminal-failure handling live here once.\n */\nclass WorkerClient {\n private nextRequestId = 0;\n private readonly pending = new Map<\n number,\n {\n resolve: (data: SplatData) => void;\n reject: (error: Error) => void;\n removeAbortListener?: () => void;\n onProgress?: SplatProgressCallback;\n }\n >();\n private disposed = false;\n /** Terminal worker failure; subsequent loads reject instead of hanging. */\n private workerFailure: SplatLoadError | null = null;\n\n constructor(private readonly worker: Worker) {\n this.worker.onmessage = (event: MessageEvent<LoadWorkerResponse>) => {\n const request = this.pending.get(event.data.id);\n if (!request) return;\n if (event.data.type === 'progress') {\n // Progress does not settle the request; the result still follows.\n request.onProgress?.(event.data.loaded, event.data.total);\n return;\n }\n this.pending.delete(event.data.id);\n request.removeAbortListener?.();\n if (event.data.ok) {\n request.resolve(event.data.data);\n } else if (event.data.cancelled) {\n request.reject(createAbortError('Chunk load was cancelled.'));\n } else {\n request.reject(deserializeSplatLoadError(event.data.error));\n }\n };\n // A worker-level failure (a CSP that blocks blob: workers is the common\n // one in real deployments) must still honour the documented contract:\n // every rejection is a SplatLoadError or an AbortError, never a bare Error.\n // There is no per-request URL here - the worker itself died - so `url` is\n // empty and the failure is not retryable: retrying constructs the same\n // worker under the same policy.\n this.worker.onerror = (event?: ErrorEvent | Event) => {\n if (this.workerFailure) return;\n const detail =\n event &&\n typeof event === 'object' &&\n 'message' in event &&\n typeof event.message === 'string'\n ? event.message\n : '';\n const failure = new SplatLoadError(\n detail ? `Chunk loading worker failed. ${detail}` : 'Chunk loading worker failed.',\n { phase: 'worker', url: '', retryable: false },\n );\n this.workerFailure = failure;\n this.worker.terminate();\n this.rejectAll(failure);\n };\n }\n\n /** Posts one load to this worker and tracks its pending promise. */\n request(\n message: Omit<Extract<LoadWorkerRequest, { type: 'load' }>, 'id'>,\n signal: AbortSignal | undefined,\n onProgress?: SplatProgressCallback,\n ): Promise<SplatData> {\n if (this.disposed) return Promise.reject(createAbortError('ChunkLoader has been disposed.'));\n if (signal?.aborted) return Promise.reject(createAbortError('Chunk load was cancelled.'));\n if (this.workerFailure) return Promise.reject(this.workerFailure);\n\n const id = this.nextRequestId++;\n const promise = new Promise<SplatData>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, ...(onProgress ? { onProgress } : {}) });\n });\n if (signal) {\n // Settle immediately on abort rather than waiting for the worker's\n // acknowledgement: the worker may be deep in a decode (or may already\n // have posted a success that is still in the message queue), and a\n // caller that cancelled must neither receive that stale result nor see\n // further progress callbacks. The late reply finds no pending entry and\n // is dropped in `onmessage`.\n const onAbort = () => {\n const pending = this.pending.get(id);\n if (!pending) return;\n this.pending.delete(id);\n const cancel: LoadWorkerRequest = { type: 'cancel', id };\n if (!this.disposed) this.worker.postMessage(cancel);\n pending.reject(createAbortError('Chunk load was cancelled.'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n const pending = this.pending.get(id);\n if (pending) pending.removeAbortListener = () => signal.removeEventListener('abort', onAbort);\n }\n\n const request: LoadWorkerRequest = { ...message, id };\n this.worker.postMessage(request);\n return promise;\n }\n\n /** Terminates this worker and rejects its in-flight requests. */\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.worker.terminate();\n this.rejectAll(createAbortError('ChunkLoader has been disposed.'));\n }\n\n private rejectAll(error: Error): void {\n for (const request of this.pending.values()) {\n request.removeAbortListener?.();\n request.reject(error);\n }\n this.pending.clear();\n }\n}\n\n/**\n * Client for the persistent scene/chunk loading workers. Multiple requests\n * may be in flight; each can be cancelled through an `AbortSignal`, which\n * aborts the worker-side fetches (useful when the camera has moved on\n * before a chunk arrived).\n *\n * There are two workers behind this one class. The streaming worker is\n * constructed eagerly and handles everything the LOD paths need; the one-shot\n * worker ({@link ONE_SHOT_FORMATS}) is constructed the first time a `.spz`,\n * `.splat` or `.ksplat` is requested. Both decode off the main thread - the\n * split exists only to keep the one-shot parsers out of the published entry.\n *\n * @example\n * const loader = new ChunkLoader();\n * const chunk = await loader.load('/scene/0_0/', { kind: 'directory' });\n */\nexport class ChunkLoader {\n private readonly streaming = new WorkerClient(new LoadWorker());\n /** The in-flight construction; `null` until a one-shot format is requested. */\n private oneShot: Promise<WorkerClient> | null = null;\n /** The constructed one-shot client, so `dispose` does not have to await. */\n private oneShotClient: WorkerClient | null = null;\n private disposed = false;\n\n /**\n * Fetches and decodes one scene file or chunk directory.\n *\n * @param url - Scene URL, absolute or relative to the page.\n * @param options.kind - 'file' (default) selects a parser by extension;\n * 'directory' decodes an unbundled SOG chunk directory.\n * @param options.format - Explicit parser; `lcc-bin` additionally needs\n * `options.lcc` to say which byte range of the file to fetch.\n * @param options.signal - Abort signal for cancellation; the returned\n * promise then rejects with a `DOMException` named `AbortError`.\n * @param options.lcc - Byte-range plumbing for `lcc-bin`. This path exists\n * for {@link StreamedSplatMesh} and is **not** part of the stable surface;\n * the type is nameable as `LccChunkParams` on `@voluma/vlam/formats/lcc`.\n * @param options.rad - Byte-range plumbing for `rad-chunk`, on the same\n * footing; the type is `RadChunkRangeRequest` on `@voluma/vlam/formats/rad`.\n * @throws Rejects only with {@link SplatLoadError} (resolve, fetch, decode or\n * worker failures - `error.phase` says which; a worker that cannot start at\n * all, e.g. under a CSP that blocks `blob:` workers, reports\n * `phase: 'worker'`) or an `Error` named `AbortError` (cancellation or\n * {@link dispose}).\n */\n // `async` so a URL or format that will not resolve rejects the returned\n // promise rather than throwing synchronously into the caller.\n async load(\n url: string,\n options: SplatInputOptions & {\n kind?: 'file' | 'directory';\n format?: SplatFormat | 'lcc-bin' | 'rad-chunk';\n lcc?: LccChunkParams;\n rad?: RadChunkRangeRequest;\n sog?: { packShBands: 1 | 2 | 3 };\n files?: Readonly<Record<string, string>>;\n } = {},\n ): Promise<SplatData> {\n const kind = options.kind ?? 'file';\n // The worker runs from an inlined blob/data URL, where relative and\n // root-relative URLs cannot be resolved - make the URL absolute here.\n const absoluteUrl = resolveSplatUrl(url, options.baseUrl).href;\n const format = resolveFileFormat(absoluteUrl, kind, options.format ?? 'auto');\n const data = await this.request(\n {\n from: 'url',\n url: absoluteUrl,\n kind,\n ...(options.request ? { request: options.request } : {}),\n },\n format,\n options.signal,\n options.lcc,\n options.rad,\n options.files,\n options.sog,\n options.onProgress,\n );\n return withSourceFormat(data, format);\n }\n\n /**\n * Decodes a local file (from a file input or a drop) in the worker - the\n * bytes are read worker-side, so a multi-million-splat decode still never\n * blocks the main thread.\n *\n * Only self-contained files are supported: `.ply`, `.spz`, `.splat`,\n * `.ksplat` and bundled `.sog`. An unbundled SOG directory needs sibling\n * fetches and so requires {@link load} with `kind: 'directory'`.\n *\n * @param file - The file; its name selects the parser unless `format` is given.\n * @throws Rejects only with {@link SplatLoadError} (unknown extension, read\n * or decode failures) or an `Error` named `AbortError` (cancellation or\n * {@link dispose}).\n */\n // `async` so an unknown extension rejects the returned promise rather than\n // throwing synchronously into the caller.\n async loadFile(file: File, options: SplatDataFileLoadOptions = {}): Promise<SplatData> {\n const requested = options.format ?? 'auto';\n const format: Exclude<SplatFormat, 'auto'> =\n requested === 'auto'\n ? splatFormatForExtension(splatNameExtension(file.name), file.name)\n : requested;\n const data = await this.request(\n { from: 'file', file },\n format,\n options.signal,\n undefined,\n undefined,\n undefined,\n undefined,\n options.onProgress,\n );\n return withSourceFormat(data, format);\n }\n\n /** Routes one load to the worker that owns its format. */\n private request(\n source: LoadWorkerSource,\n format: ChunkFileFormat,\n signal: AbortSignal | undefined,\n lcc?: LccChunkParams,\n rad?: RadChunkRangeRequest,\n files?: Readonly<Record<string, string>>,\n sog?: { packShBands: 1 | 2 | 3 },\n onProgress?: SplatProgressCallback,\n ): Promise<SplatData> {\n if (this.disposed) return Promise.reject(createAbortError('ChunkLoader has been disposed.'));\n if (signal?.aborted) return Promise.reject(createAbortError('Chunk load was cancelled.'));\n\n const message: Omit<Extract<LoadWorkerRequest, { type: 'load' }>, 'id'> = {\n type: 'load',\n source,\n format,\n ...(lcc ? { lcc } : {}),\n ...(rad ? { rad } : {}),\n ...(sog ? { sog } : {}),\n ...(files ? { files } : {}),\n ...(onProgress ? { progress: true } : {}),\n };\n\n const client = this.clientFor(format);\n // Deliberately not `async`: for a streaming format the client is already\n // there, and the request must reach the worker in the same task the caller\n // made it - a chunk scheduler that posts and then aborts within one frame\n // depends on that ordering. Only the one-shot path, which cannot avoid\n // awaiting its chunk, defers.\n if (!(client instanceof Promise)) return client.request(message, signal, onProgress);\n return client.then((resolved) => {\n // Re-checked because disposal or abort may have happened while the\n // one-shot chunk was in flight, before any listener was registered.\n if (this.disposed) throw createAbortError('ChunkLoader has been disposed.');\n return resolved.request(message, signal, onProgress);\n });\n }\n\n /**\n * The worker for `format`, constructing the one-shot worker on first need.\n *\n * The in-flight promise is cached, not just the resolved client, so two\n * concurrent `.spz` loads share one worker instead of racing to build two.\n */\n private clientFor(format: ChunkFileFormat): WorkerClient | Promise<WorkerClient> {\n if (!ONE_SHOT_FORMATS.has(format)) return this.streaming;\n if (this.oneShotClient) return this.oneShotClient;\n this.oneShot ??= import('./one-shot-worker?worker&inline').then(\n ({ default: OneShotWorker }) => {\n const client = new WorkerClient(new OneShotWorker());\n this.oneShotClient = client;\n // `dispose()` may have run while the chunk was in flight; it could not\n // terminate a worker that did not exist yet, so honour it here.\n if (this.disposed) client.dispose();\n return client;\n },\n );\n return this.oneShot;\n }\n\n /** Terminates both workers and rejects all in-flight requests. */\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.streaming.dispose();\n // A one-shot worker still being imported is terminated in `clientFor`,\n // which sees `disposed` when it finally constructs it.\n this.oneShotClient?.dispose();\n }\n}\n\n/** Self-contained scene formats {@link SplatData.format} can hold. */\nconst SOURCE_FORMATS: readonly SplatDataFormat[] = ['ply', 'spz', 'splat', 'ksplat', 'sog', 'rad'];\n\n/**\n * Stamps the resolved format onto decoded scene data so {@link SplatMesh} can\n * pick its `orientation: 'y-up'` correction. Chunk-only formats (`lcc-bin`,\n * `rad-chunk`) carry no self-contained frame, so they are left unstamped.\n */\nfunction withSourceFormat(data: SplatData, format: ChunkFileFormat): SplatData {\n return (SOURCE_FORMATS as readonly string[]).includes(format)\n ? { ...data, format: format as SplatDataFormat }\n : data;\n}\n\nfunction resolveFileFormat(\n url: string,\n kind: 'file' | 'directory',\n format: SplatFormat | 'lcc-bin' | 'rad-chunk',\n): ChunkFileFormat {\n // An LCC or RAD chunk is a byte range of a shared file, so its extension says\n // nothing about the parser - the caller's choice is authoritative.\n if (format === 'lcc-bin' || format === 'rad-chunk') return format;\n if (kind === 'directory') return 'sog';\n if (format !== 'auto') return format;\n return splatFormatForExtension(splatUrlExtension(new URL(url)), url);\n}\n"],"names":["ONE_SHOT_FORMATS","WorkerClient","worker","__publicField","event","request","_a","_b","createAbortError","deserializeSplatLoadError","detail","failure","SplatLoadError","message","signal","onProgress","id","promise","resolve","reject","onAbort","pending","cancel","error","ChunkLoader","LoadWorker","url","options","kind","absoluteUrl","resolveSplatUrl","format","resolveFileFormat","data","withSourceFormat","file","requested","splatFormatForExtension","splatNameExtension","source","lcc","rad","files","sog","client","resolved","OneShotWorker","SOURCE_FORMATS","splatUrlExtension"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
|
package/dist/chunk-loader.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SplatData } from './splat-data';
|
|
2
2
|
import type { RadChunkRangeRequest } from './load-worker-protocol';
|
|
3
3
|
import type { LccChunkParams } from './formats/lcc/parse-lcc';
|
|
4
|
-
import { type
|
|
4
|
+
import { type SplatDataFileLoadOptions, type SplatFormat, type SplatInputOptions } from './loading';
|
|
5
5
|
/**
|
|
6
6
|
* Client for the persistent scene/chunk loading workers. Multiple requests
|
|
7
7
|
* may be in flight; each can be cancelled through an `AbortSignal`, which
|
|
@@ -70,7 +70,7 @@ export declare class ChunkLoader {
|
|
|
70
70
|
* or decode failures) or an `Error` named `AbortError` (cancellation or
|
|
71
71
|
* {@link dispose}).
|
|
72
72
|
*/
|
|
73
|
-
loadFile(file: File, options?:
|
|
73
|
+
loadFile(file: File, options?: SplatDataFileLoadOptions): Promise<SplatData>;
|
|
74
74
|
/** Routes one load to the worker that owns its format. */
|
|
75
75
|
private request;
|
|
76
76
|
/**
|
package/dist/compute-sorter.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as THREE from 'three/webgpu';
|
|
|
2
2
|
import type { SplatSorter } from './sorter';
|
|
3
3
|
import type { uniformArray } from 'three/tsl';
|
|
4
4
|
/**
|
|
5
|
-
* Optional per-source world transform for a unified {@link
|
|
5
|
+
* Optional per-source world transform for a unified {@link MergedSplatMesh} pool.
|
|
6
6
|
* When present, each splat's center is transformed to world space by its
|
|
7
7
|
* source's matrix (selected by a per-splat id read from `sourceIdTexture`)
|
|
8
8
|
* before its depth is computed, so clouds with different world transforms sort
|
package/dist/effects.d.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* in-shader from a bounded uniform array (a fixed unroll over `maxShapes`
|
|
8
8
|
* slots, gated by the live count) - add/move/remove shapes is data-only
|
|
9
9
|
* (no pipeline recompile), on WebGPU and the WebGL2 fallback alike (M7.4).
|
|
10
|
-
* The kinds mirror the CPU selection volumes (`createSelectionVolume`
|
|
11
|
-
* a host can highlight exactly what a
|
|
10
|
+
* The kinds mirror the CPU selection volumes (`createSelectionVolume` on
|
|
11
|
+
* `@voluma/vlam/selection`), so a host can highlight exactly what a
|
|
12
|
+
* selection would take.
|
|
12
13
|
* - {@link lightingPreset} / {@link revealPreset}: small reference presets,
|
|
13
14
|
* starting points rather than a framework (M7.5).
|
|
14
15
|
* - {@link createRelightingProxy}: builds a gray lit mesh group from collision
|
|
@@ -24,12 +25,12 @@
|
|
|
24
25
|
* - {@link worldWarpPreset}: camera-centered sphere wrap (planet / bowl).
|
|
25
26
|
* - {@link depthOfFieldPreset}: stylized modifier-based depth-of-field (M13).
|
|
26
27
|
* For physically-modeled camera DoF prefer the core
|
|
27
|
-
* {@link SplatMesh.setDepthOfField} / `
|
|
28
|
+
* {@link SplatMesh.setDepthOfField} / `UnifiedSplatMesh.setDepthOfField`
|
|
28
29
|
* path - this preset is the soft/approximate alternative built purely on
|
|
29
30
|
* the modifier hook.
|
|
30
31
|
*
|
|
31
32
|
* Every preset here reads positions from `ctx.localCenter` - mesh-local space,
|
|
32
|
-
* which inside a `
|
|
33
|
+
* which inside a `MergedSplatMesh` is the splat's **placed** position. So a shape or
|
|
33
34
|
* a reveal spans every source as one scene instead of travelling with a moved
|
|
34
35
|
* one; see `docs/guide/effects-and-modifiers.md` for what that looks like, and
|
|
35
36
|
* `ctx.sourceCenter` for the opposite behaviour.
|
|
@@ -119,7 +120,7 @@ export interface SdfEffect {
|
|
|
119
120
|
* time (and the uniform array; very large values can exceed WebGL2's uniform
|
|
120
121
|
* limits), so keep it to what a scene needs.
|
|
121
122
|
*
|
|
122
|
-
* Shape positions are mesh-local. In a `
|
|
123
|
+
* Shape positions are mesh-local. In a `MergedSplatMesh` that means scene
|
|
123
124
|
* coordinates - a shape overlapping two sources covers both as one continuous
|
|
124
125
|
* shape - so place them with `computeSplatBounds()`, which reports the sources
|
|
125
126
|
* at their current placement.
|
|
@@ -132,7 +133,7 @@ export interface LightingPreset {
|
|
|
132
133
|
readonly modifier: SplatModifier;
|
|
133
134
|
/**
|
|
134
135
|
* Mesh-local light direction; mutate `.value` to animate (no recompile).
|
|
135
|
-
* Inside a `
|
|
136
|
+
* Inside a `MergedSplatMesh` that frame is the scene's, so one direction lights
|
|
136
137
|
* every source consistently however they are posed.
|
|
137
138
|
*/
|
|
138
139
|
readonly direction: {
|
|
@@ -144,7 +145,7 @@ export interface LightingPreset {
|
|
|
144
145
|
* (`ctx.normal`, the least-variance covariance axis). A starting point for
|
|
145
146
|
* relit product shots - not a lighting rig. Works on both backends.
|
|
146
147
|
*
|
|
147
|
-
* In a `
|
|
148
|
+
* In a `MergedSplatMesh` the normal comes from the *placed* covariance, so a source
|
|
148
149
|
* that rotates relights as it turns.
|
|
149
150
|
*/
|
|
150
151
|
export declare function lightingPreset(options?: {
|
|
@@ -183,7 +184,7 @@ export interface DepthOfFieldPreset {
|
|
|
183
184
|
* than adding a truly isotropic screen-space disc. The physically exact aperture
|
|
184
185
|
* model adds `coc²·I` to the *projected 2D* covariance with the same √(det)
|
|
185
186
|
* opacity conservation the antialias filter uses - that path is
|
|
186
|
-
* `SplatMesh.setDepthOfField` / `
|
|
187
|
+
* `SplatMesh.setDepthOfField` / `UnifiedSplatMesh.setDepthOfField`
|
|
187
188
|
* (see `docs/guide/effects-and-modifiers.md`, M13).
|
|
188
189
|
*
|
|
189
190
|
* Opacity conservation is exact only with `antialias` on: without it, the
|
|
@@ -211,7 +212,7 @@ export interface RevealPreset {
|
|
|
211
212
|
* the M7 escape hatch - so this preset is **WebGPU-only** (see the fallback
|
|
212
213
|
* notes); force WebGL2 and it will not compile.
|
|
213
214
|
*
|
|
214
|
-
* The field is anchored to the mesh, so in a `
|
|
215
|
+
* The field is anchored to the mesh, so in a `MergedSplatMesh` a moved source
|
|
215
216
|
* reveals in step with whatever it now sits beside (and the pattern slides
|
|
216
217
|
* across it as it travels) rather than carrying its own schedule around.
|
|
217
218
|
*/
|
|
@@ -340,7 +341,7 @@ export declare const MAX_RELIGHTING_SHADOW_LIGHTS = 32;
|
|
|
340
341
|
* with `distance > 0` fade their umbra to lit via a Frostbite cutoff window
|
|
341
342
|
* (steepened by `decay`) so the shadow map far plane is not a hard cliff.
|
|
342
343
|
* Contribution `fill` adds occluded Lambert (range + cone for punctual lights)
|
|
343
|
-
* on top of that identity so
|
|
344
|
+
* on top of that identity so accent lights can light splats without wrapping
|
|
344
345
|
* through collision. Cascades (`midLight` / `outerLight` / `farLight`) still
|
|
345
346
|
* attach to the **first** directional only. At most
|
|
346
347
|
* {@link MAX_RELIGHTING_SHADOW_LIGHTS} independent lights are used; extras
|
package/dist/effects.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effects.js","sources":["../src/lib/relighting-pass.ts","../src/lib/effects.ts"],"sourcesContent":["/**\n * Host-safe lighting-map pass for {@link SplatMesh.setRelighting}.\n *\n * The relight example is a sidecar `render(relightScene, camera)` into an RT.\n * That only fills TSL `shadow()` maps when the renderer looks like a fresh\n * `createSplatRenderer` (`autoClear` true, no tone-map wrap). Host apps often\n * already own a `WebGPURenderer` with `autoClear: false` and an inline ACES /\n * sRGB `contextNode`. This helper isolates those for one pass, then restores\n * them, so the example works in a custom scene.\n *\n * Important: do **not** assign `contextNode = undefined`. WebGPURenderer reads\n * `contextNode.id` while building render objects; clearing it throws and the\n * lighting RT never fills.\n */\nimport * as THREE from 'three/webgpu';\nimport { context } from 'three/tsl';\n\nconst size = new THREE.Vector2();\nconst clearColor = new THREE.Color();\n\n/**\n * Passthrough context: keeps a stable `.id` for the render-object cache, but\n * does not apply host tone-mapping / sRGB encode to the linear factor map.\n */\nconst RELIGHT_PASS_CONTEXT = context({\n getOutput: (node: THREE.Node) => node,\n});\n\n/**\n * Minimal renderer surface {@link renderRelightingFactorMap} needs. A real\n * `THREE.WebGPURenderer` satisfies this; tests can pass a stub.\n */\nexport type RelightingFactorRenderer = {\n autoClear: boolean;\n shadowMap: { enabled: boolean; autoUpdate?: boolean };\n getDrawingBufferSize(target: THREE.Vector2): THREE.Vector2;\n getRenderTarget(): THREE.RenderTarget | null;\n getActiveCubeFace(): number;\n getActiveMipmapLevel(): number;\n getMRT(): THREE.MRTNode | null;\n setMRT(mrt: THREE.MRTNode | null): void;\n setRenderTarget(\n target: THREE.RenderTarget | null,\n activeCubeFace?: number,\n activeMipmapLevel?: number,\n ): void;\n getClearColor(target: THREE.Color): THREE.Color;\n getClearAlpha(): number;\n setClearColor(color: THREE.ColorRepresentation, alpha?: number): void;\n clear(): void;\n render(scene: THREE.Object3D, camera: THREE.Camera): void;\n contextNode: unknown;\n};\n\n/**\n * Renders `scene` from `camera` into `target` as a shadow-factor map:\n * resizes to the drawing buffer, clears **white + A0**, forces `autoClear`\n * and shadow maps on, and swaps in a passthrough `contextNode` so a host\n * ACES/sRGB wrap cannot turn the linear multiplier into speckle.\n *\n * Call each frame **before** `splats.update` / the main splat draw. Restore\n * is guaranteed even if `render` throws.\n */\nexport function renderRelightingFactorMap(\n renderer: RelightingFactorRenderer,\n scene: THREE.Scene,\n camera: THREE.Camera,\n target: THREE.RenderTarget,\n): void {\n renderer.getDrawingBufferSize(size);\n target.setSize(Math.max(1, size.x), Math.max(1, size.y));\n\n const previousTarget = renderer.getRenderTarget();\n const previousCubeFace = renderer.getActiveCubeFace();\n const previousMipmapLevel = renderer.getActiveMipmapLevel();\n const previousMrt = renderer.getMRT();\n const previousAlpha = renderer.getClearAlpha();\n const previousAutoClear = renderer.autoClear;\n const previousShadowEnabled = renderer.shadowMap.enabled;\n const previousShadowAutoUpdate = renderer.shadowMap.autoUpdate;\n const previousContext = renderer.contextNode;\n renderer.getClearColor(clearColor);\n\n try {\n renderer.contextNode = RELIGHT_PASS_CONTEXT;\n renderer.autoClear = true;\n renderer.shadowMap.enabled = true;\n if (previousShadowAutoUpdate === false) renderer.shadowMap.autoUpdate = true;\n renderer.setMRT(null);\n renderer.setRenderTarget(target);\n renderer.setClearColor(0xffffff, 0);\n renderer.clear();\n renderer.render(scene, camera);\n } finally {\n renderer.setRenderTarget(previousTarget, previousCubeFace, previousMipmapLevel);\n renderer.setMRT(previousMrt);\n renderer.setClearColor(clearColor, previousAlpha);\n renderer.autoClear = previousAutoClear;\n renderer.shadowMap.enabled = previousShadowEnabled;\n if (previousShadowAutoUpdate !== undefined) {\n renderer.shadowMap.autoUpdate = previousShadowAutoUpdate;\n }\n renderer.contextNode = previousContext;\n }\n}\n","/**\n * `@voluma/vlam/effects` - optional, tree-shakeable effects built on the M7\n * {@link SplatModifier} contract. Import only what you use; nothing here is\n * pulled into the core renderer bundle.\n *\n * - {@link sdfEffects}: a declarative sphere/box/cylinder shape list evaluated\n * in-shader from a bounded uniform array (a fixed unroll over `maxShapes`\n * slots, gated by the live count) - add/move/remove shapes is data-only\n * (no pipeline recompile), on WebGPU and the WebGL2 fallback alike (M7.4).\n * The kinds mirror the CPU selection volumes (`createSelectionVolume`), so\n * a host can highlight exactly what a selection would take.\n * - {@link lightingPreset} / {@link revealPreset}: small reference presets,\n * starting points rather than a framework (M7.5).\n * - {@link createRelightingProxy}: builds a gray lit mesh group from collision\n * tiles (or raw geometries) for PlayCanvas-style {@link SplatMesh.setRelighting}.\n * - {@link createRelightingShadowFactorMaterial}: writes a shadow **multiplier**\n * (1 = lit, <1 = umbra) so coverage does not statically tint splats.\n * Accepts one light or {@link RelightingLightContribution}[] with intensity\n * weights. Optional `color` / `diffuse` / `direction` add a Lambert boost on\n * top of that identity (RGB may exceed 1 on a HalfFloat lighting RT).\n * - {@link renderRelightingFactorMap}: host-safe lighting pass. Isolates\n * `autoClear`, shadow maps, and swaps a passthrough `contextNode` so the\n * example works on an existing `WebGPURenderer`, not only `createSplatRenderer`.\n * - {@link worldWarpPreset}: camera-centered sphere wrap (planet / bowl).\n * - {@link depthOfFieldPreset}: stylized modifier-based depth-of-field (M13).\n * For physically-modeled camera DoF prefer the core\n * {@link SplatMesh.setDepthOfField} / `UnifiedSplatRenderer.setDepthOfField`\n * path - this preset is the soft/approximate alternative built purely on\n * the modifier hook.\n *\n * Every preset here reads positions from `ctx.localCenter` - mesh-local space,\n * which inside a `SplatScene` is the splat's **placed** position. So a shape or\n * a reveal spans every source as one scene instead of travelling with a moved\n * one; see `docs/guide/effects-and-modifiers.md` for what that looks like, and\n * `ctx.sourceCenter` for the opposite behaviour.\n *\n * See `docs/guide/effects-and-modifiers.md`.\n */\nimport * as THREE from 'three/webgpu';\nimport {\n cameraPosition,\n float,\n int,\n mat3,\n min,\n mix,\n modelViewMatrix,\n normalWorld,\n objectPosition,\n positionWorld,\n shadow,\n smoothstep,\n uniform,\n uniformArray,\n vec2,\n vec3,\n vec4,\n wgslFn,\n reference,\n frameGroup,\n cos,\n} from 'three/tsl';\nimport type { SplatContext, SplatModifier } from './splat-modifier';\nimport { warn } from './logging';\nimport type { CollisionMeshTile } from './formats/lcc/collision-mesh';\nimport type { TriangleMeshData } from './formats/lcc/parse-mesh-ply';\n\ntype Node<T extends string> = THREE.Node<T>;\nfunction asNode<T extends string>(node: unknown): Node<T> {\n return node as Node<T>;\n}\n\n// --- SDF effects (M7.4) -----------------------------------------------------\n\n/**\n * A signed-distance primitive kind. `cylinder` is capped and runs along the\n * shape-local Y axis (rotate it with {@link SdfShape.rotation}).\n */\nexport type SdfShapeKind = 'sphere' | 'box' | 'cylinder';\n\n/**\n * What a shape does to the splats it covers:\n * - `tint` - blend the splat color toward the shape color.\n * - `desaturate` - blend the splat color toward its luminance.\n * - `hide` - fade the splat out (soft cutaway).\n * - `rim` - tint only a band around the shape's surface.\n */\nexport type SdfMode = 'tint' | 'desaturate' | 'hide' | 'rim';\n\n/** One SDF shape in a {@link sdfEffects} list. All spaces are mesh-local. */\nexport interface SdfShape {\n kind: SdfShapeKind;\n /** Shape center, mesh-local. Default `[0, 0, 0]`. */\n center?: readonly [number, number, number];\n /**\n * Sphere/cylinder radius. Required for `kind: 'sphere'` and\n * `kind: 'cylinder'`; must be positive - {@link SdfEffect.setShapes} throws\n * otherwise.\n */\n radius?: number;\n /**\n * Cylinder full height along the shape-local Y axis. Required (and only\n * used) for `kind: 'cylinder'`; must be positive - {@link SdfEffect.setShapes}\n * throws otherwise.\n */\n height?: number;\n /**\n * Box half-extents. Required (and only used) for `kind: 'box'`; every\n * component must be positive - {@link SdfEffect.setShapes} throws otherwise.\n */\n halfExtents?: readonly [number, number, number];\n /** Orientation quaternion `[x, y, z, w]` (boxes and cylinders). Default identity. */\n rotation?: readonly [number, number, number, number];\n /** Effect color (tint/rim). Default white. */\n color?: readonly [number, number, number];\n /** Soft-edge width in world units; 0 is a hard edge. Default `0`. */\n falloff?: number;\n /** Invert the test - affect the outside of the shape instead. */\n invert?: boolean;\n /** Effect strength in `[0, 1]`. Default `1`. */\n strength?: number;\n mode: SdfMode;\n}\n\n/** A live SDF effect: one modifier plus a data-only way to update its shapes. */\nexport interface SdfEffect {\n /** Attach to `mesh.modifiers`. Built once; updating shapes never recompiles. */\n readonly modifier: SplatModifier;\n /**\n * Replaces the shape list. Purely a uniform-buffer write, so animating\n * shape poses/colors/thresholds every frame costs no pipeline recompile.\n * Shapes beyond {@link maxShapes} are dropped with a one-time warning.\n *\n * Throws (before writing anything) if a sphere or cylinder lacks a positive\n * `radius`, a cylinder lacks a positive `height`, or a box lacks positive\n * `halfExtents` - a zero-size shape is always a caller bug, never a useful\n * no-op.\n */\n setShapes(shapes: readonly SdfShape[]): void;\n /** The compiled-in shape capacity (see {@link sdfEffects} `maxShapes`). */\n readonly maxShapes: number;\n /**\n * The packed uniform state behind the modifier - `maxShapes × SHAPE_STRIDE`\n * vec4 slots plus the live shape count.\n * @internal Exposed for unit tests of the packing; not public API.\n */\n readonly _uniforms: {\n readonly slots: readonly THREE.Vector4[];\n readonly count: { value: number };\n };\n}\n\n/** vec4 slots per shape in the uniform array (see the packing below). */\nconst SHAPE_STRIDE = 5;\nconst MODE_INDEX: Record<SdfMode, number> = { tint: 0, desaturate: 1, hide: 2, rim: 3 };\n\n// Shared defaults so a per-frame `setShapes` on sparse shape literals does not\n// allocate a fresh array per omitted field.\nconst DEFAULT_CENTER: readonly [number, number, number] = [0, 0, 0];\nconst DEFAULT_COLOR: readonly [number, number, number] = [1, 1, 1];\nconst DEFAULT_ROTATION: readonly [number, number, number, number] = [0, 0, 0, 1];\n\n/**\n * Builds a {@link SdfEffect} from a declarative shape list. Shapes live in a\n * bounded uniform array and are tested in-shader against each splat's local\n * center as a **fixed unroll over all `maxShapes` slots**, each slot gated by\n * `slot < count` so unused slots contribute nothing. (An in-shader loop is\n * not viable here - see the TSL varying constraint documented inside the\n * modifier.) Adding, moving or removing a shape - or changing the count - is\n * still a pure uniform write with no recompile. Works on WebGPU and the\n * WebGL2 fallback.\n *\n * `maxShapes` is the compiled capacity (default 32). Because every slot is\n * unrolled into the shader, raising it grows the shader's size and compile\n * time (and the uniform array; very large values can exceed WebGL2's uniform\n * limits), so keep it to what a scene needs.\n *\n * Shape positions are mesh-local. In a `SplatScene` that means scene\n * coordinates - a shape overlapping two sources covers both as one continuous\n * shape - so place them with `computeSplatBounds()`, which reports the sources\n * at their current placement.\n */\nexport function sdfEffects(\n initialShapes: readonly SdfShape[] = [],\n options: { maxShapes?: number } = {},\n): SdfEffect {\n const maxShapes = Math.max(1, Math.floor(options.maxShapes ?? 32));\n\n // One flat uniform array of vec4; SHAPE_STRIDE slots per shape. Mutating\n // these Vector4s (and `count`) re-uploads as plain uniforms - never a\n // recompile. `count` bounds the in-shader loop.\n const slots: THREE.Vector4[] = [];\n for (let i = 0; i < maxShapes * SHAPE_STRIDE; i++) slots.push(new THREE.Vector4());\n const shapesUniform = uniformArray(slots, 'vec4');\n const count = uniform(0, 'int');\n\n let warnedOverflow = false;\n const setShapes = (shapes: readonly SdfShape[]): void => {\n const n = Math.min(shapes.length, maxShapes);\n if (shapes.length > maxShapes && !warnedOverflow) {\n warnedOverflow = true;\n warn(\n `sdfEffects: ${shapes.length} shapes exceeds maxShapes=${maxShapes}; ` +\n `extra shapes are ignored. Raise maxShapes if you need more.`,\n );\n }\n // Validate before touching any slot, so a bad list never leaves the\n // uniform array half-updated.\n for (let i = 0; i < n; i++) {\n const s = shapes[i] as SdfShape;\n if (s.kind === 'box') {\n const he = s.halfExtents;\n if (!he || !(he[0] > 0) || !(he[1] > 0) || !(he[2] > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a box and requires halfExtents ` +\n `with three positive components (got ${he ? `[${he.join(', ')}]` : 'undefined'}).`,\n );\n }\n } else if (!(typeof s.radius === 'number' && s.radius > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a ${s.kind} and requires a positive ` +\n `radius (got ${String(s.radius)}).`,\n );\n } else if (s.kind === 'cylinder' && !(typeof s.height === 'number' && s.height > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a cylinder and requires a positive ` +\n `height (got ${String(s.height)}).`,\n );\n }\n }\n for (let i = 0; i < n; i++) {\n const s = shapes[i] as SdfShape;\n const b = i * SHAPE_STRIDE;\n const c = s.center ?? DEFAULT_CENTER;\n const col = s.color ?? DEFAULT_COLOR;\n const rot = s.rotation ?? DEFAULT_ROTATION;\n const kindIndex = s.kind === 'box' ? 1 : s.kind === 'cylinder' ? 2 : 0;\n (slots[b + 0] as THREE.Vector4).set(c[0], c[1], c[2], kindIndex);\n if (s.kind === 'box') {\n const he = s.halfExtents as readonly [number, number, number];\n (slots[b + 1] as THREE.Vector4).set(he[0], he[1], he[2], s.falloff ?? 0);\n } else if (s.kind === 'cylinder') {\n (slots[b + 1] as THREE.Vector4).set(\n s.radius as number,\n (s.height as number) / 2,\n 0,\n s.falloff ?? 0,\n );\n } else {\n (slots[b + 1] as THREE.Vector4).set(s.radius as number, 0, 0, s.falloff ?? 0);\n }\n (slots[b + 2] as THREE.Vector4).set(col[0], col[1], col[2], MODE_INDEX[s.mode]);\n (slots[b + 3] as THREE.Vector4).set(rot[0], rot[1], rot[2], rot[3]);\n (slots[b + 4] as THREE.Vector4).set(s.invert ? 1 : 0, s.strength ?? 1, 0, 0);\n }\n count.value = n;\n };\n setShapes(initialShapes);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // Unrolled over the fixed `maxShapes` slots as *pure expressions* (no\n // control flow): a splat's color is a chain of `select`/`mix` folds, one\n // per slot, each gated by `slot < count` so unused slots are transparent.\n // Control flow inside a modifier color is not viable - three.js evaluates\n // a color that reaches the fragment stage through a varying, and a varying\n // does not emit `Loop`/`If` statements - so the shape count drives a\n // uniform-gated unroll instead. Result: changing shapes or their count is\n // still a pure uniform write (no recompile); the cost is a shader whose\n // size grows with `maxShapes`.\n const p = ctx.localCenter;\n let rgb: Node<'vec3'> = asNode<'vec3'>(ctx.color.rgb);\n let alphaMul: Node<'float'> = float(1);\n\n for (let k = 0; k < maxShapes; k++) {\n const b = k * SHAPE_STRIDE;\n const el = (j: number): Node<'vec4'> => asNode<'vec4'>(shapesUniform.element(b + j));\n const s0 = el(0);\n const s1 = el(1);\n const s2 = el(2);\n const s3 = el(3);\n const s4 = el(4);\n\n const center = s0.xyz;\n const kind = s0.w; // 0 sphere · 1 box · 2 cylinder (stored as a float index)\n const size = s1.xyz;\n const falloff = s1.w.max(1e-4);\n const shapeColor = s2.xyz;\n const mode = s2.w;\n const invert = s4.x; // 0 · 1 flag\n const strength = s4.y;\n // Slots past the live count contribute nothing.\n const gate = int(k).lessThan(count).select(float(1), float(0));\n\n // Sample point in shape-local space: R⁻¹·(p − center). The sphere/box\n // and invert choices blend with `mix` on their 0/1 flags rather than a\n // `select`: three.js miscompiles a ConditionalNode whose branches carry\n // large sub-expressions (the box SDF) here, silently zeroing the result.\n const rel = p.sub(center);\n const pl = rotateByQuatConj(s3, rel);\n const dSphere = pl.length().sub(size.x);\n const dBox = sdBox(pl, size);\n const dCylinder = sdCappedCylinder(pl, size.x, size.y);\n // Kind index → 0/1 masks, folded with `mix` like the mode masks below.\n const boxMask = kind.greaterThan(0.5).and(kind.lessThan(1.5)).select(float(1), float(0));\n const cylinderMask = kind.greaterThan(1.5).select(float(1), float(0));\n const dRaw = mix(mix(dSphere, dBox, boxMask), dCylinder, cylinderMask);\n const d = mix(dRaw, dRaw.negate(), invert);\n\n // Inside coverage (1 within, fading out over `falloff`); rim coverage\n // peaks on the surface. Both scaled by strength and the live-slot gate.\n const insideCov = float(1)\n .sub(smoothstep(0, falloff, d))\n .mul(strength)\n .mul(gate);\n const rimCov = float(1)\n .sub(smoothstep(0, falloff, d.abs()))\n .mul(strength)\n .mul(gate);\n\n // Per-mode scalar masks (1 for the active mode, else 0). Applied by\n // folding the coverage with `mix`/multiply - NOT by a `select` that\n // reassigns `rgb`: three.js miscompiles a conditional whose branches\n // reuse the running value across an unrolled reassignment chain, so the\n // accumulation must stay pure arithmetic.\n const tintMask = mode.lessThan(0.5).select(float(1), float(0));\n const desatMask = mode.greaterThan(0.5).and(mode.lessThan(1.5)).select(float(1), float(0));\n const hideMask = mode.greaterThan(1.5).and(mode.lessThan(2.5)).select(float(1), float(0));\n const rimMask = mode.greaterThan(2.5).select(float(1), float(0));\n const luma = asNode<'float'>(rgb.dot(vec3(0.2126, 0.7152, 0.0722)));\n\n rgb = asNode<'vec3'>(mix(rgb, shapeColor, insideCov.mul(tintMask)));\n rgb = asNode<'vec3'>(mix(rgb, vec3(luma), insideCov.mul(desatMask)));\n rgb = asNode<'vec3'>(mix(rgb, shapeColor, rimCov.mul(rimMask)));\n alphaMul = asNode<'float'>(alphaMul.mul(float(1).sub(insideCov.mul(hideMask))));\n }\n\n return { color: vec4(rgb, ctx.color.a.mul(alphaMul)) };\n };\n\n return { modifier, setShapes, maxShapes, _uniforms: { slots, count } };\n}\n\n/** SDF of an axis-aligned box (shape-local) with half-extents `b`. */\nfunction sdBox(p: Node<'vec3'>, b: Node<'vec3'>): Node<'float'> {\n const q = p.abs().sub(b);\n const outside = q.max(vec3(0, 0, 0)).length();\n const inside = q.x.max(q.y.max(q.z)).min(0);\n return asNode<'float'>(outside.add(inside));\n}\n\n/** SDF of a Y-axis capped cylinder (shape-local): radius `r`, half-height `h`. */\nfunction sdCappedCylinder(p: Node<'vec3'>, r: Node<'float'>, h: Node<'float'>): Node<'float'> {\n const dr = p.xz.length().sub(r);\n const dy = p.y.abs().sub(h);\n const outside = vec2(dr.max(0), dy.max(0)).length();\n const inside = dr.max(dy).min(0);\n return asNode<'float'>(outside.add(inside));\n}\n\n/** Rotates `v` by the conjugate of quaternion `q` (xyzw): R⁻¹·v. */\nfunction rotateByQuatConj(q: Node<'vec4'>, v: Node<'vec3'>): Node<'vec3'> {\n const u = q.xyz;\n const s = q.w;\n const t = u.cross(v).mul(2);\n return asNode<'vec3'>(v.sub(t.mul(s)).add(u.cross(t)));\n}\n\n// --- Reference presets (M7.5) ----------------------------------------------\n\n/** A preset that exposes a live light direction uniform. */\nexport interface LightingPreset {\n readonly modifier: SplatModifier;\n /**\n * Mesh-local light direction; mutate `.value` to animate (no recompile).\n * Inside a `SplatScene` that frame is the scene's, so one direction lights\n * every source consistently however they are posed.\n */\n readonly direction: { value: THREE.Vector3 };\n}\n\n/**\n * Simple per-splat Lambertian shading using the splat's approximate normal\n * (`ctx.normal`, the least-variance covariance axis). A starting point for\n * relit product shots - not a lighting rig. Works on both backends.\n *\n * In a `SplatScene` the normal comes from the *placed* covariance, so a source\n * that rotates relights as it turns.\n */\nexport function lightingPreset(\n options: {\n direction?: readonly [number, number, number];\n ambient?: number;\n diffuse?: number;\n } = {},\n): LightingPreset {\n const dir = new THREE.Vector3(...(options.direction ?? [0.3, 1, 0.6])).normalize();\n const direction = uniform(dir);\n const ambient = float(options.ambient ?? 0.35);\n const diffuse = float(options.diffuse ?? 0.75);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n const ndl = ctx.normal.dot(direction.normalize()).max(0);\n const shade = ambient.add(ndl.mul(diffuse));\n return { color: vec4(ctx.color.rgb.mul(shade), ctx.color.a) };\n };\n return { modifier, direction: direction };\n}\n\n/** A live depth-of-field preset: focus plane and aperture are mutable uniforms. */\nexport interface DepthOfFieldPreset {\n readonly modifier: SplatModifier;\n /** World-unit distance to the focal plane; splats there stay sharp. Mutate\n * `.value` to rack focus (no recompile). Must stay positive. */\n readonly focusDistance: { value: number };\n /** Blur strength (aperture-like); 0 is no blur. Mutate `.value`. */\n readonly aperture: { value: number };\n}\n\n/**\n * Depth-of-field on the {@link SplatModifier} hook - **stylized / soft** path\n * (M13). Prefer {@link SplatMesh.setDepthOfField} / the core projected-2D\n * aperture model for camera DoF (isotropic screen-space CoC with √det opacity).\n *\n * A splat's circle of confusion grows with how far its view-space depth\n * sits from `focusDistance`; the modifier blurs it by enlarging it\n * (`scale = 1 + coc`) and, because the material scales the projected covariance\n * by `scale²`, divides opacity by `scale²` so the widened splat conserves its\n * integrated mass instead of over-brightening. Focus and aperture are live\n * uniforms, so racking focus every frame costs no recompile. Pure TSL - works\n * on WebGPU and the WebGL2 fallback.\n *\n * This is an *approximate* DoF: `scale` is a uniform 3D-covariance multiplier,\n * so the blur enlarges the splat's own (possibly anisotropic) footprint rather\n * than adding a truly isotropic screen-space disc. The physically exact aperture\n * model adds `coc²·I` to the *projected 2D* covariance with the same √(det)\n * opacity conservation the antialias filter uses - that path is\n * `SplatMesh.setDepthOfField` / `UnifiedSplatRenderer.setDepthOfField`\n * (see `docs/guide/effects-and-modifiers.md`, M13).\n *\n * Opacity conservation is exact only with `antialias` on: without it, the\n * fixed 0.3-px dilation dominates sub-pixel splats, so strong blur *dims*\n * distant splats (their footprint barely grows while alpha still falls by\n * 1/scale²) rather than blurring them.\n */\nexport function depthOfFieldPreset(\n options: { focusDistance?: number; aperture?: number; maxBlur?: number } = {},\n): DepthOfFieldPreset {\n const focusDistance = uniform(Math.max(1e-4, options.focusDistance ?? 10));\n const aperture = uniform(Math.max(0, options.aperture ?? 0.5));\n // Cap the enlargement so a splat far behind the focus plane cannot balloon\n // to cover the screen (and its 1/scale² opacity cannot underflow to nothing).\n const maxBlur = float(Math.max(0, options.maxBlur ?? 6));\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // View-space depth along the optical axis (camera looks down −z).\n const depth = ctx.viewCenter.z.negate().max(1e-4);\n // Clamped in-shader too: the constructor clamp cannot protect against a\n // later `focusDistance.value = 0`, which would divide by zero below and\n // NaN every splat's scale.\n const focus = focusDistance.max(1e-4);\n // Circle of confusion, normalized by the focus distance so it is scale-\n // invariant: 0 at the focal plane, growing with |depth − focus|.\n const coc = depth.sub(focus).abs().div(focus).mul(aperture).min(maxBlur);\n const blur = float(1).add(coc);\n // Conserve integrated opacity: the material scales area by blur², so peak\n // opacity must fall by 1/blur² or the blurred splat over-brightens. The\n // blur multiplies the running scale (and compensates only for its own\n // factor), so a preceding modifier's scale survives composition.\n const alpha = asNode<'float'>(ctx.color.a.div(blur.mul(blur)));\n return { scale: asNode<'float'>(ctx.scale.mul(blur)), color: vec4(ctx.color.rgb, alpha) };\n };\n return { modifier, focusDistance, aperture };\n}\n\n/** A preset that exposes a reveal `progress` uniform in `[0, 1]`. */\nexport interface RevealPreset {\n readonly modifier: SplatModifier;\n /** Reveal progress; 0 hides everything, 1 shows everything. */\n readonly progress: { value: number };\n}\n\n/**\n * Time-driven dissolve/reveal. A value-noise field over the splat's local\n * position is thresholded against `progress`: splats whose noise is below the\n * threshold are shown, with a soft alpha edge. The noise is a `wgslFn` chunk -\n * the M7 escape hatch - so this preset is **WebGPU-only** (see the fallback\n * notes); force WebGL2 and it will not compile.\n *\n * The field is anchored to the mesh, so in a `SplatScene` a moved source\n * reveals in step with whatever it now sits beside (and the pattern slides\n * across it as it travels) rather than carrying its own schedule around.\n */\nexport function revealPreset(options: { frequency?: number; edge?: number } = {}): RevealPreset {\n const progress = uniform(0);\n const frequency = options.frequency ?? 3;\n // The edge doubles as the threshold-range margin below, so it must stay in\n // [0, 0.5] for the remap to be well-formed.\n const edge = Math.min(Math.max(options.edge ?? 0.08, 0), 0.5);\n\n // Value noise as a single self-contained WGSL function: hash the 8 corners\n // of the lattice cell, then trilinearly interpolate with smoothstep weights\n // (g²(3 − 2g)). Every corner hash is a `fract` in [0, 1) and `mix` is a\n // convex combination, so the result is provably in [0, 1) - the reveal\n // threshold math below depends on that bound. The hashes are `let`-bound\n // locals so the chunk stays one function - three.js `wgslFn` binds call\n // inputs to the *first* function it finds, so a multi-function chunk would\n // need the entry first anyway; keeping it to one avoids that footgun.\n const vnoise = wgslFn(/* wgsl */ `\n fn vnoise( x: vec3<f32> ) -> f32 {\n let i = floor( x );\n let g = fract( x );\n let u = g * g * ( 3.0 - 2.0 * g );\n let k = vec3<f32>( 12.9898, 78.233, 37.719 );\n let s = 43758.5453;\n let h000 = fract( sin( dot( i + vec3<f32>( 0.0, 0.0, 0.0 ), k ) ) * s );\n let h100 = fract( sin( dot( i + vec3<f32>( 1.0, 0.0, 0.0 ), k ) ) * s );\n let h010 = fract( sin( dot( i + vec3<f32>( 0.0, 1.0, 0.0 ), k ) ) * s );\n let h110 = fract( sin( dot( i + vec3<f32>( 1.0, 1.0, 0.0 ), k ) ) * s );\n let h001 = fract( sin( dot( i + vec3<f32>( 0.0, 0.0, 1.0 ), k ) ) * s );\n let h101 = fract( sin( dot( i + vec3<f32>( 1.0, 0.0, 1.0 ), k ) ) * s );\n let h011 = fract( sin( dot( i + vec3<f32>( 0.0, 1.0, 1.0 ), k ) ) * s );\n let h111 = fract( sin( dot( i + vec3<f32>( 1.0, 1.0, 1.0 ), k ) ) * s );\n let x00 = mix( h000, h100, u.x );\n let x10 = mix( h010, h110, u.x );\n let x01 = mix( h001, h101, u.x );\n let x11 = mix( h011, h111, u.x );\n let y0 = mix( x00, x10, u.y );\n let y1 = mix( x01, x11, u.y );\n return mix( y0, y1, u.z );\n }\n `);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // Each splat gets a per-position threshold from the noise; it appears as\n // `progress` sweeps past that threshold, with a soft `edge`-wide band.\n // The noise is in [0, 1); remapping it to [edge, 1 − edge] keeps the whole\n // smoothstep band inside [0, 1], so progress 0 hides every splat fully and\n // progress 1 shows every splat fully - the documented contract.\n const noise = asNode<'float'>(vnoise({ x: ctx.localCenter.mul(frequency) }));\n const threshold = asNode<'float'>(noise.mul(1 - 2 * edge).add(edge));\n const alpha = smoothstep(threshold.sub(edge), threshold.add(edge), progress);\n const visible = progress.greaterThan(threshold.sub(edge));\n return { color: vec4(ctx.color.rgb, ctx.color.a.mul(alpha)), visible: asNode<'bool'>(visible) };\n };\n return { modifier, progress: progress };\n}\n\n/** A live world-warp preset: signed intensity and radius are mutable uniforms. */\nexport interface WorldWarpPreset {\n readonly modifier: SplatModifier;\n /**\n * Signed warp amount in `[-1, 1]`. Positive wraps the street *down* so you\n * walk on top of the sphere (tiny planet); negative wraps it *up* so you\n * stand in the bowl (Inception fold). `0` is identity. Mutate `.value` to\n * animate (no recompile).\n */\n readonly intensity: { value: number };\n /**\n * Distance at which the wrap becomes strong. Nearby splats (`depth ≪\n * radius`) stay put so walk/fly controls still read as moving through the\n * unwarped near field. Must stay positive. Mutate `.value`.\n */\n readonly radius: { value: number };\n}\n\n/**\n * Camera-centered splat warp on the {@link SplatModifier} hook: one signed\n * `intensity` uniform, two opposite hinges of the same view-space wrap. The\n * camera is not moved - only splat centers (and their rigid orientation)\n * change, so WASD / orbit stay as they are.\n *\n * Both sides hinge view-space `(y, z)` around camera +X by\n * `(π/2) · atan(depth / radius)`:\n *\n * - `intensity > 0` - planet: the far street wraps *down* under your feet so\n * you walk on top of the sphere.\n * - `intensity < 0` - fold: the far street wraps *up* over your head so you\n * stand in the bowl.\n * - `intensity = 0` is identity (`offset = 0`, rotation unchanged).\n *\n * The shader mixes each full-strength hinge toward the original position by\n * `|intensity|`. Orientation follows the orthonormalized Jacobian so\n * ellipsoids wrap with the surface rather than sliding. Pure TSL - WebGPU\n * and the WebGL2 fallback.\n *\n * Displaced splats keep their **pre-displacement** depth-sort order (the\n * modifier-stack contract). Extreme warps can composite in the wrong order;\n * this preset does not re-sort on warped centers.\n */\nexport function worldWarpPreset(\n options: { intensity?: number; radius?: number } = {},\n): WorldWarpPreset {\n const intensity = uniform(Math.min(1, Math.max(-1, options.intensity ?? 0)));\n const radius = uniform(Math.max(1e-4, options.radius ?? 1));\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n const k = asNode<'float'>(intensity.clamp(-1, 1));\n const R = asNode<'float'>(radius.max(1e-4));\n const v = ctx.viewCenter;\n const planetW = k.max(0);\n const foldW = k.negate().max(0);\n\n const depth = v.z.negate().max(0);\n const alpha = depth\n .div(R)\n .atan()\n .mul(float(Math.PI * 0.5));\n const cA = alpha.cos();\n const sFold = alpha.sin();\n const sPlanet = sFold.negate();\n const foldV = vec3(v.x, v.y.mul(cA).sub(v.z.mul(sFold)), v.y.mul(sFold).add(v.z.mul(cA)));\n const planetV = vec3(v.x, v.y.mul(cA).sub(v.z.mul(sPlanet)), v.y.mul(sPlanet).add(v.z.mul(cA)));\n\n const warpedV = mix(mix(v, foldV, foldW), planetV, planetW);\n\n const halfPiR = float(Math.PI * 0.5).mul(R);\n const dFoldDz = v.z\n .lessThan(0)\n .select(halfPiR.negate().div(depth.mul(depth).add(R.mul(R))), float(0));\n const dPlanetDz = dFoldDz.negate();\n const foldC0 = vec3(1, 0, 0);\n const foldC1 = vec3(0, cA, sFold);\n const foldC2 = vec3(0, sFold.negate().sub(foldV.z.mul(dFoldDz)), cA.add(foldV.y.mul(dFoldDz)));\n const planetC0 = vec3(1, 0, 0);\n const planetC1 = vec3(0, cA, sPlanet);\n const planetC2 = vec3(\n 0,\n sPlanet.negate().sub(planetV.z.mul(dPlanetDz)),\n cA.add(planetV.y.mul(dPlanetDz)),\n );\n\n const i0 = vec3(1, 0, 0);\n const i1 = vec3(0, 1, 0);\n const i2 = vec3(0, 0, 1);\n const j0 = mix(mix(i0, foldC0, foldW), planetC0, planetW);\n const j1 = mix(mix(i1, foldC1, foldW), planetC1, planetW);\n const j2 = mix(mix(i2, foldC2, foldW), planetC2, planetW);\n\n const r0 = j0.div(j0.length().max(1e-6));\n const r1u = j1.sub(r0.mul(r0.dot(j1)));\n const r1 = r1u.div(r1u.length().max(1e-6));\n const r2gs = j2.sub(r0.mul(r0.dot(j2))).sub(r1.mul(r1.dot(j2)));\n const r2 = r2gs.div(r2gs.length().max(1e-6));\n const qView = asNode<'mat3'>(mat3(r0, r1, r2));\n const viewR = asNode<'mat3'>(modelViewMatrix.toMat3());\n const viewRT = viewR.transpose();\n const warpR = asNode<'mat3'>(viewRT.mul(qView).mul(viewR));\n\n return {\n offset: asNode<'vec3'>(ctx.offset.add(viewRT.mul(warpedV.sub(v)))),\n rotation: asNode<'mat3'>(warpR.mul(ctx.rotation)),\n };\n };\n return { modifier, intensity, radius };\n}\n\n// --- Proxy-mesh relighting helper -------------------------------------------\n\nexport { renderRelightingFactorMap, type RelightingFactorRenderer } from './relighting-pass';\n\n/** Handle returned by {@link createRelightingProxy}. */\nexport interface RelightingProxy {\n /** World-space group of lit proxy meshes (hide from the main splat scene). */\n readonly group: THREE.Group;\n /**\n * Configures a material for the relight coverage pass: gray albedo and\n * opaque alpha so uncovered RT pixels stay at clear-alpha 0.\n */\n configureMaterial(material: THREE.Material): void;\n /** Disposes geometries and the default material owned by this helper. */\n dispose(): void;\n}\n\n/** One independent shadow-casting light for {@link createRelightingShadowFactorMaterial}. */\nexport type RelightingLightContribution = {\n light: THREE.Light;\n /** Relative umbra weight. Default `1`. Values `<= 0` are skipped unless `fill` is set. */\n intensity?: number;\n /**\n * Additive Lambert fill on top of the umbra factor (RGB may exceed 1).\n * Gated by `shadow()`, facing, and for spot/point lights the Frostbite\n * `distance` window (and the spot cone). `0` / omitted = umbra only.\n */\n fill?: number;\n};\n\n/** Options for {@link createRelightingShadowFactorMaterial}. */\nexport type RelightingShadowFactorOptions = {\n umbra?: number;\n receiveUpMin?: number;\n color?: THREE.Color;\n diffuse?: number;\n direction?: THREE.Vector3;\n /**\n * How independent contributions combine into one multiplier.\n * - `average` (default): intensity-weighted mean of shadows (fill lights).\n * - `min`: union of umbras — each caster keeps its own silhouette. Contribution\n * `intensity` scales umbra depth: `0` → none, `1` → default `umbra`,\n * `>1` → darker than default (`umbra_i = clamp(1 - I*(1-umbra), 0, 1)`).\n */\n combine?: 'average' | 'min';\n midLight?: THREE.Light;\n midRadius?: number;\n outerLight?: THREE.Light;\n outerRadius?: number;\n farLight?: THREE.Light;\n nearRadius?: number;\n};\n\n/**\n * Cap on independent shadow lights (cascades of the first directional do not\n * count). Extra contributions are ignored. The compiled graph unrolls only\n * the live contribution count, not a padded 32-wide loop.\n */\nexport const MAX_RELIGHTING_SHADOW_LIGHTS = 32;\n\nconst contributionIntensity = (contribution: RelightingLightContribution): number => {\n const value = contribution.intensity;\n if (!Number.isFinite(value)) return 1;\n return Math.max(0, value as number);\n};\n\nconst contributionFill = (contribution: RelightingLightContribution): number => {\n const value = contribution.fill;\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, value as number);\n};\n\n/** Light pose/color must track every frame. `uniform(vec)` keeps object identity, so in-place `copy()` does not re-upload. */\nconst liveRef = <T extends string>(name: string, type: string, object: object): Node<T> => {\n const ref = reference(name, type, object);\n // three.js implements ReferenceNode.setGroup(), but its public declaration omits it.\n (ref as unknown as { setGroup(group: typeof frameGroup): unknown }).setGroup(frameGroup);\n return asNode<T>(ref);\n};\n\n/**\n * Fade punctual (spot / point) umbras to lit as fragments approach `light.distance`.\n * Directional lights return 1. Uses the Frostbite cutoff window so weight is 0 at\n * cutoff (no hard shadow-map cliff). `light.decay` steepens the falloff (>1).\n */\nconst punctualUmbraWeight = (light: THREE.Light): Node<'float'> => {\n const punctual = light as THREE.SpotLight & THREE.PointLight;\n const isPunctual = punctual.isSpotLight === true || punctual.isPointLight === true;\n if (!isPunctual) return float(1);\n const cutoff = Number(punctual.distance);\n if (!(cutoff > 0)) return float(1);\n const decay = Math.max(Number(punctual.decay) || 1, 0.01);\n const lightDistance = asNode<'float'>(positionWorld.distance(objectPosition(punctual)));\n const range = liveRef<'float'>('distance', 'float', punctual).max(1e-4);\n // pow2(saturate(1 - pow4(d/cutoff))) → 1 near light, 0 at cutoff\n const window = asNode<'float'>(lightDistance.div(range).pow4().oneMinus().clamp().pow2());\n return asNode<'float'>(window.pow(float(decay)));\n};\n\n/**\n * Ranged Lambert toward a punctual light, occluded by its shadow map.\n * Spot cone uses `light.angle` / `penumbra` and `light.target`.\n * Does **not** use `receiveUpMin`: that slope gate keeps umbra off noisy\n * vertical foliage, but it also erased fill on tree stems, so splats only\n * lit after the host fell back to un-occluded Lambert.\n */\nconst punctualFillTerm = (light: THREE.Light, fill: number, vis: Node<'float'>): Node<'vec3'> => {\n const punctual = light as THREE.SpotLight & THREE.PointLight;\n const isSpot = punctual.isSpotLight === true;\n const isPoint = punctual.isPointLight === true;\n if (!isSpot && !isPoint) return vec3(0, 0, 0);\n\n const lightPos = asNode<'vec3'>(objectPosition(punctual));\n const toLightVec = asNode<'vec3'>(lightPos.sub(positionWorld));\n const dist = asNode<'float'>(toLightVec.length().max(1e-4));\n const toLight = asNode<'vec3'>(toLightVec.div(dist));\n const ndl = asNode<'float'>(normalWorld.dot(toLight).max(0));\n const window = punctualUmbraWeight(light);\n let mask = asNode<'float'>(window.mul(ndl).mul(vis));\n\n if (isSpot) {\n const spot = light as THREE.SpotLight;\n const shine = asNode<'vec3'>(objectPosition(spot.target).sub(lightPos).normalize());\n const outer = liveRef<'float'>('angle', 'float', spot).max(1e-3);\n const penumbra = liveRef<'float'>('penumbra', 'float', spot).clamp(0, 0.95);\n const inner = asNode<'float'>(outer.mul(float(1).sub(penumbra)).max(1e-3));\n const cone = asNode<'float'>(smoothstep(cos(outer), cos(inner), toLight.negate().dot(shine)));\n mask = asNode<'float'>(mask.mul(cone));\n }\n\n const chroma = liveRef<'vec3'>('color', 'color', light);\n return asNode<'vec3'>(chroma.mul(float(fill)).mul(mask));\n};\n\nconst directionalFillTerm = (\n light: THREE.Light,\n fill: number,\n vis: Node<'float'>,\n): Node<'vec3'> => {\n const dirLight = light as THREE.DirectionalLight;\n if (dirLight.isDirectionalLight !== true) return vec3(0, 0, 0);\n const shine = asNode<'vec3'>(\n objectPosition(dirLight.target).sub(objectPosition(dirLight)).normalize(),\n );\n const ndl = asNode<'float'>(normalWorld.dot(shine.negate()).max(0));\n const chroma = liveRef<'vec3'>('color', 'color', light);\n return asNode<'vec3'>(chroma.mul(float(fill)).mul(ndl).mul(vis));\n};\n\nconst normalizeRelightingLights = (\n lights: THREE.Light | RelightingLightContribution[],\n): RelightingLightContribution[] => {\n const list = Array.isArray(lights) ? lights : [{ light: lights, intensity: 1 }];\n const weighted: RelightingLightContribution[] = [];\n for (const contribution of list) {\n if (weighted.length >= MAX_RELIGHTING_SHADOW_LIGHTS) break;\n if (contributionIntensity(contribution) <= 0 && contributionFill(contribution) <= 0) continue;\n weighted.push(contribution);\n }\n return weighted;\n};\n\nconst cascadedShadow = (\n light: THREE.Light,\n options: RelightingShadowFactorOptions,\n dist: Node<'float'>,\n): Node<'float'> => {\n const nearRadius = Number.isFinite(options.nearRadius)\n ? Math.max(1, options.nearRadius as number)\n : options.midLight\n ? 20\n : 100;\n const midRadius = Number.isFinite(options.midRadius)\n ? Math.max(nearRadius, options.midRadius as number)\n : 50;\n const outerRadius = Number.isFinite(options.outerRadius)\n ? Math.max(midRadius, options.outerRadius as number)\n : 160;\n const blendAt = (inner: Node<'float'>, outer: Node<'float'>, radius: number): Node<'float'> =>\n asNode<'float'>(mix(inner, outer, smoothstep(float(radius * 0.7), float(radius * 0.95), dist)));\n let shadowed = asNode<'float'>(shadow(light));\n if (options.midLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.midLight)), nearRadius);\n if (options.outerLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.outerLight)), midRadius);\n if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), outerRadius);\n }\n } else if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), midRadius);\n }\n } else if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), nearRadius);\n }\n return shadowed;\n};\n\n/**\n * Node material for a **shadow-factor** lighting RT used with\n * {@link SplatMesh.setRelighting}.\n *\n * Fragment output is `vec4(vec3(mix(umbra, 1, shadow)) + boost, 1)`:\n * - covered + lit → RGB ≈ 1 (identity modulate when brightness/background are 1)\n * - covered + umbra → RGB ≈ `umbra` (soft darken; never pure black)\n * - uncovered stays clear-alpha 0 from the RT clear\n * - with `diffuse` > 0, facing surfaces add `color * NdotL * diffuse`\n * (RGB may exceed 1; use a HalfFloat lighting RT)\n *\n * Pass one {@link THREE.Light} (demo / single-sun) or an array of\n * {@link RelightingLightContribution} so independent lights share one\n * multiplier. Default combine is intensity-weighted average\n * (`illum = sum(I_i * shadow_i) / sum(I_i)`); pass `combine: 'min'` for a\n * union of umbras so each caster keeps its silhouette. Spot / point lights\n * with `distance > 0` fade their umbra to lit via a Frostbite cutoff window\n * (steepened by `decay`) so the shadow map far plane is not a hard cliff.\n * Contribution `fill` adds occluded Lambert (range + cone for punctual lights)\n * on top of that identity so marker lamps can light splats without wrapping\n * through collision. Cascades (`midLight` / `outerLight` / `farLight`) still\n * attach to the **first** directional only. At most\n * {@link MAX_RELIGHTING_SHADOW_LIGHTS} independent lights are used; extras\n * are ignored. Graph size follows the live contribution count (not a padded\n * loop of that width).\n *\n * By default, only **upward** proxy faces receive (`receiveUpMin`). Vertical\n * and noisy foliage triangles still **cast**, but they do not self-shadow —\n * that PCF acne reads as per-frame sparkle in trees. Pass `receiveUpMin: 0`\n * to receive on every face. Contribution `fill` does **not** use that slope\n * gate (stems would otherwise stay unlit in the factor map).\n *\n * Clear the lighting RT to **RGB 1, A 0** (not black). Softness / bilinear\n * samples at coverage edges otherwise pull in black and draw a dark outline\n * of every collision triangle.\n *\n * Prefer this over a lit MeshStandard / ShadowMaterial pass when the host wants\n * cast umbras without a static dark stamp on the whole collision footprint.\n * Assign to proxy meshes with `castShadow` and `receiveShadow` both true; the\n * umbra shape follows those triangles (splat foliage cannot cast).\n *\n * @param lights - Shadow-casting light, or intensity-weighted contributions.\n * The first entry is the innermost cascade when `midLight` / `outerLight` /\n * `farLight` are set.\n * @param options.umbra - Multiplier in full shadow, in `[0, 1]`. Default `0.45`.\n * @param options.receiveUpMin - World-up `normal.y` below which the face does\n * not receive. Default `0.25`. `0` disables the slope gate.\n * @param options.color - Light tint for the optional Lambert boost. Mutate\n * in place; the material holds this object. Default white.\n * @param options.diffuse - Lambert boost on top of identity. Default `0`\n * (shadow multiplier only). Facing, unshadowed coverage becomes\n * `1 + color * NdotL * diffuse`.\n * @param options.direction - World-space direction **toward** the light.\n * Mutate in place each frame as the sun moves. Default `(0, 1, 0)`.\n * @param options.midLight - Optional mid cascade (demo: ~50 m). Blend from\n * the first light over `nearRadius`.\n * @param options.midRadius - World metres where the mid cascade yields to\n * `outerLight` (or `farLight`). Default `50`.\n * @param options.outerLight - Optional outer cascade (demo: ~160 m) between\n * mid and scene-sized far. Blend from mid over `midRadius`, then to\n * `farLight` over `outerRadius`.\n * @param options.outerRadius - World metres where the outer cascade yields to\n * `farLight`. Default `160`. Ignored without `outerLight` and `farLight`.\n * @param options.farLight - Optional scene-sized cascade. Out-of-frustum\n * `shadow()` is 1 (lit), so inner maps can stay tight without clipping\n * distant umbras. Blend by camera distance.\n * @param options.nearRadius - World metres where the inner cascade yields to\n * `midLight` (or to `farLight` if there is no mid). Default `20` with\n * `midLight`, else `100`.\n */\nexport function createRelightingShadowFactorMaterial(\n lights: THREE.Light | RelightingLightContribution[],\n options: RelightingShadowFactorOptions = {},\n): THREE.MeshStandardNodeMaterial {\n const umbra = Number.isFinite(options.umbra)\n ? Math.min(1, Math.max(0, options.umbra as number))\n : 0.45;\n const receiveUpMin = Number.isFinite(options.receiveUpMin)\n ? Math.min(0.95, Math.max(0, options.receiveUpMin as number))\n : 0.25;\n const diffuse = Number.isFinite(options.diffuse) ? Math.max(0, options.diffuse as number) : 0;\n const material = new THREE.MeshStandardNodeMaterial();\n material.side = THREE.DoubleSide;\n material.color = new THREE.Color(1, 1, 1);\n material.roughness = 1;\n material.metalness = 0;\n material.transparent = false;\n material.depthWrite = true;\n // polygonOffset reduces self-shadow acne that reads as mesh wireframe.\n material.polygonOffset = true;\n material.polygonOffsetFactor = 2;\n material.polygonOffsetUnits = 2;\n // Bypass MeshStandard lighting. Shadow attenuation is the base multiplier;\n // optional Lambert is added on top of identity so unshadowed coverage stays\n // ≈ 1 when `diffuse` is 0.\n const dist = positionWorld.distance(cameraPosition);\n const contributions = normalizeRelightingLights(lights);\n const receive =\n receiveUpMin > 0\n ? asNode<'float'>(smoothstep(float(receiveUpMin), float(receiveUpMin + 0.3), normalWorld.y))\n : float(1);\n\n const shadowTermAt = (index: number, light: THREE.Light): Node<'float'> => {\n const raw = index === 0 ? cascadedShadow(light, options, dist) : asNode<'float'>(shadow(light));\n // Mix toward lit (1) as punctual range fades so umbras soften instead of\n // clipping at the shadow-map far plane.\n return asNode<'float'>(mix(float(1), raw, punctualUmbraWeight(light)));\n };\n // A ShadowNode owns its shadow render resources. Reuse one node per\n // contribution so adding fill does not render the same shadow map twice.\n const shadowTerms = contributions.map((contribution, index) =>\n shadowTermAt(index, contribution.light),\n );\n\n let factor: Node<'float'>;\n let shadowed: Node<'float'> = float(1);\n\n if (options.combine === 'min' && contributions.length > 0) {\n // Union of umbras with per-light depth from contribution intensity:\n // umbra_i = clamp(1 - I * (1 - umbra), 0, 1)\n // so I=0 → no shadow, I=1 → default umbra, I>1 → darker than default.\n let factorCombined: Node<'float'> = float(1);\n for (let i = 0; i < contributions.length; i++) {\n const contribution = contributions[i]!;\n const intensity = contributionIntensity(contribution);\n if (intensity <= 0) continue;\n const umbra_i = Math.min(1, Math.max(0, 1 - intensity * (1 - umbra)));\n const term = shadowTerms[i]!;\n const factor_i = asNode<'float'>(mix(float(umbra_i), float(1), term));\n factorCombined = asNode<'float'>(min(factorCombined, factor_i));\n }\n shadowed = factorCombined;\n factor = asNode<'float'>(mix(float(1), factorCombined, receive));\n } else {\n const shadowIndices = contributions\n .map((contribution, index) => ({ index, intensity: contributionIntensity(contribution) }))\n .filter(({ intensity }) => intensity > 0);\n if (shadowIndices.length === 1) {\n shadowed = shadowTerms[shadowIndices[0]!.index]!;\n } else if (shadowIndices.length > 1) {\n let illum: Node<'float'> = float(0);\n let denom = 0;\n for (const { index, intensity } of shadowIndices) {\n denom += intensity;\n const term = shadowTerms[index]!;\n illum = asNode<'float'>(illum.add(float(intensity).mul(term)));\n }\n shadowed = asNode<'float'>(illum.div(float(denom)));\n }\n if (shadowIndices.length > 0) {\n const attenuation = mix(float(1), shadowed, receive);\n factor = mix(float(umbra), float(1), attenuation);\n } else {\n factor = float(1);\n }\n }\n\n let boost: Node<'vec3'> = vec3(0, 0, 0);\n if (diffuse > 0) {\n const lightColor = uniform(options.color ?? new THREE.Color(1, 1, 1));\n const lightDir = uniform(options.direction ?? new THREE.Vector3(0, 1, 0));\n const ndl = asNode<'float'>(normalWorld.dot(lightDir.normalize()).max(0));\n boost = asNode<'vec3'>(lightColor.mul(ndl).mul(float(diffuse)).mul(receive).mul(shadowed));\n }\n for (let i = 0; i < contributions.length; i++) {\n const contribution = contributions[i]!;\n const fill = contributionFill(contribution);\n if (fill <= 0) continue;\n const light = contribution.light;\n const vis = shadowTerms[i]!;\n const punctual = punctualFillTerm(light, fill, vis);\n const directional = directionalFillTerm(light, fill, vis);\n boost = asNode<'vec3'>(boost.add(punctual).add(directional));\n }\n material.outputNode = vec4(vec3(factor).add(boost), float(1));\n return material;\n}\n\nfunction triangleMeshToGeometry(data: TriangleMeshData): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(data.positions.slice(), 3));\n geometry.setIndex(new THREE.BufferAttribute(data.indices.slice(), 1));\n // Normals are computed after any world bake in {@link createRelightingProxy}.\n return geometry;\n}\n\n/**\n * Builds a PlayCanvas-style relighting **proxy** from collision tiles or raw\n * geometries. The host places {@link RelightingProxy.group} on a dedicated\n * layer / scene with lights (never the main splat scene), renders that into\n * an RGBA RT matching the main camera, then calls\n * {@link SplatMesh.setRelighting} with the RT texture.\n *\n * Collision meshes are a convenient stand-in when their silhouette is good\n * enough; a denser reconstructed mesh is often better. This helper does not\n * own lights or the render target.\n *\n * When `matrixWorld` is set (e.g. streamed mesh world matrix with the LCC\n * Z-up→Y-up correction), it is **baked into the geometry** the same way the\n * demo collision BVH path does - so the proxy aligns with splats without\n * depending on a live Object3D transform.\n *\n * Coarse collision proxies leave hard coverage silhouettes; hosts should pass\n * `RelightingSettings.softness` (and often a half-res lighting RT) so those\n * edges do not look like a static shadow.\n *\n * For cast umbras **without** a static dark footprint, render the proxy with\n * {@link createRelightingShadowFactorMaterial} so RT RGB is a multiplier\n * (unshadowed ≈ 1), not a lit-gray appearance.\n */\nexport function createRelightingProxy(\n options: {\n /** LCC collision tiles (source-local). Prefer with `matrixWorld`. */\n tiles?: readonly CollisionMeshTile[];\n /** Extra or alternative geometries already in the desired frame. */\n geometries?: readonly THREE.BufferGeometry[];\n /**\n * Source-local → world (e.g. streamed mesh `matrixWorld`). Baked into\n * tile / cloned geometries; leave unset when geometries are already world.\n */\n matrixWorld?: THREE.Matrix4;\n /** Gray albedo in `[0, 1]`. Default `0.5` (PlayCanvas brightness 2). */\n albedo?: number;\n } = {},\n): RelightingProxy {\n const albedo = options.albedo ?? 0.5;\n const group = new THREE.Group();\n const worldMatrix = options.matrixWorld ?? null;\n\n const ownedGeometries: THREE.BufferGeometry[] = [];\n const material = new THREE.MeshStandardMaterial({\n color: new THREE.Color(albedo, albedo, albedo),\n roughness: 1,\n metalness: 0,\n side: THREE.DoubleSide,\n });\n\n const bakeWorld = (geometry: THREE.BufferGeometry): void => {\n if (!worldMatrix) return;\n geometry.applyMatrix4(worldMatrix);\n };\n\n const addGeometry = (geometry: THREE.BufferGeometry, owns: boolean): void => {\n let geo = geometry;\n let owned = owns;\n if (worldMatrix && !owns) {\n // Caller still owns the original; bake a private copy.\n geo = geometry.clone();\n owned = true;\n }\n bakeWorld(geo);\n geo.computeVertexNormals();\n if (owned) ownedGeometries.push(geo);\n const mesh = new THREE.Mesh(geo, material);\n mesh.castShadow = true;\n mesh.receiveShadow = true;\n // Proxy is lighting-only; never participate in picking / raycasts.\n mesh.raycast = () => undefined;\n group.add(mesh);\n };\n\n for (const tile of options.tiles ?? []) {\n addGeometry(triangleMeshToGeometry(tile.data), true);\n }\n for (const geometry of options.geometries ?? []) {\n addGeometry(geometry, false);\n }\n\n const configureMaterial = (mat: THREE.Material): void => {\n mat.side = THREE.DoubleSide;\n mat.transparent = false;\n mat.opacity = 1;\n if ('color' in mat && mat.color instanceof THREE.Color) {\n mat.color.setRGB(albedo, albedo, albedo);\n }\n if ('roughness' in mat && typeof mat.roughness === 'number') {\n (mat as THREE.MeshStandardMaterial).roughness = 1;\n }\n if ('metalness' in mat && typeof mat.metalness === 'number') {\n (mat as THREE.MeshStandardMaterial).metalness = 0;\n }\n };\n\n return {\n group,\n configureMaterial,\n dispose: () => {\n material.dispose();\n for (const geometry of ownedGeometries) geometry.dispose();\n group.clear();\n },\n };\n}\n"],"names":["size","THREE","clearColor","RELIGHT_PASS_CONTEXT","context","node","renderRelightingFactorMap","renderer","scene","camera","target","previousTarget","previousCubeFace","previousMipmapLevel","previousMrt","previousAlpha","previousAutoClear","previousShadowEnabled","previousShadowAutoUpdate","previousContext","SHAPE_STRIDE","MODE_INDEX","DEFAULT_CENTER","DEFAULT_COLOR","DEFAULT_ROTATION","sdfEffects","initialShapes","options","maxShapes","slots","i","shapesUniform","uniformArray","count","uniform","warnedOverflow","setShapes","shapes","n","warn","he","b","c","col","rot","kindIndex","ctx","p","rgb","alphaMul","float","k","el","j","s0","s1","s2","s3","s4","center","kind","falloff","shapeColor","mode","invert","strength","gate","int","rel","pl","rotateByQuatConj","dSphere","dBox","sdBox","dCylinder","sdCappedCylinder","boxMask","cylinderMask","dRaw","mix","d","insideCov","smoothstep","rimCov","tintMask","desatMask","hideMask","rimMask","luma","vec3","vec4","q","outside","inside","r","h","dr","dy","vec2","v","u","s","t","lightingPreset","dir","direction","ambient","diffuse","ndl","shade","depthOfFieldPreset","focusDistance","aperture","maxBlur","depth","focus","coc","blur","alpha","revealPreset","progress","frequency","edge","vnoise","wgslFn","noise","threshold","visible","worldWarpPreset","intensity","radius","R","planetW","foldW","cA","sFold","sPlanet","foldV","planetV","warpedV","halfPiR","dFoldDz","dPlanetDz","foldC0","foldC1","foldC2","planetC0","planetC1","planetC2","i0","i1","i2","j0","j1","j2","r0","r1u","r1","r2gs","r2","qView","mat3","viewR","modelViewMatrix","viewRT","warpR","MAX_RELIGHTING_SHADOW_LIGHTS","contributionIntensity","contribution","value","contributionFill","liveRef","name","type","object","ref","reference","frameGroup","punctualUmbraWeight","light","punctual","decay","lightDistance","positionWorld","objectPosition","range","window","punctualFillTerm","fill","vis","isSpot","isPoint","lightPos","toLightVec","dist","toLight","normalWorld","mask","spot","shine","outer","penumbra","inner","cone","cos","chroma","directionalFillTerm","dirLight","normalizeRelightingLights","lights","list","weighted","cascadedShadow","nearRadius","midRadius","outerRadius","blendAt","shadowed","shadow","createRelightingShadowFactorMaterial","umbra","receiveUpMin","material","cameraPosition","contributions","receive","shadowTermAt","index","raw","shadowTerms","factor","factorCombined","umbra_i","term","factor_i","min","shadowIndices","illum","denom","attenuation","boost","lightColor","lightDir","directional","triangleMeshToGeometry","data","geometry","createRelightingProxy","albedo","group","worldMatrix","ownedGeometries","bakeWorld","addGeometry","owns","geo","owned","mesh","tile","mat"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"effects.js","sources":["../src/lib/relighting-pass.ts","../src/lib/effects.ts"],"sourcesContent":["/**\n * Host-safe lighting-map pass for {@link SplatMesh.setRelighting}.\n *\n * The relight example is a sidecar `render(relightScene, camera)` into an RT.\n * That only fills TSL `shadow()` maps when the renderer looks like a fresh\n * `createSplatRenderer` (`autoClear` true, no tone-map wrap). Host apps often\n * already own a `WebGPURenderer` with `autoClear: false` and an inline ACES /\n * sRGB `contextNode`. This helper isolates those for one pass, then restores\n * them, so the example works in a custom scene.\n *\n * Important: do **not** assign `contextNode = undefined`. WebGPURenderer reads\n * `contextNode.id` while building render objects; clearing it throws and the\n * lighting RT never fills.\n */\nimport * as THREE from 'three/webgpu';\nimport { context } from 'three/tsl';\n\nconst size = new THREE.Vector2();\nconst clearColor = new THREE.Color();\n\n/**\n * Passthrough context: keeps a stable `.id` for the render-object cache, but\n * does not apply host tone-mapping / sRGB encode to the linear factor map.\n */\nconst RELIGHT_PASS_CONTEXT = context({\n getOutput: (node: THREE.Node) => node,\n});\n\n/**\n * Minimal renderer surface {@link renderRelightingFactorMap} needs. A real\n * `THREE.WebGPURenderer` satisfies this; tests can pass a stub.\n */\nexport type RelightingFactorRenderer = {\n autoClear: boolean;\n shadowMap: { enabled: boolean; autoUpdate?: boolean };\n getDrawingBufferSize(target: THREE.Vector2): THREE.Vector2;\n getRenderTarget(): THREE.RenderTarget | null;\n getActiveCubeFace(): number;\n getActiveMipmapLevel(): number;\n getMRT(): THREE.MRTNode | null;\n setMRT(mrt: THREE.MRTNode | null): void;\n setRenderTarget(\n target: THREE.RenderTarget | null,\n activeCubeFace?: number,\n activeMipmapLevel?: number,\n ): void;\n getClearColor(target: THREE.Color): THREE.Color;\n getClearAlpha(): number;\n setClearColor(color: THREE.ColorRepresentation, alpha?: number): void;\n clear(): void;\n render(scene: THREE.Object3D, camera: THREE.Camera): void;\n contextNode: unknown;\n};\n\n/**\n * Renders `scene` from `camera` into `target` as a shadow-factor map:\n * resizes to the drawing buffer, clears **white + A0**, forces `autoClear`\n * and shadow maps on, and swaps in a passthrough `contextNode` so a host\n * ACES/sRGB wrap cannot turn the linear multiplier into speckle.\n *\n * Call each frame **before** `splats.update` / the main splat draw. Restore\n * is guaranteed even if `render` throws.\n */\nexport function renderRelightingFactorMap(\n renderer: RelightingFactorRenderer,\n scene: THREE.Scene,\n camera: THREE.Camera,\n target: THREE.RenderTarget,\n): void {\n renderer.getDrawingBufferSize(size);\n target.setSize(Math.max(1, size.x), Math.max(1, size.y));\n\n const previousTarget = renderer.getRenderTarget();\n const previousCubeFace = renderer.getActiveCubeFace();\n const previousMipmapLevel = renderer.getActiveMipmapLevel();\n const previousMrt = renderer.getMRT();\n const previousAlpha = renderer.getClearAlpha();\n const previousAutoClear = renderer.autoClear;\n const previousShadowEnabled = renderer.shadowMap.enabled;\n const previousShadowAutoUpdate = renderer.shadowMap.autoUpdate;\n const previousContext = renderer.contextNode;\n renderer.getClearColor(clearColor);\n\n try {\n renderer.contextNode = RELIGHT_PASS_CONTEXT;\n renderer.autoClear = true;\n renderer.shadowMap.enabled = true;\n if (previousShadowAutoUpdate === false) renderer.shadowMap.autoUpdate = true;\n renderer.setMRT(null);\n renderer.setRenderTarget(target);\n renderer.setClearColor(0xffffff, 0);\n renderer.clear();\n renderer.render(scene, camera);\n } finally {\n renderer.setRenderTarget(previousTarget, previousCubeFace, previousMipmapLevel);\n renderer.setMRT(previousMrt);\n renderer.setClearColor(clearColor, previousAlpha);\n renderer.autoClear = previousAutoClear;\n renderer.shadowMap.enabled = previousShadowEnabled;\n if (previousShadowAutoUpdate !== undefined) {\n renderer.shadowMap.autoUpdate = previousShadowAutoUpdate;\n }\n renderer.contextNode = previousContext;\n }\n}\n","/**\n * `@voluma/vlam/effects` - optional, tree-shakeable effects built on the M7\n * {@link SplatModifier} contract. Import only what you use; nothing here is\n * pulled into the core renderer bundle.\n *\n * - {@link sdfEffects}: a declarative sphere/box/cylinder shape list evaluated\n * in-shader from a bounded uniform array (a fixed unroll over `maxShapes`\n * slots, gated by the live count) - add/move/remove shapes is data-only\n * (no pipeline recompile), on WebGPU and the WebGL2 fallback alike (M7.4).\n * The kinds mirror the CPU selection volumes (`createSelectionVolume` on\n * `@voluma/vlam/selection`), so a host can highlight exactly what a\n * selection would take.\n * - {@link lightingPreset} / {@link revealPreset}: small reference presets,\n * starting points rather than a framework (M7.5).\n * - {@link createRelightingProxy}: builds a gray lit mesh group from collision\n * tiles (or raw geometries) for PlayCanvas-style {@link SplatMesh.setRelighting}.\n * - {@link createRelightingShadowFactorMaterial}: writes a shadow **multiplier**\n * (1 = lit, <1 = umbra) so coverage does not statically tint splats.\n * Accepts one light or {@link RelightingLightContribution}[] with intensity\n * weights. Optional `color` / `diffuse` / `direction` add a Lambert boost on\n * top of that identity (RGB may exceed 1 on a HalfFloat lighting RT).\n * - {@link renderRelightingFactorMap}: host-safe lighting pass. Isolates\n * `autoClear`, shadow maps, and swaps a passthrough `contextNode` so the\n * example works on an existing `WebGPURenderer`, not only `createSplatRenderer`.\n * - {@link worldWarpPreset}: camera-centered sphere wrap (planet / bowl).\n * - {@link depthOfFieldPreset}: stylized modifier-based depth-of-field (M13).\n * For physically-modeled camera DoF prefer the core\n * {@link SplatMesh.setDepthOfField} / `UnifiedSplatMesh.setDepthOfField`\n * path - this preset is the soft/approximate alternative built purely on\n * the modifier hook.\n *\n * Every preset here reads positions from `ctx.localCenter` - mesh-local space,\n * which inside a `MergedSplatMesh` is the splat's **placed** position. So a shape or\n * a reveal spans every source as one scene instead of travelling with a moved\n * one; see `docs/guide/effects-and-modifiers.md` for what that looks like, and\n * `ctx.sourceCenter` for the opposite behaviour.\n *\n * See `docs/guide/effects-and-modifiers.md`.\n */\nimport * as THREE from 'three/webgpu';\nimport {\n cameraPosition,\n float,\n int,\n mat3,\n min,\n mix,\n modelViewMatrix,\n normalWorld,\n objectPosition,\n positionWorld,\n shadow,\n smoothstep,\n uniform,\n uniformArray,\n vec2,\n vec3,\n vec4,\n wgslFn,\n reference,\n frameGroup,\n cos,\n} from 'three/tsl';\nimport type { SplatContext, SplatModifier } from './splat-modifier';\nimport { warn } from './logging';\nimport type { CollisionMeshTile } from './formats/lcc/collision-mesh';\nimport type { TriangleMeshData } from './formats/lcc/parse-mesh-ply';\n\ntype Node<T extends string> = THREE.Node<T>;\nfunction asNode<T extends string>(node: unknown): Node<T> {\n return node as Node<T>;\n}\n\n// --- SDF effects (M7.4) -----------------------------------------------------\n\n/**\n * A signed-distance primitive kind. `cylinder` is capped and runs along the\n * shape-local Y axis (rotate it with {@link SdfShape.rotation}).\n */\nexport type SdfShapeKind = 'sphere' | 'box' | 'cylinder';\n\n/**\n * What a shape does to the splats it covers:\n * - `tint` - blend the splat color toward the shape color.\n * - `desaturate` - blend the splat color toward its luminance.\n * - `hide` - fade the splat out (soft cutaway).\n * - `rim` - tint only a band around the shape's surface.\n */\nexport type SdfMode = 'tint' | 'desaturate' | 'hide' | 'rim';\n\n/** One SDF shape in a {@link sdfEffects} list. All spaces are mesh-local. */\nexport interface SdfShape {\n kind: SdfShapeKind;\n /** Shape center, mesh-local. Default `[0, 0, 0]`. */\n center?: readonly [number, number, number];\n /**\n * Sphere/cylinder radius. Required for `kind: 'sphere'` and\n * `kind: 'cylinder'`; must be positive - {@link SdfEffect.setShapes} throws\n * otherwise.\n */\n radius?: number;\n /**\n * Cylinder full height along the shape-local Y axis. Required (and only\n * used) for `kind: 'cylinder'`; must be positive - {@link SdfEffect.setShapes}\n * throws otherwise.\n */\n height?: number;\n /**\n * Box half-extents. Required (and only used) for `kind: 'box'`; every\n * component must be positive - {@link SdfEffect.setShapes} throws otherwise.\n */\n halfExtents?: readonly [number, number, number];\n /** Orientation quaternion `[x, y, z, w]` (boxes and cylinders). Default identity. */\n rotation?: readonly [number, number, number, number];\n /** Effect color (tint/rim). Default white. */\n color?: readonly [number, number, number];\n /** Soft-edge width in world units; 0 is a hard edge. Default `0`. */\n falloff?: number;\n /** Invert the test - affect the outside of the shape instead. */\n invert?: boolean;\n /** Effect strength in `[0, 1]`. Default `1`. */\n strength?: number;\n mode: SdfMode;\n}\n\n/** A live SDF effect: one modifier plus a data-only way to update its shapes. */\nexport interface SdfEffect {\n /** Attach to `mesh.modifiers`. Built once; updating shapes never recompiles. */\n readonly modifier: SplatModifier;\n /**\n * Replaces the shape list. Purely a uniform-buffer write, so animating\n * shape poses/colors/thresholds every frame costs no pipeline recompile.\n * Shapes beyond {@link maxShapes} are dropped with a one-time warning.\n *\n * Throws (before writing anything) if a sphere or cylinder lacks a positive\n * `radius`, a cylinder lacks a positive `height`, or a box lacks positive\n * `halfExtents` - a zero-size shape is always a caller bug, never a useful\n * no-op.\n */\n setShapes(shapes: readonly SdfShape[]): void;\n /** The compiled-in shape capacity (see {@link sdfEffects} `maxShapes`). */\n readonly maxShapes: number;\n /**\n * The packed uniform state behind the modifier - `maxShapes × SHAPE_STRIDE`\n * vec4 slots plus the live shape count.\n * @internal Exposed for unit tests of the packing; not public API.\n */\n readonly _uniforms: {\n readonly slots: readonly THREE.Vector4[];\n readonly count: { value: number };\n };\n}\n\n/** vec4 slots per shape in the uniform array (see the packing below). */\nconst SHAPE_STRIDE = 5;\nconst MODE_INDEX: Record<SdfMode, number> = { tint: 0, desaturate: 1, hide: 2, rim: 3 };\n\n// Shared defaults so a per-frame `setShapes` on sparse shape literals does not\n// allocate a fresh array per omitted field.\nconst DEFAULT_CENTER: readonly [number, number, number] = [0, 0, 0];\nconst DEFAULT_COLOR: readonly [number, number, number] = [1, 1, 1];\nconst DEFAULT_ROTATION: readonly [number, number, number, number] = [0, 0, 0, 1];\n\n/**\n * Builds a {@link SdfEffect} from a declarative shape list. Shapes live in a\n * bounded uniform array and are tested in-shader against each splat's local\n * center as a **fixed unroll over all `maxShapes` slots**, each slot gated by\n * `slot < count` so unused slots contribute nothing. (An in-shader loop is\n * not viable here - see the TSL varying constraint documented inside the\n * modifier.) Adding, moving or removing a shape - or changing the count - is\n * still a pure uniform write with no recompile. Works on WebGPU and the\n * WebGL2 fallback.\n *\n * `maxShapes` is the compiled capacity (default 32). Because every slot is\n * unrolled into the shader, raising it grows the shader's size and compile\n * time (and the uniform array; very large values can exceed WebGL2's uniform\n * limits), so keep it to what a scene needs.\n *\n * Shape positions are mesh-local. In a `MergedSplatMesh` that means scene\n * coordinates - a shape overlapping two sources covers both as one continuous\n * shape - so place them with `computeSplatBounds()`, which reports the sources\n * at their current placement.\n */\nexport function sdfEffects(\n initialShapes: readonly SdfShape[] = [],\n options: { maxShapes?: number } = {},\n): SdfEffect {\n const maxShapes = Math.max(1, Math.floor(options.maxShapes ?? 32));\n\n // One flat uniform array of vec4; SHAPE_STRIDE slots per shape. Mutating\n // these Vector4s (and `count`) re-uploads as plain uniforms - never a\n // recompile. `count` bounds the in-shader loop.\n const slots: THREE.Vector4[] = [];\n for (let i = 0; i < maxShapes * SHAPE_STRIDE; i++) slots.push(new THREE.Vector4());\n const shapesUniform = uniformArray(slots, 'vec4');\n const count = uniform(0, 'int');\n\n let warnedOverflow = false;\n const setShapes = (shapes: readonly SdfShape[]): void => {\n const n = Math.min(shapes.length, maxShapes);\n if (shapes.length > maxShapes && !warnedOverflow) {\n warnedOverflow = true;\n warn(\n `sdfEffects: ${shapes.length} shapes exceeds maxShapes=${maxShapes}; ` +\n `extra shapes are ignored. Raise maxShapes if you need more.`,\n );\n }\n // Validate before touching any slot, so a bad list never leaves the\n // uniform array half-updated.\n for (let i = 0; i < n; i++) {\n const s = shapes[i] as SdfShape;\n if (s.kind === 'box') {\n const he = s.halfExtents;\n if (!he || !(he[0] > 0) || !(he[1] > 0) || !(he[2] > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a box and requires halfExtents ` +\n `with three positive components (got ${he ? `[${he.join(', ')}]` : 'undefined'}).`,\n );\n }\n } else if (!(typeof s.radius === 'number' && s.radius > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a ${s.kind} and requires a positive ` +\n `radius (got ${String(s.radius)}).`,\n );\n } else if (s.kind === 'cylinder' && !(typeof s.height === 'number' && s.height > 0)) {\n throw new Error(\n `sdfEffects.setShapes: shape ${i} is a cylinder and requires a positive ` +\n `height (got ${String(s.height)}).`,\n );\n }\n }\n for (let i = 0; i < n; i++) {\n const s = shapes[i] as SdfShape;\n const b = i * SHAPE_STRIDE;\n const c = s.center ?? DEFAULT_CENTER;\n const col = s.color ?? DEFAULT_COLOR;\n const rot = s.rotation ?? DEFAULT_ROTATION;\n const kindIndex = s.kind === 'box' ? 1 : s.kind === 'cylinder' ? 2 : 0;\n (slots[b + 0] as THREE.Vector4).set(c[0], c[1], c[2], kindIndex);\n if (s.kind === 'box') {\n const he = s.halfExtents as readonly [number, number, number];\n (slots[b + 1] as THREE.Vector4).set(he[0], he[1], he[2], s.falloff ?? 0);\n } else if (s.kind === 'cylinder') {\n (slots[b + 1] as THREE.Vector4).set(\n s.radius as number,\n (s.height as number) / 2,\n 0,\n s.falloff ?? 0,\n );\n } else {\n (slots[b + 1] as THREE.Vector4).set(s.radius as number, 0, 0, s.falloff ?? 0);\n }\n (slots[b + 2] as THREE.Vector4).set(col[0], col[1], col[2], MODE_INDEX[s.mode]);\n (slots[b + 3] as THREE.Vector4).set(rot[0], rot[1], rot[2], rot[3]);\n (slots[b + 4] as THREE.Vector4).set(s.invert ? 1 : 0, s.strength ?? 1, 0, 0);\n }\n count.value = n;\n };\n setShapes(initialShapes);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // Unrolled over the fixed `maxShapes` slots as *pure expressions* (no\n // control flow): a splat's color is a chain of `select`/`mix` folds, one\n // per slot, each gated by `slot < count` so unused slots are transparent.\n // Control flow inside a modifier color is not viable - three.js evaluates\n // a color that reaches the fragment stage through a varying, and a varying\n // does not emit `Loop`/`If` statements - so the shape count drives a\n // uniform-gated unroll instead. Result: changing shapes or their count is\n // still a pure uniform write (no recompile); the cost is a shader whose\n // size grows with `maxShapes`.\n const p = ctx.localCenter;\n let rgb: Node<'vec3'> = asNode<'vec3'>(ctx.color.rgb);\n let alphaMul: Node<'float'> = float(1);\n\n for (let k = 0; k < maxShapes; k++) {\n const b = k * SHAPE_STRIDE;\n const el = (j: number): Node<'vec4'> => asNode<'vec4'>(shapesUniform.element(b + j));\n const s0 = el(0);\n const s1 = el(1);\n const s2 = el(2);\n const s3 = el(3);\n const s4 = el(4);\n\n const center = s0.xyz;\n const kind = s0.w; // 0 sphere · 1 box · 2 cylinder (stored as a float index)\n const size = s1.xyz;\n const falloff = s1.w.max(1e-4);\n const shapeColor = s2.xyz;\n const mode = s2.w;\n const invert = s4.x; // 0 · 1 flag\n const strength = s4.y;\n // Slots past the live count contribute nothing.\n const gate = int(k).lessThan(count).select(float(1), float(0));\n\n // Sample point in shape-local space: R⁻¹·(p − center). The sphere/box\n // and invert choices blend with `mix` on their 0/1 flags rather than a\n // `select`: three.js miscompiles a ConditionalNode whose branches carry\n // large sub-expressions (the box SDF) here, silently zeroing the result.\n const rel = p.sub(center);\n const pl = rotateByQuatConj(s3, rel);\n const dSphere = pl.length().sub(size.x);\n const dBox = sdBox(pl, size);\n const dCylinder = sdCappedCylinder(pl, size.x, size.y);\n // Kind index → 0/1 masks, folded with `mix` like the mode masks below.\n const boxMask = kind.greaterThan(0.5).and(kind.lessThan(1.5)).select(float(1), float(0));\n const cylinderMask = kind.greaterThan(1.5).select(float(1), float(0));\n const dRaw = mix(mix(dSphere, dBox, boxMask), dCylinder, cylinderMask);\n const d = mix(dRaw, dRaw.negate(), invert);\n\n // Inside coverage (1 within, fading out over `falloff`); rim coverage\n // peaks on the surface. Both scaled by strength and the live-slot gate.\n const insideCov = float(1)\n .sub(smoothstep(0, falloff, d))\n .mul(strength)\n .mul(gate);\n const rimCov = float(1)\n .sub(smoothstep(0, falloff, d.abs()))\n .mul(strength)\n .mul(gate);\n\n // Per-mode scalar masks (1 for the active mode, else 0). Applied by\n // folding the coverage with `mix`/multiply - NOT by a `select` that\n // reassigns `rgb`: three.js miscompiles a conditional whose branches\n // reuse the running value across an unrolled reassignment chain, so the\n // accumulation must stay pure arithmetic.\n const tintMask = mode.lessThan(0.5).select(float(1), float(0));\n const desatMask = mode.greaterThan(0.5).and(mode.lessThan(1.5)).select(float(1), float(0));\n const hideMask = mode.greaterThan(1.5).and(mode.lessThan(2.5)).select(float(1), float(0));\n const rimMask = mode.greaterThan(2.5).select(float(1), float(0));\n const luma = asNode<'float'>(rgb.dot(vec3(0.2126, 0.7152, 0.0722)));\n\n rgb = asNode<'vec3'>(mix(rgb, shapeColor, insideCov.mul(tintMask)));\n rgb = asNode<'vec3'>(mix(rgb, vec3(luma), insideCov.mul(desatMask)));\n rgb = asNode<'vec3'>(mix(rgb, shapeColor, rimCov.mul(rimMask)));\n alphaMul = asNode<'float'>(alphaMul.mul(float(1).sub(insideCov.mul(hideMask))));\n }\n\n return { color: vec4(rgb, ctx.color.a.mul(alphaMul)) };\n };\n\n return { modifier, setShapes, maxShapes, _uniforms: { slots, count } };\n}\n\n/** SDF of an axis-aligned box (shape-local) with half-extents `b`. */\nfunction sdBox(p: Node<'vec3'>, b: Node<'vec3'>): Node<'float'> {\n const q = p.abs().sub(b);\n const outside = q.max(vec3(0, 0, 0)).length();\n const inside = q.x.max(q.y.max(q.z)).min(0);\n return asNode<'float'>(outside.add(inside));\n}\n\n/** SDF of a Y-axis capped cylinder (shape-local): radius `r`, half-height `h`. */\nfunction sdCappedCylinder(p: Node<'vec3'>, r: Node<'float'>, h: Node<'float'>): Node<'float'> {\n const dr = p.xz.length().sub(r);\n const dy = p.y.abs().sub(h);\n const outside = vec2(dr.max(0), dy.max(0)).length();\n const inside = dr.max(dy).min(0);\n return asNode<'float'>(outside.add(inside));\n}\n\n/** Rotates `v` by the conjugate of quaternion `q` (xyzw): R⁻¹·v. */\nfunction rotateByQuatConj(q: Node<'vec4'>, v: Node<'vec3'>): Node<'vec3'> {\n const u = q.xyz;\n const s = q.w;\n const t = u.cross(v).mul(2);\n return asNode<'vec3'>(v.sub(t.mul(s)).add(u.cross(t)));\n}\n\n// --- Reference presets (M7.5) ----------------------------------------------\n\n/** A preset that exposes a live light direction uniform. */\nexport interface LightingPreset {\n readonly modifier: SplatModifier;\n /**\n * Mesh-local light direction; mutate `.value` to animate (no recompile).\n * Inside a `MergedSplatMesh` that frame is the scene's, so one direction lights\n * every source consistently however they are posed.\n */\n readonly direction: { value: THREE.Vector3 };\n}\n\n/**\n * Simple per-splat Lambertian shading using the splat's approximate normal\n * (`ctx.normal`, the least-variance covariance axis). A starting point for\n * relit product shots - not a lighting rig. Works on both backends.\n *\n * In a `MergedSplatMesh` the normal comes from the *placed* covariance, so a source\n * that rotates relights as it turns.\n */\nexport function lightingPreset(\n options: {\n direction?: readonly [number, number, number];\n ambient?: number;\n diffuse?: number;\n } = {},\n): LightingPreset {\n const dir = new THREE.Vector3(...(options.direction ?? [0.3, 1, 0.6])).normalize();\n const direction = uniform(dir);\n const ambient = float(options.ambient ?? 0.35);\n const diffuse = float(options.diffuse ?? 0.75);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n const ndl = ctx.normal.dot(direction.normalize()).max(0);\n const shade = ambient.add(ndl.mul(diffuse));\n return { color: vec4(ctx.color.rgb.mul(shade), ctx.color.a) };\n };\n return { modifier, direction: direction };\n}\n\n/** A live depth-of-field preset: focus plane and aperture are mutable uniforms. */\nexport interface DepthOfFieldPreset {\n readonly modifier: SplatModifier;\n /** World-unit distance to the focal plane; splats there stay sharp. Mutate\n * `.value` to rack focus (no recompile). Must stay positive. */\n readonly focusDistance: { value: number };\n /** Blur strength (aperture-like); 0 is no blur. Mutate `.value`. */\n readonly aperture: { value: number };\n}\n\n/**\n * Depth-of-field on the {@link SplatModifier} hook - **stylized / soft** path\n * (M13). Prefer {@link SplatMesh.setDepthOfField} / the core projected-2D\n * aperture model for camera DoF (isotropic screen-space CoC with √det opacity).\n *\n * A splat's circle of confusion grows with how far its view-space depth\n * sits from `focusDistance`; the modifier blurs it by enlarging it\n * (`scale = 1 + coc`) and, because the material scales the projected covariance\n * by `scale²`, divides opacity by `scale²` so the widened splat conserves its\n * integrated mass instead of over-brightening. Focus and aperture are live\n * uniforms, so racking focus every frame costs no recompile. Pure TSL - works\n * on WebGPU and the WebGL2 fallback.\n *\n * This is an *approximate* DoF: `scale` is a uniform 3D-covariance multiplier,\n * so the blur enlarges the splat's own (possibly anisotropic) footprint rather\n * than adding a truly isotropic screen-space disc. The physically exact aperture\n * model adds `coc²·I` to the *projected 2D* covariance with the same √(det)\n * opacity conservation the antialias filter uses - that path is\n * `SplatMesh.setDepthOfField` / `UnifiedSplatMesh.setDepthOfField`\n * (see `docs/guide/effects-and-modifiers.md`, M13).\n *\n * Opacity conservation is exact only with `antialias` on: without it, the\n * fixed 0.3-px dilation dominates sub-pixel splats, so strong blur *dims*\n * distant splats (their footprint barely grows while alpha still falls by\n * 1/scale²) rather than blurring them.\n */\nexport function depthOfFieldPreset(\n options: { focusDistance?: number; aperture?: number; maxBlur?: number } = {},\n): DepthOfFieldPreset {\n const focusDistance = uniform(Math.max(1e-4, options.focusDistance ?? 10));\n const aperture = uniform(Math.max(0, options.aperture ?? 0.5));\n // Cap the enlargement so a splat far behind the focus plane cannot balloon\n // to cover the screen (and its 1/scale² opacity cannot underflow to nothing).\n const maxBlur = float(Math.max(0, options.maxBlur ?? 6));\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // View-space depth along the optical axis (camera looks down −z).\n const depth = ctx.viewCenter.z.negate().max(1e-4);\n // Clamped in-shader too: the constructor clamp cannot protect against a\n // later `focusDistance.value = 0`, which would divide by zero below and\n // NaN every splat's scale.\n const focus = focusDistance.max(1e-4);\n // Circle of confusion, normalized by the focus distance so it is scale-\n // invariant: 0 at the focal plane, growing with |depth − focus|.\n const coc = depth.sub(focus).abs().div(focus).mul(aperture).min(maxBlur);\n const blur = float(1).add(coc);\n // Conserve integrated opacity: the material scales area by blur², so peak\n // opacity must fall by 1/blur² or the blurred splat over-brightens. The\n // blur multiplies the running scale (and compensates only for its own\n // factor), so a preceding modifier's scale survives composition.\n const alpha = asNode<'float'>(ctx.color.a.div(blur.mul(blur)));\n return { scale: asNode<'float'>(ctx.scale.mul(blur)), color: vec4(ctx.color.rgb, alpha) };\n };\n return { modifier, focusDistance, aperture };\n}\n\n/** A preset that exposes a reveal `progress` uniform in `[0, 1]`. */\nexport interface RevealPreset {\n readonly modifier: SplatModifier;\n /** Reveal progress; 0 hides everything, 1 shows everything. */\n readonly progress: { value: number };\n}\n\n/**\n * Time-driven dissolve/reveal. A value-noise field over the splat's local\n * position is thresholded against `progress`: splats whose noise is below the\n * threshold are shown, with a soft alpha edge. The noise is a `wgslFn` chunk -\n * the M7 escape hatch - so this preset is **WebGPU-only** (see the fallback\n * notes); force WebGL2 and it will not compile.\n *\n * The field is anchored to the mesh, so in a `MergedSplatMesh` a moved source\n * reveals in step with whatever it now sits beside (and the pattern slides\n * across it as it travels) rather than carrying its own schedule around.\n */\nexport function revealPreset(options: { frequency?: number; edge?: number } = {}): RevealPreset {\n const progress = uniform(0);\n const frequency = options.frequency ?? 3;\n // The edge doubles as the threshold-range margin below, so it must stay in\n // [0, 0.5] for the remap to be well-formed.\n const edge = Math.min(Math.max(options.edge ?? 0.08, 0), 0.5);\n\n // Value noise as a single self-contained WGSL function: hash the 8 corners\n // of the lattice cell, then trilinearly interpolate with smoothstep weights\n // (g²(3 − 2g)). Every corner hash is a `fract` in [0, 1) and `mix` is a\n // convex combination, so the result is provably in [0, 1) - the reveal\n // threshold math below depends on that bound. The hashes are `let`-bound\n // locals so the chunk stays one function - three.js `wgslFn` binds call\n // inputs to the *first* function it finds, so a multi-function chunk would\n // need the entry first anyway; keeping it to one avoids that footgun.\n const vnoise = wgslFn(/* wgsl */ `\n fn vnoise( x: vec3<f32> ) -> f32 {\n let i = floor( x );\n let g = fract( x );\n let u = g * g * ( 3.0 - 2.0 * g );\n let k = vec3<f32>( 12.9898, 78.233, 37.719 );\n let s = 43758.5453;\n let h000 = fract( sin( dot( i + vec3<f32>( 0.0, 0.0, 0.0 ), k ) ) * s );\n let h100 = fract( sin( dot( i + vec3<f32>( 1.0, 0.0, 0.0 ), k ) ) * s );\n let h010 = fract( sin( dot( i + vec3<f32>( 0.0, 1.0, 0.0 ), k ) ) * s );\n let h110 = fract( sin( dot( i + vec3<f32>( 1.0, 1.0, 0.0 ), k ) ) * s );\n let h001 = fract( sin( dot( i + vec3<f32>( 0.0, 0.0, 1.0 ), k ) ) * s );\n let h101 = fract( sin( dot( i + vec3<f32>( 1.0, 0.0, 1.0 ), k ) ) * s );\n let h011 = fract( sin( dot( i + vec3<f32>( 0.0, 1.0, 1.0 ), k ) ) * s );\n let h111 = fract( sin( dot( i + vec3<f32>( 1.0, 1.0, 1.0 ), k ) ) * s );\n let x00 = mix( h000, h100, u.x );\n let x10 = mix( h010, h110, u.x );\n let x01 = mix( h001, h101, u.x );\n let x11 = mix( h011, h111, u.x );\n let y0 = mix( x00, x10, u.y );\n let y1 = mix( x01, x11, u.y );\n return mix( y0, y1, u.z );\n }\n `);\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n // Each splat gets a per-position threshold from the noise; it appears as\n // `progress` sweeps past that threshold, with a soft `edge`-wide band.\n // The noise is in [0, 1); remapping it to [edge, 1 − edge] keeps the whole\n // smoothstep band inside [0, 1], so progress 0 hides every splat fully and\n // progress 1 shows every splat fully - the documented contract.\n const noise = asNode<'float'>(vnoise({ x: ctx.localCenter.mul(frequency) }));\n const threshold = asNode<'float'>(noise.mul(1 - 2 * edge).add(edge));\n const alpha = smoothstep(threshold.sub(edge), threshold.add(edge), progress);\n const visible = progress.greaterThan(threshold.sub(edge));\n return { color: vec4(ctx.color.rgb, ctx.color.a.mul(alpha)), visible: asNode<'bool'>(visible) };\n };\n return { modifier, progress: progress };\n}\n\n/** A live world-warp preset: signed intensity and radius are mutable uniforms. */\nexport interface WorldWarpPreset {\n readonly modifier: SplatModifier;\n /**\n * Signed warp amount in `[-1, 1]`. Positive wraps the street *down* so you\n * walk on top of the sphere (tiny planet); negative wraps it *up* so you\n * stand in the bowl (Inception fold). `0` is identity. Mutate `.value` to\n * animate (no recompile).\n */\n readonly intensity: { value: number };\n /**\n * Distance at which the wrap becomes strong. Nearby splats (`depth ≪\n * radius`) stay put so walk/fly controls still read as moving through the\n * unwarped near field. Must stay positive. Mutate `.value`.\n */\n readonly radius: { value: number };\n}\n\n/**\n * Camera-centered splat warp on the {@link SplatModifier} hook: one signed\n * `intensity` uniform, two opposite hinges of the same view-space wrap. The\n * camera is not moved - only splat centers (and their rigid orientation)\n * change, so WASD / orbit stay as they are.\n *\n * Both sides hinge view-space `(y, z)` around camera +X by\n * `(π/2) · atan(depth / radius)`:\n *\n * - `intensity > 0` - planet: the far street wraps *down* under your feet so\n * you walk on top of the sphere.\n * - `intensity < 0` - fold: the far street wraps *up* over your head so you\n * stand in the bowl.\n * - `intensity = 0` is identity (`offset = 0`, rotation unchanged).\n *\n * The shader mixes each full-strength hinge toward the original position by\n * `|intensity|`. Orientation follows the orthonormalized Jacobian so\n * ellipsoids wrap with the surface rather than sliding. Pure TSL - WebGPU\n * and the WebGL2 fallback.\n *\n * Displaced splats keep their **pre-displacement** depth-sort order (the\n * modifier-stack contract). Extreme warps can composite in the wrong order;\n * this preset does not re-sort on warped centers.\n */\nexport function worldWarpPreset(\n options: { intensity?: number; radius?: number } = {},\n): WorldWarpPreset {\n const intensity = uniform(Math.min(1, Math.max(-1, options.intensity ?? 0)));\n const radius = uniform(Math.max(1e-4, options.radius ?? 1));\n\n const modifier: SplatModifier = (ctx: SplatContext) => {\n const k = asNode<'float'>(intensity.clamp(-1, 1));\n const R = asNode<'float'>(radius.max(1e-4));\n const v = ctx.viewCenter;\n const planetW = k.max(0);\n const foldW = k.negate().max(0);\n\n const depth = v.z.negate().max(0);\n const alpha = depth\n .div(R)\n .atan()\n .mul(float(Math.PI * 0.5));\n const cA = alpha.cos();\n const sFold = alpha.sin();\n const sPlanet = sFold.negate();\n const foldV = vec3(v.x, v.y.mul(cA).sub(v.z.mul(sFold)), v.y.mul(sFold).add(v.z.mul(cA)));\n const planetV = vec3(v.x, v.y.mul(cA).sub(v.z.mul(sPlanet)), v.y.mul(sPlanet).add(v.z.mul(cA)));\n\n const warpedV = mix(mix(v, foldV, foldW), planetV, planetW);\n\n const halfPiR = float(Math.PI * 0.5).mul(R);\n const dFoldDz = v.z\n .lessThan(0)\n .select(halfPiR.negate().div(depth.mul(depth).add(R.mul(R))), float(0));\n const dPlanetDz = dFoldDz.negate();\n const foldC0 = vec3(1, 0, 0);\n const foldC1 = vec3(0, cA, sFold);\n const foldC2 = vec3(0, sFold.negate().sub(foldV.z.mul(dFoldDz)), cA.add(foldV.y.mul(dFoldDz)));\n const planetC0 = vec3(1, 0, 0);\n const planetC1 = vec3(0, cA, sPlanet);\n const planetC2 = vec3(\n 0,\n sPlanet.negate().sub(planetV.z.mul(dPlanetDz)),\n cA.add(planetV.y.mul(dPlanetDz)),\n );\n\n const i0 = vec3(1, 0, 0);\n const i1 = vec3(0, 1, 0);\n const i2 = vec3(0, 0, 1);\n const j0 = mix(mix(i0, foldC0, foldW), planetC0, planetW);\n const j1 = mix(mix(i1, foldC1, foldW), planetC1, planetW);\n const j2 = mix(mix(i2, foldC2, foldW), planetC2, planetW);\n\n const r0 = j0.div(j0.length().max(1e-6));\n const r1u = j1.sub(r0.mul(r0.dot(j1)));\n const r1 = r1u.div(r1u.length().max(1e-6));\n const r2gs = j2.sub(r0.mul(r0.dot(j2))).sub(r1.mul(r1.dot(j2)));\n const r2 = r2gs.div(r2gs.length().max(1e-6));\n const qView = asNode<'mat3'>(mat3(r0, r1, r2));\n const viewR = asNode<'mat3'>(modelViewMatrix.toMat3());\n const viewRT = viewR.transpose();\n const warpR = asNode<'mat3'>(viewRT.mul(qView).mul(viewR));\n\n return {\n offset: asNode<'vec3'>(ctx.offset.add(viewRT.mul(warpedV.sub(v)))),\n rotation: asNode<'mat3'>(warpR.mul(ctx.rotation)),\n };\n };\n return { modifier, intensity, radius };\n}\n\n// --- Proxy-mesh relighting helper -------------------------------------------\n\nexport { renderRelightingFactorMap, type RelightingFactorRenderer } from './relighting-pass';\n\n/** Handle returned by {@link createRelightingProxy}. */\nexport interface RelightingProxy {\n /** World-space group of lit proxy meshes (hide from the main splat scene). */\n readonly group: THREE.Group;\n /**\n * Configures a material for the relight coverage pass: gray albedo and\n * opaque alpha so uncovered RT pixels stay at clear-alpha 0.\n */\n configureMaterial(material: THREE.Material): void;\n /** Disposes geometries and the default material owned by this helper. */\n dispose(): void;\n}\n\n/** One independent shadow-casting light for {@link createRelightingShadowFactorMaterial}. */\nexport type RelightingLightContribution = {\n light: THREE.Light;\n /** Relative umbra weight. Default `1`. Values `<= 0` are skipped unless `fill` is set. */\n intensity?: number;\n /**\n * Additive Lambert fill on top of the umbra factor (RGB may exceed 1).\n * Gated by `shadow()`, facing, and for spot/point lights the Frostbite\n * `distance` window (and the spot cone). `0` / omitted = umbra only.\n */\n fill?: number;\n};\n\n/** Options for {@link createRelightingShadowFactorMaterial}. */\nexport type RelightingShadowFactorOptions = {\n umbra?: number;\n receiveUpMin?: number;\n color?: THREE.Color;\n diffuse?: number;\n direction?: THREE.Vector3;\n /**\n * How independent contributions combine into one multiplier.\n * - `average` (default): intensity-weighted mean of shadows (fill lights).\n * - `min`: union of umbras — each caster keeps its own silhouette. Contribution\n * `intensity` scales umbra depth: `0` → none, `1` → default `umbra`,\n * `>1` → darker than default (`umbra_i = clamp(1 - I*(1-umbra), 0, 1)`).\n */\n combine?: 'average' | 'min';\n midLight?: THREE.Light;\n midRadius?: number;\n outerLight?: THREE.Light;\n outerRadius?: number;\n farLight?: THREE.Light;\n nearRadius?: number;\n};\n\n/**\n * Cap on independent shadow lights (cascades of the first directional do not\n * count). Extra contributions are ignored. The compiled graph unrolls only\n * the live contribution count, not a padded 32-wide loop.\n */\nexport const MAX_RELIGHTING_SHADOW_LIGHTS = 32;\n\nconst contributionIntensity = (contribution: RelightingLightContribution): number => {\n const value = contribution.intensity;\n if (!Number.isFinite(value)) return 1;\n return Math.max(0, value as number);\n};\n\nconst contributionFill = (contribution: RelightingLightContribution): number => {\n const value = contribution.fill;\n if (!Number.isFinite(value)) return 0;\n return Math.max(0, value as number);\n};\n\n/** Light pose/color must track every frame. `uniform(vec)` keeps object identity, so in-place `copy()` does not re-upload. */\nconst liveRef = <T extends string>(name: string, type: string, object: object): Node<T> => {\n const ref = reference(name, type, object);\n // three.js implements ReferenceNode.setGroup(), but its public declaration omits it.\n (ref as unknown as { setGroup(group: typeof frameGroup): unknown }).setGroup(frameGroup);\n return asNode<T>(ref);\n};\n\n/**\n * Fade punctual (spot / point) umbras to lit as fragments approach `light.distance`.\n * Directional lights return 1. Uses the Frostbite cutoff window so weight is 0 at\n * cutoff (no hard shadow-map cliff). `light.decay` steepens the falloff (>1).\n */\nconst punctualUmbraWeight = (light: THREE.Light): Node<'float'> => {\n const punctual = light as THREE.SpotLight & THREE.PointLight;\n const isPunctual = punctual.isSpotLight === true || punctual.isPointLight === true;\n if (!isPunctual) return float(1);\n const cutoff = Number(punctual.distance);\n if (!(cutoff > 0)) return float(1);\n const decay = Math.max(Number(punctual.decay) || 1, 0.01);\n const lightDistance = asNode<'float'>(positionWorld.distance(objectPosition(punctual)));\n const range = liveRef<'float'>('distance', 'float', punctual).max(1e-4);\n // pow2(saturate(1 - pow4(d/cutoff))) → 1 near light, 0 at cutoff\n const window = asNode<'float'>(lightDistance.div(range).pow4().oneMinus().clamp().pow2());\n return asNode<'float'>(window.pow(float(decay)));\n};\n\n/**\n * Ranged Lambert toward a punctual light, occluded by its shadow map.\n * Spot cone uses `light.angle` / `penumbra` and `light.target`.\n * Does **not** use `receiveUpMin`: that slope gate keeps umbra off noisy\n * vertical foliage, but it also erased fill on tree stems, so splats only\n * lit after the host fell back to un-occluded Lambert.\n */\nconst punctualFillTerm = (light: THREE.Light, fill: number, vis: Node<'float'>): Node<'vec3'> => {\n const punctual = light as THREE.SpotLight & THREE.PointLight;\n const isSpot = punctual.isSpotLight === true;\n const isPoint = punctual.isPointLight === true;\n if (!isSpot && !isPoint) return vec3(0, 0, 0);\n\n const lightPos = asNode<'vec3'>(objectPosition(punctual));\n const toLightVec = asNode<'vec3'>(lightPos.sub(positionWorld));\n const dist = asNode<'float'>(toLightVec.length().max(1e-4));\n const toLight = asNode<'vec3'>(toLightVec.div(dist));\n const ndl = asNode<'float'>(normalWorld.dot(toLight).max(0));\n const window = punctualUmbraWeight(light);\n let mask = asNode<'float'>(window.mul(ndl).mul(vis));\n\n if (isSpot) {\n const spot = light as THREE.SpotLight;\n const shine = asNode<'vec3'>(objectPosition(spot.target).sub(lightPos).normalize());\n const outer = liveRef<'float'>('angle', 'float', spot).max(1e-3);\n const penumbra = liveRef<'float'>('penumbra', 'float', spot).clamp(0, 0.95);\n const inner = asNode<'float'>(outer.mul(float(1).sub(penumbra)).max(1e-3));\n const cone = asNode<'float'>(smoothstep(cos(outer), cos(inner), toLight.negate().dot(shine)));\n mask = asNode<'float'>(mask.mul(cone));\n }\n\n const chroma = liveRef<'vec3'>('color', 'color', light);\n return asNode<'vec3'>(chroma.mul(float(fill)).mul(mask));\n};\n\nconst directionalFillTerm = (\n light: THREE.Light,\n fill: number,\n vis: Node<'float'>,\n): Node<'vec3'> => {\n const dirLight = light as THREE.DirectionalLight;\n if (dirLight.isDirectionalLight !== true) return vec3(0, 0, 0);\n const shine = asNode<'vec3'>(\n objectPosition(dirLight.target).sub(objectPosition(dirLight)).normalize(),\n );\n const ndl = asNode<'float'>(normalWorld.dot(shine.negate()).max(0));\n const chroma = liveRef<'vec3'>('color', 'color', light);\n return asNode<'vec3'>(chroma.mul(float(fill)).mul(ndl).mul(vis));\n};\n\nconst normalizeRelightingLights = (\n lights: THREE.Light | RelightingLightContribution[],\n): RelightingLightContribution[] => {\n const list = Array.isArray(lights) ? lights : [{ light: lights, intensity: 1 }];\n const weighted: RelightingLightContribution[] = [];\n for (const contribution of list) {\n if (weighted.length >= MAX_RELIGHTING_SHADOW_LIGHTS) break;\n if (contributionIntensity(contribution) <= 0 && contributionFill(contribution) <= 0) continue;\n weighted.push(contribution);\n }\n return weighted;\n};\n\nconst cascadedShadow = (\n light: THREE.Light,\n options: RelightingShadowFactorOptions,\n dist: Node<'float'>,\n): Node<'float'> => {\n const nearRadius = Number.isFinite(options.nearRadius)\n ? Math.max(1, options.nearRadius as number)\n : options.midLight\n ? 20\n : 100;\n const midRadius = Number.isFinite(options.midRadius)\n ? Math.max(nearRadius, options.midRadius as number)\n : 50;\n const outerRadius = Number.isFinite(options.outerRadius)\n ? Math.max(midRadius, options.outerRadius as number)\n : 160;\n const blendAt = (inner: Node<'float'>, outer: Node<'float'>, radius: number): Node<'float'> =>\n asNode<'float'>(mix(inner, outer, smoothstep(float(radius * 0.7), float(radius * 0.95), dist)));\n let shadowed = asNode<'float'>(shadow(light));\n if (options.midLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.midLight)), nearRadius);\n if (options.outerLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.outerLight)), midRadius);\n if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), outerRadius);\n }\n } else if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), midRadius);\n }\n } else if (options.farLight) {\n shadowed = blendAt(shadowed, asNode<'float'>(shadow(options.farLight)), nearRadius);\n }\n return shadowed;\n};\n\n/**\n * Node material for a **shadow-factor** lighting RT used with\n * {@link SplatMesh.setRelighting}.\n *\n * Fragment output is `vec4(vec3(mix(umbra, 1, shadow)) + boost, 1)`:\n * - covered + lit → RGB ≈ 1 (identity modulate when brightness/background are 1)\n * - covered + umbra → RGB ≈ `umbra` (soft darken; never pure black)\n * - uncovered stays clear-alpha 0 from the RT clear\n * - with `diffuse` > 0, facing surfaces add `color * NdotL * diffuse`\n * (RGB may exceed 1; use a HalfFloat lighting RT)\n *\n * Pass one {@link THREE.Light} (demo / single-sun) or an array of\n * {@link RelightingLightContribution} so independent lights share one\n * multiplier. Default combine is intensity-weighted average\n * (`illum = sum(I_i * shadow_i) / sum(I_i)`); pass `combine: 'min'` for a\n * union of umbras so each caster keeps its silhouette. Spot / point lights\n * with `distance > 0` fade their umbra to lit via a Frostbite cutoff window\n * (steepened by `decay`) so the shadow map far plane is not a hard cliff.\n * Contribution `fill` adds occluded Lambert (range + cone for punctual lights)\n * on top of that identity so accent lights can light splats without wrapping\n * through collision. Cascades (`midLight` / `outerLight` / `farLight`) still\n * attach to the **first** directional only. At most\n * {@link MAX_RELIGHTING_SHADOW_LIGHTS} independent lights are used; extras\n * are ignored. Graph size follows the live contribution count (not a padded\n * loop of that width).\n *\n * By default, only **upward** proxy faces receive (`receiveUpMin`). Vertical\n * and noisy foliage triangles still **cast**, but they do not self-shadow —\n * that PCF acne reads as per-frame sparkle in trees. Pass `receiveUpMin: 0`\n * to receive on every face. Contribution `fill` does **not** use that slope\n * gate (stems would otherwise stay unlit in the factor map).\n *\n * Clear the lighting RT to **RGB 1, A 0** (not black). Softness / bilinear\n * samples at coverage edges otherwise pull in black and draw a dark outline\n * of every collision triangle.\n *\n * Prefer this over a lit MeshStandard / ShadowMaterial pass when the host wants\n * cast umbras without a static dark stamp on the whole collision footprint.\n * Assign to proxy meshes with `castShadow` and `receiveShadow` both true; the\n * umbra shape follows those triangles (splat foliage cannot cast).\n *\n * @param lights - Shadow-casting light, or intensity-weighted contributions.\n * The first entry is the innermost cascade when `midLight` / `outerLight` /\n * `farLight` are set.\n * @param options.umbra - Multiplier in full shadow, in `[0, 1]`. Default `0.45`.\n * @param options.receiveUpMin - World-up `normal.y` below which the face does\n * not receive. Default `0.25`. `0` disables the slope gate.\n * @param options.color - Light tint for the optional Lambert boost. Mutate\n * in place; the material holds this object. Default white.\n * @param options.diffuse - Lambert boost on top of identity. Default `0`\n * (shadow multiplier only). Facing, unshadowed coverage becomes\n * `1 + color * NdotL * diffuse`.\n * @param options.direction - World-space direction **toward** the light.\n * Mutate in place each frame as the sun moves. Default `(0, 1, 0)`.\n * @param options.midLight - Optional mid cascade (demo: ~50 m). Blend from\n * the first light over `nearRadius`.\n * @param options.midRadius - World metres where the mid cascade yields to\n * `outerLight` (or `farLight`). Default `50`.\n * @param options.outerLight - Optional outer cascade (demo: ~160 m) between\n * mid and scene-sized far. Blend from mid over `midRadius`, then to\n * `farLight` over `outerRadius`.\n * @param options.outerRadius - World metres where the outer cascade yields to\n * `farLight`. Default `160`. Ignored without `outerLight` and `farLight`.\n * @param options.farLight - Optional scene-sized cascade. Out-of-frustum\n * `shadow()` is 1 (lit), so inner maps can stay tight without clipping\n * distant umbras. Blend by camera distance.\n * @param options.nearRadius - World metres where the inner cascade yields to\n * `midLight` (or to `farLight` if there is no mid). Default `20` with\n * `midLight`, else `100`.\n */\nexport function createRelightingShadowFactorMaterial(\n lights: THREE.Light | RelightingLightContribution[],\n options: RelightingShadowFactorOptions = {},\n): THREE.MeshStandardNodeMaterial {\n const umbra = Number.isFinite(options.umbra)\n ? Math.min(1, Math.max(0, options.umbra as number))\n : 0.45;\n const receiveUpMin = Number.isFinite(options.receiveUpMin)\n ? Math.min(0.95, Math.max(0, options.receiveUpMin as number))\n : 0.25;\n const diffuse = Number.isFinite(options.diffuse) ? Math.max(0, options.diffuse as number) : 0;\n const material = new THREE.MeshStandardNodeMaterial();\n material.side = THREE.DoubleSide;\n material.color = new THREE.Color(1, 1, 1);\n material.roughness = 1;\n material.metalness = 0;\n material.transparent = false;\n material.depthWrite = true;\n // polygonOffset reduces self-shadow acne that reads as mesh wireframe.\n material.polygonOffset = true;\n material.polygonOffsetFactor = 2;\n material.polygonOffsetUnits = 2;\n // Bypass MeshStandard lighting. Shadow attenuation is the base multiplier;\n // optional Lambert is added on top of identity so unshadowed coverage stays\n // ≈ 1 when `diffuse` is 0.\n const dist = positionWorld.distance(cameraPosition);\n const contributions = normalizeRelightingLights(lights);\n const receive =\n receiveUpMin > 0\n ? asNode<'float'>(smoothstep(float(receiveUpMin), float(receiveUpMin + 0.3), normalWorld.y))\n : float(1);\n\n const shadowTermAt = (index: number, light: THREE.Light): Node<'float'> => {\n const raw = index === 0 ? cascadedShadow(light, options, dist) : asNode<'float'>(shadow(light));\n // Mix toward lit (1) as punctual range fades so umbras soften instead of\n // clipping at the shadow-map far plane.\n return asNode<'float'>(mix(float(1), raw, punctualUmbraWeight(light)));\n };\n // A ShadowNode owns its shadow render resources. Reuse one node per\n // contribution so adding fill does not render the same shadow map twice.\n const shadowTerms = contributions.map((contribution, index) =>\n shadowTermAt(index, contribution.light),\n );\n\n let factor: Node<'float'>;\n let shadowed: Node<'float'> = float(1);\n\n if (options.combine === 'min' && contributions.length > 0) {\n // Union of umbras with per-light depth from contribution intensity:\n // umbra_i = clamp(1 - I * (1 - umbra), 0, 1)\n // so I=0 → no shadow, I=1 → default umbra, I>1 → darker than default.\n let factorCombined: Node<'float'> = float(1);\n for (let i = 0; i < contributions.length; i++) {\n const contribution = contributions[i]!;\n const intensity = contributionIntensity(contribution);\n if (intensity <= 0) continue;\n const umbra_i = Math.min(1, Math.max(0, 1 - intensity * (1 - umbra)));\n const term = shadowTerms[i]!;\n const factor_i = asNode<'float'>(mix(float(umbra_i), float(1), term));\n factorCombined = asNode<'float'>(min(factorCombined, factor_i));\n }\n shadowed = factorCombined;\n factor = asNode<'float'>(mix(float(1), factorCombined, receive));\n } else {\n const shadowIndices = contributions\n .map((contribution, index) => ({ index, intensity: contributionIntensity(contribution) }))\n .filter(({ intensity }) => intensity > 0);\n if (shadowIndices.length === 1) {\n shadowed = shadowTerms[shadowIndices[0]!.index]!;\n } else if (shadowIndices.length > 1) {\n let illum: Node<'float'> = float(0);\n let denom = 0;\n for (const { index, intensity } of shadowIndices) {\n denom += intensity;\n const term = shadowTerms[index]!;\n illum = asNode<'float'>(illum.add(float(intensity).mul(term)));\n }\n shadowed = asNode<'float'>(illum.div(float(denom)));\n }\n if (shadowIndices.length > 0) {\n const attenuation = mix(float(1), shadowed, receive);\n factor = mix(float(umbra), float(1), attenuation);\n } else {\n factor = float(1);\n }\n }\n\n let boost: Node<'vec3'> = vec3(0, 0, 0);\n if (diffuse > 0) {\n const lightColor = uniform(options.color ?? new THREE.Color(1, 1, 1));\n const lightDir = uniform(options.direction ?? new THREE.Vector3(0, 1, 0));\n const ndl = asNode<'float'>(normalWorld.dot(lightDir.normalize()).max(0));\n boost = asNode<'vec3'>(lightColor.mul(ndl).mul(float(diffuse)).mul(receive).mul(shadowed));\n }\n for (let i = 0; i < contributions.length; i++) {\n const contribution = contributions[i]!;\n const fill = contributionFill(contribution);\n if (fill <= 0) continue;\n const light = contribution.light;\n const vis = shadowTerms[i]!;\n const punctual = punctualFillTerm(light, fill, vis);\n const directional = directionalFillTerm(light, fill, vis);\n boost = asNode<'vec3'>(boost.add(punctual).add(directional));\n }\n material.outputNode = vec4(vec3(factor).add(boost), float(1));\n return material;\n}\n\nfunction triangleMeshToGeometry(data: TriangleMeshData): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n geometry.setAttribute('position', new THREE.BufferAttribute(data.positions.slice(), 3));\n geometry.setIndex(new THREE.BufferAttribute(data.indices.slice(), 1));\n // Normals are computed after any world bake in {@link createRelightingProxy}.\n return geometry;\n}\n\n/**\n * Builds a PlayCanvas-style relighting **proxy** from collision tiles or raw\n * geometries. The host places {@link RelightingProxy.group} on a dedicated\n * layer / scene with lights (never the main splat scene), renders that into\n * an RGBA RT matching the main camera, then calls\n * {@link SplatMesh.setRelighting} with the RT texture.\n *\n * Collision meshes are a convenient stand-in when their silhouette is good\n * enough; a denser reconstructed mesh is often better. This helper does not\n * own lights or the render target.\n *\n * When `matrixWorld` is set (e.g. streamed mesh world matrix with the LCC\n * Z-up→Y-up correction), it is **baked into the geometry** the same way the\n * demo collision BVH path does - so the proxy aligns with splats without\n * depending on a live Object3D transform.\n *\n * Coarse collision proxies leave hard coverage silhouettes; hosts should pass\n * `RelightingSettings.softness` (and often a half-res lighting RT) so those\n * edges do not look like a static shadow.\n *\n * For cast umbras **without** a static dark footprint, render the proxy with\n * {@link createRelightingShadowFactorMaterial} so RT RGB is a multiplier\n * (unshadowed ≈ 1), not a lit-gray appearance.\n */\nexport function createRelightingProxy(\n options: {\n /** LCC collision tiles (source-local). Prefer with `matrixWorld`. */\n tiles?: readonly CollisionMeshTile[];\n /** Extra or alternative geometries already in the desired frame. */\n geometries?: readonly THREE.BufferGeometry[];\n /**\n * Source-local → world (e.g. streamed mesh `matrixWorld`). Baked into\n * tile / cloned geometries; leave unset when geometries are already world.\n */\n matrixWorld?: THREE.Matrix4;\n /** Gray albedo in `[0, 1]`. Default `0.5` (PlayCanvas brightness 2). */\n albedo?: number;\n } = {},\n): RelightingProxy {\n const albedo = options.albedo ?? 0.5;\n const group = new THREE.Group();\n const worldMatrix = options.matrixWorld ?? null;\n\n const ownedGeometries: THREE.BufferGeometry[] = [];\n const material = new THREE.MeshStandardMaterial({\n color: new THREE.Color(albedo, albedo, albedo),\n roughness: 1,\n metalness: 0,\n side: THREE.DoubleSide,\n });\n\n const bakeWorld = (geometry: THREE.BufferGeometry): void => {\n if (!worldMatrix) return;\n geometry.applyMatrix4(worldMatrix);\n };\n\n const addGeometry = (geometry: THREE.BufferGeometry, owns: boolean): void => {\n let geo = geometry;\n let owned = owns;\n if (worldMatrix && !owns) {\n // Caller still owns the original; bake a private copy.\n geo = geometry.clone();\n owned = true;\n }\n bakeWorld(geo);\n geo.computeVertexNormals();\n if (owned) ownedGeometries.push(geo);\n const mesh = new THREE.Mesh(geo, material);\n mesh.castShadow = true;\n mesh.receiveShadow = true;\n // Proxy is lighting-only; never participate in picking / raycasts.\n mesh.raycast = () => undefined;\n group.add(mesh);\n };\n\n for (const tile of options.tiles ?? []) {\n addGeometry(triangleMeshToGeometry(tile.data), true);\n }\n for (const geometry of options.geometries ?? []) {\n addGeometry(geometry, false);\n }\n\n const configureMaterial = (mat: THREE.Material): void => {\n mat.side = THREE.DoubleSide;\n mat.transparent = false;\n mat.opacity = 1;\n if ('color' in mat && mat.color instanceof THREE.Color) {\n mat.color.setRGB(albedo, albedo, albedo);\n }\n if ('roughness' in mat && typeof mat.roughness === 'number') {\n (mat as THREE.MeshStandardMaterial).roughness = 1;\n }\n if ('metalness' in mat && typeof mat.metalness === 'number') {\n (mat as THREE.MeshStandardMaterial).metalness = 0;\n }\n };\n\n return {\n group,\n configureMaterial,\n dispose: () => {\n material.dispose();\n for (const geometry of ownedGeometries) geometry.dispose();\n group.clear();\n },\n };\n}\n"],"names":["size","THREE","clearColor","RELIGHT_PASS_CONTEXT","context","node","renderRelightingFactorMap","renderer","scene","camera","target","previousTarget","previousCubeFace","previousMipmapLevel","previousMrt","previousAlpha","previousAutoClear","previousShadowEnabled","previousShadowAutoUpdate","previousContext","SHAPE_STRIDE","MODE_INDEX","DEFAULT_CENTER","DEFAULT_COLOR","DEFAULT_ROTATION","sdfEffects","initialShapes","options","maxShapes","slots","i","shapesUniform","uniformArray","count","uniform","warnedOverflow","setShapes","shapes","n","warn","he","b","c","col","rot","kindIndex","ctx","p","rgb","alphaMul","float","k","el","j","s0","s1","s2","s3","s4","center","kind","falloff","shapeColor","mode","invert","strength","gate","int","rel","pl","rotateByQuatConj","dSphere","dBox","sdBox","dCylinder","sdCappedCylinder","boxMask","cylinderMask","dRaw","mix","d","insideCov","smoothstep","rimCov","tintMask","desatMask","hideMask","rimMask","luma","vec3","vec4","q","outside","inside","r","h","dr","dy","vec2","v","u","s","t","lightingPreset","dir","direction","ambient","diffuse","ndl","shade","depthOfFieldPreset","focusDistance","aperture","maxBlur","depth","focus","coc","blur","alpha","revealPreset","progress","frequency","edge","vnoise","wgslFn","noise","threshold","visible","worldWarpPreset","intensity","radius","R","planetW","foldW","cA","sFold","sPlanet","foldV","planetV","warpedV","halfPiR","dFoldDz","dPlanetDz","foldC0","foldC1","foldC2","planetC0","planetC1","planetC2","i0","i1","i2","j0","j1","j2","r0","r1u","r1","r2gs","r2","qView","mat3","viewR","modelViewMatrix","viewRT","warpR","MAX_RELIGHTING_SHADOW_LIGHTS","contributionIntensity","contribution","value","contributionFill","liveRef","name","type","object","ref","reference","frameGroup","punctualUmbraWeight","light","punctual","decay","lightDistance","positionWorld","objectPosition","range","window","punctualFillTerm","fill","vis","isSpot","isPoint","lightPos","toLightVec","dist","toLight","normalWorld","mask","spot","shine","outer","penumbra","inner","cone","cos","chroma","directionalFillTerm","dirLight","normalizeRelightingLights","lights","list","weighted","cascadedShadow","nearRadius","midRadius","outerRadius","blendAt","shadowed","shadow","createRelightingShadowFactorMaterial","umbra","receiveUpMin","material","cameraPosition","contributions","receive","shadowTermAt","index","raw","shadowTerms","factor","factorCombined","umbra_i","term","factor_i","min","shadowIndices","illum","denom","attenuation","boost","lightColor","lightDir","directional","triangleMeshToGeometry","data","geometry","createRelightingProxy","albedo","group","worldMatrix","ownedGeometries","bakeWorld","addGeometry","owns","geo","owned","mesh","tile","mat"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@voluma/vlam/formats/ksplat` - mkkellogg `.ksplat` parser.
|
|
3
3
|
*
|
|
4
|
-
* Loading through {@link
|
|
4
|
+
* Loading through {@link loadSplatData} / {@link loadSplatDataFile} does not require
|
|
5
5
|
* this import; the loading worker already knows this format. Use this subpath
|
|
6
6
|
* for direct decode outside the default loaders.
|
|
7
7
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as THREE from 'three/webgpu';
|
|
2
2
|
import { type SplatRequestOptions } from '../../loading';
|
|
3
3
|
import { type TriangleMeshData } from './parse-mesh-ply';
|
|
4
|
-
import type {
|
|
4
|
+
import type { SplatCollisionData } from '../../lod-source';
|
|
5
5
|
/**
|
|
6
6
|
* Fetches the collision-mesh tiles a streamed scene declares.
|
|
7
7
|
*
|
|
@@ -31,7 +31,7 @@ export interface CollisionMeshTile {
|
|
|
31
31
|
*
|
|
32
32
|
* @throws a `DOMException` named `AbortError` when cancelled.
|
|
33
33
|
*/
|
|
34
|
-
export declare function loadCollisionMeshTiles(collision:
|
|
34
|
+
export declare function loadCollisionMeshTiles(collision: SplatCollisionData, options?: {
|
|
35
35
|
request?: SplatRequestOptions;
|
|
36
36
|
signal?: AbortSignal;
|
|
37
37
|
}): Promise<CollisionMeshTile[]>;
|