@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,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
|
+
}
|