@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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -0
  3. package/THIRD_PARTY_NOTICES.md +86 -0
  4. package/dist/budget-governor.d.ts +154 -0
  5. package/dist/camera-budget-governor.d.ts +234 -0
  6. package/dist/chunk-cache-budget.d.ts +156 -0
  7. package/dist/chunk-fetch-scheduler.d.ts +168 -0
  8. package/dist/chunk-loader.d.ts +85 -0
  9. package/dist/compute-sorter.d.ts +131 -0
  10. package/dist/create-splat-renderer.d.ts +99 -0
  11. package/dist/dataset-source.d.ts +55 -0
  12. package/dist/depth-of-field.d.ts +73 -0
  13. package/dist/effects.d.ts +337 -0
  14. package/dist/effects.js +180 -0
  15. package/dist/effects.js.map +1 -0
  16. package/dist/formats/ksplat/index.d.ts +8 -0
  17. package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
  18. package/dist/formats/ksplat.js +167 -0
  19. package/dist/formats/ksplat.js.map +1 -0
  20. package/dist/formats/lcc/collision-mesh.d.ts +37 -0
  21. package/dist/formats/lcc/collision-partition.d.ts +35 -0
  22. package/dist/formats/lcc/index.d.ts +16 -0
  23. package/dist/formats/lcc/lcc.d.ts +28 -0
  24. package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
  25. package/dist/formats/lcc/lcc2.d.ts +4 -0
  26. package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
  27. package/dist/formats/lcc/parse-lcc.d.ts +177 -0
  28. package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
  29. package/dist/formats/lcc.js +969 -0
  30. package/dist/formats/lcc.js.map +1 -0
  31. package/dist/formats/ply/index.d.ts +12 -0
  32. package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
  33. package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
  34. package/dist/formats/ply.js +230 -0
  35. package/dist/formats/ply.js.map +1 -0
  36. package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
  37. package/dist/formats/rad/index.d.ts +10 -0
  38. package/dist/formats/rad/parse-rad.d.ts +88 -0
  39. package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
  40. package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
  41. package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
  42. package/dist/formats/rad/rad.d.ts +132 -0
  43. package/dist/formats/rad.js +1098 -0
  44. package/dist/formats/rad.js.map +1 -0
  45. package/dist/formats/sog/index.d.ts +9 -0
  46. package/dist/formats/sog/parse-sog.d.ts +45 -0
  47. package/dist/formats/sog.js +257 -0
  48. package/dist/formats/sog.js.map +1 -0
  49. package/dist/formats/splat/index.d.ts +8 -0
  50. package/dist/formats/splat/parse-splat.d.ts +10 -0
  51. package/dist/formats/splat.js +39 -0
  52. package/dist/formats/splat.js.map +1 -0
  53. package/dist/formats/spz/index.d.ts +8 -0
  54. package/dist/formats/spz/parse-spz.d.ts +10 -0
  55. package/dist/formats/spz.js +218 -0
  56. package/dist/formats/spz.js.map +1 -0
  57. package/dist/frontier-worker-CAnK_Cmu.js +566 -0
  58. package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
  59. package/dist/half-float-Cs4FDq8Q.js +30 -0
  60. package/dist/half-float-Cs4FDq8Q.js.map +1 -0
  61. package/dist/half-float.d.ts +15 -0
  62. package/dist/index.d.ts +54 -0
  63. package/dist/index.js +9054 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/load-scene.d.ts +50 -0
  66. package/dist/load-worker-protocol.d.ts +82 -0
  67. package/dist/loading-wLo8vRbA.js +102 -0
  68. package/dist/loading-wLo8vRbA.js.map +1 -0
  69. package/dist/loading.d.ts +150 -0
  70. package/dist/lod-manifest.d.ts +61 -0
  71. package/dist/lod-scheduler.d.ts +229 -0
  72. package/dist/lod-source.d.ts +207 -0
  73. package/dist/logging-BfPdd7NJ.js +19 -0
  74. package/dist/logging-BfPdd7NJ.js.map +1 -0
  75. package/dist/logging.d.ts +37 -0
  76. package/dist/one-shot-worker-G80btnth.js +664 -0
  77. package/dist/one-shot-worker-G80btnth.js.map +1 -0
  78. package/dist/orientation.d.ts +35 -0
  79. package/dist/ply-header-BwgExCn-.js +125 -0
  80. package/dist/ply-header-BwgExCn-.js.map +1 -0
  81. package/dist/ply-header.d.ts +87 -0
  82. package/dist/radix-sort.d.ts +28 -0
  83. package/dist/radix-sorter.d.ts +46 -0
  84. package/dist/relighting.d.ts +50 -0
  85. package/dist/selection-volume.d.ts +87 -0
  86. package/dist/sh-pack-D5wAe5gg.js +54 -0
  87. package/dist/sh-pack-D5wAe5gg.js.map +1 -0
  88. package/dist/sh-pack.d.ts +49 -0
  89. package/dist/sort-scheduler.d.ts +62 -0
  90. package/dist/sort-worker-protocol.d.ts +37 -0
  91. package/dist/sorter.d.ts +31 -0
  92. package/dist/source-transform.d.ts +57 -0
  93. package/dist/splat-budget-PSojLJPO.js +157 -0
  94. package/dist/splat-budget-PSojLJPO.js.map +1 -0
  95. package/dist/splat-budget.d.ts +404 -0
  96. package/dist/splat-data-BZM5dhhx.js +10 -0
  97. package/dist/splat-data-BZM5dhhx.js.map +1 -0
  98. package/dist/splat-data.d.ts +150 -0
  99. package/dist/splat-depth-pack.d.ts +39 -0
  100. package/dist/splat-mesh-material.d.ts +255 -0
  101. package/dist/splat-mesh-picking.d.ts +87 -0
  102. package/dist/splat-mesh-pool.d.ts +241 -0
  103. package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
  104. package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
  105. package/dist/splat-mesh-types.d.ts +390 -0
  106. package/dist/splat-mesh.d.ts +737 -0
  107. package/dist/splat-modifier-stack.d.ts +38 -0
  108. package/dist/splat-modifier.d.ts +156 -0
  109. package/dist/splat-partition.d.ts +37 -0
  110. package/dist/splat-query.d.ts +51 -0
  111. package/dist/splat-scene.d.ts +127 -0
  112. package/dist/splat-sort-bounds.d.ts +11 -0
  113. package/dist/static-lod-splat-mesh.d.ts +53 -0
  114. package/dist/static-lod-worker-D2fyqQZO.js +474 -0
  115. package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
  116. package/dist/static-lod-worker-protocol.d.ts +37 -0
  117. package/dist/static-lod.d.ts +24 -0
  118. package/dist/storage-attribute-mirror.d.ts +103 -0
  119. package/dist/streamed-splat-mesh-utils.d.ts +146 -0
  120. package/dist/streamed-splat-mesh.d.ts +985 -0
  121. package/dist/unified-splat-renderer.d.ts +205 -0
  122. package/dist/webgpu-limits.d.ts +133 -0
  123. package/dist/work-buffer-gather.d.ts +119 -0
  124. package/dist/work-buffer-material.d.ts +42 -0
  125. package/dist/worker-sorter.d.ts +57 -0
  126. package/dist/xr-view.d.ts +92 -0
  127. package/package.json +135 -21
@@ -0,0 +1,737 @@
1
+ import { type SplatChannelOptions, type SplatHeightResult, type SplatMeshOptions, type SplatNearestResult, type SplatPerformanceProfile, type SplatPickOptions, type SplatPickResult, type SplatRayResult, type SplatRange, type SplatUpdateOptions, type UnifiedSourceView } from './splat-mesh-types';
2
+ export * from './splat-mesh-types';
3
+ import * as THREE from 'three/webgpu';
4
+ import type { SplatData } from './splat-data';
5
+ import { type SplatOrientation } from './orientation';
6
+ import type { SplatModifier } from './splat-modifier';
7
+ import { type PerSourceSortTransform } from './compute-sorter';
8
+ import { type DepthOfFieldSettings } from './depth-of-field';
9
+ import { type RelightingSettings, type RelightingUniforms } from './relighting';
10
+ import { type SplatPoolRange, type SplatPoolTenant } from './splat-mesh-pool';
11
+ /**
12
+ * Renders a set of 3D Gaussians with three.js's WebGPURenderer.
13
+ *
14
+ * Rendering technique: EWA splatting per the 3DGS paper (Kerbl et al.,
15
+ * SIGGRAPH 2023). Each splat is an instanced quad; the vertex stage projects
16
+ * the splat's 3D covariance to a screen-space ellipse (the same math used by
17
+ * antimatter15/splat and PlayCanvas, both MIT) and the fragment stage applies
18
+ * the Gaussian falloff. Shaders are written in TSL, so three.js compiles
19
+ * them to WGSL on WebGPU and to GLSL on the automatic WebGL2 fallback.
20
+ *
21
+ * Splat data lives in pool data textures, indexed through one small
22
+ * per-instance `splatIndex` attribute. Correct alpha blending needs
23
+ * back-to-front ordering, so the indices are depth-sorted whenever the
24
+ * camera moves - only that index buffer is rewritten, never the splat data
25
+ * itself. On the WebGPU backend the sort runs in TSL compute passes
26
+ * (`ComputeSorter`); the WebGL2 fallback uses a Web Worker (`WorkerSorter`).
27
+ *
28
+ * Two construction modes:
29
+ *
30
+ * - `new SplatMesh(data)` - static: capacity equals the data's count and
31
+ * the whole scene is resident. Works on WebGPU and the WebGL2 fallback,
32
+ * including SOG view-dependent color (shN).
33
+ * - `new SplatMesh({ capacity })` - dynamic: an empty pool for up to
34
+ * `capacity` splats. Content is managed with {@link appendRange} /
35
+ * {@link removeRange} (ranges are row-aligned in the pool textures, so
36
+ * uploads are rectangular). This mode is the substrate for LOD
37
+ * streaming and works on both backends - the WebGL2 fallback sorts the
38
+ * active pool spans on the CPU. shN data on appended ranges is ignored
39
+ * (palettes are per-file and cannot be merged here).
40
+ */
41
+ export declare class SplatMesh extends THREE.Mesh implements SplatPoolTenant {
42
+ private static readonly DATA_TEXTURE_WIDTH;
43
+ /**
44
+ * The storage this mesh draws from. Held rather than inlined because a
45
+ * splat's draw identity is already independent of its pool slot (`splatIndex`
46
+ * indirects every instance), so the pool is a separable concern - see
47
+ * {@link SplatPool}.
48
+ */
49
+ private readonly pool;
50
+ /** False when the pool was supplied by the caller, and so outlives this mesh. */
51
+ private readonly ownsPool;
52
+ private readonly splatIndexAttribute;
53
+ private readonly sourceIndexAttribute;
54
+ private dataTextures;
55
+ /**
56
+ * Pool index → this mesh's packed active-list slot. Lives on the pool: it is
57
+ * keyed by pool index, so a per-mesh copy would span the whole pool and
58
+ * several meshes sharing one would each pay for all of it. A row has one
59
+ * owner at a time, so the single array is unambiguous.
60
+ */
61
+ private get activeSlotByPoolIndex();
62
+ /** Rows in the pool backing this mesh. */
63
+ private get poolRows();
64
+ private get centersTexture();
65
+ private get backing();
66
+ private get freeRowSpans();
67
+ private set freeRowSpans(value);
68
+ private get poolFloatTextures();
69
+ /** Bands of per-splat (non-palette) SH this pool stores; 0 when disabled. */
70
+ private get packedShBands();
71
+ private get shPackedTextures();
72
+ /** True when constructed from a complete SplatData (single fixed range). */
73
+ private readonly isStatic;
74
+ /** Constructor-created range, retained for static in-place LOD replacement. */
75
+ private staticRange;
76
+ private readonly shEnabled;
77
+ /**
78
+ * Dequantization range for the packed SH, shared by every coefficient.
79
+ * Uniforms rather than constants: the first chunk to arrive supplies it,
80
+ * long after the material is built.
81
+ */
82
+ private readonly shRange;
83
+ /** Set once the first packed-SH chunk has supplied {@link shRange}. */
84
+ private shRangeSet;
85
+ /** Whether SH-less rows were neutral-filled before the scene range locked. */
86
+ private wrotePreLockNeutralSh;
87
+ private readonly ranges;
88
+ private activeCount;
89
+ /**
90
+ * Bumped whenever the resident splat set or its positions change (activate,
91
+ * deactivate, compact) - the signal the lazy spatial-query grid rebuilds on.
92
+ * A depth re-sort reorders the active list but changes neither, so it does
93
+ * not bump this. See {@link queryNearest}.
94
+ */
95
+ private queryEpoch;
96
+ private queryGrid;
97
+ private queryGridEpoch;
98
+ /**
99
+ * True when the shared depth-order buffer holds a *secondary* view's order
100
+ * (left by {@link renderView}), so the next primary {@link update} must
101
+ * re-sort even if its camera has not moved. See {@link renderView} (M10).
102
+ */
103
+ private orderIsForeign;
104
+ /**
105
+ * True while the draw list holds a CPU-worker depth permutation rather than
106
+ * the identity active order. Active-list mutations must then resync the
107
+ * whole drawn prefix instead of patching slots: patching a *permutation*
108
+ * with identity active-list values creates duplicates and drops live splats
109
+ * for every frame until the worker's next order lands (the WebGL2 streamed
110
+ * flicker, ROADMAP L5).
111
+ */
112
+ private drawListSorted;
113
+ /** CPU timings from the most recent render-preparation update (reset in place per frame). */
114
+ private readonly updateTimings;
115
+ protected readonly localBounds: THREE.Box3;
116
+ protected readonly boundingSphereLocal: THREE.Sphere;
117
+ protected boundsDirty: boolean;
118
+ /** Cached {@link getUnifiedSourceView} result; see the invalidation checks there. */
119
+ private cachedUnifiedView;
120
+ private readonly cachedUnifiedViewMatrixWorld;
121
+ private readonly cachedUnifiedViewLocalBounds;
122
+ private readonly cachedUnifiedViewWorldBounds;
123
+ /** Row spans written since the last flush, awaiting GPU upload. */
124
+ private pendingUploadRows;
125
+ /** Row spans awaiting mirroring to the CPU sort worker (WebGL2 path). */
126
+ private workerDirtyRows;
127
+ /** Persistent copy sources avoid allocating four temporary textures per upload region. */
128
+ private readonly uploadStaging;
129
+ /** Reusable float→half encode buffers keyed like {@link uploadStaging}. */
130
+ private readonly halfEncodeBuffers;
131
+ /** Opt-in per-splat channels by name; see {@link defineChannel}. */
132
+ private readonly channels;
133
+ /** Created on the first update, when the renderer backend is known. */
134
+ private sorter;
135
+ /**
136
+ * Set by a unified {@link SplatScene} subclass before the first sort: makes
137
+ * the WebGPU sorter transform each splat's center to world space by its
138
+ * source's matrix before measuring depth. `null` for an ordinary mesh, whose
139
+ * sorter is byte-identical to before. See `source-transform.ts`.
140
+ */
141
+ protected perSourceSort: PerSourceSortTransform | null;
142
+ /** Effect hooks folded into the vertex graph; see {@link modifiers}. */
143
+ private modifierList;
144
+ /** Increments when a unified gather must rebuild its source graph. */
145
+ private graphRevision;
146
+ /** Version for unified work-buffer caching; source pool writes are observable. */
147
+ private contentRevision;
148
+ /** Material-graph inputs kept for rebuilds when the modifier list changes. */
149
+ private materialInputs;
150
+ /** Projection focal lengths in pixels; updated every frame. */
151
+ private readonly focal;
152
+ /** Drawing-buffer size in pixels; updated every frame. */
153
+ private readonly viewport;
154
+ /** Camera position in this mesh's local space, for SH view dependence. */
155
+ private readonly localCameraPosition;
156
+ /**
157
+ * Frontier-cut limit for `foveationMode: 'frontier'` - the maximum
158
+ * `own_size / distance` a splat may have and still draw (Spark's
159
+ * `pixelScaleLimit`). Set each frame to `foveationTargetPx / focalPx` so the
160
+ * cut targets a fixed on-screen size. Unused in `'band'` mode.
161
+ */
162
+ private readonly pixelScaleLimit;
163
+ /**
164
+ * Core projected-2D depth of field (live uniforms). Aperture `0` disables.
165
+ * Prefer this over the M13 `depthOfFieldPreset` modifier for camera DoF.
166
+ */
167
+ private readonly dofFocusDistance;
168
+ private readonly dofAperture;
169
+ /**
170
+ * Proxy-mesh screen-space relighting (live uniforms + map). `blend === 0`
171
+ * disables. Swapping the map rebuilds the material graph once.
172
+ */
173
+ private readonly relightPlaceholder;
174
+ private relightMap;
175
+ private readonly relightBlend;
176
+ private readonly relightBrightness;
177
+ private readonly relightBackground;
178
+ private readonly relightSoftness;
179
+ /**
180
+ * Live screen-radius band bounds (px), seeded from
181
+ * {@link SplatMeshOptions.minSplatScreenRadius} / `maxSplatScreenRadius` and
182
+ * moved by {@link setScreenRadiusBand} - so a foveated mesh can widen the band
183
+ * as its LOD cut refines rather than culling the detail the refinement bought.
184
+ */
185
+ private readonly screenBandMin;
186
+ private readonly screenBandMax;
187
+ /** The pick pass and everything it allocates; see `splat-mesh-picking.ts`. */
188
+ private readonly picker;
189
+ /** Visibility supplied by an owning unified renderer for source-only picks. */
190
+ private unifiedPickVisibility;
191
+ /** Set by {@link dispose}; subclasses use it to drop late async results. */
192
+ protected disposed: boolean;
193
+ /** The renderer this mesh last updated with, so {@link dispose} can free
194
+ * storage-attribute GPU buffers that never sat in a geometry. */
195
+ private lastRenderer;
196
+ private readonly currentModelView;
197
+ /** Initialized to an impossible matrix so the first frame always sorts. */
198
+ private readonly lastSortedModelView;
199
+ private readonly sortScheduler;
200
+ /** One-frame queue-headroom hint used before a staged atomic commit. */
201
+ private deferSortRequestOnce;
202
+ /** Bumped by every active-list mutation; identifies a draw list. */
203
+ private activeListVersion;
204
+ /** The active list the current depth order was built from. */
205
+ private sortedActiveListVersion;
206
+ private readonly sortStrategy;
207
+ /** Resolved only when `sortStrategy === 'radix'`; see {@link ensureRadixSorter}. */
208
+ private RadixSorterCtor;
209
+ private radixSorterLoad;
210
+ private performanceProfileValue;
211
+ /** Gaussian cutoff radius in σ; baked into the material graph. */
212
+ private maxStdDevValue;
213
+ /** Screen-space minimum splat radius in px; baked into the material graph. */
214
+ private readonly minSplatSizePx;
215
+ /** Mip-Splatting 2D antialiasing filter; baked into the material graph. */
216
+ private readonly antialias;
217
+ /** Format-selected low-pass and opacity-compensation policy. */
218
+ private readonly projectedFilterProfile;
219
+ /** Emit sRGB colors for gamma-space compositing; baked into the material graph. */
220
+ private readonly srgbOutput;
221
+ /** Screen-radius cull threshold in px (0 = off); baked into the material graph. */
222
+ private readonly maxSplatScreenRadius;
223
+ private readonly minSplatScreenRadius;
224
+ /** `.rad` foveation cut selector; baked into the material graph. */
225
+ private readonly foveationMode;
226
+ /** Finest on-screen node size (px) the frontier cut may request. */
227
+ private readonly foveationTargetPx;
228
+ /** Target upper bound on the frontier cut's drawn-splat count. */
229
+ private readonly foveationDrawBudget;
230
+ /** Rendered major/minor axis-ratio cap (0 = off); baked into the material graph. */
231
+ private readonly maxSplatAspect;
232
+ /** Spark LOD alpha encoding + merged-node rendering; baked into the material. */
233
+ private readonly lodAlpha;
234
+ /** Live frontier limit in px; self-adjusts between `foveationTargetPx` (finest)
235
+ * and coarser to hold `foveationDrawBudget`. Feeds `pixelScaleLimit` each frame. */
236
+ private foveationLimitPx;
237
+ /** Throttle timestamp for the adaptive-limit estimate (ms, `performance.now`). */
238
+ private lastFoveationAdaptAt;
239
+ /** Y-up normalization policy; drives the static self-orientation below. */
240
+ readonly orientation: SplatOrientation;
241
+ /** Set once the pool has been checked against the device texture limit. */
242
+ private textureLimitChecked;
243
+ constructor(source: SplatData | {
244
+ capacity: number;
245
+ }, options?: SplatMeshOptions);
246
+ /**
247
+ * Higher-order SH bands this mesh actually renders (0 when it has none),
248
+ * whatever the source: a static mesh's palette shN, or per-splat SH in a
249
+ * dynamic pool. Reflects what was resolved at construction, not what was
250
+ * asked for - useful for a debug readout.
251
+ */
252
+ get shBands(): number;
253
+ /**
254
+ * The contribution-culling profile currently in effect (resolved at
255
+ * construction, or the last {@link setPerformanceProfile} value).
256
+ */
257
+ get performanceProfile(): SplatPerformanceProfile;
258
+ /**
259
+ * Changes contribution culling without changing scene data or persisted
260
+ * settings.
261
+ *
262
+ * Mutator convention: state that is plain data is a property (get/set pair);
263
+ * a `setX` method exists where the write has behavior - this one rebuilds the
264
+ * material graph, {@link StreamedSplatMesh.setBudget} returns its clamp.
265
+ * Every mutator has a matching readable property.
266
+ */
267
+ setPerformanceProfile(profile: SplatPerformanceProfile): void;
268
+ /**
269
+ * Gaussian cutoff radius currently in effect, in standard deviations.
270
+ * Resolved at construction, or the last {@link setMaxStdDev} value.
271
+ */
272
+ get maxStdDev(): number;
273
+ /**
274
+ * Changes the Gaussian cutoff without changing scene data.
275
+ *
276
+ * Lowering it shrinks every quad and is the main fill-rate lever in a busy
277
+ * view; raising it restores the faint outer tail. Rebuilds the material
278
+ * graph, same convention as {@link setPerformanceProfile}.
279
+ */
280
+ setMaxStdDev(value: number): void;
281
+ /**
282
+ * Copies a decoded scene (or chunk) into the pool and activates it.
283
+ * Ranges are row-aligned in the pool textures, so each allocation may
284
+ * round up to the next multiple of the texture width internally.
285
+ *
286
+ * @returns A handle to pass to {@link removeRange}.
287
+ * @throws {Error} when the remaining pool capacity cannot fit the range.
288
+ */
289
+ appendRange(data: SplatData): SplatRange;
290
+ /** Appends uploaded data without exposing it to draw/sort until activated. */
291
+ protected appendInactiveRange(data: SplatData): SplatRange;
292
+ /** Reserves an inactive pool range whose data can be filled over multiple frames. */
293
+ protected reserveInactiveRange(count: number): SplatRange;
294
+ /** Writes one contiguous segment of a reserved inactive range. */
295
+ protected writeInactiveRange(handle: SplatRange, data: SplatData, offset: number): void;
296
+ /**
297
+ * Overwrites splats at `offset` within a range **in place** - unlike
298
+ * {@link writeInactiveRange} this allows an *active* range, so the frontier
299
+ * page table can page individual slots of its always-active slab. Queues the
300
+ * touched rows for upload; the caller must {@link invalidateSort} once per
301
+ * batch since splat depths changed.
302
+ */
303
+ protected overwriteRangeData(handle: SplatRange, data: SplatData, offset: number): void;
304
+ /**
305
+ * Zeros splats `[offset, offset + count)` of a range so they draw nothing
306
+ * (zero covariance → degenerate quad, zero color). Used to free frontier slab
307
+ * slots that leave the frontier. Queues the rows for upload.
308
+ */
309
+ protected degenerateRange(handle: SplatRange, offset: number, count: number): void;
310
+ /**
311
+ * Copies one chunk's splats into the pool's backing arrays at `destination`.
312
+ * Shared by the append and staged-write paths, which differ only in where
313
+ * the rows come from and when they are activated.
314
+ */
315
+ private writeSplatRows;
316
+ /**
317
+ * Scatters a chunk's packed SH words into the per-group pool textures.
318
+ *
319
+ * A chunk without SH still writes: pool rows are reused, so leaving the
320
+ * previous occupant's words behind would give a DC-only chunk somebody
321
+ * else's view-dependent color. Zero is not neutral (the range is signed and
322
+ * rarely symmetric), so the code for "0.0" in this scene's range is.
323
+ */
324
+ private writePackedSh;
325
+ /**
326
+ * Adopts the first packed-SH chunk's dequantization range as the scene's one
327
+ * pool uniform. LCC/`.rad` chunks already share one range; a streamed SOG
328
+ * chunk measures its own extent, so later chunks are requantized into this
329
+ * one at write time ({@link writePackedSh}) rather than ignored.
330
+ */
331
+ private applyShRange;
332
+ /** The scene's locked packed-SH range as a plain tuple, or null if unset. */
333
+ private currentShRange;
334
+ /** The packed word decoding to 0 in every channel under the scene's range. */
335
+ private neutralShWord;
336
+ /** Warns once per call site when a source's SH cannot be stored. */
337
+ private warnOnIgnoredSh;
338
+ /** Atomically includes or excludes a resident range on the next active-list rebuild. */
339
+ protected setRangeActive(handle: SplatRange, active: boolean): void;
340
+ /**
341
+ * Activates only the first `prefix` splats of a range (see
342
+ * {@link RangeRecord.activePrefix}). The page-table slab keeps its used slots
343
+ * densely packed at the front, so this bounds per-frame sorting and vertex
344
+ * work to the *drawn* frontier instead of the whole pool-sized slab. Write
345
+ * APIs (`overwriteRangeData` / `degenerateRange`) still address the full range.
346
+ */
347
+ protected setRangeActivePrefix(handle: SplatRange, prefix: number): void;
348
+ /**
349
+ * Replaces the drawn pool indices for an immutable static hierarchy.
350
+ *
351
+ * This is intentionally narrower than the range API: subclasses may select
352
+ * an arbitrary hierarchy frontier, but the referenced pool rows must remain
353
+ * resident for the lifetime of the mesh.
354
+ */
355
+ protected replaceActiveIndices(indices: Uint32Array): void;
356
+ /** Fast identity-frontier variant that avoids allocating a large index array. */
357
+ protected replaceActivePrefix(count: number): void;
358
+ /** Replaces a static mesh's resident cut without increasing its fixed pool allocation. */
359
+ protected replaceStaticData(data: SplatData): void;
360
+ private appendRangeWithState;
361
+ /**
362
+ * Deactivates a previously appended range and frees its pool rows for
363
+ * reuse. The bounding sphere used for depth quantization stays a
364
+ * conservative superset until new ranges grow it again.
365
+ */
366
+ removeRange(handle: SplatRange): void;
367
+ /** Maximum number of splats the pool can hold (row-aligned internally). */
368
+ get capacity(): number;
369
+ /** Number of splats currently active (drawn and depth-sorted). */
370
+ get activeSplatCount(): number;
371
+ /**
372
+ * Returns this mesh's current pool state for an internal unified gather.
373
+ * Call {@link update} first for streamed sources so the active list reflects
374
+ * the current LOD cut. Consumers must treat the returned GPU resources as
375
+ * read-only; range lifecycle remains owned by this mesh.
376
+ */
377
+ getUnifiedSourceView(): UnifiedSourceView;
378
+ /**
379
+ * Marks this mesh as a source drawn by {@link UnifiedSplatRenderer}.
380
+ * The source stays invisible to the regular scene draw, while its own picker
381
+ * mirrors this resolved visibility so existing per-source hit testing works.
382
+ * Internal consumers should clear the state with `null` before disposing.
383
+ */
384
+ setUnifiedPickVisibility(visible: boolean | null): void;
385
+ /**
386
+ * The visibility that actually decides whether this mesh's splats reach the
387
+ * screen: the owning {@link UnifiedSplatRenderer}'s per-source visibility
388
+ * while one owns the draw, else `Object3D.visible`. Consumers that gate on
389
+ * "is this mesh showing" - the picker, `CameraBudgetGovernor` - must read
390
+ * this rather than `visible`, which a unified renderer forces to `false` on
391
+ * every source it owns purely to keep the regular scene draw from
392
+ * double-drawing them.
393
+ */
394
+ get effectiveVisibility(): boolean;
395
+ /**
396
+ * Pool splats still allocatable, in whole rows. Because allocation is
397
+ * row-aligned, an append of n splats fits when
398
+ * `ceil(n / rowWidth) · rowWidth ≤ freeSplatCapacity` *and* a contiguous
399
+ * span exists; callers use this for conservative pre-checks before
400
+ * falling back to {@link compact}.
401
+ */
402
+ get freeSplatCapacity(): number;
403
+ /**
404
+ * Declares a named per-splat data channel - one extra value per pool slot,
405
+ * in its own pool-aligned data texture. Channels are opt-in: nothing is
406
+ * allocated until you call this. Read a channel from a modifier with
407
+ * `ctx.channel(name)` and write values per range with {@link writeChannel}.
408
+ *
409
+ * Because a splat keeps its pool row for its whole residency, a channel
410
+ * value follows the splat across depth re-sorts and pool {@link compact}ion
411
+ * - the persistent per-splat label the SDF/selection effects build on. Call
412
+ * this **before** assigning a modifier that reads the channel; a modifier
413
+ * that reads an undeclared channel is a material-build error.
414
+ *
415
+ * @throws {Error} if a channel of this name already exists.
416
+ */
417
+ defineChannel(name: string, options?: SplatChannelOptions): void;
418
+ /**
419
+ * Writes channel values for a resident range, at `[offset, offset + data.length)`
420
+ * splats within it (default `offset` 0). Values ride the same staging-upload
421
+ * path as pool data, flushed on the next {@link update}. `byte` channels take
422
+ * raw `0..255`; `float` channels take the value verbatim.
423
+ *
424
+ * @throws {Error} for an unknown channel or range, or a write past the range.
425
+ */
426
+ writeChannel(range: SplatRange, name: string, data: ArrayLike<number>, offset?: number): void;
427
+ /**
428
+ * Effect hooks, folded into the vertex stage in array order - each
429
+ * modifier sees the running result of the ones before it. Assigning a
430
+ * changed list rebuilds the material (a pipeline recompile); animating a
431
+ * modifier's own uniforms or storage buffers never does. With an empty
432
+ * list the material is exactly the unhooked renderer.
433
+ * See `docs/guide/effects-and-modifiers.md`.
434
+ */
435
+ get modifiers(): readonly SplatModifier[];
436
+ set modifiers(value: readonly SplatModifier[]);
437
+ /**
438
+ * Core projected-2D depth of field. Adds an isotropic screen-space CoC disc
439
+ * after EWA projection (not the stylized M13 scale modifier). Live uniforms
440
+ * - no material rebuild. Pass `aperture: 0` to disable.
441
+ */
442
+ setDepthOfField(settings: Partial<DepthOfFieldSettings>): void;
443
+ /**
444
+ * PlayCanvas-style proxy-mesh relighting. Multiplies baked splat color in the
445
+ * **display** fragment by a screen-space sample of `map` (RGB = lit proxy,
446
+ * A = coverage). Pass `null` to disable (`blend → 0`, placeholder map).
447
+ *
448
+ * Blend / brightness / background are live uniforms (no rebuild). Changing
449
+ * the map texture identity rebuilds the material once. Does not affect the
450
+ * pick pass. Does not invalidate a unified gather cache.
451
+ */
452
+ setRelighting(options: RelightingSettings | null): void;
453
+ /** Current relight numeric uniforms (`blend === 0` means off). */
454
+ getRelighting(): RelightingUniforms;
455
+ /**
456
+ * Moves the screen-radius band (px) without rebuilding the material.
457
+ *
458
+ * Only meaningful on a mesh constructed *with* a band - the graph decides at
459
+ * build time whether to cull on radius at all, from
460
+ * {@link SplatMeshOptions.minSplatScreenRadius} / `maxSplatScreenRadius`.
461
+ *
462
+ * A foveated mesh uses this to keep the band aligned with its LOD cut: the
463
+ * band spans roughly one level, so refining the cut to spend spare budget
464
+ * selects smaller splats, and a band left where it was would cull precisely
465
+ * the detail that refinement bought.
466
+ */
467
+ protected setScreenRadiusBand(minPx: number, maxPx: number): void;
468
+ /** Current core DoF uniforms (`aperture === 0` means off). */
469
+ getDepthOfField(): DepthOfFieldSettings;
470
+ /**
471
+ * Packs the resident ranges toward the start of the pool, removing the
472
+ * gaps that add/remove churn leaves behind and restoring a single
473
+ * contiguous free span. Use this when {@link appendRange} throws despite
474
+ * enough total free rows (row-alignment fragmentation).
475
+ *
476
+ * The CPU backing arrays are authoritative, so no data is re-fetched;
477
+ * moved rows are re-uploaded to the GPU on the next {@link update}.
478
+ */
479
+ compact(): void;
480
+ /** {@link SplatPoolTenant}: the ranges this mesh holds in the pool. */
481
+ poolRanges(): Iterable<SplatPoolRange>;
482
+ /**
483
+ * {@link SplatPoolTenant}: follow one range to its new rows. The pool has
484
+ * already moved the splat data; this moves what the mesh keys by pool row.
485
+ */
486
+ relocatePoolRange(range: SplatPoolRange, targetRow: number): void;
487
+ /** {@link SplatPoolTenant}: rebuild everything keyed by pool index. */
488
+ onPoolCompacted(): void;
489
+ /**
490
+ * Per-frame update: uploads pending pool writes, refreshes the projection
491
+ * uniforms and requests a depth re-sort when the camera or the content
492
+ * has changed. Call this every frame, before `renderer.render`.
493
+ */
494
+ update(camera: THREE.PerspectiveCamera, renderer: THREE.WebGPURenderer, options?: SplatUpdateOptions): void;
495
+ /** Returns the render-preparation CPU timings for the current update. */
496
+ protected getUpdateTimings(): Readonly<{
497
+ activeListMs: number;
498
+ uploadMs: number;
499
+ sortSubmitMs: number;
500
+ stagingTextureAllocations: number;
501
+ activeListUpdateRanges: number;
502
+ }>;
503
+ /**
504
+ * Skips one otherwise ordinary sort request, but only while the active list
505
+ * is unchanged since the last sort. Streamed staging uses this immediately
506
+ * before a forced commit sort so the GPU queue has a frame to drain; a
507
+ * concurrent swap group that mutates the active list revokes the request,
508
+ * because the depth order must always describe the list being drawn.
509
+ */
510
+ protected deferNextSortRequest(): void;
511
+ /**
512
+ * Once, on the first update (when the backend is known), fails loudly if the
513
+ * pool's `2048 × poolRows` data textures exceed the device's maximum texture
514
+ * dimension - otherwise the upload fails with a cryptic backend error and a
515
+ * frozen canvas. Streamed scenes stay under the limit (budget-bounded); this
516
+ * guards very large *static* scenes, especially on mobile (≈8192 max → about
517
+ * 16.7 M splats). If the limit can't be read, the check is skipped rather
518
+ * than risk a false rejection.
519
+ */
520
+ private assertPoolFitsDevice;
521
+ /** Bounding box of the appended splats, in this mesh's local space. */
522
+ computeSplatBounds(): THREE.Box3;
523
+ /**
524
+ * Asynchronously picks the frontmost visible splat under an NDC coordinate.
525
+ *
526
+ * Renders a one-pixel GPU depth pass (shared projection/Gaussian math with
527
+ * the display material), reads the encoded view depth, and reconstructs a
528
+ * world-space point on the splat's rendered center plane. Concurrent calls
529
+ * on this mesh are serialized. Returns `null` for misses, empty meshes,
530
+ * out-of-canvas coordinates, or after {@link dispose}.
531
+ *
532
+ * Does not return a persistent splat id - LOD streaming and pool reuse make
533
+ * identity unstable across frames.
534
+ */
535
+ pick(ndc: THREE.Vector2, camera: THREE.Camera, renderer: THREE.WebGPURenderer, options?: SplatPickOptions): Promise<SplatPickResult | null>;
536
+ /**
537
+ * The resident splat center nearest a world point, within `radius` (world
538
+ * units), or `null` if none. A synchronous CPU query over the pool's decoded
539
+ * centers - no GPU round-trip - backed by a uniform grid rebuilt only when
540
+ * the resident set changes (M9). The primitive behind measurement markers and
541
+ * proximity tests.
542
+ *
543
+ * **Resident-only:** a {@link StreamedSplatMesh} searches only the splats
544
+ * currently in the pool (the LOD the camera has resolved), so the answer is
545
+ * the nearest *loaded* splat - coarser far from the camera, absent where no
546
+ * chunk has streamed in. A static mesh searches its whole scene.
547
+ *
548
+ * World-correct under any rotation + per-axis (including non-uniform) scale:
549
+ * candidates are gathered in local space with a conservative radius
550
+ * (`radius / min axis scale`) and then ranked by **world** distance, so the
551
+ * nearest-in-world splat wins even when the axes stretch differently. Shear
552
+ * in an ancestor transform is not supported. Splats displaced by GPU
553
+ * modifiers are queried at their undisplaced CPU positions.
554
+ */
555
+ queryNearest(worldPoint: THREE.Vector3, radius: number): SplatNearestResult | null;
556
+ /**
557
+ * Returns the first resident splat center inside a world-space ray cone.
558
+ * This is a synchronous CPU query, intended for interactions that cannot
559
+ * wait for (or rely on) a GPU readback. `radiusAtUnitDistance` is the cone's
560
+ * world-space radius one unit from the origin; `minimumRadius` keeps nearby
561
+ * point-like splats practical to target.
562
+ *
563
+ * Like {@link queryNearest}, this searches resident, undisplaced CPU centers.
564
+ */
565
+ queryRay(ray: THREE.Ray, radiusAtUnitDistance?: number, minimumRadius?: number): SplatRayResult | null;
566
+ /**
567
+ * The supporting surface beneath a world point: the highest resident splat
568
+ * that lies no more than `maxDrop` below it (world −Y) and within `radius`
569
+ * horizontally, or `null` if none. A downward probe over splat centers - the
570
+ * primitive behind floor-following and teleport validation - without a GPU
571
+ * pick or a collision mesh (M9), so it works for every format, not only the
572
+ * `.lcc2` captures that ship collision geometry.
573
+ *
574
+ * `radius` (default `maxDrop / 2`) is the horizontal tolerance: splats are
575
+ * points, so an exact vertical hit is unlikely and a small disc is searched.
576
+ * Resident-only semantics and the world-correctness contract (rotation +
577
+ * per-axis scale supported, shear not) are the same as {@link queryNearest};
578
+ * every candidate is judged in world space, where the −Y probe direction is
579
+ * defined, so the vertical test never assumes local axis alignment.
580
+ */
581
+ queryHeight(worldPoint: THREE.Vector3, maxDrop: number, radius?: number): SplatHeightResult | null;
582
+ /**
583
+ * Renders this mesh from a second camera - into `target`, or the canvas when
584
+ * omitted - with depth order **and** projection correct for *that* camera
585
+ * (M10). The primitive behind mirrors, portals, split panes, and thumbnails.
586
+ *
587
+ * Depth order is view-dependent, but a mesh keeps one sorted order buffer:
588
+ * `update()` sorts it for the single camera it is given, so a second view
589
+ * drawn with that order shows transparency/pop errors. `renderView` re-sorts
590
+ * for `camera` into the shared buffer, sets the view-dependent uniforms
591
+ * (viewport from `target`), draws, and marks the primary order stale so the
592
+ * next `update()` re-sorts for the main camera. GPU submissions execute in
593
+ * order, so each view's draw reads the order and uniforms it just wrote.
594
+ *
595
+ * Call once per extra view per frame, before or after the primary
596
+ * `update()` + render - both are correct. On the WebGL2 fallback the sorter
597
+ * is an asynchronous worker owned by the primary view, so `renderView` does
598
+ * not sort at all there: the secondary view draws with the primary view's
599
+ * order (projection is still per-camera correct) - a documented single-view
600
+ * ordering limitation; WebGPU gets exact per-view order.
601
+ *
602
+ * @param target - Destination render target, or `null`/omitted for the canvas.
603
+ */
604
+ renderView(camera: THREE.PerspectiveCamera, renderer: THREE.WebGPURenderer, target?: THREE.RenderTarget | null): void;
605
+ /**
606
+ * Sorts the shared order buffer for a secondary view's camera, bypassing the
607
+ * primary view's sort scheduler and its `lastSortedModelView` record (that
608
+ * state belongs to `update()`'s camera). WebGPU dispatches synchronously into
609
+ * the render queue, so the following draw reads this order.
610
+ */
611
+ private sortForView;
612
+ /** Rebuilds the query grid if the resident set changed since it was built. */
613
+ private ensureQueryGrid;
614
+ /** World-space position of pool splat `poolIndex`, written into `out`. */
615
+ private splatWorldPosition;
616
+ /**
617
+ * The smallest world axis scale, for conservatively mapping a world radius
618
+ * into local space: a world sphere of radius `r` fits inside a local sphere
619
+ * of `r / minAxisScale`, for any per-axis (non-uniform) scale.
620
+ */
621
+ private queryWorldMinScale;
622
+ /**
623
+ * Releases every GPU resource and worker this mesh owns. Idempotent: a
624
+ * second call is a no-op, and a render loop that outlives the mesh by a
625
+ * frame is safe - {@link update}, {@link renderView} and {@link pick} all
626
+ * become no-ops after dispose.
627
+ */
628
+ dispose(): void;
629
+ /** Matches the viewport / focal / local-camera uniforms written by {@link update}. */
630
+ private refreshProjectionUniforms;
631
+ /**
632
+ * Writes the three view-dependent uniforms - viewport, focal (splat screen
633
+ * size), and the camera position in mesh-local space (SH view direction +
634
+ * screen-radius math) - for a camera drawing into a viewport of the given
635
+ * pixel size. {@link update} passes the canvas size (or, while an XR session
636
+ * presents, the per-eye viewport); {@link renderView} passes its render
637
+ * target's size.
638
+ *
639
+ * @param positionCamera - Camera whose world position seeds the SH view
640
+ * direction and screen-radius math. Defaults to `camera`; in XR it is the
641
+ * head, so both eyes shade from one cyclopean point while each still
642
+ * projects through its own eye.
643
+ */
644
+ private writeViewUniforms;
645
+ /**
646
+ * Self-adjusts {@link foveationLimitPx} so the frontier cut's drawn-splat count
647
+ * tracks {@link foveationDrawBudget} (Spark's `maxSplats` feedback). Estimates
648
+ * the drawn count by replaying the cut on a strided sample of the pool, then
649
+ * coarsens (grows the limit) when over budget and refines (shrinks toward the
650
+ * finest {@link foveationTargetPx}) when comfortably under. Throttled; a no-op
651
+ * outside `'frontier'` mode.
652
+ *
653
+ * `viewportY` must be the **same** height {@link writeViewUniforms} is given
654
+ * for this frame - the two share `foveationLimitPx` in pixels and each
655
+ * converts it with its own `focalY`, so a mismatch (the full drawing buffer
656
+ * here against a per-eye viewport there) makes the estimator judge a cut the
657
+ * shader is not applying, and the ratchet runs away toward the maximum.
658
+ */
659
+ private adaptFoveationLimit;
660
+ /** Records a written row span for GPU upload and the sort-worker mirror. */
661
+ private markRowsWritten;
662
+ /** Appends one newly active pool range to the packed source-index list. */
663
+ private activateRecord;
664
+ /**
665
+ * Removes one active range and backfills its holes from the packed tail.
666
+ * The common contiguous case uses one native block copy; the fallback
667
+ * handles a range split by earlier tail backfills in O(record.count).
668
+ */
669
+ private deactivateRecord;
670
+ /** Uploads only the changed packed slots and invalidates the current depth order. */
671
+ private commitActiveListMutation;
672
+ /** Rewrites the active-splat list (pool indices, range by range). */
673
+ private rebuildActiveList;
674
+ /** The pool's reusable index ramp; shared by every mesh drawing from it. */
675
+ private getPoolIndexTemplate;
676
+ /**
677
+ * Uploads rows written since the last flush by copying only those rows
678
+ * through a staging texture. (Constructor-time writes never appear here;
679
+ * they ride the textures' initial full upload instead.)
680
+ */
681
+ private flushPendingUploads;
682
+ /**
683
+ * Packs float32 CPU backing into the half GPU images for centers/covA.
684
+ * Used once after a static constructor write so the initial `needsUpdate`
685
+ * upload carries half bits (staging is skipped for that path).
686
+ */
687
+ private syncHalfFloatPoolImages;
688
+ /**
689
+ * Uploads the given row spans of one or more same-layout pool textures via
690
+ * per-region staging copies. Spans are merged first so consecutive appends
691
+ * upload as one rectangle each - the copyTextureToTexture call count, not
692
+ * the pixel volume, dominates.
693
+ */
694
+ private uploadRows;
695
+ /** Reuses a same-length half encode buffer for one staging key. */
696
+ private acquireHalfEncodeBuffer;
697
+ /**
698
+ * Reuses one of the recent same-sized staging textures for this upload. WebGPU
699
+ * texture dimensions are immutable, so each cached entry remains exact-sized. Keeping
700
+ * a small LRU per channel avoids recreating all core/SH staging resources when
701
+ * a stream alternates among a few chunk heights, while bounding GPU memory.
702
+ */
703
+ private acquireUploadStaging;
704
+ /** Gathers everything the material graph reads. See `splat-mesh-material.ts`. */
705
+ private graphInputs;
706
+ private buildMaterial;
707
+ private requestSortIfNeeded;
708
+ /**
709
+ * Refreshes {@link boundingSphereLocal} - the sphere the sorter quantizes
710
+ * depth over - when {@link boundsDirty}. The base mesh uses its local splat
711
+ * bounds; a unified {@link SplatScene} overrides this to supply a world-space
712
+ * bound spanning all its sources (whose transforms live in the shader).
713
+ */
714
+ protected refreshSortBounds(): void;
715
+ /** The data texture backing a channel defined with {@link defineChannel}. */
716
+ protected channelTexture(name: string): THREE.DataTexture | undefined;
717
+ /** CPU backing storage of a float channel, for internal CPU-side consumers. */
718
+ protected channelBacking(name: string): Float32Array;
719
+ /**
720
+ * Forces the next {@link update} to re-sort even if the camera has not moved
721
+ * - used by a unified pool when a source's transform changes, since the depth
722
+ * order then changes without any camera motion.
723
+ */
724
+ protected invalidateSort(): void;
725
+ private createSorter;
726
+ /** Prefetches the experimental radix sorter; safe to call repeatedly. */
727
+ private ensureRadixSorter;
728
+ }
729
+ /** Default frontier-cut target size (px). Matches Spark's `lodRenderScale`
730
+ * default of 1 - nodes refine until ~1 px on screen, so the draw budget (not a
731
+ * coarser fixed cut) is what bounds detail. At the old value of 4 the cut
732
+ * stopped ~2 LOD levels early everywhere the tree doesn't bottom out at
733
+ * leaves, which made large-scale scenes visibly coarser than Spark. */
734
+ export declare const DEFAULT_FOVEATION_TARGET_PX = 1;
735
+ /** Default target for the frontier cut's drawn-splat count (Spark's `maxSplats`).
736
+ * Coarsens the cut once the estimate exceeds it, keeping frame cost bounded. */
737
+ export declare const DEFAULT_FOVEATION_DRAW_BUDGET = 900000;