@voluma/vlam 0.1.0 → 0.2.1
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/LICENSE +21 -0
- package/README.md +68 -0
- package/THIRD_PARTY_NOTICES.md +86 -0
- package/dist/budget-governor.d.ts +154 -0
- package/dist/camera-budget-governor.d.ts +234 -0
- package/dist/chunk-cache-budget.d.ts +156 -0
- package/dist/chunk-fetch-scheduler.d.ts +168 -0
- package/dist/chunk-loader.d.ts +85 -0
- package/dist/compute-sorter.d.ts +131 -0
- package/dist/create-splat-renderer.d.ts +99 -0
- package/dist/dataset-source.d.ts +55 -0
- package/dist/depth-of-field.d.ts +73 -0
- package/dist/effects.d.ts +337 -0
- package/dist/effects.js +180 -0
- package/dist/effects.js.map +1 -0
- package/dist/formats/ksplat/index.d.ts +8 -0
- package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
- package/dist/formats/ksplat.js +167 -0
- package/dist/formats/ksplat.js.map +1 -0
- package/dist/formats/lcc/collision-mesh.d.ts +37 -0
- package/dist/formats/lcc/collision-partition.d.ts +35 -0
- package/dist/formats/lcc/index.d.ts +16 -0
- package/dist/formats/lcc/lcc.d.ts +28 -0
- package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
- package/dist/formats/lcc/lcc2.d.ts +4 -0
- package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
- package/dist/formats/lcc/parse-lcc.d.ts +177 -0
- package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
- package/dist/formats/lcc.js +969 -0
- package/dist/formats/lcc.js.map +1 -0
- package/dist/formats/ply/index.d.ts +12 -0
- package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
- package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
- package/dist/formats/ply.js +230 -0
- package/dist/formats/ply.js.map +1 -0
- package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
- package/dist/formats/rad/index.d.ts +10 -0
- package/dist/formats/rad/parse-rad.d.ts +88 -0
- package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
- package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
- package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
- package/dist/formats/rad/rad.d.ts +132 -0
- package/dist/formats/rad.js +1098 -0
- package/dist/formats/rad.js.map +1 -0
- package/dist/formats/sog/index.d.ts +9 -0
- package/dist/formats/sog/parse-sog.d.ts +45 -0
- package/dist/formats/sog.js +257 -0
- package/dist/formats/sog.js.map +1 -0
- package/dist/formats/splat/index.d.ts +8 -0
- package/dist/formats/splat/parse-splat.d.ts +10 -0
- package/dist/formats/splat.js +39 -0
- package/dist/formats/splat.js.map +1 -0
- package/dist/formats/spz/index.d.ts +8 -0
- package/dist/formats/spz/parse-spz.d.ts +10 -0
- package/dist/formats/spz.js +218 -0
- package/dist/formats/spz.js.map +1 -0
- package/dist/frontier-worker-CAnK_Cmu.js +566 -0
- package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
- package/dist/half-float-Cs4FDq8Q.js +30 -0
- package/dist/half-float-Cs4FDq8Q.js.map +1 -0
- package/dist/half-float.d.ts +15 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +9054 -0
- package/dist/index.js.map +1 -0
- package/dist/load-scene.d.ts +50 -0
- package/dist/load-worker-protocol.d.ts +82 -0
- package/dist/loading-wLo8vRbA.js +102 -0
- package/dist/loading-wLo8vRbA.js.map +1 -0
- package/dist/loading.d.ts +150 -0
- package/dist/lod-manifest.d.ts +61 -0
- package/dist/lod-scheduler.d.ts +229 -0
- package/dist/lod-source.d.ts +207 -0
- package/dist/logging-BfPdd7NJ.js +19 -0
- package/dist/logging-BfPdd7NJ.js.map +1 -0
- package/dist/logging.d.ts +37 -0
- package/dist/one-shot-worker-G80btnth.js +664 -0
- package/dist/one-shot-worker-G80btnth.js.map +1 -0
- package/dist/orientation.d.ts +35 -0
- package/dist/ply-header-BwgExCn-.js +125 -0
- package/dist/ply-header-BwgExCn-.js.map +1 -0
- package/dist/ply-header.d.ts +87 -0
- package/dist/radix-sort.d.ts +28 -0
- package/dist/radix-sorter.d.ts +46 -0
- package/dist/relighting.d.ts +50 -0
- package/dist/selection-volume.d.ts +87 -0
- package/dist/sh-pack-D5wAe5gg.js +54 -0
- package/dist/sh-pack-D5wAe5gg.js.map +1 -0
- package/dist/sh-pack.d.ts +49 -0
- package/dist/sort-scheduler.d.ts +62 -0
- package/dist/sort-worker-protocol.d.ts +37 -0
- package/dist/sorter.d.ts +31 -0
- package/dist/source-transform.d.ts +57 -0
- package/dist/splat-budget-PSojLJPO.js +157 -0
- package/dist/splat-budget-PSojLJPO.js.map +1 -0
- package/dist/splat-budget.d.ts +404 -0
- package/dist/splat-data-BZM5dhhx.js +10 -0
- package/dist/splat-data-BZM5dhhx.js.map +1 -0
- package/dist/splat-data.d.ts +150 -0
- package/dist/splat-depth-pack.d.ts +39 -0
- package/dist/splat-mesh-material.d.ts +255 -0
- package/dist/splat-mesh-picking.d.ts +87 -0
- package/dist/splat-mesh-pool.d.ts +241 -0
- package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
- package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
- package/dist/splat-mesh-types.d.ts +390 -0
- package/dist/splat-mesh.d.ts +737 -0
- package/dist/splat-modifier-stack.d.ts +38 -0
- package/dist/splat-modifier.d.ts +156 -0
- package/dist/splat-partition.d.ts +37 -0
- package/dist/splat-query.d.ts +51 -0
- package/dist/splat-scene.d.ts +127 -0
- package/dist/splat-sort-bounds.d.ts +11 -0
- package/dist/static-lod-splat-mesh.d.ts +53 -0
- package/dist/static-lod-worker-D2fyqQZO.js +474 -0
- package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
- package/dist/static-lod-worker-protocol.d.ts +37 -0
- package/dist/static-lod.d.ts +24 -0
- package/dist/storage-attribute-mirror.d.ts +103 -0
- package/dist/streamed-splat-mesh-utils.d.ts +146 -0
- package/dist/streamed-splat-mesh.d.ts +985 -0
- package/dist/unified-splat-renderer.d.ts +205 -0
- package/dist/webgpu-limits.d.ts +133 -0
- package/dist/work-buffer-gather.d.ts +119 -0
- package/dist/work-buffer-material.d.ts +42 -0
- package/dist/worker-sorter.d.ts +57 -0
- package/dist/xr-view.d.ts +92 -0
- package/package.json +135 -21
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"one-shot-worker-G80btnth.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
/**
|
|
3
|
+
* How a loaded scene is oriented into the three.js Y-up world.
|
|
4
|
+
*
|
|
5
|
+
* - `'y-up'` (default): VLAM normalizes every known format to three.js Y-up -
|
|
6
|
+
* the OpenCV-frame formats (PLY/`.splat`/`.ksplat`/SOG/`.rad`) are flipped
|
|
7
|
+
* 180° about X, SPZ is already Y-up, and LCC carries its own Z-up→Y-up matrix.
|
|
8
|
+
* "Drop a file and it stands up", matching engine viewers (PlayCanvas, Babylon).
|
|
9
|
+
* - `'source'`: no cosmetic correction - the scene renders in its data frame,
|
|
10
|
+
* matching raw Spark / mkkellogg. A host that manages orientation itself (e.g.
|
|
11
|
+
* swapping viewers while keeping one stored per-scene rotation) uses this.
|
|
12
|
+
*
|
|
13
|
+
* Note LCC's Z-up→Y-up matrix is *format semantics*, not a cosmetic default: it
|
|
14
|
+
* is applied in **both** modes (Spark applies it too). Only the 180°-X flip for
|
|
15
|
+
* the Y-down formats is gated by this option.
|
|
16
|
+
*/
|
|
17
|
+
export type SplatOrientation = 'y-up' | 'source';
|
|
18
|
+
/**
|
|
19
|
+
* Formats {@link yUpTransformForFormat} understands: the self-contained scene
|
|
20
|
+
* formats ({@link SplatData.sourceFormat}) plus the streamed ones.
|
|
21
|
+
*/
|
|
22
|
+
export type OrientableFormat = 'ply' | 'splat' | 'ksplat' | 'sog' | 'spz' | 'rad' | 'streamed-sog' | 'lcc' | 'lcc2';
|
|
23
|
+
/**
|
|
24
|
+
* The 180°-about-X rotation that maps a 3DGS Y-down capture to three.js Y-up -
|
|
25
|
+
* the same correction Spark documents as `quaternion.set(1, 0, 0, 0)`. Returned
|
|
26
|
+
* as a fresh {@link THREE.Matrix4} so callers can mutate/decompose it freely.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createYUpTransform(): THREE.Matrix4;
|
|
29
|
+
/**
|
|
30
|
+
* The `'y-up'` correction for a format, or `null` when nothing is needed:
|
|
31
|
+
* SPZ is already Y-up, and LCC self-orients via its `formatTransform`
|
|
32
|
+
* (callers apply that in both modes, so this returns `null` for LCC to avoid
|
|
33
|
+
* doubling it). Y-down formats (PLY/`.splat`/`.ksplat`/SOG) get the 180°-X flip.
|
|
34
|
+
*/
|
|
35
|
+
export declare function yUpTransformForFormat(format: OrientableFormat | undefined): THREE.Matrix4 | null;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const c = {
|
|
2
|
+
float: 4,
|
|
3
|
+
float32: 4,
|
|
4
|
+
double: 8,
|
|
5
|
+
float64: 8,
|
|
6
|
+
int: 4,
|
|
7
|
+
int32: 4,
|
|
8
|
+
uint: 4,
|
|
9
|
+
uint32: 4,
|
|
10
|
+
short: 2,
|
|
11
|
+
int16: 2,
|
|
12
|
+
ushort: 2,
|
|
13
|
+
uint16: 2,
|
|
14
|
+
char: 1,
|
|
15
|
+
int8: 1,
|
|
16
|
+
uchar: 1,
|
|
17
|
+
uint8: 1
|
|
18
|
+
};
|
|
19
|
+
function P(t) {
|
|
20
|
+
const s = new Uint8Array(t, 0, Math.min(t.byteLength, 65536)), o = new TextDecoder("ascii").decode(s), p = /end_header\r?\n/.exec(o);
|
|
21
|
+
if (!/^ply\r?\n/.test(o))
|
|
22
|
+
throw new Error('Not a PLY file (missing "ply" magic).');
|
|
23
|
+
if (!p)
|
|
24
|
+
throw new Error(
|
|
25
|
+
'Unsupported PLY file: no "end_header" within the first 64 KiB - the header exceeds the supported window, or the file is truncated.'
|
|
26
|
+
);
|
|
27
|
+
if (!o.includes("format binary_little_endian 1.0"))
|
|
28
|
+
throw new Error("Unsupported PLY format; expected binary_little_endian 1.0.");
|
|
29
|
+
const i = [];
|
|
30
|
+
for (const e of o.slice(0, p.index).split(`
|
|
31
|
+
`)) {
|
|
32
|
+
const n = e.trim().split(/\s+/);
|
|
33
|
+
if (n[0] === "element") {
|
|
34
|
+
const r = n[1], f = Number(n[2]);
|
|
35
|
+
if (r === void 0) throw new Error(`Malformed PLY element: "${e.trim()}"`);
|
|
36
|
+
if (!Number.isSafeInteger(f) || f < 0)
|
|
37
|
+
throw new Error(`Invalid PLY ${r} count: ${n[2]}.`);
|
|
38
|
+
i.push({
|
|
39
|
+
name: r,
|
|
40
|
+
count: f,
|
|
41
|
+
properties: /* @__PURE__ */ new Map(),
|
|
42
|
+
propertyNames: [],
|
|
43
|
+
listProperties: /* @__PURE__ */ new Map(),
|
|
44
|
+
stride: 0,
|
|
45
|
+
hasListProperty: !1
|
|
46
|
+
});
|
|
47
|
+
} else if (n[0] === "property") {
|
|
48
|
+
const r = i[i.length - 1];
|
|
49
|
+
if (!r) throw new Error(`PLY property declared before any element: "${e.trim()}"`);
|
|
50
|
+
if (n[1] === "list") {
|
|
51
|
+
const m = n[2] ?? "", w = n[3] ?? "", l = n[4];
|
|
52
|
+
if (c[m] === void 0 || c[w] === void 0 || l === void 0)
|
|
53
|
+
throw new Error(`Unsupported PLY property: "${e.trim()}"`);
|
|
54
|
+
if (r.properties.has(l) || r.listProperties.has(l))
|
|
55
|
+
throw new Error(`Duplicate PLY property "${l}" in element "${r.name}".`);
|
|
56
|
+
r.listProperties.set(l, { countType: m, itemType: w }), r.hasListProperty = !0;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const f = n[1] ?? "", y = c[f], d = n[2];
|
|
60
|
+
if (y === void 0 || d === void 0)
|
|
61
|
+
throw new Error(`Unsupported PLY property: "${e.trim()}"`);
|
|
62
|
+
if (r.properties.has(d) || r.listProperties.has(d))
|
|
63
|
+
throw new Error(`Duplicate PLY property "${d}" in element "${r.name}".`);
|
|
64
|
+
r.properties.set(d, { type: f, offset: r.stride }), r.propertyNames.push(d), r.stride += y;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
let a = p.index + p[0].length, u = !0;
|
|
68
|
+
const h = [];
|
|
69
|
+
for (const e of i)
|
|
70
|
+
h.push({
|
|
71
|
+
name: e.name,
|
|
72
|
+
count: e.count,
|
|
73
|
+
properties: e.properties,
|
|
74
|
+
propertyNames: e.propertyNames,
|
|
75
|
+
listProperties: e.listProperties,
|
|
76
|
+
stride: e.stride,
|
|
77
|
+
offset: u ? a : -1,
|
|
78
|
+
hasListProperty: e.hasListProperty
|
|
79
|
+
}), e.hasListProperty ? u = !1 : a += e.count * e.stride;
|
|
80
|
+
return {
|
|
81
|
+
elements: h,
|
|
82
|
+
element: (e) => h.find((n) => n.name === e)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function $(t, s) {
|
|
86
|
+
if (t.offset < 0)
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Unsupported PLY layout: element "${t.name}" follows a list property, so its position cannot be determined.`
|
|
89
|
+
);
|
|
90
|
+
const o = t.offset + t.count * t.stride;
|
|
91
|
+
if (o > s)
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Truncated PLY file: element "${t.name}" needs ${o} bytes but the file is ${s}.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function L(t) {
|
|
97
|
+
const s = c[t];
|
|
98
|
+
if (s === void 0) throw new Error(`Unsupported PLY property type: "${t}".`);
|
|
99
|
+
return s;
|
|
100
|
+
}
|
|
101
|
+
function E(t, s) {
|
|
102
|
+
const o = t.properties.get(s);
|
|
103
|
+
if (!o)
|
|
104
|
+
throw new Error(`PLY element "${t.name}" is missing property "${s}".`);
|
|
105
|
+
return o.offset;
|
|
106
|
+
}
|
|
107
|
+
function Y(t, s, o, p) {
|
|
108
|
+
for (const i of s) {
|
|
109
|
+
const a = t.properties.get(i);
|
|
110
|
+
if (!a)
|
|
111
|
+
throw new Error(`${p}: missing property "${i}".`);
|
|
112
|
+
if (!o.includes(a.type))
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Unsupported PLY property type for "${i}": expected ${o.join(" or ")}, got ${a.type}.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
Y as a,
|
|
120
|
+
$ as b,
|
|
121
|
+
E as c,
|
|
122
|
+
L as d,
|
|
123
|
+
P as p
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=ply-header-BwgExCn-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ply-header-BwgExCn-.js","sources":["../src/lib/ply-header.ts"],"sourcesContent":["/**\n * Shared PLY header reader.\n *\n * PLY is a container, not a format: the same `.ply` extension carries the\n * uncompressed \"INRIA flavor\" 3DGS export (see `parse-splat-ply.ts`) and\n * SuperSplat/PlayCanvas's compressed flavor (`parse-compressed-ply.ts`).\n * Both need the same thing first - the element list, their property layouts,\n * and where each element's records start - so that lives here.\n */\n\n/** Byte size of each scalar type PLY can declare. */\nconst PROPERTY_SIZES: Record<string, number> = {\n float: 4,\n float32: 4,\n double: 8,\n float64: 8,\n int: 4,\n int32: 4,\n uint: 4,\n uint32: 4,\n short: 2,\n int16: 2,\n ushort: 2,\n uint16: 2,\n char: 1,\n int8: 1,\n uchar: 1,\n uint8: 1,\n};\n\n/** One scalar property of an element. */\nexport interface PlyProperty {\n readonly type: string;\n /** Byte offset within one record of the element. */\n readonly offset: number;\n}\n\n/**\n * One list property (`property list <countType> <itemType> <name>`), such as a\n * mesh's `face` indices. Only the types are recorded - the data is variable-size\n * and must be walked record by record (see `parse-mesh-ply.ts`).\n */\nexport interface PlyListProperty {\n /** Type of the per-record item count, e.g. `uchar`. */\n readonly countType: string;\n /** Type of each item, e.g. `int`. */\n readonly itemType: string;\n}\n\n/** One PLY element (`element <name> <count>`) and its record layout. */\nexport interface PlyElement {\n readonly name: string;\n readonly count: number;\n readonly properties: ReadonlyMap<string, PlyProperty>;\n /** Property names in declaration order. */\n readonly propertyNames: readonly string[];\n /** List properties by name; empty unless {@link hasListProperty}. */\n readonly listProperties: ReadonlyMap<string, PlyListProperty>;\n /**\n * Bytes per record - meaningless when {@link hasListProperty}, since a list\n * makes each record's size depend on its contents.\n */\n readonly stride: number;\n /**\n * Byte offset of this element's first record within the file, or -1 when a\n * preceding element has a variable-size record and no arithmetic can find it.\n */\n readonly offset: number;\n /** A list property makes this element's records variable-size. */\n readonly hasListProperty: boolean;\n}\n\n/** A parsed PLY header. */\nexport interface PlyHeader {\n readonly elements: readonly PlyElement[];\n /** The element of that name, or undefined. */\n element(name: string): PlyElement | undefined;\n}\n\n/**\n * Reads a binary little-endian PLY header and resolves every element's byte\n * offset by walking the elements in declaration order.\n *\n * Whether the declared records actually fit in the buffer is deliberately not\n * checked here - call {@link assertPlyElementFits} once the caller has\n * validated the properties it needs, so a wrong property type is reported as\n * such rather than as the truncation it happens to imply.\n *\n * @throws {Error} if the file is not a binary little-endian PLY, or declares a\n * property type this reader cannot size.\n */\nexport function parsePlyHeader(buffer: ArrayBuffer): PlyHeader {\n const headerBytes = new Uint8Array(buffer, 0, Math.min(buffer.byteLength, 64 * 1024));\n const headerText = new TextDecoder('ascii').decode(headerBytes);\n const endMatch = /end_header\\r?\\n/.exec(headerText);\n if (!/^ply\\r?\\n/.test(headerText)) {\n throw new Error('Not a PLY file (missing \"ply\" magic).');\n }\n if (!endMatch) {\n throw new Error(\n 'Unsupported PLY file: no \"end_header\" within the first 64 KiB - the header exceeds the ' +\n 'supported window, or the file is truncated.',\n );\n }\n if (!headerText.includes('format binary_little_endian 1.0')) {\n throw new Error('Unsupported PLY format; expected binary_little_endian 1.0.');\n }\n\n interface Building {\n name: string;\n count: number;\n properties: Map<string, PlyProperty>;\n propertyNames: string[];\n listProperties: Map<string, PlyListProperty>;\n stride: number;\n hasListProperty: boolean;\n }\n const building: Building[] = [];\n\n for (const line of headerText.slice(0, endMatch.index).split('\\n')) {\n const tokens = line.trim().split(/\\s+/);\n if (tokens[0] === 'element') {\n const name = tokens[1];\n const count = Number(tokens[2]);\n if (name === undefined) throw new Error(`Malformed PLY element: \"${line.trim()}\"`);\n if (!Number.isSafeInteger(count) || count < 0) {\n throw new Error(`Invalid PLY ${name} count: ${tokens[2]}.`);\n }\n building.push({\n name,\n count,\n properties: new Map(),\n propertyNames: [],\n listProperties: new Map(),\n stride: 0,\n hasListProperty: false,\n });\n } else if (tokens[0] === 'property') {\n const element = building[building.length - 1];\n if (!element) throw new Error(`PLY property declared before any element: \"${line.trim()}\"`);\n if (tokens[1] === 'list') {\n // A list makes this element's records variable-size. That is fine for\n // the element itself (a trailing `face` list is common) - it only\n // costs later elements their computable offset. The types are recorded\n // so a reader that does want the data can walk it record by record.\n const countType = tokens[2] ?? '';\n const itemType = tokens[3] ?? '';\n const listName = tokens[4];\n if (\n PROPERTY_SIZES[countType] === undefined ||\n PROPERTY_SIZES[itemType] === undefined ||\n listName === undefined\n ) {\n throw new Error(`Unsupported PLY property: \"${line.trim()}\"`);\n }\n if (element.properties.has(listName) || element.listProperties.has(listName)) {\n throw new Error(`Duplicate PLY property \"${listName}\" in element \"${element.name}\".`);\n }\n element.listProperties.set(listName, { countType, itemType });\n element.hasListProperty = true;\n continue;\n }\n const type = tokens[1] ?? '';\n const size = PROPERTY_SIZES[type];\n const name = tokens[2];\n if (size === undefined || name === undefined) {\n throw new Error(`Unsupported PLY property: \"${line.trim()}\"`);\n }\n if (element.properties.has(name) || element.listProperties.has(name)) {\n // A duplicate name would let the later offset overwrite the earlier\n // one while the stride counts both - every read after it lands on\n // the wrong bytes.\n throw new Error(`Duplicate PLY property \"${name}\" in element \"${element.name}\".`);\n }\n element.properties.set(name, { type, offset: element.stride });\n element.propertyNames.push(name);\n element.stride += size;\n }\n }\n\n // Records are laid out back to back in element order, starting right after\n // the header - so each element begins where the previous one ended, until a\n // variable-size element makes the rest unreachable by arithmetic.\n let offset = endMatch.index + endMatch[0].length;\n let resolved = true;\n const elements: PlyElement[] = [];\n for (const element of building) {\n elements.push({\n name: element.name,\n count: element.count,\n properties: element.properties,\n propertyNames: element.propertyNames,\n listProperties: element.listProperties,\n stride: element.stride,\n offset: resolved ? offset : -1,\n hasListProperty: element.hasListProperty,\n });\n if (element.hasListProperty) resolved = false;\n else offset += element.count * element.stride;\n }\n\n return {\n elements,\n element: (name: string) => elements.find((candidate) => candidate.name === name),\n };\n}\n\n/**\n * Asserts an element's records are actually present in a file of `totalBytes`.\n *\n * Takes a size rather than a buffer so it works for a streamed parse, which\n * only ever holds a window of the file.\n *\n * @throws {Error} if the element sits behind a variable-size one, or its\n * records run past the end of the file.\n */\nexport function assertPlyElementFits(element: PlyElement, totalBytes: number): void {\n if (element.offset < 0) {\n throw new Error(\n `Unsupported PLY layout: element \"${element.name}\" follows a list property, so its ` +\n 'position cannot be determined.',\n );\n }\n const end = element.offset + element.count * element.stride;\n if (end > totalBytes) {\n throw new Error(\n `Truncated PLY file: element \"${element.name}\" needs ${end} bytes but the file is ` +\n `${totalBytes}.`,\n );\n }\n}\n\n/**\n * Byte size of a PLY scalar type.\n *\n * @throws {Error} if the type is not one PLY can declare.\n */\nexport function plyTypeSize(type: string): number {\n const size = PROPERTY_SIZES[type];\n if (size === undefined) throw new Error(`Unsupported PLY property type: \"${type}\".`);\n return size;\n}\n\n/** The byte offset of `property` within `element`'s record. */\nexport function plyPropertyOffset(element: PlyElement, property: string): number {\n const found = element.properties.get(property);\n if (!found) {\n throw new Error(`PLY element \"${element.name}\" is missing property \"${property}\".`);\n }\n return found.offset;\n}\n\n/** Asserts every named property exists on `element` with the given type. */\nexport function assertPlyPropertyTypes(\n element: PlyElement,\n properties: readonly string[],\n types: readonly string[],\n what: string,\n): void {\n for (const name of properties) {\n const property = element.properties.get(name);\n if (!property) {\n throw new Error(`${what}: missing property \"${name}\".`);\n }\n if (!types.includes(property.type)) {\n throw new Error(\n `Unsupported PLY property type for \"${name}\": expected ${types.join(' or ')}, ` +\n `got ${property.type}.`,\n );\n }\n }\n}\n"],"names":["PROPERTY_SIZES","parsePlyHeader","buffer","headerBytes","headerText","endMatch","building","line","tokens","name","count","element","countType","itemType","listName","type","size","offset","resolved","elements","candidate","assertPlyElementFits","totalBytes","end","plyTypeSize","plyPropertyOffset","property","found","assertPlyPropertyTypes","properties","types","what"],"mappings":"AAWA,MAAMA,IAAyC;AAAA,EAC7C,OAAO;AAAA,EACP,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,KAAK;AAAA,EACL,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,OAAO;AACT;AA+DO,SAASC,EAAeC,GAAgC;AAC7D,QAAMC,IAAc,IAAI,WAAWD,GAAQ,GAAG,KAAK,IAAIA,EAAO,YAAY,KAAS,CAAC,GAC9EE,IAAa,IAAI,YAAY,OAAO,EAAE,OAAOD,CAAW,GACxDE,IAAW,kBAAkB,KAAKD,CAAU;AAClD,MAAI,CAAC,YAAY,KAAKA,CAAU;AAC9B,UAAM,IAAI,MAAM,uCAAuC;AAEzD,MAAI,CAACC;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,MAAI,CAACD,EAAW,SAAS,iCAAiC;AACxD,UAAM,IAAI,MAAM,4DAA4D;AAY9E,QAAME,IAAuB,CAAA;AAE7B,aAAWC,KAAQH,EAAW,MAAM,GAAGC,EAAS,KAAK,EAAE,MAAM;AAAA,CAAI,GAAG;AAClE,UAAMG,IAASD,EAAK,KAAA,EAAO,MAAM,KAAK;AACtC,QAAIC,EAAO,CAAC,MAAM,WAAW;AAC3B,YAAMC,IAAOD,EAAO,CAAC,GACfE,IAAQ,OAAOF,EAAO,CAAC,CAAC;AAC9B,UAAIC,MAAS,OAAW,OAAM,IAAI,MAAM,2BAA2BF,EAAK,MAAM,GAAG;AACjF,UAAI,CAAC,OAAO,cAAcG,CAAK,KAAKA,IAAQ;AAC1C,cAAM,IAAI,MAAM,eAAeD,CAAI,WAAWD,EAAO,CAAC,CAAC,GAAG;AAE5D,MAAAF,EAAS,KAAK;AAAA,QACZ,MAAAG;AAAA,QACA,OAAAC;AAAA,QACA,gCAAgB,IAAA;AAAA,QAChB,eAAe,CAAA;AAAA,QACf,oCAAoB,IAAA;AAAA,QACpB,QAAQ;AAAA,QACR,iBAAiB;AAAA,MAAA,CAClB;AAAA,IACH,WAAWF,EAAO,CAAC,MAAM,YAAY;AACnC,YAAMG,IAAUL,EAASA,EAAS,SAAS,CAAC;AAC5C,UAAI,CAACK,EAAS,OAAM,IAAI,MAAM,8CAA8CJ,EAAK,MAAM,GAAG;AAC1F,UAAIC,EAAO,CAAC,MAAM,QAAQ;AAKxB,cAAMI,IAAYJ,EAAO,CAAC,KAAK,IACzBK,IAAWL,EAAO,CAAC,KAAK,IACxBM,IAAWN,EAAO,CAAC;AACzB,YACER,EAAeY,CAAS,MAAM,UAC9BZ,EAAea,CAAQ,MAAM,UAC7BC,MAAa;AAEb,gBAAM,IAAI,MAAM,8BAA8BP,EAAK,KAAA,CAAM,GAAG;AAE9D,YAAII,EAAQ,WAAW,IAAIG,CAAQ,KAAKH,EAAQ,eAAe,IAAIG,CAAQ;AACzE,gBAAM,IAAI,MAAM,2BAA2BA,CAAQ,iBAAiBH,EAAQ,IAAI,IAAI;AAEtF,QAAAA,EAAQ,eAAe,IAAIG,GAAU,EAAE,WAAAF,GAAW,UAAAC,GAAU,GAC5DF,EAAQ,kBAAkB;AAC1B;AAAA,MACF;AACA,YAAMI,IAAOP,EAAO,CAAC,KAAK,IACpBQ,IAAOhB,EAAee,CAAI,GAC1BN,IAAOD,EAAO,CAAC;AACrB,UAAIQ,MAAS,UAAaP,MAAS;AACjC,cAAM,IAAI,MAAM,8BAA8BF,EAAK,KAAA,CAAM,GAAG;AAE9D,UAAII,EAAQ,WAAW,IAAIF,CAAI,KAAKE,EAAQ,eAAe,IAAIF,CAAI;AAIjE,cAAM,IAAI,MAAM,2BAA2BA,CAAI,iBAAiBE,EAAQ,IAAI,IAAI;AAElF,MAAAA,EAAQ,WAAW,IAAIF,GAAM,EAAE,MAAAM,GAAM,QAAQJ,EAAQ,QAAQ,GAC7DA,EAAQ,cAAc,KAAKF,CAAI,GAC/BE,EAAQ,UAAUK;AAAA,IACpB;AAAA,EACF;AAKA,MAAIC,IAASZ,EAAS,QAAQA,EAAS,CAAC,EAAE,QACtCa,IAAW;AACf,QAAMC,IAAyB,CAAA;AAC/B,aAAWR,KAAWL;AACpB,IAAAa,EAAS,KAAK;AAAA,MACZ,MAAMR,EAAQ;AAAA,MACd,OAAOA,EAAQ;AAAA,MACf,YAAYA,EAAQ;AAAA,MACpB,eAAeA,EAAQ;AAAA,MACvB,gBAAgBA,EAAQ;AAAA,MACxB,QAAQA,EAAQ;AAAA,MAChB,QAAQO,IAAWD,IAAS;AAAA,MAC5B,iBAAiBN,EAAQ;AAAA,IAAA,CAC1B,GACGA,EAAQ,kBAAiBO,IAAW,KACnCD,KAAUN,EAAQ,QAAQA,EAAQ;AAGzC,SAAO;AAAA,IACL,UAAAQ;AAAA,IACA,SAAS,CAACV,MAAiBU,EAAS,KAAK,CAACC,MAAcA,EAAU,SAASX,CAAI;AAAA,EAAA;AAEnF;AAWO,SAASY,EAAqBV,GAAqBW,GAA0B;AAClF,MAAIX,EAAQ,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,oCAAoCA,EAAQ,IAAI;AAAA,IAAA;AAIpD,QAAMY,IAAMZ,EAAQ,SAASA,EAAQ,QAAQA,EAAQ;AACrD,MAAIY,IAAMD;AACR,UAAM,IAAI;AAAA,MACR,gCAAgCX,EAAQ,IAAI,WAAWY,CAAG,0BACrDD,CAAU;AAAA,IAAA;AAGrB;AAOO,SAASE,EAAYT,GAAsB;AAChD,QAAMC,IAAOhB,EAAee,CAAI;AAChC,MAAIC,MAAS,OAAW,OAAM,IAAI,MAAM,mCAAmCD,CAAI,IAAI;AACnF,SAAOC;AACT;AAGO,SAASS,EAAkBd,GAAqBe,GAA0B;AAC/E,QAAMC,IAAQhB,EAAQ,WAAW,IAAIe,CAAQ;AAC7C,MAAI,CAACC;AACH,UAAM,IAAI,MAAM,gBAAgBhB,EAAQ,IAAI,0BAA0Be,CAAQ,IAAI;AAEpF,SAAOC,EAAM;AACf;AAGO,SAASC,EACdjB,GACAkB,GACAC,GACAC,GACM;AACN,aAAWtB,KAAQoB,GAAY;AAC7B,UAAMH,IAAWf,EAAQ,WAAW,IAAIF,CAAI;AAC5C,QAAI,CAACiB;AACH,YAAM,IAAI,MAAM,GAAGK,CAAI,uBAAuBtB,CAAI,IAAI;AAExD,QAAI,CAACqB,EAAM,SAASJ,EAAS,IAAI;AAC/B,YAAM,IAAI;AAAA,QACR,sCAAsCjB,CAAI,eAAeqB,EAAM,KAAK,MAAM,CAAC,SAClEJ,EAAS,IAAI;AAAA,MAAA;AAAA,EAG5B;AACF;"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared PLY header reader.
|
|
3
|
+
*
|
|
4
|
+
* PLY is a container, not a format: the same `.ply` extension carries the
|
|
5
|
+
* uncompressed "INRIA flavor" 3DGS export (see `parse-splat-ply.ts`) and
|
|
6
|
+
* SuperSplat/PlayCanvas's compressed flavor (`parse-compressed-ply.ts`).
|
|
7
|
+
* Both need the same thing first - the element list, their property layouts,
|
|
8
|
+
* and where each element's records start - so that lives here.
|
|
9
|
+
*/
|
|
10
|
+
/** One scalar property of an element. */
|
|
11
|
+
export interface PlyProperty {
|
|
12
|
+
readonly type: string;
|
|
13
|
+
/** Byte offset within one record of the element. */
|
|
14
|
+
readonly offset: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One list property (`property list <countType> <itemType> <name>`), such as a
|
|
18
|
+
* mesh's `face` indices. Only the types are recorded - the data is variable-size
|
|
19
|
+
* and must be walked record by record (see `parse-mesh-ply.ts`).
|
|
20
|
+
*/
|
|
21
|
+
export interface PlyListProperty {
|
|
22
|
+
/** Type of the per-record item count, e.g. `uchar`. */
|
|
23
|
+
readonly countType: string;
|
|
24
|
+
/** Type of each item, e.g. `int`. */
|
|
25
|
+
readonly itemType: string;
|
|
26
|
+
}
|
|
27
|
+
/** One PLY element (`element <name> <count>`) and its record layout. */
|
|
28
|
+
export interface PlyElement {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly count: number;
|
|
31
|
+
readonly properties: ReadonlyMap<string, PlyProperty>;
|
|
32
|
+
/** Property names in declaration order. */
|
|
33
|
+
readonly propertyNames: readonly string[];
|
|
34
|
+
/** List properties by name; empty unless {@link hasListProperty}. */
|
|
35
|
+
readonly listProperties: ReadonlyMap<string, PlyListProperty>;
|
|
36
|
+
/**
|
|
37
|
+
* Bytes per record - meaningless when {@link hasListProperty}, since a list
|
|
38
|
+
* makes each record's size depend on its contents.
|
|
39
|
+
*/
|
|
40
|
+
readonly stride: number;
|
|
41
|
+
/**
|
|
42
|
+
* Byte offset of this element's first record within the file, or -1 when a
|
|
43
|
+
* preceding element has a variable-size record and no arithmetic can find it.
|
|
44
|
+
*/
|
|
45
|
+
readonly offset: number;
|
|
46
|
+
/** A list property makes this element's records variable-size. */
|
|
47
|
+
readonly hasListProperty: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** A parsed PLY header. */
|
|
50
|
+
export interface PlyHeader {
|
|
51
|
+
readonly elements: readonly PlyElement[];
|
|
52
|
+
/** The element of that name, or undefined. */
|
|
53
|
+
element(name: string): PlyElement | undefined;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reads a binary little-endian PLY header and resolves every element's byte
|
|
57
|
+
* offset by walking the elements in declaration order.
|
|
58
|
+
*
|
|
59
|
+
* Whether the declared records actually fit in the buffer is deliberately not
|
|
60
|
+
* checked here - call {@link assertPlyElementFits} once the caller has
|
|
61
|
+
* validated the properties it needs, so a wrong property type is reported as
|
|
62
|
+
* such rather than as the truncation it happens to imply.
|
|
63
|
+
*
|
|
64
|
+
* @throws {Error} if the file is not a binary little-endian PLY, or declares a
|
|
65
|
+
* property type this reader cannot size.
|
|
66
|
+
*/
|
|
67
|
+
export declare function parsePlyHeader(buffer: ArrayBuffer): PlyHeader;
|
|
68
|
+
/**
|
|
69
|
+
* Asserts an element's records are actually present in a file of `totalBytes`.
|
|
70
|
+
*
|
|
71
|
+
* Takes a size rather than a buffer so it works for a streamed parse, which
|
|
72
|
+
* only ever holds a window of the file.
|
|
73
|
+
*
|
|
74
|
+
* @throws {Error} if the element sits behind a variable-size one, or its
|
|
75
|
+
* records run past the end of the file.
|
|
76
|
+
*/
|
|
77
|
+
export declare function assertPlyElementFits(element: PlyElement, totalBytes: number): void;
|
|
78
|
+
/**
|
|
79
|
+
* Byte size of a PLY scalar type.
|
|
80
|
+
*
|
|
81
|
+
* @throws {Error} if the type is not one PLY can declare.
|
|
82
|
+
*/
|
|
83
|
+
export declare function plyTypeSize(type: string): number;
|
|
84
|
+
/** The byte offset of `property` within `element`'s record. */
|
|
85
|
+
export declare function plyPropertyOffset(element: PlyElement, property: string): number;
|
|
86
|
+
/** Asserts every named property exists on `element` with the given type. */
|
|
87
|
+
export declare function assertPlyPropertyTypes(element: PlyElement, properties: readonly string[], types: readonly string[], what: string): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @role Utility
|
|
3
|
+
* Shared constants and deterministic reference logic for GPU radix sorting.
|
|
4
|
+
*/
|
|
5
|
+
/** Depth precision retained by both the GPU radix path and WebGL worker path. */
|
|
6
|
+
export declare const RADIX_KEY_BITS = 24;
|
|
7
|
+
/** Exact Float32 depth keys use every sortable IEEE-754 bit. */
|
|
8
|
+
export declare const RADIX_EXACT_KEY_BITS = 32;
|
|
9
|
+
/** Four-bit digits keep the portable shader's workgroup memory requirements small. */
|
|
10
|
+
export declare const RADIX_BITS_PER_PASS = 4;
|
|
11
|
+
export declare const RADIX_BUCKETS: number;
|
|
12
|
+
export declare const RADIX_PASS_COUNT: number;
|
|
13
|
+
export declare const RADIX_KEY_MAX: number;
|
|
14
|
+
/** Number of four-bit passes required for one unsigned radix key width. */
|
|
15
|
+
export declare function radixPassCount(keyBits: number): number;
|
|
16
|
+
/**
|
|
17
|
+
* Maps a Float32 bit pattern onto unsigned integer order without losing depth
|
|
18
|
+
* precision. Ascending mapped keys are ascending numeric floats, including
|
|
19
|
+
* negative view-space Z values (farther splats first in VLAM's convention).
|
|
20
|
+
*/
|
|
21
|
+
export declare function sortableFloat32Bits(bits: number): number;
|
|
22
|
+
/** Quantizes a depth into the unsigned 24-bit key used by the GPU sorter. */
|
|
23
|
+
export declare function quantizeDepthKey(depth: number, minimum: number, maximum: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Stable CPU reference for validating the GPU's six four-bit radix passes.
|
|
26
|
+
* This is deliberately test/debug-only logic, not the WebGL production sorter.
|
|
27
|
+
*/
|
|
28
|
+
export declare function stableRadixSortReference(keys: Uint32Array, values?: Uint32Array, keyBits?: number): Uint32Array;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @role Bridge
|
|
3
|
+
* Portable stable WebGPU radix sorting adapted to Three.js TSL compute nodes.
|
|
4
|
+
*/
|
|
5
|
+
import * as THREE from 'three/webgpu';
|
|
6
|
+
import type { SplatSorter } from './sorter';
|
|
7
|
+
/**
|
|
8
|
+
* Stable four-bit-per-pass WebGPU radix sorter.
|
|
9
|
+
*
|
|
10
|
+
* The ranked scatter follows PlayCanvas/WebGPU-Radix-Sort's per-digit bitmask
|
|
11
|
+
* design. Three.js does not currently expose atomic workgroup arrays in TSL,
|
|
12
|
+
* so masks use disjoint global-storage slices per workgroup. Workgroup and
|
|
13
|
+
* storage barriers retain the same stable ranking semantics without relying
|
|
14
|
+
* on cross-workgroup execution order.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RadixSorter implements SplatSorter {
|
|
17
|
+
readonly kind: "radix";
|
|
18
|
+
private readonly renderer;
|
|
19
|
+
private readonly stages;
|
|
20
|
+
private readonly workingAttributes;
|
|
21
|
+
/** Frees the JS mirrors three keeps behind the GPU-only ping-pong buffers. */
|
|
22
|
+
private readonly mirrors;
|
|
23
|
+
/** Set by {@link dispose}; makes a second dispose a no-op. */
|
|
24
|
+
private disposed;
|
|
25
|
+
private readonly viewRow2;
|
|
26
|
+
private readonly depthMin;
|
|
27
|
+
private readonly depthScale;
|
|
28
|
+
private readonly activeCount;
|
|
29
|
+
private readonly viewCenter;
|
|
30
|
+
/** Exact mode avoids scene-bounds quantization entirely. */
|
|
31
|
+
private readonly exactDepth;
|
|
32
|
+
constructor(options: {
|
|
33
|
+
renderer: THREE.WebGPURenderer;
|
|
34
|
+
capacity: number;
|
|
35
|
+
centersTexture?: THREE.DataTexture;
|
|
36
|
+
dataTextureWidth?: number;
|
|
37
|
+
/** Gathered world-space centers for a unified renderer. */
|
|
38
|
+
centersBuffer?: THREE.StorageBufferAttribute;
|
|
39
|
+
splatIndexAttribute: THREE.StorageInstancedBufferAttribute;
|
|
40
|
+
sourceIndexAttribute: THREE.StorageBufferAttribute;
|
|
41
|
+
/** Keep every Float32 depth bit instead of quantizing to 24 bits. */
|
|
42
|
+
exactDepth?: boolean;
|
|
43
|
+
});
|
|
44
|
+
sort(modelView: THREE.Matrix4, activeCount: number, bounds: THREE.Sphere): boolean;
|
|
45
|
+
dispose(): void;
|
|
46
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlayCanvas-style proxy-mesh splat relighting (screen-space modulate).
|
|
3
|
+
*
|
|
4
|
+
* Host lights a proxy mesh into an RGBA render target (RGB = lit color,
|
|
5
|
+
* A = coverage), then {@link SplatMesh.setRelighting} /
|
|
6
|
+
* {@link UnifiedSplatRenderer.setRelighting} multiplies baked splat color in
|
|
7
|
+
* the display fragment. Not a `SplatModifier` - coverage is per-pixel.
|
|
8
|
+
*
|
|
9
|
+
* See `docs/guide/relighting.md`.
|
|
10
|
+
*/
|
|
11
|
+
import * as THREE from 'three/webgpu';
|
|
12
|
+
/** Defaults match PlayCanvas `GsplatRelighting` (0.5 gray proxy albedo → brightness 2). */
|
|
13
|
+
export declare const DEFAULT_RELIGHT_BLEND = 1;
|
|
14
|
+
export declare const DEFAULT_RELIGHT_BRIGHTNESS = 2;
|
|
15
|
+
export declare const DEFAULT_RELIGHT_BACKGROUND = 1;
|
|
16
|
+
/** Screen-space soft edge on coverage (px). Softens coarse proxy silhouettes. */
|
|
17
|
+
export declare const DEFAULT_RELIGHT_SOFTNESS = 0;
|
|
18
|
+
/** Live screen-space relighting settings for {@link SplatMesh.setRelighting}. */
|
|
19
|
+
export type RelightingSettings = {
|
|
20
|
+
/** Lit proxy render: RGB = lighting, A = mesh coverage (0 = sky / uncovered). */
|
|
21
|
+
map: THREE.Texture;
|
|
22
|
+
/** How much the map affects splat color (`0` = baked only, `1` = full modulate). */
|
|
23
|
+
blend?: number;
|
|
24
|
+
/** Scales `map.rgb` before multiply; `2` compensates a 0.5 gray proxy albedo. */
|
|
25
|
+
brightness?: number;
|
|
26
|
+
/** Multiplier for splats where `map.a ≈ 0` (sky / uncovered). */
|
|
27
|
+
background?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Softens the coverage mask over this many screen pixels (box filter). Use
|
|
30
|
+
* `2`–`4` when the proxy is a coarse collision mesh so triangle silhouettes
|
|
31
|
+
* do not read as a static shadow. `0` = hard PlayCanvas-style edges.
|
|
32
|
+
*/
|
|
33
|
+
softness?: number;
|
|
34
|
+
};
|
|
35
|
+
/** Resolved numeric fields after clamping (map omitted - still the host texture). */
|
|
36
|
+
export type RelightingUniforms = {
|
|
37
|
+
blend: number;
|
|
38
|
+
brightness: number;
|
|
39
|
+
background: number;
|
|
40
|
+
softness: number;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Clamps blend / brightness / background / softness.
|
|
44
|
+
*/
|
|
45
|
+
export declare function clampRelightingSettings(partial: Partial<Pick<RelightingSettings, 'blend' | 'brightness' | 'background' | 'softness'>>, previous?: RelightingUniforms): RelightingUniforms;
|
|
46
|
+
/**
|
|
47
|
+
* 1×1 opaque white placeholder so the fragment graph can always sample a map
|
|
48
|
+
* when relighting is off (`blend === 0`).
|
|
49
|
+
*/
|
|
50
|
+
export declare function createPlaceholderRelightTexture(): THREE.DataTexture;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import type { SplatData } from './splat-data';
|
|
3
|
+
/**
|
|
4
|
+
* Volume selection over splat centers (CPU).
|
|
5
|
+
*
|
|
6
|
+
* A {@link SelectionVolume} answers one question - is this point inside the
|
|
7
|
+
* volume? - so hosts can select a region of a loaded splat cloud with a placed
|
|
8
|
+
* box, sphere or cylinder ({@link createSelectionVolume}), or with any custom
|
|
9
|
+
* shape by implementing the interface directly.
|
|
10
|
+
*
|
|
11
|
+
* Selection runs over a `SplatData`'s positions ({@link selectInData}); the
|
|
12
|
+
* split itself lives in `splat-partition.ts`. Everything here is pure array
|
|
13
|
+
* math - no GPU, no mesh - so it is cheap to test and works the same for any
|
|
14
|
+
* host. A one-shot selection gesture over a few million centers is a
|
|
15
|
+
* brute-force scan by design: it costs milliseconds, needs no index to build
|
|
16
|
+
* or invalidate, and supports arbitrarily transformed (including non-uniformly
|
|
17
|
+
* scaled) volumes that a radius-based spatial grid cannot.
|
|
18
|
+
*/
|
|
19
|
+
export interface SelectionVolume {
|
|
20
|
+
/**
|
|
21
|
+
* Whether the point `(x, y, z)` lies inside the volume, in **the frame the
|
|
22
|
+
* volume was built for** - for {@link createSelectionVolume} that is the
|
|
23
|
+
* frame of the positions passed to {@link selectInData} (splat-local for a
|
|
24
|
+
* `SplatData`, source-local for a collision mesh), fixed by the
|
|
25
|
+
* `worldMatrix` argument.
|
|
26
|
+
*
|
|
27
|
+
* A splat is selected iff its *center* passes this test; a splat whose
|
|
28
|
+
* center is outside but whose ellipsoid extent overlaps the volume is not
|
|
29
|
+
* selected.
|
|
30
|
+
*/
|
|
31
|
+
containsPoint(x: number, y: number, z: number): boolean;
|
|
32
|
+
}
|
|
33
|
+
/** The built-in selection shapes. Custom shapes implement {@link SelectionVolume}. */
|
|
34
|
+
export type SelectionVolumeKind = 'box' | 'sphere' | 'cylinder';
|
|
35
|
+
/** Describes a built-in selection shape for {@link createSelectionVolume}. */
|
|
36
|
+
export interface SelectionVolumeOptions {
|
|
37
|
+
kind: SelectionVolumeKind;
|
|
38
|
+
/**
|
|
39
|
+
* Places the shape: volume-local → world. Encodes the shape's center,
|
|
40
|
+
* orientation and any (possibly non-uniform) scale. Default identity.
|
|
41
|
+
* Must be invertible - a zero scale axis throws.
|
|
42
|
+
*/
|
|
43
|
+
transform?: THREE.Matrix4;
|
|
44
|
+
/**
|
|
45
|
+
* Box half-extents (volume-local units). Required for `kind: 'box'`; every
|
|
46
|
+
* component must be positive.
|
|
47
|
+
*/
|
|
48
|
+
halfExtents?: readonly [number, number, number];
|
|
49
|
+
/**
|
|
50
|
+
* Sphere or cylinder radius (volume-local units). Required for
|
|
51
|
+
* `kind: 'sphere'` and `kind: 'cylinder'`; must be positive.
|
|
52
|
+
*/
|
|
53
|
+
radius?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Cylinder full height along the volume-local Y axis. Required for
|
|
56
|
+
* `kind: 'cylinder'`; must be positive.
|
|
57
|
+
*/
|
|
58
|
+
height?: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Builds a {@link SelectionVolume} for one of the built-in shapes.
|
|
62
|
+
*
|
|
63
|
+
* `worldMatrix` is the local → world matrix of the geometry being selected
|
|
64
|
+
* (`mesh.matrixWorld`; omit for data not attached to a mesh). It fixes the
|
|
65
|
+
* frame {@link SelectionVolume.containsPoint} expects: each point is mapped
|
|
66
|
+
* through `transform⁻¹ · worldMatrix` into the shape's own frame, so a rotated
|
|
67
|
+
* or non-uniformly scaled volume placement costs the same as an axis-aligned
|
|
68
|
+
* one.
|
|
69
|
+
*
|
|
70
|
+
* @throws {Error} if the shape's dimensions are missing/non-positive, or if
|
|
71
|
+
* the combined `transform⁻¹ · worldMatrix` is singular or non-finite (a
|
|
72
|
+
* collapsed axis on either matrix would otherwise select silent garbage).
|
|
73
|
+
*/
|
|
74
|
+
export declare function createSelectionVolume(options: SelectionVolumeOptions, worldMatrix?: THREE.Matrix4): SelectionVolume;
|
|
75
|
+
/**
|
|
76
|
+
* The indices of every splat in `data` whose center is inside `volume`,
|
|
77
|
+
* ascending. Pure read - `data` is untouched; feed the result (or the volume
|
|
78
|
+
* directly) to `partitionSplatData` to split.
|
|
79
|
+
*/
|
|
80
|
+
export declare function selectInData(data: SplatData, volume: SelectionVolume): Uint32Array;
|
|
81
|
+
/**
|
|
82
|
+
* How many splats in `data` fall inside `volume`, without materializing the
|
|
83
|
+
* index list - for a live "n selected" readout while a host drags a volume
|
|
84
|
+
* around, where {@link selectInData}'s `Uint32Array(count)` would allocate tens
|
|
85
|
+
* of megabytes per pointer event on a large scene.
|
|
86
|
+
*/
|
|
87
|
+
export declare function countInData(data: SplatData, volume: SelectionVolume): number;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const p = [2047, 1023, 2047];
|
|
2
|
+
function v(n) {
|
|
3
|
+
return [0, 3, 8, 15][n] ?? 0;
|
|
4
|
+
}
|
|
5
|
+
function C(n, t, a, m) {
|
|
6
|
+
const i = v(a);
|
|
7
|
+
let o = m ?? 0;
|
|
8
|
+
if (m === void 0)
|
|
9
|
+
for (const c of n) o = Math.max(o, Math.abs(c));
|
|
10
|
+
const s = {
|
|
11
|
+
min: [-o, -o, -o],
|
|
12
|
+
max: [o, o, o]
|
|
13
|
+
}, r = new Uint32Array(t * i), h = o || 1;
|
|
14
|
+
for (let c = 0; c < r.length; c++) {
|
|
15
|
+
const e = c * 3, u = (l, M) => Math.min(M, Math.max(0, Math.round((l / h + 1) * 0.5 * M))), x = u(n[e], 2047), d = u(n[e + 1], 1023), f = u(n[e + 2], 2047);
|
|
16
|
+
r[c] = (x | d << 11 | f << 21) >>> 0;
|
|
17
|
+
}
|
|
18
|
+
return { bands: a, packed: r, range: s };
|
|
19
|
+
}
|
|
20
|
+
function k(n) {
|
|
21
|
+
const t = (a, m, i) => {
|
|
22
|
+
if (m === a) return 0;
|
|
23
|
+
const o = (0 - a) / (m - a);
|
|
24
|
+
return Math.max(0, Math.min(i, Math.round(o * i)));
|
|
25
|
+
};
|
|
26
|
+
return (t(n.min[0], n.max[0], 2047) | t(n.min[1], n.max[1], 1023) << 11 | t(n.min[2], n.max[2], 2047) << 21) >>> 0;
|
|
27
|
+
}
|
|
28
|
+
function q(n, t) {
|
|
29
|
+
return n.min[0] === t.min[0] && n.min[1] === t.min[1] && n.min[2] === t.min[2] && n.max[0] === t.max[0] && n.max[1] === t.max[1] && n.max[2] === t.max[2];
|
|
30
|
+
}
|
|
31
|
+
function S(n, t, a) {
|
|
32
|
+
return n + (t - n) * a;
|
|
33
|
+
}
|
|
34
|
+
function A(n, t, a) {
|
|
35
|
+
const m = [n & 2047, n >>> 11 & 1023, n >>> 21 & 2047];
|
|
36
|
+
let i = 0, o = 0;
|
|
37
|
+
for (let s = 0; s < 3; s++) {
|
|
38
|
+
const r = p[s], h = S(
|
|
39
|
+
t.min[s],
|
|
40
|
+
t.max[s],
|
|
41
|
+
m[s] / r
|
|
42
|
+
), c = a.min[s], e = a.max[s], u = e === c ? 0 : (h - c) / (e - c), x = Math.max(0, Math.min(r, Math.round(u * r)));
|
|
43
|
+
i |= x << o, o += s === 0 ? 11 : 10;
|
|
44
|
+
}
|
|
45
|
+
return i >>> 0;
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
q as a,
|
|
49
|
+
k as n,
|
|
50
|
+
C as p,
|
|
51
|
+
A as r,
|
|
52
|
+
v as s
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=sh-pack-D5wAe5gg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sh-pack-D5wAe5gg.js","sources":["../src/lib/sh-pack.ts"],"sourcesContent":["/**\n * Packing and requantization for per-splat higher-order SH.\n *\n * Every streamed format that keeps view-dependent color stores it in one\n * layout: {@link SplatPackedShData}, a `DecodePacked_11_10_11` word per\n * coefficient (R: bits 0-10 /2047, G: 11-20 /1023, B: 21-31 /2047) dequantized\n * across a per-channel range. LCC `Quality` captures and `.rad` deliver it directly; a\n * streamed SOG (or `.lcc2`) palette is *converted* into it at decode\n * ({@link packPaletteSh}) so it survives the shared pool - see\n * `docs/formats/streamed-shn-notes.md` (M11). The pool decodes the whole scene through\n * one range uniform, so a chunk whose range differs is requantized into the\n * scene's range at append ({@link requantizeShWord}).\n *\n * Pure array math, no THREE - safe to import in the decode worker.\n */\nimport type { SplatPackedShData, SplatShData } from './splat-data';\n\n/** A per-channel `[min, max]` dequantization range for packed SH. */\nexport type ShRange = SplatPackedShData['range'];\n\n/** The 11/10/11 field maxima of a packed SH word, per channel. */\nconst SH_FIELD_MAX = [2047, 1023, 2047] as const;\n\n/** Coefficients per channel for a band count (0 → none, 3 → 3rd order). */\nexport function shCoefficientCount(bands: number): number {\n return [0, 3, 8, 15][bands] ?? 0;\n}\n\n/**\n * Quantizes splat-major coefficient triples into packed 11/10/11 words:\n * `count * shCoefficientCount(bands)` words, each holding one coefficient's\n * (R, G, B). `knownExtent` pins the symmetric range to a scene-wide value\n * (values outside clamp); without it the extent is measured from\n * `coefficients` - only safe when they are the whole scene, since every chunk\n * must share one range.\n */\nexport function packShCoefficients(\n coefficients: Float32Array,\n count: number,\n bands: 1 | 2 | 3,\n knownExtent?: number,\n): SplatPackedShData {\n const words = shCoefficientCount(bands);\n let extent = knownExtent ?? 0;\n if (knownExtent === undefined) {\n for (const value of coefficients) extent = Math.max(extent, Math.abs(value));\n }\n // The shader has one range for every band. A symmetric common range\n // preserves the signed SH convention; exact 0 is not representable (it falls\n // between the two middle codes, a half-LSB positive bias - see the tests).\n const range = {\n min: [-extent, -extent, -extent] as const,\n max: [extent, extent, extent] as const,\n };\n const packed = new Uint32Array(count * words);\n const divisor = extent || 1;\n for (let i = 0; i < packed.length; i++) {\n const base = i * 3;\n const encode = (value: number, max: number): number =>\n Math.min(max, Math.max(0, Math.round((value / divisor + 1) * 0.5 * max)));\n const r = encode(coefficients[base] as number, 2047);\n const g = encode(coefficients[base + 1] as number, 1023);\n const b = encode(coefficients[base + 2] as number, 2047);\n packed[i] = (r | (g << 11) | (b << 21)) >>> 0;\n }\n return { bands, packed, range };\n}\n\n/**\n * Converts a palette-compressed SH source ({@link SplatShData}, as SOG/`.lcc2`\n * store it) into the per-splat packed form, keeping the lowest `bands` bands.\n * Each splat's palette label indexes the codebook image; the coefficients are\n * read out into splat-major triples and quantized against this chunk's own\n * measured extent. Later chunks may measure a different extent - the pool\n * requantizes any mismatch into the scene's range at append.\n */\nexport function packPaletteSh(sh: SplatShData, count: number, bands: 1 | 2 | 3): SplatPackedShData {\n const want = shCoefficientCount(bands);\n // The palette stores its own (possibly higher) band count; a lower request is\n // a prefix of each entry's coefficients, since bands run low-order first.\n const have = shCoefficientCount(sh.bands);\n const width = sh.paletteWidth;\n const coefficients = new Float32Array(count * want * 3);\n for (let i = 0; i < count; i++) {\n const label = sh.labels[i] as number;\n // SOG centroids layout: entry n, coefficient c at column (n % 64) · have + c,\n // row ⌊n / 64⌋; R/G/B channels hold that coefficient for the R/G/B SH.\n const column0 = (label % 64) * have;\n const row = Math.floor(label / 64);\n // Labels are 16-bit and the parser validates only the labels image, not\n // the label *values*. An out-of-bounds palette read yields NaN\n // coefficients, NaN-poisoning this chunk's measured range - and, were it\n // the scene's first SH chunk, locking a NaN pool range that destroys SH\n // scene-wide. Throw instead, like the `.rad` label path does.\n // Check the row extent as well as the linear index: a label whose entry\n // would wrap past the row's end must fail rather than silently reading\n // the start of another entry on the next row.\n if (column0 + have > width || (row * width + column0 + have) * 4 > sh.palette.length) {\n throw new Error(`packPaletteSh: splat ${i} has SH label ${label} outside the palette.`);\n }\n // Read the min(want, have) low-order coefficients; if the caller asked for\n // more bands than the palette carries, the surplus stays 0 (neutral) so the\n // packed band count still matches the pool's, rather than a band mismatch\n // that would drop the SH entirely.\n const readable = Math.min(want, have);\n for (let c = 0; c < readable; c++) {\n const texel = (row * width + column0 + c) * 4;\n const base = (i * want + c) * 3;\n coefficients[base] = sh.palette[texel] as number;\n coefficients[base + 1] = sh.palette[texel + 1] as number;\n coefficients[base + 2] = sh.palette[texel + 2] as number;\n }\n }\n return packShCoefficients(coefficients, count, bands);\n}\n\n/** The packed word that decodes to 0.0 in every channel under `range`. */\nexport function neutralShWord(range: ShRange): number {\n const code = (lo: number, hi: number, maxCode: number): number => {\n if (hi === lo) return 0;\n const t = (0 - lo) / (hi - lo);\n return Math.max(0, Math.min(maxCode, Math.round(t * maxCode)));\n };\n return (\n (code(range.min[0], range.max[0], 2047) |\n (code(range.min[1], range.max[1], 1023) << 11) |\n (code(range.min[2], range.max[2], 2047) << 21)) >>>\n 0\n );\n}\n\n/** Whether two packed-SH ranges are bit-identical (no requantization needed). */\nexport function packedRangesEqual(a: ShRange, b: ShRange): boolean {\n return (\n a.min[0] === b.min[0] &&\n a.min[1] === b.min[1] &&\n a.min[2] === b.min[2] &&\n a.max[0] === b.max[0] &&\n a.max[1] === b.max[1] &&\n a.max[2] === b.max[2]\n );\n}\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n/**\n * Re-encodes one packed word so it decodes to the same three channel values\n * under `to` as it did under `from`. Values outside `to` clip to its endpoints;\n * a zero-width `to` channel collapses to code 0. This is what lets chunks\n * quantized against different ranges share one pool range.\n */\nexport function requantizeShWord(word: number, from: ShRange, to: ShRange): number {\n const codes = [word & 0x7ff, (word >>> 11) & 0x3ff, (word >>> 21) & 0x7ff];\n let out = 0;\n let shift = 0;\n for (let ch = 0; ch < 3; ch++) {\n const maxCode = SH_FIELD_MAX[ch] as number;\n const value = lerp(\n from.min[ch] as number,\n from.max[ch] as number,\n (codes[ch] as number) / maxCode,\n );\n const lo = to.min[ch] as number;\n const hi = to.max[ch] as number;\n const t = hi === lo ? 0 : (value - lo) / (hi - lo);\n const code = Math.max(0, Math.min(maxCode, Math.round(t * maxCode)));\n out |= code << shift;\n shift += ch === 0 ? 11 : 10;\n }\n return out >>> 0;\n}\n"],"names":["SH_FIELD_MAX","shCoefficientCount","bands","packShCoefficients","coefficients","count","knownExtent","words","extent","value","range","packed","divisor","i","base","encode","max","r","g","b","neutralShWord","code","lo","hi","maxCode","t","packedRangesEqual","a","lerp","requantizeShWord","word","from","to","codes","out","shift","ch"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Packing and requantization for per-splat higher-order SH.
|
|
3
|
+
*
|
|
4
|
+
* Every streamed format that keeps view-dependent color stores it in one
|
|
5
|
+
* layout: {@link SplatPackedShData}, a `DecodePacked_11_10_11` word per
|
|
6
|
+
* coefficient (R: bits 0-10 /2047, G: 11-20 /1023, B: 21-31 /2047) dequantized
|
|
7
|
+
* across a per-channel range. LCC `Quality` captures and `.rad` deliver it directly; a
|
|
8
|
+
* streamed SOG (or `.lcc2`) palette is *converted* into it at decode
|
|
9
|
+
* ({@link packPaletteSh}) so it survives the shared pool - see
|
|
10
|
+
* `docs/formats/streamed-shn-notes.md` (M11). The pool decodes the whole scene through
|
|
11
|
+
* one range uniform, so a chunk whose range differs is requantized into the
|
|
12
|
+
* scene's range at append ({@link requantizeShWord}).
|
|
13
|
+
*
|
|
14
|
+
* Pure array math, no THREE - safe to import in the decode worker.
|
|
15
|
+
*/
|
|
16
|
+
import type { SplatPackedShData, SplatShData } from './splat-data';
|
|
17
|
+
/** A per-channel `[min, max]` dequantization range for packed SH. */
|
|
18
|
+
export type ShRange = SplatPackedShData['range'];
|
|
19
|
+
/** Coefficients per channel for a band count (0 → none, 3 → 3rd order). */
|
|
20
|
+
export declare function shCoefficientCount(bands: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* Quantizes splat-major coefficient triples into packed 11/10/11 words:
|
|
23
|
+
* `count * shCoefficientCount(bands)` words, each holding one coefficient's
|
|
24
|
+
* (R, G, B). `knownExtent` pins the symmetric range to a scene-wide value
|
|
25
|
+
* (values outside clamp); without it the extent is measured from
|
|
26
|
+
* `coefficients` - only safe when they are the whole scene, since every chunk
|
|
27
|
+
* must share one range.
|
|
28
|
+
*/
|
|
29
|
+
export declare function packShCoefficients(coefficients: Float32Array, count: number, bands: 1 | 2 | 3, knownExtent?: number): SplatPackedShData;
|
|
30
|
+
/**
|
|
31
|
+
* Converts a palette-compressed SH source ({@link SplatShData}, as SOG/`.lcc2`
|
|
32
|
+
* store it) into the per-splat packed form, keeping the lowest `bands` bands.
|
|
33
|
+
* Each splat's palette label indexes the codebook image; the coefficients are
|
|
34
|
+
* read out into splat-major triples and quantized against this chunk's own
|
|
35
|
+
* measured extent. Later chunks may measure a different extent - the pool
|
|
36
|
+
* requantizes any mismatch into the scene's range at append.
|
|
37
|
+
*/
|
|
38
|
+
export declare function packPaletteSh(sh: SplatShData, count: number, bands: 1 | 2 | 3): SplatPackedShData;
|
|
39
|
+
/** The packed word that decodes to 0.0 in every channel under `range`. */
|
|
40
|
+
export declare function neutralShWord(range: ShRange): number;
|
|
41
|
+
/** Whether two packed-SH ranges are bit-identical (no requantization needed). */
|
|
42
|
+
export declare function packedRangesEqual(a: ShRange, b: ShRange): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Re-encodes one packed word so it decodes to the same three channel values
|
|
45
|
+
* under `to` as it did under `from`. Values outside `to` clip to its endpoints;
|
|
46
|
+
* a zero-width `to` channel collapses to code 0. This is what lets chunks
|
|
47
|
+
* quantized against different ranges share one pool range.
|
|
48
|
+
*/
|
|
49
|
+
export declare function requantizeShWord(word: number, from: ShRange, to: ShRange): number;
|