@voluma/vlam 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/chunk-loader-DlNwggfG.js.map +1 -1
- package/dist/{index.d.ts → core/index.d.ts} +1 -1
- package/dist/{splat-budget.d.ts → core/splat-budget.d.ts} +1 -1
- package/dist/{splat-data.d.ts → core/splat-data.d.ts} +1 -1
- package/dist/{splat-mesh-types.d.ts → core/splat-mesh-types.d.ts} +5 -2
- package/dist/{effects.d.ts → effects/index.d.ts} +3 -3
- package/dist/effects.js.map +1 -1
- package/dist/formats/ksplat/parse-ksplat.d.ts +1 -1
- package/dist/formats/ksplat.js.map +1 -1
- package/dist/formats/lcc/collision-mesh.d.ts +2 -2
- package/dist/formats/lcc/collision-partition.d.ts +1 -1
- package/dist/formats/lcc/lcc.d.ts +3 -3
- package/dist/formats/lcc/lcc2.d.ts +2 -2
- package/dist/formats/lcc/parse-lcc.d.ts +1 -1
- package/dist/formats/lcc.js.map +1 -1
- package/dist/formats/ply/parse-compressed-ply.d.ts +2 -2
- package/dist/formats/ply/parse-splat-ply.d.ts +2 -2
- package/dist/formats/ply.js.map +1 -1
- package/dist/formats/rad/frontier-worker-protocol.d.ts +8 -1
- package/dist/formats/rad/index.d.ts +1 -1
- package/dist/formats/rad/parse-rad.d.ts +1 -1
- package/dist/formats/rad/rad-foveated-source.d.ts +3 -3
- package/dist/formats/rad/rad.d.ts +5 -5
- package/dist/formats/rad.js.map +1 -1
- package/dist/formats/sog/parse-sog.d.ts +2 -2
- package/dist/formats/sog.js.map +1 -1
- package/dist/formats/splat/parse-splat.d.ts +1 -1
- package/dist/formats/splat.js.map +1 -1
- package/dist/formats/spz/parse-spz.d.ts +1 -1
- package/dist/formats/spz.js.map +1 -1
- package/dist/half-float-Cs4FDq8Q.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/{chunk-loader.d.ts → loaders/chunk-loader.d.ts} +2 -2
- package/dist/{load-splat-data.d.ts → loaders/load-splat-data.d.ts} +1 -1
- package/dist/{load-worker-protocol.d.ts → loaders/load-worker-protocol.d.ts} +2 -2
- package/dist/loaders.js.map +1 -1
- package/dist/loading-wLo8vRbA.js.map +1 -1
- package/dist/lod-scheduler-B0a_uBlv.js.map +1 -1
- package/dist/logging-BfPdd7NJ.js.map +1 -1
- package/dist/ply-header-B9WllbPg.js.map +1 -1
- package/dist/{radix-sorter-R0FCSqKu.js → radix-sorter-BrbUg_CV.js} +2 -2
- package/dist/radix-sorter-BrbUg_CV.js.map +1 -0
- package/dist/{relighting-CSQF3UcL.js → relighting-Tiwep8yd.js} +302 -294
- package/dist/relighting-Tiwep8yd.js.map +1 -0
- package/dist/{selection-volume.d.ts → selection/selection-volume.d.ts} +1 -1
- package/dist/{splat-partition.d.ts → selection/splat-partition.d.ts} +1 -1
- package/dist/selection.js.map +1 -1
- package/dist/sh-pack-D5wAe5gg.js.map +1 -1
- package/dist/splat-budget-DuVZspPQ.js.map +1 -1
- package/dist/splat-data-BZM5dhhx.js.map +1 -1
- package/dist/{splat-mesh-aQdcWA6v.js → splat-mesh-CsLOQb08.js} +3 -3
- package/dist/splat-mesh-CsLOQb08.js.map +1 -0
- package/dist/splat-mesh-types-BZIko-_9.js.map +1 -1
- package/dist/{static-lod-splat-mesh.d.ts → static-lod/static-lod-splat-mesh.d.ts} +2 -2
- package/dist/{static-lod-worker-protocol.d.ts → static-lod/static-lod-worker-protocol.d.ts} +1 -1
- package/dist/{static-lod.d.ts → static-lod/static-lod.d.ts} +1 -1
- package/dist/static-lod.js +1 -1
- package/dist/static-lod.js.map +1 -1
- package/dist/{dataset-source.d.ts → streaming/dataset-source.d.ts} +1 -1
- package/dist/{streaming.d.ts → streaming/index.d.ts} +3 -3
- package/dist/{lod-source.d.ts → streaming/lod-source.d.ts} +4 -4
- package/dist/{streamed-splat-mesh-utils.d.ts → streaming/streamed-splat-mesh-utils.d.ts} +2 -2
- package/dist/{streamed-splat-mesh.d.ts → streaming/streamed-splat-mesh.d.ts} +45 -5
- package/dist/streaming.js +844 -810
- package/dist/streaming.js.map +1 -1
- package/dist/{unified-splat-mesh.d.ts → unified/unified-splat-mesh.d.ts} +13 -6
- package/dist/{work-buffer-gather.d.ts → unified/work-buffer-gather.d.ts} +5 -5
- package/dist/{work-buffer-material.d.ts → unified/work-buffer-material.d.ts} +1 -1
- package/dist/unified.js +280 -272
- package/dist/unified.js.map +1 -1
- package/dist/webgpu-limits-Y3CI_6_6.js.map +1 -1
- package/package.json +9 -9
- package/dist/radix-sorter-R0FCSqKu.js.map +0 -1
- package/dist/relighting-CSQF3UcL.js.map +0 -1
- package/dist/splat-mesh-aQdcWA6v.js.map +0 -1
- /package/dist/{compute-sorter.d.ts → core/compute-sorter.d.ts} +0 -0
- /package/dist/{create-splat-renderer.d.ts → core/create-splat-renderer.d.ts} +0 -0
- /package/dist/{depth-of-field.d.ts → core/depth-of-field.d.ts} +0 -0
- /package/dist/{half-float.d.ts → core/half-float.d.ts} +0 -0
- /package/dist/{logging.d.ts → core/logging.d.ts} +0 -0
- /package/dist/{merged-splat-mesh.d.ts → core/merged-splat-mesh.d.ts} +0 -0
- /package/dist/{orientation.d.ts → core/orientation.d.ts} +0 -0
- /package/dist/{radix-sort.d.ts → core/radix-sort.d.ts} +0 -0
- /package/dist/{radix-sorter.d.ts → core/radix-sorter.d.ts} +0 -0
- /package/dist/{relighting-pass.d.ts → core/relighting-pass.d.ts} +0 -0
- /package/dist/{relighting.d.ts → core/relighting.d.ts} +0 -0
- /package/dist/{sh-pack.d.ts → core/sh-pack.d.ts} +0 -0
- /package/dist/{sort-scheduler.d.ts → core/sort-scheduler.d.ts} +0 -0
- /package/dist/{sort-worker-protocol.d.ts → core/sort-worker-protocol.d.ts} +0 -0
- /package/dist/{sorter.d.ts → core/sorter.d.ts} +0 -0
- /package/dist/{source-transform.d.ts → core/source-transform.d.ts} +0 -0
- /package/dist/{splat-depth-pack.d.ts → core/splat-depth-pack.d.ts} +0 -0
- /package/dist/{splat-mesh-material.d.ts → core/splat-mesh-material.d.ts} +0 -0
- /package/dist/{splat-mesh-picking.d.ts → core/splat-mesh-picking.d.ts} +0 -0
- /package/dist/{splat-mesh-pool.d.ts → core/splat-mesh-pool.d.ts} +0 -0
- /package/dist/{splat-mesh.d.ts → core/splat-mesh.d.ts} +0 -0
- /package/dist/{splat-modifier-stack.d.ts → core/splat-modifier-stack.d.ts} +0 -0
- /package/dist/{splat-modifier.d.ts → core/splat-modifier.d.ts} +0 -0
- /package/dist/{splat-query.d.ts → core/splat-query.d.ts} +0 -0
- /package/dist/{splat-sort-bounds.d.ts → core/splat-sort-bounds.d.ts} +0 -0
- /package/dist/{storage-attribute-mirror.d.ts → core/storage-attribute-mirror.d.ts} +0 -0
- /package/dist/{webgpu-limits.d.ts → core/webgpu-limits.d.ts} +0 -0
- /package/dist/{worker-sorter.d.ts → core/worker-sorter.d.ts} +0 -0
- /package/dist/{xr-view.d.ts → core/xr-view.d.ts} +0 -0
- /package/dist/{loaders.d.ts → loaders/index.d.ts} +0 -0
- /package/dist/{loading.d.ts → loaders/loading.d.ts} +0 -0
- /package/dist/{ply-header.d.ts → loaders/ply-header.d.ts} +0 -0
- /package/dist/{selection.d.ts → selection/index.d.ts} +0 -0
- /package/dist/{static-lod-entry.d.ts → static-lod/index.d.ts} +0 -0
- /package/dist/{budget-governor.d.ts → streaming/budget-governor.d.ts} +0 -0
- /package/dist/{camera-budget-governor.d.ts → streaming/camera-budget-governor.d.ts} +0 -0
- /package/dist/{chunk-cache-budget.d.ts → streaming/chunk-cache-budget.d.ts} +0 -0
- /package/dist/{chunk-fetch-scheduler.d.ts → streaming/chunk-fetch-scheduler.d.ts} +0 -0
- /package/dist/{lod-manifest.d.ts → streaming/lod-manifest.d.ts} +0 -0
- /package/dist/{lod-scheduler.d.ts → streaming/lod-scheduler.d.ts} +0 -0
- /package/dist/{unified.d.ts → unified/index.d.ts} +0 -0
- /package/dist/{unified-work-buffer.d.ts → unified/unified-work-buffer.d.ts} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type SplatData } from '../../splat-data';
|
|
2
|
-
import { type PlyHeader } from '../../ply-header';
|
|
1
|
+
import { type SplatData } from '../../core/splat-data';
|
|
2
|
+
import { type PlyHeader } from '../../loaders/ply-header';
|
|
3
3
|
/**
|
|
4
4
|
* Whether this PLY is the compressed flavor rather than the uncompressed
|
|
5
5
|
* 3DGS one. Keyed on the packed vertex properties, which no uncompressed
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type SplatData } from '../../splat-data';
|
|
2
|
-
import { type SplatProgressCallback } from '../../loading';
|
|
1
|
+
import { type SplatData } from '../../core/splat-data';
|
|
2
|
+
import { type SplatProgressCallback } from '../../loaders/loading';
|
|
3
3
|
/**
|
|
4
4
|
* Parses a binary Gaussian splat PLY file into flat, GPU-friendly arrays -
|
|
5
5
|
* either the uncompressed 3DGS flavor or the compressed SuperSplat one, which
|
package/dist/formats/ply.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ply.js","sources":["../../src/lib/formats/ply/parse-compressed-ply.ts","../../src/lib/formats/ply/parse-splat-ply.ts"],"sourcesContent":["import { writeCovariance, type SplatData, type SplatPackedShData } from '../../splat-data';\nimport {\n assertPlyElementFits,\n assertPlyPropertyTypes,\n plyPropertyOffset,\n type PlyHeader,\n} from '../../ply-header';\n\n/**\n * Decoder for the compressed PLY flavor written by SuperSplat / PlayCanvas\n * (and by `@playcanvas/splat-transform -c`). It is the same container as the\n * uncompressed 3DGS PLY but quantized ~10× smaller:\n *\n * - `element chunk` - one record per 256 splats, holding the float min/max\n * bounds those splats' quantized values are interpolated across.\n * - `element vertex` - four `uint32` per splat: position and scale packed\n * 11-10-11, rotation as smallest-three (2-bit selector + 3×10 bits), and\n * color as RGBA8.\n * - `element sh` - optional per-splat higher-order SH, one `uchar` per\n * coefficient per channel, channel-major, quantized across [-4, 4].\n *\n * Spec: PlayCanvas's `GSplatCompressedData` is the reference implementation\n * (MIT). The decode below matches its unpacking and dequantization; the\n * implementation is original.\n */\n\n/** Splats per `chunk` record - fixed by the format. */\nconst SPLATS_PER_CHUNK = 256;\n\n/** Chunk bounds every compressed PLY carries. */\nconst CHUNK_BOUNDS = [\n 'min_x',\n 'min_y',\n 'min_z',\n 'max_x',\n 'max_y',\n 'max_z',\n 'min_scale_x',\n 'min_scale_y',\n 'min_scale_z',\n 'max_scale_x',\n 'max_scale_y',\n 'max_scale_z',\n] as const;\n\n/** Optional per-chunk color bounds (absent in the format's earliest writers). */\nconst CHUNK_COLOR_BOUNDS = ['min_r', 'min_g', 'min_b', 'max_r', 'max_g', 'max_b'] as const;\n\n/** The four packed words that make a compressed vertex. */\nconst VERTEX_PROPERTIES = [\n 'packed_position',\n 'packed_rotation',\n 'packed_scale',\n 'packed_color',\n] as const;\n\n/** SH coefficient count per channel, indexed by band. */\nconst SH_COEFFICIENTS: Record<number, number> = { 1: 3, 2: 8, 3: 15 };\n\n/** Bytes the `sh` element's uchar values dequantize across. */\nconst SH_MIN = -4;\nconst SH_MAX = 4;\n\n/**\n * Whether this PLY is the compressed flavor rather than the uncompressed\n * 3DGS one. Keyed on the packed vertex properties, which no uncompressed\n * export has.\n */\nexport function isCompressedPly(header: PlyHeader): boolean {\n const vertex = header.element('vertex');\n return vertex !== undefined && vertex.properties.has('packed_position');\n}\n\nfunction unpackUnorm(value: number, bits: number): number {\n const max = (1 << bits) - 1;\n return (value & max) / max;\n}\n\nfunction lerp(min: number, max: number, t: number): number {\n return min + (max - min) * t;\n}\n\n/**\n * Parses a compressed splat PLY into flat, GPU-friendly arrays.\n *\n * @param header - The already-parsed header (its element offsets locate the\n * chunk, vertex and sh records).\n * @throws {Error} if the chunk/vertex layout is not the compressed one.\n */\nexport function parseCompressedPly(buffer: ArrayBuffer, header: PlyHeader): SplatData {\n const chunks = header.element('chunk');\n const vertices = header.element('vertex');\n if (!chunks || !vertices) {\n throw new Error('Compressed PLY is missing its \"chunk\" or \"vertex\" element.');\n }\n assertPlyPropertyTypes(vertices, VERTEX_PROPERTIES, ['uint', 'uint32'], 'Compressed PLY vertex');\n assertPlyPropertyTypes(chunks, CHUNK_BOUNDS, ['float', 'float32'], 'Compressed PLY chunk');\n // Color bounds arrived later in the format's life; without them the packed\n // color is already the final value (the reference skips the lerp).\n const hasColorBounds = chunks.properties.has('min_r');\n if (hasColorBounds) {\n assertPlyPropertyTypes(\n chunks,\n CHUNK_COLOR_BOUNDS,\n ['float', 'float32'],\n 'Compressed PLY chunk',\n );\n }\n\n assertPlyElementFits(chunks, buffer.byteLength);\n assertPlyElementFits(vertices, buffer.byteLength);\n\n const count = vertices.count;\n const neededChunks = Math.ceil(count / SPLATS_PER_CHUNK);\n if (chunks.count < neededChunks) {\n throw new Error(\n `Compressed PLY declares ${chunks.count} chunks, too few for ${count} splats ` +\n `(needs ${neededChunks} at ${SPLATS_PER_CHUNK} splats per chunk).`,\n );\n }\n\n const chunkView = new DataView(buffer, chunks.offset);\n const vertexView = new DataView(buffer, vertices.offset);\n const chunkAt = (chunk: number, property: string): number =>\n chunkView.getFloat32(chunk * chunks.stride + plyPropertyOffset(chunks, property), true);\n\n const positionOffset = plyPropertyOffset(vertices, 'packed_position');\n const rotationOffset = plyPropertyOffset(vertices, 'packed_rotation');\n const scaleOffset = plyPropertyOffset(vertices, 'packed_scale');\n const colorOffset = plyPropertyOffset(vertices, 'packed_color');\n\n const positions = new Float32Array(count * 3);\n const colors = new Uint8Array(count * 4);\n const covariances = new Float32Array(count * 6);\n\n for (let chunk = 0; chunk < neededChunks; chunk++) {\n // Chunk bounds are read once per 256 splats rather than per splat.\n const minX = chunkAt(chunk, 'min_x'),\n minY = chunkAt(chunk, 'min_y'),\n minZ = chunkAt(chunk, 'min_z');\n const maxX = chunkAt(chunk, 'max_x'),\n maxY = chunkAt(chunk, 'max_y'),\n maxZ = chunkAt(chunk, 'max_z');\n const minSx = chunkAt(chunk, 'min_scale_x'),\n minSy = chunkAt(chunk, 'min_scale_y'),\n minSz = chunkAt(chunk, 'min_scale_z');\n const maxSx = chunkAt(chunk, 'max_scale_x'),\n maxSy = chunkAt(chunk, 'max_scale_y'),\n maxSz = chunkAt(chunk, 'max_scale_z');\n const minR = hasColorBounds ? chunkAt(chunk, 'min_r') : 0;\n const minG = hasColorBounds ? chunkAt(chunk, 'min_g') : 0;\n const minB = hasColorBounds ? chunkAt(chunk, 'min_b') : 0;\n const maxR = hasColorBounds ? chunkAt(chunk, 'max_r') : 1;\n const maxG = hasColorBounds ? chunkAt(chunk, 'max_g') : 1;\n const maxB = hasColorBounds ? chunkAt(chunk, 'max_b') : 1;\n\n const first = chunk * SPLATS_PER_CHUNK;\n const last = Math.min(first + SPLATS_PER_CHUNK, count);\n for (let i = first; i < last; i++) {\n const base = i * vertices.stride;\n\n // Position: 11-10-11 unorm, interpolated across the chunk's bounds.\n const packedPosition = vertexView.getUint32(base + positionOffset, true);\n positions[i * 3 + 0] = lerp(minX, maxX, unpackUnorm(packedPosition >>> 21, 11));\n positions[i * 3 + 1] = lerp(minY, maxY, unpackUnorm(packedPosition >>> 11, 10));\n positions[i * 3 + 2] = lerp(minZ, maxZ, unpackUnorm(packedPosition, 11));\n\n // Color: RGB is the base color in 0..1 (the 0.5 + Y₀₀·f_dc the\n // uncompressed flavor stores unevaluated), alpha the already-activated\n // opacity - so neither needs the PLY parser's SH/sigmoid conversion.\n const packedColor = vertexView.getUint32(base + colorOffset, true);\n const r = unpackUnorm(packedColor >>> 24, 8);\n const g = unpackUnorm(packedColor >>> 16, 8);\n const b = unpackUnorm(packedColor >>> 8, 8);\n const a = unpackUnorm(packedColor, 8);\n colors[i * 4 + 0] = toByte(hasColorBounds ? lerp(minR, maxR, r) : r);\n colors[i * 4 + 1] = toByte(hasColorBounds ? lerp(minG, maxG, g) : g);\n colors[i * 4 + 2] = toByte(hasColorBounds ? lerp(minB, maxB, b) : b);\n colors[i * 4 + 3] = toByte(a);\n\n // Scale: 11-10-11 unorm across the chunk's bounds, in log space.\n const packedScale = vertexView.getUint32(base + scaleOffset, true);\n const scaleX = Math.exp(lerp(minSx, maxSx, unpackUnorm(packedScale >>> 21, 11)));\n const scaleY = Math.exp(lerp(minSy, maxSy, unpackUnorm(packedScale >>> 11, 10)));\n const scaleZ = Math.exp(lerp(minSz, maxSz, unpackUnorm(packedScale, 11)));\n\n // Rotation: \"smallest three\". The 2-bit selector says which component\n // was dropped (the largest, rebuilt from unit length); the other three\n // are 10-bit unorms over [-√½, +√½]. Component order is (w, x, y, z) -\n // the same convention the SOG decoder uses.\n const packedRotation = vertexView.getUint32(base + rotationOffset, true);\n const q0 = (unpackUnorm(packedRotation >>> 20, 10) - 0.5) * Math.SQRT2;\n const q1 = (unpackUnorm(packedRotation >>> 10, 10) - 0.5) * Math.SQRT2;\n const q2 = (unpackUnorm(packedRotation, 10) - 0.5) * Math.SQRT2;\n const largest = Math.sqrt(Math.max(0, 1 - (q0 * q0 + q1 * q1 + q2 * q2)));\n const [qw, qx, qy, qz] =\n packedRotation >>> 30 === 0\n ? [largest, q0, q1, q2]\n : packedRotation >>> 30 === 1\n ? [q0, largest, q1, q2]\n : packedRotation >>> 30 === 2\n ? [q0, q1, largest, q2]\n : [q0, q1, q2, largest];\n\n writeCovariance(covariances, i, scaleX, scaleY, scaleZ, qw, qx, qy, qz);\n }\n }\n\n const shPacked = decodeSh(buffer, header, count);\n return { count, positions, colors, covariances, ...(shPacked ? { shPacked } : {}) };\n}\n\nfunction toByte(value: number): number {\n return Math.max(0, Math.min(255, Math.round(value * 255)));\n}\n\n/**\n * Decodes the optional `sh` element into the per-splat packed form.\n *\n * The file stores one uchar per (channel, coefficient), channel-major:\n * `f_rest_0…n-1` are red, then green, then blue. The packed form is\n * coefficient-major with all three channels in one word (11-10-11), so this\n * transposes as it goes. Both quantizations are unit fractions of the same\n * [-4, 4] range, so re-quantizing 8-bit into 10/11-bit fields is exact to\n * well within the source's own resolution.\n */\nfunction decodeSh(\n buffer: ArrayBuffer,\n header: PlyHeader,\n count: number,\n): SplatPackedShData | undefined {\n const sh = header.element('sh');\n if (!sh || sh.count === 0) return undefined;\n if (sh.count !== count) {\n throw new Error(`Compressed PLY \"sh\" element has ${sh.count} records for ${count} splats.`);\n }\n\n const coefficients = sh.propertyNames.length / 3;\n const bands = Number(\n Object.keys(SH_COEFFICIENTS).find((band) => SH_COEFFICIENTS[Number(band)] === coefficients),\n );\n if (!bands) {\n throw new Error(\n `Compressed PLY \"sh\" element has ${sh.propertyNames.length} properties, which is not ` +\n '3, 8 or 15 coefficients across three channels.',\n );\n }\n const names = Array.from({ length: coefficients * 3 }, (_, i) => `f_rest_${i}`);\n assertPlyPropertyTypes(sh, names, ['uchar', 'uint8'], 'Compressed PLY sh');\n assertPlyElementFits(sh, buffer.byteLength);\n\n const bytes = new Uint8Array(buffer, sh.offset);\n const offsets = names.map((name) => plyPropertyOffset(sh, name));\n const packed = new Uint32Array(count * coefficients);\n for (let i = 0; i < count; i++) {\n const base = i * sh.stride;\n for (let c = 0; c < coefficients; c++) {\n // Channel-major in the file: red coefficients, then green, then blue.\n const r = (bytes[base + (offsets[c] as number)] as number) / 255;\n const g = (bytes[base + (offsets[coefficients + c] as number)] as number) / 255;\n const b = (bytes[base + (offsets[2 * coefficients + c] as number)] as number) / 255;\n packed[i * coefficients + c] =\n (Math.round(r * 2047) | (Math.round(g * 1023) << 11) | (Math.round(b * 2047) << 21)) >>> 0;\n }\n }\n\n return {\n bands: bands as 1 | 2 | 3,\n packed,\n range: { min: [SH_MIN, SH_MIN, SH_MIN], max: [SH_MAX, SH_MAX, SH_MAX] },\n };\n}\n","import { packShCoefficients } from '../../sh-pack';\nimport { SH_C0, writeCovariance, type SplatData } from '../../splat-data';\nimport { isCompressedPly, parseCompressedPly } from './parse-compressed-ply';\nimport { readWholeFile, type SplatProgressCallback } from '../../loading';\nimport {\n assertPlyElementFits,\n assertPlyPropertyTypes,\n parsePlyHeader,\n plyPropertyOffset,\n type PlyElement,\n} from '../../ply-header';\n\n/**\n * Parser for the \"INRIA flavor\" Gaussian splat PLY format: a binary\n * little-endian PLY whose vertices carry position, spherical-harmonics\n * color (f_dc_*), opacity, scale and rotation properties.\n *\n * `.ply` is a container rather than one format, so {@link parseSplatPly} also\n * dispatches to the compressed flavor (see `parse-compressed-ply.ts`), which\n * SuperSplat and `@playcanvas/splat-transform -c` write.\n *\n * Only the math from the 3DGS paper (Kerbl et al., SIGGRAPH 2023) is used\n * here; the implementation is original.\n */\n\n/** Property names this parser requires, in no particular order. */\nconst REQUIRED_PROPERTIES = [\n 'x',\n 'y',\n 'z',\n 'f_dc_0',\n 'f_dc_1',\n 'f_dc_2',\n 'opacity',\n 'scale_0',\n 'scale_1',\n 'scale_2',\n 'rot_0',\n 'rot_1',\n 'rot_2',\n 'rot_3',\n] as const;\n\n/** Higher-order SH coefficient counts per band (channel-major `f_rest_*`). */\nconst REST_COEFFICIENTS: Record<number, 1 | 2 | 3> = { 3: 1, 8: 2, 15: 3 };\n\nfunction sigmoid(x: number): number {\n return 1 / (1 + Math.exp(-x));\n}\n\n/**\n * Parses a binary Gaussian splat PLY file into flat, GPU-friendly arrays -\n * either the uncompressed 3DGS flavor or the compressed SuperSplat one, which\n * is detected from the header.\n *\n * For the uncompressed flavor the spherical harmonics DC term is baked into an\n * 8-bit RGB color; optional `f_rest_*` higher-order coefficients are packed\n * into per-splat `shPacked`. Opacity is passed through a sigmoid, and per-splat\n * (log-space) + rotation (quaternion) are folded into a 3D covariance matrix -\n * all per the 3DGS paper's parameterization.\n *\n * @throws {Error} if the file is not a binary little-endian splat PLY.\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 function parseSplatPly(buffer: ArrayBuffer): SplatData {\n const header = parsePlyHeader(buffer);\n if (isCompressedPly(header)) return parseCompressedPly(buffer, header);\n\n const vertices = requireSplatVertexElement(header);\n assertPlyElementFits(vertices, buffer.byteLength);\n\n const out = allocate(vertices.count);\n const rest = restLayout(vertices);\n const coefficients = rest ? new Float32Array(vertices.count * rest.coefficients * 3) : null;\n decodeRecords(\n new DataView(buffer, vertices.offset),\n vertexOffsets(vertices),\n vertices.stride,\n 0,\n 0,\n vertices.count,\n out,\n );\n if (rest && coefficients) {\n decodeRestRecords(\n new DataView(buffer, vertices.offset),\n vertices.stride,\n 0,\n 0,\n vertices.count,\n rest,\n coefficients,\n );\n }\n return finalizePlyDecode(vertices.count, out, rest, coefficients);\n}\n\n/**\n * Bytes read per slice while streaming. Large enough that a multi-gigabyte\n * file costs tens of reads rather than thousands, small enough to stay far\n * below the 2 GiB an ArrayBuffer can hold.\n */\nconst STREAM_WINDOW_BYTES = 64 * 1024 * 1024;\n\n/** Bytes of a PLY that the header is assumed to fit inside. */\nconst HEADER_WINDOW_BYTES = 64 * 1024;\n\n/**\n * Parses a local splat PLY without ever holding the whole file.\n *\n * A browser cannot allocate an ArrayBuffer over 2 GiB, so `file.arrayBuffer()`\n * fails outright on a raw 3DGS export of a few million splats (a full-SH\n * `point_cloud.ply` runs ~248 bytes per splat). The uncompressed flavor has\n * fixed-stride records, so it is read a window at a time. Higher-order\n * `f_rest_*` coefficients ride in the same stride and are decoded per window.\n *\n * The compressed flavor is read whole: it is ~10× smaller, and its chunked\n * layout would need a second pass to stream for no practical gain.\n *\n * @param options.windowBytes - Bytes read per slice; defaults to 64 MiB. Lower\n * trades read count for peak memory.\n * @param options.onProgress - Called after each window with the vertex bytes\n * decoded so far and the total. A multi-gigabyte decode takes tens of seconds,\n * which needs a determinate indicator rather than a spinner.\n * @throws {Error} if the file is not a binary little-endian splat PLY;\n * a `DOMException` named `AbortError` when cancelled.\n */\nexport async function parseSplatPlyFile(\n file: File,\n options: { signal?: AbortSignal; windowBytes?: number; onProgress?: SplatProgressCallback } = {},\n): Promise<SplatData> {\n const { signal, onProgress } = options;\n const windowBytes = options.windowBytes ?? STREAM_WINDOW_BYTES;\n const head = await file.slice(0, Math.min(file.size, HEADER_WINDOW_BYTES)).arrayBuffer();\n const header = parsePlyHeader(head);\n if (isCompressedPly(header)) return parseCompressedPly(await readWholeFile(file), header);\n\n const vertices = requireSplatVertexElement(header);\n assertPlyElementFits(vertices, file.size);\n signal?.throwIfAborted();\n\n const count = vertices.count;\n const stride = vertices.stride;\n const out = allocate(count);\n const offsets = vertexOffsets(vertices);\n const rest = restLayout(vertices);\n const coefficients = rest ? new Float32Array(count * rest.coefficients * 3) : null;\n // At least one whole record per read: a window never splits a record, so\n // the decoder always sees complete ones.\n const perWindow = Math.max(1, Math.floor(windowBytes / stride));\n\n const totalBytes = count * stride;\n onProgress?.(0, totalBytes);\n for (let first = 0; first < count; first += perWindow) {\n signal?.throwIfAborted();\n const last = Math.min(first + perWindow, count);\n const slice = file.slice(vertices.offset + first * stride, vertices.offset + last * stride);\n const view = new DataView(await slice.arrayBuffer());\n // The window's byte 0 is record `first`, so the decoder rebases onto it.\n decodeRecords(view, offsets, stride, first, first, last, out);\n if (rest && coefficients)\n decodeRestRecords(view, stride, first, first, last, rest, coefficients);\n onProgress?.(last * stride, totalBytes);\n }\n return finalizePlyDecode(count, out, rest, coefficients);\n}\n\n/** The vertex element of a splat PLY, with its required properties checked. */\nfunction requireSplatVertexElement(header: ReturnType<typeof parsePlyHeader>): PlyElement {\n const vertices = header.element('vertex');\n if (!vertices) throw new Error('PLY file is not a Gaussian splat file: no \"vertex\" element.');\n assertPlyPropertyTypes(\n vertices,\n REQUIRED_PROPERTIES,\n ['float', 'float32'],\n 'PLY file is not a Gaussian splat file',\n );\n return vertices;\n}\n\ninterface RestLayout {\n bands: 1 | 2 | 3;\n coefficients: number;\n offsets: readonly number[];\n}\n\n/** Resolves optional `f_rest_*` higher-order SH in a 3DGS vertex element. */\nfunction restLayout(vertices: PlyElement): RestLayout | null {\n const restNames = vertices.propertyNames.filter((name) => name.startsWith('f_rest_'));\n if (restNames.length === 0) return null;\n if (restNames.length % 3 !== 0) {\n throw new Error(\n `PLY vertex has ${restNames.length} f_rest_* properties, which is not divisible by three.`,\n );\n }\n const coefficients = restNames.length / 3;\n const bands = REST_COEFFICIENTS[coefficients];\n if (!bands) {\n throw new Error(\n `PLY vertex has ${coefficients} SH coefficients per channel; expected 3, 8 or 15.`,\n );\n }\n const names = Array.from({ length: coefficients * 3 }, (_, i) => `f_rest_${i}`);\n assertPlyPropertyTypes(vertices, names, ['float', 'float32'], 'PLY f_rest');\n const offsets = names.map((name) => plyPropertyOffset(vertices, name));\n return { bands, coefficients, offsets };\n}\n\nfunction finalizePlyDecode(\n count: number,\n out: SplatArrays,\n rest: RestLayout | null,\n coefficients: Float32Array | null,\n): SplatData {\n const shPacked =\n rest && coefficients ? packShCoefficients(coefficients, count, rest.bands) : undefined;\n return { count, ...out, ...(shPacked ? { shPacked } : {}) };\n}\n\ninterface SplatArrays {\n positions: Float32Array;\n colors: Uint8Array;\n covariances: Float32Array;\n}\n\nfunction allocate(count: number): SplatArrays {\n return {\n positions: new Float32Array(count * 3),\n colors: new Uint8Array(count * 4),\n covariances: new Float32Array(count * 6),\n };\n}\n\n/** Byte offsets of the properties the decoder reads, within one record. */\ninterface VertexOffsets {\n x: number;\n y: number;\n z: number;\n r: number;\n g: number;\n b: number;\n alpha: number;\n s0: number;\n s1: number;\n s2: number;\n q0: number;\n q1: number;\n q2: number;\n q3: number;\n}\n\nfunction vertexOffsets(vertices: PlyElement): VertexOffsets {\n const at = (name: string): number => plyPropertyOffset(vertices, name);\n return {\n x: at('x'),\n y: at('y'),\n z: at('z'),\n r: at('f_dc_0'),\n g: at('f_dc_1'),\n b: at('f_dc_2'),\n alpha: at('opacity'),\n s0: at('scale_0'),\n s1: at('scale_1'),\n s2: at('scale_2'),\n q0: at('rot_0'),\n q1: at('rot_1'),\n q2: at('rot_2'),\n q3: at('rot_3'),\n };\n}\n\n/**\n * Decodes records `[from, to)` into `out`, reading from a view whose byte 0\n * holds record `viewFirst`. Shared by the whole-buffer and streamed paths, so\n * both produce identical arrays.\n */\nfunction decodeRecords(\n view: DataView,\n o: VertexOffsets,\n stride: number,\n viewFirst: number,\n from: number,\n to: number,\n out: SplatArrays,\n): void {\n const { positions, colors, covariances } = out;\n for (let i = from; i < to; i++) {\n const base = (i - viewFirst) * stride;\n const readFloat = (offset: number): number => view.getFloat32(base + offset, true);\n\n positions[i * 3 + 0] = readFloat(o.x);\n positions[i * 3 + 1] = readFloat(o.y);\n positions[i * 3 + 2] = readFloat(o.z);\n\n // Degree-0 SH to linear color: c = 0.5 + Y₀₀ · f_dc.\n const toByte = (sh: number): number =>\n Math.max(0, Math.min(255, Math.round((0.5 + SH_C0 * sh) * 255)));\n colors[i * 4 + 0] = toByte(readFloat(o.r));\n colors[i * 4 + 1] = toByte(readFloat(o.g));\n colors[i * 4 + 2] = toByte(readFloat(o.b));\n colors[i * 4 + 3] = Math.round(sigmoid(readFloat(o.alpha)) * 255);\n\n // Scale is stored in log-space; rotation as a (w, x, y, z) quaternion.\n writeCovariance(\n covariances,\n i,\n Math.exp(readFloat(o.s0)),\n Math.exp(readFloat(o.s1)),\n Math.exp(readFloat(o.s2)),\n readFloat(o.q0),\n readFloat(o.q1),\n readFloat(o.q2),\n readFloat(o.q3),\n );\n }\n}\n\n/**\n * Decodes channel-major `f_rest_*` floats into coefficient-major RGB triples.\n */\nfunction decodeRestRecords(\n view: DataView,\n stride: number,\n viewFirst: number,\n from: number,\n to: number,\n rest: RestLayout,\n out: Float32Array,\n): void {\n const { coefficients, offsets } = rest;\n for (let i = from; i < to; i++) {\n const base = (i - viewFirst) * stride;\n const dest = i * coefficients * 3;\n for (let c = 0; c < coefficients; c++) {\n out[dest + c * 3 + 0] = view.getFloat32(base + (offsets[c] as number), true);\n out[dest + c * 3 + 1] = view.getFloat32(base + (offsets[coefficients + c] as number), true);\n out[dest + c * 3 + 2] = view.getFloat32(\n base + (offsets[2 * coefficients + c] as number),\n true,\n );\n }\n }\n}\n"],"names":["SPLATS_PER_CHUNK","CHUNK_BOUNDS","CHUNK_COLOR_BOUNDS","VERTEX_PROPERTIES","SH_COEFFICIENTS","SH_MIN","SH_MAX","isCompressedPly","header","vertex","unpackUnorm","value","bits","max","lerp","min","t","parseCompressedPly","buffer","chunks","vertices","assertPlyPropertyTypes","hasColorBounds","assertPlyElementFits","count","neededChunks","chunkView","vertexView","chunkAt","chunk","property","plyPropertyOffset","positionOffset","rotationOffset","scaleOffset","colorOffset","positions","colors","covariances","minX","minY","minZ","maxX","maxY","maxZ","minSx","minSy","minSz","maxSx","maxSy","maxSz","minR","minG","minB","maxR","maxG","maxB","first","last","i","base","packedPosition","packedColor","r","g","b","a","toByte","packedScale","scaleX","scaleY","scaleZ","packedRotation","q0","q1","q2","largest","qw","qx","qy","qz","writeCovariance","shPacked","decodeSh","sh","coefficients","bands","band","names","_","bytes","offsets","name","packed","c","REQUIRED_PROPERTIES","REST_COEFFICIENTS","sigmoid","x","parseSplatPly","parsePlyHeader","requireSplatVertexElement","out","allocate","rest","restLayout","decodeRecords","vertexOffsets","decodeRestRecords","finalizePlyDecode","restNames","packShCoefficients","at","view","o","stride","viewFirst","from","to","readFloat","offset","SH_C0","dest"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ply.js","sources":["../../src/lib/formats/ply/parse-compressed-ply.ts","../../src/lib/formats/ply/parse-splat-ply.ts"],"sourcesContent":["import { writeCovariance, type SplatData, type SplatPackedShData } from '../../core/splat-data';\nimport {\n assertPlyElementFits,\n assertPlyPropertyTypes,\n plyPropertyOffset,\n type PlyHeader,\n} from '../../loaders/ply-header';\n\n/**\n * Decoder for the compressed PLY flavor written by SuperSplat / PlayCanvas\n * (and by `@playcanvas/splat-transform -c`). It is the same container as the\n * uncompressed 3DGS PLY but quantized ~10× smaller:\n *\n * - `element chunk` - one record per 256 splats, holding the float min/max\n * bounds those splats' quantized values are interpolated across.\n * - `element vertex` - four `uint32` per splat: position and scale packed\n * 11-10-11, rotation as smallest-three (2-bit selector + 3×10 bits), and\n * color as RGBA8.\n * - `element sh` - optional per-splat higher-order SH, one `uchar` per\n * coefficient per channel, channel-major, quantized across [-4, 4].\n *\n * Spec: PlayCanvas's `GSplatCompressedData` is the reference implementation\n * (MIT). The decode below matches its unpacking and dequantization; the\n * implementation is original.\n */\n\n/** Splats per `chunk` record - fixed by the format. */\nconst SPLATS_PER_CHUNK = 256;\n\n/** Chunk bounds every compressed PLY carries. */\nconst CHUNK_BOUNDS = [\n 'min_x',\n 'min_y',\n 'min_z',\n 'max_x',\n 'max_y',\n 'max_z',\n 'min_scale_x',\n 'min_scale_y',\n 'min_scale_z',\n 'max_scale_x',\n 'max_scale_y',\n 'max_scale_z',\n] as const;\n\n/** Optional per-chunk color bounds (absent in the format's earliest writers). */\nconst CHUNK_COLOR_BOUNDS = ['min_r', 'min_g', 'min_b', 'max_r', 'max_g', 'max_b'] as const;\n\n/** The four packed words that make a compressed vertex. */\nconst VERTEX_PROPERTIES = [\n 'packed_position',\n 'packed_rotation',\n 'packed_scale',\n 'packed_color',\n] as const;\n\n/** SH coefficient count per channel, indexed by band. */\nconst SH_COEFFICIENTS: Record<number, number> = { 1: 3, 2: 8, 3: 15 };\n\n/** Bytes the `sh` element's uchar values dequantize across. */\nconst SH_MIN = -4;\nconst SH_MAX = 4;\n\n/**\n * Whether this PLY is the compressed flavor rather than the uncompressed\n * 3DGS one. Keyed on the packed vertex properties, which no uncompressed\n * export has.\n */\nexport function isCompressedPly(header: PlyHeader): boolean {\n const vertex = header.element('vertex');\n return vertex !== undefined && vertex.properties.has('packed_position');\n}\n\nfunction unpackUnorm(value: number, bits: number): number {\n const max = (1 << bits) - 1;\n return (value & max) / max;\n}\n\nfunction lerp(min: number, max: number, t: number): number {\n return min + (max - min) * t;\n}\n\n/**\n * Parses a compressed splat PLY into flat, GPU-friendly arrays.\n *\n * @param header - The already-parsed header (its element offsets locate the\n * chunk, vertex and sh records).\n * @throws {Error} if the chunk/vertex layout is not the compressed one.\n */\nexport function parseCompressedPly(buffer: ArrayBuffer, header: PlyHeader): SplatData {\n const chunks = header.element('chunk');\n const vertices = header.element('vertex');\n if (!chunks || !vertices) {\n throw new Error('Compressed PLY is missing its \"chunk\" or \"vertex\" element.');\n }\n assertPlyPropertyTypes(vertices, VERTEX_PROPERTIES, ['uint', 'uint32'], 'Compressed PLY vertex');\n assertPlyPropertyTypes(chunks, CHUNK_BOUNDS, ['float', 'float32'], 'Compressed PLY chunk');\n // Color bounds arrived later in the format's life; without them the packed\n // color is already the final value (the reference skips the lerp).\n const hasColorBounds = chunks.properties.has('min_r');\n if (hasColorBounds) {\n assertPlyPropertyTypes(\n chunks,\n CHUNK_COLOR_BOUNDS,\n ['float', 'float32'],\n 'Compressed PLY chunk',\n );\n }\n\n assertPlyElementFits(chunks, buffer.byteLength);\n assertPlyElementFits(vertices, buffer.byteLength);\n\n const count = vertices.count;\n const neededChunks = Math.ceil(count / SPLATS_PER_CHUNK);\n if (chunks.count < neededChunks) {\n throw new Error(\n `Compressed PLY declares ${chunks.count} chunks, too few for ${count} splats ` +\n `(needs ${neededChunks} at ${SPLATS_PER_CHUNK} splats per chunk).`,\n );\n }\n\n const chunkView = new DataView(buffer, chunks.offset);\n const vertexView = new DataView(buffer, vertices.offset);\n const chunkAt = (chunk: number, property: string): number =>\n chunkView.getFloat32(chunk * chunks.stride + plyPropertyOffset(chunks, property), true);\n\n const positionOffset = plyPropertyOffset(vertices, 'packed_position');\n const rotationOffset = plyPropertyOffset(vertices, 'packed_rotation');\n const scaleOffset = plyPropertyOffset(vertices, 'packed_scale');\n const colorOffset = plyPropertyOffset(vertices, 'packed_color');\n\n const positions = new Float32Array(count * 3);\n const colors = new Uint8Array(count * 4);\n const covariances = new Float32Array(count * 6);\n\n for (let chunk = 0; chunk < neededChunks; chunk++) {\n // Chunk bounds are read once per 256 splats rather than per splat.\n const minX = chunkAt(chunk, 'min_x'),\n minY = chunkAt(chunk, 'min_y'),\n minZ = chunkAt(chunk, 'min_z');\n const maxX = chunkAt(chunk, 'max_x'),\n maxY = chunkAt(chunk, 'max_y'),\n maxZ = chunkAt(chunk, 'max_z');\n const minSx = chunkAt(chunk, 'min_scale_x'),\n minSy = chunkAt(chunk, 'min_scale_y'),\n minSz = chunkAt(chunk, 'min_scale_z');\n const maxSx = chunkAt(chunk, 'max_scale_x'),\n maxSy = chunkAt(chunk, 'max_scale_y'),\n maxSz = chunkAt(chunk, 'max_scale_z');\n const minR = hasColorBounds ? chunkAt(chunk, 'min_r') : 0;\n const minG = hasColorBounds ? chunkAt(chunk, 'min_g') : 0;\n const minB = hasColorBounds ? chunkAt(chunk, 'min_b') : 0;\n const maxR = hasColorBounds ? chunkAt(chunk, 'max_r') : 1;\n const maxG = hasColorBounds ? chunkAt(chunk, 'max_g') : 1;\n const maxB = hasColorBounds ? chunkAt(chunk, 'max_b') : 1;\n\n const first = chunk * SPLATS_PER_CHUNK;\n const last = Math.min(first + SPLATS_PER_CHUNK, count);\n for (let i = first; i < last; i++) {\n const base = i * vertices.stride;\n\n // Position: 11-10-11 unorm, interpolated across the chunk's bounds.\n const packedPosition = vertexView.getUint32(base + positionOffset, true);\n positions[i * 3 + 0] = lerp(minX, maxX, unpackUnorm(packedPosition >>> 21, 11));\n positions[i * 3 + 1] = lerp(minY, maxY, unpackUnorm(packedPosition >>> 11, 10));\n positions[i * 3 + 2] = lerp(minZ, maxZ, unpackUnorm(packedPosition, 11));\n\n // Color: RGB is the base color in 0..1 (the 0.5 + Y₀₀·f_dc the\n // uncompressed flavor stores unevaluated), alpha the already-activated\n // opacity - so neither needs the PLY parser's SH/sigmoid conversion.\n const packedColor = vertexView.getUint32(base + colorOffset, true);\n const r = unpackUnorm(packedColor >>> 24, 8);\n const g = unpackUnorm(packedColor >>> 16, 8);\n const b = unpackUnorm(packedColor >>> 8, 8);\n const a = unpackUnorm(packedColor, 8);\n colors[i * 4 + 0] = toByte(hasColorBounds ? lerp(minR, maxR, r) : r);\n colors[i * 4 + 1] = toByte(hasColorBounds ? lerp(minG, maxG, g) : g);\n colors[i * 4 + 2] = toByte(hasColorBounds ? lerp(minB, maxB, b) : b);\n colors[i * 4 + 3] = toByte(a);\n\n // Scale: 11-10-11 unorm across the chunk's bounds, in log space.\n const packedScale = vertexView.getUint32(base + scaleOffset, true);\n const scaleX = Math.exp(lerp(minSx, maxSx, unpackUnorm(packedScale >>> 21, 11)));\n const scaleY = Math.exp(lerp(minSy, maxSy, unpackUnorm(packedScale >>> 11, 10)));\n const scaleZ = Math.exp(lerp(minSz, maxSz, unpackUnorm(packedScale, 11)));\n\n // Rotation: \"smallest three\". The 2-bit selector says which component\n // was dropped (the largest, rebuilt from unit length); the other three\n // are 10-bit unorms over [-√½, +√½]. Component order is (w, x, y, z) -\n // the same convention the SOG decoder uses.\n const packedRotation = vertexView.getUint32(base + rotationOffset, true);\n const q0 = (unpackUnorm(packedRotation >>> 20, 10) - 0.5) * Math.SQRT2;\n const q1 = (unpackUnorm(packedRotation >>> 10, 10) - 0.5) * Math.SQRT2;\n const q2 = (unpackUnorm(packedRotation, 10) - 0.5) * Math.SQRT2;\n const largest = Math.sqrt(Math.max(0, 1 - (q0 * q0 + q1 * q1 + q2 * q2)));\n const [qw, qx, qy, qz] =\n packedRotation >>> 30 === 0\n ? [largest, q0, q1, q2]\n : packedRotation >>> 30 === 1\n ? [q0, largest, q1, q2]\n : packedRotation >>> 30 === 2\n ? [q0, q1, largest, q2]\n : [q0, q1, q2, largest];\n\n writeCovariance(covariances, i, scaleX, scaleY, scaleZ, qw, qx, qy, qz);\n }\n }\n\n const shPacked = decodeSh(buffer, header, count);\n return { count, positions, colors, covariances, ...(shPacked ? { shPacked } : {}) };\n}\n\nfunction toByte(value: number): number {\n return Math.max(0, Math.min(255, Math.round(value * 255)));\n}\n\n/**\n * Decodes the optional `sh` element into the per-splat packed form.\n *\n * The file stores one uchar per (channel, coefficient), channel-major:\n * `f_rest_0…n-1` are red, then green, then blue. The packed form is\n * coefficient-major with all three channels in one word (11-10-11), so this\n * transposes as it goes. Both quantizations are unit fractions of the same\n * [-4, 4] range, so re-quantizing 8-bit into 10/11-bit fields is exact to\n * well within the source's own resolution.\n */\nfunction decodeSh(\n buffer: ArrayBuffer,\n header: PlyHeader,\n count: number,\n): SplatPackedShData | undefined {\n const sh = header.element('sh');\n if (!sh || sh.count === 0) return undefined;\n if (sh.count !== count) {\n throw new Error(`Compressed PLY \"sh\" element has ${sh.count} records for ${count} splats.`);\n }\n\n const coefficients = sh.propertyNames.length / 3;\n const bands = Number(\n Object.keys(SH_COEFFICIENTS).find((band) => SH_COEFFICIENTS[Number(band)] === coefficients),\n );\n if (!bands) {\n throw new Error(\n `Compressed PLY \"sh\" element has ${sh.propertyNames.length} properties, which is not ` +\n '3, 8 or 15 coefficients across three channels.',\n );\n }\n const names = Array.from({ length: coefficients * 3 }, (_, i) => `f_rest_${i}`);\n assertPlyPropertyTypes(sh, names, ['uchar', 'uint8'], 'Compressed PLY sh');\n assertPlyElementFits(sh, buffer.byteLength);\n\n const bytes = new Uint8Array(buffer, sh.offset);\n const offsets = names.map((name) => plyPropertyOffset(sh, name));\n const packed = new Uint32Array(count * coefficients);\n for (let i = 0; i < count; i++) {\n const base = i * sh.stride;\n for (let c = 0; c < coefficients; c++) {\n // Channel-major in the file: red coefficients, then green, then blue.\n const r = (bytes[base + (offsets[c] as number)] as number) / 255;\n const g = (bytes[base + (offsets[coefficients + c] as number)] as number) / 255;\n const b = (bytes[base + (offsets[2 * coefficients + c] as number)] as number) / 255;\n packed[i * coefficients + c] =\n (Math.round(r * 2047) | (Math.round(g * 1023) << 11) | (Math.round(b * 2047) << 21)) >>> 0;\n }\n }\n\n return {\n bands: bands as 1 | 2 | 3,\n packed,\n range: { min: [SH_MIN, SH_MIN, SH_MIN], max: [SH_MAX, SH_MAX, SH_MAX] },\n };\n}\n","import { packShCoefficients } from '../../core/sh-pack';\nimport { SH_C0, writeCovariance, type SplatData } from '../../core/splat-data';\nimport { isCompressedPly, parseCompressedPly } from './parse-compressed-ply';\nimport { readWholeFile, type SplatProgressCallback } from '../../loaders/loading';\nimport {\n assertPlyElementFits,\n assertPlyPropertyTypes,\n parsePlyHeader,\n plyPropertyOffset,\n type PlyElement,\n} from '../../loaders/ply-header';\n\n/**\n * Parser for the \"INRIA flavor\" Gaussian splat PLY format: a binary\n * little-endian PLY whose vertices carry position, spherical-harmonics\n * color (f_dc_*), opacity, scale and rotation properties.\n *\n * `.ply` is a container rather than one format, so {@link parseSplatPly} also\n * dispatches to the compressed flavor (see `parse-compressed-ply.ts`), which\n * SuperSplat and `@playcanvas/splat-transform -c` write.\n *\n * Only the math from the 3DGS paper (Kerbl et al., SIGGRAPH 2023) is used\n * here; the implementation is original.\n */\n\n/** Property names this parser requires, in no particular order. */\nconst REQUIRED_PROPERTIES = [\n 'x',\n 'y',\n 'z',\n 'f_dc_0',\n 'f_dc_1',\n 'f_dc_2',\n 'opacity',\n 'scale_0',\n 'scale_1',\n 'scale_2',\n 'rot_0',\n 'rot_1',\n 'rot_2',\n 'rot_3',\n] as const;\n\n/** Higher-order SH coefficient counts per band (channel-major `f_rest_*`). */\nconst REST_COEFFICIENTS: Record<number, 1 | 2 | 3> = { 3: 1, 8: 2, 15: 3 };\n\nfunction sigmoid(x: number): number {\n return 1 / (1 + Math.exp(-x));\n}\n\n/**\n * Parses a binary Gaussian splat PLY file into flat, GPU-friendly arrays -\n * either the uncompressed 3DGS flavor or the compressed SuperSplat one, which\n * is detected from the header.\n *\n * For the uncompressed flavor the spherical harmonics DC term is baked into an\n * 8-bit RGB color; optional `f_rest_*` higher-order coefficients are packed\n * into per-splat `shPacked`. Opacity is passed through a sigmoid, and per-splat\n * (log-space) + rotation (quaternion) are folded into a 3D covariance matrix -\n * all per the 3DGS paper's parameterization.\n *\n * @throws {Error} if the file is not a binary little-endian splat PLY.\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 function parseSplatPly(buffer: ArrayBuffer): SplatData {\n const header = parsePlyHeader(buffer);\n if (isCompressedPly(header)) return parseCompressedPly(buffer, header);\n\n const vertices = requireSplatVertexElement(header);\n assertPlyElementFits(vertices, buffer.byteLength);\n\n const out = allocate(vertices.count);\n const rest = restLayout(vertices);\n const coefficients = rest ? new Float32Array(vertices.count * rest.coefficients * 3) : null;\n decodeRecords(\n new DataView(buffer, vertices.offset),\n vertexOffsets(vertices),\n vertices.stride,\n 0,\n 0,\n vertices.count,\n out,\n );\n if (rest && coefficients) {\n decodeRestRecords(\n new DataView(buffer, vertices.offset),\n vertices.stride,\n 0,\n 0,\n vertices.count,\n rest,\n coefficients,\n );\n }\n return finalizePlyDecode(vertices.count, out, rest, coefficients);\n}\n\n/**\n * Bytes read per slice while streaming. Large enough that a multi-gigabyte\n * file costs tens of reads rather than thousands, small enough to stay far\n * below the 2 GiB an ArrayBuffer can hold.\n */\nconst STREAM_WINDOW_BYTES = 64 * 1024 * 1024;\n\n/** Bytes of a PLY that the header is assumed to fit inside. */\nconst HEADER_WINDOW_BYTES = 64 * 1024;\n\n/**\n * Parses a local splat PLY without ever holding the whole file.\n *\n * A browser cannot allocate an ArrayBuffer over 2 GiB, so `file.arrayBuffer()`\n * fails outright on a raw 3DGS export of a few million splats (a full-SH\n * `point_cloud.ply` runs ~248 bytes per splat). The uncompressed flavor has\n * fixed-stride records, so it is read a window at a time. Higher-order\n * `f_rest_*` coefficients ride in the same stride and are decoded per window.\n *\n * The compressed flavor is read whole: it is ~10× smaller, and its chunked\n * layout would need a second pass to stream for no practical gain.\n *\n * @param options.windowBytes - Bytes read per slice; defaults to 64 MiB. Lower\n * trades read count for peak memory.\n * @param options.onProgress - Called after each window with the vertex bytes\n * decoded so far and the total. A multi-gigabyte decode takes tens of seconds,\n * which needs a determinate indicator rather than a spinner.\n * @throws {Error} if the file is not a binary little-endian splat PLY;\n * a `DOMException` named `AbortError` when cancelled.\n */\nexport async function parseSplatPlyFile(\n file: File,\n options: { signal?: AbortSignal; windowBytes?: number; onProgress?: SplatProgressCallback } = {},\n): Promise<SplatData> {\n const { signal, onProgress } = options;\n const windowBytes = options.windowBytes ?? STREAM_WINDOW_BYTES;\n const head = await file.slice(0, Math.min(file.size, HEADER_WINDOW_BYTES)).arrayBuffer();\n const header = parsePlyHeader(head);\n if (isCompressedPly(header)) return parseCompressedPly(await readWholeFile(file), header);\n\n const vertices = requireSplatVertexElement(header);\n assertPlyElementFits(vertices, file.size);\n signal?.throwIfAborted();\n\n const count = vertices.count;\n const stride = vertices.stride;\n const out = allocate(count);\n const offsets = vertexOffsets(vertices);\n const rest = restLayout(vertices);\n const coefficients = rest ? new Float32Array(count * rest.coefficients * 3) : null;\n // At least one whole record per read: a window never splits a record, so\n // the decoder always sees complete ones.\n const perWindow = Math.max(1, Math.floor(windowBytes / stride));\n\n const totalBytes = count * stride;\n onProgress?.(0, totalBytes);\n for (let first = 0; first < count; first += perWindow) {\n signal?.throwIfAborted();\n const last = Math.min(first + perWindow, count);\n const slice = file.slice(vertices.offset + first * stride, vertices.offset + last * stride);\n const view = new DataView(await slice.arrayBuffer());\n // The window's byte 0 is record `first`, so the decoder rebases onto it.\n decodeRecords(view, offsets, stride, first, first, last, out);\n if (rest && coefficients)\n decodeRestRecords(view, stride, first, first, last, rest, coefficients);\n onProgress?.(last * stride, totalBytes);\n }\n return finalizePlyDecode(count, out, rest, coefficients);\n}\n\n/** The vertex element of a splat PLY, with its required properties checked. */\nfunction requireSplatVertexElement(header: ReturnType<typeof parsePlyHeader>): PlyElement {\n const vertices = header.element('vertex');\n if (!vertices) throw new Error('PLY file is not a Gaussian splat file: no \"vertex\" element.');\n assertPlyPropertyTypes(\n vertices,\n REQUIRED_PROPERTIES,\n ['float', 'float32'],\n 'PLY file is not a Gaussian splat file',\n );\n return vertices;\n}\n\ninterface RestLayout {\n bands: 1 | 2 | 3;\n coefficients: number;\n offsets: readonly number[];\n}\n\n/** Resolves optional `f_rest_*` higher-order SH in a 3DGS vertex element. */\nfunction restLayout(vertices: PlyElement): RestLayout | null {\n const restNames = vertices.propertyNames.filter((name) => name.startsWith('f_rest_'));\n if (restNames.length === 0) return null;\n if (restNames.length % 3 !== 0) {\n throw new Error(\n `PLY vertex has ${restNames.length} f_rest_* properties, which is not divisible by three.`,\n );\n }\n const coefficients = restNames.length / 3;\n const bands = REST_COEFFICIENTS[coefficients];\n if (!bands) {\n throw new Error(\n `PLY vertex has ${coefficients} SH coefficients per channel; expected 3, 8 or 15.`,\n );\n }\n const names = Array.from({ length: coefficients * 3 }, (_, i) => `f_rest_${i}`);\n assertPlyPropertyTypes(vertices, names, ['float', 'float32'], 'PLY f_rest');\n const offsets = names.map((name) => plyPropertyOffset(vertices, name));\n return { bands, coefficients, offsets };\n}\n\nfunction finalizePlyDecode(\n count: number,\n out: SplatArrays,\n rest: RestLayout | null,\n coefficients: Float32Array | null,\n): SplatData {\n const shPacked =\n rest && coefficients ? packShCoefficients(coefficients, count, rest.bands) : undefined;\n return { count, ...out, ...(shPacked ? { shPacked } : {}) };\n}\n\ninterface SplatArrays {\n positions: Float32Array;\n colors: Uint8Array;\n covariances: Float32Array;\n}\n\nfunction allocate(count: number): SplatArrays {\n return {\n positions: new Float32Array(count * 3),\n colors: new Uint8Array(count * 4),\n covariances: new Float32Array(count * 6),\n };\n}\n\n/** Byte offsets of the properties the decoder reads, within one record. */\ninterface VertexOffsets {\n x: number;\n y: number;\n z: number;\n r: number;\n g: number;\n b: number;\n alpha: number;\n s0: number;\n s1: number;\n s2: number;\n q0: number;\n q1: number;\n q2: number;\n q3: number;\n}\n\nfunction vertexOffsets(vertices: PlyElement): VertexOffsets {\n const at = (name: string): number => plyPropertyOffset(vertices, name);\n return {\n x: at('x'),\n y: at('y'),\n z: at('z'),\n r: at('f_dc_0'),\n g: at('f_dc_1'),\n b: at('f_dc_2'),\n alpha: at('opacity'),\n s0: at('scale_0'),\n s1: at('scale_1'),\n s2: at('scale_2'),\n q0: at('rot_0'),\n q1: at('rot_1'),\n q2: at('rot_2'),\n q3: at('rot_3'),\n };\n}\n\n/**\n * Decodes records `[from, to)` into `out`, reading from a view whose byte 0\n * holds record `viewFirst`. Shared by the whole-buffer and streamed paths, so\n * both produce identical arrays.\n */\nfunction decodeRecords(\n view: DataView,\n o: VertexOffsets,\n stride: number,\n viewFirst: number,\n from: number,\n to: number,\n out: SplatArrays,\n): void {\n const { positions, colors, covariances } = out;\n for (let i = from; i < to; i++) {\n const base = (i - viewFirst) * stride;\n const readFloat = (offset: number): number => view.getFloat32(base + offset, true);\n\n positions[i * 3 + 0] = readFloat(o.x);\n positions[i * 3 + 1] = readFloat(o.y);\n positions[i * 3 + 2] = readFloat(o.z);\n\n // Degree-0 SH to linear color: c = 0.5 + Y₀₀ · f_dc.\n const toByte = (sh: number): number =>\n Math.max(0, Math.min(255, Math.round((0.5 + SH_C0 * sh) * 255)));\n colors[i * 4 + 0] = toByte(readFloat(o.r));\n colors[i * 4 + 1] = toByte(readFloat(o.g));\n colors[i * 4 + 2] = toByte(readFloat(o.b));\n colors[i * 4 + 3] = Math.round(sigmoid(readFloat(o.alpha)) * 255);\n\n // Scale is stored in log-space; rotation as a (w, x, y, z) quaternion.\n writeCovariance(\n covariances,\n i,\n Math.exp(readFloat(o.s0)),\n Math.exp(readFloat(o.s1)),\n Math.exp(readFloat(o.s2)),\n readFloat(o.q0),\n readFloat(o.q1),\n readFloat(o.q2),\n readFloat(o.q3),\n );\n }\n}\n\n/**\n * Decodes channel-major `f_rest_*` floats into coefficient-major RGB triples.\n */\nfunction decodeRestRecords(\n view: DataView,\n stride: number,\n viewFirst: number,\n from: number,\n to: number,\n rest: RestLayout,\n out: Float32Array,\n): void {\n const { coefficients, offsets } = rest;\n for (let i = from; i < to; i++) {\n const base = (i - viewFirst) * stride;\n const dest = i * coefficients * 3;\n for (let c = 0; c < coefficients; c++) {\n out[dest + c * 3 + 0] = view.getFloat32(base + (offsets[c] as number), true);\n out[dest + c * 3 + 1] = view.getFloat32(base + (offsets[coefficients + c] as number), true);\n out[dest + c * 3 + 2] = view.getFloat32(\n base + (offsets[2 * coefficients + c] as number),\n true,\n );\n }\n }\n}\n"],"names":["SPLATS_PER_CHUNK","CHUNK_BOUNDS","CHUNK_COLOR_BOUNDS","VERTEX_PROPERTIES","SH_COEFFICIENTS","SH_MIN","SH_MAX","isCompressedPly","header","vertex","unpackUnorm","value","bits","max","lerp","min","t","parseCompressedPly","buffer","chunks","vertices","assertPlyPropertyTypes","hasColorBounds","assertPlyElementFits","count","neededChunks","chunkView","vertexView","chunkAt","chunk","property","plyPropertyOffset","positionOffset","rotationOffset","scaleOffset","colorOffset","positions","colors","covariances","minX","minY","minZ","maxX","maxY","maxZ","minSx","minSy","minSz","maxSx","maxSy","maxSz","minR","minG","minB","maxR","maxG","maxB","first","last","i","base","packedPosition","packedColor","r","g","b","a","toByte","packedScale","scaleX","scaleY","scaleZ","packedRotation","q0","q1","q2","largest","qw","qx","qy","qz","writeCovariance","shPacked","decodeSh","sh","coefficients","bands","band","names","_","bytes","offsets","name","packed","c","REQUIRED_PROPERTIES","REST_COEFFICIENTS","sigmoid","x","parseSplatPly","parsePlyHeader","requireSplatVertexElement","out","allocate","rest","restLayout","decodeRecords","vertexOffsets","decodeRestRecords","finalizePlyDecode","restNames","packShCoefficients","at","view","o","stride","viewFirst","from","to","readFloat","offset","SH_C0","dest"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* without pulling worker source into the published declarations - the worker is
|
|
7
7
|
* reached only through `?worker&inline` and is not a public entry point.
|
|
8
8
|
*/
|
|
9
|
-
import type { SplatData } from '../../splat-data';
|
|
9
|
+
import type { SplatData } from '../../core/splat-data';
|
|
10
10
|
/**
|
|
11
11
|
* Foveation ramp for the frontier traversal, matching Spark's `SparkRenderer`
|
|
12
12
|
* defaults (`coneFov0` / `coneFov` / `coneFoveate` / `behindFoveate`). Detail is
|
|
@@ -174,4 +174,11 @@ export interface FrontierPlanMessage {
|
|
|
174
174
|
*/
|
|
175
175
|
readonly cacheBytes: number;
|
|
176
176
|
readonly cacheLimitBytes: number;
|
|
177
|
+
readonly pendingFrontierSplats?: number;
|
|
178
|
+
readonly staleResidentSplats?: number;
|
|
179
|
+
readonly lastPlanAppends?: number;
|
|
180
|
+
readonly lastPlanMoves?: number;
|
|
181
|
+
readonly cameraLocal?: readonly [number, number, number];
|
|
182
|
+
readonly planBudget?: number;
|
|
183
|
+
readonly planGeneration?: number;
|
|
177
184
|
}
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export { RAD_MAGIC, RAD_CHUNK_MAGIC, parseRad, parseRadHeaderMeta, parseRadChunkStreaming, type RadChunkRange, type RadMeta, type RadTree, } from './parse-rad';
|
|
9
9
|
export { buildRadScene, RadLodSource } from './rad';
|
|
10
|
-
export type { RadChunkRangeRequest } from '../../load-worker-protocol';
|
|
10
|
+
export type { RadChunkRangeRequest } from '../../loaders/load-worker-protocol';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RadShCodebook, type SplatData } from '../../splat-data';
|
|
1
|
+
import { type RadShCodebook, type SplatData } from '../../core/splat-data';
|
|
2
2
|
/**
|
|
3
3
|
* Parser for World Labs' Spark `.rad`/`.radc` format (a precomputed LOD splat
|
|
4
4
|
* tree with per-column, gzip/deflate-compressed properties). Ported from the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as THREE from 'three/webgpu';
|
|
2
|
-
import type { LodRun } from '../../lod-scheduler';
|
|
3
|
-
import type { LodSource, LodSourceOptions } from '../../lod-source';
|
|
4
|
-
import type { SplatData } from '../../splat-data';
|
|
2
|
+
import type { LodRun } from '../../streaming/lod-scheduler';
|
|
3
|
+
import type { LodSource, LodSourceOptions } from '../../streaming/lod-source';
|
|
4
|
+
import type { SplatData } from '../../core/splat-data';
|
|
5
5
|
export declare class RadFoveatedSource implements LodSource {
|
|
6
6
|
budget: number;
|
|
7
7
|
lodBaseDistance: number;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as THREE from 'three/webgpu';
|
|
2
|
-
import type { LodRun } from '../../lod-scheduler';
|
|
3
|
-
import type { LodSource, LodSourceOptions, StreamedScene } from '../../lod-source';
|
|
4
|
-
import type { SplatData } from '../../splat-data';
|
|
5
|
-
import type { SplatDatasetSource } from '../../dataset-source';
|
|
6
|
-
import { type SplatRequestOptions } from '../../loading';
|
|
2
|
+
import type { LodRun } from '../../streaming/lod-scheduler';
|
|
3
|
+
import type { LodSource, LodSourceOptions, StreamedScene } from '../../streaming/lod-source';
|
|
4
|
+
import type { SplatData } from '../../core/splat-data';
|
|
5
|
+
import type { SplatDatasetSource } from '../../streaming/dataset-source';
|
|
6
|
+
import { type SplatRequestOptions } from '../../loaders/loading';
|
|
7
7
|
/**
|
|
8
8
|
* Builds a streamed scene from a single-file `.rad`. Fetches the header and
|
|
9
9
|
* chunk table, then decodes chunk 0 (the coarsest whole-scene overview) to
|