@voluma/vlam 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/THIRD_PARTY_NOTICES.md +86 -0
  4. package/dist/budget-governor.d.ts +154 -0
  5. package/dist/camera-budget-governor.d.ts +234 -0
  6. package/dist/chunk-cache-budget.d.ts +156 -0
  7. package/dist/chunk-fetch-scheduler.d.ts +168 -0
  8. package/dist/chunk-loader.d.ts +85 -0
  9. package/dist/compute-sorter.d.ts +131 -0
  10. package/dist/create-splat-renderer.d.ts +99 -0
  11. package/dist/dataset-source.d.ts +55 -0
  12. package/dist/depth-of-field.d.ts +73 -0
  13. package/dist/effects.d.ts +337 -0
  14. package/dist/effects.js +180 -0
  15. package/dist/effects.js.map +1 -0
  16. package/dist/formats/ksplat/index.d.ts +8 -0
  17. package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
  18. package/dist/formats/ksplat.js +167 -0
  19. package/dist/formats/ksplat.js.map +1 -0
  20. package/dist/formats/lcc/collision-mesh.d.ts +37 -0
  21. package/dist/formats/lcc/collision-partition.d.ts +35 -0
  22. package/dist/formats/lcc/index.d.ts +16 -0
  23. package/dist/formats/lcc/lcc.d.ts +28 -0
  24. package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
  25. package/dist/formats/lcc/lcc2.d.ts +4 -0
  26. package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
  27. package/dist/formats/lcc/parse-lcc.d.ts +177 -0
  28. package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
  29. package/dist/formats/lcc.js +969 -0
  30. package/dist/formats/lcc.js.map +1 -0
  31. package/dist/formats/ply/index.d.ts +12 -0
  32. package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
  33. package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
  34. package/dist/formats/ply.js +230 -0
  35. package/dist/formats/ply.js.map +1 -0
  36. package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
  37. package/dist/formats/rad/index.d.ts +10 -0
  38. package/dist/formats/rad/parse-rad.d.ts +88 -0
  39. package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
  40. package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
  41. package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
  42. package/dist/formats/rad/rad.d.ts +132 -0
  43. package/dist/formats/rad.js +1098 -0
  44. package/dist/formats/rad.js.map +1 -0
  45. package/dist/formats/sog/index.d.ts +9 -0
  46. package/dist/formats/sog/parse-sog.d.ts +45 -0
  47. package/dist/formats/sog.js +257 -0
  48. package/dist/formats/sog.js.map +1 -0
  49. package/dist/formats/splat/index.d.ts +8 -0
  50. package/dist/formats/splat/parse-splat.d.ts +10 -0
  51. package/dist/formats/splat.js +39 -0
  52. package/dist/formats/splat.js.map +1 -0
  53. package/dist/formats/spz/index.d.ts +8 -0
  54. package/dist/formats/spz/parse-spz.d.ts +10 -0
  55. package/dist/formats/spz.js +218 -0
  56. package/dist/formats/spz.js.map +1 -0
  57. package/dist/frontier-worker-CAnK_Cmu.js +566 -0
  58. package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
  59. package/dist/half-float-Cs4FDq8Q.js +30 -0
  60. package/dist/half-float-Cs4FDq8Q.js.map +1 -0
  61. package/dist/half-float.d.ts +15 -0
  62. package/dist/index.d.ts +54 -0
  63. package/dist/index.js +9054 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/load-scene.d.ts +50 -0
  66. package/dist/load-worker-protocol.d.ts +82 -0
  67. package/dist/loading-wLo8vRbA.js +102 -0
  68. package/dist/loading-wLo8vRbA.js.map +1 -0
  69. package/dist/loading.d.ts +150 -0
  70. package/dist/lod-manifest.d.ts +61 -0
  71. package/dist/lod-scheduler.d.ts +229 -0
  72. package/dist/lod-source.d.ts +207 -0
  73. package/dist/logging-BfPdd7NJ.js +19 -0
  74. package/dist/logging-BfPdd7NJ.js.map +1 -0
  75. package/dist/logging.d.ts +37 -0
  76. package/dist/one-shot-worker-G80btnth.js +664 -0
  77. package/dist/one-shot-worker-G80btnth.js.map +1 -0
  78. package/dist/orientation.d.ts +35 -0
  79. package/dist/ply-header-BwgExCn-.js +125 -0
  80. package/dist/ply-header-BwgExCn-.js.map +1 -0
  81. package/dist/ply-header.d.ts +87 -0
  82. package/dist/radix-sort.d.ts +28 -0
  83. package/dist/radix-sorter.d.ts +46 -0
  84. package/dist/relighting.d.ts +50 -0
  85. package/dist/selection-volume.d.ts +87 -0
  86. package/dist/sh-pack-D5wAe5gg.js +54 -0
  87. package/dist/sh-pack-D5wAe5gg.js.map +1 -0
  88. package/dist/sh-pack.d.ts +49 -0
  89. package/dist/sort-scheduler.d.ts +62 -0
  90. package/dist/sort-worker-protocol.d.ts +37 -0
  91. package/dist/sorter.d.ts +31 -0
  92. package/dist/source-transform.d.ts +57 -0
  93. package/dist/splat-budget-PSojLJPO.js +157 -0
  94. package/dist/splat-budget-PSojLJPO.js.map +1 -0
  95. package/dist/splat-budget.d.ts +404 -0
  96. package/dist/splat-data-BZM5dhhx.js +10 -0
  97. package/dist/splat-data-BZM5dhhx.js.map +1 -0
  98. package/dist/splat-data.d.ts +150 -0
  99. package/dist/splat-depth-pack.d.ts +39 -0
  100. package/dist/splat-mesh-material.d.ts +255 -0
  101. package/dist/splat-mesh-picking.d.ts +87 -0
  102. package/dist/splat-mesh-pool.d.ts +241 -0
  103. package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
  104. package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
  105. package/dist/splat-mesh-types.d.ts +390 -0
  106. package/dist/splat-mesh.d.ts +737 -0
  107. package/dist/splat-modifier-stack.d.ts +38 -0
  108. package/dist/splat-modifier.d.ts +156 -0
  109. package/dist/splat-partition.d.ts +37 -0
  110. package/dist/splat-query.d.ts +51 -0
  111. package/dist/splat-scene.d.ts +127 -0
  112. package/dist/splat-sort-bounds.d.ts +11 -0
  113. package/dist/static-lod-splat-mesh.d.ts +53 -0
  114. package/dist/static-lod-worker-D2fyqQZO.js +474 -0
  115. package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
  116. package/dist/static-lod-worker-protocol.d.ts +37 -0
  117. package/dist/static-lod.d.ts +24 -0
  118. package/dist/storage-attribute-mirror.d.ts +103 -0
  119. package/dist/streamed-splat-mesh-utils.d.ts +146 -0
  120. package/dist/streamed-splat-mesh.d.ts +985 -0
  121. package/dist/unified-splat-renderer.d.ts +205 -0
  122. package/dist/webgpu-limits.d.ts +133 -0
  123. package/dist/work-buffer-gather.d.ts +119 -0
  124. package/dist/work-buffer-material.d.ts +42 -0
  125. package/dist/worker-sorter.d.ts +57 -0
  126. package/dist/xr-view.d.ts +92 -0
  127. package/package.json +135 -21
@@ -0,0 +1,38 @@
1
+ /** Modifier-stack folding for the splat material graph. */
2
+ import * as THREE from 'three/webgpu';
3
+ import type { SplatModifier } from './splat-modifier';
4
+ import type { Vec3Uniform } from './splat-mesh-material';
5
+ /**
6
+ * Builds the per-splat {@link SplatContext}, folds the modifier stack
7
+ * over it in order, and returns the graph fragments the renderer applies.
8
+ * `null` fragments mean "untouched": with an empty stack every fragment
9
+ * is null and the caller emits exactly the unhooked graph.
10
+ *
11
+ * Modifiers are called once, here, at material build time - they are
12
+ * graph builders, not per-frame callbacks. Derived context fields
13
+ * (worldCenter, viewCenter, normal) are memoized lazily so only the
14
+ * modifiers that read them add their nodes to the graph.
15
+ */
16
+ export declare function foldSplatModifierStack(modifierList: readonly SplatModifier[], localCameraPosition: Vec3Uniform, inputs: {
17
+ index: THREE.Node<'int'>;
18
+ localCenter: THREE.Node<'vec3'>;
19
+ /** Pre-placement pool position; defaults to {@link localCenter}. */
20
+ sourceCenter?: THREE.Node<'vec3'>;
21
+ /** Source-data-frame → mesh-local linear transform; defaults to identity. */
22
+ sourceToLocal?: THREE.Node<'mat3'>;
23
+ color: THREE.Node<'vec4'>;
24
+ makeNormal: () => THREE.Node<'vec3'>;
25
+ makeChannel: (name: string) => THREE.Node<'float'>;
26
+ /** Optional coordinate-space adapters for compute-time modifier folding. */
27
+ makeWorldCenter?: () => THREE.Node<'vec3'>;
28
+ makeViewCenter?: () => THREE.Node<'vec3'>;
29
+ }): {
30
+ color: THREE.Node<'vec4'>;
31
+ offset: THREE.Node<'vec3'> | null;
32
+ scaleSquared: THREE.Node<'float'> | null;
33
+ rotation: THREE.Node<'mat3'> | null;
34
+ visible: THREE.Node<'bool'> | null;
35
+ isotropicCovarianceMix: THREE.Node<'float'> | null;
36
+ isotropicVarianceScale: THREE.Node<'float'> | null;
37
+ isotropicScreenRadiusPx: THREE.Node<'float'> | null;
38
+ };
@@ -0,0 +1,156 @@
1
+ import type { Node } from 'three/webgpu';
2
+ /**
3
+ * Per-splat context a {@link SplatModifier} reads, evaluated in the vertex
4
+ * stage. All fields are TSL nodes; see `docs/guide/effects-and-modifiers.md`.
5
+ */
6
+ export interface SplatContext {
7
+ /** Pool splat index (stable identity for per-splat channels). */
8
+ readonly index: Node<'int'>;
9
+ /**
10
+ * Splat center in mesh-local space (pre-displacement).
11
+ *
12
+ * Inside a `SplatScene` this is the splat's **placed** position
13
+ * (`sourceMatrix · poolCenter`) - the placement is applied before the stack
14
+ * runs, so effects stay anchored to the scene rather than travelling with a
15
+ * moved source. Use {@link sourceCenter} for the pre-placement position.
16
+ */
17
+ readonly localCenter: Node<'vec3'>;
18
+ /**
19
+ * The splat's position in its own source's data frame. Identical to
20
+ * {@link localCenter} on a plain mesh; inside a `SplatScene` it is the
21
+ * **pre-placement** position, for effects that should travel *with* a moved
22
+ * source instead of staying put in the scene.
23
+ */
24
+ readonly sourceCenter: Node<'vec3'>;
25
+ /**
26
+ * Linear transform from the source data frame to mesh-local space. This is
27
+ * identity on a plain mesh; inside a {@link SplatScene} it is the linear part
28
+ * of the source placement. Apply it to source-frame displacement vectors.
29
+ * To express a source-frame rotation `R` as a mesh-local `rotation` output,
30
+ * conjugate it: `sourceToLocal · R · sourceToLocal.inverse()`.
31
+ */
32
+ readonly sourceToLocal: Node<'mat3'>;
33
+ /** `modelMatrix · localCenter` (world space). */
34
+ readonly worldCenter: Node<'vec3'>;
35
+ /** `modelViewMatrix · localCenter` (view space). */
36
+ readonly viewCenter: Node<'vec3'>;
37
+ /** Camera position in mesh-local space. */
38
+ readonly cameraLocal: Node<'vec3'>;
39
+ /** Color + opacity after SH - the stack's input color. */
40
+ readonly baseColor: Node<'vec4'>;
41
+ /** Approximate surface normal (least-variance axis of the splat's
42
+ * covariance), mesh-local, oriented toward the camera. Built lazily -
43
+ * only modifiers that read it pay for the eigen approximation. */
44
+ readonly normal: Node<'vec3'>;
45
+ /**
46
+ * Reads a per-splat channel by name (see `SplatMesh.defineChannel` /
47
+ * `writeChannel`). Float channels return their stored value; byte channels
48
+ * return the value normalized to `[0, 1]`. Reading an undefined channel is
49
+ * a build-time error (a missing mask is a bug, not a silent zero). The read
50
+ * is memoized per name, so referencing the same channel twice costs one
51
+ * texture fetch.
52
+ */
53
+ channel(name: string): Node<'float'>;
54
+ /** rgb + opacity. */
55
+ readonly color: Node<'vec4'>;
56
+ /** Local-space center displacement. */
57
+ readonly offset: Node<'vec3'>;
58
+ /** Uniform scale multiplier. */
59
+ readonly scale: Node<'float'>;
60
+ /** Rigid rotation. */
61
+ readonly rotation: Node<'mat3'>;
62
+ /** Visibility. */
63
+ readonly visible: Node<'bool'>;
64
+ }
65
+ /** The transform fields a modifier changes; omitted fields pass through. */
66
+ export interface SplatOutputs {
67
+ color?: Node<'vec4'>;
68
+ offset?: Node<'vec3'>;
69
+ scale?: Node<'float'>;
70
+ rotation?: Node<'mat3'>;
71
+ visible?: Node<'bool'>;
72
+ /** Blend `Σ` toward σ²·I (0 = keep, 1 = full collapse). Screen λ equalized when > 0. */
73
+ isotropicCovarianceMix?: Node<'float'>;
74
+ /** Multiplier on max(n·Σ·n, ε); defaults to 0.35² when omitted. */
75
+ isotropicVarianceScale?: Node<'float'>;
76
+ /**
77
+ * Optional isotropic screen-space σ radius in pixels. When set (> 0), caps
78
+ * projected λ for zoom-stable dots. Omit for Spark-style world-space points
79
+ * (size grows mildly on zoom-in).
80
+ */
81
+ isotropicScreenRadiusPx?: Node<'float'>;
82
+ }
83
+ /**
84
+ * A splat effect hook. Reads the per-splat {@link SplatContext} and returns
85
+ * the transform fields it wants to change. Pure TSL, so it compiles to WGSL
86
+ * and GLSL and runs on both backends. Modifiers compose by folding over
87
+ * `SplatMesh.modifiers` in order - each sees the running result of the ones
88
+ * before it. Adding/removing/reordering modifiers recompiles the material;
89
+ * changing a modifier's own uniforms or storage buffers never does.
90
+ *
91
+ * Constraints (see `docs/guide/effects-and-modifiers.md`): covariance is pre-baked,
92
+ * so `scale` is uniform-only and `rotation` rigid-only; displaced splats
93
+ * keep their pre-displacement depth-sort order.
94
+ */
95
+ export type SplatModifier = (context: SplatContext) => SplatOutputs;
96
+ /** Anything with a `modifiers` list - {@link ModifierSlots.apply} target. */
97
+ export interface ModifierStackTarget {
98
+ modifiers: readonly SplatModifier[];
99
+ }
100
+ /**
101
+ * Named, ordered modifier slots for hosts that stack several effects (reveal,
102
+ * SDF, lighting, fog, opacity, …) on one mesh. The slot **order is fixed at
103
+ * construction** - it defines the fold order of the compiled stack - while
104
+ * each slot's occupant can change at runtime.
105
+ *
106
+ * The rebuild vs uniform-update contract (see
107
+ * `docs/guide/effects-and-modifiers.md`, "Multi-slot stacks"):
108
+ *
109
+ * - An **empty slot costs nothing**: it is omitted from the compacted list
110
+ * entirely, never compiled as a passthrough. All slots empty ⇒ the mesh
111
+ * gets an empty list ⇒ the unhooked graph (and the unified renderer's
112
+ * zero-modifier gather fast path stays eligible - except for a `SplatScene`,
113
+ * which `UnifiedSplatRenderer` does not accept as a source at all).
114
+ * - {@link apply} hands the mesh a **compacted array whose reference is
115
+ * stable** while the occupancy is unchanged, so re-applying after
116
+ * uniform-only changes is a guaranteed no-op on the mesh's identity diff -
117
+ * no material rebuild, no `graphRevision` bump.
118
+ * - Filling, clearing, or replacing a slot with a *different* function is a
119
+ * structural change: the next {@link apply} rebuilds the material once.
120
+ * Setting a slot to the function it already holds is a no-op.
121
+ */
122
+ export declare class ModifierSlots {
123
+ private readonly order;
124
+ private readonly occupants;
125
+ /** Compacted stack in slot order; rebuilt only on structural change so the
126
+ * array reference itself is stable across uniform-only frames. */
127
+ private compacted;
128
+ /** @param order Slot names, in fold order. Must be non-empty and unique. */
129
+ constructor(order: readonly string[]);
130
+ /** The declared slot names, in fold order. */
131
+ get slotNames(): readonly string[];
132
+ /**
133
+ * Fills or clears a slot (`null` clears). Returns `true` when the change is
134
+ * structural - i.e. the next {@link apply} will trigger a material rebuild -
135
+ * and `false` when it was a no-op (same function, or clearing an already
136
+ * empty slot). Unknown slot names throw: a typo must not silently drop an
137
+ * effect.
138
+ */
139
+ set(name: string, modifier: SplatModifier | null): boolean;
140
+ /** The slot's current occupant, or `null` when empty. */
141
+ get(name: string): SplatModifier | null;
142
+ /** Sugar for `set(name, null)`; returns whether the slot was occupied. */
143
+ clear(name: string): boolean;
144
+ /**
145
+ * The compacted stack: occupied slots only, in slot order. The reference is
146
+ * stable while occupancy is unchanged, so it is safe to assign every frame.
147
+ */
148
+ get modifiers(): readonly SplatModifier[];
149
+ /**
150
+ * Assigns the compacted stack to `target.modifiers` (a `SplatMesh` or
151
+ * anything with the same setter). The mesh's identity diff makes this free
152
+ * when nothing structural changed, so calling it after every `set` - or
153
+ * even every frame - is fine.
154
+ */
155
+ apply(target: ModifierStackTarget): void;
156
+ }
@@ -0,0 +1,37 @@
1
+ import type { SplatData } from './splat-data';
2
+ import { type SelectionVolume } from './selection-volume';
3
+ /**
4
+ * Splits a {@link SplatData} into the part inside a {@link SelectionVolume}
5
+ * and the rest, so the selected region can live as its own independently
6
+ * transformed (and therefore independently animated) object.
7
+ *
8
+ * Both halves keep the source's local frame - separation does not bake any
9
+ * transform. Register them as `SplatScene` or `UnifiedSplatRenderer` sources
10
+ * for one global sort while they are posed independently. Separate
11
+ * `SplatMesh` draw calls sort only within each mesh and can misblend where
12
+ * their Gaussian footprints overlap.
13
+ */
14
+ export interface SplatPartition {
15
+ /** The splats whose centers passed the volume test. */
16
+ readonly inside: SplatData;
17
+ /** Everything else. */
18
+ readonly outside: SplatData;
19
+ /** Source indices of {@link inside}'s splats, ascending. */
20
+ readonly insideIndices: Uint32Array;
21
+ }
22
+ /**
23
+ * Partitions `data` by a selection.
24
+ *
25
+ * `selection` is either a volume (each splat center is tested) or an already
26
+ * computed ascending index list, e.g. from {@link selectInData} - passing the
27
+ * list avoids re-testing when a host previews a selection before separating.
28
+ *
29
+ * Per-splat arrays (positions, colors, covariances, SH) are gathered by index;
30
+ * a SOG SH palette is shared by reference between the halves (it is immutable
31
+ * and per-splat `labels` keep it valid for any subset). Streamed-only fields
32
+ * (`radTree`, `frontierParent`, `radShCodebook`) do not survive a partition -
33
+ * their index topology is meaningless on a subset - so they are dropped;
34
+ * partitioning is for fully resident data. An empty or all-inclusive selection
35
+ * is valid and yields a zero-count half.
36
+ */
37
+ export declare function partitionSplatData(data: SplatData, selection: SelectionVolume | Uint32Array): SplatPartition;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * A lazily built uniform spatial grid over splat centers, for the CPU spatial
3
+ * queries `SplatMesh` exposes (M9). Pure array math - no THREE, no GPU - over
4
+ * the pool's CPU-side centers, so it is cheap to build and to test.
5
+ *
6
+ * The grid indexes a set of *pool indices* into a shared centers array (the
7
+ * pool's `backing.centers`, RGBA-strided: splat `p`'s center is
8
+ * `centers[p*4 + 0..2]`). Only the indices handed in are indexed, so a static
9
+ * mesh grids its whole scene and a streamed mesh grids exactly its resident
10
+ * splats. Everything is in the mesh's **local** space; the caller converts
11
+ * world queries in and results out.
12
+ */
13
+ export declare class UniformGrid {
14
+ /** Populated-region minimum corner (local space). */
15
+ private readonly min;
16
+ /** Per-axis cell size (local units). */
17
+ private readonly cellSize;
18
+ /** Per-axis cell counts. */
19
+ private readonly dims;
20
+ /** CSR cell offsets: cell c owns `items[start[c] .. start[c+1])`. */
21
+ private readonly cellStart;
22
+ /** Pool indices, bucketed by cell. */
23
+ private readonly items;
24
+ /** Pool centers array this grid points into (RGBA stride 4). */
25
+ private readonly centers;
26
+ readonly count: number;
27
+ /**
28
+ * @param centers - The pool's CPU centers (stride 4: x,y,z,_ per splat).
29
+ * @param poolIndices - The pool indices to index (e.g. the active list).
30
+ * @param count - How many entries of `poolIndices` are valid.
31
+ */
32
+ constructor(centers: Float32Array, poolIndices: Uint32Array, count: number);
33
+ /** The cell index of pool splat `p`, clamped into range. */
34
+ private cellOf;
35
+ private axisCell;
36
+ /**
37
+ * Visits every indexed splat within `radius` (local units) of the local
38
+ * point, calling `visit(poolIndex, distanceSq)`. Cells are pruned by their
39
+ * axis span and each candidate is distance-filtered here, so the callback
40
+ * only ever sees splats truly within the radius.
41
+ */
42
+ forEachWithin(x: number, y: number, z: number, radius: number, visit: (poolIndex: number, distanceSq: number) => void): void;
43
+ /**
44
+ * The nearest indexed splat to the local point within `radius`, or null.
45
+ * Returns its pool index and squared local distance.
46
+ */
47
+ nearest(x: number, y: number, z: number, radius: number): {
48
+ poolIndex: number;
49
+ distSq: number;
50
+ } | null;
51
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * A scene of several independently-posed Gaussian splat clouds that all sort
3
+ * and blend as one.
4
+ *
5
+ * The problem it solves: two separate {@link SplatMesh} instances are two
6
+ * transparent draw calls, each depth-sorted only within itself, so where the
7
+ * clouds overlap one is painted wholesale in front of the other ("pasted over")
8
+ * - the same artifact PlayCanvas's unified rendering and Spark's accumulator
9
+ * exist to fix. `SplatScene` concatenates every cloud into **one pool** and
10
+ * runs **one global depth sort**, so splats from different clouds interleave
11
+ * correctly, from any angle.
12
+ *
13
+ * Each cloud ("source") keeps its own world transform, applied in the shader
14
+ * (never baked into the pool): the sorter transforms each splat's center to
15
+ * world space before measuring depth, and the material places each splat's
16
+ * center and covariance by its source's matrix. So {@link setSourceTransform}
17
+ * is a handful of uniform writes - a source can be dragged or animated every
18
+ * frame at full rate, at any splat count, with no data re-upload.
19
+ *
20
+ * The placement runs **before** the modifier stack, so a host effect sees each
21
+ * splat where it visually is: an SDF light shape spanning two sources paints
22
+ * one continuous shape, and a reveal sweeps them together. A modifier that
23
+ * wants to travel *with* a moved source reads `ctx.sourceCenter` instead of
24
+ * `ctx.localCenter`. See `docs/guide/effects-and-modifiers.md`.
25
+ *
26
+ * On WebGPU the world-depth sort runs in a compute pass; on the WebGL2
27
+ * fallback the worker applies the same source matrices on the CPU before
28
+ * sorting. Both backends therefore inter-sort sources correctly.
29
+ *
30
+ * Limitations, all inherited from the dynamic pool: sources may not carry
31
+ * palette (SOG `shN`) view-dependent color - per-file palettes cannot be merged
32
+ * - but DC color and per-splat (LCC) SH are fine; all sources share one
33
+ * antialias setting (from the scene options).
34
+ *
35
+ * Sources are **static**: {@link addSource} takes a fully-resident
36
+ * {@link SplatData} and copies it once into the pool, so a streaming
37
+ * {@link StreamedSplatMesh} (which swaps its resident LOD cut every frame)
38
+ * cannot be a source yet - keep a streamed main splat separate and unify only
39
+ * the static sources. Streamed sources are ROADMAP M15.4.
40
+ */
41
+ import * as THREE from 'three/webgpu';
42
+ import { SplatMesh, type SplatMeshOptions } from './splat-mesh';
43
+ import type { SplatData } from './splat-data';
44
+ import { type SplatOrientation } from './orientation';
45
+ /** Construction options for a {@link SplatScene}. */
46
+ export interface SplatSceneOptions extends SplatMeshOptions {
47
+ /** Total pool size in splats - must cover the sum of every source added. */
48
+ capacity: number;
49
+ /** Maximum number of sources (default {@link MAX_SOURCES}). */
50
+ maxSources?: number;
51
+ }
52
+ /** Per-source placement options. */
53
+ export interface AddSourceOptions {
54
+ /**
55
+ * Y-up normalization for this source's data frame, like {@link SplatMesh}'s
56
+ * `orientation`. `'y-up'` (default) applies the format's stand-up correction
57
+ * before the placement matrix; `'source'` places the raw data frame.
58
+ */
59
+ orientation?: SplatOrientation;
60
+ }
61
+ /**
62
+ * One pool, one global sort, many independently-posed splat sources - see the
63
+ * module overview above for the artifact this exists to remove.
64
+ *
65
+ * @experimental May change in a minor release.
66
+ */
67
+ export declare class SplatScene extends SplatMesh {
68
+ private readonly matrices;
69
+ private readonly maxSources;
70
+ /** Indexed by source id; holes are left by {@link removeSource}. */
71
+ private readonly sources;
72
+ private nextId;
73
+ constructor(options: SplatSceneOptions);
74
+ /** Number of live sources. */
75
+ get sourceCount(): number;
76
+ /**
77
+ * Adds a cloud at a world placement and returns its id (for
78
+ * {@link setSourceTransform} / {@link removeSource}). The data is copied into
79
+ * the shared pool in its own local frame; the placement is applied live in
80
+ * the shader.
81
+ *
82
+ * @throws if the pool cannot fit the cloud, or the source limit is reached.
83
+ */
84
+ addSource(data: SplatData, placement?: THREE.Matrix4, options?: AddSourceOptions): number;
85
+ /**
86
+ * Re-places a source. Cheap by design: only the shared matrix uniform and the
87
+ * sort bound change, so it is safe to call every frame while dragging.
88
+ *
89
+ * Bad-id semantics across the triad: this method **throws** (a write to a
90
+ * dead source is a caller bug that would otherwise vanish silently),
91
+ * {@link getSourceTransform} returns `undefined` (querying is how you ask
92
+ * whether a source is live), and {@link removeSource} returns `false`
93
+ * (removal is idempotent).
94
+ *
95
+ * @throws if `id` is not a live source.
96
+ */
97
+ setSourceTransform(id: number, placement: THREE.Matrix4): void;
98
+ /**
99
+ * The current world matrix of a source (a copy), or `undefined` if `id` is
100
+ * not a live source - see {@link setSourceTransform} for the bad-id triad.
101
+ */
102
+ getSourceTransform(id: number): THREE.Matrix4 | undefined;
103
+ /**
104
+ * Removes a source and frees its pool range. The id is not reused (later ids
105
+ * keep their matrix slots), so long-lived churn should prefer reusing a scene
106
+ * over unbounded add/remove.
107
+ *
108
+ * @returns `true` when a live source was removed, `false` when `id` was not
109
+ * live (idempotent - see {@link setSourceTransform} for the bad-id triad).
110
+ */
111
+ removeSource(id: number): boolean;
112
+ /** Recomputes a source's world matrix (placement · correction) and republishes it. */
113
+ private applyPlacement;
114
+ /** Sort depth is quantized over the world span of every source, not the pool's local box. */
115
+ protected refreshSortBounds(): void;
116
+ /**
117
+ * Mesh-local bounds spanning every source at its **current placement** -
118
+ * the pool's own box would be the union of the sources' data frames, which
119
+ * is not where any of them is drawn. Each source's data-frame box is
120
+ * transformed by its matrix, so a rotated source contributes the (standard,
121
+ * conservative) axis-aligned box of its rotated bounds.
122
+ *
123
+ * Hosts use this to place effects, so it has to agree with what
124
+ * `ctx.localCenter` reports; it tracks {@link setSourceTransform}.
125
+ */
126
+ computeSplatBounds(): THREE.Box3;
127
+ }
@@ -0,0 +1,11 @@
1
+ import type * as THREE from 'three/webgpu';
2
+ /**
3
+ * The greatest view-space depth displacement of a local bounding sphere.
4
+ *
5
+ * View depth is the dot product of the local point and row 2 of `modelView`.
6
+ * A sphere with radius `r` therefore spans exactly `r · ||row2.xyz||` either
7
+ * side of its transformed center. Unlike a maximum-axis-scale estimate, this
8
+ * remains correct when a non-uniformly scaled ancestor and a rotated child
9
+ * compose into a shear.
10
+ */
11
+ export declare function viewDepthRadius(modelView: THREE.Matrix4, radius: number): number;
@@ -0,0 +1,53 @@
1
+ /** @role Bridge - Camera-aware rendering for worker-built static splat hierarchies. */
2
+ import * as THREE from 'three/webgpu';
3
+ import { type SplatLoadOptions } from './load-scene';
4
+ import { SplatMesh, type SplatMeshOptions, type SplatUpdateOptions } from './splat-mesh';
5
+ import type { StaticLodBuildProgress } from './static-lod';
6
+ export interface StaticLodSplatMeshOptions extends SplatMeshOptions {
7
+ /** Initial selected-frontier budget. Defaults to the build ceiling. */
8
+ budget?: number;
9
+ /** Finest frontier retained by the hierarchy and immutable pool ceiling. */
10
+ maxBudget: number;
11
+ /** Reports the spatial ordering and hierarchy-build phase. */
12
+ onLodProgress?: (progress: StaticLodBuildProgress) => void;
13
+ /** Phase-aware progress spanning download, decode completion and LOD build. */
14
+ onAutoLodProgress?: (progress: StaticLodLoadProgress) => void;
15
+ }
16
+ export interface StaticLodLoadProgress {
17
+ readonly phase: 'download' | 'decode' | 'lod-build';
18
+ readonly completed: number;
19
+ readonly total: number;
20
+ }
21
+ export interface StaticLodSplatMeshLoadOptions extends StaticLodSplatMeshOptions, SplatLoadOptions {
22
+ }
23
+ /**
24
+ * Static splat mesh backed by a merged hierarchy and camera-aware frontier.
25
+ * The full hierarchy is uploaded once; camera/budget cuts only remap the
26
+ * active pool indices and never rewrite resident splat attributes.
27
+ */
28
+ export declare class StaticLodSplatMesh extends SplatMesh {
29
+ private readonly worker;
30
+ private readonly ceilingValue;
31
+ private readonly contentCountValue;
32
+ private budgetValue;
33
+ private sequence;
34
+ private lastScheduleAt;
35
+ private selectionPending;
36
+ private readonly cameraLocal;
37
+ private readonly cameraForward;
38
+ private readonly inverseWorld;
39
+ private constructor();
40
+ /** Loads, decodes and builds a merged static hierarchy without blocking UI. */
41
+ static load(input: string | URL, options: StaticLodSplatMeshLoadOptions): Promise<StaticLodSplatMesh>;
42
+ get budget(): number;
43
+ get budgetCeiling(): number;
44
+ get maxBudget(): number;
45
+ get contentSplatCount(): number;
46
+ /** Changes the selected frontier budget within the immutable build ceiling. */
47
+ setBudget(value: number): number;
48
+ update(camera: THREE.PerspectiveCamera, renderer: THREE.WebGPURenderer, options?: SplatUpdateOptions): void;
49
+ /** Schedules a camera-aware cut when a unified renderer owns drawing/sorting. */
50
+ updateLod(camera: THREE.PerspectiveCamera): void;
51
+ dispose(): void;
52
+ private scheduleSelection;
53
+ }