@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,229 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import type { LodManifest } from './lod-manifest';
3
+ import type { LodSource } from './lod-source';
4
+ /**
5
+ * A contiguous splat range within one chunk file, at one LOD level - the
6
+ * unit the pool activates. Adjacent manifest leaves that resolve to the same
7
+ * `(file, level)` and whose ranges abut are coalesced into one run, so the
8
+ * pool sees a handful of large ranges instead of thousands of tiny leaves.
9
+ */
10
+ export interface LodRun {
11
+ readonly file: number;
12
+ readonly level: number;
13
+ readonly offset: number;
14
+ readonly count: number;
15
+ /** First manifest-leaf index this run covers. */
16
+ readonly leafStart: number;
17
+ /** One past the last covered leaf index. */
18
+ readonly leafEnd: number;
19
+ /**
20
+ * Physical-coverage identity when several manifest leaves form one region.
21
+ * A classic LCC cell may be split into fetch-sized leaves, but its slices
22
+ * must still swap as one visible unit. Undefined retains interval-based
23
+ * grouping for hierarchical sources.
24
+ */
25
+ readonly coverageGroup?: number;
26
+ /**
27
+ * Camera distance (mesh-local) of the nearest leaf in this run, from the
28
+ * last {@link LodScheduler.computeDesiredRuns} pass. Streamed meshes use it
29
+ * to fetch near detail before far coarse coverage. Absent on sources that
30
+ * do not track per-leaf distance.
31
+ */
32
+ readonly distance?: number;
33
+ /**
34
+ * True when any leaf in this run intersects the view frustum (with the
35
+ * scheduler's edge margin). Used to rank classic-path fetches so in-view
36
+ * detail beats behind-camera work; absent when the source does not track it.
37
+ */
38
+ readonly inView?: boolean;
39
+ /**
40
+ * Fetch-only angular distance from the camera's forward axis. Smaller values
41
+ * are nearer the screen centre. It never affects LOD selection or budget.
42
+ */
43
+ readonly screenImportance?: number;
44
+ }
45
+ /** Stable identity of a run, for diffing desired vs. resident sets. */
46
+ export declare function runKey(run: LodRun): string;
47
+ /**
48
+ * Chooses a level of detail per spatial leaf from the camera, keeps the
49
+ * total within a splat budget, and coalesces the result into runs.
50
+ *
51
+ * The scheduler is pure with respect to rendering: it takes a camera
52
+ * position and frustum (both in the mesh's local space) and returns the set
53
+ * of runs that should be resident. It owns only the small amount of state
54
+ * needed for temporal stability (each leaf's current level and when it last
55
+ * changed), so it is straightforward to drive from a test harness.
56
+ *
57
+ * LOD distance model (PlayCanvas-compatible): level 0 is used within
58
+ * `lodBaseDistance`; each successive level covers a band `lodMultiplier`×
59
+ * farther out. By default out-of-frustum leaves act {@link FRUSTUM_PENALTY}×
60
+ * farther away. Formats with broad spatial cells can disable that bias so an
61
+ * orbit does not churn already-refined cells at the frustum edges.
62
+ *
63
+ * The distance model only sets the *floor* and the *priority*: when the
64
+ * distance-chosen set leaves budget unused, {@link fillBudget} promotes
65
+ * in-frustum leaves nearest-first until its configured headroom target is
66
+ * spent (90% by default). Formats can cap that optional refinement and avoid
67
+ * a full-finest cut even when an explicitly large budget could hold it. The
68
+ * budget
69
+ * therefore decides how far refinement reaches - a desktop budget yields
70
+ * full detail even with the whole scene in view, a mobile budget refines
71
+ * only near the camera.
72
+ */
73
+ export declare class LodScheduler implements LodSource {
74
+ /** World-unit distance inside which the finest level (0) is used. */
75
+ lodBaseDistance: number;
76
+ /** Distance ratio between successive LOD levels. */
77
+ lodMultiplier: number;
78
+ /** Maximum active splats; the desired set is demoted to fit. */
79
+ budget: number;
80
+ private readonly leaves;
81
+ private readonly coarsest;
82
+ /** Per leaf: lowest/highest LOD level that has data. */
83
+ private readonly minLevel;
84
+ private readonly maxLevel;
85
+ /**
86
+ * Per leaf: the distance *ambition* - the finest level the camera distance
87
+ * asks for, with hysteresis/dwell applied. This is the hysteresis *state*:
88
+ * only {@link selectLevels} writes it, so budget demotion can never feed
89
+ * back into the dead-band/dwell logic and oscillate. With `fillPastDistance`
90
+ * false, {@link fillBudget} restores {@link resolved} only up to here.
91
+ */
92
+ private readonly level;
93
+ /**
94
+ * Per leaf: the resolved target - the ambition capped to fit the budget by
95
+ * {@link enforceBudget}, then refilled from leftover headroom by
96
+ * {@link fillBudget} (or DROPPED). This is what {@link coalesceRuns} emits.
97
+ */
98
+ private readonly resolved;
99
+ /** Per leaf: timestamp of the last distance-level change, for dwell. */
100
+ private readonly changedAt;
101
+ private readonly distance;
102
+ private readonly inFrustum;
103
+ /** Leaves that must move through the budget cut together. */
104
+ private readonly budgetGroups;
105
+ /** Per-leaf physical coverage group, when the manifest defines one. */
106
+ private readonly coverageGroups;
107
+ /** Whether view-frustum membership affects distance and fill priority. */
108
+ private readonly frustumAware;
109
+ /** Fraction of the usable budget that optional refinement may consume. */
110
+ private readonly budgetFillFraction;
111
+ /** Absolute cap on optional refinement; distance-selected detail may exceed it. */
112
+ private readonly budgetFillCap;
113
+ /** Whether a budget that holds every finest leaf forces a full-finest cut. */
114
+ private readonly forceFinestWhenFits;
115
+ /**
116
+ * Leaves at or inside this camera distance always resolve to their finest
117
+ * available level (no hysteresis climb). Classic LCC uses a multi-band
118
+ * radius so adjacent broad cells do not paint coarse discs at startup.
119
+ */
120
+ private readonly forceFinestWithin;
121
+ /**
122
+ * When false, {@link fillBudget} only restores toward each leaf's
123
+ * distance-selected {@link level} - never finer. Classic LCC sets this so
124
+ * a mid-range cell does not fetch L0 only to demote to L1 once the cut
125
+ * settles.
126
+ */
127
+ private readonly fillPastDistance;
128
+ /**
129
+ * What the last cut actually decided, for the HUD.
130
+ *
131
+ * The resident splat count alone cannot distinguish "the scheduler asked for
132
+ * this" from "the scheduler asked for more and the mesh could not apply it",
133
+ * and those have opposite fixes. Measured rather than inferred after a
134
+ * zoomed-out `sandwijck` sat at exactly its coarsest total (190,730) with a
135
+ * 600k budget - 68% of the budget unspent, with no way to see which stage
136
+ * declined to spend it.
137
+ */
138
+ readonly stats: {
139
+ /** Leaves the frustum test accepted - `fillBudget`'s candidate set. */
140
+ inFrustum: number;
141
+ /** Total leaves in the tree. */
142
+ leaves: number;
143
+ /** Splats implied by the resolved cut, before the mesh applies anything. */
144
+ desired: number;
145
+ /** Splats `fillBudget` added on top of the distance-chosen levels. */
146
+ filled: number;
147
+ };
148
+ private readonly scratchBox;
149
+ private readonly scratchSize;
150
+ private readonly scratchCenter;
151
+ private readonly screenImportance;
152
+ /** Whether the last selection pass received a camera-forward vector. */
153
+ private hasScreenImportance;
154
+ /**
155
+ * Persistent leaf-index ordering scratch for {@link enforceBudget} and
156
+ * {@link fillBudget}, sorted in place per call instead of allocating a
157
+ * fresh `[...keys]` array. Comparators break ties on the index itself so
158
+ * the order is identical to the stable `Array#sort` this replaces
159
+ * (`TypedArray#sort` stability is not guaranteed).
160
+ */
161
+ private readonly orderScratch;
162
+ constructor(manifest: LodManifest, options: {
163
+ budget: number;
164
+ lodBaseDistance: number;
165
+ lodMultiplier: number;
166
+ frustumAware?: boolean;
167
+ budgetFillFraction?: number;
168
+ budgetFillCap?: number;
169
+ forceFinestWhenFits?: boolean;
170
+ forceFinestWithin?: number;
171
+ /**
172
+ * When false, leftover-budget fill never refines past the distance-
173
+ * selected level (see {@link fillPastDistance}). Default true.
174
+ */
175
+ fillPastDistance?: boolean;
176
+ });
177
+ /**
178
+ * Recomputes the desired resident run set for the current camera.
179
+ *
180
+ * @param cameraLocal - Camera position in the mesh's local space.
181
+ * @param frustum - View frustum in the mesh's local space.
182
+ * @param now - Monotonic timestamp (ms) for dwell hysteresis.
183
+ */
184
+ computeDesiredRuns(cameraLocal: THREE.Vector3, frustum: THREE.Frustum, now: number, cameraForward?: THREE.Vector3): LodRun[];
185
+ /** Total active splats implied by the current resolved levels. */
186
+ private activeTotal;
187
+ /** Splats if every leaf sat at its finest available level (none dropped). */
188
+ private finestTotal;
189
+ private selectLevels;
190
+ /**
191
+ * Pure distance→level band (independent of the leaf's current level), so a
192
+ * classic LCC cut never has to dwell through intermediate rungs.
193
+ */
194
+ private distanceBandLevel;
195
+ /**
196
+ * Stable level from distance: coarsen only past `threshold·(1 + margin)`,
197
+ * refine only within `threshold·(1 − margin)`. The available-level clamp
198
+ * keeps leaves that lack a level on their nearest coarser one.
199
+ */
200
+ private hysteresisLevel;
201
+ private resolveAvailable;
202
+ /** Demotes/drops lowest-priority leaves until the total fits the budget. */
203
+ private enforceBudget;
204
+ /**
205
+ * Uses leftover budget to restore detail after {@link enforceBudget}
206
+ * demoted leaves, nearest first, while the total stays under the fill
207
+ * target. By default this may also refine *past* the distance-chosen level
208
+ * when headroom remains (octree / pagetable streams). With
209
+ * {@link fillPastDistance} false (classic LCC), fill never goes finer than
210
+ * each leaf's distance-selected level - so a cell at the L1 band does not
211
+ * fetch L0 only to demote moments later with the camera still.
212
+ */
213
+ private fillBudget;
214
+ private coalesceRuns;
215
+ /**
216
+ * Runs covering [from, to) at each leaf's coarsest available level - used
217
+ * to substitute always-cached coverage while a finer level is fetching.
218
+ */
219
+ coarsestRunsFor(from: number, to: number): LodRun[];
220
+ /**
221
+ * Runs covering [from, to) at `level`, clamped per leaf to an available rung
222
+ * (prefer the requested level, else the next coarser, else the next finer).
223
+ */
224
+ runsAtLevelFor(from: number, to: number, level: number): LodRun[];
225
+ /** Prefer `wanted`, else next coarser, else next finer; {@link DROPPED} if none. */
226
+ private clampLeafLevel;
227
+ /** Coalesces leaves [from, to) at `levelOf(leaf)` into contiguous runs. */
228
+ private buildRuns;
229
+ }
@@ -0,0 +1,207 @@
1
+ import * as THREE from 'three/webgpu';
2
+ import { type LodRun } from './lod-scheduler';
3
+ import type { ChunkFileFormat } from './loading';
4
+ import type { RadChunkRangeRequest } from './load-worker-protocol';
5
+ import type { LccChunkParams } from './formats/lcc/parse-lcc';
6
+ import type { SplatData } from './splat-data';
7
+ import type { SplatDatasetSource } from './dataset-source';
8
+ /**
9
+ * Per-frame LOD decision maker: given the camera, returns the set of runs
10
+ * (`{file, offset, count}` slices with a leaf interval) that should be
11
+ * resident. Two implementations exist - {@link LodScheduler} for the flat
12
+ * Streamed SOG leaves-with-levels model, and `OctreeLodSource` for the
13
+ * cut-based LCC2 octree - so {@link StreamedSplatMesh} is format-agnostic.
14
+ */
15
+ export interface LodSource {
16
+ /** Active-splat budget the returned set stays within. */
17
+ budget: number;
18
+ /** World-unit distance inside which the finest LOD is used. */
19
+ lodBaseDistance: number;
20
+ /** Distance ratio between successive LOD levels. */
21
+ lodMultiplier: number;
22
+ /** The runs that should be resident for the given camera. */
23
+ computeDesiredRuns(cameraLocal: THREE.Vector3, frustum: THREE.Frustum, now: number, cameraForward?: THREE.Vector3): LodRun[];
24
+ /** Coarsest-level runs covering finest cells `[from, to)` - always-cached
25
+ * substitute coverage while a finer level is fetching. */
26
+ coarsestRunsFor(from: number, to: number): LodRun[];
27
+ /**
28
+ * Runs covering `[from, to)` at a requested LOD level (clamped per leaf to an
29
+ * available rung). Used by classic-LCC startup hold to coarsen a nearby home
30
+ * cell when the resolved L0 cut overflows the pool. Optional: sources without
31
+ * a flat leaf ladder leave it undefined.
32
+ */
33
+ runsAtLevelFor?(from: number, to: number, level: number): LodRun[];
34
+ /**
35
+ * Notified when a chunk finishes decoding, so a source that discovers its
36
+ * structure from chunk payloads (a `.rad` LOD tree lives in the chunks, not
37
+ * a manifest) can incorporate it. Sources with a fully-known manifest
38
+ * (Streamed SOG, LCC) leave this undefined.
39
+ */
40
+ onChunkDecoded?(file: number, data: SplatData): void;
41
+ }
42
+ /**
43
+ * Per-chunk fetch and decode settings, for formats where the URL alone is not
44
+ * enough. LCC (`.lcc`, manifest v3–v5) uses this to name a byte range inside the one big
45
+ * `data.bin` and to carry that range's dequantization ranges. LCC2 and local
46
+ * Streamed SOG use it so a `blob:` URL (no file extension) still selects the
47
+ * SOG parser.
48
+ */
49
+ export interface StreamedChunkOptions {
50
+ readonly format: ChunkFileFormat;
51
+ readonly lcc?: LccChunkParams;
52
+ /** Byte range within the single-file `.rad`; carried by a `rad-chunk`. */
53
+ readonly rad?: RadChunkRangeRequest;
54
+ /**
55
+ * Convert this SOG chunk's palette-compressed shN into per-splat packed shN
56
+ * at decode, keeping this many bands, so it survives the shared pool (M11).
57
+ * Absent leaves the palette shN to be dropped, as streamed scenes did before.
58
+ */
59
+ readonly sog?: {
60
+ readonly packShBands: 1 | 2 | 3;
61
+ };
62
+ /**
63
+ * For an unbundled SOG chunk from a dropped folder: the directory's files as
64
+ * `name → blob URL`. Absent over HTTP, where the worker resolves each image
65
+ * against the chunk's directory URL instead.
66
+ */
67
+ readonly files?: Readonly<Record<string, string>>;
68
+ }
69
+ /**
70
+ * A single always-resident environment/background tile a format ships outside
71
+ * its LOD structure - the `.lcc2` `env.sog` sky, loaded once and toggled with
72
+ * {@link StreamedSplatMesh.setEnvironmentEnabled} rather than scheduled by
73
+ * camera distance. Its splat count is absent from the manifest and measured
74
+ * when the tile decodes (see `docs/formats/lcc2-notes.md`).
75
+ */
76
+ export interface EnvironmentTile {
77
+ /** Chunk-file index (into {@link StreamedScene.chunkUrls}) of the tile. */
78
+ readonly file: number;
79
+ }
80
+ /** One collision-mesh tile a scene ships alongside its splats. */
81
+ export interface CollisionMeshDescriptor {
82
+ /**
83
+ * Fetchable URL of the tile: a binary triangle-mesh PLY (`.lcc2`), or a
84
+ * classic-LCC `collision.lci` that expands into per-cell meshes at load.
85
+ */
86
+ readonly url: string;
87
+ /**
88
+ * The tile's source-local bounds, when the format declares them. Lets a host
89
+ * order or cull tile work without parsing them first. Unused for a whole-file
90
+ * `.lci` descriptor - per-cell bounds come from the parser.
91
+ */
92
+ readonly bounds?: THREE.Box3;
93
+ }
94
+ /**
95
+ * Collision geometry a format ships next to its splats - absent for formats
96
+ * that carry none (Streamed SOG), present for XGRIDS `.lcc` / `.lcc2` captures
97
+ * that include a collision sidecar.
98
+ *
99
+ * Coordinates are source-local, the same frame as {@link StreamedScene.bounds}:
100
+ * {@link StreamedScene.formatTransform} maps them to world.
101
+ */
102
+ export interface SceneCollision {
103
+ readonly meshes: readonly CollisionMeshDescriptor[];
104
+ }
105
+ /**
106
+ * Everything {@link StreamedSplatMesh} needs about a scene, independent of
107
+ * whether it came from a Streamed SOG manifest or an LCC dataset.
108
+ */
109
+ export interface StreamedScene {
110
+ readonly source: LodSource;
111
+ /** Chunk-file URLs, indexed by a run's `file`. */
112
+ readonly chunkUrls: readonly string[];
113
+ /** How each chunk URL is fetched: an unbundled SOG directory (Streamed
114
+ * SOG) or a single bundled `.sog`/`.ply` file (LCC2 tiles). */
115
+ readonly chunkKind: 'directory' | 'file';
116
+ /** Optional per-chunk overrides, aligned with {@link chunkUrls}. */
117
+ readonly chunkOptions?: readonly (StreamedChunkOptions | undefined)[];
118
+ /**
119
+ * Per-splat SH bands this scene's chunks actually carry, after the format
120
+ * has had its say - a `Portable` LCC capture reports 0 however many bands
121
+ * the caller asked for. The pool sizes its SH textures from this, so it
122
+ * never allocates ~64 B/splat for SH that will never arrive.
123
+ */
124
+ readonly shBands?: 0 | 1 | 2 | 3;
125
+ /** Root bounds of the whole scene (valid before any chunk loads). */
126
+ readonly bounds: THREE.Box3;
127
+ /** Files backing the coarsest levels - pinned in cache as substitutes. */
128
+ readonly pinnedFiles: ReadonlySet<number>;
129
+ /** Finest-level splat total, for sizing the pool. */
130
+ readonly maxResidentSplats: number;
131
+ /**
132
+ * The capture's real content size - the splat count a host would need to hold
133
+ * it at full resolution. Distinct from {@link maxResidentSplats}, which a
134
+ * foveated source reports as the *requested budget* because its pool holds a
135
+ * camera-directed resident set rather than the whole tree. A host budgeting
136
+ * across several streamed meshes needs the real number: a mesh cannot spend
137
+ * more than it contains, so this is the clamp on any share handed to it.
138
+ *
139
+ * Undefined when the format does not declare it.
140
+ */
141
+ readonly contentSplatCount?: number;
142
+ /** Smallest budget that can retain full-scene coarsest coverage. */
143
+ readonly minimumCoverageSplats: number;
144
+ /**
145
+ * Optional local-to-world format correction applied once to the mesh at
146
+ * load time. Bounds and LOD data remain source-local so scheduling and
147
+ * picking consistently use the encoded coordinate frame.
148
+ */
149
+ readonly formatTransform?: THREE.Matrix4;
150
+ /** Optional collision geometry the format ships; undefined when it has none. */
151
+ readonly collision?: SceneCollision;
152
+ /**
153
+ * Optional always-resident environment tile the format ships outside its LOD
154
+ * structure (the `.lcc2` sky) - present only when the dataset carries one.
155
+ */
156
+ readonly environment?: EnvironmentTile;
157
+ /**
158
+ * Screen-space foveation band (px), for a scene that renders whole resident
159
+ * chunks and lets the material's projected-radius cull pick the LOD cut per
160
+ * splat - a `.rad` too large to load whole. Only splats sized `(min, max]` on
161
+ * screen draw, so near view rays land on fine leaves and far rays on coarse
162
+ * nodes. `StreamedSplatMesh` applies it as the mesh's screen-radius band.
163
+ */
164
+ readonly foveation?: {
165
+ readonly minScreenRadiusPx: number;
166
+ readonly maxScreenRadiusPx: number;
167
+ };
168
+ /**
169
+ * Splats per chunk file, so a `(file, localIndex)` pair maps to a stable global
170
+ * splat index (`file * chunkSize + local`). Set by `.rad`; the page-table
171
+ * renderer (`foveationMode: 'pagetable'`) keys frontier splats by that global.
172
+ */
173
+ readonly chunkSize?: number;
174
+ /**
175
+ * A chunk the scene builder already fetched and decoded (`.rad` decodes chunk
176
+ * 0 for the scene bounds and the SH codebook). Handing it straight to the
177
+ * renderer saves a second round trip - and in `pagetable` mode it is what
178
+ * seeds the worker's tree roots, without which the first traversals return an
179
+ * empty frontier and the view stays blank until chunk 0 arrives a second time.
180
+ */
181
+ readonly bootstrapChunk?: {
182
+ readonly file: number;
183
+ readonly data: SplatData;
184
+ };
185
+ /**
186
+ * Source-local xyz subsample of the coarsest overview (`.rad` chunk 0).
187
+ * Survives after pagetable mode transfers chunk-0 buffers to the worker, so
188
+ * a host can estimate terrain height before any later chunk decodes.
189
+ */
190
+ readonly overviewPositions?: Float32Array;
191
+ }
192
+ export interface LodSourceOptions {
193
+ budget: number;
194
+ lodBaseDistance: number;
195
+ lodMultiplier: number;
196
+ }
197
+ /**
198
+ * Builds a scene from a parsed Streamed SOG manifest (JSON already fetched).
199
+ *
200
+ * `shBands` opts into view-dependent color (M11): when ≥ 1, each chunk's
201
+ * palette shN is converted to per-splat packed shN at decode so it survives
202
+ * the shared pool, and the pool allocates that many SH bands. Unset/0 keeps
203
+ * the historical behavior (palette shN dropped). It is opt-in because the
204
+ * manifest does not declare whether the tiles carry shN; a scene with none
205
+ * renders unchanged but wastes the allocated SH textures, so ask deliberately.
206
+ */
207
+ export declare function buildSogScene(json: unknown, source: SplatDatasetSource, options: LodSourceOptions, shBands?: 0 | 1 | 2 | 3): StreamedScene;
@@ -0,0 +1,19 @@
1
+ const a = (o, n, ...l) => {
2
+ o === "error" ? console.error(n, ...l) : console.warn(n, ...l);
3
+ };
4
+ let r = a;
5
+ function c(o) {
6
+ r = o === void 0 ? a : o;
7
+ }
8
+ function e(o, ...n) {
9
+ r == null || r("warn", `vlam: ${o}`, ...n);
10
+ }
11
+ function s(o, ...n) {
12
+ r == null || r("error", `vlam: ${o}`, ...n);
13
+ }
14
+ export {
15
+ s as l,
16
+ c as s,
17
+ e as w
18
+ };
19
+ //# sourceMappingURL=logging-BfPdd7NJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging-BfPdd7NJ.js","sources":["../src/lib/logging.ts"],"sourcesContent":["/**\n * The library's diagnostic output, behind one replaceable hook.\n *\n * A library that writes straight to the host console gives embedders no way\n * out - and some of these sites are per-item (a malformed chunk in a large\n * streamed scene can warn on every tick). Everything routes through\n * {@link setVlamLogHandler} instead, so a host can forward VLAM! diagnostics\n * into its own logger, or silence them entirely.\n *\n * Worker scopes are the deliberate exception: `load-worker.ts` and\n * `sort-worker.ts` run in a separate realm with no access to this module's\n * state, and plumbing a handler across `postMessage` would mean serializing\n * user callbacks. Neither worker logs today - failures travel back as\n * structured messages - so there is nothing to route.\n */\n\n/** Severity of a library diagnostic. */\nexport type VlamLogLevel = 'warn' | 'error';\n\n/**\n * Receives library diagnostics. `message` is already prefixed with `vlam:`;\n * `details` carries any extra values the call site passed.\n */\nexport type VlamLogHandler = (level: VlamLogLevel, message: string, ...details: unknown[]) => void;\n\nconst defaultHandler: VlamLogHandler = (level, message, ...details) => {\n if (level === 'error') console.error(message, ...details);\n else console.warn(message, ...details);\n};\n\nlet handler: VlamLogHandler | null = defaultHandler;\n\n/**\n * Redirects (or silences) every diagnostic the library emits.\n *\n * @param next - A handler to receive diagnostics, `null` to drop them\n * silently, or omitted/`undefined` to restore the default console handler.\n *\n * @example\n * setVlamLogHandler((level, message) => myLogger.log(level, message));\n * setVlamLogHandler(null); // silence VLAM! entirely\n */\nexport function setVlamLogHandler(next?: VlamLogHandler | null): void {\n handler = next === undefined ? defaultHandler : next;\n}\n\n/** Emits a `vlam:`-prefixed warning through the current handler. */\nexport function warn(message: string, ...details: unknown[]): void {\n handler?.('warn', `vlam: ${message}`, ...details);\n}\n\n/** Emits a `vlam:`-prefixed error through the current handler. */\nexport function logError(message: string, ...details: unknown[]): void {\n handler?.('error', `vlam: ${message}`, ...details);\n}\n"],"names":["defaultHandler","level","message","details","handler","setVlamLogHandler","next","warn","logError"],"mappings":"AAyBA,MAAMA,IAAiC,CAACC,GAAOC,MAAYC,MAAY;AACrE,EAAIF,MAAU,UAAS,QAAQ,MAAMC,GAAS,GAAGC,CAAO,IACnD,QAAQ,KAAKD,GAAS,GAAGC,CAAO;AACvC;AAEA,IAAIC,IAAiCJ;AAY9B,SAASK,EAAkBC,GAAoC;AACpE,EAAAF,IAAUE,MAAS,SAAYN,IAAiBM;AAClD;AAGO,SAASC,EAAKL,MAAoBC,GAA0B;AACjE,EAAAC,KAAA,QAAAA,EAAU,QAAQ,SAASF,CAAO,IAAI,GAAGC;AAC3C;AAGO,SAASK,EAASN,MAAoBC,GAA0B;AACrE,EAAAC,KAAA,QAAAA,EAAU,SAAS,SAASF,CAAO,IAAI,GAAGC;AAC5C;"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The library's diagnostic output, behind one replaceable hook.
3
+ *
4
+ * A library that writes straight to the host console gives embedders no way
5
+ * out - and some of these sites are per-item (a malformed chunk in a large
6
+ * streamed scene can warn on every tick). Everything routes through
7
+ * {@link setVlamLogHandler} instead, so a host can forward VLAM! diagnostics
8
+ * into its own logger, or silence them entirely.
9
+ *
10
+ * Worker scopes are the deliberate exception: `load-worker.ts` and
11
+ * `sort-worker.ts` run in a separate realm with no access to this module's
12
+ * state, and plumbing a handler across `postMessage` would mean serializing
13
+ * user callbacks. Neither worker logs today - failures travel back as
14
+ * structured messages - so there is nothing to route.
15
+ */
16
+ /** Severity of a library diagnostic. */
17
+ export type VlamLogLevel = 'warn' | 'error';
18
+ /**
19
+ * Receives library diagnostics. `message` is already prefixed with `vlam:`;
20
+ * `details` carries any extra values the call site passed.
21
+ */
22
+ export type VlamLogHandler = (level: VlamLogLevel, message: string, ...details: unknown[]) => void;
23
+ /**
24
+ * Redirects (or silences) every diagnostic the library emits.
25
+ *
26
+ * @param next - A handler to receive diagnostics, `null` to drop them
27
+ * silently, or omitted/`undefined` to restore the default console handler.
28
+ *
29
+ * @example
30
+ * setVlamLogHandler((level, message) => myLogger.log(level, message));
31
+ * setVlamLogHandler(null); // silence VLAM! entirely
32
+ */
33
+ export declare function setVlamLogHandler(next?: VlamLogHandler | null): void;
34
+ /** Emits a `vlam:`-prefixed warning through the current handler. */
35
+ export declare function warn(message: string, ...details: unknown[]): void;
36
+ /** Emits a `vlam:`-prefixed error through the current handler. */
37
+ export declare function logError(message: string, ...details: unknown[]): void;