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