@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.
Files changed (90) 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 +11 -10
  11. package/dist/effects.js.map +1 -1
  12. package/dist/formats/ksplat/index.d.ts +1 -1
  13. package/dist/formats/lcc/collision-mesh.d.ts +2 -2
  14. package/dist/formats/lcc.js +17 -16
  15. package/dist/formats/lcc.js.map +1 -1
  16. package/dist/formats/ply/index.d.ts +1 -1
  17. package/dist/formats/ply/parse-splat-ply.d.ts +2 -2
  18. package/dist/formats/ply.js +1 -1
  19. package/dist/formats/ply.js.map +1 -1
  20. package/dist/formats/rad/index.d.ts +1 -1
  21. package/dist/formats/rad/parse-rad.d.ts +1 -1
  22. package/dist/formats/rad/rad.d.ts +2 -2
  23. package/dist/formats/rad.js +3 -3
  24. package/dist/formats/rad.js.map +1 -1
  25. package/dist/formats/sog/index.d.ts +1 -1
  26. package/dist/formats/sog/parse-sog.d.ts +4 -4
  27. package/dist/formats/sog.js.map +1 -1
  28. package/dist/formats/splat/index.d.ts +1 -1
  29. package/dist/formats/spz/index.d.ts +1 -1
  30. package/dist/index.d.ts +16 -25
  31. package/dist/index.js +253 -9047
  32. package/dist/index.js.map +1 -1
  33. package/dist/{load-scene.d.ts → load-splat-data.d.ts} +15 -13
  34. package/dist/loaders.d.ts +15 -0
  35. package/dist/loaders.js +26 -0
  36. package/dist/loaders.js.map +1 -0
  37. package/dist/loading-wLo8vRbA.js.map +1 -1
  38. package/dist/loading.d.ts +8 -8
  39. package/dist/{splat-mesh-types-CTN_duw2.js → lod-scheduler-B0a_uBlv.js} +133 -140
  40. package/dist/lod-scheduler-B0a_uBlv.js.map +1 -0
  41. package/dist/lod-scheduler.d.ts +1 -1
  42. package/dist/lod-source.d.ts +5 -5
  43. package/dist/{splat-scene.d.ts → merged-splat-mesh.d.ts} +14 -15
  44. package/dist/orientation.d.ts +7 -7
  45. package/dist/{ply-header-BwgExCn-.js → ply-header-B9WllbPg.js} +3 -3
  46. package/dist/{ply-header-BwgExCn-.js.map → ply-header-B9WllbPg.js.map} +1 -1
  47. package/dist/radix-sorter-R0FCSqKu.js +178 -0
  48. package/dist/radix-sorter-R0FCSqKu.js.map +1 -0
  49. package/dist/relighting-CSQF3UcL.js +969 -0
  50. package/dist/relighting-CSQF3UcL.js.map +1 -0
  51. package/dist/relighting.d.ts +1 -1
  52. package/dist/selection.d.ts +13 -0
  53. package/dist/selection.js +137 -0
  54. package/dist/selection.js.map +1 -0
  55. package/dist/source-transform.d.ts +4 -4
  56. package/dist/{splat-budget-DXew_86c.js → splat-budget-DuVZspPQ.js} +7 -7
  57. package/dist/{splat-budget-DXew_86c.js.map → splat-budget-DuVZspPQ.js.map} +1 -1
  58. package/dist/splat-budget.d.ts +1 -1
  59. package/dist/splat-data-BZM5dhhx.js.map +1 -1
  60. package/dist/splat-data.d.ts +4 -4
  61. package/dist/splat-mesh-aQdcWA6v.js +1942 -0
  62. package/dist/splat-mesh-aQdcWA6v.js.map +1 -0
  63. package/dist/splat-mesh-material.d.ts +2 -2
  64. package/dist/splat-mesh-types-BZIko-_9.js +18 -0
  65. package/dist/splat-mesh-types-BZIko-_9.js.map +1 -0
  66. package/dist/splat-mesh-types.d.ts +14 -8
  67. package/dist/splat-mesh.d.ts +4 -4
  68. package/dist/splat-modifier.d.ts +5 -5
  69. package/dist/splat-partition.d.ts +1 -1
  70. package/dist/static-lod-entry.d.ts +12 -0
  71. package/dist/static-lod-splat-mesh.d.ts +2 -2
  72. package/dist/{static-lod-worker-D2fyqQZO.js → static-lod-worker-DktKnJkR.js} +46 -46
  73. package/dist/{static-lod-worker-D2fyqQZO.js.map → static-lod-worker-DktKnJkR.js.map} +1 -1
  74. package/dist/static-lod.js +148 -0
  75. package/dist/static-lod.js.map +1 -0
  76. package/dist/streamed-splat-mesh.d.ts +21 -27
  77. package/dist/streaming.d.ts +23 -0
  78. package/dist/streaming.js +2877 -0
  79. package/dist/streaming.js.map +1 -0
  80. package/dist/{unified-splat-renderer.d.ts → unified-splat-mesh.d.ts} +10 -10
  81. package/dist/unified-work-buffer.d.ts +66 -0
  82. package/dist/unified.d.ts +11 -0
  83. package/dist/unified.js +738 -0
  84. package/dist/unified.js.map +1 -0
  85. package/dist/webgpu-limits-Y3CI_6_6.js +43 -0
  86. package/dist/webgpu-limits-Y3CI_6_6.js.map +1 -0
  87. package/dist/webgpu-limits.d.ts +0 -59
  88. package/dist/work-buffer-gather.d.ts +1 -1
  89. package/package.json +24 -4
  90. package/dist/splat-mesh-types-CTN_duw2.js.map +0 -1
@@ -2,7 +2,7 @@
2
2
  * `@voluma/vlam/formats/sog` - PlayCanvas SOG (v2) decoder, for both the
3
3
  * bundled `.sog` ZIP and the unbundled directory form Streamed SOG chunks use.
4
4
  *
5
- * Loading through {@link loadScene} / {@link StreamedSplatMesh.load} does not
5
+ * Loading through {@link loadSplatData} / {@link StreamedSplatMesh.load} does not
6
6
  * require this import; the loading worker already knows this format. Use this
7
7
  * subpath for direct decode outside the default loaders.
8
8
  */
@@ -10,8 +10,8 @@ import { type SplatRequestOptions } from '../../loading';
10
10
  * a `DOMException` named `AbortError` when cancelled.
11
11
  *
12
12
  * A direct parser call sits outside the `SplatLoadError`-or-`AbortError`
13
- * contract of the worker-mediated loaders ({@link loadScene},
14
- * {@link loadSceneFile}, `ChunkLoader`, `StreamedSplatMesh`): malformed input
13
+ * contract of the worker-mediated loaders ({@link loadSplatData},
14
+ * {@link loadSplatDataFile}, `ChunkLoader`, `StreamedSplatMesh`): malformed input
15
15
  * surfaces here as a plain `Error`.
16
16
  */
17
17
  export declare function parseSog(buffer: ArrayBuffer, options?: {
@@ -29,8 +29,8 @@ export declare function parseSog(buffer: ArrayBuffer, options?: {
29
29
  * a `DOMException` named `AbortError` when cancelled.
30
30
  *
31
31
  * A direct parser call sits outside the `SplatLoadError`-or-`AbortError`
32
- * contract of the worker-mediated loaders ({@link loadScene},
33
- * {@link loadSceneFile}, `ChunkLoader`, `StreamedSplatMesh`): malformed input
32
+ * contract of the worker-mediated loaders ({@link loadSplatData},
33
+ * {@link loadSplatDataFile}, `ChunkLoader`, `StreamedSplatMesh`): malformed input
34
34
  * surfaces here as a plain `Error`.
35
35
  */
36
36
  export declare function parseSogDirectory(baseUrl: string, options?: {
@@ -1 +1 @@
1
- {"version":3,"file":"sog.js","sources":["../../src/lib/formats/sog/parse-sog.ts"],"sourcesContent":["import { SH_C0, writeCovariance, type SplatData, type SplatShData } from '../../splat-data';\nimport {\n isAbortError,\n toRequestInit,\n toSplatLoadError,\n type SplatRequestOptions,\n} from '../../loading';\n\n/**\n * Decoder for PlayCanvas's SOG format (v2), Voluma's canonical delivery\n * format: `meta.json` plus WebP-encoded attribute images, delivered either\n * as a ZIP bundle (`.sog` file) or as an unbundled directory (the form\n * Streamed SOG chunks use).\n *\n * Spec: https://developer.playcanvas.com/user-manual/gaussian-splatting/formats/sog/\n * (an open specification; PlayCanvas explicitly invites third-party\n * implementations). The decoder below is an original implementation of that\n * spec. Splat i lives at pixel (i % width, ⌊i / width⌋) in every image.\n *\n * Note: this prototype decodes on the CPU for simplicity. SOG is designed\n * to be GPU-ready - a later optimization is uploading the WebP images as\n * textures and dequantizing in the shader, as PlayCanvas does.\n */\n\n/** meta.json schema (the subset this decoder needs). */\ninterface SogMeta {\n version: number;\n count: number;\n /** Optional: the scene was exported with antialiasing (Mip-Splatting). */\n antialias?: boolean;\n means: { mins: [number, number, number]; maxs: [number, number, number]; files: string[] };\n scales: { codebook: number[]; files: string[] };\n quats: { files: string[] };\n sh0: { codebook: number[]; files: string[] };\n shN?: { count: number; bands: number; codebook: number[]; files: string[] };\n}\n\ninterface DecodedImage {\n width: number;\n height: number;\n /** RGBA bytes, row-major. */\n data: Uint8Array;\n}\n\n/**\n * Upper bound on `meta.count` before any allocation happens. The renderer\n * cannot draw more anyway: 2048-wide pool textures cap at ~16.7M splats\n * (ROADMAP M4.2) and the float32 `splatIndex` is exact only up to 2²⁴, so\n * anything larger in an untrusted meta.json is rejected rather than\n * ballooning into a multi-gigabyte allocation.\n */\nconst MAX_SPLAT_COUNT = 1 << 24;\n\n/**\n * Parses a bundled `.sog` file into flat, GPU-friendly arrays, including\n * the palette-compressed higher-order SH coefficients (`shN`) if present.\n *\n * @param options.signal - Optional abort signal; the decode stages check it\n * between steps and inside the dequantization loop.\n * @throws {Error} if the archive or its `meta.json` is malformed;\n * a `DOMException` named `AbortError` when cancelled.\n *\n * A direct parser call sits outside the `SplatLoadError`-or-`AbortError`\n * contract of the worker-mediated loaders ({@link loadScene},\n * {@link loadSceneFile}, `ChunkLoader`, `StreamedSplatMesh`): malformed input\n * surfaces here as a plain `Error`.\n */\nexport async function parseSog(\n buffer: ArrayBuffer,\n options: { signal?: AbortSignal } = {},\n): Promise<SplatData> {\n const archive = readZip(buffer);\n return parseSogFromEntries(async (name) => {\n const entry = archive.get(name);\n if (!entry) throw new Error(`SOG bundle is missing \"${name}\".`);\n return entry();\n }, options.signal);\n}\n\n/**\n * Parses an unbundled SOG directory (`meta.json` + WebP files fetched\n * individually) into flat, GPU-friendly arrays - the layout Streamed SOG\n * chunks use.\n *\n * @param baseUrl - Absolute URL of the directory containing `meta.json`.\n * @param options.signal - Optional abort signal; in-flight fetches are\n * cancelled when it fires, and the decode stages check it between steps.\n * @throws {Error} on fetch failures or a malformed `meta.json`;\n * a `DOMException` named `AbortError` when cancelled.\n *\n * A direct parser call sits outside the `SplatLoadError`-or-`AbortError`\n * contract of the worker-mediated loaders ({@link loadScene},\n * {@link loadSceneFile}, `ChunkLoader`, `StreamedSplatMesh`): malformed input\n * surfaces here as a plain `Error`.\n */\nexport async function parseSogDirectory(\n baseUrl: string,\n options: {\n signal?: AbortSignal;\n request?: SplatRequestOptions;\n /**\n * The directory's files as `name → URL`, for a chunk that has no\n * directory URL to resolve against - a folder dropped into the page,\n * whose files are reached through `blob:` URLs.\n */\n files?: Readonly<Record<string, string>>;\n } = {},\n): Promise<SplatData> {\n const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n return parseSogFromEntries(async (name) => {\n assertPlainRelativeName(name);\n const mapped = options.files?.[name];\n if (options.files && mapped === undefined) {\n throw new Error(`SOG chunk is missing \"${name}\".`);\n }\n const url = mapped === undefined ? new URL(name, base) : new URL(mapped);\n let response: Response;\n try {\n response = await fetch(url, toRequestInit(options.request, options.signal));\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw toSplatLoadError(error, { phase: 'fetch', url: url.href });\n }\n if (!response.ok) {\n throw toSplatLoadError(new Error(`Failed to load ${url.href}: HTTP ${response.status}`), {\n phase: 'fetch',\n url: url.href,\n status: response.status,\n });\n }\n return new Uint8Array(await response.arrayBuffer());\n }, options.signal);\n}\n\n/**\n * Shared decode core; `readEntry` supplies file contents by name. The\n * abort signal is checked between decode stages so a cancellation skips\n * the expensive dequantization even when all fetches already finished.\n */\nasync function parseSogFromEntries(\n readEntry: (name: string) => Promise<Uint8Array>,\n signal?: AbortSignal,\n): Promise<SplatData> {\n const meta = JSON.parse(new TextDecoder().decode(await readEntry('meta.json'))) as SogMeta;\n validateMeta(meta);\n signal?.throwIfAborted();\n\n const [meansL, meansU, quats, scales, sh0] = await Promise.all([\n readEntry(meta.means.files[0] ?? 'means_l.webp').then(decodeWebp),\n readEntry(meta.means.files[1] ?? 'means_u.webp').then(decodeWebp),\n readEntry(meta.quats.files[0] ?? 'quats.webp').then(decodeWebp),\n readEntry(meta.scales.files[0] ?? 'scales.webp').then(decodeWebp),\n readEntry(meta.sh0.files[0] ?? 'sh0.webp').then(decodeWebp),\n ]);\n signal?.throwIfAborted();\n\n const count = meta.count;\n assertImageHoldsCount(meansL, 'means_l', count);\n assertImageHoldsCount(meansU, 'means_u', count);\n assertImageHoldsCount(quats, 'quats', count);\n assertImageHoldsCount(scales, 'scales', count);\n assertImageHoldsCount(sh0, 'sh0', count);\n const positions = new Float32Array(count * 3);\n const colors = new Uint8Array(count * 4);\n const covariances = new Float32Array(count * 6);\n\n // Positions: 16 bit per axis, normalized over [mins, maxs] in a\n // symmetric-log domain: unlog(n) = sign(n)·(eⁿ − 1).\n const { mins, maxs } = meta.means;\n const unlog = (n: number): number => Math.sign(n) * (Math.exp(Math.abs(n)) - 1);\n\n // Quaternions: \"smallest three\" encoding. RGB hold three components in\n // [-√½, +√½]; alpha − 252 says which component was omitted (the largest,\n // reconstructed from unit length). Component order is (w, x, y, z).\n const quatComponent = (byte: number): number => (byte / 255 - 0.5) * Math.SQRT2;\n\n const toColorByte = (sh: number): number =>\n Math.max(0, Math.min(255, Math.round((0.5 + sh * SH_C0) * 255)));\n\n for (let i = 0; i < count; i++) {\n // The dequantization loop dominates decode time. With a signal, yield\n // to the event loop every 65,536 splats so a pending cancel message\n // can be processed at all (the worker is single-threaded), then honor\n // it. Without a signal this branch never runs - no overhead.\n if (signal && (i & 0xffff) === 0 && i > 0) {\n await new Promise((resolve) => setTimeout(resolve, 0));\n signal.throwIfAborted();\n }\n const p = i * 4; // Every attribute image shares the same pixel layout.\n\n for (let axis = 0; axis < 3; axis++) {\n const quantized =\n (((meansU.data[p + axis] as number) << 8) | (meansL.data[p + axis] as number)) / 65535;\n const lo = mins[axis] as number;\n const hi = maxs[axis] as number;\n positions[i * 3 + axis] = unlog(lo + (hi - lo) * quantized);\n }\n\n // SH0: RGB are codebook indices for the DC term; alpha is the opacity,\n // stored already activated (no sigmoid, unlike PLY).\n colors[p + 0] = toColorByte(meta.sh0.codebook[sh0.data[p + 0] as number] as number);\n colors[p + 1] = toColorByte(meta.sh0.codebook[sh0.data[p + 1] as number] as number);\n colors[p + 2] = toColorByte(meta.sh0.codebook[sh0.data[p + 2] as number] as number);\n colors[p + 3] = sh0.data[p + 3] as number;\n\n const a = quatComponent(quats.data[p + 0] as number);\n const b = quatComponent(quats.data[p + 1] as number);\n const c = quatComponent(quats.data[p + 2] as number);\n const d = Math.sqrt(Math.max(0, 1 - (a * a + b * b + c * c)));\n const mode = (quats.data[p + 3] as number) - 252;\n const [qw, qx, qy, qz] =\n mode === 0\n ? [d, a, b, c]\n : mode === 1\n ? [a, d, b, c]\n : mode === 2\n ? [a, b, d, c]\n : [a, b, c, d];\n\n writeCovariance(\n covariances,\n i,\n Math.exp(meta.scales.codebook[scales.data[p + 0] as number] as number),\n Math.exp(meta.scales.codebook[scales.data[p + 1] as number] as number),\n Math.exp(meta.scales.codebook[scales.data[p + 2] as number] as number),\n qw,\n qx,\n qy,\n qz,\n );\n }\n\n signal?.throwIfAborted();\n const sh = meta.shN ? await decodeShN(meta.shN, count, readEntry) : undefined;\n return { count, positions, colors, covariances, sh, antialias: meta.antialias === true };\n}\n\n/**\n * Validates the untrusted `meta.json` before anything is allocated or\n * fetched based on it: the splat count must be a sane non-negative integer\n * and every codebook must resolve all 256 possible byte indices to finite\n * numbers (image bytes index them unchecked in the hot loop).\n */\nfunction validateMeta(meta: SogMeta): void {\n if (meta.version !== 2) throw new Error(`Unsupported SOG version: ${meta.version}.`);\n if (!Number.isSafeInteger(meta.count) || meta.count < 0) {\n throw new Error(`SOG meta.json declares an invalid splat count: ${meta.count}.`);\n }\n if (meta.count > MAX_SPLAT_COUNT) {\n throw new Error(\n `SOG meta.json declares ${meta.count} splats, above the supported maximum of ${MAX_SPLAT_COUNT}.`,\n );\n }\n validateVector3(meta.means?.mins, 'means.mins');\n validateVector3(meta.means?.maxs, 'means.maxs');\n validateCodebook(meta.scales?.codebook, 'scales');\n validateCodebook(meta.sh0?.codebook, 'sh0');\n if (meta.shN) {\n validateCodebook(meta.shN.codebook, 'shN');\n const bands = meta.shN.bands;\n if (!Number.isInteger(bands) || bands < 1 || bands > 3) {\n throw new Error(`SOG meta.json declares an invalid shN band count: ${bands}.`);\n }\n }\n}\n\n/** Asserts a meta.json vector is three finite numbers. */\nfunction validateVector3(vector: unknown, label: string): void {\n if (\n !Array.isArray(vector) ||\n vector.length !== 3 ||\n vector.some((value) => typeof value !== 'number' || !Number.isFinite(value))\n ) {\n throw new Error(`SOG meta.json: \"${label}\" must be an array of 3 finite numbers.`);\n }\n}\n\n/** Asserts a codebook covers every byte index with a finite number. */\nfunction validateCodebook(codebook: number[] | undefined, label: string): void {\n if (!Array.isArray(codebook) || codebook.length < 256) {\n throw new Error(`SOG meta.json: the \"${label}\" codebook must contain at least 256 entries.`);\n }\n for (const value of codebook) {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`SOG meta.json: the \"${label}\" codebook contains a non-finite entry.`);\n }\n }\n}\n\n/** Asserts an attribute image has at least one pixel per declared splat. */\nfunction assertImageHoldsCount(image: DecodedImage, label: string, count: number): void {\n if (count > image.width * image.height) {\n throw new Error(\n `SOG \"${label}\" image (${image.width}×${image.height}) holds fewer than the declared ${count} splats.`,\n );\n }\n}\n\n/**\n * Rejects file names from `meta.json` that would resolve outside the SOG\n * directory - absolute URLs, scheme-relative or root-relative paths, and\n * `..` traversal - so a hostile manifest cannot redirect worker fetches.\n */\nfunction assertPlainRelativeName(name: string): void {\n const escapes =\n typeof name !== 'string' ||\n name.length === 0 ||\n /^[a-z][a-z0-9+.-]*:/i.test(name) ||\n name.startsWith('/') ||\n name.includes('\\\\') ||\n name.split('/').some((segment) => segment === '..');\n if (escapes) {\n throw new Error(`SOG meta.json references a file outside its directory: \"${name}\".`);\n }\n}\n\n/**\n * Decodes the palette-compressed higher-order SH data: per-splat 16-bit\n * labels plus a centroids image whose bytes are codebook indices. The\n * centroids keep their SOG grid layout so they can be uploaded as-is.\n */\nasync function decodeShN(\n meta: NonNullable<SogMeta['shN']>,\n count: number,\n readEntry: (name: string) => Promise<Uint8Array>,\n): Promise<SplatShData> {\n const [centroids, labelsImage] = await Promise.all([\n readEntry(meta.files[0] ?? 'shN_centroids.webp').then(decodeWebp),\n readEntry(meta.files[1] ?? 'shN_labels.webp').then(decodeWebp),\n ]);\n assertImageHoldsCount(labelsImage, 'shN labels', count);\n\n // A label indexes a palette entry laid out 64 entries per row, each entry\n // `coeffs` texels wide. Bound every decoded label against the centroids\n // image so a hostile labels image cannot address texels outside it.\n const coeffs = meta.bands === 1 ? 3 : meta.bands === 2 ? 8 : 15;\n const labels = new Uint32Array(count);\n for (let i = 0; i < count; i++) {\n const label =\n (labelsImage.data[i * 4] as number) | ((labelsImage.data[i * 4 + 1] as number) << 8);\n const row = Math.floor(label / 64);\n const column0 = (label % 64) * coeffs;\n if (row >= centroids.height || column0 + coeffs > centroids.width) {\n throw new Error(\n `SOG shN label ${label} (splat ${i}) lies outside the ` +\n `${centroids.width}×${centroids.height} centroids palette.`,\n );\n }\n labels[i] = label;\n }\n\n const palette = new Float32Array(centroids.width * centroids.height * 4);\n for (let i = 0; i < centroids.width * centroids.height; i++) {\n palette[i * 4 + 0] = meta.codebook[centroids.data[i * 4 + 0] as number] as number;\n palette[i * 4 + 1] = meta.codebook[centroids.data[i * 4 + 1] as number] as number;\n palette[i * 4 + 2] = meta.codebook[centroids.data[i * 4 + 2] as number] as number;\n }\n\n return {\n bands: meta.bands,\n labels,\n palette,\n paletteWidth: centroids.width,\n paletteHeight: centroids.height,\n };\n}\n\n/**\n * Minimal ZIP reader: returns a map from entry name to a function that\n * produces the entry's bytes. Supports the two methods that occur in\n * practice - STORE (SOG bundles are stored; WebP is already compressed)\n * and DEFLATE via the browser's native DecompressionStream.\n */\nfunction readZip(buffer: ArrayBuffer): Map<string, () => Promise<Uint8Array>> {\n const view = new DataView(buffer);\n const bytes = new Uint8Array(buffer);\n\n // Find the End Of Central Directory record (its signature, scanned\n // backwards past the variable-length archive comment).\n let eocd = -1;\n const scanEnd = Math.max(0, buffer.byteLength - 65557);\n for (let offset = buffer.byteLength - 22; offset >= scanEnd; offset--) {\n if (offset < 0) break;\n if (view.getUint32(offset, true) === 0x06054b50) {\n eocd = offset;\n break;\n }\n }\n if (eocd < 0) throw new Error('Not a ZIP archive (no end-of-central-directory record).');\n\n const entryCount = view.getUint16(eocd + 10, true);\n let cursor = view.getUint32(eocd + 16, true); // Central directory offset.\n\n const entries = new Map<string, () => Promise<Uint8Array>>();\n for (let i = 0; i < entryCount; i++) {\n // Range-check the untrusted cursor before every DataView read, so a\n // hostile central-directory offset fails as the corrupt ZIP it is\n // instead of surfacing a bare RangeError.\n if (cursor < 0 || cursor + 46 > buffer.byteLength) {\n throw new Error('Corrupt ZIP central directory.');\n }\n if (view.getUint32(cursor, true) !== 0x02014b50) {\n throw new Error('Corrupt ZIP central directory.');\n }\n const method = view.getUint16(cursor + 10, true);\n const compressedSize = view.getUint32(cursor + 20, true);\n const nameLength = view.getUint16(cursor + 28, true);\n const extraLength = view.getUint16(cursor + 30, true);\n const commentLength = view.getUint16(cursor + 32, true);\n const localHeaderOffset = view.getUint32(cursor + 42, true);\n if (cursor + 46 + nameLength > buffer.byteLength) {\n throw new Error('Corrupt ZIP central directory.');\n }\n const name = new TextDecoder().decode(bytes.subarray(cursor + 46, cursor + 46 + nameLength));\n cursor += 46 + nameLength + extraLength + commentLength;\n\n entries.set(name, async () => {\n // The local header repeats name/extra with possibly different sizes.\n if (localHeaderOffset + 30 > buffer.byteLength) {\n throw new Error(`Corrupt ZIP entry \"${name}\": local header extends past the archive.`);\n }\n const localNameLength = view.getUint16(localHeaderOffset + 26, true);\n const localExtraLength = view.getUint16(localHeaderOffset + 28, true);\n const dataStart = localHeaderOffset + 30 + localNameLength + localExtraLength;\n if (dataStart + compressedSize > buffer.byteLength) {\n throw new Error(`Corrupt ZIP entry \"${name}\": data extends past the archive.`);\n }\n const compressed = bytes.subarray(dataStart, dataStart + compressedSize);\n\n if (method === 0) return compressed; // STORE\n if (method === 8) {\n // DEFLATE\n // Blob copies the viewed byte range itself; no defensive slice needed.\n const stream = new Blob([compressed])\n .stream()\n .pipeThrough(new DecompressionStream('deflate-raw'));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n }\n throw new Error(`Unsupported ZIP compression method ${method} for \"${name}\".`);\n });\n }\n return entries;\n}\n\nlet webpReadbackContext: WebGL2RenderingContext | undefined;\n\n/** Returns one worker-local WebGL2 context for consistent WebP readback. */\nfunction getWebpReadbackContext(): WebGL2RenderingContext | undefined {\n if (!('OffscreenCanvas' in globalThis) || !('createImageBitmap' in globalThis)) return undefined;\n if (webpReadbackContext && !webpReadbackContext.isContextLost()) return webpReadbackContext;\n\n const canvas = new OffscreenCanvas(1, 1);\n const context = canvas.getContext('webgl2', {\n alpha: false,\n antialias: false,\n depth: false,\n premultipliedAlpha: false,\n preserveDrawingBuffer: true,\n stencil: false,\n });\n if (!context) return undefined;\n webpReadbackContext = context;\n return context;\n}\n\n/**\n * Decodes through the same path PlayCanvas uses for SOG data textures.\n * Firefox's ImageDecoder premultiplies RGB by alpha even when conversion is\n * disabled. ImageBitmap -> WebGL keeps RGB unassociated with alpha.\n */\nasync function decodeWebpViaGpu(\n bytes: Uint8Array,\n context: WebGL2RenderingContext,\n): Promise<DecodedImage> {\n const blobBytes = bytes as Uint8Array<ArrayBuffer>;\n const bitmap = await createImageBitmap(new Blob([blobBytes], { type: 'image/webp' }), {\n colorSpaceConversion: 'none',\n premultiplyAlpha: 'none',\n });\n const texture = context.createTexture();\n const framebuffer = context.createFramebuffer();\n if (!texture || !framebuffer) {\n bitmap.close();\n throw new Error('Could not allocate a WebGL texture for SOG WebP decoding.');\n }\n\n try {\n context.bindTexture(context.TEXTURE_2D, texture);\n context.pixelStorei(context.UNPACK_FLIP_Y_WEBGL, false);\n context.pixelStorei(context.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n context.pixelStorei(context.UNPACK_COLORSPACE_CONVERSION_WEBGL, context.NONE);\n context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST);\n context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.NEAREST);\n context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, context.CLAMP_TO_EDGE);\n context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, context.CLAMP_TO_EDGE);\n context.texImage2D(\n context.TEXTURE_2D,\n 0,\n context.RGBA,\n context.RGBA,\n context.UNSIGNED_BYTE,\n bitmap,\n );\n\n context.bindFramebuffer(context.FRAMEBUFFER, framebuffer);\n context.framebufferTexture2D(\n context.FRAMEBUFFER,\n context.COLOR_ATTACHMENT0,\n context.TEXTURE_2D,\n texture,\n 0,\n );\n if (context.checkFramebufferStatus(context.FRAMEBUFFER) !== context.FRAMEBUFFER_COMPLETE) {\n throw new Error('Could not read the decoded SOG WebP texture.');\n }\n\n const data = new Uint8Array(bitmap.width * bitmap.height * 4);\n context.readPixels(\n 0,\n 0,\n bitmap.width,\n bitmap.height,\n context.RGBA,\n context.UNSIGNED_BYTE,\n data,\n );\n return { width: bitmap.width, height: bitmap.height, data };\n } finally {\n context.bindFramebuffer(context.FRAMEBUFFER, null);\n context.bindTexture(context.TEXTURE_2D, null);\n context.deleteFramebuffer(framebuffer);\n context.deleteTexture(texture);\n bitmap.close();\n }\n}\n\n/** Decodes a WebP image to raw RGBA bytes. */\nasync function decodeWebp(bytes: Uint8Array): Promise<DecodedImage> {\n const gpuContext = getWebpReadbackContext();\n if (gpuContext) return decodeWebpViaGpu(bytes, gpuContext);\n\n // WebCodecs fallback for environments without worker WebGL2. Chromium\n // preserves these data bytes; Firefox uses the GPU path above because its\n // VideoFrame copy currently premultiplies low-alpha RGB values.\n if ('ImageDecoder' in globalThis) {\n const decoder = new ImageDecoder({\n data: bytes,\n type: 'image/webp',\n colorSpaceConversion: 'none',\n });\n const { image } = await decoder.decode();\n const width = image.codedWidth;\n const height = image.codedHeight;\n const data = new Uint8Array(width * height * 4);\n await image.copyTo(data, { format: 'RGBA' });\n image.close();\n decoder.close();\n return { width, height, data };\n }\n\n // Fallback: canvas readback. The premultiply round-trip can cost ±1 bit\n // of RGB precision on low-alpha pixels; acceptable for a fallback path.\n // Blob copies the viewed byte range itself, so no defensive `.slice()` is\n // needed; the cast only bridges lib.dom's BlobPart rejecting the\n // ArrayBufferLike-generic view (these bytes never sit in a SharedArrayBuffer).\n const blobBytes = bytes as Uint8Array<ArrayBuffer>;\n const bitmap = await createImageBitmap(new Blob([blobBytes], { type: 'image/webp' }), {\n premultiplyAlpha: 'none',\n colorSpaceConversion: 'none',\n });\n const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);\n const context = canvas.getContext('2d');\n if (!context) throw new Error('Could not create a 2D canvas context.');\n context.drawImage(bitmap, 0, 0);\n const imageData = context.getImageData(0, 0, bitmap.width, bitmap.height);\n bitmap.close();\n return {\n width: imageData.width,\n height: imageData.height,\n data: new Uint8Array(imageData.data.buffer),\n };\n}\n"],"names":["MAX_SPLAT_COUNT","parseSog","buffer","options","archive","readZip","parseSogFromEntries","name","entry","parseSogDirectory","baseUrl","base","assertPlainRelativeName","mapped","_a","url","response","toRequestInit","error","isAbortError","toSplatLoadError","readEntry","signal","meta","validateMeta","meansL","meansU","quats","scales","sh0","decodeWebp","count","assertImageHoldsCount","positions","colors","covariances","mins","maxs","unlog","n","quatComponent","byte","toColorByte","sh","SH_C0","i","resolve","p","axis","quantized","lo","hi","a","b","c","d","mode","qw","qx","qy","qz","writeCovariance","decodeShN","validateVector3","_b","validateCodebook","_c","_d","bands","vector","label","value","codebook","image","segment","centroids","labelsImage","coeffs","labels","row","column0","palette","view","bytes","eocd","scanEnd","offset","entryCount","cursor","entries","method","compressedSize","nameLength","extraLength","commentLength","localHeaderOffset","localNameLength","localExtraLength","dataStart","compressed","stream","webpReadbackContext","getWebpReadbackContext","context","decodeWebpViaGpu","blobBytes","bitmap","texture","framebuffer","data","gpuContext","decoder","width","height","imageData"],"mappings":"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"}
1
+ {"version":3,"file":"sog.js","sources":["../../src/lib/formats/sog/parse-sog.ts"],"sourcesContent":["import { SH_C0, writeCovariance, type SplatData, type SplatShData } from '../../splat-data';\nimport {\n isAbortError,\n toRequestInit,\n toSplatLoadError,\n type SplatRequestOptions,\n} from '../../loading';\n\n/**\n * Decoder for PlayCanvas's SOG format (v2), Voluma's canonical delivery\n * format: `meta.json` plus WebP-encoded attribute images, delivered either\n * as a ZIP bundle (`.sog` file) or as an unbundled directory (the form\n * Streamed SOG chunks use).\n *\n * Spec: https://developer.playcanvas.com/user-manual/gaussian-splatting/formats/sog/\n * (an open specification; PlayCanvas explicitly invites third-party\n * implementations). The decoder below is an original implementation of that\n * spec. Splat i lives at pixel (i % width, ⌊i / width⌋) in every image.\n *\n * Note: this prototype decodes on the CPU for simplicity. SOG is designed\n * to be GPU-ready - a later optimization is uploading the WebP images as\n * textures and dequantizing in the shader, as PlayCanvas does.\n */\n\n/** meta.json schema (the subset this decoder needs). */\ninterface SogMeta {\n version: number;\n count: number;\n /** Optional: the scene was exported with antialiasing (Mip-Splatting). */\n antialias?: boolean;\n means: { mins: [number, number, number]; maxs: [number, number, number]; files: string[] };\n scales: { codebook: number[]; files: string[] };\n quats: { files: string[] };\n sh0: { codebook: number[]; files: string[] };\n shN?: { count: number; bands: number; codebook: number[]; files: string[] };\n}\n\ninterface DecodedImage {\n width: number;\n height: number;\n /** RGBA bytes, row-major. */\n data: Uint8Array;\n}\n\n/**\n * Upper bound on `meta.count` before any allocation happens. The renderer\n * cannot draw more anyway: 2048-wide pool textures cap at ~16.7M splats\n * (ROADMAP M4.2) and the float32 `splatIndex` is exact only up to 2²⁴, so\n * anything larger in an untrusted meta.json is rejected rather than\n * ballooning into a multi-gigabyte allocation.\n */\nconst MAX_SPLAT_COUNT = 1 << 24;\n\n/**\n * Parses a bundled `.sog` file into flat, GPU-friendly arrays, including\n * the palette-compressed higher-order SH coefficients (`shN`) if present.\n *\n * @param options.signal - Optional abort signal; the decode stages check it\n * between steps and inside the dequantization loop.\n * @throws {Error} if the archive or its `meta.json` is malformed;\n * a `DOMException` named `AbortError` when cancelled.\n *\n * A direct parser call sits outside the `SplatLoadError`-or-`AbortError`\n * contract of the worker-mediated loaders ({@link loadSplatData},\n * {@link loadSplatDataFile}, `ChunkLoader`, `StreamedSplatMesh`): malformed input\n * surfaces here as a plain `Error`.\n */\nexport async function parseSog(\n buffer: ArrayBuffer,\n options: { signal?: AbortSignal } = {},\n): Promise<SplatData> {\n const archive = readZip(buffer);\n return parseSogFromEntries(async (name) => {\n const entry = archive.get(name);\n if (!entry) throw new Error(`SOG bundle is missing \"${name}\".`);\n return entry();\n }, options.signal);\n}\n\n/**\n * Parses an unbundled SOG directory (`meta.json` + WebP files fetched\n * individually) into flat, GPU-friendly arrays - the layout Streamed SOG\n * chunks use.\n *\n * @param baseUrl - Absolute URL of the directory containing `meta.json`.\n * @param options.signal - Optional abort signal; in-flight fetches are\n * cancelled when it fires, and the decode stages check it between steps.\n * @throws {Error} on fetch failures or a malformed `meta.json`;\n * a `DOMException` named `AbortError` when cancelled.\n *\n * A direct parser call sits outside the `SplatLoadError`-or-`AbortError`\n * contract of the worker-mediated loaders ({@link loadSplatData},\n * {@link loadSplatDataFile}, `ChunkLoader`, `StreamedSplatMesh`): malformed input\n * surfaces here as a plain `Error`.\n */\nexport async function parseSogDirectory(\n baseUrl: string,\n options: {\n signal?: AbortSignal;\n request?: SplatRequestOptions;\n /**\n * The directory's files as `name → URL`, for a chunk that has no\n * directory URL to resolve against - a folder dropped into the page,\n * whose files are reached through `blob:` URLs.\n */\n files?: Readonly<Record<string, string>>;\n } = {},\n): Promise<SplatData> {\n const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n return parseSogFromEntries(async (name) => {\n assertPlainRelativeName(name);\n const mapped = options.files?.[name];\n if (options.files && mapped === undefined) {\n throw new Error(`SOG chunk is missing \"${name}\".`);\n }\n const url = mapped === undefined ? new URL(name, base) : new URL(mapped);\n let response: Response;\n try {\n response = await fetch(url, toRequestInit(options.request, options.signal));\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw toSplatLoadError(error, { phase: 'fetch', url: url.href });\n }\n if (!response.ok) {\n throw toSplatLoadError(new Error(`Failed to load ${url.href}: HTTP ${response.status}`), {\n phase: 'fetch',\n url: url.href,\n status: response.status,\n });\n }\n return new Uint8Array(await response.arrayBuffer());\n }, options.signal);\n}\n\n/**\n * Shared decode core; `readEntry` supplies file contents by name. The\n * abort signal is checked between decode stages so a cancellation skips\n * the expensive dequantization even when all fetches already finished.\n */\nasync function parseSogFromEntries(\n readEntry: (name: string) => Promise<Uint8Array>,\n signal?: AbortSignal,\n): Promise<SplatData> {\n const meta = JSON.parse(new TextDecoder().decode(await readEntry('meta.json'))) as SogMeta;\n validateMeta(meta);\n signal?.throwIfAborted();\n\n const [meansL, meansU, quats, scales, sh0] = await Promise.all([\n readEntry(meta.means.files[0] ?? 'means_l.webp').then(decodeWebp),\n readEntry(meta.means.files[1] ?? 'means_u.webp').then(decodeWebp),\n readEntry(meta.quats.files[0] ?? 'quats.webp').then(decodeWebp),\n readEntry(meta.scales.files[0] ?? 'scales.webp').then(decodeWebp),\n readEntry(meta.sh0.files[0] ?? 'sh0.webp').then(decodeWebp),\n ]);\n signal?.throwIfAborted();\n\n const count = meta.count;\n assertImageHoldsCount(meansL, 'means_l', count);\n assertImageHoldsCount(meansU, 'means_u', count);\n assertImageHoldsCount(quats, 'quats', count);\n assertImageHoldsCount(scales, 'scales', count);\n assertImageHoldsCount(sh0, 'sh0', count);\n const positions = new Float32Array(count * 3);\n const colors = new Uint8Array(count * 4);\n const covariances = new Float32Array(count * 6);\n\n // Positions: 16 bit per axis, normalized over [mins, maxs] in a\n // symmetric-log domain: unlog(n) = sign(n)·(eⁿ − 1).\n const { mins, maxs } = meta.means;\n const unlog = (n: number): number => Math.sign(n) * (Math.exp(Math.abs(n)) - 1);\n\n // Quaternions: \"smallest three\" encoding. RGB hold three components in\n // [-√½, +√½]; alpha − 252 says which component was omitted (the largest,\n // reconstructed from unit length). Component order is (w, x, y, z).\n const quatComponent = (byte: number): number => (byte / 255 - 0.5) * Math.SQRT2;\n\n const toColorByte = (sh: number): number =>\n Math.max(0, Math.min(255, Math.round((0.5 + sh * SH_C0) * 255)));\n\n for (let i = 0; i < count; i++) {\n // The dequantization loop dominates decode time. With a signal, yield\n // to the event loop every 65,536 splats so a pending cancel message\n // can be processed at all (the worker is single-threaded), then honor\n // it. Without a signal this branch never runs - no overhead.\n if (signal && (i & 0xffff) === 0 && i > 0) {\n await new Promise((resolve) => setTimeout(resolve, 0));\n signal.throwIfAborted();\n }\n const p = i * 4; // Every attribute image shares the same pixel layout.\n\n for (let axis = 0; axis < 3; axis++) {\n const quantized =\n (((meansU.data[p + axis] as number) << 8) | (meansL.data[p + axis] as number)) / 65535;\n const lo = mins[axis] as number;\n const hi = maxs[axis] as number;\n positions[i * 3 + axis] = unlog(lo + (hi - lo) * quantized);\n }\n\n // SH0: RGB are codebook indices for the DC term; alpha is the opacity,\n // stored already activated (no sigmoid, unlike PLY).\n colors[p + 0] = toColorByte(meta.sh0.codebook[sh0.data[p + 0] as number] as number);\n colors[p + 1] = toColorByte(meta.sh0.codebook[sh0.data[p + 1] as number] as number);\n colors[p + 2] = toColorByte(meta.sh0.codebook[sh0.data[p + 2] as number] as number);\n colors[p + 3] = sh0.data[p + 3] as number;\n\n const a = quatComponent(quats.data[p + 0] as number);\n const b = quatComponent(quats.data[p + 1] as number);\n const c = quatComponent(quats.data[p + 2] as number);\n const d = Math.sqrt(Math.max(0, 1 - (a * a + b * b + c * c)));\n const mode = (quats.data[p + 3] as number) - 252;\n const [qw, qx, qy, qz] =\n mode === 0\n ? [d, a, b, c]\n : mode === 1\n ? [a, d, b, c]\n : mode === 2\n ? [a, b, d, c]\n : [a, b, c, d];\n\n writeCovariance(\n covariances,\n i,\n Math.exp(meta.scales.codebook[scales.data[p + 0] as number] as number),\n Math.exp(meta.scales.codebook[scales.data[p + 1] as number] as number),\n Math.exp(meta.scales.codebook[scales.data[p + 2] as number] as number),\n qw,\n qx,\n qy,\n qz,\n );\n }\n\n signal?.throwIfAborted();\n const sh = meta.shN ? await decodeShN(meta.shN, count, readEntry) : undefined;\n return { count, positions, colors, covariances, sh, antialias: meta.antialias === true };\n}\n\n/**\n * Validates the untrusted `meta.json` before anything is allocated or\n * fetched based on it: the splat count must be a sane non-negative integer\n * and every codebook must resolve all 256 possible byte indices to finite\n * numbers (image bytes index them unchecked in the hot loop).\n */\nfunction validateMeta(meta: SogMeta): void {\n if (meta.version !== 2) throw new Error(`Unsupported SOG version: ${meta.version}.`);\n if (!Number.isSafeInteger(meta.count) || meta.count < 0) {\n throw new Error(`SOG meta.json declares an invalid splat count: ${meta.count}.`);\n }\n if (meta.count > MAX_SPLAT_COUNT) {\n throw new Error(\n `SOG meta.json declares ${meta.count} splats, above the supported maximum of ${MAX_SPLAT_COUNT}.`,\n );\n }\n validateVector3(meta.means?.mins, 'means.mins');\n validateVector3(meta.means?.maxs, 'means.maxs');\n validateCodebook(meta.scales?.codebook, 'scales');\n validateCodebook(meta.sh0?.codebook, 'sh0');\n if (meta.shN) {\n validateCodebook(meta.shN.codebook, 'shN');\n const bands = meta.shN.bands;\n if (!Number.isInteger(bands) || bands < 1 || bands > 3) {\n throw new Error(`SOG meta.json declares an invalid shN band count: ${bands}.`);\n }\n }\n}\n\n/** Asserts a meta.json vector is three finite numbers. */\nfunction validateVector3(vector: unknown, label: string): void {\n if (\n !Array.isArray(vector) ||\n vector.length !== 3 ||\n vector.some((value) => typeof value !== 'number' || !Number.isFinite(value))\n ) {\n throw new Error(`SOG meta.json: \"${label}\" must be an array of 3 finite numbers.`);\n }\n}\n\n/** Asserts a codebook covers every byte index with a finite number. */\nfunction validateCodebook(codebook: number[] | undefined, label: string): void {\n if (!Array.isArray(codebook) || codebook.length < 256) {\n throw new Error(`SOG meta.json: the \"${label}\" codebook must contain at least 256 entries.`);\n }\n for (const value of codebook) {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(`SOG meta.json: the \"${label}\" codebook contains a non-finite entry.`);\n }\n }\n}\n\n/** Asserts an attribute image has at least one pixel per declared splat. */\nfunction assertImageHoldsCount(image: DecodedImage, label: string, count: number): void {\n if (count > image.width * image.height) {\n throw new Error(\n `SOG \"${label}\" image (${image.width}×${image.height}) holds fewer than the declared ${count} splats.`,\n );\n }\n}\n\n/**\n * Rejects file names from `meta.json` that would resolve outside the SOG\n * directory - absolute URLs, scheme-relative or root-relative paths, and\n * `..` traversal - so a hostile manifest cannot redirect worker fetches.\n */\nfunction assertPlainRelativeName(name: string): void {\n const escapes =\n typeof name !== 'string' ||\n name.length === 0 ||\n /^[a-z][a-z0-9+.-]*:/i.test(name) ||\n name.startsWith('/') ||\n name.includes('\\\\') ||\n name.split('/').some((segment) => segment === '..');\n if (escapes) {\n throw new Error(`SOG meta.json references a file outside its directory: \"${name}\".`);\n }\n}\n\n/**\n * Decodes the palette-compressed higher-order SH data: per-splat 16-bit\n * labels plus a centroids image whose bytes are codebook indices. The\n * centroids keep their SOG grid layout so they can be uploaded as-is.\n */\nasync function decodeShN(\n meta: NonNullable<SogMeta['shN']>,\n count: number,\n readEntry: (name: string) => Promise<Uint8Array>,\n): Promise<SplatShData> {\n const [centroids, labelsImage] = await Promise.all([\n readEntry(meta.files[0] ?? 'shN_centroids.webp').then(decodeWebp),\n readEntry(meta.files[1] ?? 'shN_labels.webp').then(decodeWebp),\n ]);\n assertImageHoldsCount(labelsImage, 'shN labels', count);\n\n // A label indexes a palette entry laid out 64 entries per row, each entry\n // `coeffs` texels wide. Bound every decoded label against the centroids\n // image so a hostile labels image cannot address texels outside it.\n const coeffs = meta.bands === 1 ? 3 : meta.bands === 2 ? 8 : 15;\n const labels = new Uint32Array(count);\n for (let i = 0; i < count; i++) {\n const label =\n (labelsImage.data[i * 4] as number) | ((labelsImage.data[i * 4 + 1] as number) << 8);\n const row = Math.floor(label / 64);\n const column0 = (label % 64) * coeffs;\n if (row >= centroids.height || column0 + coeffs > centroids.width) {\n throw new Error(\n `SOG shN label ${label} (splat ${i}) lies outside the ` +\n `${centroids.width}×${centroids.height} centroids palette.`,\n );\n }\n labels[i] = label;\n }\n\n const palette = new Float32Array(centroids.width * centroids.height * 4);\n for (let i = 0; i < centroids.width * centroids.height; i++) {\n palette[i * 4 + 0] = meta.codebook[centroids.data[i * 4 + 0] as number] as number;\n palette[i * 4 + 1] = meta.codebook[centroids.data[i * 4 + 1] as number] as number;\n palette[i * 4 + 2] = meta.codebook[centroids.data[i * 4 + 2] as number] as number;\n }\n\n return {\n bands: meta.bands,\n labels,\n palette,\n paletteWidth: centroids.width,\n paletteHeight: centroids.height,\n };\n}\n\n/**\n * Minimal ZIP reader: returns a map from entry name to a function that\n * produces the entry's bytes. Supports the two methods that occur in\n * practice - STORE (SOG bundles are stored; WebP is already compressed)\n * and DEFLATE via the browser's native DecompressionStream.\n */\nfunction readZip(buffer: ArrayBuffer): Map<string, () => Promise<Uint8Array>> {\n const view = new DataView(buffer);\n const bytes = new Uint8Array(buffer);\n\n // Find the End Of Central Directory record (its signature, scanned\n // backwards past the variable-length archive comment).\n let eocd = -1;\n const scanEnd = Math.max(0, buffer.byteLength - 65557);\n for (let offset = buffer.byteLength - 22; offset >= scanEnd; offset--) {\n if (offset < 0) break;\n if (view.getUint32(offset, true) === 0x06054b50) {\n eocd = offset;\n break;\n }\n }\n if (eocd < 0) throw new Error('Not a ZIP archive (no end-of-central-directory record).');\n\n const entryCount = view.getUint16(eocd + 10, true);\n let cursor = view.getUint32(eocd + 16, true); // Central directory offset.\n\n const entries = new Map<string, () => Promise<Uint8Array>>();\n for (let i = 0; i < entryCount; i++) {\n // Range-check the untrusted cursor before every DataView read, so a\n // hostile central-directory offset fails as the corrupt ZIP it is\n // instead of surfacing a bare RangeError.\n if (cursor < 0 || cursor + 46 > buffer.byteLength) {\n throw new Error('Corrupt ZIP central directory.');\n }\n if (view.getUint32(cursor, true) !== 0x02014b50) {\n throw new Error('Corrupt ZIP central directory.');\n }\n const method = view.getUint16(cursor + 10, true);\n const compressedSize = view.getUint32(cursor + 20, true);\n const nameLength = view.getUint16(cursor + 28, true);\n const extraLength = view.getUint16(cursor + 30, true);\n const commentLength = view.getUint16(cursor + 32, true);\n const localHeaderOffset = view.getUint32(cursor + 42, true);\n if (cursor + 46 + nameLength > buffer.byteLength) {\n throw new Error('Corrupt ZIP central directory.');\n }\n const name = new TextDecoder().decode(bytes.subarray(cursor + 46, cursor + 46 + nameLength));\n cursor += 46 + nameLength + extraLength + commentLength;\n\n entries.set(name, async () => {\n // The local header repeats name/extra with possibly different sizes.\n if (localHeaderOffset + 30 > buffer.byteLength) {\n throw new Error(`Corrupt ZIP entry \"${name}\": local header extends past the archive.`);\n }\n const localNameLength = view.getUint16(localHeaderOffset + 26, true);\n const localExtraLength = view.getUint16(localHeaderOffset + 28, true);\n const dataStart = localHeaderOffset + 30 + localNameLength + localExtraLength;\n if (dataStart + compressedSize > buffer.byteLength) {\n throw new Error(`Corrupt ZIP entry \"${name}\": data extends past the archive.`);\n }\n const compressed = bytes.subarray(dataStart, dataStart + compressedSize);\n\n if (method === 0) return compressed; // STORE\n if (method === 8) {\n // DEFLATE\n // Blob copies the viewed byte range itself; no defensive slice needed.\n const stream = new Blob([compressed])\n .stream()\n .pipeThrough(new DecompressionStream('deflate-raw'));\n return new Uint8Array(await new Response(stream).arrayBuffer());\n }\n throw new Error(`Unsupported ZIP compression method ${method} for \"${name}\".`);\n });\n }\n return entries;\n}\n\nlet webpReadbackContext: WebGL2RenderingContext | undefined;\n\n/** Returns one worker-local WebGL2 context for consistent WebP readback. */\nfunction getWebpReadbackContext(): WebGL2RenderingContext | undefined {\n if (!('OffscreenCanvas' in globalThis) || !('createImageBitmap' in globalThis)) return undefined;\n if (webpReadbackContext && !webpReadbackContext.isContextLost()) return webpReadbackContext;\n\n const canvas = new OffscreenCanvas(1, 1);\n const context = canvas.getContext('webgl2', {\n alpha: false,\n antialias: false,\n depth: false,\n premultipliedAlpha: false,\n preserveDrawingBuffer: true,\n stencil: false,\n });\n if (!context) return undefined;\n webpReadbackContext = context;\n return context;\n}\n\n/**\n * Decodes through the same path PlayCanvas uses for SOG data textures.\n * Firefox's ImageDecoder premultiplies RGB by alpha even when conversion is\n * disabled. ImageBitmap -> WebGL keeps RGB unassociated with alpha.\n */\nasync function decodeWebpViaGpu(\n bytes: Uint8Array,\n context: WebGL2RenderingContext,\n): Promise<DecodedImage> {\n const blobBytes = bytes as Uint8Array<ArrayBuffer>;\n const bitmap = await createImageBitmap(new Blob([blobBytes], { type: 'image/webp' }), {\n colorSpaceConversion: 'none',\n premultiplyAlpha: 'none',\n });\n const texture = context.createTexture();\n const framebuffer = context.createFramebuffer();\n if (!texture || !framebuffer) {\n bitmap.close();\n throw new Error('Could not allocate a WebGL texture for SOG WebP decoding.');\n }\n\n try {\n context.bindTexture(context.TEXTURE_2D, texture);\n context.pixelStorei(context.UNPACK_FLIP_Y_WEBGL, false);\n context.pixelStorei(context.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);\n context.pixelStorei(context.UNPACK_COLORSPACE_CONVERSION_WEBGL, context.NONE);\n context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST);\n context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.NEAREST);\n context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_S, context.CLAMP_TO_EDGE);\n context.texParameteri(context.TEXTURE_2D, context.TEXTURE_WRAP_T, context.CLAMP_TO_EDGE);\n context.texImage2D(\n context.TEXTURE_2D,\n 0,\n context.RGBA,\n context.RGBA,\n context.UNSIGNED_BYTE,\n bitmap,\n );\n\n context.bindFramebuffer(context.FRAMEBUFFER, framebuffer);\n context.framebufferTexture2D(\n context.FRAMEBUFFER,\n context.COLOR_ATTACHMENT0,\n context.TEXTURE_2D,\n texture,\n 0,\n );\n if (context.checkFramebufferStatus(context.FRAMEBUFFER) !== context.FRAMEBUFFER_COMPLETE) {\n throw new Error('Could not read the decoded SOG WebP texture.');\n }\n\n const data = new Uint8Array(bitmap.width * bitmap.height * 4);\n context.readPixels(\n 0,\n 0,\n bitmap.width,\n bitmap.height,\n context.RGBA,\n context.UNSIGNED_BYTE,\n data,\n );\n return { width: bitmap.width, height: bitmap.height, data };\n } finally {\n context.bindFramebuffer(context.FRAMEBUFFER, null);\n context.bindTexture(context.TEXTURE_2D, null);\n context.deleteFramebuffer(framebuffer);\n context.deleteTexture(texture);\n bitmap.close();\n }\n}\n\n/** Decodes a WebP image to raw RGBA bytes. */\nasync function decodeWebp(bytes: Uint8Array): Promise<DecodedImage> {\n const gpuContext = getWebpReadbackContext();\n if (gpuContext) return decodeWebpViaGpu(bytes, gpuContext);\n\n // WebCodecs fallback for environments without worker WebGL2. Chromium\n // preserves these data bytes; Firefox uses the GPU path above because its\n // VideoFrame copy currently premultiplies low-alpha RGB values.\n if ('ImageDecoder' in globalThis) {\n const decoder = new ImageDecoder({\n data: bytes,\n type: 'image/webp',\n colorSpaceConversion: 'none',\n });\n const { image } = await decoder.decode();\n const width = image.codedWidth;\n const height = image.codedHeight;\n const data = new Uint8Array(width * height * 4);\n await image.copyTo(data, { format: 'RGBA' });\n image.close();\n decoder.close();\n return { width, height, data };\n }\n\n // Fallback: canvas readback. The premultiply round-trip can cost ±1 bit\n // of RGB precision on low-alpha pixels; acceptable for a fallback path.\n // Blob copies the viewed byte range itself, so no defensive `.slice()` is\n // needed; the cast only bridges lib.dom's BlobPart rejecting the\n // ArrayBufferLike-generic view (these bytes never sit in a SharedArrayBuffer).\n const blobBytes = bytes as Uint8Array<ArrayBuffer>;\n const bitmap = await createImageBitmap(new Blob([blobBytes], { type: 'image/webp' }), {\n premultiplyAlpha: 'none',\n colorSpaceConversion: 'none',\n });\n const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);\n const context = canvas.getContext('2d');\n if (!context) throw new Error('Could not create a 2D canvas context.');\n context.drawImage(bitmap, 0, 0);\n const imageData = context.getImageData(0, 0, bitmap.width, bitmap.height);\n bitmap.close();\n return {\n width: imageData.width,\n height: imageData.height,\n data: new Uint8Array(imageData.data.buffer),\n };\n}\n"],"names":["MAX_SPLAT_COUNT","parseSog","buffer","options","archive","readZip","parseSogFromEntries","name","entry","parseSogDirectory","baseUrl","base","assertPlainRelativeName","mapped","_a","url","response","toRequestInit","error","isAbortError","toSplatLoadError","readEntry","signal","meta","validateMeta","meansL","meansU","quats","scales","sh0","decodeWebp","count","assertImageHoldsCount","positions","colors","covariances","mins","maxs","unlog","n","quatComponent","byte","toColorByte","sh","SH_C0","i","resolve","p","axis","quantized","lo","hi","a","b","c","d","mode","qw","qx","qy","qz","writeCovariance","decodeShN","validateVector3","_b","validateCodebook","_c","_d","bands","vector","label","value","codebook","image","segment","centroids","labelsImage","coeffs","labels","row","column0","palette","view","bytes","eocd","scanEnd","offset","entryCount","cursor","entries","method","compressedSize","nameLength","extraLength","commentLength","localHeaderOffset","localNameLength","localExtraLength","dataStart","compressed","stream","webpReadbackContext","getWebpReadbackContext","context","decodeWebpViaGpu","blobBytes","bitmap","texture","framebuffer","data","gpuContext","decoder","width","height","imageData"],"mappings":";;AAmDA,MAAMA,IAAkB,KAAK;AAgB7B,eAAsBC,EACpBC,GACAC,IAAoC,IAChB;AACpB,QAAMC,IAAUC,EAAQH,CAAM;AAC9B,SAAOI,EAAoB,OAAOC,MAAS;AACzC,UAAMC,IAAQJ,EAAQ,IAAIG,CAAI;AAC9B,QAAI,CAACC,EAAO,OAAM,IAAI,MAAM,0BAA0BD,CAAI,IAAI;AAC9D,WAAOC,EAAA;AAAA,EACT,GAAGL,EAAQ,MAAM;AACnB;AAkBA,eAAsBM,GACpBC,GACAP,IASI,IACgB;AACpB,QAAMQ,IAAOD,EAAQ,SAAS,GAAG,IAAIA,IAAU,GAAGA,CAAO;AACzD,SAAOJ,EAAoB,OAAOC,MAAS;;AACzC,IAAAK,EAAwBL,CAAI;AAC5B,UAAMM,KAASC,IAAAX,EAAQ,UAAR,gBAAAW,EAAgBP;AAC/B,QAAIJ,EAAQ,SAASU,MAAW;AAC9B,YAAM,IAAI,MAAM,yBAAyBN,CAAI,IAAI;AAEnD,UAAMQ,IAAMF,MAAW,SAAY,IAAI,IAAIN,GAAMI,CAAI,IAAI,IAAI,IAAIE,CAAM;AACvE,QAAIG;AACJ,QAAI;AACF,MAAAA,IAAW,MAAM,MAAMD,GAAKE,EAAcd,EAAQ,SAASA,EAAQ,MAAM,CAAC;AAAA,IAC5E,SAASe,GAAO;AACd,YAAIC,EAAaD,CAAK,IAASA,IACzBE,EAAiBF,GAAO,EAAE,OAAO,SAAS,KAAKH,EAAI,MAAM;AAAA,IACjE;AACA,QAAI,CAACC,EAAS;AACZ,YAAMI,EAAiB,IAAI,MAAM,kBAAkBL,EAAI,IAAI,UAAUC,EAAS,MAAM,EAAE,GAAG;AAAA,QACvF,OAAO;AAAA,QACP,KAAKD,EAAI;AAAA,QACT,QAAQC,EAAS;AAAA,MAAA,CAClB;AAEH,WAAO,IAAI,WAAW,MAAMA,EAAS,aAAa;AAAA,EACpD,GAAGb,EAAQ,MAAM;AACnB;AAOA,eAAeG,EACbe,GACAC,GACoB;AACpB,QAAMC,IAAO,KAAK,MAAM,IAAI,cAAc,OAAO,MAAMF,EAAU,WAAW,CAAC,CAAC;AAC9E,EAAAG,EAAaD,CAAI,GACjBD,KAAA,QAAAA,EAAQ;AAER,QAAM,CAACG,GAAQC,GAAQC,GAAOC,GAAQC,CAAG,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC7DR,EAAUE,EAAK,MAAM,MAAM,CAAC,KAAK,cAAc,EAAE,KAAKO,CAAU;AAAA,IAChET,EAAUE,EAAK,MAAM,MAAM,CAAC,KAAK,cAAc,EAAE,KAAKO,CAAU;AAAA,IAChET,EAAUE,EAAK,MAAM,MAAM,CAAC,KAAK,YAAY,EAAE,KAAKO,CAAU;AAAA,IAC9DT,EAAUE,EAAK,OAAO,MAAM,CAAC,KAAK,aAAa,EAAE,KAAKO,CAAU;AAAA,IAChET,EAAUE,EAAK,IAAI,MAAM,CAAC,KAAK,UAAU,EAAE,KAAKO,CAAU;AAAA,EAAA,CAC3D;AACD,EAAAR,KAAA,QAAAA,EAAQ;AAER,QAAMS,IAAQR,EAAK;AACnB,EAAAS,EAAsBP,GAAQ,WAAWM,CAAK,GAC9CC,EAAsBN,GAAQ,WAAWK,CAAK,GAC9CC,EAAsBL,GAAO,SAASI,CAAK,GAC3CC,EAAsBJ,GAAQ,UAAUG,CAAK,GAC7CC,EAAsBH,GAAK,OAAOE,CAAK;AACvC,QAAME,IAAY,IAAI,aAAaF,IAAQ,CAAC,GACtCG,IAAS,IAAI,WAAWH,IAAQ,CAAC,GACjCI,IAAc,IAAI,aAAaJ,IAAQ,CAAC,GAIxC,EAAE,MAAAK,GAAM,MAAAC,EAAA,IAASd,EAAK,OACtBe,IAAQ,CAACC,MAAsB,KAAK,KAAKA,CAAC,KAAK,KAAK,IAAI,KAAK,IAAIA,CAAC,CAAC,IAAI,IAKvEC,IAAgB,CAACC,OAA0BA,IAAO,MAAM,OAAO,KAAK,OAEpEC,IAAc,CAACC,MACnB,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,OAAO,MAAMA,IAAKC,KAAS,GAAG,CAAC,CAAC;AAEjE,WAASC,IAAI,GAAGA,IAAId,GAAOc,KAAK;AAK9B,IAAIvB,MAAWuB,IAAI,WAAY,KAAKA,IAAI,MACtC,MAAM,IAAI,QAAQ,CAACC,MAAY,WAAWA,GAAS,CAAC,CAAC,GACrDxB,EAAO,eAAA;AAET,UAAMyB,IAAIF,IAAI;AAEd,aAASG,IAAO,GAAGA,IAAO,GAAGA,KAAQ;AACnC,YAAMC,KACDvB,EAAO,KAAKqB,IAAIC,CAAI,KAAgB,IAAMvB,EAAO,KAAKsB,IAAIC,CAAI,KAAgB,OAC7EE,IAAKd,EAAKY,CAAI,GACdG,IAAKd,EAAKW,CAAI;AACpB,MAAAf,EAAUY,IAAI,IAAIG,CAAI,IAAIV,EAAMY,KAAMC,IAAKD,KAAMD,CAAS;AAAA,IAC5D;AAIA,IAAAf,EAAOa,IAAI,CAAC,IAAIL,EAAYnB,EAAK,IAAI,SAASM,EAAI,KAAKkB,IAAI,CAAC,CAAW,CAAW,GAClFb,EAAOa,IAAI,CAAC,IAAIL,EAAYnB,EAAK,IAAI,SAASM,EAAI,KAAKkB,IAAI,CAAC,CAAW,CAAW,GAClFb,EAAOa,IAAI,CAAC,IAAIL,EAAYnB,EAAK,IAAI,SAASM,EAAI,KAAKkB,IAAI,CAAC,CAAW,CAAW,GAClFb,EAAOa,IAAI,CAAC,IAAIlB,EAAI,KAAKkB,IAAI,CAAC;AAE9B,UAAMK,IAAIZ,EAAcb,EAAM,KAAKoB,IAAI,CAAC,CAAW,GAC7CM,IAAIb,EAAcb,EAAM,KAAKoB,IAAI,CAAC,CAAW,GAC7CO,IAAId,EAAcb,EAAM,KAAKoB,IAAI,CAAC,CAAW,GAC7CQ,IAAI,KAAK,KAAK,KAAK,IAAI,GAAG,KAAKH,IAAIA,IAAIC,IAAIA,IAAIC,IAAIA,EAAE,CAAC,GACtDE,IAAQ7B,EAAM,KAAKoB,IAAI,CAAC,IAAe,KACvC,CAACU,GAAIC,GAAIC,GAAIC,CAAE,IACnBJ,MAAS,IACL,CAACD,GAAGH,GAAGC,GAAGC,CAAC,IACXE,MAAS,IACP,CAACJ,GAAGG,GAAGF,GAAGC,CAAC,IACXE,MAAS,IACP,CAACJ,GAAGC,GAAGE,GAAGD,CAAC,IACX,CAACF,GAAGC,GAAGC,GAAGC,CAAC;AAErB,IAAAM;AAAA,MACE1B;AAAA,MACAU;AAAA,MACA,KAAK,IAAItB,EAAK,OAAO,SAASK,EAAO,KAAKmB,IAAI,CAAC,CAAW,CAAW;AAAA,MACrE,KAAK,IAAIxB,EAAK,OAAO,SAASK,EAAO,KAAKmB,IAAI,CAAC,CAAW,CAAW;AAAA,MACrE,KAAK,IAAIxB,EAAK,OAAO,SAASK,EAAO,KAAKmB,IAAI,CAAC,CAAW,CAAW;AAAA,MACrEU;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,IAAA;AAAA,EAEJ;AAEA,EAAAtC,KAAA,QAAAA,EAAQ;AACR,QAAMqB,IAAKpB,EAAK,MAAM,MAAMuC,EAAUvC,EAAK,KAAKQ,GAAOV,CAAS,IAAI;AACpE,SAAO,EAAE,OAAAU,GAAO,WAAAE,GAAW,QAAAC,GAAQ,aAAAC,GAAa,IAAAQ,GAAI,WAAWpB,EAAK,cAAc,GAAA;AACpF;AAQA,SAASC,EAAaD,GAAqB;;AACzC,MAAIA,EAAK,YAAY,EAAG,OAAM,IAAI,MAAM,4BAA4BA,EAAK,OAAO,GAAG;AACnF,MAAI,CAAC,OAAO,cAAcA,EAAK,KAAK,KAAKA,EAAK,QAAQ;AACpD,UAAM,IAAI,MAAM,kDAAkDA,EAAK,KAAK,GAAG;AAEjF,MAAIA,EAAK,QAAQvB;AACf,UAAM,IAAI;AAAA,MACR,0BAA0BuB,EAAK,KAAK,2CAA2CvB,CAAe;AAAA,IAAA;AAOlG,MAJA+D,GAAgBjD,IAAAS,EAAK,UAAL,gBAAAT,EAAY,MAAM,YAAY,GAC9CiD,GAAgBC,IAAAzC,EAAK,UAAL,gBAAAyC,EAAY,MAAM,YAAY,GAC9CC,GAAiBC,IAAA3C,EAAK,WAAL,gBAAA2C,EAAa,UAAU,QAAQ,GAChDD,GAAiBE,IAAA5C,EAAK,QAAL,gBAAA4C,EAAU,UAAU,KAAK,GACtC5C,EAAK,KAAK;AACZ,IAAA0C,EAAiB1C,EAAK,IAAI,UAAU,KAAK;AACzC,UAAM6C,IAAQ7C,EAAK,IAAI;AACvB,QAAI,CAAC,OAAO,UAAU6C,CAAK,KAAKA,IAAQ,KAAKA,IAAQ;AACnD,YAAM,IAAI,MAAM,qDAAqDA,CAAK,GAAG;AAAA,EAEjF;AACF;AAGA,SAASL,EAAgBM,GAAiBC,GAAqB;AAC7D,MACE,CAAC,MAAM,QAAQD,CAAM,KACrBA,EAAO,WAAW,KAClBA,EAAO,KAAK,CAACE,MAAU,OAAOA,KAAU,YAAY,CAAC,OAAO,SAASA,CAAK,CAAC;AAE3E,UAAM,IAAI,MAAM,mBAAmBD,CAAK,yCAAyC;AAErF;AAGA,SAASL,EAAiBO,GAAgCF,GAAqB;AAC7E,MAAI,CAAC,MAAM,QAAQE,CAAQ,KAAKA,EAAS,SAAS;AAChD,UAAM,IAAI,MAAM,uBAAuBF,CAAK,+CAA+C;AAE7F,aAAWC,KAASC;AAClB,QAAI,OAAOD,KAAU,YAAY,CAAC,OAAO,SAASA,CAAK;AACrD,YAAM,IAAI,MAAM,uBAAuBD,CAAK,yCAAyC;AAG3F;AAGA,SAAStC,EAAsByC,GAAqBH,GAAevC,GAAqB;AACtF,MAAIA,IAAQ0C,EAAM,QAAQA,EAAM;AAC9B,UAAM,IAAI;AAAA,MACR,QAAQH,CAAK,YAAYG,EAAM,KAAK,IAAIA,EAAM,MAAM,mCAAmC1C,CAAK;AAAA,IAAA;AAGlG;AAOA,SAASnB,EAAwBL,GAAoB;AAQnD,MANE,OAAOA,KAAS,YAChBA,EAAK,WAAW,KAChB,uBAAuB,KAAKA,CAAI,KAChCA,EAAK,WAAW,GAAG,KACnBA,EAAK,SAAS,IAAI,KAClBA,EAAK,MAAM,GAAG,EAAE,KAAK,CAACmE,MAAYA,MAAY,IAAI;AAElD,UAAM,IAAI,MAAM,2DAA2DnE,CAAI,IAAI;AAEvF;AAOA,eAAeuD,EACbvC,GACAQ,GACAV,GACsB;AACtB,QAAM,CAACsD,GAAWC,CAAW,IAAI,MAAM,QAAQ,IAAI;AAAA,IACjDvD,EAAUE,EAAK,MAAM,CAAC,KAAK,oBAAoB,EAAE,KAAKO,CAAU;AAAA,IAChET,EAAUE,EAAK,MAAM,CAAC,KAAK,iBAAiB,EAAE,KAAKO,CAAU;AAAA,EAAA,CAC9D;AACD,EAAAE,EAAsB4C,GAAa,cAAc7C,CAAK;AAKtD,QAAM8C,IAAStD,EAAK,UAAU,IAAI,IAAIA,EAAK,UAAU,IAAI,IAAI,IACvDuD,IAAS,IAAI,YAAY/C,CAAK;AACpC,WAASc,IAAI,GAAGA,IAAId,GAAOc,KAAK;AAC9B,UAAMyB,IACHM,EAAY,KAAK/B,IAAI,CAAC,IAAiB+B,EAAY,KAAK/B,IAAI,IAAI,CAAC,KAAgB,GAC9EkC,IAAM,KAAK,MAAMT,IAAQ,EAAE,GAC3BU,IAAWV,IAAQ,KAAMO;AAC/B,QAAIE,KAAOJ,EAAU,UAAUK,IAAUH,IAASF,EAAU;AAC1D,YAAM,IAAI;AAAA,QACR,iBAAiBL,CAAK,WAAWzB,CAAC,sBAC7B8B,EAAU,KAAK,IAAIA,EAAU,MAAM;AAAA,MAAA;AAG5C,IAAAG,EAAOjC,CAAC,IAAIyB;AAAA,EACd;AAEA,QAAMW,IAAU,IAAI,aAAaN,EAAU,QAAQA,EAAU,SAAS,CAAC;AACvE,WAAS9B,IAAI,GAAGA,IAAI8B,EAAU,QAAQA,EAAU,QAAQ9B;AACtD,IAAAoC,EAAQpC,IAAI,IAAI,CAAC,IAAItB,EAAK,SAASoD,EAAU,KAAK9B,IAAI,IAAI,CAAC,CAAW,GACtEoC,EAAQpC,IAAI,IAAI,CAAC,IAAItB,EAAK,SAASoD,EAAU,KAAK9B,IAAI,IAAI,CAAC,CAAW,GACtEoC,EAAQpC,IAAI,IAAI,CAAC,IAAItB,EAAK,SAASoD,EAAU,KAAK9B,IAAI,IAAI,CAAC,CAAW;AAGxE,SAAO;AAAA,IACL,OAAOtB,EAAK;AAAA,IACZ,QAAAuD;AAAA,IACA,SAAAG;AAAA,IACA,cAAcN,EAAU;AAAA,IACxB,eAAeA,EAAU;AAAA,EAAA;AAE7B;AAQA,SAAStE,EAAQH,GAA6D;AAC5E,QAAMgF,IAAO,IAAI,SAAShF,CAAM,GAC1BiF,IAAQ,IAAI,WAAWjF,CAAM;AAInC,MAAIkF,IAAO;AACX,QAAMC,IAAU,KAAK,IAAI,GAAGnF,EAAO,aAAa,KAAK;AACrD,WAASoF,IAASpF,EAAO,aAAa,IAAIoF,KAAUD,KAC9C,EAAAC,IAAS,IAD8CA;AAE3D,QAAIJ,EAAK,UAAUI,GAAQ,EAAI,MAAM,WAAY;AAC/C,MAAAF,IAAOE;AACP;AAAA,IACF;AAEF,MAAIF,IAAO,EAAG,OAAM,IAAI,MAAM,yDAAyD;AAEvF,QAAMG,IAAaL,EAAK,UAAUE,IAAO,IAAI,EAAI;AACjD,MAAII,IAASN,EAAK,UAAUE,IAAO,IAAI,EAAI;AAE3C,QAAMK,wBAAc,IAAA;AACpB,WAAS5C,IAAI,GAAGA,IAAI0C,GAAY1C,KAAK;AAInC,QAAI2C,IAAS,KAAKA,IAAS,KAAKtF,EAAO;AACrC,YAAM,IAAI,MAAM,gCAAgC;AAElD,QAAIgF,EAAK,UAAUM,GAAQ,EAAI,MAAM;AACnC,YAAM,IAAI,MAAM,gCAAgC;AAElD,UAAME,IAASR,EAAK,UAAUM,IAAS,IAAI,EAAI,GACzCG,IAAiBT,EAAK,UAAUM,IAAS,IAAI,EAAI,GACjDI,IAAaV,EAAK,UAAUM,IAAS,IAAI,EAAI,GAC7CK,IAAcX,EAAK,UAAUM,IAAS,IAAI,EAAI,GAC9CM,IAAgBZ,EAAK,UAAUM,IAAS,IAAI,EAAI,GAChDO,IAAoBb,EAAK,UAAUM,IAAS,IAAI,EAAI;AAC1D,QAAIA,IAAS,KAAKI,IAAa1F,EAAO;AACpC,YAAM,IAAI,MAAM,gCAAgC;AAElD,UAAMK,IAAO,IAAI,YAAA,EAAc,OAAO4E,EAAM,SAASK,IAAS,IAAIA,IAAS,KAAKI,CAAU,CAAC;AAC3F,IAAAJ,KAAU,KAAKI,IAAaC,IAAcC,GAE1CL,EAAQ,IAAIlF,GAAM,YAAY;AAE5B,UAAIwF,IAAoB,KAAK7F,EAAO;AAClC,cAAM,IAAI,MAAM,sBAAsBK,CAAI,2CAA2C;AAEvF,YAAMyF,IAAkBd,EAAK,UAAUa,IAAoB,IAAI,EAAI,GAC7DE,IAAmBf,EAAK,UAAUa,IAAoB,IAAI,EAAI,GAC9DG,IAAYH,IAAoB,KAAKC,IAAkBC;AAC7D,UAAIC,IAAYP,IAAiBzF,EAAO;AACtC,cAAM,IAAI,MAAM,sBAAsBK,CAAI,mCAAmC;AAE/E,YAAM4F,IAAahB,EAAM,SAASe,GAAWA,IAAYP,CAAc;AAEvE,UAAID,MAAW,EAAG,QAAOS;AACzB,UAAIT,MAAW,GAAG;AAGhB,cAAMU,IAAS,IAAI,KAAK,CAACD,CAAU,CAAC,EACjC,OAAA,EACA,YAAY,IAAI,oBAAoB,aAAa,CAAC;AACrD,eAAO,IAAI,WAAW,MAAM,IAAI,SAASC,CAAM,EAAE,aAAa;AAAA,MAChE;AACA,YAAM,IAAI,MAAM,sCAAsCV,CAAM,SAASnF,CAAI,IAAI;AAAA,IAC/E,CAAC;AAAA,EACH;AACA,SAAOkF;AACT;AAEA,IAAIY;AAGJ,SAASC,IAA6D;AACpE,MAAI,EAAE,qBAAqB,eAAe,EAAE,uBAAuB,YAAa;AAChF,MAAID,KAAuB,CAACA,EAAoB,cAAA,EAAiB,QAAOA;AAGxE,QAAME,IADS,IAAI,gBAAgB,GAAG,CAAC,EAChB,WAAW,UAAU;AAAA,IAC1C,OAAO;AAAA,IACP,WAAW;AAAA,IACX,OAAO;AAAA,IACP,oBAAoB;AAAA,IACpB,uBAAuB;AAAA,IACvB,SAAS;AAAA,EAAA,CACV;AACD,MAAKA;AACL,WAAAF,IAAsBE,GACfA;AACT;AAOA,eAAeC,EACbrB,GACAoB,GACuB;AACvB,QAAME,IAAYtB,GACZuB,IAAS,MAAM,kBAAkB,IAAI,KAAK,CAACD,CAAS,GAAG,EAAE,MAAM,aAAA,CAAc,GAAG;AAAA,IACpF,sBAAsB;AAAA,IACtB,kBAAkB;AAAA,EAAA,CACnB,GACKE,IAAUJ,EAAQ,cAAA,GAClBK,IAAcL,EAAQ,kBAAA;AAC5B,MAAI,CAACI,KAAW,CAACC;AACf,UAAAF,EAAO,MAAA,GACD,IAAI,MAAM,2DAA2D;AAG7E,MAAI;AA0BF,QAzBAH,EAAQ,YAAYA,EAAQ,YAAYI,CAAO,GAC/CJ,EAAQ,YAAYA,EAAQ,qBAAqB,EAAK,GACtDA,EAAQ,YAAYA,EAAQ,gCAAgC,EAAK,GACjEA,EAAQ,YAAYA,EAAQ,oCAAoCA,EAAQ,IAAI,GAC5EA,EAAQ,cAAcA,EAAQ,YAAYA,EAAQ,oBAAoBA,EAAQ,OAAO,GACrFA,EAAQ,cAAcA,EAAQ,YAAYA,EAAQ,oBAAoBA,EAAQ,OAAO,GACrFA,EAAQ,cAAcA,EAAQ,YAAYA,EAAQ,gBAAgBA,EAAQ,aAAa,GACvFA,EAAQ,cAAcA,EAAQ,YAAYA,EAAQ,gBAAgBA,EAAQ,aAAa,GACvFA,EAAQ;AAAA,MACNA,EAAQ;AAAA,MACR;AAAA,MACAA,EAAQ;AAAA,MACRA,EAAQ;AAAA,MACRA,EAAQ;AAAA,MACRG;AAAA,IAAA,GAGFH,EAAQ,gBAAgBA,EAAQ,aAAaK,CAAW,GACxDL,EAAQ;AAAA,MACNA,EAAQ;AAAA,MACRA,EAAQ;AAAA,MACRA,EAAQ;AAAA,MACRI;AAAA,MACA;AAAA,IAAA,GAEEJ,EAAQ,uBAAuBA,EAAQ,WAAW,MAAMA,EAAQ;AAClE,YAAM,IAAI,MAAM,8CAA8C;AAGhE,UAAMM,IAAO,IAAI,WAAWH,EAAO,QAAQA,EAAO,SAAS,CAAC;AAC5D,WAAAH,EAAQ;AAAA,MACN;AAAA,MACA;AAAA,MACAG,EAAO;AAAA,MACPA,EAAO;AAAA,MACPH,EAAQ;AAAA,MACRA,EAAQ;AAAA,MACRM;AAAA,IAAA,GAEK,EAAE,OAAOH,EAAO,OAAO,QAAQA,EAAO,QAAQ,MAAAG,EAAA;AAAA,EACvD,UAAA;AACE,IAAAN,EAAQ,gBAAgBA,EAAQ,aAAa,IAAI,GACjDA,EAAQ,YAAYA,EAAQ,YAAY,IAAI,GAC5CA,EAAQ,kBAAkBK,CAAW,GACrCL,EAAQ,cAAcI,CAAO,GAC7BD,EAAO,MAAA;AAAA,EACT;AACF;AAGA,eAAe5E,EAAWqD,GAA0C;AAClE,QAAM2B,IAAaR,EAAA;AACnB,MAAIQ,EAAY,QAAON,EAAiBrB,GAAO2B,CAAU;AAKzD,MAAI,kBAAkB,YAAY;AAChC,UAAMC,IAAU,IAAI,aAAa;AAAA,MAC/B,MAAM5B;AAAA,MACN,MAAM;AAAA,MACN,sBAAsB;AAAA,IAAA,CACvB,GACK,EAAE,OAAAV,EAAA,IAAU,MAAMsC,EAAQ,OAAA,GAC1BC,IAAQvC,EAAM,YACdwC,IAASxC,EAAM,aACfoC,IAAO,IAAI,WAAWG,IAAQC,IAAS,CAAC;AAC9C,iBAAMxC,EAAM,OAAOoC,GAAM,EAAE,QAAQ,QAAQ,GAC3CpC,EAAM,MAAA,GACNsC,EAAQ,MAAA,GACD,EAAE,OAAAC,GAAO,QAAAC,GAAQ,MAAAJ,EAAA;AAAA,EAC1B;AAOA,QAAMJ,IAAYtB,GACZuB,IAAS,MAAM,kBAAkB,IAAI,KAAK,CAACD,CAAS,GAAG,EAAE,MAAM,aAAA,CAAc,GAAG;AAAA,IACpF,kBAAkB;AAAA,IAClB,sBAAsB;AAAA,EAAA,CACvB,GAEKF,IADS,IAAI,gBAAgBG,EAAO,OAAOA,EAAO,MAAM,EACvC,WAAW,IAAI;AACtC,MAAI,CAACH,EAAS,OAAM,IAAI,MAAM,uCAAuC;AACrE,EAAAA,EAAQ,UAAUG,GAAQ,GAAG,CAAC;AAC9B,QAAMQ,IAAYX,EAAQ,aAAa,GAAG,GAAGG,EAAO,OAAOA,EAAO,MAAM;AACxE,SAAAA,EAAO,MAAA,GACA;AAAA,IACL,OAAOQ,EAAU;AAAA,IACjB,QAAQA,EAAU;AAAA,IAClB,MAAM,IAAI,WAAWA,EAAU,KAAK,MAAM;AAAA,EAAA;AAE9C;"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `@voluma/vlam/formats/splat` - antimatter15 `.splat` parser.
3
3
  *
4
- * Loading through {@link loadScene} / {@link loadSceneFile} does not require
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
  /**
2
2
  * `@voluma/vlam/formats/spz` - Niantic `.spz` parser.
3
3
  *
4
- * Loading through {@link loadScene} / {@link loadSceneFile} does not require
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
  */
package/dist/index.d.ts CHANGED
@@ -4,7 +4,14 @@
4
4
  * Public API surface. Everything else under `src/lib/` is internal and may
5
5
  * change without notice.
6
6
  *
7
- * Format-specific parsers live on optional subpaths:
7
+ * Optional systems live on dedicated subpaths so an application that only draws a
8
+ * fully loaded mesh does not pay for workers, schedulers, or work buffers:
9
+ * - `@voluma/vlam/loaders` - {@link loadSplatData}, {@link ChunkLoader}
10
+ * - `@voluma/vlam/static-lod` - {@link StaticLodSplatMesh}
11
+ * - `@voluma/vlam/streaming` - {@link StreamedSplatMesh}, budget governors
12
+ * - `@voluma/vlam/unified` - {@link UnifiedSplatMesh}
13
+ * - `@voluma/vlam/selection` - volume select and partition
14
+ * - `@voluma/vlam/effects` - tree-shakeable modifier presets
8
15
  * - `@voluma/vlam/formats/ply` - 3DGS `.ply`, raw and compressed
9
16
  * - `@voluma/vlam/formats/sog` - PlayCanvas SOG, bundled and unbundled
10
17
  * - `@voluma/vlam/formats/rad` - Spark `.rad`/`.radc`
@@ -13,42 +20,26 @@
13
20
  * - `@voluma/vlam/formats/splat` - antimatter15 `.splat`
14
21
  * - `@voluma/vlam/formats/ksplat` - mkkellogg `.ksplat`
15
22
  *
16
- * {@link loadScene} / {@link StreamedSplatMesh.load} still accept those formats
17
- * without importing a subpath; the library loads format code on demand.
23
+ * {@link loadSplatData} / {@link StreamedSplatMesh.load} still accept those formats
24
+ * without importing a format subpath; the library loads format code on demand.
18
25
  *
19
26
  * @module core
20
27
  */
21
- export { SplatMesh, MAX_SH_BANDS, DEFAULT_FOVEATION_TARGET_PX, DEFAULT_FOVEATION_DRAW_BUDGET, resolveSplatPerformanceProfile, type UnifiedSourceView, type SplatMeshOptions, type SplatUpdateOptions, type SplatSortStrategy, type SplatPerformanceProfile, type SplatRange, type SplatPickOptions, type SplatPickResult, type SplatNearestResult, type SplatRayResult, type SplatHeightResult, type SplatChannelType, type SplatChannelOptions, } from './splat-mesh';
28
+ export { SplatMesh, MAX_SH_BANDS, DEFAULT_FOVEATION_TARGET_PX, DEFAULT_FOVEATION_DRAW_BUDGET, resolveSplatPerformanceProfile, type UnifiedSourceView, type SplatMeshOptions, type SplatUpdateOptions, type SplatSortStrategy, type SplatPerformanceProfile, type SplatFoveationMode, type SplatRange, type SplatPickOptions, type SplatPickResult, type SplatNearestResult, type SplatRayResult, type SplatHeightResult, type SplatChannelType, type SplatChannelOptions, } from './splat-mesh';
22
29
  export { SplatPool, type SplatPoolOptions } from './splat-mesh-pool';
23
30
  export { SPLAT_DATA_TEXTURE_WIDTH, deviceMaxTextureSize, assertPoolRowsFitDevice, } from './splat-mesh-pool';
24
31
  export { MAX_DOF_RADIUS_PX, MAX_DOF_VARIANCE, apertureAngleFromSize, computeDofCocVariancePx2, computeDofOpacityFade, clampDepthOfFieldSettings, type DepthOfFieldSettings, } from './depth-of-field';
25
32
  export { DEFAULT_RELIGHT_BLEND, DEFAULT_RELIGHT_BRIGHTNESS, DEFAULT_RELIGHT_BACKGROUND, DEFAULT_RELIGHT_SOFTNESS, clampRelightingSettings, createPlaceholderRelightTexture, type RelightingSettings, type RelightingUniforms, } from './relighting';
26
33
  export { type SplatOrientation, type OrientableFormat, createYUpTransform, yUpTransformForFormat, } from './orientation';
27
- export { StreamedSplatMesh, type StreamedSplatMeshOptions, type InitialRevealState, type PersistentChannelOptions, type StreamedSplatPerformanceEvent, } from './streamed-splat-mesh';
28
- export { StaticLodSplatMesh, type StaticLodSplatMeshOptions, type StaticLodSplatMeshLoadOptions, type StaticLodLoadProgress, } from './static-lod-splat-mesh';
29
- export type { StaticLodBuildProgress } from './static-lod';
30
- export { SplatScene, type SplatSceneOptions, type AddSourceOptions } from './splat-scene';
31
- export { MAX_SOURCES } from './source-transform';
34
+ export { MergedSplatMesh, type MergedSplatMeshOptions, type MergedSplatSourceOptions, } from './merged-splat-mesh';
35
+ export { MAX_MERGED_SPLAT_SOURCES } from './source-transform';
32
36
  export type { SplatShInputs, Vec3Uniform } from './splat-mesh-material';
33
- export { UnifiedSplatRenderer, supportsUnifiedSplatRenderer, type UnifiedSplatPickResult, type UnifiedSplatRendererOptions, type UnifiedSplatSourceOptions, } from './unified-splat-renderer';
34
- export { resolveSplatBudget, detectSplatDeviceProfile, classifySplatGpuClass, probeSplatGpuClass, isFillConstrainedSplatDevice, recommendedMaxPixelRatio, recommendedRadMaxStdDev, recommendedXrFramebufferScale, resolveXrSplatBudget, resolveCpuCacheBytes, suggestAdaptivePixelRatio, ADAPTIVE_PIXEL_RATIO_WARMUP_FRAMES, estimateSplatPoolBytes, type SplatBudgetOptions, type SplatCostClass, type SplatDeviceProfile, type SplatGpuClass, type SplatGpuAdapterInfo, type AdaptivePixelRatioInput, type AdaptivePixelRatioResult, type SplatPoolBytesOptions, } from './splat-budget';
37
+ export { resolveSplatBudget, detectSplatDeviceProfile, classifySplatGpuClass, probeSplatGpuClass, isFillConstrainedSplatDevice, recommendedMaxPixelRatio, recommendedRadMaxStdDev, recommendedXrFramebufferScale, resolveXrSplatBudget, suggestAdaptivePixelRatio, ADAPTIVE_PIXEL_RATIO_WARMUP_FRAMES, estimateSplatPoolBytes, type SplatBudgetOptions, type SplatCostClass, type SplatDeviceProfile, type SplatGpuClass, type SplatGpuAdapterInfo, type AdaptivePixelRatioInput, type AdaptivePixelRatioResult, type SplatPoolBytesOptions, } from './splat-budget';
35
38
  export { xrSessionInit } from './xr-view';
36
- export { BudgetGovernor, type BudgetGovernorOptions, type BudgetGovernedMember, } from './budget-governor';
37
- export { CameraBudgetGovernor, type CameraBudgetGovernorOptions, type CameraBudgetMember, type CameraBudgetMemberOptions, } from './camera-budget-governor';
38
39
  export { createSplatRenderer, type CreateSplatRendererOptions, type SplatRendererGpu, type SplatRendererGpuAdapter, type SplatRendererGpuDevice, } from './create-splat-renderer';
39
- export { recommendedWebGpuRequiredLimits, supportsWebGpuPowerPreference, webGpuPowerPreferenceOptions, estimateLargestStorageBufferBytes, estimateUnifiedWorkBufferBytes, estimateUnifiedWorkBufferPeakBytes, deviceMaxStorageBufferBindingSize, assertStorageBufferFitsDevice, WORK_BUFFER_CENTERS_BYTES_PER_SPLAT, WORK_BUFFER_BYTES_PER_SLOT, WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE, type WebGpuRequiredLimits, type WebGpuPowerPreference, } from './webgpu-limits';
40
- export { loadScene, loadSceneFile, type SplatLoadOptions, type SplatFileLoadOptions, } from './load-scene';
41
- export { ChunkLoader } from './chunk-loader';
42
- export { ChunkFetchScheduler, type ChunkFetchSchedulerOptions, type ChunkFetchClient, type ChunkFetchHandle, type ChunkFetchKind, } from './chunk-fetch-scheduler';
43
- export { ChunkCacheBudget, type ChunkCacheBudgetOptions, type ChunkCacheClient, type ChunkCacheHandle, } from './chunk-cache-budget';
44
- export { SplatLoadError, isAbortError, type SplatProgressCallback, type SplatFormat, type SplatSourceFormat, type StreamedSplatFormat, type SplatRequestOptions, type SplatLoadPhase, type SplatInputOptions, type ChunkFileFormat, } from './loading';
45
- export type { CollisionMeshTile } from './formats/lcc/collision-mesh';
46
- export type { SceneCollision, CollisionMeshDescriptor, EnvironmentTile } from './lod-source';
47
- export { httpDatasetSource, createLocalDataset } from './dataset-source';
48
- export type { SplatDatasetSource, LocalDataset } from './dataset-source';
40
+ export { recommendedWebGpuRequiredLimits, supportsWebGpuPowerPreference, webGpuPowerPreferenceOptions, deviceMaxStorageBufferBindingSize, assertStorageBufferFitsDevice, WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE, type WebGpuRequiredLimits, type WebGpuPowerPreference, } from './webgpu-limits';
49
41
  export type { SplatData, SplatShData, SplatPackedShData } from './splat-data';
50
- export { createSelectionVolume, selectInData, countInData, type SelectionVolume, type SelectionVolumeKind, type SelectionVolumeOptions, } from './selection-volume';
51
- export { partitionSplatData, type SplatPartition } from './splat-partition';
42
+ export type { SplatDataFormat, StreamedSplatFormat } from './loading';
52
43
  export type { SplatModifier, SplatContext, SplatOutputs, ModifierStackTarget, } from './splat-modifier';
53
44
  export { ModifierSlots } from './splat-modifier';
54
45
  export { setVlamLogHandler, type VlamLogHandler, type VlamLogLevel } from './logging';