@voluma/vlam 0.2.4 → 0.3.2

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 (138) hide show
  1. package/README.md +3 -4
  2. package/dist/chunk-loader-DlNwggfG.js +1859 -0
  3. package/dist/chunk-loader-DlNwggfG.js.map +1 -0
  4. package/dist/{compute-sorter.d.ts → core/compute-sorter.d.ts} +1 -1
  5. package/dist/core/index.d.ts +45 -0
  6. package/dist/{splat-scene.d.ts → core/merged-splat-mesh.d.ts} +14 -15
  7. package/dist/{orientation.d.ts → core/orientation.d.ts} +7 -7
  8. package/dist/{relighting.d.ts → core/relighting.d.ts} +1 -1
  9. package/dist/{source-transform.d.ts → core/source-transform.d.ts} +4 -4
  10. package/dist/{splat-budget.d.ts → core/splat-budget.d.ts} +2 -2
  11. package/dist/{splat-data.d.ts → core/splat-data.d.ts} +4 -4
  12. package/dist/{splat-mesh-material.d.ts → core/splat-mesh-material.d.ts} +2 -2
  13. package/dist/{splat-mesh-types.d.ts → core/splat-mesh-types.d.ts} +19 -10
  14. package/dist/{splat-mesh.d.ts → core/splat-mesh.d.ts} +4 -4
  15. package/dist/{splat-modifier.d.ts → core/splat-modifier.d.ts} +5 -5
  16. package/dist/{webgpu-limits.d.ts → core/webgpu-limits.d.ts} +0 -59
  17. package/dist/{effects.d.ts → effects/index.d.ts} +14 -13
  18. package/dist/effects.js.map +1 -1
  19. package/dist/formats/ksplat/index.d.ts +1 -1
  20. package/dist/formats/ksplat/parse-ksplat.d.ts +1 -1
  21. package/dist/formats/ksplat.js.map +1 -1
  22. package/dist/formats/lcc/collision-mesh.d.ts +3 -3
  23. package/dist/formats/lcc/collision-partition.d.ts +1 -1
  24. package/dist/formats/lcc/lcc.d.ts +3 -3
  25. package/dist/formats/lcc/lcc2.d.ts +2 -2
  26. package/dist/formats/lcc/parse-lcc.d.ts +1 -1
  27. package/dist/formats/lcc.js +17 -16
  28. package/dist/formats/lcc.js.map +1 -1
  29. package/dist/formats/ply/index.d.ts +1 -1
  30. package/dist/formats/ply/parse-compressed-ply.d.ts +2 -2
  31. package/dist/formats/ply/parse-splat-ply.d.ts +4 -4
  32. package/dist/formats/ply.js +1 -1
  33. package/dist/formats/ply.js.map +1 -1
  34. package/dist/formats/rad/frontier-worker-protocol.d.ts +8 -1
  35. package/dist/formats/rad/index.d.ts +2 -2
  36. package/dist/formats/rad/parse-rad.d.ts +2 -2
  37. package/dist/formats/rad/rad-foveated-source.d.ts +3 -3
  38. package/dist/formats/rad/rad.d.ts +7 -7
  39. package/dist/formats/rad.js +3 -3
  40. package/dist/formats/rad.js.map +1 -1
  41. package/dist/formats/sog/index.d.ts +1 -1
  42. package/dist/formats/sog/parse-sog.d.ts +6 -6
  43. package/dist/formats/sog.js.map +1 -1
  44. package/dist/formats/splat/index.d.ts +1 -1
  45. package/dist/formats/splat/parse-splat.d.ts +1 -1
  46. package/dist/formats/splat.js.map +1 -1
  47. package/dist/formats/spz/index.d.ts +1 -1
  48. package/dist/formats/spz/parse-spz.d.ts +1 -1
  49. package/dist/formats/spz.js.map +1 -1
  50. package/dist/half-float-Cs4FDq8Q.js.map +1 -1
  51. package/dist/index.js +253 -9047
  52. package/dist/index.js.map +1 -1
  53. package/dist/{chunk-loader.d.ts → loaders/chunk-loader.d.ts} +4 -4
  54. package/dist/loaders/index.d.ts +15 -0
  55. package/dist/{load-scene.d.ts → loaders/load-splat-data.d.ts} +16 -14
  56. package/dist/{load-worker-protocol.d.ts → loaders/load-worker-protocol.d.ts} +2 -2
  57. package/dist/{loading.d.ts → loaders/loading.d.ts} +8 -8
  58. package/dist/loaders.js +26 -0
  59. package/dist/loaders.js.map +1 -0
  60. package/dist/loading-wLo8vRbA.js.map +1 -1
  61. package/dist/{splat-mesh-types-CTN_duw2.js → lod-scheduler-B0a_uBlv.js} +133 -140
  62. package/dist/lod-scheduler-B0a_uBlv.js.map +1 -0
  63. package/dist/logging-BfPdd7NJ.js.map +1 -1
  64. package/dist/{ply-header-BwgExCn-.js → ply-header-B9WllbPg.js} +3 -3
  65. package/dist/ply-header-B9WllbPg.js.map +1 -0
  66. package/dist/radix-sorter-BrbUg_CV.js +178 -0
  67. package/dist/radix-sorter-BrbUg_CV.js.map +1 -0
  68. package/dist/relighting-Tiwep8yd.js +977 -0
  69. package/dist/relighting-Tiwep8yd.js.map +1 -0
  70. package/dist/selection/index.d.ts +13 -0
  71. package/dist/{selection-volume.d.ts → selection/selection-volume.d.ts} +1 -1
  72. package/dist/{splat-partition.d.ts → selection/splat-partition.d.ts} +2 -2
  73. package/dist/selection.js +137 -0
  74. package/dist/selection.js.map +1 -0
  75. package/dist/sh-pack-D5wAe5gg.js.map +1 -1
  76. package/dist/{splat-budget-DXew_86c.js → splat-budget-DuVZspPQ.js} +7 -7
  77. package/dist/splat-budget-DuVZspPQ.js.map +1 -0
  78. package/dist/splat-data-BZM5dhhx.js.map +1 -1
  79. package/dist/splat-mesh-CsLOQb08.js +1942 -0
  80. package/dist/splat-mesh-CsLOQb08.js.map +1 -0
  81. package/dist/splat-mesh-types-BZIko-_9.js +18 -0
  82. package/dist/splat-mesh-types-BZIko-_9.js.map +1 -0
  83. package/dist/static-lod/index.d.ts +12 -0
  84. package/dist/{static-lod-splat-mesh.d.ts → static-lod/static-lod-splat-mesh.d.ts} +3 -3
  85. package/dist/{static-lod-worker-protocol.d.ts → static-lod/static-lod-worker-protocol.d.ts} +1 -1
  86. package/dist/{static-lod.d.ts → static-lod/static-lod.d.ts} +1 -1
  87. package/dist/{static-lod-worker-D2fyqQZO.js → static-lod-worker-DktKnJkR.js} +46 -46
  88. package/dist/{static-lod-worker-D2fyqQZO.js.map → static-lod-worker-DktKnJkR.js.map} +1 -1
  89. package/dist/static-lod.js +148 -0
  90. package/dist/static-lod.js.map +1 -0
  91. package/dist/{budget-governor.d.ts → streaming/budget-governor.d.ts} +4 -4
  92. package/dist/{camera-budget-governor.d.ts → streaming/camera-budget-governor.d.ts} +11 -11
  93. package/dist/{chunk-cache-budget.d.ts → streaming/chunk-cache-budget.d.ts} +5 -5
  94. package/dist/{chunk-fetch-scheduler.d.ts → streaming/chunk-fetch-scheduler.d.ts} +4 -4
  95. package/dist/{dataset-source.d.ts → streaming/dataset-source.d.ts} +1 -1
  96. package/dist/streaming/index.d.ts +23 -0
  97. package/dist/{lod-scheduler.d.ts → streaming/lod-scheduler.d.ts} +1 -1
  98. package/dist/{lod-source.d.ts → streaming/lod-source.d.ts} +9 -9
  99. package/dist/{streamed-splat-mesh-utils.d.ts → streaming/streamed-splat-mesh-utils.d.ts} +2 -2
  100. package/dist/{streamed-splat-mesh.d.ts → streaming/streamed-splat-mesh.d.ts} +66 -32
  101. package/dist/streaming.js +2911 -0
  102. package/dist/streaming.js.map +1 -0
  103. package/dist/unified/index.d.ts +11 -0
  104. package/dist/{unified-splat-renderer.d.ts → unified/unified-splat-mesh.d.ts} +23 -16
  105. package/dist/unified/unified-work-buffer.d.ts +66 -0
  106. package/dist/{work-buffer-gather.d.ts → unified/work-buffer-gather.d.ts} +6 -6
  107. package/dist/{work-buffer-material.d.ts → unified/work-buffer-material.d.ts} +1 -1
  108. package/dist/unified.js +746 -0
  109. package/dist/unified.js.map +1 -0
  110. package/dist/webgpu-limits-Y3CI_6_6.js +43 -0
  111. package/dist/webgpu-limits-Y3CI_6_6.js.map +1 -0
  112. package/package.json +27 -7
  113. package/dist/index.d.ts +0 -54
  114. package/dist/ply-header-BwgExCn-.js.map +0 -1
  115. package/dist/splat-budget-DXew_86c.js.map +0 -1
  116. package/dist/splat-mesh-types-CTN_duw2.js.map +0 -1
  117. /package/dist/{create-splat-renderer.d.ts → core/create-splat-renderer.d.ts} +0 -0
  118. /package/dist/{depth-of-field.d.ts → core/depth-of-field.d.ts} +0 -0
  119. /package/dist/{half-float.d.ts → core/half-float.d.ts} +0 -0
  120. /package/dist/{logging.d.ts → core/logging.d.ts} +0 -0
  121. /package/dist/{radix-sort.d.ts → core/radix-sort.d.ts} +0 -0
  122. /package/dist/{radix-sorter.d.ts → core/radix-sorter.d.ts} +0 -0
  123. /package/dist/{relighting-pass.d.ts → core/relighting-pass.d.ts} +0 -0
  124. /package/dist/{sh-pack.d.ts → core/sh-pack.d.ts} +0 -0
  125. /package/dist/{sort-scheduler.d.ts → core/sort-scheduler.d.ts} +0 -0
  126. /package/dist/{sort-worker-protocol.d.ts → core/sort-worker-protocol.d.ts} +0 -0
  127. /package/dist/{sorter.d.ts → core/sorter.d.ts} +0 -0
  128. /package/dist/{splat-depth-pack.d.ts → core/splat-depth-pack.d.ts} +0 -0
  129. /package/dist/{splat-mesh-picking.d.ts → core/splat-mesh-picking.d.ts} +0 -0
  130. /package/dist/{splat-mesh-pool.d.ts → core/splat-mesh-pool.d.ts} +0 -0
  131. /package/dist/{splat-modifier-stack.d.ts → core/splat-modifier-stack.d.ts} +0 -0
  132. /package/dist/{splat-query.d.ts → core/splat-query.d.ts} +0 -0
  133. /package/dist/{splat-sort-bounds.d.ts → core/splat-sort-bounds.d.ts} +0 -0
  134. /package/dist/{storage-attribute-mirror.d.ts → core/storage-attribute-mirror.d.ts} +0 -0
  135. /package/dist/{worker-sorter.d.ts → core/worker-sorter.d.ts} +0 -0
  136. /package/dist/{xr-view.d.ts → core/xr-view.d.ts} +0 -0
  137. /package/dist/{ply-header.d.ts → loaders/ply-header.d.ts} +0 -0
  138. /package/dist/{lod-manifest.d.ts → streaming/lod-manifest.d.ts} +0 -0
@@ -0,0 +1,746 @@
1
+ var je = Object.defineProperty;
2
+ var We = (t, n, e) => n in t ? je(t, n, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[n] = e;
3
+ var s = (t, n, e) => We(t, typeof n != "symbol" ? n + "" : n, e);
4
+ import * as l from "three/webgpu";
5
+ import { uniform as x, storage as k, Fn as ye, If as ze, float as A, instanceIndex as ue, int as Se, ivec2 as Ie, textureLoad as ee, mat3 as Re, vec3 as R, vec4 as C, bool as Ne, colorSpaceToWorking as _e, varying as te, positionGeometry as we, modelViewMatrix as Ae, cameraProjectionMatrix as qe, mix as ce, vec2 as I, Discard as He, texture as re, screenUV as se } from "three/tsl";
6
+ import { S as De, o as Ke, p as Xe, q as le, s as Ye, t as Ze, d as $e, u as Qe, w as Je, m as et, b as tt, D as rt, c as st, W as it, C as ot, i as at, j as nt, y as ct, r as lt } from "./relighting-Tiwep8yd.js";
7
+ import { RadixSorter as ut } from "./radix-sorter-BrbUg_CV.js";
8
+ import { i as dt } from "./splat-budget-DuVZspPQ.js";
9
+ import { a as ht } from "./webgpu-limits-Y3CI_6_6.js";
10
+ const he = class he {
11
+ constructor(n) {
12
+ /** World-space center xyz; w is display opacity (`visibility × source opacity ×` RAD modifier ratio). */
13
+ s(this, "centers");
14
+ /** Resolved color and opacity per work slot. */
15
+ s(this, "colors");
16
+ /** First packed covariance row per work slot. */
17
+ s(this, "covarianceA");
18
+ /** Second packed covariance row per work slot. */
19
+ s(this, "covarianceB");
20
+ /** Per-slot isotropic covariance mix (0 = keep Σ, 1 = round dot). */
21
+ s(this, "isotropicMix");
22
+ /** Per-slot isotropic target sigma radius in screen px (0 = disabled). */
23
+ s(this, "isotropicScreenRadius");
24
+ s(this, "mirrors");
25
+ this.capacity = n;
26
+ const e = n + he.SCRATCH_SLOTS;
27
+ this.centers = new l.StorageBufferAttribute(new Float32Array(e * 4), 4), this.colors = new l.StorageBufferAttribute(new Float32Array(e * 4), 4), this.covarianceA = new l.StorageBufferAttribute(new Float32Array(e * 4), 4), this.covarianceB = new l.StorageBufferAttribute(new Float32Array(e * 4), 4), this.isotropicMix = new l.StorageBufferAttribute(new Float32Array(e), 1), this.isotropicScreenRadius = new l.StorageBufferAttribute(new Float32Array(e), 1), this.mirrors = new De([
28
+ this.centers,
29
+ this.colors,
30
+ this.covarianceA,
31
+ this.covarianceB,
32
+ this.isotropicMix,
33
+ this.isotropicScreenRadius
34
+ ]);
35
+ }
36
+ /**
37
+ * Drops the JS-heap copies three keeps behind these storage buffers.
38
+ *
39
+ * Every one of them is written only by the gather compute pass and read only
40
+ * by the draw material - the CPU side is zeroes that no code ever reads, and
41
+ * three's WebGPU backend never frees it on its own. At 72 B/slot that mirror
42
+ * is ~720 MB of JS heap on a 10M-slot scene. See `storage-attribute-mirror`.
43
+ *
44
+ * Safe to call after any dispatch that bound this buffer; a no-op once
45
+ * settled, and on WebGL2 or a renderer double that has no WebGPU backend.
46
+ */
47
+ releaseCpuMirrors(n) {
48
+ this.mirrors.settled || this.mirrors.release(n);
49
+ }
50
+ };
51
+ /**
52
+ * Slots past {@link capacity}, reserved so a pipeline warm-up has somewhere
53
+ * harmless to write. See {@link WorkBufferGather.warmUp}: compiling a compute
54
+ * pipeline requires actually dispatching it, and dispatching zero workgroups
55
+ * - while it does compile - makes WebGPU log an "unusual workgroup count of 0"
56
+ * warning for every source. One workgroup aimed at this tail compiles just the
57
+ * same, silently, and cannot disturb a drawn slot: admission never allocates
58
+ * past `capacity` and the draw's `instanceCount` never covers it.
59
+ */
60
+ s(he, "SCRATCH_SLOTS", 256);
61
+ let de = he;
62
+ class ft {
63
+ constructor(n) {
64
+ /** World-space `xyzw` center buffer, one element per unified work slot. */
65
+ s(this, "centers");
66
+ /** Resolved source RGBA values; production draw consumes this next. */
67
+ s(this, "colors");
68
+ /** Packed symmetric world-space covariance rows. */
69
+ s(this, "covarianceA");
70
+ s(this, "covarianceB");
71
+ s(this, "isotropicMix");
72
+ s(this, "isotropicScreenRadius");
73
+ /** Shared target for this source's gathered range. */
74
+ s(this, "workBuffer");
75
+ s(this, "capacity");
76
+ s(this, "activeCount", x(0));
77
+ s(this, "targetOffset", x(0));
78
+ s(this, "sourceMatrix", x(new l.Matrix4()));
79
+ s(this, "cameraViewMatrix", x(new l.Matrix4()));
80
+ s(this, "opacity", x(1));
81
+ s(this, "pass");
82
+ const {
83
+ capacity: e,
84
+ sourceCapacity: i,
85
+ sourceIndex: a,
86
+ centersTexture: c,
87
+ colorsTexture: S,
88
+ covarianceATexture: B,
89
+ covarianceBTexture: u,
90
+ dataTextureWidth: f
91
+ } = n, D = n.sh ?? null, V = n.modifiers ?? [], T = n.channels ?? /* @__PURE__ */ new Map(), y = n.localCameraPosition ?? x(new l.Vector3());
92
+ this.workBuffer = n.workBuffer ?? new de(e), this.capacity = this.workBuffer.capacity, this.centers = this.workBuffer.centers, this.colors = this.workBuffer.colors, this.covarianceA = this.workBuffer.covarianceA, this.covarianceB = this.workBuffer.covarianceB, this.isotropicMix = this.workBuffer.isotropicMix, this.isotropicScreenRadius = this.workBuffer.isotropicScreenRadius;
93
+ const p = k(a, "uint", i), M = k(this.centers, "vec4", e), P = k(this.colors, "vec4", e), r = k(this.covarianceA, "vec4", e), o = k(this.covarianceB, "vec4", e), d = k(this.isotropicMix, "float", e), w = k(this.isotropicScreenRadius, "float", e);
94
+ this.pass = ye(() => {
95
+ ze(A(ue).lessThan(this.activeCount), () => {
96
+ const b = Se(p.element(ue)), m = Ie(
97
+ b.mod(Se(f)),
98
+ b.div(Se(f))
99
+ ), g = ee(c, m).xyz, v = ue.add(this.targetOffset.toUint()), F = ee(B, m), G = ee(u, m), E = Re(
100
+ R(F.x, F.y, F.z),
101
+ R(F.y, F.w, G.x),
102
+ R(F.z, G.x, G.y)
103
+ ), L = ee(S, m), O = D === null ? L : C(
104
+ L.rgb.add(
105
+ Ke(
106
+ D,
107
+ { covarianceBTexture: u },
108
+ m,
109
+ g.sub(y).normalize()
110
+ )
111
+ ).clamp(0, 1),
112
+ L.a
113
+ ), W = this.sourceMatrix.mul(C(g, 1)).xyz, h = Xe(V, y, {
114
+ index: b,
115
+ localCenter: g,
116
+ color: O,
117
+ makeWorldCenter: () => W,
118
+ makeViewCenter: () => this.cameraViewMatrix.mul(C(W, 1)).xyz,
119
+ makeNormal: () => {
120
+ const U = E.inverse(), X = y.sub(g), ne = U.mul(U.mul(X)).normalize();
121
+ return ne.mul(ne.dot(X).sign());
122
+ },
123
+ makeChannel: (U) => {
124
+ const X = T.get(U);
125
+ if (!X)
126
+ throw new Error(
127
+ `UnifiedSplatMesh: source modifier reads undefined channel "${U}".`
128
+ );
129
+ return ee(X.texture, m).r;
130
+ }
131
+ }), j = h.offset === null ? g : g.add(h.offset), N = h.visible === null ? Ne(!0) : h.visible, _ = this.sourceMatrix.mul(C(j, 1)).xyz, q = le(O.a), fe = le(h.color.a), ie = N.select(A(1), A(0)), Y = n.srgbOutput ? h.color : le(_e(h.color, l.SRGBColorSpace));
132
+ if (n.lodAlpha) {
133
+ const U = q.greaterThan(0).select(fe.div(q.max(1e-8)), A(1));
134
+ M.element(v).assign(C(_, ie.mul(this.opacity).mul(U))), P.element(v).assign(C(Y.rgb, q.mul(2)));
135
+ } else
136
+ M.element(v).assign(C(_, ie.mul(this.opacity))), P.element(v).assign(C(Y.rgb, Y.a));
137
+ const oe = h.rotation === null ? E : h.rotation.mul(E).mul(h.rotation.transpose());
138
+ let z = le(oe);
139
+ if (h.isotropicCovarianceMix !== null) {
140
+ const U = h.isotropicVarianceScale ?? A(Ye);
141
+ z = Ze(
142
+ z,
143
+ R(0, 0, 1),
144
+ h.isotropicCovarianceMix,
145
+ U
146
+ );
147
+ }
148
+ const Z = this.sourceMatrix.toMat3(), H = Z.mul(z).mul(Z.transpose()), K = h.scaleSquared === null ? H : H.mul(h.scaleSquared), $ = K.mul(R(1, 0, 0)), ae = K.mul(R(0, 1, 0)), pe = K.mul(R(0, 0, 1));
149
+ r.element(v).assign(C($.x, $.y, $.z, ae.y)), o.element(v).assign(C(ae.z, pe.z, 0, 0)), d.element(v).assign(h.isotropicCovarianceMix === null ? A(0) : h.isotropicCovarianceMix), w.element(v).assign(
150
+ h.isotropicCovarianceMix === null ? A(0) : h.isotropicScreenRadiusPx ?? A(0)
151
+ );
152
+ });
153
+ })().compute(i, [256]);
154
+ }
155
+ /** Gathers a source's active pool slots into a contiguous work-buffer range. */
156
+ gather(n, e, i, a, c = new l.Matrix4(), S = 1) {
157
+ if (e < 0 || i < 0 || i + e > this.capacity)
158
+ throw new Error("WorkBufferGather: requested range exceeds work-buffer capacity.");
159
+ this.activeCount.value = e, this.targetOffset.value = i, this.sourceMatrix.value.copy(a);
160
+ const B = typeof c == "number" ? new l.Matrix4() : c;
161
+ this.cameraViewMatrix.value.copy(B), this.opacity.value = Math.max(
162
+ 0,
163
+ typeof c == "number" ? c : S
164
+ ), this.pass.count = e, n.compute(this.pass), this.workBuffer.releaseCpuMirrors(n);
165
+ }
166
+ /**
167
+ * Compiles this gather's compute pipeline without gathering anything.
168
+ *
169
+ * WebGPU compiles a compute pipeline lazily, at its first dispatch - not when
170
+ * the node graph is built. Measured on a 13-mesh scene, a source's *first*
171
+ * `gather` took up to 2.0s while every later dispatch of the same pipeline
172
+ * took under 0.4ms, and because `gather` runs inside `update` that compile
173
+ * landed whole in one render frame. Running it through the async path here
174
+ * compiles off the critical frame, so the first real gather finds it ready.
175
+ *
176
+ * The dispatch targets {@link WorkBuffer.SCRATCH_SLOTS} past the drawable
177
+ * capacity rather than dispatching zero workgroups: zero also compiles, but
178
+ * WebGPU warns about it once per source, and writing into a drawn slot could
179
+ * leave junk behind when a cached gather is reused instead of re-run.
180
+ *
181
+ * Best-effort: a failure here costs only the stall it was meant to avoid, so
182
+ * callers may ignore it.
183
+ */
184
+ async warmUp(n) {
185
+ this.activeCount.value = 1, this.targetOffset.value = this.capacity, this.pass.count = 1, await n.computeAsync(this.pass), this.activeCount.value = 0, this.targetOffset.value = 0, this.workBuffer.releaseCpuMirrors(n);
186
+ }
187
+ /** Releases the gather pipeline. The owner releases the storage attribute. */
188
+ dispose() {
189
+ this.pass.dispose();
190
+ }
191
+ }
192
+ function ke(t) {
193
+ const n = new l.NodeMaterial(), e = k(t.centers, "vec4", t.capacity), i = k(t.order, "float", t.capacity), a = k(t.colors, "vec4", t.capacity), c = k(t.covarianceA, "vec4", t.capacity), S = k(t.covarianceB, "vec4", t.capacity), B = k(t.isotropicMix, "float", t.capacity), u = k(t.isotropicScreenRadius, "float", t.capacity), f = te(C(1, 1, 1, 1), "vWorkColor"), D = te(we.xy, "vWorkQuadPosition"), V = te(A(1), "vWorkOpacityCompensation"), T = te(A(1), "vWorkDisplayOpacity"), y = te(A(0), "vWorkAdjustedStdDev");
194
+ return n.vertexNode = ye(() => {
195
+ const p = i.element(ue).toInt(), M = e.element(p), P = M.xyz, r = M.w.greaterThan(0);
196
+ T.assign(M.w), f.assign(a.element(p));
197
+ const o = Ae.mul(C(P, 1)).toVar(), d = qe.mul(o).toVar(), w = c.element(p), b = S.element(p), m = Re(
198
+ R(w.x, w.y, w.z),
199
+ R(w.y, w.w, b.x),
200
+ R(w.z, b.x, b.y)
201
+ ), g = A(1).div(o.z), v = g.mul(g), F = R(
202
+ t.focal.x.mul(g),
203
+ 0,
204
+ t.focal.x.negate().mul(o.x).mul(v)
205
+ ), G = R(
206
+ 0,
207
+ t.focal.y.mul(g),
208
+ t.focal.y.negate().mul(o.y).mul(v)
209
+ ), E = Ae.toMat3().transpose(), L = E.mul(F), O = E.mul(G), W = L.dot(m.mul(L)), h = O.dot(m.mul(O)), j = L.dot(m.mul(O)), N = o.z.negate().max(1e-4), _ = t.dofFocusDistance.max(1e-4), q = t.dofAperture.max(0).mul(0.5).div(_).atan(), fe = N.sub(_).abs().div(N), ie = t.focal.x.mul(q.tan()), Y = fe.mul(ie), oe = Y.mul(Y).min(A($e)), z = W.add(t.projectedLowPassVariance).add(oe), Z = h.add(t.projectedLowPassVariance).add(oe), H = j, K = B.element(p), $ = z.mul(Z).sub(H.mul(H)).max(1e-9), ae = W.mul(h).sub(j.mul(j)).max(0), pe = W.add(t.projectedLowPassVariance).mul(h.add(t.projectedLowPassVariance)).sub(j.mul(j)).max(1e-9), U = ae.div($).sqrt(), X = pe.div($).sqrt(), ne = t.antialias.max(t.compensateProjectedLowPass), Fe = ce(X, U, ne);
210
+ V.assign(ce(Fe, A(1), K));
211
+ const be = z.add(Z).mul(0.5), Be = I(z.sub(Z).mul(0.5), H).length();
212
+ let Q = be.add(Be), J = be.sub(Be).max(0);
213
+ const Me = Qe(Q, J, K);
214
+ Q = Me.lambda1, J = Me.lambda2;
215
+ const Ce = Je(
216
+ Q,
217
+ J,
218
+ K,
219
+ u.element(p),
220
+ t.maxStdDev
221
+ );
222
+ Q = Ce.lambda1, J = Ce.lambda2;
223
+ const Le = f.a.mul(4).sub(3).min(5), me = f.a.greaterThan(1).select(t.maxStdDev.add(Le.sub(1).mul(0.7)), t.maxStdDev);
224
+ y.assign(me);
225
+ const ve = I(H, Q.sub(z)).add(I(1e-6, 0)).normalize(), Pe = t.minSplatSizePx, Ue = ve.mul(Q.sqrt().mul(me).min(1024).max(Pe)), Ee = I(ve.y, ve.x.negate()).mul(
226
+ J.sqrt().mul(me).min(1024).max(Pe)
227
+ ), Ve = Ue.mul(we.x).add(Ee.mul(we.y)), Ge = d.xy.div(d.w), Oe = C(
228
+ Ge.add(Ve.mul(2).div(t.viewport)),
229
+ d.z.div(d.w),
230
+ 1
231
+ ), ge = d.w.mul(1.2);
232
+ return d.z.greaterThan(ge.negate()).and(d.x.abs().lessThan(ge)).and(d.y.abs().lessThan(ge)).and(r).select(Oe, C(0, 0, 2, 1));
233
+ })(), n.fragmentNode = ye(() => {
234
+ const p = D.dot(D);
235
+ He(p.greaterThan(1));
236
+ const M = p.mul(y.mul(y).mul(-0.5)).exp(), P = f.a.mul(4).sub(3).min(5), r = P.mul(P).sub(1).mul(1 / Math.E).exp(), o = M.oneMinus().pow(r).oneMinus(), w = f.a.greaterThan(1).select(o, M.mul(f.a)).mul(V).mul(T), b = re(t.relightMap, se), m = t.relightSoftness.div(t.viewport.x.max(1)), g = t.relightSoftness.div(t.viewport.y.max(1)), v = b, F = re(t.relightMap, se.add(I(m, 0))), G = re(t.relightMap, se.add(I(m.negate(), 0))), E = re(t.relightMap, se.add(I(0, g))), L = re(t.relightMap, se.add(I(0, g.negate()))), O = v.a.add(F.a).add(G.a).add(E.a).add(L.a).max(1e-4), W = v.rgb.mul(v.a).add(F.rgb.mul(F.a)).add(G.rgb.mul(G.a)).add(E.rgb.mul(E.a)).add(L.rgb.mul(L.a)).div(O), h = O.mul(0.2), j = C(W, h), N = t.relightSoftness.greaterThan(0.5).select(j, b), _ = ce(
237
+ R(t.relightBackground),
238
+ N.rgb.mul(t.relightBrightness),
239
+ N.a
240
+ ), q = ce(f.rgb, f.rgb.mul(_), t.relightBlend);
241
+ return C(q.mul(w), w);
242
+ })(), n.transparent = !0, n.depthTest = !0, n.depthWrite = !1, n.side = l.DoubleSide, n.blending = l.CustomBlending, n.blendSrc = l.OneFactor, n.blendDst = l.OneMinusSrcAlphaFactor, n.blendSrcAlpha = l.OneFactor, n.blendDstAlpha = l.OneMinusSrcAlphaFactor, n.toneMapped = !1, n;
243
+ }
244
+ const pt = 16, mt = 72;
245
+ function Te(t) {
246
+ if (!Number.isFinite(t) || t < 0)
247
+ throw new RangeError("Splat capacity must be a non-negative finite number.");
248
+ }
249
+ function vt(t) {
250
+ return Te(t), Math.floor(t) * pt;
251
+ }
252
+ function gt(t) {
253
+ return Te(t), Math.floor(t) * mt;
254
+ }
255
+ function Ct(t) {
256
+ return gt(t) * 2;
257
+ }
258
+ function xe(t) {
259
+ var n;
260
+ return ((n = t.backend) == null ? void 0 : n.isWebGPUBackend) === !0;
261
+ }
262
+ class Pt extends l.Mesh {
263
+ constructor(e, i, a = {}) {
264
+ if (!xe(e))
265
+ throw new Error(
266
+ "UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend). On WebGL2 use standalone SplatMesh draws or static MergedSplatMesh."
267
+ );
268
+ ht(e, vt(i), i);
269
+ const c = new de(i), S = x(new l.Vector2()), B = x(new l.Vector2()), u = x(3), f = x(0), D = x(0), V = x(0.3), T = x(0), y = x(10), p = x(0), M = et(), P = x(0), r = x(tt), o = x(rt), d = x(st), w = new l.StorageInstancedBufferAttribute(new Float32Array(i), 1), b = new l.InstancedBufferGeometry();
270
+ b.setIndex([0, 1, 2, 0, 2, 3]), b.setAttribute(
271
+ "position",
272
+ new l.BufferAttribute(new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]), 3)
273
+ ), b.instanceCount = 0;
274
+ super(
275
+ b,
276
+ ke({
277
+ capacity: i,
278
+ centers: c.centers,
279
+ colors: c.colors,
280
+ covarianceA: c.covarianceA,
281
+ covarianceB: c.covarianceB,
282
+ isotropicMix: c.isotropicMix,
283
+ isotropicScreenRadius: c.isotropicScreenRadius,
284
+ order: w,
285
+ focal: S,
286
+ viewport: B,
287
+ maxStdDev: u,
288
+ minSplatSizePx: f,
289
+ antialias: D,
290
+ projectedLowPassVariance: V,
291
+ compensateProjectedLowPass: T,
292
+ dofFocusDistance: y,
293
+ dofAperture: p,
294
+ relightMap: M,
295
+ relightBlend: P,
296
+ relightBrightness: r,
297
+ relightBackground: o,
298
+ relightSoftness: d
299
+ })
300
+ );
301
+ s(this, "workBuffer");
302
+ s(this, "workSourceIndex");
303
+ /** The sorted draw-order buffer; lives outside the geometry, freed on dispose. */
304
+ s(this, "orderAttribute");
305
+ /**
306
+ * Frees the JS mirrors three keeps behind `workSourceIndex` and `order`.
307
+ *
308
+ * `workSourceIndex` is an identity ramp written once at construction; `order`
309
+ * is written only by the sorter, on the GPU. Neither is ever read back, so
310
+ * both mirrors are 4 B/slot of dead JS heap once uploaded.
311
+ */
312
+ s(this, "mirrors");
313
+ s(this, "sorter");
314
+ s(this, "renderer");
315
+ s(this, "sources", []);
316
+ s(this, "previousLayout", []);
317
+ /** Previous layout keyed by source; kept in step with {@link previousLayout}. */
318
+ s(this, "previousLayoutBySource", /* @__PURE__ */ new Map());
319
+ /** Per-prepare scratch lists, reused so a steady frame allocates nothing. */
320
+ s(this, "candidateScratch", []);
321
+ s(this, "admittedScratch", []);
322
+ s(this, "drawingBufferSize", new l.Vector2());
323
+ /** Reused target dimensions for secondary views; avoids per-view garbage. */
324
+ s(this, "secondaryViewSize", new l.Vector2());
325
+ /**
326
+ * Camera gating for the global sorter, mirroring the standalone
327
+ * `SplatMesh.requestSortIfNeeded` path: an unchanged work buffer under a
328
+ * stationary camera skips the whole-buffer re-sort. Content changes
329
+ * (regather, layout change) force the next sort through the scheduler.
330
+ */
331
+ s(this, "sortScheduler");
332
+ /** Model-view of the last accepted sort; starts unmatchable (zero scale). */
333
+ s(this, "lastSortedModelView", new l.Matrix4().makeScale(0, 0, 0));
334
+ /**
335
+ * Total admitted splats in the last prepared frame. `removeSource` filters
336
+ * {@link previousLayout} directly, so a tail source's removal can leave the
337
+ * remaining entries identical - only this total betrays the shrink, and a
338
+ * shrunk draw range must re-sort (the old permutation indexes past it).
339
+ */
340
+ s(this, "previousAdmittedTotal", 0);
341
+ s(this, "bounds", new l.Sphere());
342
+ s(this, "focal");
343
+ s(this, "viewport");
344
+ s(this, "maxStdDev");
345
+ s(this, "minSplatSizePx");
346
+ s(this, "antialias");
347
+ s(this, "projectedLowPassVariance");
348
+ s(this, "compensateProjectedLowPass");
349
+ s(this, "dofFocusDistance");
350
+ s(this, "dofAperture");
351
+ s(this, "relightPlaceholder");
352
+ s(this, "relightMap");
353
+ s(this, "relightBlend");
354
+ s(this, "relightBrightness");
355
+ s(this, "relightBackground");
356
+ s(this, "relightSoftness");
357
+ s(this, "srgbOutput");
358
+ s(this, "sourceMaxStdDev", null);
359
+ s(this, "sourceAntialias", null);
360
+ s(this, "sourceProjectedFilterProfile", null);
361
+ s(this, "overflowedSourceCount", 0);
362
+ s(this, "overflowedSplatCount", 0);
363
+ s(this, "disposed", !1);
364
+ const m = new Uint32Array(i);
365
+ for (let v = 0; v < i; v++) m[v] = v;
366
+ this.workBuffer = c, this.focal = S, this.viewport = B, this.maxStdDev = u, this.minSplatSizePx = f, this.antialias = D, this.projectedLowPassVariance = V, this.compensateProjectedLowPass = T, this.dofFocusDistance = y, this.dofAperture = p, this.relightPlaceholder = M, this.relightMap = M, this.relightBlend = P, this.relightBrightness = r, this.relightBackground = o, this.relightSoftness = d, this.srgbOutput = a.srgbOutput ?? !1, this.sortScheduler = new it(void 0, dt()), this.orderAttribute = w, this.workSourceIndex = new l.StorageBufferAttribute(m, 1), this.mirrors = new De([this.workSourceIndex, this.orderAttribute]), this.renderer = e;
367
+ const g = {
368
+ renderer: e,
369
+ capacity: i,
370
+ centersBuffer: c.centers,
371
+ splatIndexAttribute: w,
372
+ sourceIndexAttribute: this.workSourceIndex
373
+ };
374
+ this.sorter = a.sortStrategy === "exact" ? new ut({ ...g, exactDepth: !0 }) : new ot(g), this.frustumCulled = !1, this.matrixAutoUpdate = !1, this.matrix.identity(), this.matrixWorld.identity();
375
+ }
376
+ /** Fixed work-buffer splat capacity chosen at construction. */
377
+ get capacity() {
378
+ return this.workBuffer.capacity;
379
+ }
380
+ /** Registers a source. The caller keeps ownership and may still query it. */
381
+ addSource(e, i = {}) {
382
+ if (this.assertNotDisposed("addSource"), this.sources.some((S) => S.source === e)) return;
383
+ const a = e.getUnifiedSourceView(), c = i.visible ?? e.visible;
384
+ if (a.hasSourcePlacement)
385
+ throw new Error(
386
+ "UnifiedSplatMesh: a MergedSplatMesh is already a unified pool with its own global sort; add its sources individually, or draw the mesh directly."
387
+ );
388
+ if (a.srgbOutput !== this.srgbOutput)
389
+ throw new Error("UnifiedSplatMesh: every source must use the renderer's srgbOutput setting.");
390
+ if (this.sourceMaxStdDev === null)
391
+ this.sourceMaxStdDev = a.maxStdDev, this.maxStdDev.value = a.maxStdDev, this.minSplatSizePx.value = a.minSplatSizePx;
392
+ else {
393
+ if (this.sourceMaxStdDev !== a.maxStdDev)
394
+ throw new Error("UnifiedSplatMesh: every source must use the same maxStdDev setting.");
395
+ if (this.minSplatSizePx.value !== a.minSplatSizePx)
396
+ throw new Error("UnifiedSplatMesh: every source must use the same minSplatSizePx setting.");
397
+ }
398
+ if (this.sourceAntialias === null)
399
+ this.sourceAntialias = a.antialias, this.antialias.value = a.antialias ? 1 : 0;
400
+ else if (this.sourceAntialias !== a.antialias)
401
+ throw new Error("UnifiedSplatMesh: every source must use the same antialias setting.");
402
+ if (this.sourceProjectedFilterProfile === null) {
403
+ this.sourceProjectedFilterProfile = a.projectedFilterProfile;
404
+ const S = a.projectedFilterProfile === "lcc";
405
+ this.projectedLowPassVariance.value = S ? 0.1 : 0.3, this.compensateProjectedLowPass.value = S ? 1 : 0;
406
+ } else if (this.sourceProjectedFilterProfile !== a.projectedFilterProfile)
407
+ throw new Error("UnifiedSplatMesh: every source must use the same projected filter profile.");
408
+ this.sources.push({
409
+ source: e,
410
+ gather: this.createGather(a),
411
+ graphRevision: a.graphRevision,
412
+ priority: i.priority ?? 0,
413
+ opacity: i.opacity ?? 1,
414
+ visible: c,
415
+ cacheModifiers: i.cacheModifiers ?? !1,
416
+ originalVisible: e.visible,
417
+ lastGather: null,
418
+ view: null,
419
+ registrationOrder: 0,
420
+ offset: 0
421
+ }), e.visible = !1, e.setUnifiedPickVisibility(c);
422
+ }
423
+ /**
424
+ * Invalidates one registered source's cached gather output.
425
+ *
426
+ * Required after changing modifier uniforms or channels when that source was
427
+ * registered with `cacheModifiers: true`. The following {@link update}
428
+ * re-gathers the source and re-sorts because modifiers may move splats.
429
+ * Returns `false` when the source is not registered.
430
+ */
431
+ invalidateSource(e) {
432
+ this.assertNotDisposed("invalidateSource");
433
+ const i = this.sources.find((a) => a.source === e);
434
+ return i ? (i.lastGather = null, !0) : !1;
435
+ }
436
+ /** Number of whole sources omitted in the most recent update due to capacity. */
437
+ get droppedSourceCount() {
438
+ return this.overflowedSourceCount;
439
+ }
440
+ /** Number of active splats omitted in the most recent update due to capacity. */
441
+ get droppedSplatCount() {
442
+ return this.overflowedSplatCount;
443
+ }
444
+ /**
445
+ * Sets whole-source display opacity without rebuilding the gather pipeline.
446
+ *
447
+ * This scales the completed fragment after RAD merged-vs-leaf classification
448
+ * and falloff. It must never alter LOD selection or splat shape; hosts such as
449
+ * marker crossfades rely on that. Values `<= 0` skip drawing the source.
450
+ */
451
+ setSourceOpacity(e, i) {
452
+ this.assertNotDisposed("setSourceOpacity");
453
+ const a = this.sources.find((c) => c.source === e);
454
+ if (!a) throw new Error("UnifiedSplatMesh: source is not registered.");
455
+ a.opacity = i;
456
+ }
457
+ /** Shows or hides a source without changing its residency. */
458
+ setSourceVisible(e, i) {
459
+ this.assertNotDisposed("setSourceVisible");
460
+ const a = this.sources.find((c) => c.source === e);
461
+ if (!a) throw new Error("UnifiedSplatMesh: source is not registered.");
462
+ a.visible = i, a.source.setUnifiedPickVisibility(i);
463
+ }
464
+ /**
465
+ * Core projected-2D depth of field on the unified draw pass (not gather).
466
+ * Live uniforms - no gather-cache invalidation. Pass `aperture: 0` to disable.
467
+ */
468
+ setDepthOfField(e) {
469
+ this.assertNotDisposed("setDepthOfField");
470
+ const i = at(e, this.getDepthOfField());
471
+ this.dofFocusDistance.value = i.focusDistance, this.dofAperture.value = i.aperture;
472
+ }
473
+ /** Current core DoF uniforms (`aperture === 0` means off). */
474
+ getDepthOfField() {
475
+ return {
476
+ focusDistance: this.dofFocusDistance.value,
477
+ aperture: this.dofAperture.value
478
+ };
479
+ }
480
+ /**
481
+ * PlayCanvas-style proxy-mesh relighting on the unified draw pass (not gather).
482
+ * Live blend/brightness/background; map identity change rebuilds the draw
483
+ * material. Does not invalidate gather caches. Pass `null` to disable.
484
+ */
485
+ setRelighting(e) {
486
+ if (this.assertNotDisposed("setRelighting"), e === null) {
487
+ this.relightBlend.value = 0, this.relightMap !== this.relightPlaceholder && (this.relightMap = this.relightPlaceholder, this.rebuildDrawMaterial());
488
+ return;
489
+ }
490
+ const i = nt(e, this.getRelighting());
491
+ this.relightBlend.value = i.blend, this.relightBrightness.value = i.brightness, this.relightBackground.value = i.background, this.relightSoftness.value = i.softness, e.map !== this.relightMap && (this.relightMap = e.map, this.rebuildDrawMaterial());
492
+ }
493
+ /** Current relight numeric uniforms (`blend === 0` means off). */
494
+ getRelighting() {
495
+ return {
496
+ blend: this.relightBlend.value,
497
+ brightness: this.relightBrightness.value,
498
+ background: this.relightBackground.value,
499
+ softness: this.relightSoftness.value
500
+ };
501
+ }
502
+ /**
503
+ * Asynchronously picks the frontmost visible splat under an NDC coordinate
504
+ * across every registered source.
505
+ *
506
+ * Semantics: each source that is
507
+ * currently visible in this unified renderer runs its own depth-tested GPU
508
+ * pick pass; the hit nearest the camera wins and the result names its source
509
+ * mesh. Hidden sources (`setSourceVisible(source, false)`) never hit. A hit
510
+ * whose source was removed, hidden, or disposed while its readback was in
511
+ * flight is dropped rather than misattributed. Resolves `null` for misses,
512
+ * an empty source list, and picks pending across {@link dispose}.
513
+ *
514
+ * Uses the construction renderer. Concurrent calls are safe: each source
515
+ * serializes its own picks and shared renderer state is restored
516
+ * synchronously before any await.
517
+ */
518
+ async pick(e, i, a) {
519
+ if (this.disposed) return null;
520
+ const c = this.sources.filter((u) => u.visible).map((u) => u.source);
521
+ if (c.length === 0) return null;
522
+ const S = await Promise.all(
523
+ c.map(async (u) => {
524
+ const f = await u.pick(e, i, this.renderer, a);
525
+ return f === null ? null : { source: u, point: f.point, distance: f.distance };
526
+ })
527
+ );
528
+ if (this.disposed) return null;
529
+ let B = null;
530
+ for (const u of S) {
531
+ if (u === null) continue;
532
+ const f = this.sources.find((D) => D.source === u.source);
533
+ f === void 0 || !f.visible || (B === null || u.distance < B.distance) && (B = u);
534
+ }
535
+ return B;
536
+ }
537
+ /** Unregisters a source and restores its standalone draw visibility. */
538
+ removeSource(e) {
539
+ this.assertNotDisposed("removeSource");
540
+ const i = this.sources.findIndex((c) => c.source === e);
541
+ if (i < 0) return !1;
542
+ const [a] = this.sources.splice(i, 1);
543
+ return a && (a.gather.dispose(), a.source.visible = a.originalVisible, a.source.setUnifiedPickVisibility(null), a.lastGather = null), this.previousLayout = this.previousLayout.filter((c) => c.source !== e), this.previousLayoutBySource.delete(e), this.sources.length === 0 && (this.sourceMaxStdDev = null, this.sourceAntialias = null, this.sourceProjectedFilterProfile = null, this.previousLayout = [], this.previousLayoutBySource.clear()), !0;
544
+ }
545
+ /** Updates source LODs, gathers their active splats, then globally sorts them. */
546
+ update(e) {
547
+ this.disposed || this.prepare(e);
548
+ }
549
+ /**
550
+ * Re-sorts and draws this unified source list for a secondary camera.
551
+ * Mirrors and portals use this instead of rendering the hidden source meshes
552
+ * individually, so their reflection keeps one global transparent order.
553
+ */
554
+ renderView(e, i, a = null) {
555
+ if (this.disposed) return;
556
+ if (i !== this.renderer)
557
+ throw new Error("UnifiedSplatMesh: renderView must use its construction renderer.");
558
+ if (!xe(i))
559
+ throw new Error(
560
+ "UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend)."
561
+ );
562
+ const c = a ? this.secondaryViewSize.set(a.width, a.height) : void 0;
563
+ this.prepare(e, c, !0);
564
+ const S = i.getRenderTarget();
565
+ try {
566
+ i.setRenderTarget(a), i.render(this, e);
567
+ } finally {
568
+ i.setRenderTarget(S), this.sortScheduler.invalidate();
569
+ }
570
+ }
571
+ prepare(e, i, a = !1) {
572
+ if (!xe(this.renderer))
573
+ throw new Error(
574
+ "UnifiedSplatMesh requires a WebGPU backend (renderer.backend.isWebGPUBackend)."
575
+ );
576
+ this.matrix.identity(), this.matrixWorld.identity();
577
+ const c = i ? null : ct(e, this.renderer), S = (c == null ? void 0 : c.eye) ?? e, B = (c == null ? void 0 : c.head) ?? e, u = this.drawingBufferSize;
578
+ i ? u.copy(i) : c ? u.set(c.width, c.height) : this.renderer.getDrawingBufferSize(u);
579
+ const f = S.projectionMatrix.elements;
580
+ this.viewport.value.copy(u);
581
+ const D = f[0] ?? 0, V = f[5] ?? 0;
582
+ this.focal.value.set(D * u.x / 2, V * u.y / 2);
583
+ const T = this.candidateScratch;
584
+ T.length = 0;
585
+ for (let r = 0; r < this.sources.length; r++) {
586
+ const o = this.sources[r];
587
+ o.source.update(e, this.renderer, { sort: !1 }), o.view = o.source.getUnifiedSourceView(), o.registrationOrder = r, T.push(o);
588
+ }
589
+ T.sort((r, o) => o.priority - r.priority || r.registrationOrder - o.registrationOrder), this.overflowedSourceCount = 0, this.overflowedSplatCount = 0, this.bounds.makeEmpty();
590
+ const y = this.admittedScratch;
591
+ y.length = 0;
592
+ let p = 0;
593
+ for (const r of T) {
594
+ const o = r.view;
595
+ if (r.graphRevision !== o.graphRevision) {
596
+ const d = this.createGather(o);
597
+ r.gather.dispose(), r.gather = d, r.graphRevision = o.graphRevision, r.lastGather = null;
598
+ }
599
+ if (!r.visible) {
600
+ r.lastGather = null;
601
+ continue;
602
+ }
603
+ if (o.activeCount === 0) {
604
+ r.lastGather = null;
605
+ continue;
606
+ }
607
+ if (p + o.activeCount > this.workBuffer.capacity) {
608
+ this.overflowedSourceCount++, this.overflowedSplatCount += o.activeCount, r.lastGather = null;
609
+ continue;
610
+ }
611
+ r.offset = p, y.push(r), this.bounds.union(o.worldBounds), p += o.activeCount;
612
+ }
613
+ let M = !1;
614
+ for (const r of y) {
615
+ const o = r.view, d = r.offset, w = this.previousLayoutBySource.get(r.source), b = w !== void 0 && w.offset === d && w.activeCount === o.activeCount, m = r.lastGather, g = m !== null && b && m.activeCount === o.activeCount && m.contentRevision === o.contentRevision && m.offset === d && m.opacity === r.opacity && m.matrixWorld.equals(o.matrixWorld);
616
+ g && // SH depends on camera position, not orientation. A stationary camera
617
+ // can reuse the resolved colors instead of re-running a full-source
618
+ // gather every frame; camera motion invalidates only SH-bearing slices.
619
+ (o.sh === null || m.localCameraPosition.equals(o.localCameraPosition.value)) && (o.modifiers.length === 0 || r.cacheModifiers) && // Belt-and-braces: a placed pool's splats move without its own
620
+ // `matrixWorld` changing, so a cached gather would go stale silently.
621
+ // `addSource` already rejects these.
622
+ !o.hasSourcePlacement || (r.gather.gather(
623
+ this.renderer,
624
+ o.activeCount,
625
+ d,
626
+ o.matrixWorld,
627
+ B.matrixWorldInverse,
628
+ r.opacity
629
+ ), g || (M = !0), r.lastGather === null ? r.lastGather = {
630
+ activeCount: o.activeCount,
631
+ contentRevision: o.contentRevision,
632
+ offset: d,
633
+ opacity: r.opacity,
634
+ matrixWorld: o.matrixWorld.clone(),
635
+ localCameraPosition: o.localCameraPosition.value.clone()
636
+ } : (r.lastGather.activeCount = o.activeCount, r.lastGather.contentRevision = o.contentRevision, r.lastGather.offset = d, r.lastGather.opacity = r.opacity, r.lastGather.matrixWorld.copy(o.matrixWorld), r.lastGather.localCameraPosition.copy(o.localCameraPosition.value)));
637
+ }
638
+ let P = y.length !== this.previousLayout.length || p !== this.previousAdmittedTotal;
639
+ if (this.previousAdmittedTotal = p, !P)
640
+ for (let r = 0; r < y.length; r++) {
641
+ const o = y[r], d = this.previousLayout[r];
642
+ if (d.source !== o.source || d.offset !== o.offset || d.activeCount !== o.view.activeCount) {
643
+ P = !0;
644
+ break;
645
+ }
646
+ }
647
+ if (P) {
648
+ this.previousLayout = y.map((r) => ({
649
+ source: r.source,
650
+ offset: r.offset,
651
+ activeCount: r.view.activeCount
652
+ })), this.previousLayoutBySource.clear();
653
+ for (const r of this.previousLayout) this.previousLayoutBySource.set(r.source, r);
654
+ }
655
+ if ((M || P) && this.sortScheduler.invalidateContent(), p > 0) {
656
+ const r = performance.now(), o = B.matrixWorldInverse;
657
+ (a || this.sortScheduler.shouldSubmit(o, this.lastSortedModelView, p, r)) && this.sorter.sort(o, p, this.bounds) && (this.lastSortedModelView.copy(o), this.sortScheduler.markAccepted(r));
658
+ }
659
+ this.geometry.instanceCount = p, this.mirrors.settled || this.mirrors.release(this.renderer);
660
+ }
661
+ dispose() {
662
+ if (!this.disposed) {
663
+ this.disposed = !0;
664
+ for (const e of this.sources)
665
+ e.gather.dispose(), e.source.visible = e.originalVisible, e.source.setUnifiedPickVisibility(null), e.lastGather = null;
666
+ this.sources.length = 0, this.previousLayout = [], this.previousLayoutBySource.clear(), this.candidateScratch.length = 0, this.admittedScratch.length = 0, this.sourceMaxStdDev = null, this.sourceAntialias = null, this.sourceProjectedFilterProfile = null, this.sorter.dispose(), this.geometry.dispose(), this.material.dispose(), this.relightPlaceholder.dispose(), lt(this.renderer, [
667
+ this.workBuffer.centers,
668
+ this.workBuffer.colors,
669
+ this.workBuffer.covarianceA,
670
+ this.workBuffer.covarianceB,
671
+ this.workBuffer.isotropicMix,
672
+ this.workBuffer.isotropicScreenRadius,
673
+ this.workSourceIndex,
674
+ this.orderAttribute
675
+ ]);
676
+ }
677
+ }
678
+ assertNotDisposed(e) {
679
+ if (this.disposed)
680
+ throw new Error(`UnifiedSplatMesh: ${e} called after dispose.`);
681
+ }
682
+ /** Rebuilds the EWA draw material when the relight map identity changes. */
683
+ rebuildDrawMaterial() {
684
+ const e = this.material;
685
+ this.material = ke({
686
+ capacity: this.workBuffer.capacity,
687
+ centers: this.workBuffer.centers,
688
+ colors: this.workBuffer.colors,
689
+ covarianceA: this.workBuffer.covarianceA,
690
+ covarianceB: this.workBuffer.covarianceB,
691
+ isotropicMix: this.workBuffer.isotropicMix,
692
+ isotropicScreenRadius: this.workBuffer.isotropicScreenRadius,
693
+ order: this.orderAttribute,
694
+ focal: this.focal,
695
+ viewport: this.viewport,
696
+ maxStdDev: this.maxStdDev,
697
+ minSplatSizePx: this.minSplatSizePx,
698
+ antialias: this.antialias,
699
+ projectedLowPassVariance: this.projectedLowPassVariance,
700
+ compensateProjectedLowPass: this.compensateProjectedLowPass,
701
+ dofFocusDistance: this.dofFocusDistance,
702
+ dofAperture: this.dofAperture,
703
+ relightMap: this.relightMap,
704
+ relightBlend: this.relightBlend,
705
+ relightBrightness: this.relightBrightness,
706
+ relightBackground: this.relightBackground,
707
+ relightSoftness: this.relightSoftness
708
+ }), e.dispose();
709
+ }
710
+ createGather(e) {
711
+ const i = this.buildGather(e);
712
+ return i.warmUp(this.renderer).catch(() => {
713
+ }), i;
714
+ }
715
+ buildGather(e) {
716
+ return new ft({
717
+ capacity: this.workBuffer.capacity,
718
+ sourceCapacity: e.capacity,
719
+ sourceIndex: e.sourceIndex,
720
+ centersTexture: e.centersTexture,
721
+ colorsTexture: e.colorsTexture,
722
+ covarianceATexture: e.covarianceATexture,
723
+ covarianceBTexture: e.covarianceBTexture,
724
+ dataTextureWidth: e.dataTextureWidth,
725
+ sh: e.sh,
726
+ modifiers: e.modifiers,
727
+ channels: e.channels,
728
+ localCameraPosition: e.localCameraPosition,
729
+ srgbOutput: this.srgbOutput,
730
+ // Per source, not a compatibility field: the gather normalizes `.rad`
731
+ // half-alpha here so one shared draw material serves mixed scenes.
732
+ lodAlpha: e.lodAlpha,
733
+ workBuffer: this.workBuffer
734
+ });
735
+ }
736
+ }
737
+ export {
738
+ Pt as UnifiedSplatMesh,
739
+ mt as WORK_BUFFER_BYTES_PER_SLOT,
740
+ pt as WORK_BUFFER_CENTERS_BYTES_PER_SPLAT,
741
+ vt as estimateLargestStorageBufferBytes,
742
+ gt as estimateUnifiedWorkBufferBytes,
743
+ Ct as estimateUnifiedWorkBufferPeakBytes,
744
+ xe as supportsUnifiedSplatMesh
745
+ };
746
+ //# sourceMappingURL=unified.js.map