@voluma/vlam 0.2.3 → 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.
Files changed (91) hide show
  1. package/README.md +3 -2
  2. package/dist/budget-governor.d.ts +4 -4
  3. package/dist/camera-budget-governor.d.ts +11 -11
  4. package/dist/chunk-cache-budget.d.ts +5 -5
  5. package/dist/chunk-fetch-scheduler.d.ts +4 -4
  6. package/dist/chunk-loader-DlNwggfG.js +1859 -0
  7. package/dist/chunk-loader-DlNwggfG.js.map +1 -0
  8. package/dist/chunk-loader.d.ts +2 -2
  9. package/dist/compute-sorter.d.ts +1 -1
  10. package/dist/effects.d.ts +26 -16
  11. package/dist/effects.js +199 -165
  12. package/dist/effects.js.map +1 -1
  13. package/dist/formats/ksplat/index.d.ts +1 -1
  14. package/dist/formats/lcc/collision-mesh.d.ts +2 -2
  15. package/dist/formats/lcc.js +17 -16
  16. package/dist/formats/lcc.js.map +1 -1
  17. package/dist/formats/ply/index.d.ts +1 -1
  18. package/dist/formats/ply/parse-splat-ply.d.ts +2 -2
  19. package/dist/formats/ply.js +1 -1
  20. package/dist/formats/ply.js.map +1 -1
  21. package/dist/formats/rad/index.d.ts +1 -1
  22. package/dist/formats/rad/parse-rad.d.ts +1 -1
  23. package/dist/formats/rad/rad.d.ts +2 -2
  24. package/dist/formats/rad.js +3 -3
  25. package/dist/formats/rad.js.map +1 -1
  26. package/dist/formats/sog/index.d.ts +1 -1
  27. package/dist/formats/sog/parse-sog.d.ts +4 -4
  28. package/dist/formats/sog.js.map +1 -1
  29. package/dist/formats/splat/index.d.ts +1 -1
  30. package/dist/formats/spz/index.d.ts +1 -1
  31. package/dist/index.d.ts +16 -25
  32. package/dist/index.js +253 -9047
  33. package/dist/index.js.map +1 -1
  34. package/dist/{load-scene.d.ts → load-splat-data.d.ts} +15 -13
  35. package/dist/loaders.d.ts +15 -0
  36. package/dist/loaders.js +26 -0
  37. package/dist/loaders.js.map +1 -0
  38. package/dist/loading-wLo8vRbA.js.map +1 -1
  39. package/dist/loading.d.ts +8 -8
  40. package/dist/{splat-mesh-types-CTN_duw2.js → lod-scheduler-B0a_uBlv.js} +133 -140
  41. package/dist/lod-scheduler-B0a_uBlv.js.map +1 -0
  42. package/dist/lod-scheduler.d.ts +1 -1
  43. package/dist/lod-source.d.ts +5 -5
  44. package/dist/{splat-scene.d.ts → merged-splat-mesh.d.ts} +14 -15
  45. package/dist/orientation.d.ts +7 -7
  46. package/dist/{ply-header-BwgExCn-.js → ply-header-B9WllbPg.js} +3 -3
  47. package/dist/{ply-header-BwgExCn-.js.map → ply-header-B9WllbPg.js.map} +1 -1
  48. package/dist/radix-sorter-R0FCSqKu.js +178 -0
  49. package/dist/radix-sorter-R0FCSqKu.js.map +1 -0
  50. package/dist/relighting-CSQF3UcL.js +969 -0
  51. package/dist/relighting-CSQF3UcL.js.map +1 -0
  52. package/dist/relighting.d.ts +1 -1
  53. package/dist/selection.d.ts +13 -0
  54. package/dist/selection.js +137 -0
  55. package/dist/selection.js.map +1 -0
  56. package/dist/source-transform.d.ts +4 -4
  57. package/dist/{splat-budget-DXew_86c.js → splat-budget-DuVZspPQ.js} +7 -7
  58. package/dist/{splat-budget-DXew_86c.js.map → splat-budget-DuVZspPQ.js.map} +1 -1
  59. package/dist/splat-budget.d.ts +1 -1
  60. package/dist/splat-data-BZM5dhhx.js.map +1 -1
  61. package/dist/splat-data.d.ts +4 -4
  62. package/dist/splat-mesh-aQdcWA6v.js +1942 -0
  63. package/dist/splat-mesh-aQdcWA6v.js.map +1 -0
  64. package/dist/splat-mesh-material.d.ts +2 -2
  65. package/dist/splat-mesh-types-BZIko-_9.js +18 -0
  66. package/dist/splat-mesh-types-BZIko-_9.js.map +1 -0
  67. package/dist/splat-mesh-types.d.ts +14 -8
  68. package/dist/splat-mesh.d.ts +4 -4
  69. package/dist/splat-modifier.d.ts +5 -5
  70. package/dist/splat-partition.d.ts +1 -1
  71. package/dist/static-lod-entry.d.ts +12 -0
  72. package/dist/static-lod-splat-mesh.d.ts +2 -2
  73. package/dist/{static-lod-worker-D2fyqQZO.js → static-lod-worker-DktKnJkR.js} +46 -46
  74. package/dist/{static-lod-worker-D2fyqQZO.js.map → static-lod-worker-DktKnJkR.js.map} +1 -1
  75. package/dist/static-lod.js +148 -0
  76. package/dist/static-lod.js.map +1 -0
  77. package/dist/streamed-splat-mesh.d.ts +21 -27
  78. package/dist/streaming.d.ts +23 -0
  79. package/dist/streaming.js +2877 -0
  80. package/dist/streaming.js.map +1 -0
  81. package/dist/{unified-splat-renderer.d.ts → unified-splat-mesh.d.ts} +10 -10
  82. package/dist/unified-work-buffer.d.ts +66 -0
  83. package/dist/unified.d.ts +11 -0
  84. package/dist/unified.js +738 -0
  85. package/dist/unified.js.map +1 -0
  86. package/dist/webgpu-limits-Y3CI_6_6.js +43 -0
  87. package/dist/webgpu-limits-Y3CI_6_6.js.map +1 -0
  88. package/dist/webgpu-limits.d.ts +0 -59
  89. package/dist/work-buffer-gather.d.ts +1 -1
  90. package/package.json +24 -4
  91. 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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"}
@@ -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 SplatFileLoadOptions, type SplatFormat, type SplatInputOptions } from './loading';
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?: SplatFileLoadOptions): Promise<SplatData>;
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
  /**
@@ -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 SplatScene} pool.
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`), so
11
- * a host can highlight exactly what a selection would take.
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} / `UnifiedSplatRenderer.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 `SplatScene` is the splat's **placed** position. So a shape or
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 `SplatScene` that means scene
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 `SplatScene` that frame is the scene's, so one direction lights
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 `SplatScene` the normal comes from the *placed* covariance, so a source
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` / `UnifiedSplatRenderer.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 `SplatScene` a moved source
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
  */
@@ -284,8 +285,14 @@ export interface RelightingProxy {
284
285
  /** One independent shadow-casting light for {@link createRelightingShadowFactorMaterial}. */
285
286
  export type RelightingLightContribution = {
286
287
  light: THREE.Light;
287
- /** Relative umbra weight. Default `1`. Values `<= 0` are skipped. */
288
+ /** Relative umbra weight. Default `1`. Values `<= 0` are skipped unless `fill` is set. */
288
289
  intensity?: number;
290
+ /**
291
+ * Additive Lambert fill on top of the umbra factor (RGB may exceed 1).
292
+ * Gated by `shadow()`, facing, and for spot/point lights the Frostbite
293
+ * `distance` window (and the spot cone). `0` / omitted = umbra only.
294
+ */
295
+ fill?: number;
289
296
  };
290
297
  /** Options for {@link createRelightingShadowFactorMaterial}. */
291
298
  export type RelightingShadowFactorOptions = {
@@ -333,16 +340,19 @@ export declare const MAX_RELIGHTING_SHADOW_LIGHTS = 32;
333
340
  * union of umbras so each caster keeps its silhouette. Spot / point lights
334
341
  * with `distance &gt; 0` fade their umbra to lit via a Frostbite cutoff window
335
342
  * (steepened by `decay`) so the shadow map far plane is not a hard cliff.
336
- * Cascades (`midLight` / `outerLight` / `farLight`) still attach to the
337
- * **first** directional only. At most {@link MAX_RELIGHTING_SHADOW_LIGHTS}
338
- * independent lights are used; extras are ignored. Graph size follows the
339
- * live contribution count (not a padded loop of that width).
343
+ * Contribution `fill` adds occluded Lambert (range + cone for punctual lights)
344
+ * on top of that identity so accent lights can light splats without wrapping
345
+ * through collision. Cascades (`midLight` / `outerLight` / `farLight`) still
346
+ * attach to the **first** directional only. At most
347
+ * {@link MAX_RELIGHTING_SHADOW_LIGHTS} independent lights are used; extras
348
+ * are ignored. Graph size follows the live contribution count (not a padded
349
+ * loop of that width).
340
350
  *
341
351
  * By default, only **upward** proxy faces receive (`receiveUpMin`). Vertical
342
352
  * and noisy foliage triangles still **cast**, but they do not self-shadow —
343
353
  * that PCF acne reads as per-frame sparkle in trees. Pass `receiveUpMin: 0`
344
- * to receive on every face. The Lambert term uses the same slope gate, so
345
- * steep foliage does not pick up a noisy tint.
354
+ * to receive on every face. Contribution `fill` does **not** use that slope
355
+ * gate (stems would otherwise stay unlit in the factor map).
346
356
  *
347
357
  * Clear the lighting RT to **RGB 1, A 0** (not black). Softness / bilinear
348
358
  * samples at coverage edges otherwise pull in black and draw a dark outline