@voluma/vlam 0.3.0 → 0.3.4

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 (123) hide show
  1. package/README.md +2 -4
  2. package/dist/chunk-loader-DlNwggfG.js.map +1 -1
  3. package/dist/{index.d.ts → core/index.d.ts} +2 -2
  4. package/dist/{splat-budget.d.ts → core/splat-budget.d.ts} +1 -1
  5. package/dist/{splat-data.d.ts → core/splat-data.d.ts} +1 -1
  6. package/dist/{splat-mesh-types.d.ts → core/splat-mesh-types.d.ts} +5 -2
  7. package/dist/{effects.d.ts → effects/index.d.ts} +3 -3
  8. package/dist/effects.js.map +1 -1
  9. package/dist/formats/ksplat/parse-ksplat.d.ts +1 -1
  10. package/dist/formats/ksplat.js.map +1 -1
  11. package/dist/formats/lcc/collision-mesh.d.ts +2 -2
  12. package/dist/formats/lcc/collision-partition.d.ts +1 -1
  13. package/dist/formats/lcc/lcc.d.ts +3 -3
  14. package/dist/formats/lcc/lcc2.d.ts +2 -2
  15. package/dist/formats/lcc/parse-lcc.d.ts +1 -1
  16. package/dist/formats/lcc.js +276 -277
  17. package/dist/formats/lcc.js.map +1 -1
  18. package/dist/formats/ply/parse-compressed-ply.d.ts +2 -2
  19. package/dist/formats/ply/parse-splat-ply.d.ts +2 -2
  20. package/dist/formats/ply.js.map +1 -1
  21. package/dist/formats/rad/frontier-worker-protocol.d.ts +8 -1
  22. package/dist/formats/rad/index.d.ts +2 -2
  23. package/dist/formats/rad/parse-rad.d.ts +2 -2
  24. package/dist/formats/rad/rad-foveated-source.d.ts +3 -3
  25. package/dist/formats/rad/rad.d.ts +5 -5
  26. package/dist/formats/rad.js.map +1 -1
  27. package/dist/formats/sog/parse-sog.d.ts +2 -2
  28. package/dist/formats/sog.js.map +1 -1
  29. package/dist/formats/splat/parse-splat.d.ts +1 -1
  30. package/dist/formats/splat.js.map +1 -1
  31. package/dist/formats/spz/parse-spz.d.ts +1 -1
  32. package/dist/formats/spz.js.map +1 -1
  33. package/dist/half-float-Cs4FDq8Q.js.map +1 -1
  34. package/dist/index.js +3 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/{chunk-loader.d.ts → loaders/chunk-loader.d.ts} +2 -2
  37. package/dist/{load-splat-data.d.ts → loaders/load-splat-data.d.ts} +1 -1
  38. package/dist/{load-worker-protocol.d.ts → loaders/load-worker-protocol.d.ts} +2 -2
  39. package/dist/loaders.js.map +1 -1
  40. package/dist/loading-wLo8vRbA.js.map +1 -1
  41. package/dist/lod-scheduler-ChoZyEeq.js +466 -0
  42. package/dist/lod-scheduler-ChoZyEeq.js.map +1 -0
  43. package/dist/logging-BfPdd7NJ.js.map +1 -1
  44. package/dist/ply-header-B9WllbPg.js.map +1 -1
  45. package/dist/{radix-sorter-R0FCSqKu.js → radix-sorter-BrbUg_CV.js} +2 -2
  46. package/dist/radix-sorter-BrbUg_CV.js.map +1 -0
  47. package/dist/{relighting-CSQF3UcL.js → relighting-Tiwep8yd.js} +302 -294
  48. package/dist/relighting-Tiwep8yd.js.map +1 -0
  49. package/dist/{selection-volume.d.ts → selection/selection-volume.d.ts} +1 -1
  50. package/dist/{splat-partition.d.ts → selection/splat-partition.d.ts} +1 -1
  51. package/dist/selection.js.map +1 -1
  52. package/dist/sh-pack-D5wAe5gg.js.map +1 -1
  53. package/dist/splat-budget-DuVZspPQ.js.map +1 -1
  54. package/dist/splat-data-BZM5dhhx.js.map +1 -1
  55. package/dist/{splat-mesh-aQdcWA6v.js → splat-mesh-CsLOQb08.js} +3 -3
  56. package/dist/splat-mesh-CsLOQb08.js.map +1 -0
  57. package/dist/splat-mesh-types-BZIko-_9.js.map +1 -1
  58. package/dist/{static-lod-splat-mesh.d.ts → static-lod/static-lod-splat-mesh.d.ts} +2 -2
  59. package/dist/{static-lod-worker-protocol.d.ts → static-lod/static-lod-worker-protocol.d.ts} +1 -1
  60. package/dist/{static-lod.d.ts → static-lod/static-lod.d.ts} +1 -1
  61. package/dist/static-lod.js +1 -1
  62. package/dist/static-lod.js.map +1 -1
  63. package/dist/{dataset-source.d.ts → streaming/dataset-source.d.ts} +1 -1
  64. package/dist/{streaming.d.ts → streaming/index.d.ts} +3 -3
  65. package/dist/{lod-scheduler.d.ts → streaming/lod-scheduler.d.ts} +32 -0
  66. package/dist/{lod-source.d.ts → streaming/lod-source.d.ts} +19 -13
  67. package/dist/{streamed-splat-mesh-utils.d.ts → streaming/streamed-splat-mesh-utils.d.ts} +2 -2
  68. package/dist/{streamed-splat-mesh.d.ts → streaming/streamed-splat-mesh.d.ts} +83 -27
  69. package/dist/streaming.js +711 -645
  70. package/dist/streaming.js.map +1 -1
  71. package/dist/{unified-splat-mesh.d.ts → unified/unified-splat-mesh.d.ts} +13 -6
  72. package/dist/{work-buffer-gather.d.ts → unified/work-buffer-gather.d.ts} +5 -5
  73. package/dist/{work-buffer-material.d.ts → unified/work-buffer-material.d.ts} +1 -1
  74. package/dist/unified.js +280 -272
  75. package/dist/unified.js.map +1 -1
  76. package/dist/webgpu-limits-Y3CI_6_6.js.map +1 -1
  77. package/package.json +9 -9
  78. package/dist/lod-scheduler-B0a_uBlv.js +0 -383
  79. package/dist/lod-scheduler-B0a_uBlv.js.map +0 -1
  80. package/dist/radix-sorter-R0FCSqKu.js.map +0 -1
  81. package/dist/relighting-CSQF3UcL.js.map +0 -1
  82. package/dist/splat-mesh-aQdcWA6v.js.map +0 -1
  83. /package/dist/{compute-sorter.d.ts → core/compute-sorter.d.ts} +0 -0
  84. /package/dist/{create-splat-renderer.d.ts → core/create-splat-renderer.d.ts} +0 -0
  85. /package/dist/{depth-of-field.d.ts → core/depth-of-field.d.ts} +0 -0
  86. /package/dist/{half-float.d.ts → core/half-float.d.ts} +0 -0
  87. /package/dist/{logging.d.ts → core/logging.d.ts} +0 -0
  88. /package/dist/{merged-splat-mesh.d.ts → core/merged-splat-mesh.d.ts} +0 -0
  89. /package/dist/{orientation.d.ts → core/orientation.d.ts} +0 -0
  90. /package/dist/{radix-sort.d.ts → core/radix-sort.d.ts} +0 -0
  91. /package/dist/{radix-sorter.d.ts → core/radix-sorter.d.ts} +0 -0
  92. /package/dist/{relighting-pass.d.ts → core/relighting-pass.d.ts} +0 -0
  93. /package/dist/{relighting.d.ts → core/relighting.d.ts} +0 -0
  94. /package/dist/{sh-pack.d.ts → core/sh-pack.d.ts} +0 -0
  95. /package/dist/{sort-scheduler.d.ts → core/sort-scheduler.d.ts} +0 -0
  96. /package/dist/{sort-worker-protocol.d.ts → core/sort-worker-protocol.d.ts} +0 -0
  97. /package/dist/{sorter.d.ts → core/sorter.d.ts} +0 -0
  98. /package/dist/{source-transform.d.ts → core/source-transform.d.ts} +0 -0
  99. /package/dist/{splat-depth-pack.d.ts → core/splat-depth-pack.d.ts} +0 -0
  100. /package/dist/{splat-mesh-material.d.ts → core/splat-mesh-material.d.ts} +0 -0
  101. /package/dist/{splat-mesh-picking.d.ts → core/splat-mesh-picking.d.ts} +0 -0
  102. /package/dist/{splat-mesh-pool.d.ts → core/splat-mesh-pool.d.ts} +0 -0
  103. /package/dist/{splat-mesh.d.ts → core/splat-mesh.d.ts} +0 -0
  104. /package/dist/{splat-modifier-stack.d.ts → core/splat-modifier-stack.d.ts} +0 -0
  105. /package/dist/{splat-modifier.d.ts → core/splat-modifier.d.ts} +0 -0
  106. /package/dist/{splat-query.d.ts → core/splat-query.d.ts} +0 -0
  107. /package/dist/{splat-sort-bounds.d.ts → core/splat-sort-bounds.d.ts} +0 -0
  108. /package/dist/{storage-attribute-mirror.d.ts → core/storage-attribute-mirror.d.ts} +0 -0
  109. /package/dist/{webgpu-limits.d.ts → core/webgpu-limits.d.ts} +0 -0
  110. /package/dist/{worker-sorter.d.ts → core/worker-sorter.d.ts} +0 -0
  111. /package/dist/{xr-view.d.ts → core/xr-view.d.ts} +0 -0
  112. /package/dist/{loaders.d.ts → loaders/index.d.ts} +0 -0
  113. /package/dist/{loading.d.ts → loaders/loading.d.ts} +0 -0
  114. /package/dist/{ply-header.d.ts → loaders/ply-header.d.ts} +0 -0
  115. /package/dist/{selection.d.ts → selection/index.d.ts} +0 -0
  116. /package/dist/{static-lod-entry.d.ts → static-lod/index.d.ts} +0 -0
  117. /package/dist/{budget-governor.d.ts → streaming/budget-governor.d.ts} +0 -0
  118. /package/dist/{camera-budget-governor.d.ts → streaming/camera-budget-governor.d.ts} +0 -0
  119. /package/dist/{chunk-cache-budget.d.ts → streaming/chunk-cache-budget.d.ts} +0 -0
  120. /package/dist/{chunk-fetch-scheduler.d.ts → streaming/chunk-fetch-scheduler.d.ts} +0 -0
  121. /package/dist/{lod-manifest.d.ts → streaming/lod-manifest.d.ts} +0 -0
  122. /package/dist/{unified.d.ts → unified/index.d.ts} +0 -0
  123. /package/dist/{unified-work-buffer.d.ts → unified/unified-work-buffer.d.ts} +0 -0
@@ -1,383 +0,0 @@
1
- var G = Object.defineProperty;
2
- var M = (p, s, i) => s in p ? G(p, s, { enumerable: !0, configurable: !0, writable: !0, value: i }) : p[s] = i;
3
- var h = (p, s, i) => M(p, typeof s != "symbol" ? s + "" : s, i);
4
- import * as S from "three/webgpu";
5
- function P(p) {
6
- return `${p.file}:${p.level}:${p.offset}:${p.count}`;
7
- }
8
- const L = -1, y = 128e3, w = 0.1, T = 500, R = 0.1, D = 3;
9
- class N {
10
- constructor(s, i) {
11
- /** World-unit distance inside which the finest level (0) is used. */
12
- h(this, "lodBaseDistance");
13
- /** Distance ratio between successive LOD levels. */
14
- h(this, "lodMultiplier");
15
- /** Maximum active splats; the desired set is demoted to fit. */
16
- h(this, "budget");
17
- h(this, "leaves");
18
- h(this, "coarsest");
19
- /** Per leaf: lowest/highest LOD level that has data. */
20
- h(this, "minLevel");
21
- h(this, "maxLevel");
22
- /**
23
- * Per leaf: the distance *ambition* - the finest level the camera distance
24
- * asks for, with hysteresis/dwell applied. This is the hysteresis *state*:
25
- * only {@link selectLevels} writes it, so budget demotion can never feed
26
- * back into the dead-band/dwell logic and oscillate. With `fillPastDistance`
27
- * false, {@link fillBudget} restores {@link resolved} only up to here.
28
- */
29
- h(this, "level");
30
- /**
31
- * Per leaf: the resolved target - the ambition capped to fit the budget by
32
- * {@link enforceBudget}, then refilled from leftover headroom by
33
- * {@link fillBudget} (or DROPPED). This is what {@link coalesceRuns} emits.
34
- */
35
- h(this, "resolved");
36
- /** Per leaf: timestamp of the last distance-level change, for dwell. */
37
- h(this, "changedAt");
38
- h(this, "distance");
39
- h(this, "inFrustum");
40
- /** Leaves that must move through the budget cut together. */
41
- h(this, "budgetGroups");
42
- /** Per-leaf physical coverage group, when the manifest defines one. */
43
- h(this, "coverageGroups");
44
- /** Whether view-frustum membership affects distance and fill priority. */
45
- h(this, "frustumAware");
46
- /** Fraction of the usable budget that optional refinement may consume. */
47
- h(this, "budgetFillFraction");
48
- /** Absolute cap on optional refinement; distance-selected detail may exceed it. */
49
- h(this, "budgetFillCap");
50
- /** Whether a budget that holds every finest leaf forces a full-finest cut. */
51
- h(this, "forceFinestWhenFits");
52
- /**
53
- * Leaves at or inside this camera distance always resolve to their finest
54
- * available level (no hysteresis climb). Classic LCC uses a multi-band
55
- * radius so adjacent broad cells do not paint coarse discs at startup.
56
- */
57
- h(this, "forceFinestWithin");
58
- /**
59
- * When false, {@link fillBudget} only restores toward each leaf's
60
- * distance-selected {@link level} - never finer. Classic LCC sets this so
61
- * a mid-range cell does not fetch L0 only to demote to L1 once the cut
62
- * settles.
63
- */
64
- h(this, "fillPastDistance");
65
- /**
66
- * What the last cut actually decided, for the HUD.
67
- *
68
- * The resident splat count alone cannot distinguish "the scheduler asked for
69
- * this" from "the scheduler asked for more and the mesh could not apply it",
70
- * and those have opposite fixes. Measured rather than inferred after a
71
- * zoomed-out `sandwijck` sat at exactly its coarsest total (190,730) with a
72
- * 600k budget - 68% of the budget unspent, with no way to see which stage
73
- * declined to spend it.
74
- */
75
- h(this, "stats", {
76
- /** Leaves the frustum test accepted - `fillBudget`'s candidate set. */
77
- inFrustum: 0,
78
- /** Total leaves in the tree. */
79
- leaves: 0,
80
- /** Splats implied by the resolved cut, before the mesh applies anything. */
81
- desired: 0,
82
- /** Splats `fillBudget` added on top of the distance-chosen levels. */
83
- filled: 0
84
- });
85
- h(this, "scratchBox", new S.Box3());
86
- h(this, "scratchSize", new S.Vector3());
87
- h(this, "scratchCenter", new S.Vector3());
88
- h(this, "screenImportance");
89
- /** Whether the last selection pass received a camera-forward vector. */
90
- h(this, "hasScreenImportance", !1);
91
- /**
92
- * Persistent leaf-index ordering scratch for {@link enforceBudget} and
93
- * {@link fillBudget}, sorted in place per call instead of allocating a
94
- * fresh `[...keys]` array. Comparators break ties on the index itself so
95
- * the order is identical to the stable `Array#sort` this replaces
96
- * (`TypedArray#sort` stability is not guaranteed).
97
- */
98
- h(this, "orderScratch");
99
- if (this.leaves = s.leaves, this.coarsest = s.lodLevels - 1, this.budget = i.budget, this.lodBaseDistance = i.lodBaseDistance, this.lodMultiplier = i.lodMultiplier, this.frustumAware = i.frustumAware !== !1, this.budgetFillFraction = i.budgetFillFraction ?? 0.9, this.budgetFillCap = i.budgetFillCap ?? Number.POSITIVE_INFINITY, this.forceFinestWhenFits = i.forceFinestWhenFits !== !1, this.forceFinestWithin = i.forceFinestWithin, this.fillPastDistance = i.fillPastDistance !== !1, this.budgetFillFraction <= 0 || this.budgetFillFraction > 1)
100
- throw new RangeError("LodScheduler budgetFillFraction must be in (0, 1].");
101
- if (!(this.budgetFillCap > 0))
102
- throw new RangeError("LodScheduler budgetFillCap must be positive.");
103
- if (this.forceFinestWithin !== void 0 && !(this.forceFinestWithin >= 0))
104
- throw new RangeError("LodScheduler forceFinestWithin must be >= 0.");
105
- const e = this.leaves.length;
106
- this.minLevel = new Int32Array(e), this.maxLevel = new Int32Array(e), this.level = new Int32Array(e).fill(this.coarsest), this.resolved = new Int32Array(e).fill(this.coarsest), this.changedAt = new Float64Array(e), this.distance = new Float64Array(e), this.inFrustum = new Uint8Array(e), this.screenImportance = new Float32Array(e), this.orderScratch = new Uint32Array(e), this.coverageGroups = new Int32Array(e).fill(-1);
107
- const l = [], d = /* @__PURE__ */ new Map();
108
- for (let n = 0; n < e; n++) {
109
- const r = this.leaves[n].budgetGroup;
110
- if (r === void 0) {
111
- l.push([n]);
112
- continue;
113
- }
114
- let t = d.get(r);
115
- t === void 0 && (t = l.length, d.set(r, t), l.push([])), l[t].push(n), this.coverageGroups[n] = r;
116
- }
117
- this.budgetGroups = l.map((n) => Uint32Array.from(n));
118
- for (let n = 0; n < e; n++) {
119
- const r = this.leaves[n].lods;
120
- let t = this.coarsest, a = 0;
121
- for (let o = 0; o < r.length; o++)
122
- r[o] !== void 0 && (t = Math.min(t, o), a = Math.max(a, o));
123
- this.minLevel[n] = t, this.maxLevel[n] = a, this.level[n] = a;
124
- }
125
- }
126
- /**
127
- * Recomputes the desired resident run set for the current camera.
128
- *
129
- * @param cameraLocal - Camera position in the mesh's local space.
130
- * @param frustum - View frustum in the mesh's local space.
131
- * @param now - Monotonic timestamp (ms) for dwell hysteresis.
132
- */
133
- computeDesiredRuns(s, i, e, l) {
134
- if (this.selectLevels(s, i, e, l), this.forceFinestWhenFits && this.budget >= this.finestTotal()) {
135
- for (let n = 0; n < this.leaves.length; n++)
136
- this.resolved[n] = this.minLevel[n];
137
- return this.stats.desired = this.activeTotal(), this.stats.filled = 0, this.coalesceRuns();
138
- }
139
- this.resolved.set(this.level), this.enforceBudget();
140
- const d = this.activeTotal();
141
- return this.fillBudget(), this.stats.desired = this.activeTotal(), this.stats.filled = this.stats.desired - d, this.coalesceRuns();
142
- }
143
- /** Total active splats implied by the current resolved levels. */
144
- activeTotal() {
145
- var i;
146
- let s = 0;
147
- for (let e = 0; e < this.leaves.length; e++) {
148
- const l = this.resolved[e];
149
- l !== L && (s += ((i = this.leaves[e].lods[l]) == null ? void 0 : i.count) ?? 0);
150
- }
151
- return s;
152
- }
153
- /** Splats if every leaf sat at its finest available level (none dropped). */
154
- finestTotal() {
155
- var i;
156
- let s = 0;
157
- for (let e = 0; e < this.leaves.length; e++) {
158
- const l = this.minLevel[e];
159
- s += ((i = this.leaves[e].lods[l]) == null ? void 0 : i.count) ?? 0;
160
- }
161
- return s;
162
- }
163
- selectLevels(s, i, e, l) {
164
- const { lodBaseDistance: d, lodMultiplier: n } = this;
165
- this.hasScreenImportance = l !== void 0;
166
- let r = 0;
167
- this.stats.leaves = this.leaves.length;
168
- for (let t = 0; t < this.leaves.length; t++) {
169
- const a = this.leaves[t], o = a.bounds.distanceToPoint(s);
170
- this.distance[t] = o, this.scratchBox.copy(a.bounds), a.bounds.getSize(this.scratchSize), this.scratchBox.expandByScalar(this.scratchSize.length() * R);
171
- const c = i.intersectsBox(this.scratchBox);
172
- if (this.inFrustum[t] = c ? 1 : 0, c && r++, l) {
173
- a.bounds.getCenter(this.scratchCenter).sub(s);
174
- const f = this.scratchCenter.dot(l), F = Math.max(0, this.scratchCenter.lengthSq() - f * f);
175
- this.screenImportance[t] = f > 0 ? Math.sqrt(F) / Math.max(f, 0.25) : Number.POSITIVE_INFINITY;
176
- } else
177
- this.screenImportance[t] = 0;
178
- const u = this.level[t];
179
- if (this.fillPastDistance && this.forceFinestWithin !== void 0 && o <= this.forceFinestWithin) {
180
- const f = this.minLevel[t];
181
- u !== f && (this.level[t] = f, this.changedAt[t] = e);
182
- continue;
183
- }
184
- const v = !this.frustumAware || c ? o : o * D;
185
- if (!this.fillPastDistance) {
186
- const f = this.distanceBandLevel(t, v, d, n);
187
- if (this.changedAt[t] === 0 && u === this.maxLevel[t]) {
188
- f !== u && (this.level[t] = f, this.changedAt[t] = e);
189
- continue;
190
- }
191
- if (f === u) continue;
192
- const b = this.hysteresisLevel(t, v, d, n, u);
193
- if (b === u) continue;
194
- (b > u || e - this.changedAt[t] >= T) && (this.level[t] = b, this.changedAt[t] = e);
195
- continue;
196
- }
197
- const m = this.hysteresisLevel(t, v, d, n, u);
198
- if (m === u) continue;
199
- (this.changedAt[t] === 0 && u === this.maxLevel[t] || e - this.changedAt[t] >= T) && (this.level[t] = m, this.changedAt[t] = e);
200
- }
201
- this.stats.inFrustum = r;
202
- }
203
- /**
204
- * Pure distance→level band (independent of the leaf's current level), so a
205
- * classic LCC cut never has to dwell through intermediate rungs.
206
- */
207
- distanceBandLevel(s, i, e, l) {
208
- const d = this.minLevel[s], n = this.maxLevel[s];
209
- let r = d;
210
- for (; r < n && i > e * l ** r * (1 + w); ) r++;
211
- return this.resolveAvailable(s, r);
212
- }
213
- /**
214
- * Stable level from distance: coarsen only past `threshold·(1 + margin)`,
215
- * refine only within `threshold·(1 − margin)`. The available-level clamp
216
- * keeps leaves that lack a level on their nearest coarser one.
217
- */
218
- hysteresisLevel(s, i, e, l, d) {
219
- const n = this.minLevel[s], r = this.maxLevel[s];
220
- let t = Math.min(r, Math.max(n, d));
221
- for (; t < r && i > e * l ** t * (1 + w); ) t++;
222
- for (; t > n && i <= e * l ** (t - 1) * (1 - w); ) t--;
223
- return this.resolveAvailable(s, t);
224
- }
225
- resolveAvailable(s, i) {
226
- const e = this.leaves[s].lods;
227
- for (let l = i; l <= this.maxLevel[s]; l++) if (e[l]) return l;
228
- for (let l = i - 1; l >= this.minLevel[s]; l--) if (e[l]) return l;
229
- return this.maxLevel[s];
230
- }
231
- /** Demotes/drops lowest-priority leaves until the total fits the budget. */
232
- enforceBudget() {
233
- var e, l, d;
234
- let s = this.activeTotal();
235
- if (s <= this.budget) return;
236
- const i = this.orderScratch.subarray(0, this.budgetGroups.length);
237
- for (let n = 0; n < i.length; n++) i[n] = n;
238
- i.sort((n, r) => {
239
- const t = this.budgetGroups[n][0], a = this.budgetGroups[r][0];
240
- if (this.frustumAware) {
241
- const o = this.inFrustum[t], c = this.inFrustum[a];
242
- if (o !== c) return o - c;
243
- }
244
- return this.distance[a] - this.distance[t] || t - a;
245
- });
246
- for (const n of i) {
247
- const r = this.budgetGroups[n];
248
- for (; s > this.budget; ) {
249
- let t = 0, a = !1;
250
- for (const o of r) {
251
- const c = this.resolved[o];
252
- if (c >= this.maxLevel[o]) continue;
253
- const u = this.leaves[o].lods, v = this.resolveAvailable(o, c + 1);
254
- v !== c && (t += (((e = u[c]) == null ? void 0 : e.count) ?? 0) - (((l = u[v]) == null ? void 0 : l.count) ?? 0), this.resolved[o] = v, a = !0);
255
- }
256
- if (!a) break;
257
- s -= t;
258
- }
259
- if (s <= this.budget) return;
260
- }
261
- for (const n of i) {
262
- if (s <= this.budget) return;
263
- for (const r of this.budgetGroups[n])
264
- this.resolved[r] !== L && (s -= ((d = this.leaves[r].lods[this.resolved[r]]) == null ? void 0 : d.count) ?? 0, this.resolved[r] = L);
265
- }
266
- }
267
- /**
268
- * Uses leftover budget to restore detail after {@link enforceBudget}
269
- * demoted leaves, nearest first, while the total stays under the fill
270
- * target. By default this may also refine *past* the distance-chosen level
271
- * when headroom remains (octree / page-table streams). With
272
- * {@link fillPastDistance} false (classic LCC), fill never goes finer than
273
- * each leaf's distance-selected level - so a cell at the L1 band does not
274
- * fetch L0 only to demote moments later with the camera still.
275
- */
276
- fillBudget() {
277
- var d, n;
278
- const s = this.forceFinestWhenFits && this.budget >= this.finestTotal() ? this.budget : Math.min(this.budget, this.budgetFillCap) * this.budgetFillFraction;
279
- let i = this.activeTotal();
280
- if (i >= s) return;
281
- let e = 0;
282
- for (let r = 0; r < this.budgetGroups.length; r++) {
283
- const t = this.budgetGroups[r][0];
284
- (!this.frustumAware || this.inFrustum[t] === 1) && this.resolved[t] !== L && (this.orderScratch[e++] = r);
285
- }
286
- const l = this.orderScratch.subarray(0, e);
287
- l.sort((r, t) => {
288
- const a = this.budgetGroups[r][0], o = this.budgetGroups[t][0];
289
- return this.distance[a] - this.distance[o] || a - o;
290
- });
291
- for (const r of l) {
292
- const t = this.budgetGroups[r];
293
- for (; ; ) {
294
- let a = 0, o = !1;
295
- for (const c of t) {
296
- const u = this.resolved[c], v = this.fillPastDistance ? this.minLevel[c] : this.level[c];
297
- if (u <= v) continue;
298
- const m = this.leaves[c];
299
- let g = u - 1;
300
- for (; g > v && !m.lods[g]; ) g--;
301
- g < v || !m.lods[g] || (a += (((d = m.lods[g]) == null ? void 0 : d.count) ?? 0) - (((n = m.lods[u]) == null ? void 0 : n.count) ?? 0), o = !0);
302
- }
303
- if (!o || i + a > s) break;
304
- for (const c of t) {
305
- const u = this.resolved[c], v = this.fillPastDistance ? this.minLevel[c] : this.level[c];
306
- if (u <= v) continue;
307
- const m = this.leaves[c];
308
- let g = u - 1;
309
- for (; g > v && !m.lods[g]; ) g--;
310
- g >= v && m.lods[g] && (this.resolved[c] = g);
311
- }
312
- i += a;
313
- }
314
- }
315
- }
316
- coalesceRuns() {
317
- return this.buildRuns(0, this.leaves.length, (s) => this.resolved[s]);
318
- }
319
- /**
320
- * Runs covering [from, to) at each leaf's coarsest available level - used
321
- * to substitute always-cached coverage while a finer level is fetching.
322
- */
323
- coarsestRunsFor(s, i) {
324
- return this.buildRuns(s, i, (e) => this.maxLevel[e]);
325
- }
326
- /**
327
- * Runs covering [from, to) at `level`, clamped per leaf to an available rung
328
- * (prefer the requested level, else the next coarser, else the next finer).
329
- */
330
- runsAtLevelFor(s, i, e) {
331
- const l = Math.floor(e);
332
- return this.buildRuns(s, i, (d) => this.clampLeafLevel(d, l));
333
- }
334
- /** Prefer `wanted`, else next coarser, else next finer; {@link DROPPED} if none. */
335
- clampLeafLevel(s, i) {
336
- const e = this.leaves[s];
337
- if (e.lods[i]) return i;
338
- for (let l = i + 1; l < e.lods.length; l++)
339
- if (e.lods[l]) return l;
340
- for (let l = i - 1; l >= 0; l--)
341
- if (e.lods[l]) return l;
342
- return L;
343
- }
344
- /** Coalesces leaves [from, to) at `levelOf(leaf)` into contiguous runs. */
345
- buildRuns(s, i, e) {
346
- const l = [];
347
- let d = -1, n = -1, r = 0, t = 0, a = -1, o = -1, c = -1, u = Number.POSITIVE_INFINITY, v = !1, m = Number.POSITIVE_INFINITY;
348
- const g = () => {
349
- t > 0 && l.push({
350
- file: d,
351
- level: n,
352
- offset: r,
353
- count: t,
354
- leafStart: a,
355
- leafEnd: o,
356
- distance: u,
357
- inView: v,
358
- ...this.hasScreenImportance ? { screenImportance: m } : {},
359
- ...c >= 0 ? { coverageGroup: c } : {}
360
- }), t = 0;
361
- };
362
- for (let f = s; f < i; f++) {
363
- const F = e(f);
364
- if (F === L) {
365
- g();
366
- continue;
367
- }
368
- const b = this.leaves[f].lods[F];
369
- if (!b) {
370
- g();
371
- continue;
372
- }
373
- const I = this.coverageGroups[f], A = this.distance[f], x = this.inFrustum[f] === 1;
374
- t > 0 && b.file === d && F === n && I === c && b.offset === r + t && t + b.count <= y ? (t += b.count, o = f + 1, A < u && (u = A), x && (v = !0), m = Math.min(m, this.screenImportance[f])) : (g(), d = b.file, n = F, r = b.offset, t = b.count, a = f, o = f + 1, c = I, u = A, v = x, m = this.screenImportance[f]);
375
- }
376
- return g(), l;
377
- }
378
- }
379
- export {
380
- N as L,
381
- P as r
382
- };
383
- //# sourceMappingURL=lod-scheduler-B0a_uBlv.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lod-scheduler-B0a_uBlv.js","sources":["../src/lib/lod-scheduler.ts"],"sourcesContent":["import * as THREE from 'three/webgpu';\nimport type { LodLeaf, LodManifest } from './lod-manifest';\nimport type { LodSource } from './lod-source';\n\n/**\n * A contiguous splat range within one chunk file, at one LOD level - the\n * unit the pool activates. Adjacent manifest leaves that resolve to the same\n * `(file, level)` and whose ranges abut are coalesced into one run, so the\n * pool sees a handful of large ranges instead of thousands of tiny leaves.\n */\nexport interface LodRun {\n readonly file: number;\n readonly level: number;\n readonly offset: number;\n readonly count: number;\n /** First manifest-leaf index this run covers. */\n readonly leafStart: number;\n /** One past the last covered leaf index. */\n readonly leafEnd: number;\n /**\n * Physical-coverage identity when several manifest leaves form one region.\n * A classic LCC cell may be split into fetch-sized leaves, but its slices\n * must still swap as one visible unit. Undefined retains interval-based\n * grouping for hierarchical sources.\n */\n readonly coverageGroup?: number;\n /**\n * Camera distance (mesh-local) of the nearest leaf in this run, from the\n * last {@link LodScheduler.computeDesiredRuns} pass. Streamed meshes use it\n * to fetch near detail before far coarse coverage. Absent on sources that\n * do not track per-leaf distance.\n */\n readonly distance?: number;\n /**\n * True when any leaf in this run intersects the view frustum (with the\n * scheduler's edge margin). Used to rank classic-path fetches so in-view\n * detail beats behind-camera work; absent when the source does not track it.\n */\n readonly inView?: boolean;\n /**\n * Fetch-only angular distance from the camera's forward axis. Smaller values\n * are nearer the screen centre. It never affects LOD selection or budget.\n */\n readonly screenImportance?: number;\n}\n\n/** Stable identity of a run, for diffing desired vs. resident sets. */\nexport function runKey(run: LodRun): string {\n return `${run.file}:${run.level}:${run.offset}:${run.count}`;\n}\n\n/** Sentinel level meaning \"this leaf is currently dropped (renders nothing)\". */\nconst DROPPED = -1;\n\n/**\n * Maximum splats in one run. Large contiguous regions are split into several\n * runs so no single pool append (and thus no single frame) does an unbounded\n * amount of copy + upload work. Split runs stay contiguous, so they still\n * upload as one coalesced rectangle.\n */\nconst MAX_RUN_SPLATS = 128_000;\n\n/** Hysteresis dead-band around each LOD distance threshold. */\nconst THRESHOLD_MARGIN = 0.1;\n/** Minimum time a leaf must hold a level before changing again, ms. */\nconst DWELL_MS = 500;\n/** Frustum test margin, as a fraction of each leaf's own size. */\nconst FRUSTUM_MARGIN = 0.1;\n/**\n * Out-of-frustum leaves act this many times farther away instead of being\n * hard-coarsened. Rotating the camera then re-ranks leaves smoothly (and\n * dwell applies), rather than mass-flipping edge leaves every frame -\n * PlayCanvas uses the same behind-camera-penalty idea.\n */\nconst FRUSTUM_PENALTY = 3;\n\n/**\n * Chooses a level of detail per spatial leaf from the camera, keeps the\n * total within a splat budget, and coalesces the result into runs.\n *\n * The scheduler is pure with respect to rendering: it takes a camera\n * position and frustum (both in the mesh's local space) and returns the set\n * of runs that should be resident. It owns only the small amount of state\n * needed for temporal stability (each leaf's current level and when it last\n * changed), so it is straightforward to drive from a test harness.\n *\n * LOD distance model (PlayCanvas-compatible): level 0 is used within\n * `lodBaseDistance`; each successive level covers a band `lodMultiplier`×\n * farther out. By default out-of-frustum leaves act {@link FRUSTUM_PENALTY}×\n * farther away. Formats with broad spatial cells can disable that bias so an\n * orbit does not churn already-refined cells at the frustum edges.\n *\n * The distance model only sets the *floor* and the *priority*: when the\n * distance-chosen set leaves budget unused, {@link fillBudget} promotes\n * in-frustum leaves nearest-first until its configured headroom target is\n * spent (90% by default). Formats can cap that optional refinement and avoid\n * a full-finest cut even when an explicitly large budget could hold it. The\n * budget\n * therefore decides how far refinement reaches - a desktop budget yields\n * full detail even with the whole scene in view, a mobile budget refines\n * only near the camera.\n */\nexport class LodScheduler implements LodSource {\n /** World-unit distance inside which the finest level (0) is used. */\n lodBaseDistance: number;\n /** Distance ratio between successive LOD levels. */\n lodMultiplier: number;\n /** Maximum active splats; the desired set is demoted to fit. */\n budget: number;\n\n private readonly leaves: readonly LodLeaf[];\n private readonly coarsest: number;\n /** Per leaf: lowest/highest LOD level that has data. */\n private readonly minLevel: Int32Array;\n private readonly maxLevel: Int32Array;\n /**\n * Per leaf: the distance *ambition* - the finest level the camera distance\n * asks for, with hysteresis/dwell applied. This is the hysteresis *state*:\n * only {@link selectLevels} writes it, so budget demotion can never feed\n * back into the dead-band/dwell logic and oscillate. With `fillPastDistance`\n * false, {@link fillBudget} restores {@link resolved} only up to here.\n */\n private readonly level: Int32Array;\n /**\n * Per leaf: the resolved target - the ambition capped to fit the budget by\n * {@link enforceBudget}, then refilled from leftover headroom by\n * {@link fillBudget} (or DROPPED). This is what {@link coalesceRuns} emits.\n */\n private readonly resolved: Int32Array;\n /** Per leaf: timestamp of the last distance-level change, for dwell. */\n private readonly changedAt: Float64Array;\n\n private readonly distance: Float64Array;\n private readonly inFrustum: Uint8Array;\n /** Leaves that must move through the budget cut together. */\n private readonly budgetGroups: readonly Uint32Array[];\n /** Per-leaf physical coverage group, when the manifest defines one. */\n private readonly coverageGroups: Int32Array;\n /** Whether view-frustum membership affects distance and fill priority. */\n private readonly frustumAware: boolean;\n /** Fraction of the usable budget that optional refinement may consume. */\n private readonly budgetFillFraction: number;\n /** Absolute cap on optional refinement; distance-selected detail may exceed it. */\n private readonly budgetFillCap: number;\n /** Whether a budget that holds every finest leaf forces a full-finest cut. */\n private readonly forceFinestWhenFits: boolean;\n /**\n * Leaves at or inside this camera distance always resolve to their finest\n * available level (no hysteresis climb). Classic LCC uses a multi-band\n * radius so adjacent broad cells do not paint coarse discs at startup.\n */\n private readonly forceFinestWithin: number | undefined;\n /**\n * When false, {@link fillBudget} only restores toward each leaf's\n * distance-selected {@link level} - never finer. Classic LCC sets this so\n * a mid-range cell does not fetch L0 only to demote to L1 once the cut\n * settles.\n */\n private readonly fillPastDistance: boolean;\n\n /**\n * What the last cut actually decided, for the HUD.\n *\n * The resident splat count alone cannot distinguish \"the scheduler asked for\n * this\" from \"the scheduler asked for more and the mesh could not apply it\",\n * and those have opposite fixes. Measured rather than inferred after a\n * zoomed-out `sandwijck` sat at exactly its coarsest total (190,730) with a\n * 600k budget - 68% of the budget unspent, with no way to see which stage\n * declined to spend it.\n */\n readonly stats = {\n /** Leaves the frustum test accepted - `fillBudget`'s candidate set. */\n inFrustum: 0,\n /** Total leaves in the tree. */\n leaves: 0,\n /** Splats implied by the resolved cut, before the mesh applies anything. */\n desired: 0,\n /** Splats `fillBudget` added on top of the distance-chosen levels. */\n filled: 0,\n };\n\n private readonly scratchBox = new THREE.Box3();\n private readonly scratchSize = new THREE.Vector3();\n private readonly scratchCenter = new THREE.Vector3();\n private readonly screenImportance: Float32Array;\n /** Whether the last selection pass received a camera-forward vector. */\n private hasScreenImportance = false;\n /**\n * Persistent leaf-index ordering scratch for {@link enforceBudget} and\n * {@link fillBudget}, sorted in place per call instead of allocating a\n * fresh `[...keys]` array. Comparators break ties on the index itself so\n * the order is identical to the stable `Array#sort` this replaces\n * (`TypedArray#sort` stability is not guaranteed).\n */\n private readonly orderScratch: Uint32Array;\n\n constructor(\n manifest: LodManifest,\n options: {\n budget: number;\n lodBaseDistance: number;\n lodMultiplier: number;\n frustumAware?: boolean;\n budgetFillFraction?: number;\n budgetFillCap?: number;\n forceFinestWhenFits?: boolean;\n forceFinestWithin?: number;\n /**\n * When false, leftover-budget fill never refines past the distance-\n * selected level (see {@link fillPastDistance}). Default true.\n */\n fillPastDistance?: boolean;\n },\n ) {\n this.leaves = manifest.leaves;\n this.coarsest = manifest.lodLevels - 1;\n this.budget = options.budget;\n this.lodBaseDistance = options.lodBaseDistance;\n this.lodMultiplier = options.lodMultiplier;\n this.frustumAware = options.frustumAware !== false;\n this.budgetFillFraction = options.budgetFillFraction ?? 0.9;\n this.budgetFillCap = options.budgetFillCap ?? Number.POSITIVE_INFINITY;\n this.forceFinestWhenFits = options.forceFinestWhenFits !== false;\n this.forceFinestWithin = options.forceFinestWithin;\n this.fillPastDistance = options.fillPastDistance !== false;\n if (this.budgetFillFraction <= 0 || this.budgetFillFraction > 1) {\n throw new RangeError('LodScheduler budgetFillFraction must be in (0, 1].');\n }\n if (!(this.budgetFillCap > 0)) {\n throw new RangeError('LodScheduler budgetFillCap must be positive.');\n }\n if (this.forceFinestWithin !== undefined && !(this.forceFinestWithin >= 0)) {\n throw new RangeError('LodScheduler forceFinestWithin must be >= 0.');\n }\n\n const n = this.leaves.length;\n this.minLevel = new Int32Array(n);\n this.maxLevel = new Int32Array(n);\n this.level = new Int32Array(n).fill(this.coarsest);\n this.resolved = new Int32Array(n).fill(this.coarsest);\n this.changedAt = new Float64Array(n);\n this.distance = new Float64Array(n);\n this.inFrustum = new Uint8Array(n);\n this.screenImportance = new Float32Array(n);\n this.orderScratch = new Uint32Array(n);\n this.coverageGroups = new Int32Array(n).fill(-1);\n\n const groups: number[][] = [];\n const explicitGroups = new Map<number, number>();\n for (let i = 0; i < n; i++) {\n const key = (this.leaves[i] as LodLeaf).budgetGroup;\n if (key === undefined) {\n groups.push([i]);\n continue;\n }\n let groupIndex = explicitGroups.get(key);\n if (groupIndex === undefined) {\n groupIndex = groups.length;\n explicitGroups.set(key, groupIndex);\n groups.push([]);\n }\n (groups[groupIndex] as number[]).push(i);\n this.coverageGroups[i] = key;\n }\n this.budgetGroups = groups.map((members) => Uint32Array.from(members));\n\n for (let i = 0; i < n; i++) {\n const lods = (this.leaves[i] as LodLeaf).lods;\n let min = this.coarsest;\n let max = 0;\n for (let l = 0; l < lods.length; l++) {\n if (lods[l] === undefined) continue;\n min = Math.min(min, l);\n max = Math.max(max, l);\n }\n this.minLevel[i] = min;\n this.maxLevel[i] = max;\n this.level[i] = max; // start coarse\n }\n }\n\n /**\n * Recomputes the desired resident run set for the current camera.\n *\n * @param cameraLocal - Camera position in the mesh's local space.\n * @param frustum - View frustum in the mesh's local space.\n * @param now - Monotonic timestamp (ms) for dwell hysteresis.\n */\n computeDesiredRuns(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n cameraForward?: THREE.Vector3,\n ): LodRun[] {\n this.selectLevels(cameraLocal, frustum, now, cameraForward);\n // When the budget already holds every leaf's finest level, skip distance /\n // frustum demotion entirely. Otherwise a small rotate marks leaves\n // out-of-frustum (×{@link FRUSTUM_PENALTY}), coarsens them, and the mesh\n // aborts fine fetches / swaps to coarse LCC discs - then has to climb\n // back when they re-enter the view.\n if (this.forceFinestWhenFits && this.budget >= this.finestTotal()) {\n for (let i = 0; i < this.leaves.length; i++) {\n this.resolved[i] = this.minLevel[i] as number;\n }\n this.stats.desired = this.activeTotal();\n this.stats.filled = 0;\n return this.coalesceRuns();\n }\n // Copy the ambition into the resolved target, cap it to the budget, then\n // refill leftover headroom. Budget stages touch only `resolved`, never the\n // ambition/hysteresis state - a stationary camera therefore resolves the\n // same cut every pass instead of oscillating red↔orange.\n this.resolved.set(this.level);\n this.enforceBudget();\n const beforeFill = this.activeTotal();\n this.fillBudget();\n this.stats.desired = this.activeTotal();\n this.stats.filled = this.stats.desired - beforeFill;\n return this.coalesceRuns();\n }\n\n /** Total active splats implied by the current resolved levels. */\n private activeTotal(): number {\n let total = 0;\n for (let i = 0; i < this.leaves.length; i++) {\n const level = this.resolved[i] as number;\n if (level === DROPPED) continue;\n total += (this.leaves[i] as LodLeaf).lods[level]?.count ?? 0;\n }\n return total;\n }\n\n /** Splats if every leaf sat at its finest available level (none dropped). */\n private finestTotal(): number {\n let total = 0;\n for (let i = 0; i < this.leaves.length; i++) {\n const level = this.minLevel[i] as number;\n total += (this.leaves[i] as LodLeaf).lods[level]?.count ?? 0;\n }\n return total;\n }\n\n private selectLevels(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n cameraForward?: THREE.Vector3,\n ): void {\n const { lodBaseDistance: base, lodMultiplier: m } = this;\n this.hasScreenImportance = cameraForward !== undefined;\n let visibleCount = 0;\n this.stats.leaves = this.leaves.length;\n for (let i = 0; i < this.leaves.length; i++) {\n const leaf = this.leaves[i] as LodLeaf;\n const d = leaf.bounds.distanceToPoint(cameraLocal);\n this.distance[i] = d;\n\n // Widen the box by a fraction of its own size for edge stability.\n this.scratchBox.copy(leaf.bounds);\n leaf.bounds.getSize(this.scratchSize);\n this.scratchBox.expandByScalar(this.scratchSize.length() * FRUSTUM_MARGIN);\n const visible = frustum.intersectsBox(this.scratchBox);\n this.inFrustum[i] = visible ? 1 : 0;\n if (visible) visibleCount++;\n\n // Broad classic-LCC tiles frequently all intersect the frustum, so the\n // boolean above cannot tell the centre of the screen from its edges.\n // This is deliberately fetch metadata only: distance remains the source\n // of truth for the resolved LOD and the budget cut.\n if (cameraForward) {\n leaf.bounds.getCenter(this.scratchCenter).sub(cameraLocal);\n const depth = this.scratchCenter.dot(cameraForward);\n const lateralSquared = Math.max(0, this.scratchCenter.lengthSq() - depth * depth);\n this.screenImportance[i] =\n depth > 0 ? Math.sqrt(lateralSquared) / Math.max(depth, 0.25) : Number.POSITIVE_INFINITY;\n } else {\n this.screenImportance[i] = 0;\n }\n\n const current = this.level[i] as number;\n // Blanket force-finest only when fill may still climb past the distance\n // band (octree / page-table). Classic LCC never uses this path.\n if (\n this.fillPastDistance &&\n this.forceFinestWithin !== undefined &&\n d <= this.forceFinestWithin\n ) {\n const finest = this.minLevel[i] as number;\n if (current !== finest) {\n this.level[i] = finest;\n this.changedAt[i] = now;\n }\n continue;\n }\n\n const effectiveDistance = !this.frustumAware || visible ? d : d * FRUSTUM_PENALTY;\n\n // Classic LCC: the ambition tracks the pure distance band only. Budget\n // demotions live in `resolved`, never here, so they cannot feed back\n // into hysteresis. A cold cell snaps straight to its band (no dwell\n // climb through intermediate LODs); afterward only a band *crossing*\n // moves the ambition - a stationary camera in the same band holds,\n // even if the budget just demoted its resolved level.\n if (!this.fillPastDistance) {\n const band = this.distanceBandLevel(i, effectiveDistance, base, m);\n const coldStart =\n (this.changedAt[i] as number) === 0 && current === (this.maxLevel[i] as number);\n if (coldStart) {\n if (band !== current) {\n this.level[i] = band;\n this.changedAt[i] = now;\n }\n continue;\n }\n if (band === current) continue;\n const proposed = this.hysteresisLevel(i, effectiveDistance, base, m, current);\n if (proposed === current) continue;\n // Coarsen immediately (moving away should not linger over budget);\n // only refinement waits out the dwell window.\n const coarsening = proposed > current;\n if (coarsening || now - (this.changedAt[i] as number) >= DWELL_MS) {\n this.level[i] = proposed;\n this.changedAt[i] = now;\n }\n continue;\n }\n\n const proposed = this.hysteresisLevel(i, effectiveDistance, base, m, current);\n if (proposed === current) continue;\n const coldSnap =\n (this.changedAt[i] as number) === 0 && current === (this.maxLevel[i] as number);\n if (coldSnap || now - (this.changedAt[i] as number) >= DWELL_MS) {\n this.level[i] = proposed;\n this.changedAt[i] = now;\n }\n }\n this.stats.inFrustum = visibleCount;\n }\n\n /**\n * Pure distance→level band (independent of the leaf's current level), so a\n * classic LCC cut never has to dwell through intermediate rungs.\n */\n private distanceBandLevel(index: number, d: number, base: number, m: number): number {\n const min = this.minLevel[index] as number;\n const max = this.maxLevel[index] as number;\n let level = min;\n while (level < max && d > base * m ** level * (1 + THRESHOLD_MARGIN)) level++;\n return this.resolveAvailable(index, level);\n }\n\n /**\n * Stable level from distance: coarsen only past `threshold·(1 + margin)`,\n * refine only within `threshold·(1 − margin)`. The available-level clamp\n * keeps leaves that lack a level on their nearest coarser one.\n */\n private hysteresisLevel(\n index: number,\n d: number,\n base: number,\n m: number,\n current: number,\n ): number {\n const min = this.minLevel[index] as number;\n const max = this.maxLevel[index] as number;\n let level = Math.min(max, Math.max(min, current));\n // threshold(L) = base·m^L is the boundary between level L and L+1.\n while (level < max && d > base * m ** level * (1 + THRESHOLD_MARGIN)) level++;\n while (level > min && d <= base * m ** (level - 1) * (1 - THRESHOLD_MARGIN)) level--;\n // Resolve to the nearest available level (prefer coarser) if this exact\n // one has no data (leaves need not carry every level).\n return this.resolveAvailable(index, level);\n }\n\n private resolveAvailable(index: number, target: number): number {\n const lods = (this.leaves[index] as LodLeaf).lods;\n for (let l = target; l <= (this.maxLevel[index] as number); l++) if (lods[l]) return l;\n for (let l = target - 1; l >= (this.minLevel[index] as number); l--) if (lods[l]) return l;\n return this.maxLevel[index] as number;\n }\n\n /** Demotes/drops lowest-priority leaves until the total fits the budget. */\n private enforceBudget(): void {\n let total = this.activeTotal();\n if (total <= this.budget) return;\n\n // Demote lowest priority first. When frustum-aware: out-of-frustum before\n // in-frustum, then farthest. Classic LCC sets frustumAware false - broad\n // XY cells often sit mostly behind the camera while the user stands in\n // them (d≈0); frustum demotion would coarsen that cell first and let\n // thinner cells ahead keep fine detail (backwards load). Distance only.\n const order = this.orderScratch.subarray(0, this.budgetGroups.length);\n for (let i = 0; i < order.length; i++) order[i] = i;\n order.sort((a, b) => {\n const ia = (this.budgetGroups[a] as Uint32Array)[0] as number;\n const ib = (this.budgetGroups[b] as Uint32Array)[0] as number;\n if (this.frustumAware) {\n const fa = this.inFrustum[ia] as number;\n const fb = this.inFrustum[ib] as number;\n if (fa !== fb) return fa - fb; // out-of-frustum (0) first\n }\n return (this.distance[ib] as number) - (this.distance[ia] as number) || ia - ib;\n });\n\n // Pass 1: coarsen toward the coarsest available level.\n for (const groupIndex of order) {\n const group = this.budgetGroups[groupIndex] as Uint32Array;\n while (total > this.budget) {\n let reduction = 0;\n let changed = false;\n for (const i of group) {\n const current = this.resolved[i] as number;\n if (current >= (this.maxLevel[i] as number)) continue;\n const lods = (this.leaves[i] as LodLeaf).lods;\n const nextLevel = this.resolveAvailable(i, current + 1);\n if (nextLevel === current) continue;\n reduction += (lods[current]?.count ?? 0) - (lods[nextLevel]?.count ?? 0);\n this.resolved[i] = nextLevel;\n changed = true;\n }\n if (!changed) break;\n total -= reduction;\n }\n if (total <= this.budget) return;\n }\n\n // Pass 2 (last resort): drop groups entirely, lowest priority first.\n for (const groupIndex of order) {\n if (total <= this.budget) return;\n for (const i of this.budgetGroups[groupIndex] as Uint32Array) {\n if ((this.resolved[i] as number) === DROPPED) continue;\n total -= (this.leaves[i] as LodLeaf).lods[this.resolved[i] as number]?.count ?? 0;\n this.resolved[i] = DROPPED;\n }\n }\n }\n\n /**\n * Uses leftover budget to restore detail after {@link enforceBudget}\n * demoted leaves, nearest first, while the total stays under the fill\n * target. By default this may also refine *past* the distance-chosen level\n * when headroom remains (octree / page-table streams). With\n * {@link fillPastDistance} false (classic LCC), fill never goes finer than\n * each leaf's distance-selected level - so a cell at the L1 band does not\n * fetch L0 only to demote moments later with the camera still.\n */\n private fillBudget(): void {\n const target =\n this.forceFinestWhenFits && this.budget >= this.finestTotal()\n ? this.budget\n : Math.min(this.budget, this.budgetFillCap) * this.budgetFillFraction;\n let total = this.activeTotal();\n if (total >= target) return;\n\n // Candidate in-frustum groups, nearest first (ties keep manifest order),\n // packed into the front of the shared index scratch.\n let candidateCount = 0;\n for (let groupIndex = 0; groupIndex < this.budgetGroups.length; groupIndex++) {\n const first = (this.budgetGroups[groupIndex] as Uint32Array)[0] as number;\n if (\n (!this.frustumAware || this.inFrustum[first] === 1) &&\n (this.resolved[first] as number) !== DROPPED\n ) {\n this.orderScratch[candidateCount++] = groupIndex;\n }\n }\n const order = this.orderScratch.subarray(0, candidateCount);\n order.sort((a, b) => {\n const ia = (this.budgetGroups[a] as Uint32Array)[0] as number;\n const ib = (this.budgetGroups[b] as Uint32Array)[0] as number;\n return (this.distance[ia] as number) - (this.distance[ib] as number) || ia - ib;\n });\n\n // Nearest groups refine first (up to the distance floor when capped).\n for (const groupIndex of order) {\n const group = this.budgetGroups[groupIndex] as Uint32Array;\n while (true) {\n let growth = 0;\n let canPromote = false;\n for (const i of group) {\n const current = this.resolved[i] as number;\n const floor = this.fillPastDistance\n ? (this.minLevel[i] as number)\n : (this.level[i] as number);\n if (current <= floor) continue;\n const leaf = this.leaves[i] as LodLeaf;\n let finer = current - 1;\n while (finer > floor && !leaf.lods[finer]) finer--;\n if (finer < floor || !leaf.lods[finer]) continue;\n growth += (leaf.lods[finer]?.count ?? 0) - (leaf.lods[current]?.count ?? 0);\n canPromote = true;\n }\n if (!canPromote || total + growth > target) break;\n for (const i of group) {\n const current = this.resolved[i] as number;\n const floor = this.fillPastDistance\n ? (this.minLevel[i] as number)\n : (this.level[i] as number);\n if (current <= floor) continue;\n const leaf = this.leaves[i] as LodLeaf;\n let finer = current - 1;\n while (finer > floor && !leaf.lods[finer]) finer--;\n if (finer >= floor && leaf.lods[finer]) this.resolved[i] = finer;\n }\n total += growth;\n }\n }\n }\n\n private coalesceRuns(): LodRun[] {\n return this.buildRuns(0, this.leaves.length, (i) => this.resolved[i] as number);\n }\n\n /**\n * Runs covering [from, to) at each leaf's coarsest available level - used\n * to substitute always-cached coverage while a finer level is fetching.\n */\n coarsestRunsFor(from: number, to: number): LodRun[] {\n return this.buildRuns(from, to, (i) => this.maxLevel[i] as number);\n }\n\n /**\n * Runs covering [from, to) at `level`, clamped per leaf to an available rung\n * (prefer the requested level, else the next coarser, else the next finer).\n */\n runsAtLevelFor(from: number, to: number, level: number): LodRun[] {\n const wanted = Math.floor(level);\n return this.buildRuns(from, to, (i) => this.clampLeafLevel(i, wanted));\n }\n\n /** Prefer `wanted`, else next coarser, else next finer; {@link DROPPED} if none. */\n private clampLeafLevel(leafIndex: number, wanted: number): number {\n const leaf = this.leaves[leafIndex] as LodLeaf;\n if (leaf.lods[wanted]) return wanted;\n for (let l = wanted + 1; l < leaf.lods.length; l++) {\n if (leaf.lods[l]) return l;\n }\n for (let l = wanted - 1; l >= 0; l--) {\n if (leaf.lods[l]) return l;\n }\n return DROPPED;\n }\n\n /** Coalesces leaves [from, to) at `levelOf(leaf)` into contiguous runs. */\n private buildRuns(from: number, to: number, levelOf: (index: number) => number): LodRun[] {\n const runs: LodRun[] = [];\n let file = -1;\n let level = -1;\n let offset = 0;\n let count = 0;\n let leafStart = -1;\n let leafEnd = -1;\n let coverageGroup = -1;\n let distance = Number.POSITIVE_INFINITY;\n let inView = false;\n let screenImportance = Number.POSITIVE_INFINITY;\n const flush = (): void => {\n if (count > 0) {\n runs.push({\n file,\n level,\n offset,\n count,\n leafStart,\n leafEnd,\n distance,\n inView,\n ...(this.hasScreenImportance ? { screenImportance } : {}),\n ...(coverageGroup >= 0 ? { coverageGroup } : {}),\n });\n }\n count = 0;\n };\n\n for (let i = from; i < to; i++) {\n const l = levelOf(i);\n if (l === DROPPED) {\n flush();\n continue;\n }\n const range = (this.leaves[i] as LodLeaf).lods[l];\n if (!range) {\n flush();\n continue;\n }\n const leafCoverageGroup = this.coverageGroups[i] as number;\n const leafDistance = this.distance[i] as number;\n const leafInView = (this.inFrustum[i] as number) === 1;\n if (\n count > 0 &&\n range.file === file &&\n l === level &&\n leafCoverageGroup === coverageGroup &&\n range.offset === offset + count &&\n count + range.count <= MAX_RUN_SPLATS\n ) {\n count += range.count; // extend the current run\n leafEnd = i + 1;\n if (leafDistance < distance) distance = leafDistance;\n if (leafInView) inView = true;\n screenImportance = Math.min(screenImportance, this.screenImportance[i] as number);\n } else {\n flush();\n file = range.file;\n level = l;\n offset = range.offset;\n count = range.count;\n leafStart = i;\n leafEnd = i + 1;\n coverageGroup = leafCoverageGroup;\n distance = leafDistance;\n inView = leafInView;\n screenImportance = this.screenImportance[i] as number;\n }\n }\n flush();\n return runs;\n }\n}\n"],"names":["runKey","run","DROPPED","MAX_RUN_SPLATS","THRESHOLD_MARGIN","DWELL_MS","FRUSTUM_MARGIN","FRUSTUM_PENALTY","LodScheduler","manifest","options","__publicField","THREE","n","groups","explicitGroups","i","key","groupIndex","members","lods","min","max","l","cameraLocal","frustum","now","cameraForward","beforeFill","total","level","_a","base","m","visibleCount","leaf","d","visible","depth","lateralSquared","current","finest","effectiveDistance","band","proposed","index","target","order","a","b","ia","ib","fa","fb","group","reduction","changed","nextLevel","_b","_c","candidateCount","first","growth","canPromote","floor","finer","from","to","wanted","leafIndex","levelOf","runs","file","offset","count","leafStart","leafEnd","coverageGroup","distance","inView","screenImportance","flush","range","leafCoverageGroup","leafDistance","leafInView"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"radix-sorter-R0FCSqKu.js","sources":["../src/lib/radix-sort.ts","../src/lib/radix-sorter.ts"],"sourcesContent":["/**\n * @role Utility\n * Shared constants and deterministic reference logic for GPU radix sorting.\n */\n\n/** Depth precision retained by both the GPU radix path and WebGL worker path. */\nexport const RADIX_KEY_BITS = 24;\n/** Exact Float32 depth keys use every sortable IEEE-754 bit. */\nexport const RADIX_EXACT_KEY_BITS = 32;\n/** Four-bit digits keep the portable shader's workgroup memory requirements small. */\nexport const RADIX_BITS_PER_PASS = 4;\nexport const RADIX_BUCKETS = 1 << RADIX_BITS_PER_PASS;\nexport const RADIX_PASS_COUNT = RADIX_KEY_BITS / RADIX_BITS_PER_PASS;\nexport const RADIX_KEY_MAX = (1 << RADIX_KEY_BITS) - 1;\n\n/** Number of four-bit passes required for one unsigned radix key width. */\nexport function radixPassCount(keyBits: number): number {\n if (!Number.isInteger(keyBits) || keyBits <= 0 || keyBits % RADIX_BITS_PER_PASS !== 0) {\n throw new RangeError('Radix key width must be a positive multiple of four bits.');\n }\n return keyBits / RADIX_BITS_PER_PASS;\n}\n\n/**\n * Maps a Float32 bit pattern onto unsigned integer order without losing depth\n * precision. Ascending mapped keys are ascending numeric floats, including\n * negative view-space Z values (farther splats first in VLAM's convention).\n */\nexport function sortableFloat32Bits(bits: number): number {\n const value = bits >>> 0;\n return (value ^ ((value & 0x80000000) === 0 ? 0x80000000 : 0xffffffff)) >>> 0;\n}\n\n/** Quantizes a depth into the unsigned 24-bit key used by the GPU sorter. */\nexport function quantizeDepthKey(depth: number, minimum: number, maximum: number): number {\n const span = maximum - minimum;\n if (!Number.isFinite(depth) || !Number.isFinite(span) || span <= 0) return 0;\n return Math.min(\n RADIX_KEY_MAX,\n Math.max(0, Math.round(((depth - minimum) / span) * RADIX_KEY_MAX)),\n );\n}\n\n/**\n * Stable CPU reference for validating the GPU's six four-bit radix passes.\n * This is deliberately test/debug-only logic, not the WebGL production sorter.\n */\nexport function stableRadixSortReference(\n keys: Uint32Array,\n values?: Uint32Array,\n keyBits = RADIX_KEY_BITS,\n): Uint32Array {\n const count = keys.length;\n let inputKeys = Uint32Array.from(keys);\n let outputKeys = new Uint32Array(count);\n let inputValues = values ? Uint32Array.from(values) : Uint32Array.from(keys.keys());\n let outputValues = new Uint32Array(count);\n const counts = new Uint32Array(RADIX_BUCKETS);\n const offsets = new Uint32Array(RADIX_BUCKETS);\n\n for (let pass = 0; pass < radixPassCount(keyBits); pass++) {\n counts.fill(0);\n const shift = pass * RADIX_BITS_PER_PASS;\n for (let i = 0; i < count; i++) {\n const digit = ((inputKeys[i] as number) >>> shift) & 0xf;\n counts[digit] = (counts[digit] as number) + 1;\n }\n let total = 0;\n for (let digit = 0; digit < RADIX_BUCKETS; digit++) {\n offsets[digit] = total;\n total += counts[digit] as number;\n }\n for (let i = 0; i < count; i++) {\n const key = inputKeys[i] as number;\n const digit = (key >>> shift) & 0xf;\n const target = offsets[digit] as number;\n offsets[digit] = target + 1;\n outputKeys[target] = key;\n outputValues[target] = inputValues[i] as number;\n }\n [inputKeys, outputKeys] = [outputKeys, inputKeys];\n [inputValues, outputValues] = [outputValues, inputValues];\n }\n return inputValues;\n}\n","/**\n * @role Bridge\n * Portable stable WebGPU radix sorting adapted to Three.js TSL compute nodes.\n */\nimport * as THREE from 'three/webgpu';\nimport {\n Fn,\n If,\n Loop,\n atomicAdd,\n atomicLoad,\n atomicOr,\n atomicStore,\n countOneBits,\n float,\n floatBitsToUint,\n instanceIndex,\n int,\n invocationLocalIndex,\n ivec2,\n storage,\n storageBarrier,\n textureLoad,\n uint,\n uniform,\n workgroupArray,\n workgroupBarrier,\n workgroupId,\n} from 'three/tsl';\nimport type { SplatSorter } from './sorter';\nimport { releaseRendererAttributes } from './compute-sorter';\nimport {\n RADIX_BITS_PER_PASS,\n RADIX_EXACT_KEY_BITS,\n RADIX_KEY_BITS,\n RADIX_KEY_MAX,\n radixPassCount,\n} from './radix-sort';\nimport { viewDepthRadius } from './splat-sort-bounds';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\n\nconst WORKGROUP_SIZE = 256;\nconst ELEMENTS_PER_THREAD = 8;\nconst ELEMENTS_PER_WORKGROUP = WORKGROUP_SIZE * ELEMENTS_PER_THREAD;\nconst DIGIT_COUNT = 16;\nconst MASK_WORDS_PER_DIGIT = WORKGROUP_SIZE / 32;\nconst MASK_WORDS_PER_GROUP = DIGIT_COUNT * MASK_WORDS_PER_DIGIT;\nconst SCAN_BLOCK_SIZE = 256;\n\ntype UintWorkgroupArray = {\n element(index: THREE.Node<'uint'>): THREE.Node<'uint'>;\n};\n\n/** Isolates gaps in Three's current WorkgroupInfo/Bitcount TypeScript declarations. */\nfunction asUintNode(node: unknown): THREE.Node<'uint'> {\n return node as THREE.Node<'uint'>;\n}\n\n/**\n * Stable four-bit-per-pass WebGPU radix sorter.\n *\n * The ranked scatter follows PlayCanvas/WebGPU-Radix-Sort's per-digit bitmask\n * design. Three.js does not currently expose atomic workgroup arrays in TSL,\n * so masks use disjoint global-storage slices per workgroup. Workgroup and\n * storage barriers retain the same stable ranking semantics without relying\n * on cross-workgroup execution order.\n */\nexport class RadixSorter implements SplatSorter {\n readonly kind = 'radix' as const;\n private readonly renderer: THREE.WebGPURenderer;\n private readonly stages: THREE.ComputeNode[] = [];\n private readonly workingAttributes: THREE.StorageBufferAttribute[];\n /** Frees the JS mirrors three keeps behind the GPU-only ping-pong buffers. */\n private readonly mirrors: StorageMirrorReleaser;\n /** Set by {@link dispose}; makes a second dispose a no-op. */\n private disposed = false;\n private readonly viewRow2 = uniform(new THREE.Vector4());\n private readonly depthMin = uniform(0);\n private readonly depthScale = uniform(0);\n private readonly activeCount = uniform(0);\n private readonly viewCenter = new THREE.Vector3();\n /** Exact mode avoids scene-bounds quantization entirely. */\n private readonly exactDepth: boolean;\n\n constructor(options: {\n renderer: THREE.WebGPURenderer;\n capacity: number;\n centersTexture?: THREE.DataTexture;\n dataTextureWidth?: number;\n /** Gathered world-space centers for a unified renderer. */\n centersBuffer?: THREE.StorageBufferAttribute;\n splatIndexAttribute: THREE.StorageInstancedBufferAttribute;\n sourceIndexAttribute: THREE.StorageBufferAttribute;\n /** Keep every Float32 depth bit instead of quantizing to 24 bits. */\n exactDepth?: boolean;\n }) {\n const { capacity, centersTexture, dataTextureWidth, centersBuffer } = options;\n if (!centersTexture && !centersBuffer) {\n throw new Error('RadixSorter: provide centersTexture or centersBuffer.');\n }\n if (centersTexture && dataTextureWidth === undefined) {\n throw new Error('RadixSorter: dataTextureWidth is required with centersTexture.');\n }\n this.renderer = options.renderer;\n const exactDepth = options.exactDepth === true;\n this.exactDepth = exactDepth;\n const passCount = radixPassCount(exactDepth ? RADIX_EXACT_KEY_BITS : RADIX_KEY_BITS);\n const groupCount = Math.ceil(capacity / ELEMENTS_PER_WORKGROUP);\n const histogramLength = DIGIT_COUNT * groupCount;\n const scanBlockCount = Math.ceil(histogramLength / SCAN_BLOCK_SIZE);\n\n const keysAAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const keysBAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const valuesAAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const valuesBAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const histogramAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(histogramLength),\n 1,\n );\n const scanSumsAttribute = new THREE.StorageBufferAttribute(new Uint32Array(scanBlockCount), 1);\n const masksAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(groupCount * MASK_WORDS_PER_GROUP),\n 1,\n );\n this.workingAttributes = [\n keysAAttribute,\n keysBAttribute,\n valuesAAttribute,\n valuesBAttribute,\n histogramAttribute,\n scanSumsAttribute,\n masksAttribute,\n ];\n this.mirrors = new StorageMirrorReleaser(this.workingAttributes);\n\n const keysA = storage(keysAAttribute, 'uint', capacity);\n const keysB = storage(keysBAttribute, 'uint', capacity);\n const valuesA = storage(valuesAAttribute, 'uint', capacity);\n const valuesB = storage(valuesBAttribute, 'uint', capacity);\n const source = storage(options.sourceIndexAttribute, 'uint', capacity);\n const order = storage(options.splatIndexAttribute, 'float', capacity);\n const workCenters = centersBuffer ? storage(centersBuffer, 'vec4', capacity) : null;\n const histogram = storage(histogramAttribute, 'uint', histogramLength).toAtomic();\n const scanSums = storage(scanSumsAttribute, 'uint', scanBlockCount);\n const masks = storage(masksAttribute, 'uint', groupCount * MASK_WORDS_PER_GROUP).toAtomic();\n\n const buildKeys = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = source.element(instanceIndex);\n const texel = centersTexture\n ? ivec2(\n int(poolIndex).mod(int(dataTextureWidth as number)),\n int(poolIndex).div(int(dataTextureWidth as number)),\n )\n : null;\n const center = workCenters\n ? workCenters.element(poolIndex).xyz\n : textureLoad(centersTexture, texel as THREE.Node<'ivec2'>).xyz;\n const depth = this.viewRow2.xyz.dot(center).add(this.viewRow2.w);\n if (exactDepth) {\n // IEEE-754 bits are monotonic only for positive values. Flip the\n // sign partition so ascending unsigned keys remain ascending numeric\n // depth, including VLAM's negative view-space Z convention.\n // Three's BitcastNode typings omit shift/xor; runtime nodes support them.\n const bits = asUintNode(floatBitsToUint(depth));\n const mask = bits\n .shiftRight(uint(31))\n .equal(uint(0))\n .select(uint(0x80000000), uint(0xffffffff));\n keysA.element(instanceIndex).assign(bits.bitXor(mask));\n } else {\n keysA\n .element(instanceIndex)\n .assign(depth.sub(this.depthMin).mul(this.depthScale).clamp(0, RADIX_KEY_MAX).toUint());\n }\n valuesA.element(instanceIndex).assign(poolIndex);\n });\n })().compute(capacity, [WORKGROUP_SIZE]);\n this.stages.push(buildKeys);\n\n for (let pass = 0; pass < passCount; pass++) {\n const inputKeys = pass % 2 === 0 ? keysA : keysB;\n const outputKeys = pass % 2 === 0 ? keysB : keysA;\n const inputValues = pass % 2 === 0 ? valuesA : valuesB;\n const outputValues = pass % 2 === 0 ? valuesB : valuesA;\n const bit = uint(pass * RADIX_BITS_PER_PASS);\n\n const clearHistogram = Fn(() => {\n atomicStore(histogram.element(instanceIndex), uint(0));\n })().compute(histogramLength, [WORKGROUP_SIZE]);\n\n const buildHistogram = Fn(() => {\n const base = workgroupId.x.mul(uint(ELEMENTS_PER_WORKGROUP));\n Loop(ELEMENTS_PER_THREAD, ({ i }) => {\n const index = base.add(uint(i).mul(uint(WORKGROUP_SIZE))).add(invocationLocalIndex);\n If(float(index).lessThan(this.activeCount), () => {\n const digit = inputKeys.element(index).shiftRight(bit).bitAnd(uint(0xf));\n atomicAdd(histogram.element(digit.mul(uint(groupCount)).add(workgroupId.x)), uint(1));\n });\n });\n })().compute(groupCount * WORKGROUP_SIZE, [WORKGROUP_SIZE]);\n\n const scanBlocks = Fn(() => {\n const start = instanceIndex.mul(uint(SCAN_BLOCK_SIZE));\n const running = uint(0).toVar();\n Loop(SCAN_BLOCK_SIZE, ({ i }) => {\n const index = start.add(uint(i));\n If(index.lessThan(uint(histogramLength)), () => {\n const value = atomicLoad(histogram.element(index)).toVar();\n atomicStore(histogram.element(index), running);\n running.addAssign(value);\n });\n });\n scanSums.element(instanceIndex).assign(running);\n })().compute(scanBlockCount, [64]);\n\n const scanBlockSums = Fn(() => {\n If(instanceIndex.equal(uint(0)), () => {\n const running = uint(0).toVar();\n Loop(scanBlockCount, ({ i }) => {\n const value = scanSums.element(i).toVar();\n scanSums.element(i).assign(running);\n running.addAssign(value);\n });\n });\n })().compute(1, [64]);\n\n const addBlockOffsets = Fn(() => {\n const block = instanceIndex.div(uint(SCAN_BLOCK_SIZE));\n atomicStore(\n histogram.element(instanceIndex),\n atomicLoad(histogram.element(instanceIndex)).add(scanSums.element(block)),\n );\n })().compute(histogramLength, [WORKGROUP_SIZE]);\n\n const digitOffsets = workgroupArray('uint', DIGIT_COUNT) as unknown as UintWorkgroupArray;\n const rankedScatter = Fn(() => {\n const thread = invocationLocalIndex;\n const group = workgroupId.x;\n const maskBase = group.mul(uint(MASK_WORDS_PER_GROUP));\n If(thread.lessThan(uint(DIGIT_COUNT)), () => {\n digitOffsets.element(thread).assign(uint(0));\n });\n If(thread.lessThan(uint(MASK_WORDS_PER_GROUP)), () => {\n atomicStore(masks.element(maskBase.add(thread)), uint(0));\n });\n storageBarrier();\n workgroupBarrier();\n\n Loop(ELEMENTS_PER_THREAD, ({ i }) => {\n const index = group\n .mul(uint(ELEMENTS_PER_WORKGROUP))\n .add(i.toUint().mul(uint(WORKGROUP_SIZE)))\n .add(thread);\n const valid = float(index).lessThan(this.activeCount);\n const key = uint(0).toVar();\n const value = uint(0).toVar();\n const digit = uint(0).toVar();\n If(valid, () => {\n key.assign(inputKeys.element(index));\n value.assign(inputValues.element(index));\n digit.assign(key.shiftRight(bit).bitAnd(uint(0xf)));\n const word = thread.shiftRight(uint(5));\n const bitInWord = thread.bitAnd(uint(31));\n const maskIndex = maskBase.add(digit.mul(uint(MASK_WORDS_PER_DIGIT))).add(word);\n atomicOr(masks.element(maskIndex), uint(1).shiftLeft(bitInWord));\n });\n storageBarrier();\n workgroupBarrier();\n\n If(valid, () => {\n const word = thread.shiftRight(uint(5));\n const bitInWord = thread.bitAnd(uint(31));\n const digitBase = maskBase.add(digit.mul(uint(MASK_WORDS_PER_DIGIT)));\n const localRank = digitOffsets.element(digit).toVar();\n Loop(MASK_WORDS_PER_DIGIT, ({ i: wordIndex }) => {\n If(uint(wordIndex).lessThan(word), () => {\n localRank.addAssign(\n asUintNode(\n countOneBits(atomicLoad(masks.element(digitBase.add(wordIndex.toUint())))),\n ),\n );\n });\n });\n const precedingMask = uint(1).shiftLeft(bitInWord).sub(uint(1));\n localRank.addAssign(\n asUintNode(\n countOneBits(atomicLoad(masks.element(digitBase.add(word))).bitAnd(precedingMask)),\n ),\n );\n const prefix = atomicLoad(histogram.element(digit.mul(uint(groupCount)).add(group)));\n const target = prefix.add(localRank);\n outputKeys.element(target).assign(key);\n outputValues.element(target).assign(value);\n });\n\n storageBarrier();\n workgroupBarrier();\n If(thread.lessThan(uint(DIGIT_COUNT)), () => {\n const total = uint(0).toVar();\n Loop(MASK_WORDS_PER_DIGIT, ({ i: wordIndex }) => {\n const maskIndex = maskBase\n .add(thread.mul(uint(MASK_WORDS_PER_DIGIT)))\n .add(wordIndex.toUint());\n total.addAssign(asUintNode(countOneBits(atomicLoad(masks.element(maskIndex)))));\n atomicStore(masks.element(maskIndex), uint(0));\n });\n digitOffsets.element(thread).addAssign(total);\n });\n storageBarrier();\n workgroupBarrier();\n });\n })().compute(groupCount * WORKGROUP_SIZE, [WORKGROUP_SIZE]);\n\n this.stages.push(\n clearHistogram,\n buildHistogram,\n scanBlocks,\n scanBlockSums,\n addBlockOffsets,\n rankedScatter,\n );\n }\n\n const writeOrder = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n order.element(instanceIndex).assign(float(valuesA.element(instanceIndex)));\n });\n })().compute(capacity, [WORKGROUP_SIZE]);\n this.stages.push(writeOrder);\n }\n\n sort(modelView: THREE.Matrix4, activeCount: number, bounds: THREE.Sphere): boolean {\n if (activeCount === 0) return true;\n const m = modelView.elements;\n this.viewRow2.value.set(m[2], m[6], m[10], m[14]);\n this.activeCount.value = activeCount;\n if (!this.exactDepth) {\n this.viewCenter.copy(bounds.center).applyMatrix4(modelView);\n const viewRadius = viewDepthRadius(modelView, bounds.radius);\n const minimum = this.viewCenter.z - viewRadius;\n const maximum = this.viewCenter.z + viewRadius;\n this.depthMin.value = minimum;\n this.depthScale.value = RADIX_KEY_MAX / (maximum - minimum || 1);\n }\n this.stages[0]!.count = activeCount;\n this.stages[this.stages.length - 1]!.count = activeCount;\n this.renderer.compute(this.stages);\n // Key/value ping-pong, histogram, scan sums and masks are all written and\n // read entirely on the GPU - at 4 B per splat of capacity each, their JS\n // mirrors are ~16 B/splat of pure waste. Deliberately *not*\n // `sourceIndex`/`splatIndex`: those belong to the mesh and are rewritten\n // from the CPU every frame. Same ownership line as `releaseRendererAttributes`.\n if (!this.mirrors.settled) this.mirrors.release(this.renderer);\n return true;\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n for (const stage of this.stages) stage.dispose();\n releaseRendererAttributes(this.renderer, this.workingAttributes);\n }\n}\n"],"names":["RADIX_KEY_BITS","RADIX_EXACT_KEY_BITS","RADIX_BITS_PER_PASS","RADIX_KEY_MAX","radixPassCount","keyBits","WORKGROUP_SIZE","ELEMENTS_PER_THREAD","ELEMENTS_PER_WORKGROUP","DIGIT_COUNT","MASK_WORDS_PER_DIGIT","MASK_WORDS_PER_GROUP","SCAN_BLOCK_SIZE","RadixSorter","options","__publicField","uniform","THREE","capacity","centersTexture","dataTextureWidth","centersBuffer","exactDepth","passCount","groupCount","histogramLength","scanBlockCount","keysAAttribute","keysBAttribute","valuesAAttribute","valuesBAttribute","histogramAttribute","scanSumsAttribute","masksAttribute","StorageMirrorReleaser","keysA","storage","keysB","valuesA","valuesB","source","order","workCenters","histogram","scanSums","masks","buildKeys","Fn","If","float","instanceIndex","poolIndex","texel","ivec2","int","center","textureLoad","depth","bits","floatBitsToUint","mask","uint","pass","inputKeys","outputKeys","inputValues","outputValues","bit","clearHistogram","atomicStore","buildHistogram","base","workgroupId","Loop","i","index","invocationLocalIndex","digit","atomicAdd","scanBlocks","start","running","value","atomicLoad","scanBlockSums","addBlockOffsets","block","digitOffsets","workgroupArray","rankedScatter","thread","group","maskBase","storageBarrier","workgroupBarrier","valid","key","word","bitInWord","maskIndex","atomicOr","digitBase","localRank","wordIndex","countOneBits","precedingMask","target","total","writeOrder","modelView","activeCount","bounds","m","viewRadius","viewDepthRadius","minimum","maximum","stage","releaseRendererAttributes"],"mappings":"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"}