@voluma/vlam 0.1.0 → 0.2.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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/THIRD_PARTY_NOTICES.md +86 -0
  4. package/dist/budget-governor.d.ts +154 -0
  5. package/dist/camera-budget-governor.d.ts +234 -0
  6. package/dist/chunk-cache-budget.d.ts +156 -0
  7. package/dist/chunk-fetch-scheduler.d.ts +168 -0
  8. package/dist/chunk-loader.d.ts +85 -0
  9. package/dist/compute-sorter.d.ts +131 -0
  10. package/dist/create-splat-renderer.d.ts +99 -0
  11. package/dist/dataset-source.d.ts +55 -0
  12. package/dist/depth-of-field.d.ts +73 -0
  13. package/dist/effects.d.ts +337 -0
  14. package/dist/effects.js +180 -0
  15. package/dist/effects.js.map +1 -0
  16. package/dist/formats/ksplat/index.d.ts +8 -0
  17. package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
  18. package/dist/formats/ksplat.js +167 -0
  19. package/dist/formats/ksplat.js.map +1 -0
  20. package/dist/formats/lcc/collision-mesh.d.ts +37 -0
  21. package/dist/formats/lcc/collision-partition.d.ts +35 -0
  22. package/dist/formats/lcc/index.d.ts +16 -0
  23. package/dist/formats/lcc/lcc.d.ts +28 -0
  24. package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
  25. package/dist/formats/lcc/lcc2.d.ts +4 -0
  26. package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
  27. package/dist/formats/lcc/parse-lcc.d.ts +177 -0
  28. package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
  29. package/dist/formats/lcc.js +969 -0
  30. package/dist/formats/lcc.js.map +1 -0
  31. package/dist/formats/ply/index.d.ts +12 -0
  32. package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
  33. package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
  34. package/dist/formats/ply.js +230 -0
  35. package/dist/formats/ply.js.map +1 -0
  36. package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
  37. package/dist/formats/rad/index.d.ts +10 -0
  38. package/dist/formats/rad/parse-rad.d.ts +88 -0
  39. package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
  40. package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
  41. package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
  42. package/dist/formats/rad/rad.d.ts +132 -0
  43. package/dist/formats/rad.js +1098 -0
  44. package/dist/formats/rad.js.map +1 -0
  45. package/dist/formats/sog/index.d.ts +9 -0
  46. package/dist/formats/sog/parse-sog.d.ts +45 -0
  47. package/dist/formats/sog.js +257 -0
  48. package/dist/formats/sog.js.map +1 -0
  49. package/dist/formats/splat/index.d.ts +8 -0
  50. package/dist/formats/splat/parse-splat.d.ts +10 -0
  51. package/dist/formats/splat.js +39 -0
  52. package/dist/formats/splat.js.map +1 -0
  53. package/dist/formats/spz/index.d.ts +8 -0
  54. package/dist/formats/spz/parse-spz.d.ts +10 -0
  55. package/dist/formats/spz.js +218 -0
  56. package/dist/formats/spz.js.map +1 -0
  57. package/dist/frontier-worker-CAnK_Cmu.js +566 -0
  58. package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
  59. package/dist/half-float-Cs4FDq8Q.js +30 -0
  60. package/dist/half-float-Cs4FDq8Q.js.map +1 -0
  61. package/dist/half-float.d.ts +15 -0
  62. package/dist/index.d.ts +54 -0
  63. package/dist/index.js +9054 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/load-scene.d.ts +50 -0
  66. package/dist/load-worker-protocol.d.ts +82 -0
  67. package/dist/loading-wLo8vRbA.js +102 -0
  68. package/dist/loading-wLo8vRbA.js.map +1 -0
  69. package/dist/loading.d.ts +150 -0
  70. package/dist/lod-manifest.d.ts +61 -0
  71. package/dist/lod-scheduler.d.ts +229 -0
  72. package/dist/lod-source.d.ts +207 -0
  73. package/dist/logging-BfPdd7NJ.js +19 -0
  74. package/dist/logging-BfPdd7NJ.js.map +1 -0
  75. package/dist/logging.d.ts +37 -0
  76. package/dist/one-shot-worker-G80btnth.js +664 -0
  77. package/dist/one-shot-worker-G80btnth.js.map +1 -0
  78. package/dist/orientation.d.ts +35 -0
  79. package/dist/ply-header-BwgExCn-.js +125 -0
  80. package/dist/ply-header-BwgExCn-.js.map +1 -0
  81. package/dist/ply-header.d.ts +87 -0
  82. package/dist/radix-sort.d.ts +28 -0
  83. package/dist/radix-sorter.d.ts +46 -0
  84. package/dist/relighting.d.ts +50 -0
  85. package/dist/selection-volume.d.ts +87 -0
  86. package/dist/sh-pack-D5wAe5gg.js +54 -0
  87. package/dist/sh-pack-D5wAe5gg.js.map +1 -0
  88. package/dist/sh-pack.d.ts +49 -0
  89. package/dist/sort-scheduler.d.ts +62 -0
  90. package/dist/sort-worker-protocol.d.ts +37 -0
  91. package/dist/sorter.d.ts +31 -0
  92. package/dist/source-transform.d.ts +57 -0
  93. package/dist/splat-budget-PSojLJPO.js +157 -0
  94. package/dist/splat-budget-PSojLJPO.js.map +1 -0
  95. package/dist/splat-budget.d.ts +404 -0
  96. package/dist/splat-data-BZM5dhhx.js +10 -0
  97. package/dist/splat-data-BZM5dhhx.js.map +1 -0
  98. package/dist/splat-data.d.ts +150 -0
  99. package/dist/splat-depth-pack.d.ts +39 -0
  100. package/dist/splat-mesh-material.d.ts +255 -0
  101. package/dist/splat-mesh-picking.d.ts +87 -0
  102. package/dist/splat-mesh-pool.d.ts +241 -0
  103. package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
  104. package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
  105. package/dist/splat-mesh-types.d.ts +390 -0
  106. package/dist/splat-mesh.d.ts +737 -0
  107. package/dist/splat-modifier-stack.d.ts +38 -0
  108. package/dist/splat-modifier.d.ts +156 -0
  109. package/dist/splat-partition.d.ts +37 -0
  110. package/dist/splat-query.d.ts +51 -0
  111. package/dist/splat-scene.d.ts +127 -0
  112. package/dist/splat-sort-bounds.d.ts +11 -0
  113. package/dist/static-lod-splat-mesh.d.ts +53 -0
  114. package/dist/static-lod-worker-D2fyqQZO.js +474 -0
  115. package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
  116. package/dist/static-lod-worker-protocol.d.ts +37 -0
  117. package/dist/static-lod.d.ts +24 -0
  118. package/dist/storage-attribute-mirror.d.ts +103 -0
  119. package/dist/streamed-splat-mesh-utils.d.ts +146 -0
  120. package/dist/streamed-splat-mesh.d.ts +985 -0
  121. package/dist/unified-splat-renderer.d.ts +205 -0
  122. package/dist/webgpu-limits.d.ts +133 -0
  123. package/dist/work-buffer-gather.d.ts +119 -0
  124. package/dist/work-buffer-material.d.ts +42 -0
  125. package/dist/worker-sorter.d.ts +57 -0
  126. package/dist/xr-view.d.ts +92 -0
  127. package/package.json +135 -21
@@ -0,0 +1,50 @@
1
+ import type { SplatData } from './splat-data';
2
+ import type { SplatFormat, SplatFileLoadOptions, SplatInputOptions } from './loading';
3
+ export type { SplatFileLoadOptions };
4
+ /** Options for {@link loadScene}. */
5
+ export interface SplatLoadOptions extends SplatInputOptions {
6
+ /** Parser selection; URL extension detection is used only for `auto`. */
7
+ format?: SplatFormat;
8
+ }
9
+ /**
10
+ * Fetches and decodes a splat scene (`.sog`, `.ply`, `.spz`, `.splat` or
11
+ * `.ksplat`) in a Web Worker, so
12
+ * even multi-million-splat decodes never block the main thread. The
13
+ * decoded arrays are transferred back, not copied.
14
+ *
15
+ * One-shot convenience over {@link ChunkLoader}; use `ChunkLoader`
16
+ * directly when loading many chunks or when cancellation is needed.
17
+ *
18
+ * @param input - Scene URL; its extension selects the parser unless an
19
+ * explicit `format` option is provided.
20
+ * @returns The decoded splat data, ready for {@link SplatMesh}.
21
+ * @throws Rejects only with `SplatLoadError` (resolve, fetch or decode
22
+ * failures - `error.phase` says which) or a `DOMException` named `AbortError`
23
+ * when `options.signal` fires.
24
+ *
25
+ * @example
26
+ * const splats = new SplatMesh(await loadScene('/scene.sog'));
27
+ * scene.add(splats);
28
+ */
29
+ export declare function loadScene(input: string | URL, options?: SplatLoadOptions): Promise<SplatData>;
30
+ /**
31
+ * Decodes a local splat file - from a `<input type="file">` or a drag-and-drop
32
+ * - in a Web Worker, without uploading it anywhere. The file's bytes are read
33
+ * worker-side and the decoded arrays are transferred back, not copied.
34
+ *
35
+ * Only self-contained files are supported: `.ply`, `.spz`, `.splat`, `.ksplat`
36
+ * and bundled `.sog`. An unbundled SOG directory needs sibling fetches, so it
37
+ * must be served over HTTP and loaded with {@link loadScene}.
38
+ *
39
+ * @param file - The file; its name selects the parser unless an explicit
40
+ * `format` option is provided.
41
+ * @returns The decoded splat data, ready for {@link SplatMesh}.
42
+ * @throws Rejects only with `SplatLoadError` (unknown extension, read or
43
+ * decode failures) or a `DOMException` named `AbortError` when
44
+ * `options.signal` fires.
45
+ *
46
+ * @example
47
+ * const splats = new SplatMesh(await loadSceneFile(event.dataTransfer.files[0]));
48
+ * scene.add(splats);
49
+ */
50
+ export declare function loadSceneFile(file: File, options?: SplatFileLoadOptions): Promise<SplatData>;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Wire protocol for the loading worker (`load-worker.ts`).
3
+ *
4
+ * These message types live outside the worker module on purpose: the worker
5
+ * itself is bundled into a blob URL and is not part of the published entry
6
+ * graph, so keeping the shared interfaces here is what lets the main-thread
7
+ * client ({@link ChunkLoader}) be typed without dragging worker source into
8
+ * the shipped declarations.
9
+ */
10
+ import type { LccChunkParams } from './formats/lcc/parse-lcc';
11
+ import type { RadShCodebook, SplatData } from './splat-data';
12
+ import type { ChunkFileFormat, SerializedSplatLoadError, SplatRequestOptions } from './loading';
13
+ /** Where a load's bytes come from. `File` crosses the worker boundary by
14
+ * structured clone, so a local file is read (and decoded) in the worker. */
15
+ export type LoadWorkerSource = {
16
+ from: 'url';
17
+ /** Absolute URL to a supported file, or a SOG chunk directory. */
18
+ url: string;
19
+ /** 'file' decodes by extension; 'directory' is an unbundled SOG. */
20
+ kind: 'file' | 'directory';
21
+ /** Serializable fetch settings supplied by the consumer. */
22
+ request?: SplatRequestOptions;
23
+ } | {
24
+ from: 'file';
25
+ file: File;
26
+ };
27
+ export type LoadWorkerRequest = {
28
+ type: 'load';
29
+ id: number;
30
+ source: LoadWorkerSource;
31
+ /** Explicit parser selection; 'directory' sources ignore it. */
32
+ format: ChunkFileFormat;
33
+ /** Byte range and dequantization ranges; required by `lcc-bin`. */
34
+ lcc?: LccChunkParams;
35
+ /** Byte range within the single-file `.rad`; required by `rad-chunk`. */
36
+ rad?: RadChunkRangeRequest;
37
+ /** Convert a SOG chunk's palette shN into packed shN at decode (M11). */
38
+ sog?: {
39
+ packShBands: 1 | 2 | 3;
40
+ };
41
+ /** A 'directory' chunk's files by name, when it has no directory URL. */
42
+ files?: Readonly<Record<string, string>>;
43
+ /** Report read progress; off unless the caller passed `onProgress`. */
44
+ progress?: boolean;
45
+ } | {
46
+ type: 'cancel';
47
+ id: number;
48
+ };
49
+ /** A `rad-chunk`'s location. A single-file `.rad` gives a byte range; a
50
+ * `--rad-chunked` set gives no range - the URL is the whole `.radc` file. */
51
+ export interface RadChunkRangeRequest {
52
+ /** Byte offset within the single-file `.rad`; omitted for an external
53
+ * `.radc` file (fetched whole). */
54
+ readonly start?: number;
55
+ /** Byte length of the range; omitted for an external `.radc` file. */
56
+ readonly length?: number;
57
+ /** Spark writes clustered SH coefficients only in chunk zero. */
58
+ readonly shCodebook?: RadShCodebook;
59
+ /** Scene-wide SH quantization extent established by chunk zero, so every
60
+ * chunk's packed SH shares the one range the pool decodes against. */
61
+ readonly shExtent?: number;
62
+ /** Reorder splats leaves-first for the prefix reader (default). The foveation
63
+ * reader passes `false` to keep file order (see `parseRadChunkStreaming`). */
64
+ readonly reorder?: boolean;
65
+ }
66
+ export type LoadWorkerResponse = {
67
+ type: 'result';
68
+ id: number;
69
+ ok: true;
70
+ data: SplatData;
71
+ } | {
72
+ type: 'result';
73
+ id: number;
74
+ ok: false;
75
+ error: SerializedSplatLoadError;
76
+ cancelled: boolean;
77
+ } | {
78
+ type: 'progress';
79
+ id: number;
80
+ loaded: number;
81
+ total: number;
82
+ };
@@ -0,0 +1,102 @@
1
+ var c = Object.defineProperty;
2
+ var l = (e, r, a) => r in e ? c(e, r, { enumerable: !0, configurable: !0, writable: !0, value: a }) : e[r] = a;
3
+ var s = (e, r, a) => l(e, typeof r != "symbol" ? r + "" : r, a);
4
+ class n extends Error {
5
+ constructor(a, t) {
6
+ super(a, { cause: t.cause });
7
+ s(this, "phase");
8
+ s(this, "url");
9
+ s(this, "status");
10
+ s(this, "retryable");
11
+ this.name = "SplatLoadError", this.phase = t.phase, this.url = t.url, this.status = t.status, this.retryable = t.retryable;
12
+ }
13
+ }
14
+ function h(e, r) {
15
+ var a;
16
+ if (e instanceof URL) return new URL(e.href);
17
+ try {
18
+ const t = r ?? ((a = globalThis.location) == null ? void 0 : a.href);
19
+ return t === void 0 ? new URL(e) : new URL(e, t);
20
+ } catch (t) {
21
+ throw new n(`Unable to resolve splat URL "${e}".`, {
22
+ phase: "resolve",
23
+ url: e,
24
+ retryable: !1,
25
+ cause: t
26
+ });
27
+ }
28
+ }
29
+ function E(e) {
30
+ return u(e.pathname);
31
+ }
32
+ function u(e) {
33
+ const r = e.toLowerCase(), a = r.lastIndexOf(".");
34
+ return a < 0 ? "" : r.slice(a);
35
+ }
36
+ function i(e, r) {
37
+ switch (e) {
38
+ case ".ply":
39
+ return "ply";
40
+ case ".spz":
41
+ return "spz";
42
+ case ".splat":
43
+ return "splat";
44
+ case ".ksplat":
45
+ return "ksplat";
46
+ case ".sog":
47
+ return "sog";
48
+ case ".rad":
49
+ return "rad";
50
+ default:
51
+ throw new n(`Unsupported splat file extension in ${r}.`, {
52
+ phase: "resolve",
53
+ url: r,
54
+ retryable: !1
55
+ });
56
+ }
57
+ }
58
+ function b(e, r) {
59
+ return {
60
+ ...e != null && e.headers ? { headers: { ...e.headers } } : {},
61
+ ...e != null && e.credentials ? { credentials: e.credentials } : {},
62
+ ...e != null && e.mode ? { mode: e.mode } : {},
63
+ ...e != null && e.cache ? { cache: e.cache } : {},
64
+ ...r ? { signal: r } : {}
65
+ };
66
+ }
67
+ function w(e) {
68
+ if (typeof DOMException < "u") return new DOMException(e, "AbortError");
69
+ const r = new Error(e);
70
+ return r.name = "AbortError", r;
71
+ }
72
+ function o(e) {
73
+ return e instanceof Error && e.name === "AbortError";
74
+ }
75
+ function m(e, r) {
76
+ if (e instanceof n) return e;
77
+ const a = e instanceof Error ? e.message : String(e);
78
+ return new n(a, {
79
+ ...r,
80
+ retryable: f(r.status, r.phase),
81
+ cause: e
82
+ });
83
+ }
84
+ function y(e) {
85
+ return new n(e.message, e);
86
+ }
87
+ function f(e, r) {
88
+ return e !== void 0 ? e === 408 || e === 425 || e === 429 || e >= 500 : r === "fetch" || r === "manifest" || r === "worker";
89
+ }
90
+ export {
91
+ n as S,
92
+ m as a,
93
+ u as b,
94
+ w as c,
95
+ y as d,
96
+ E as e,
97
+ o as i,
98
+ h as r,
99
+ i as s,
100
+ b as t
101
+ };
102
+ //# sourceMappingURL=loading-wLo8vRbA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loading-wLo8vRbA.js","sources":["../src/lib/loading.ts"],"sourcesContent":["/**\n * The self-contained scene formats - files (or bundles) that decode to a whole\n * scene on their own, with no external manifest. This is the value set of\n * {@link SplatData.sourceFormat} and the non-`auto` half of {@link SplatFormat}.\n */\nexport type SplatSourceFormat = 'ply' | 'sog' | 'spz' | 'splat' | 'ksplat' | 'rad';\n\n/** Explicit formats accepted by {@link loadScene}. */\nexport type SplatFormat = 'auto' | SplatSourceFormat;\n\n/** Explicit formats accepted by {@link StreamedSplatMesh.load}. */\nexport type StreamedSplatFormat = 'auto' | 'streamed-sog' | 'lcc2' | 'lcc' | 'rad';\n\n/**\n * Parsers the loading worker can run on a chunk. This extends {@link SplatFormat}\n * with formats that are only ever reached as a streamed chunk, never as a\n * standalone scene handed to {@link loadScene}: `lcc-bin` is a byte range of an\n * LCC (`.lcc`) `data.bin`, and `rad-chunk` a byte range of a single-file `.rad` -\n * both meaningless without their manifest/header.\n */\nexport type ChunkFileFormat = Exclude<SplatFormat, 'auto'> | 'lcc-bin' | 'rad-chunk';\n\n/** Serializable fetch settings that can cross the loading-worker boundary. */\nexport interface SplatRequestOptions {\n headers?: Readonly<Record<string, string>>;\n credentials?: RequestCredentials;\n mode?: RequestMode;\n cache?: RequestCache;\n}\n\n/**\n * Reports load progress in bytes, for a determinate progress indicator.\n *\n * `total` is 0 when the size is not known ahead of time (a response with no\n * `Content-Length`), which means \"still working\" rather than \"finished\".\n */\nexport type SplatProgressCallback = (loaded: number, total: number) => void;\n\n/** Shortest gap between two progress reports, in milliseconds (~10/s). */\nconst PROGRESS_INTERVAL_MS = 100;\n\n/**\n * Rate-limits progress reports to ~10/s.\n *\n * A body arrives in ~64 KB chunks, so an unthrottled report would post\n * thousands of messages no indicator can render. The final report is exempt -\n * it is what leaves a determinate bar exactly full rather than at 97%.\n *\n * Identifying that final report needs a known size, so the exemption requires\n * `total > 0`. When the size is unknown there is no bar to fill (the caller\n * shows a spinner) and every report is throttled alike - without the `total`\n * check, `loaded < total` would be false for all of them and the throttle\n * would never engage at all.\n */\nexport function createProgressThrottle(\n report: SplatProgressCallback,\n intervalMs: number = PROGRESS_INTERVAL_MS,\n): SplatProgressCallback {\n let lastReportAt = -Infinity;\n return (loaded: number, total: number): void => {\n const isFinal = total > 0 && loaded >= total;\n const now = monotonicNow();\n if (!isFinal && now - lastReportAt < intervalMs) return;\n lastReportAt = now;\n report(loaded, total);\n };\n}\n\n/**\n * Options for decoding a local file.\n *\n * Shared by {@link loadSceneFile} and `ChunkLoader.loadFile`. There is no\n * `baseUrl`/`request` counterpart to {@link SplatInputOptions} here: a file has\n * no URL to resolve and no request to configure.\n */\nexport interface SplatFileLoadOptions {\n signal?: AbortSignal;\n /** Parser selection; the file name's extension is used only for `auto`. */\n format?: SplatFormat;\n /**\n * Called as the file is read, for a determinate progress bar. A multi-\n * gigabyte PLY takes tens of seconds, so the streamed PLY path reports as it\n * goes; the whole-buffer formats have nothing to report until they are done.\n */\n onProgress?: SplatProgressCallback;\n}\n\n/** Shared URL, cancellation, and request options for package loaders. */\nexport interface SplatInputOptions {\n signal?: AbortSignal;\n baseUrl?: string | URL;\n request?: SplatRequestOptions;\n /**\n * Called as bytes are read. Fires from the loading worker, so it is\n * throttled to whole read windows rather than every byte.\n */\n onProgress?: SplatProgressCallback;\n}\n\n/** The stage at which a splat load failed. */\nexport type SplatLoadPhase = 'resolve' | 'manifest' | 'fetch' | 'decode' | 'worker';\n\n/** Serializable error details returned by the loading worker. */\nexport interface SerializedSplatLoadError {\n name: string;\n message: string;\n phase: SplatLoadPhase;\n url: string;\n status?: number;\n retryable: boolean;\n}\n\n/** Structured error thrown for non-cancellation loading failures. */\nexport class SplatLoadError extends Error {\n readonly phase: SplatLoadPhase;\n readonly url: string;\n readonly status?: number;\n readonly retryable: boolean;\n\n constructor(\n message: string,\n options: {\n phase: SplatLoadPhase;\n url: string;\n status?: number;\n retryable: boolean;\n cause?: unknown;\n },\n ) {\n super(message, { cause: options.cause });\n this.name = 'SplatLoadError';\n this.phase = options.phase;\n this.url = options.url;\n this.status = options.status;\n this.retryable = options.retryable;\n }\n}\n\n/** Resolves a loader input without assuming that a browser location exists. */\nexport function resolveSplatUrl(input: string | URL, baseUrl?: string | URL): URL {\n if (input instanceof URL) return new URL(input.href);\n try {\n const fallbackBase = baseUrl ?? globalThis.location?.href;\n if (fallbackBase === undefined) return new URL(input);\n return new URL(input, fallbackBase);\n } catch (cause) {\n throw new SplatLoadError(`Unable to resolve splat URL \"${input}\".`, {\n phase: 'resolve',\n url: input,\n retryable: false,\n cause,\n });\n }\n}\n\n/** Returns the lower-case pathname extension, excluding query/hash text. */\nexport function splatUrlExtension(url: URL): string {\n return splatNameExtension(url.pathname);\n}\n\n/** Returns the lower-case extension of a plain file name or path. */\nexport function splatNameExtension(name: string): string {\n const lower = name.toLowerCase();\n const dot = lower.lastIndexOf('.');\n return dot < 0 ? '' : lower.slice(dot);\n}\n\n/**\n * Maps a file extension to its parser. `label` names the input in the\n * failure message - a URL for a fetched scene, a file name for a local one.\n *\n * @throws {SplatLoadError} (phase `'resolve'`, not retryable) for an unknown\n * extension, keeping the loader contract uniform: callers of the loading\n * pipeline only ever see `SplatLoadError` or `AbortError`.\n */\nexport function splatFormatForExtension(extension: string, label: string): SplatSourceFormat {\n switch (extension) {\n case '.ply':\n return 'ply';\n case '.spz':\n return 'spz';\n case '.splat':\n return 'splat';\n case '.ksplat':\n return 'ksplat';\n case '.sog':\n return 'sog';\n case '.rad':\n return 'rad';\n default:\n throw new SplatLoadError(`Unsupported splat file extension in ${label}.`, {\n phase: 'resolve',\n url: label,\n retryable: false,\n });\n }\n}\n\n/** Creates a fetch init object without sharing mutable header state. */\nexport function toRequestInit(options?: SplatRequestOptions, signal?: AbortSignal): RequestInit {\n return {\n ...(options?.headers ? { headers: { ...options.headers } } : {}),\n ...(options?.credentials ? { credentials: options.credentials } : {}),\n ...(options?.mode ? { mode: options.mode } : {}),\n ...(options?.cache ? { cache: options.cache } : {}),\n ...(signal ? { signal } : {}),\n };\n}\n\n/**\n * The largest `ArrayBuffer` a browser will allocate: 2 GiB, in every current\n * engine. A file bigger than this cannot be read in one piece at all - the\n * read fails rather than the decode.\n */\nexport const MAX_ARRAY_BUFFER_BYTES = 2 ** 31;\n\n/** Bytes as a short \"1.7 GB\" for a message. */\nfunction gigabytes(bytes: number): string {\n return `${(bytes / 1e9).toFixed(1)} GB`;\n}\n\n/**\n * Reads a whole local file, turning the browser's two unhelpful failure modes\n * into messages that name the real cause.\n *\n * Chrome reports an over-2 GiB read as `NotReadableError`, whose stock text\n * blames file permissions - so a perfectly good 4 GB capture reads as \"your\n * file is broken\". Check the size up front instead.\n *\n * Formats with a streaming parser (see `parseSplatPlyFile`) never come here.\n */\nexport async function readWholeFile(file: File): Promise<ArrayBuffer> {\n if (file.size > MAX_ARRAY_BUFFER_BYTES) {\n throw new Error(\n `${file.name} is ${gigabytes(file.size)}. A browser cannot read more than 2 GB of a file ` +\n 'at once, and this format has to be decoded in one piece. Convert it to SOG ' +\n '(npx @playcanvas/splat-transform in.ply out.sog) or a compressed .ply.',\n );\n }\n try {\n return await file.arrayBuffer();\n } catch (error) {\n if (isAbortError(error)) throw error;\n // NotReadableError also covers a file that moved or changed since the drop\n // - the reference is a snapshot, and reading revalidates it.\n if (error instanceof Error && error.name === 'NotReadableError') {\n throw new Error(\n `${file.name} could not be read. It may have been moved, renamed or changed since it ` +\n 'was dropped, or it may be on a drive the browser cannot reach.',\n { cause: error },\n );\n }\n throw error;\n }\n}\n\n/** Creates an AbortError even where DOMException is unavailable. */\nexport function createAbortError(message: string): Error {\n if (typeof DOMException !== 'undefined') return new DOMException(message, 'AbortError');\n const error = new Error(message);\n error.name = 'AbortError';\n return error;\n}\n\n/** Whether an unknown failure represents cancellation. */\nexport function isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError';\n}\n\n/** Maps an arbitrary failure to the package's structured loading error. */\nexport function toSplatLoadError(\n error: unknown,\n options: { phase: SplatLoadPhase; url: string; status?: number },\n): SplatLoadError {\n if (error instanceof SplatLoadError) return error;\n const message = error instanceof Error ? error.message : String(error);\n return new SplatLoadError(message, {\n ...options,\n retryable: isRetryableFailure(options.status, options.phase),\n cause: error,\n });\n}\n\n/** Converts a structured load failure into worker-safe data. */\nexport function serializeSplatLoadError(error: SplatLoadError): SerializedSplatLoadError {\n return {\n name: error.name,\n message: error.message,\n phase: error.phase,\n url: error.url,\n ...(error.status === undefined ? {} : { status: error.status }),\n retryable: error.retryable,\n };\n}\n\n/** Reconstructs a package error received from a loading worker. */\nexport function deserializeSplatLoadError(error: SerializedSplatLoadError): SplatLoadError {\n return new SplatLoadError(error.message, error);\n}\n\n/** Monotonic time where available, with an SSR-safe fallback. */\nexport function monotonicNow(): number {\n return globalThis.performance?.now() ?? Date.now();\n}\n\nfunction isRetryableFailure(status: number | undefined, phase: SplatLoadPhase): boolean {\n if (status !== undefined)\n return status === 408 || status === 425 || status === 429 || status >= 500;\n return phase === 'fetch' || phase === 'manifest' || phase === 'worker';\n}\n"],"names":["SplatLoadError","message","options","__publicField","resolveSplatUrl","input","baseUrl","_a","fallbackBase","cause","splatUrlExtension","url","splatNameExtension","name","lower","dot","splatFormatForExtension","extension","label","toRequestInit","signal","createAbortError","error","isAbortError","toSplatLoadError","isRetryableFailure","deserializeSplatLoadError","status","phase"],"mappings":";;;AAiHO,MAAMA,UAAuB,MAAM;AAAA,EAMxC,YACEC,GACAC,GAOA;AACA,UAAMD,GAAS,EAAE,OAAOC,EAAQ,OAAO;AAfhC,IAAAC,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AAaP,SAAK,OAAO,kBACZ,KAAK,QAAQD,EAAQ,OACrB,KAAK,MAAMA,EAAQ,KACnB,KAAK,SAASA,EAAQ,QACtB,KAAK,YAAYA,EAAQ;AAAA,EAC3B;AACF;AAGO,SAASE,EAAgBC,GAAqBC,GAA6B;AA1B3E,MAAAC;AA2BL,MAAIF,aAAiB,IAAK,QAAO,IAAI,IAAIA,EAAM,IAAI;AACnD,MAAI;AACF,UAAMG,IAAeF,OAAWC,IAAA,WAAW,aAAX,gBAAAA,EAAqB;AACrD,WAAIC,MAAiB,SAAkB,IAAI,IAAIH,CAAK,IAC7C,IAAI,IAAIA,GAAOG,CAAY;AAAA,EACpC,SAASC,GAAO;AACd,UAAM,IAAIT,EAAe,gCAAgCK,CAAK,MAAM;AAAA,MAClE,OAAO;AAAA,MACP,KAAKA;AAAA,MACL,WAAW;AAAA,MACX,OAAAI;AAAA,IAAA,CACD;AAAA,EACH;AACF;AAGO,SAASC,EAAkBC,GAAkB;AAClD,SAAOC,EAAmBD,EAAI,QAAQ;AACxC;AAGO,SAASC,EAAmBC,GAAsB;AACvD,QAAMC,IAAQD,EAAK,YAAA,GACbE,IAAMD,EAAM,YAAY,GAAG;AACjC,SAAOC,IAAM,IAAI,KAAKD,EAAM,MAAMC,CAAG;AACvC;AAUO,SAASC,EAAwBC,GAAmBC,GAAkC;AAC3F,UAAQD,GAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,YAAM,IAAIjB,EAAe,uCAAuCkB,CAAK,KAAK;AAAA,QACxE,OAAO;AAAA,QACP,KAAKA;AAAA,QACL,WAAW;AAAA,MAAA,CACZ;AAAA,EAAA;AAEP;AAGO,SAASC,EAAcjB,GAA+BkB,GAAmC;AAC9F,SAAO;AAAA,IACL,GAAIlB,KAAA,QAAAA,EAAS,UAAU,EAAE,SAAS,EAAE,GAAGA,EAAQ,QAAA,EAAQ,IAAM,CAAA;AAAA,IAC7D,GAAIA,KAAA,QAAAA,EAAS,cAAc,EAAE,aAAaA,EAAQ,YAAA,IAAgB,CAAA;AAAA,IAClE,GAAIA,KAAA,QAAAA,EAAS,OAAO,EAAE,MAAMA,EAAQ,KAAA,IAAS,CAAA;AAAA,IAC7C,GAAIA,KAAA,QAAAA,EAAS,QAAQ,EAAE,OAAOA,EAAQ,MAAA,IAAU,CAAA;AAAA,IAChD,GAAIkB,IAAS,EAAE,QAAAA,MAAW,CAAA;AAAA,EAAC;AAE/B;AAkDO,SAASC,EAAiBpB,GAAwB;AACvD,MAAI,OAAO,eAAiB,YAAoB,IAAI,aAAaA,GAAS,YAAY;AACtF,QAAMqB,IAAQ,IAAI,MAAMrB,CAAO;AAC/B,SAAAqB,EAAM,OAAO,cACNA;AACT;AAGO,SAASC,EAAaD,GAAyB;AACpD,SAAOA,aAAiB,SAASA,EAAM,SAAS;AAClD;AAGO,SAASE,EACdF,GACApB,GACgB;AAChB,MAAIoB,aAAiBtB,EAAgB,QAAOsB;AAC5C,QAAMrB,IAAUqB,aAAiB,QAAQA,EAAM,UAAU,OAAOA,CAAK;AACrE,SAAO,IAAItB,EAAeC,GAAS;AAAA,IACjC,GAAGC;AAAA,IACH,WAAWuB,EAAmBvB,EAAQ,QAAQA,EAAQ,KAAK;AAAA,IAC3D,OAAOoB;AAAA,EAAA,CACR;AACH;AAeO,SAASI,EAA0BJ,GAAiD;AACzF,SAAO,IAAItB,EAAesB,EAAM,SAASA,CAAK;AAChD;AAOA,SAASG,EAAmBE,GAA4BC,GAAgC;AACtF,SAAID,MAAW,SACNA,MAAW,OAAOA,MAAW,OAAOA,MAAW,OAAOA,KAAU,MAClEC,MAAU,WAAWA,MAAU,cAAcA,MAAU;AAChE;"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * The self-contained scene formats - files (or bundles) that decode to a whole
3
+ * scene on their own, with no external manifest. This is the value set of
4
+ * {@link SplatData.sourceFormat} and the non-`auto` half of {@link SplatFormat}.
5
+ */
6
+ export type SplatSourceFormat = 'ply' | 'sog' | 'spz' | 'splat' | 'ksplat' | 'rad';
7
+ /** Explicit formats accepted by {@link loadScene}. */
8
+ export type SplatFormat = 'auto' | SplatSourceFormat;
9
+ /** Explicit formats accepted by {@link StreamedSplatMesh.load}. */
10
+ export type StreamedSplatFormat = 'auto' | 'streamed-sog' | 'lcc2' | 'lcc' | 'rad';
11
+ /**
12
+ * Parsers the loading worker can run on a chunk. This extends {@link SplatFormat}
13
+ * with formats that are only ever reached as a streamed chunk, never as a
14
+ * standalone scene handed to {@link loadScene}: `lcc-bin` is a byte range of an
15
+ * LCC (`.lcc`) `data.bin`, and `rad-chunk` a byte range of a single-file `.rad` -
16
+ * both meaningless without their manifest/header.
17
+ */
18
+ export type ChunkFileFormat = Exclude<SplatFormat, 'auto'> | 'lcc-bin' | 'rad-chunk';
19
+ /** Serializable fetch settings that can cross the loading-worker boundary. */
20
+ export interface SplatRequestOptions {
21
+ headers?: Readonly<Record<string, string>>;
22
+ credentials?: RequestCredentials;
23
+ mode?: RequestMode;
24
+ cache?: RequestCache;
25
+ }
26
+ /**
27
+ * Reports load progress in bytes, for a determinate progress indicator.
28
+ *
29
+ * `total` is 0 when the size is not known ahead of time (a response with no
30
+ * `Content-Length`), which means "still working" rather than "finished".
31
+ */
32
+ export type SplatProgressCallback = (loaded: number, total: number) => void;
33
+ /**
34
+ * Rate-limits progress reports to ~10/s.
35
+ *
36
+ * A body arrives in ~64 KB chunks, so an unthrottled report would post
37
+ * thousands of messages no indicator can render. The final report is exempt -
38
+ * it is what leaves a determinate bar exactly full rather than at 97%.
39
+ *
40
+ * Identifying that final report needs a known size, so the exemption requires
41
+ * `total > 0`. When the size is unknown there is no bar to fill (the caller
42
+ * shows a spinner) and every report is throttled alike - without the `total`
43
+ * check, `loaded < total` would be false for all of them and the throttle
44
+ * would never engage at all.
45
+ */
46
+ export declare function createProgressThrottle(report: SplatProgressCallback, intervalMs?: number): SplatProgressCallback;
47
+ /**
48
+ * Options for decoding a local file.
49
+ *
50
+ * Shared by {@link loadSceneFile} and `ChunkLoader.loadFile`. There is no
51
+ * `baseUrl`/`request` counterpart to {@link SplatInputOptions} here: a file has
52
+ * no URL to resolve and no request to configure.
53
+ */
54
+ export interface SplatFileLoadOptions {
55
+ signal?: AbortSignal;
56
+ /** Parser selection; the file name's extension is used only for `auto`. */
57
+ format?: SplatFormat;
58
+ /**
59
+ * Called as the file is read, for a determinate progress bar. A multi-
60
+ * gigabyte PLY takes tens of seconds, so the streamed PLY path reports as it
61
+ * goes; the whole-buffer formats have nothing to report until they are done.
62
+ */
63
+ onProgress?: SplatProgressCallback;
64
+ }
65
+ /** Shared URL, cancellation, and request options for package loaders. */
66
+ export interface SplatInputOptions {
67
+ signal?: AbortSignal;
68
+ baseUrl?: string | URL;
69
+ request?: SplatRequestOptions;
70
+ /**
71
+ * Called as bytes are read. Fires from the loading worker, so it is
72
+ * throttled to whole read windows rather than every byte.
73
+ */
74
+ onProgress?: SplatProgressCallback;
75
+ }
76
+ /** The stage at which a splat load failed. */
77
+ export type SplatLoadPhase = 'resolve' | 'manifest' | 'fetch' | 'decode' | 'worker';
78
+ /** Serializable error details returned by the loading worker. */
79
+ export interface SerializedSplatLoadError {
80
+ name: string;
81
+ message: string;
82
+ phase: SplatLoadPhase;
83
+ url: string;
84
+ status?: number;
85
+ retryable: boolean;
86
+ }
87
+ /** Structured error thrown for non-cancellation loading failures. */
88
+ export declare class SplatLoadError extends Error {
89
+ readonly phase: SplatLoadPhase;
90
+ readonly url: string;
91
+ readonly status?: number;
92
+ readonly retryable: boolean;
93
+ constructor(message: string, options: {
94
+ phase: SplatLoadPhase;
95
+ url: string;
96
+ status?: number;
97
+ retryable: boolean;
98
+ cause?: unknown;
99
+ });
100
+ }
101
+ /** Resolves a loader input without assuming that a browser location exists. */
102
+ export declare function resolveSplatUrl(input: string | URL, baseUrl?: string | URL): URL;
103
+ /** Returns the lower-case pathname extension, excluding query/hash text. */
104
+ export declare function splatUrlExtension(url: URL): string;
105
+ /** Returns the lower-case extension of a plain file name or path. */
106
+ export declare function splatNameExtension(name: string): string;
107
+ /**
108
+ * Maps a file extension to its parser. `label` names the input in the
109
+ * failure message - a URL for a fetched scene, a file name for a local one.
110
+ *
111
+ * @throws {SplatLoadError} (phase `'resolve'`, not retryable) for an unknown
112
+ * extension, keeping the loader contract uniform: callers of the loading
113
+ * pipeline only ever see `SplatLoadError` or `AbortError`.
114
+ */
115
+ export declare function splatFormatForExtension(extension: string, label: string): SplatSourceFormat;
116
+ /** Creates a fetch init object without sharing mutable header state. */
117
+ export declare function toRequestInit(options?: SplatRequestOptions, signal?: AbortSignal): RequestInit;
118
+ /**
119
+ * The largest `ArrayBuffer` a browser will allocate: 2 GiB, in every current
120
+ * engine. A file bigger than this cannot be read in one piece at all - the
121
+ * read fails rather than the decode.
122
+ */
123
+ export declare const MAX_ARRAY_BUFFER_BYTES: number;
124
+ /**
125
+ * Reads a whole local file, turning the browser's two unhelpful failure modes
126
+ * into messages that name the real cause.
127
+ *
128
+ * Chrome reports an over-2 GiB read as `NotReadableError`, whose stock text
129
+ * blames file permissions - so a perfectly good 4 GB capture reads as "your
130
+ * file is broken". Check the size up front instead.
131
+ *
132
+ * Formats with a streaming parser (see `parseSplatPlyFile`) never come here.
133
+ */
134
+ export declare function readWholeFile(file: File): Promise<ArrayBuffer>;
135
+ /** Creates an AbortError even where DOMException is unavailable. */
136
+ export declare function createAbortError(message: string): Error;
137
+ /** Whether an unknown failure represents cancellation. */
138
+ export declare function isAbortError(error: unknown): boolean;
139
+ /** Maps an arbitrary failure to the package's structured loading error. */
140
+ export declare function toSplatLoadError(error: unknown, options: {
141
+ phase: SplatLoadPhase;
142
+ url: string;
143
+ status?: number;
144
+ }): SplatLoadError;
145
+ /** Converts a structured load failure into worker-safe data. */
146
+ export declare function serializeSplatLoadError(error: SplatLoadError): SerializedSplatLoadError;
147
+ /** Reconstructs a package error received from a loading worker. */
148
+ export declare function deserializeSplatLoadError(error: SerializedSplatLoadError): SplatLoadError;
149
+ /** Monotonic time where available, with an SSR-safe fallback. */
150
+ export declare function monotonicNow(): number;
@@ -0,0 +1,61 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import type { SplatDatasetSource } from './dataset-source';
3
+ /**
4
+ * Parser for the Streamed SOG manifest (`lod-meta.json`, version 1).
5
+ *
6
+ * The manifest describes a large scene as a binary spatial tree whose leaves
7
+ * each cover one region at several levels of detail. LOD level 0 is the
8
+ * finest; higher levels are progressively coarser. Each leaf references, per
9
+ * level, a contiguous `[offset, offset + count)` splat range inside one chunk
10
+ * file (an unbundled SOG v2 directory). One chunk file serves many leaves.
11
+ *
12
+ * Spec:
13
+ * https://developer.playcanvas.com/user-manual/gaussian-splatting/formats/streamed-sog/
14
+ */
15
+ /** A leaf's splat range at one LOD level. */
16
+ export interface LodRange {
17
+ /** Index into {@link LodManifest.chunkUrls}. */
18
+ readonly file: number;
19
+ /** First splat row within that chunk's decoded arrays. */
20
+ readonly offset: number;
21
+ /** Number of splats. */
22
+ readonly count: number;
23
+ }
24
+ /** A spatial region, present at one or more LOD levels. */
25
+ export interface LodLeaf {
26
+ readonly bounds: THREE.Box3;
27
+ /** Range per level; `lods[level]` is undefined if absent at that level. */
28
+ readonly lods: readonly (LodRange | undefined)[];
29
+ /**
30
+ * Leaves with the same group are budgeted atomically. Formats may use this
31
+ * when one spatial region is split into several independently streamed
32
+ * ranges: the ranges may arrive separately, but must select one LOD cut.
33
+ */
34
+ readonly budgetGroup?: number;
35
+ }
36
+ export interface LodManifest {
37
+ /** Leaves in tree-traversal order (load-bearing: adjacent leaves within a
38
+ * chunk have contiguous ranges, which the scheduler coalesces into runs). */
39
+ readonly leaves: readonly LodLeaf[];
40
+ /** Absolute chunk-directory URLs, indexed by {@link LodRange.file}. */
41
+ readonly chunkUrls: readonly string[];
42
+ /**
43
+ * Chunk directories relative to the manifest, aligned with {@link chunkUrls}.
44
+ * Only the unbundled-SOG layout has them; formats whose chunks are single
45
+ * files (LCC of either generation) leave this out.
46
+ */
47
+ readonly chunkDirectories?: readonly string[];
48
+ /** Total splats per LOD level (index = level). */
49
+ readonly counts: readonly number[];
50
+ readonly lodLevels: number;
51
+ /** Root bounding box of the whole scene. */
52
+ readonly bounds: THREE.Box3;
53
+ }
54
+ /**
55
+ * Parses a `lod-meta.json` object into a flat, render-ready manifest.
56
+ *
57
+ * @param json - The parsed manifest JSON.
58
+ * @param baseUrl - URL of the manifest, used to resolve chunk directories.
59
+ * @throws {Error} on an unsupported version or malformed tree.
60
+ */
61
+ export declare function parseLodManifest(json: unknown, source: SplatDatasetSource): LodManifest;