@needle-tools/gltf-progressive 4.0.0-alpha.1 → 4.0.0-alpha.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 (40) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/LICENSE +21 -0
  3. package/README.md +47 -11
  4. package/examples/modelviewer-multiple.html +4 -4
  5. package/examples/modelviewer.html +4 -4
  6. package/examples/offscreen/index.html +15 -0
  7. package/examples/offscreen/main.js +98 -0
  8. package/examples/react-three-fiber/index.html +2 -2
  9. package/examples/react-three-fiber/package-lock.json +482 -484
  10. package/examples/react-three-fiber/package.json +4 -4
  11. package/examples/react-three-fiber/src/App.tsx +76 -21
  12. package/examples/react-three-fiber/src/styles.css +2 -4
  13. package/examples/react-three-fiber/vite.config.js +2 -2
  14. package/examples/shared/example-utils.js +297 -0
  15. package/examples/shared/example.css +44 -0
  16. package/examples/shared/runtime.js +34 -0
  17. package/examples/threejs/index.html +6 -10
  18. package/examples/threejs/main.js +5 -23
  19. package/examples/webgpu/index.html +15 -0
  20. package/examples/webgpu/main.js +105 -0
  21. package/examples/worker-rendering/index.html +15 -0
  22. package/examples/worker-rendering/main.js +109 -0
  23. package/examples/worker-rendering/worker.js +166 -0
  24. package/gltf-progressive.js +1025 -669
  25. package/gltf-progressive.min.js +9 -9
  26. package/gltf-progressive.umd.cjs +9 -9
  27. package/lib/extension.d.ts +73 -7
  28. package/lib/extension.js +351 -111
  29. package/lib/index.d.ts +1 -1
  30. package/lib/index.js +1 -1
  31. package/lib/loaders.js +17 -3
  32. package/lib/lods.debug.js +2 -2
  33. package/lib/lods.manager.d.ts +93 -15
  34. package/lib/lods.manager.js +331 -157
  35. package/lib/lods.promise.js +1 -1
  36. package/lib/plugins/modelviewer.js +1 -1
  37. package/lib/utils.internal.js +20 -6
  38. package/lib/version.js +1 -1
  39. package/lib/worker/loader.mainthread.js +4 -1
  40. package/package.json +10 -3
@@ -1,87 +1,99 @@
1
- import { RedFormat as Ue, RedIntegerFormat as ze, RGFormat as Ne, RGIntegerFormat as qe, RGBFormat as Ve, RGBAFormat as Ee, RGBAIntegerFormat as je, BufferGeometry as E, Mesh as j, Box3 as ge, Vector3 as A, Sphere as Ce, CompressedTexture as Xe, Texture as F, Matrix3 as Ke, InterleavedBuffer as Ye, InterleavedBufferAttribute as He, BufferAttribute as Qe, TextureLoader as Je, Matrix4 as Me, Timer as Ze, MeshStandardMaterial as et } from "three";
2
- import { GLTFLoader as _e } from "three/examples/jsm/loaders/GLTFLoader.js";
3
- import { MeshoptDecoder as tt } from "three/examples/jsm/libs/meshopt_decoder.module.js";
4
- import { DRACOLoader as st } from "three/examples/jsm/loaders/DRACOLoader.js";
5
- import { KTX2Loader as rt } from "three/examples/jsm/loaders/KTX2Loader.js";
6
- const ot = "";
7
- globalThis.GLTF_PROGRESSIVE_VERSION = ot;
1
+ import * as Xe from "three";
2
+ import { RedFormat as je, RedIntegerFormat as Ke, RGFormat as He, RGIntegerFormat as Ye, RGBFormat as Qe, RGBAFormat as Je, RGBAIntegerFormat as Ze, BufferGeometry as j, Mesh as K, Box3 as ue, Vector3 as k, Sphere as Ae, CompressedTexture as et, Texture as q, Matrix3 as tt, InterleavedBuffer as st, InterleavedBufferAttribute as rt, BufferAttribute as nt, TextureLoader as it, Color as Ie, Matrix4 as $e } from "three";
3
+ import { GLTFLoader as De } from "three/examples/jsm/loaders/GLTFLoader.js";
4
+ import { MeshoptDecoder as ot } from "three/examples/jsm/libs/meshopt_decoder.module.js";
5
+ import { DRACOLoader as at } from "three/examples/jsm/loaders/DRACOLoader.js";
6
+ import { KTX2Loader as lt } from "three/examples/jsm/loaders/KTX2Loader.js";
7
+ const ut = "";
8
+ globalThis.GLTF_PROGRESSIVE_VERSION = ut;
8
9
  console.debug("[gltf-progressive] version -");
9
- let I = "https://www.gstatic.com/draco/versioned/decoders/1.5.7/", X = "https://cdn.needle.tools/static/three/0.179.1/basis2/";
10
- const nt = I, it = X, Ae = new URL(I + "draco_decoder.js");
11
- Ae.searchParams.append("range", "true");
12
- fetch(Ae, {
10
+ let $ = "https://www.gstatic.com/draco/versioned/decoders/1.5.7/", H = "https://cdn.needle.tools/static/three/0.179.1/basis2/";
11
+ const ct = $, ft = H, Be = new URL($ + "draco_decoder.js");
12
+ Be.searchParams.append("range", "true");
13
+ fetch(Be, {
13
14
  method: "GET",
14
15
  headers: {
15
16
  Range: "bytes=0-1"
16
17
  }
17
18
  }).catch((i) => {
18
- console.debug(`Failed to fetch remote Draco decoder from ${I} (offline: ${typeof navigator < "u" ? navigator.onLine : "unknown"})`), I === nt && lt("./include/draco/"), X === it && ut("./include/ktx2/");
19
+ console.debug(`Failed to fetch remote Draco decoder from ${$} (offline: ${typeof navigator < "u" ? navigator.onLine : "unknown"})`), $ === ct && ht("./include/draco/"), H === ft && gt("./include/ktx2/");
19
20
  }).finally(() => {
20
- ke();
21
+ qe();
21
22
  });
22
- const at = () => ({
23
- dracoDecoderPath: I,
24
- ktx2TranscoderPath: X
23
+ const dt = () => ({
24
+ dracoDecoderPath: $,
25
+ ktx2TranscoderPath: H
25
26
  });
26
- function lt(i) {
27
- I = i, C && C[pe] != I ? (console.debug("Updating Draco decoder path to " + i), C[pe] = I, C.setDecoderPath(I), C.preload()) : console.debug("Setting Draco decoder path to " + i);
27
+ function ht(i) {
28
+ $ = i, C && C[ye] != $ ? (console.debug("Updating Draco decoder path to " + i), C[ye] = $, C.setDecoderPath($), C.preload()) : console.debug("Setting Draco decoder path to " + i);
28
29
  }
29
- function ut(i) {
30
- X = i, $ && $.transcoderPath != X ? (console.debug("Updating KTX2 transcoder path to " + i), $.setTranscoderPath(X), $.init()) : console.debug("Setting KTX2 transcoder path to " + i);
30
+ function gt(i) {
31
+ H = i, B && B.transcoderPath != H ? (console.debug("Updating KTX2 transcoder path to " + i), B.setTranscoderPath(H), B.init()) : console.debug("Setting KTX2 transcoder path to " + i);
31
32
  }
32
- function Le(i) {
33
- return ke(), i ? $.detectSupport(i) : i !== null && console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"), { dracoLoader: C, ktx2Loader: $, meshoptDecoder: ne };
33
+ function _e(i) {
34
+ return qe(), i ? B.detectSupport(i) : i !== null && console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"), { dracoLoader: C, ktx2Loader: B, meshoptDecoder: le };
34
35
  }
35
- function Re(i) {
36
- i.dracoLoader || i.setDRACOLoader(C), i.ktx2Loader || i.setKTX2Loader($), i.meshoptDecoder || i.setMeshoptDecoder(ne);
36
+ function Ge(i) {
37
+ i.dracoLoader || i.setDRACOLoader(C), i.ktx2Loader || i.setKTX2Loader(B), i.meshoptDecoder || i.setMeshoptDecoder(le);
37
38
  }
38
- const pe = /* @__PURE__ */ Symbol("dracoDecoderPath");
39
- let C, ne, $;
40
- function ke() {
41
- C || (C = new st(), C[pe] = I, C.setDecoderPath(I), C.setDecoderConfig({ type: "js" }), C.preload()), $ || ($ = new rt(), $.setTranscoderPath(X), $.init()), ne || (ne = tt);
39
+ const ye = /* @__PURE__ */ Symbol("dracoDecoderPath");
40
+ let C, le, B;
41
+ function qe() {
42
+ C || (C = new at(), C[ye] = $, C.setDecoderPath($), C.preload()), B || (B = new lt(), B.setTranscoderPath(H), B.init()), le || (le = ot);
42
43
  }
43
- const me = /* @__PURE__ */ new WeakMap();
44
- function Ie(i, t) {
45
- let e = me.get(i);
46
- e ? e = Object.assign(e, t) : e = t, me.set(i, e);
44
+ const xe = /* @__PURE__ */ new WeakMap();
45
+ function We(i, t) {
46
+ let e = xe.get(i);
47
+ e ? e = Object.assign(e, t) : e = t, xe.set(i, e);
47
48
  }
48
- const ct = _e.prototype.load;
49
- function dt(...i) {
50
- const t = me.get(this);
49
+ const pt = De.prototype.load;
50
+ function mt(...i) {
51
+ const t = xe.get(this);
51
52
  let e = i[0];
52
- const s = new URL(e, window.location.href);
53
- if (s.hostname.endsWith("needle.tools")) {
54
- const o = t?.progressive !== void 0 ? t.progressive : !0, n = t?.usecase ? t.usecase : "default";
55
- o ? this.requestHeader.Accept = `*/*;progressive=allowed;usecase=${n}` : this.requestHeader.Accept = `*/*;usecase=${n}`, e = s.toString();
53
+ const s = yt(e);
54
+ if (s?.hostname.endsWith("needle.tools")) {
55
+ const n = t?.progressive !== void 0 ? t.progressive : !0, o = t?.usecase ? t.usecase : "default";
56
+ n ? this.requestHeader.Accept = `*/*;progressive=allowed;usecase=${o}` : this.requestHeader.Accept = `*/*;usecase=${o}`, e = s.toString();
57
+ }
58
+ return i[0] = e, pt?.call(this, ...i);
59
+ }
60
+ De.prototype.load = mt;
61
+ function yt(i) {
62
+ try {
63
+ if (i instanceof URL)
64
+ return i;
65
+ const t = globalThis.location?.href;
66
+ return t ? new URL(i, t) : new URL(i);
67
+ } catch {
68
+ return null;
56
69
  }
57
- return i[0] = e, ct?.call(this, ...i);
58
70
  }
59
- _e.prototype.load = dt;
60
- function De(i) {
71
+ function Te(i) {
61
72
  return i != null && i.data != null;
62
73
  }
63
- function ye(i) {
74
+ function we(i) {
64
75
  const t = i.source?.data;
65
76
  return t != null && typeof t == "object" ? t : null;
66
77
  }
67
- function ft(i) {
78
+ function xt(i) {
68
79
  const t = i.image;
69
80
  return t != null && typeof t == "object" ? t : null;
70
81
  }
71
- function xe(i) {
72
- const t = ft(i), e = ye(i);
82
+ function Le(i) {
83
+ const t = xt(i), e = we(i);
73
84
  return {
74
85
  width: t?.width || e?.width || 0,
75
86
  height: t?.height || e?.height || 0
76
87
  };
77
88
  }
78
- N("debugprogressive");
79
- function N(i) {
80
- if (typeof window > "u") return !1;
81
- const e = new URL(window.location.href).searchParams.get(i);
82
- return e == null || e === "0" || e === "false" ? !1 : e === "" ? !0 : e;
89
+ V("debugprogressive");
90
+ function V(i) {
91
+ const t = globalThis.location?.href;
92
+ if (!t) return !1;
93
+ const s = new URL(t).searchParams.get(i);
94
+ return s == null || s === "0" || s === "false" ? !1 : s === "" ? !0 : s;
83
95
  }
84
- function ht(i, t) {
96
+ function wt(i, t) {
85
97
  if (t === void 0 || i === void 0 || t.startsWith("./") || t.startsWith("http") || t.startsWith("data:") || t.startsWith("blob:"))
86
98
  return t;
87
99
  const e = i.lastIndexOf("/");
@@ -92,18 +104,21 @@ function ht(i, t) {
92
104
  }
93
105
  return t;
94
106
  }
95
- function ve() {
96
- return H !== void 0 || (H = /iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent), N("debugprogressive") && console.log("[glTF Progressive]: isMobileDevice", H)), H;
107
+ function Me() {
108
+ if (Z !== void 0) return Z;
109
+ const i = globalThis.navigator?.userAgent || "";
110
+ return Z = /iPhone|iPad|iPod|Android|IEMobile/i.test(i), V("debugprogressive") && console.log("[glTF Progressive]: isMobileDevice", Z), Z;
97
111
  }
98
- let H;
99
- function Oe() {
100
- if (typeof window > "u") return !1;
101
- const i = new URL(window.location.href), t = i.hostname === "localhost" || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(i.hostname);
102
- return i.hostname === "127.0.0.1" || t;
112
+ let Z;
113
+ function Fe() {
114
+ const i = globalThis.location?.href;
115
+ if (!i) return !1;
116
+ const t = new URL(i), e = t.hostname === "localhost" || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(t.hostname);
117
+ return t.hostname === "127.0.0.1" || e;
103
118
  }
104
- class gt {
119
+ class Lt {
105
120
  constructor(t, e = {}) {
106
- this.maxConcurrent = t, this.debug = e.debug ?? !1, window.requestAnimationFrame(this.tick);
121
+ this.maxConcurrent = t, this.debug = e.debug ?? !1, typeof globalThis.requestAnimationFrame == "function" ? globalThis.requestAnimationFrame(this.tick) : setTimeout(this.tick, 0);
107
122
  }
108
123
  _running = /* @__PURE__ */ new Map();
109
124
  _queue = [];
@@ -130,85 +145,86 @@ class gt {
130
145
  this.debug && console.debug(`[PromiseQueue]: Running ${this._running.size} promises, waiting for ${this._queue.length} more.`);
131
146
  const { key: s, resolve: r } = this._queue.shift();
132
147
  r({
133
- use: (o) => this.add(s, o)
148
+ use: (n) => this.add(s, n)
134
149
  });
135
150
  }
136
151
  }
137
152
  }
138
- function pt(i) {
139
- const t = i.image, e = t?.width ?? 0, s = t?.height ?? 0, r = t?.depth ?? 1, o = Math.floor(Math.log2(Math.max(e, s, r))) + 1, n = mt(i);
140
- return e * s * r * n * (1 - Math.pow(0.25, o)) / (1 - 0.25);
153
+ function vt(i) {
154
+ const t = i.image, e = t?.width ?? 0, s = t?.height ?? 0, r = t?.depth ?? 1, n = Math.floor(Math.log2(Math.max(e, s, r))) + 1, o = Dt(i);
155
+ return e * s * r * o * (1 - Math.pow(0.25, n)) / (1 - 0.25);
141
156
  }
142
- function mt(i) {
157
+ function Dt(i) {
143
158
  let t = 4;
144
159
  const e = i.format;
145
- e === Ue || e === ze ? t = 1 : e === Ne || e === qe ? t = 2 : e === Ve || e === 1029 ? t = 3 : (e === Ee || e === je) && (t = 4);
160
+ e === je || e === Ke ? t = 1 : e === He || e === Ye ? t = 2 : e === Qe || e === 1029 ? t = 3 : (e === Je || e === Ze) && (t = 4);
146
161
  let s = 1;
147
162
  const r = i.type;
148
163
  return r === 1009 || r === 1010 ? s = 1 : r === 1011 || r === 1012 ? s = 2 : r === 1013 || r === 1014 || r === 1015 ? s = 4 : r === 1016 && (s = 2), t * s;
149
164
  }
150
- const yt = typeof window > "u" && typeof document > "u", we = /* @__PURE__ */ Symbol("needle:raycast-mesh");
151
- function ee(i) {
152
- return i?.[we] instanceof E ? i[we] : null;
165
+ const _t = typeof window > "u" && typeof document > "u", ve = /* @__PURE__ */ Symbol("needle:raycast-mesh");
166
+ function ne(i) {
167
+ return i?.[ve] instanceof j ? i[ve] : null;
153
168
  }
154
- function xt(i, t) {
155
- if ((i.type === "Mesh" || i.type === "SkinnedMesh") && !ee(i)) {
156
- const s = _t(t);
157
- s.userData = { isRaycastMesh: !0 }, i[we] = s;
169
+ function Mt(i, t) {
170
+ if ((i.type === "Mesh" || i.type === "SkinnedMesh") && !ne(i)) {
171
+ const s = Tt(t);
172
+ s.userData = { isRaycastMesh: !0 }, i[ve] = s;
158
173
  }
159
174
  }
160
- function wt(i = !0) {
175
+ function bt(i = !0) {
161
176
  if (i) {
162
- if (Q) return;
163
- const t = Q = j.prototype.raycast;
164
- j.prototype.raycast = function(e, s) {
165
- const r = this, o = ee(r);
166
- let n;
167
- o && r.isMesh && (n = r.geometry, r.geometry = o), t.call(this, e, s), n && (r.geometry = n);
177
+ if (ee) return;
178
+ const t = ee = K.prototype.raycast;
179
+ K.prototype.raycast = function(e, s) {
180
+ const r = this, n = ne(r);
181
+ let o;
182
+ n && r.isMesh && (o = r.geometry, r.geometry = n), t.call(this, e, s), o && (r.geometry = o);
168
183
  };
169
184
  } else {
170
- if (!Q) return;
171
- j.prototype.raycast = Q, Q = null;
185
+ if (!ee) return;
186
+ K.prototype.raycast = ee, ee = null;
172
187
  }
173
188
  }
174
- let Q = null;
175
- function _t(i) {
176
- const t = new E();
189
+ let ee = null;
190
+ function Tt(i) {
191
+ const t = new j();
177
192
  for (const e in i.attributes)
178
193
  t.setAttribute(e, i.getAttribute(e));
179
194
  return t.setIndex(i.getIndex()), t;
180
195
  }
181
- const z = new Array(), f = N("debugprogressive");
182
- let Z, V = -1;
183
- if (f) {
196
+ const F = new Array(), h = V("debugprogressive");
197
+ let re, X = -1;
198
+ if (h && typeof window < "u") {
184
199
  let i = function() {
185
- V += 1, V >= t && (V = -1), console.log(`Toggle LOD level [${V}]`);
186
- }, t = 6;
200
+ X += 1, X >= t && (X = -1), console.log(`Toggle LOD level [${X}]`);
201
+ };
202
+ const t = 6;
187
203
  window.addEventListener("keyup", (e) => {
188
- e.key === "p" && i(), e.key === "w" && (Z = !Z, console.log(`Toggle wireframe [${Z}]`));
204
+ e.key === "p" && i(), e.key === "w" && (re = !re, console.log(`Toggle wireframe [${re}]`));
189
205
  const s = parseInt(e.key);
190
- !isNaN(s) && s >= 0 && (V = s, console.log(`Set LOD level to [${V}]`));
206
+ !isNaN(s) && s >= 0 && (X = s, console.log(`Set LOD level to [${X}]`));
191
207
  });
192
208
  }
193
- function $e(i) {
194
- if (f && Z !== void 0)
209
+ function Ue(i) {
210
+ if (h && re !== void 0)
195
211
  if (Array.isArray(i))
196
212
  for (const t of i)
197
- $e(t);
198
- else i && "wireframe" in i && (i.wireframe = Z === !0);
213
+ Ue(t);
214
+ else i && "wireframe" in i && (i.wireframe = re === !0);
199
215
  }
200
- const J = new Array();
201
- let Lt = 0;
202
- const vt = ve() ? 2 : 10;
203
- function bt(i) {
204
- if (J.length < vt) {
205
- const s = J.length;
206
- f && console.warn(`[Worker] Creating new worker #${s}`);
216
+ const te = new Array();
217
+ let Ot = 0;
218
+ const St = Me() ? 2 : 10;
219
+ function Rt(i) {
220
+ if (te.length < St) {
221
+ const s = te.length;
222
+ h && console.warn(`[Worker] Creating new worker #${s}`);
207
223
  const r = be.createWorker(i || {});
208
- return J.push(r), r;
224
+ return te.push(r), r;
209
225
  }
210
- const t = Lt++ % J.length;
211
- return J[t];
226
+ const t = Ot++ % te.length;
227
+ return te[t];
212
228
  }
213
229
  class be {
214
230
  constructor(t, e) {
@@ -216,11 +232,11 @@ class be {
216
232
  const r = s.data;
217
233
  switch (this._debug && console.log("[Worker] EVENT", r), r.type) {
218
234
  case "loaded-gltf":
219
- for (const o of this._running)
220
- if (o.url === r.result.url) {
221
- Mt(r.result), o.resolve(r.result);
222
- const n = o.url;
223
- n.startsWith("blob:") && URL.revokeObjectURL(n);
235
+ for (const n of this._running)
236
+ if (n.url === r.result.url) {
237
+ Pt(r.result), n.resolve(r.result);
238
+ const o = n.url;
239
+ o.startsWith("blob:") && URL.revokeObjectURL(o);
224
240
  }
225
241
  }
226
242
  }, t.onerror = (s) => {
@@ -242,22 +258,29 @@ class be {
242
258
  _running = [];
243
259
  _webglRenderer = null;
244
260
  async load(t, e) {
245
- const s = at();
261
+ const s = dt();
246
262
  let r = e?.renderer;
247
263
  r || (this._webglRenderer ??= (async () => {
248
264
  const { WebGLRenderer: u } = await import("three");
249
265
  return new u();
250
266
  })(), r = await this._webglRenderer);
251
- const l = Le(r).ktx2Loader.workerConfig;
252
- t instanceof URL ? t = t.toString() : t.startsWith("file:") ? t = URL.createObjectURL(new Blob([t])) : !t.startsWith("blob:") && !t.startsWith("http:") && !t.startsWith("https:") && (t = new URL(t, window.location.href).toString());
253
- const a = {
267
+ const a = _e(r).ktx2Loader.workerConfig;
268
+ if (t instanceof URL)
269
+ t = t.toString();
270
+ else if (t.startsWith("file:"))
271
+ t = URL.createObjectURL(new Blob([t]));
272
+ else if (!t.startsWith("blob:") && !t.startsWith("http:") && !t.startsWith("https:")) {
273
+ const u = globalThis.location?.href;
274
+ u && (t = new URL(t, u).toString());
275
+ }
276
+ const l = {
254
277
  type: "load",
255
278
  url: t,
256
279
  dracoDecoderPath: s.dracoDecoderPath,
257
280
  ktx2TranscoderPath: s.ktx2TranscoderPath,
258
- ktx2LoaderConfig: l
281
+ ktx2LoaderConfig: a
259
282
  };
260
- return this._debug && console.debug("[Worker] Sending load request", a), this.worker.postMessage(a), new Promise((u) => {
283
+ return this._debug && console.debug("[Worker] Sending load request", l), this.worker.postMessage(l), new Promise((u) => {
261
284
  this._running.push({
262
285
  url: t.toString(),
263
286
  resolve: u
@@ -266,32 +289,32 @@ class be {
266
289
  }
267
290
  _debug = !1;
268
291
  }
269
- function Mt(i) {
292
+ function Pt(i) {
270
293
  for (const t of i.geometries) {
271
- const e = t.geometry, s = new E();
294
+ const e = t.geometry, s = new j();
272
295
  if (s.name = e.name || "", e.index) {
273
296
  const r = e.index;
274
- s.setIndex(le(r));
297
+ s.setIndex(fe(r));
275
298
  }
276
299
  for (const r in e.attributes) {
277
- const o = e.attributes[r], n = le(o);
278
- s.setAttribute(r, n);
300
+ const n = e.attributes[r], o = fe(n);
301
+ s.setAttribute(r, o);
279
302
  }
280
303
  if (e.morphAttributes)
281
304
  for (const r in e.morphAttributes) {
282
- const n = e.morphAttributes[r].map((l) => le(l));
283
- s.morphAttributes[r] = n;
305
+ const o = e.morphAttributes[r].map((a) => fe(a));
306
+ s.morphAttributes[r] = o;
284
307
  }
285
- if (s.morphTargetsRelative = e.morphTargetsRelative ?? !1, s.boundingBox = new ge(), s.boundingBox.min = new A(
308
+ if (s.morphTargetsRelative = e.morphTargetsRelative ?? !1, s.boundingBox = new ue(), s.boundingBox.min = new k(
286
309
  e.boundingBox?.min.x,
287
310
  e.boundingBox?.min.y,
288
311
  e.boundingBox?.min.z
289
- ), s.boundingBox.max = new A(
312
+ ), s.boundingBox.max = new k(
290
313
  e.boundingBox?.max.x,
291
314
  e.boundingBox?.max.y,
292
315
  e.boundingBox?.max.z
293
- ), s.boundingSphere = new Ce(
294
- new A(
316
+ ), s.boundingSphere = new Ae(
317
+ new k(
295
318
  e.boundingSphere?.center.x,
296
319
  e.boundingSphere?.center.y,
297
320
  e.boundingSphere?.center.z
@@ -306,11 +329,11 @@ function Mt(i) {
306
329
  const e = t.texture;
307
330
  let s = null;
308
331
  if (e.isCompressedTexture) {
309
- const r = e.mipmaps, { width: o, height: n } = xe(e);
310
- s = new Xe(
332
+ const r = e.mipmaps, { width: n, height: o } = Le(e);
333
+ s = new et(
311
334
  r,
312
- o,
313
335
  n,
336
+ o,
314
337
  e.format,
315
338
  e.type,
316
339
  e.mapping,
@@ -322,7 +345,7 @@ function Mt(i) {
322
345
  e.colorSpace
323
346
  );
324
347
  } else
325
- s = new F(
348
+ s = new q(
326
349
  e.image,
327
350
  e.mapping,
328
351
  e.wrapS,
@@ -333,7 +356,7 @@ function Mt(i) {
333
356
  e.type,
334
357
  e.anisotropy,
335
358
  e.colorSpace
336
- ), s.mipmaps = e.mipmaps, s.channel = e.channel, s.source.data = e.source.data, s.flipY = e.flipY, s.premultiplyAlpha = e.premultiplyAlpha, s.unpackAlignment = e.unpackAlignment, s.matrix = new Ke(...e.matrix.elements);
359
+ ), s.mipmaps = e.mipmaps, s.channel = e.channel, s.source.data = e.source.data, s.flipY = e.flipY, s.premultiplyAlpha = e.premultiplyAlpha, s.unpackAlignment = e.unpackAlignment, s.matrix = new tt(...e.matrix.elements);
337
360
  if (!s) {
338
361
  console.error("[Worker] Failed to create new texture from received data. Texture is not a CompressedTexture or Texture.");
339
362
  continue;
@@ -342,66 +365,89 @@ function Mt(i) {
342
365
  }
343
366
  return i;
344
367
  }
345
- function le(i) {
368
+ function fe(i) {
346
369
  let t = i;
347
370
  if ("isInterleavedBufferAttribute" in i && i.isInterleavedBufferAttribute) {
348
- const e = i.data, s = e.array, r = new Ye(s, e.stride);
349
- t = new He(r, i.itemSize, s.byteOffset, i.normalized), t.offset = i.offset;
350
- } else "isBufferAttribute" in i && i.isBufferAttribute && (t = new Qe(i.array, i.itemSize, i.normalized), t.usage = i.usage, t.gpuType = i.gpuType, t.updateRanges = i.updateRanges);
371
+ const e = i.data, s = e.array, r = new st(s, e.stride);
372
+ t = new rt(r, i.itemSize, s.byteOffset, i.normalized), t.offset = i.offset;
373
+ } else "isBufferAttribute" in i && i.isBufferAttribute && (t = new nt(i.array, i.itemSize, i.normalized), t.usage = i.usage, t.gpuType = i.gpuType, t.updateRanges = i.updateRanges);
351
374
  return t;
352
375
  }
353
- const Dt = N("gltf-progressive-worker");
354
- N("gltf-progressive-reduce-mipmaps");
355
- const K = N("gltf-progressive-gc"), ue = /* @__PURE__ */ Symbol("needle-progressive-texture"), W = "NEEDLE_progressive";
356
- class m {
376
+ const Ct = V("gltf-progressive-worker");
377
+ V("gltf-progressive-reduce-mipmaps");
378
+ const se = V("gltf-progressive-gc"), de = /* @__PURE__ */ Symbol("needle-progressive-texture"), U = "NEEDLE_progressive";
379
+ class g {
357
380
  /** The name of the extension */
358
381
  get name() {
359
- return W;
382
+ return U;
360
383
  }
361
384
  // #region PUBLIC API
385
+ /**
386
+ * Get the progressive mesh LOD extension data associated with a geometry.
387
+ * Returns the extension metadata (available LOD levels, vertex/index counts, densities) if the geometry was registered with progressive LODs, or `null` otherwise.
388
+ *
389
+ * @param geo - The buffer geometry to look up.
390
+ * @returns The mesh LOD extension data, or `null` if no progressive LODs are registered for this geometry.
391
+ */
362
392
  static getMeshLODExtension(t) {
363
393
  const e = this.getAssignedLODInformation(t);
364
394
  return e?.key ? this.lodInfos.get(e.key) : null;
365
395
  }
396
+ /**
397
+ * Get the glTF primitive index for a geometry within its parent mesh.
398
+ * A single glTF mesh node can contain multiple primitives (sub-geometries). This returns which primitive the geometry corresponds to.
399
+ *
400
+ * @param geo - The buffer geometry to look up.
401
+ * @returns The zero-based primitive index, or `-1` if no LOD information is assigned to this geometry.
402
+ */
366
403
  static getPrimitiveIndex(t) {
367
404
  const e = this.getAssignedLODInformation(t)?.index;
368
405
  return e ?? -1;
369
406
  }
407
+ /**
408
+ * Compute the minimum and maximum number of texture LOD levels across all textures of a material (or array of materials).
409
+ * Iterates over all texture slots on the material and collects LOD count ranges and per-level resolution bounds.
410
+ * Results are cached on the material so subsequent calls are free.
411
+ *
412
+ * @param material - A single material or an array of materials to inspect.
413
+ * @param minmax - Optional accumulator to merge results into (used internally for recursive calls with material arrays).
414
+ * @returns An object with `min_count` / `max_count` (the range of LOD levels across all textures) and a per-level `lods` array with `min_height` / `max_height`.
415
+ */
370
416
  static getMaterialMinMaxLODsCount(t, e) {
371
- const s = this, r = "LODS:minmax", o = t[r];
372
- if (o != null) return o;
417
+ const s = this, r = "LODS:minmax", n = t[r];
418
+ if (n != null) return n;
373
419
  if (e || (e = {
374
420
  min_count: 1 / 0,
375
421
  max_count: 0,
376
422
  lods: []
377
423
  }), Array.isArray(t)) {
378
- for (const l of t)
379
- this.getMaterialMinMaxLODsCount(l, e);
424
+ for (const a of t)
425
+ this.getMaterialMinMaxLODsCount(a, e);
380
426
  return t[r] = e, e;
381
427
  }
382
- if (f === "verbose" && console.log("getMaterialMinMaxLODsCount", t), t.type === "ShaderMaterial" || t.type === "RawShaderMaterial") {
383
- const l = t;
384
- for (const a of Object.keys(l.uniforms)) {
385
- const u = l.uniforms[a].value;
386
- u?.isTexture === !0 && n(u, e);
428
+ if (h === "verbose" && console.log("getMaterialMinMaxLODsCount", t), t.type === "ShaderMaterial" || t.type === "RawShaderMaterial") {
429
+ const a = t;
430
+ for (const l of Object.keys(a.uniforms)) {
431
+ const u = a.uniforms[l].value;
432
+ u?.isTexture === !0 && o(u, e);
387
433
  }
388
434
  } else if (t.isMaterial)
389
- for (const l of Object.keys(t)) {
390
- const a = t[l];
391
- a?.isTexture === !0 && n(a, e);
435
+ for (const a of Object.keys(t)) {
436
+ const l = t[a];
437
+ l?.isTexture === !0 && o(l, e);
392
438
  }
393
439
  else
394
- f && console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);
440
+ h && console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);
395
441
  return t[r] = e, e;
396
- function n(l, a) {
397
- const u = s.getAssignedLODInformation(l);
442
+ function o(a, l) {
443
+ const u = s.getAssignedLODInformation(a);
398
444
  if (u) {
399
- const c = s.lodInfos.get(u.key);
400
- if (c && c.lods) {
401
- a.min_count = Math.min(a.min_count, c.lods.length), a.max_count = Math.max(a.max_count, c.lods.length);
402
- for (let y = 0; y < c.lods.length; y++) {
403
- const w = c.lods[y];
404
- w.width && (a.lods[y] = a.lods[y] || { min_height: 1 / 0, max_height: 0 }, a.lods[y].min_height = Math.min(a.lods[y].min_height, w.height), a.lods[y].max_height = Math.max(a.lods[y].max_height, w.height));
445
+ const f = s.lodInfos.get(u.key);
446
+ if (f && f.lods) {
447
+ l.min_count = Math.min(l.min_count, f.lods.length), l.max_count = Math.max(l.max_count, f.lods.length);
448
+ for (let c = 0; c < f.lods.length; c++) {
449
+ const d = f.lods[c];
450
+ d.width && (l.lods[c] = l.lods[c] || { min_height: 1 / 0, max_height: 0 }, l.lods[c].min_height = Math.min(l.lods[c].min_height, d.height), l.lods[c].max_height = Math.max(l.lods[c].max_height, d.height));
405
451
  }
406
452
  }
407
453
  }
@@ -414,26 +460,26 @@ class m {
414
460
  */
415
461
  static hasLODLevelAvailable(t, e) {
416
462
  if (Array.isArray(t)) {
417
- for (const o of t)
418
- if (this.hasLODLevelAvailable(o, e)) return !0;
463
+ for (const n of t)
464
+ if (this.hasLODLevelAvailable(n, e)) return !0;
419
465
  return !1;
420
466
  }
421
467
  if (t.isMaterial === !0) {
422
- for (const o of Object.keys(t)) {
423
- const n = t[o];
424
- if (n && n.isTexture && this.hasLODLevelAvailable(n, e))
468
+ for (const n of Object.keys(t)) {
469
+ const o = t[n];
470
+ if (o && o.isTexture && this.hasLODLevelAvailable(o, e))
425
471
  return !0;
426
472
  }
427
473
  return !1;
428
474
  } else if (t.isGroup === !0) {
429
- for (const o of t.children)
430
- if (o.isMesh === !0 && this.hasLODLevelAvailable(o, e))
475
+ for (const n of t.children)
476
+ if (n.isMesh === !0 && this.hasLODLevelAvailable(n, e))
431
477
  return !0;
432
478
  }
433
479
  let s, r;
434
480
  if (t.isMesh ? s = t.geometry : (t.isBufferGeometry || t.isTexture) && (s = t), s && s?.userData?.LODS) {
435
- const o = s.userData.LODS;
436
- if (r = this.lodInfos.get(o.key), e === void 0) return r != null;
481
+ const n = s.userData.LODS;
482
+ if (r = this.lodInfos.get(n.key), e === void 0) return r != null;
437
483
  if (r)
438
484
  return Array.isArray(r.lods) ? e < r.lods.length : e === 0;
439
485
  }
@@ -453,74 +499,80 @@ class m {
453
499
  * });
454
500
  * ```
455
501
  */
456
- static assignMeshLOD(t, e) {
502
+ static assignMeshLOD(t, e, s) {
457
503
  if (!t) return Promise.resolve(null);
458
- if (t instanceof j || t.isMesh === !0) {
459
- const s = t.geometry, r = this.getAssignedLODInformation(s);
460
- if (!r)
504
+ if (t instanceof K || t.isMesh === !0) {
505
+ const r = t.geometry, n = this.getAssignedLODInformation(r);
506
+ if (!n)
461
507
  return Promise.resolve(null);
462
- for (const o of z)
463
- o.onBeforeGetLODMesh?.(t, e);
464
- return t["LOD:requested level"] = e, m.getOrLoadLOD(s, e).then((o) => {
465
- if (Array.isArray(o)) {
466
- const n = r.index || 0;
467
- o = o[n];
508
+ for (const a of F)
509
+ a.onBeforeGetLODMesh?.(t, e);
510
+ t["LOD:requested level"] = e;
511
+ const o = () => t["LOD:requested level"] === e || this.shouldApplyStaleMeshLOD(t, e);
512
+ return g.getOrLoadLOD(r, e, {
513
+ isCurrent: o
514
+ }).then((a) => {
515
+ if (Array.isArray(a)) {
516
+ const u = n.index || 0;
517
+ a = a[u];
468
518
  }
469
- return t["LOD:requested level"] === e && (delete t["LOD:requested level"], o && s != o && (o?.isBufferGeometry ? t.geometry = o : f && console.error("Invalid LOD geometry", o))), o;
470
- }).catch((o) => (console.error("Error loading mesh LOD", t, o), null));
471
- } else f && console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh", t);
519
+ const l = t["LOD:requested level"] === e;
520
+ return (l || this.shouldApplyStaleMeshLOD(t, e)) && (l && delete t["LOD:requested level"], a && r != a && (a?.isBufferGeometry ? typeof s?.apply == "function" ? s.apply(a, e, t) : s?.apply !== !1 && (t.geometry = a) : h && console.error("Invalid LOD geometry", a))), a;
521
+ }).catch((a) => (console.error("Error loading mesh LOD", t, a), null));
522
+ } else h && console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh", t);
472
523
  return Promise.resolve(null);
473
524
  }
474
- static assignTextureLOD(t, e = 0) {
525
+ static assignTextureLOD(t, e = 0, s) {
475
526
  if (!t) return Promise.resolve(null);
527
+ const r = s?.force === !0;
476
528
  if (t.isMesh === !0) {
477
- const s = t;
478
- if (Array.isArray(s.material)) {
479
- const r = new Array();
480
- for (const o of s.material) {
481
- const n = this.assignTextureLOD(o, e);
482
- r.push(n);
529
+ const n = t;
530
+ if (Array.isArray(n.material)) {
531
+ const o = new Array();
532
+ for (const a of n.material) {
533
+ const l = this.assignTextureLOD(a, e, s);
534
+ o.push(l);
483
535
  }
484
- return Promise.all(r).then((o) => {
485
- const n = new Array();
486
- for (const l of o)
487
- Array.isArray(l) && n.push(...l);
488
- return n;
536
+ return Promise.all(o).then((a) => {
537
+ const l = new Array();
538
+ for (const u of a)
539
+ Array.isArray(u) && l.push(...u);
540
+ return l;
489
541
  });
490
542
  } else
491
- return this.assignTextureLOD(s.material, e);
543
+ return this.assignTextureLOD(n.material, e, s);
492
544
  }
493
545
  if (t.isMaterial === !0) {
494
- const s = t, r = [], o = new Array();
495
- if (s.uniforms && (s.isRawShaderMaterial || s.isShaderMaterial === !0)) {
496
- const n = s;
497
- for (const l of Object.keys(n.uniforms)) {
498
- const a = n.uniforms[l].value;
499
- if (a?.isTexture === !0) {
500
- const u = this.assignTextureLODForSlot(a, e, s, l).then((c) => (c && n.uniforms[l].value != c && (n.uniforms[l].value = c, n.uniformsNeedUpdate = !0), c));
501
- r.push(u), o.push(l);
546
+ const n = t, o = [], a = new Array();
547
+ if (this.trackCurrentMaterialTextureSlots(n), n.uniforms && (n.isRawShaderMaterial || n.isShaderMaterial === !0)) {
548
+ const l = n;
549
+ for (const u of Object.keys(l.uniforms)) {
550
+ const f = l.uniforms[u].value;
551
+ if (f?.isTexture === !0) {
552
+ const c = this.assignTextureLODForSlot(f, e, n, u, r).then((d) => (d && l.uniforms[u].value != d && (l.uniforms[u].value = d, l.uniformsNeedUpdate = !0), d));
553
+ o.push(c), a.push(u);
502
554
  }
503
555
  }
504
556
  } else
505
- for (const n of Object.keys(s)) {
506
- const l = s[n];
507
- if (l?.isTexture === !0) {
508
- const a = this.assignTextureLODForSlot(l, e, s, n);
509
- r.push(a), o.push(n);
557
+ for (const l of Object.keys(n)) {
558
+ const u = n[l];
559
+ if (u?.isTexture === !0) {
560
+ const f = this.assignTextureLODForSlot(u, e, n, l, r);
561
+ o.push(f), a.push(l);
510
562
  }
511
563
  }
512
- return Promise.all(r).then((n) => {
513
- const l = new Array();
514
- for (let a = 0; a < n.length; a++) {
515
- const u = n[a], c = o[a];
516
- u && u.isTexture === !0 ? l.push({ material: s, slot: c, texture: u, level: e }) : l.push({ material: s, slot: c, texture: null, level: e });
564
+ return Promise.all(o).then((l) => {
565
+ const u = new Array();
566
+ for (let f = 0; f < l.length; f++) {
567
+ const c = l[f], d = a[f];
568
+ c && c.isTexture === !0 ? u.push({ material: n, slot: d, texture: c, level: e }) : u.push({ material: n, slot: d, texture: null, level: e });
517
569
  }
518
- return l;
570
+ return u;
519
571
  });
520
572
  }
521
- if (t instanceof F || t.isTexture === !0) {
522
- const s = t;
523
- return this.assignTextureLODForSlot(s, e, null, null);
573
+ if (t instanceof q || t.isTexture === !0) {
574
+ const n = t;
575
+ return this.assignTextureLODForSlot(n, e, null, null, r);
524
576
  }
525
577
  return Promise.resolve(null);
526
578
  }
@@ -529,46 +581,144 @@ class m {
529
581
  * @default 50 on desktop, 20 on mobile devices
530
582
  */
531
583
  static set maxConcurrentLoadingTasks(t) {
532
- m.queue.maxConcurrent = t;
584
+ g.queue.maxConcurrent = t;
533
585
  }
534
586
  static get maxConcurrentLoadingTasks() {
535
- return m.queue.maxConcurrent;
587
+ return g.queue.maxConcurrent;
536
588
  }
537
589
  // #region INTERNAL
538
- static assignTextureLODForSlot(t, e, s, r) {
539
- return t?.isTexture !== !0 ? Promise.resolve(null) : r === "glyphMap" ? Promise.resolve(t) : m.getOrLoadLOD(t, e).then((o) => {
540
- if (Array.isArray(o))
590
+ static assignTextureLODForSlot(t, e, s, r, n) {
591
+ if (t?.isTexture !== !0)
592
+ return Promise.resolve(null);
593
+ if (r === "glyphMap")
594
+ return Promise.resolve(t);
595
+ const o = this.getAssignedLODInformation(t);
596
+ if (o && (o.level === e || !n && o.level < e))
597
+ return Promise.resolve(t);
598
+ if (s && r) {
599
+ const c = this.getPendingTextureSlotRequest(s, r);
600
+ if (c && c.level === e && c.force === n)
601
+ return c.promise;
602
+ }
603
+ const a = s && r ? this.nextTextureSlotRequestId(s, r, e, n) : 0, l = () => !s || !r || this.getLatestTextureSlotRequest(s, r)?.id === a, u = () => l() || this.shouldApplyStaleTextureSlotLOD(s, r, e, n), f = g.getOrLoadLOD(t, e, {
604
+ isCurrent: u
605
+ }).then((c) => {
606
+ if (!l() && !this.shouldApplyStaleTextureSlotLOD(s, r, e, n)) return null;
607
+ if (Array.isArray(c))
541
608
  return console.warn("Progressive: Got an array of textures for a texture slot, this should not happen..."), null;
542
- if (o?.isTexture === !0) {
543
- if (o != t && s && r) {
544
- const n = s[r];
545
- if (n && !f) {
546
- const l = this.getAssignedLODInformation(n);
547
- if (l && l?.level < e)
548
- return f === "verbose" && console.warn("Assigned texture level is already higher: ", l.level, e, s, n, o), o && o !== n && ((f || K) && console.log(`[gltf-progressive] Disposing rejected lower-quality texture LOD ${e} (assigned is ${l.level})`, o.uuid), o.dispose()), null;
609
+ if (c?.isTexture === !0) {
610
+ if (c != t && s && r) {
611
+ const d = this.getMaterialTextureSlot(s, r) ?? t;
612
+ if (d && !n) {
613
+ const p = this.getAssignedLODInformation(d);
614
+ if (p && p?.level < e)
615
+ return h === "verbose" && console.warn("Assigned texture level is already higher: ", p.level, e, s, d, c), null;
549
616
  }
550
- if (this.trackTextureUsage(o), n && n !== o && this.untrackTextureUsage(n) && (f || K)) {
551
- const a = this.getAssignedLODInformation(n);
552
- console.log(`[gltf-progressive] Disposed old texture LOD ${a?.level ?? "?"} → ${e} for ${s.name || s.type}.${r}`, n.uuid);
553
- }
554
- s[r] = o;
617
+ this.assignTrackedTextureSlot(s, r, c);
555
618
  }
556
- return o;
557
- } else f == "verbose" && console.warn("No LOD found for", t, e);
619
+ return c;
620
+ } else h == "verbose" && console.warn("No LOD found for", t, e);
558
621
  return null;
559
- }).catch((o) => (console.error("Error loading LOD", t, o), null));
622
+ }).catch((c) => (console.error("Error loading LOD", t, c), null));
623
+ return s && r && this.setPendingTextureSlotRequest(s, r, e, n, a, f), f;
624
+ }
625
+ // Track material slots, not just texture objects. A shared fallback texture can be
626
+ // referenced by many slots and should only be disposed after every slot moved away.
627
+ static trackedTextureSlots = /* @__PURE__ */ new WeakMap();
628
+ static pendingTextureSlotRequests = /* @__PURE__ */ new WeakMap();
629
+ static latestTextureSlotRequests = /* @__PURE__ */ new WeakMap();
630
+ static textureSlotRequestId = 0;
631
+ static trackCurrentMaterialTextureSlots(t) {
632
+ if (t.uniforms && (t.isRawShaderMaterial || t.isShaderMaterial === !0)) {
633
+ const e = t;
634
+ for (const s of Object.keys(e.uniforms)) {
635
+ const r = e.uniforms[s].value;
636
+ r?.isTexture === !0 && this.ensureTrackedTextureSlot(t, s, r);
637
+ }
638
+ return;
639
+ }
640
+ for (const e of Object.keys(t)) {
641
+ const s = t[e];
642
+ s?.isTexture === !0 && this.ensureTrackedTextureSlot(t, e, s);
643
+ }
644
+ }
645
+ static getPendingTextureSlotRequest(t, e) {
646
+ return this.pendingTextureSlotRequests.get(t)?.get(e);
647
+ }
648
+ static nextTextureSlotRequestId(t, e, s, r) {
649
+ let n = this.latestTextureSlotRequests.get(t);
650
+ n || (n = /* @__PURE__ */ new Map(), this.latestTextureSlotRequests.set(t, n));
651
+ const o = ++this.textureSlotRequestId;
652
+ return n.set(e, { id: o, level: s, force: r }), o;
653
+ }
654
+ static getLatestTextureSlotRequest(t, e) {
655
+ return this.latestTextureSlotRequests.get(t)?.get(e);
656
+ }
657
+ static shouldApplyStaleTextureSlotLOD(t, e, s, r) {
658
+ if (!t || !e) return !1;
659
+ const n = this.getLatestTextureSlotRequest(t, e), o = this.getMaterialTextureSlot(t, e), a = this.getAssignedLODInformation(o)?.level ?? 1 / 0;
660
+ return s >= a ? !1 : r ? n ? s >= n.level : !1 : !0;
661
+ }
662
+ static shouldApplyStaleMeshLOD(t, e) {
663
+ const s = t["LOD:requested level"];
664
+ if (typeof s != "number") return !1;
665
+ const r = this.getAssignedLODInformation(t.geometry)?.level ?? 1 / 0;
666
+ return e < r && e >= s;
667
+ }
668
+ static setPendingTextureSlotRequest(t, e, s, r, n, o) {
669
+ let a = this.pendingTextureSlotRequests.get(t);
670
+ a || (a = /* @__PURE__ */ new Map(), this.pendingTextureSlotRequests.set(t, a));
671
+ const l = { level: s, force: r, id: n, promise: o };
672
+ a.set(e, l), o.finally(() => {
673
+ a.get(e)?.id === n && a.delete(e);
674
+ });
675
+ }
676
+ static getMaterialTextureSlot(t, e) {
677
+ const r = t.uniforms?.[e];
678
+ if (r?.value?.isTexture === !0)
679
+ return r.value;
680
+ const n = t[e];
681
+ return n?.isTexture === !0 ? n : null;
682
+ }
683
+ static setMaterialTextureSlot(t, e, s) {
684
+ const n = t.uniforms?.[e];
685
+ if (n?.value?.isTexture === !0) {
686
+ n.value = s, t.uniformsNeedUpdate = !0;
687
+ return;
688
+ }
689
+ t[e] = s;
690
+ }
691
+ static assignTrackedTextureSlot(t, e, s) {
692
+ let r = this.trackedTextureSlots.get(t);
693
+ r || (r = /* @__PURE__ */ new Map(), this.trackedTextureSlots.set(t, r));
694
+ const n = this.getMaterialTextureSlot(t, e);
695
+ let o = r.get(e);
696
+ !o && n ? o = this.ensureTrackedTextureSlot(t, e, n) : o && n && o !== n && (this.releaseTrackedTextureSlot(t, e, o), o = this.ensureTrackedTextureSlot(t, e, n)), !(o === s && n === s) && (o && o !== s && this.releaseTrackedTextureSlot(t, e, o), o !== s && (this.trackTextureUsage(s), r.set(e, s)), n !== s && this.setMaterialTextureSlot(t, e, s));
697
+ }
698
+ static ensureTrackedTextureSlot(t, e, s) {
699
+ let r = this.trackedTextureSlots.get(t);
700
+ r || (r = /* @__PURE__ */ new Map(), this.trackedTextureSlots.set(t, r));
701
+ const n = r.get(e);
702
+ return n === s ? n : (n && this.releaseTrackedTextureSlot(t, e, n), this.trackTextureUsage(s), r.set(e, s), s);
703
+ }
704
+ static releaseTrackedTextureSlot(t, e, s) {
705
+ const r = this.trackedTextureSlots.get(t);
706
+ if (r?.get(e) === s && r.delete(e), this.untrackTextureUsage(s) && (h || se)) {
707
+ const o = this.getAssignedLODInformation(s);
708
+ console.log(`[gltf-progressive] Disposed old texture LOD ${o?.level ?? "?"} for ${t.name || t.type}.${e}`, s.uuid);
709
+ }
560
710
  }
561
711
  parser;
562
712
  url;
563
713
  constructor(t) {
564
714
  const e = t.options.path;
565
- f && console.log("Progressive extension registered for", e), this.parser = t, this.url = e;
715
+ h && console.log("Progressive extension registered for", e), this.parser = t, this.url = e;
566
716
  }
567
717
  _isLoadingMesh;
568
718
  loadMesh = (t) => {
569
719
  if (this._isLoadingMesh) return null;
570
- const e = this.parser.json.meshes[t]?.extensions?.[W];
571
- return e ? (this._isLoadingMesh = !0, this.parser.getDependency("mesh", t).then((s) => (this._isLoadingMesh = !1, s && m.registerMesh(this.url, e.guid, s, e.lods?.length, 0, e), s))) : null;
720
+ const e = this.parser.json.meshes[t]?.extensions?.[U];
721
+ return e ? (this._isLoadingMesh = !0, this.parser.getDependency("mesh", t).then((s) => (this._isLoadingMesh = !1, s && g.registerMesh(this.url, e.guid, s, e.lods?.length, 0, e), s))) : null;
572
722
  };
573
723
  // private _isLoadingTexture;
574
724
  // loadTexture = (textureIndex: number) => {
@@ -585,65 +735,83 @@ class m {
585
735
  // });
586
736
  // }
587
737
  afterRoot(t) {
588
- return f && console.log("AFTER", this.url, t), this.parser.json.textures?.forEach((e, s) => {
738
+ return h && console.log("AFTER", this.url, t), this.parser.json.textures?.forEach((e, s) => {
589
739
  if (e?.extensions) {
590
- const r = e?.extensions[W];
740
+ const r = e?.extensions[U];
591
741
  if (r) {
592
742
  if (!r.lods) {
593
- f && console.warn("Texture has no LODs", r);
743
+ h && console.warn("Texture has no LODs", r);
594
744
  return;
595
745
  }
596
- let o = !1;
597
- for (const n of this.parser.associations.keys())
598
- n.isTexture === !0 && this.parser.associations.get(n)?.textures === s && (o = !0, m.registerTexture(this.url, n, r.lods?.length, s, r));
599
- o || this.parser.getDependency("texture", s).then((n) => {
600
- n && m.registerTexture(this.url, n, r.lods?.length, s, r);
746
+ let n = !1;
747
+ for (const o of this.parser.associations.keys())
748
+ o.isTexture === !0 && this.parser.associations.get(o)?.textures === s && (n = !0, g.registerTexture(this.url, o, r.lods?.length, s, r));
749
+ n || this.parser.getDependency("texture", s).then((o) => {
750
+ o && g.registerTexture(this.url, o, r.lods?.length, s, r);
601
751
  });
602
752
  }
603
753
  }
604
754
  }), this.parser.json.meshes?.forEach((e, s) => {
605
755
  if (e?.extensions) {
606
- const r = e?.extensions[W];
756
+ const r = e?.extensions[U];
607
757
  if (r && r.lods) {
608
- for (const o of this.parser.associations.keys())
609
- if (o.isMesh) {
610
- const n = this.parser.associations.get(o);
611
- n?.meshes === s && m.registerMesh(this.url, r.guid, o, r.lods.length, n.primitives, r);
758
+ for (const n of this.parser.associations.keys())
759
+ if (n.isMesh) {
760
+ const o = this.parser.associations.get(n);
761
+ o?.meshes === s && g.registerMesh(this.url, r.guid, n, r.lods.length, o.primitives, r);
612
762
  }
613
763
  }
614
764
  }
615
765
  }), null;
616
766
  }
617
767
  /**
618
- * Register a texture with LOD information
768
+ * Register a texture with progressive LOD information. This associates the texture with its LOD extension data
769
+ * so the LODs manager can later swap it for higher or lower resolution versions based on screen coverage.
770
+ * Typically called during glTF loading when the progressive extension is parsed.
771
+ *
772
+ * @param url - The source URL of the glTF file this texture was loaded from.
773
+ * @param tex - The three.js Texture instance to register.
774
+ * @param level - The LOD level this texture represents (0 = highest resolution).
775
+ * @param index - The texture index within the glTF file.
776
+ * @param ext - The parsed progressive texture extension data containing all available LOD levels and their dimensions.
619
777
  */
620
- static registerTexture = (t, e, s, r, o) => {
778
+ static registerTexture = (t, e, s, r, n) => {
621
779
  if (!e) {
622
- f && console.error("!! gltf-progressive: Called register texture without texture");
780
+ h && console.error("!! gltf-progressive: Called register texture without texture");
623
781
  return;
624
782
  }
625
- if (f) {
626
- const { width: l, height: a } = xe(e);
627
- console.log(`> gltf-progressive: register texture[${r}] "${e.name || e.uuid}", Current: ${l}x${a}, Max: ${o.lods[0]?.width}x${o.lods[0]?.height}, uuid: ${e.uuid}`, o, e);
783
+ if (h) {
784
+ const { width: a, height: l } = Le(e);
785
+ console.log(`> gltf-progressive: register texture[${r}] "${e.name || e.uuid}", Current: ${a}x${l}, Max: ${n.lods[0]?.width}x${n.lods[0]?.height}, uuid: ${e.uuid}`, n, e);
628
786
  }
629
- e.source && (e.source[ue] = o);
630
- const n = o.guid;
631
- m.assignLODInformation(t, e, n, s, r), m.lodInfos.set(n, o), m.lowresCache.set(n, new WeakRef(e));
787
+ e.source && (e.source[de] = n);
788
+ const o = n.guid;
789
+ g.assignLODInformation(t, e, o, s, r), g.lodInfos.set(o, n), g.lowresCache.set(o, new WeakRef(e));
632
790
  };
633
791
  /**
634
- * Register a mesh with LOD information
792
+ * Register a mesh with progressive LOD information. This associates the mesh geometry with its LOD extension data
793
+ * so the LODs manager can later swap it for higher or lower density versions based on screen coverage.
794
+ * Typically called during glTF loading when the progressive extension is parsed.
795
+ * If the mesh is registered at a level > 0 (i.e. not full resolution), a raycast mesh is automatically preserved for accurate picking.
796
+ *
797
+ * @param url - The source URL of the glTF file this mesh was loaded from.
798
+ * @param key - A unique key identifying this mesh's LOD group (typically derived from the extension GUID).
799
+ * @param mesh - The three.js Mesh instance to register.
800
+ * @param level - The LOD level this mesh represents (0 = highest resolution / full density).
801
+ * @param index - The primitive index within the glTF mesh node.
802
+ * @param ext - The parsed progressive mesh extension data containing all available LOD levels with vertex/index counts and densities.
635
803
  */
636
- static registerMesh = (t, e, s, r, o, n) => {
637
- const l = s.geometry;
638
- if (!l) {
639
- f && console.warn("gltf-progressive: Register mesh without geometry");
804
+ static registerMesh = (t, e, s, r, n, o) => {
805
+ const a = s.geometry;
806
+ if (!a) {
807
+ h && console.warn("gltf-progressive: Register mesh without geometry");
640
808
  return;
641
809
  }
642
- l.userData || (l.userData = {}), f && console.log("> Progressive: register mesh " + s.name, { index: o, uuid: s.uuid }, n, s), m.assignLODInformation(t, l, e, r, o), m.lodInfos.set(e, n);
643
- let u = m.lowresCache.get(e)?.deref();
644
- u ? u.push(s.geometry) : u = [s.geometry], m.lowresCache.set(e, new WeakRef(u)), r > 0 && !ee(s) && xt(s, l);
645
- for (const c of z)
646
- c.onRegisteredNewMesh?.(s, n);
810
+ a.userData || (a.userData = {}), h && console.log("> Progressive: register mesh " + s.name, { index: n, uuid: s.uuid }, o, s), g.assignLODInformation(t, a, e, r, n), g.lodInfos.set(e, o);
811
+ let u = g.lowresCache.get(e)?.deref();
812
+ u ? u.push(s.geometry) : u = [s.geometry], g.lowresCache.set(e, new WeakRef(u)), r > 0 && !ne(s) && Mt(s, a);
813
+ for (const f of F)
814
+ f.onRegisteredNewMesh?.(s, o);
647
815
  };
648
816
  /**
649
817
  * Dispose cached resources to free memory.
@@ -684,7 +852,7 @@ class m {
684
852
  this.lowresCache.clear();
685
853
  for (const [, e] of this.cache)
686
854
  this._disposeCacheEntry(e);
687
- this.cache.clear(), this.textureRefCounts.clear();
855
+ this.cache.clear(), this.textureRefCounts.clear(), this.trackedTextureSlots = /* @__PURE__ */ new WeakMap(), this.pendingTextureSlotRequests = /* @__PURE__ */ new WeakMap(), this.latestTextureSlotRequests = /* @__PURE__ */ new WeakMap(), this.textureSlotRequestId = 0;
688
856
  }
689
857
  }
690
858
  /** Dispose a single cache entry's three.js resource(s) to free GPU memory. */
@@ -716,16 +884,16 @@ class m {
716
884
  * The held value is the cache key string used in `previouslyLoaded`.
717
885
  */
718
886
  static _resourceRegistry = new FinalizationRegistry((t) => {
719
- const e = m.cache.get(t);
720
- (f || K) && console.debug(`[gltf-progressive] Memory: Resource GC'd
721
- ${t}`), e instanceof WeakRef && (e.deref() || (m.cache.delete(t), (f || K) && console.log("[gltf-progressive] ↪ Cache entry deleted (GC)")));
887
+ const e = g.cache.get(t);
888
+ (h || se) && console.debug(`[gltf-progressive] Memory: Resource GC'd
889
+ ${t}`), e instanceof WeakRef && (e.deref() || (g.cache.delete(t), (h || se) && console.log("[gltf-progressive] ↪ Cache entry deleted (GC)")));
722
890
  });
723
891
  /**
724
892
  * Track texture usage by incrementing reference count
725
893
  */
726
894
  static trackTextureUsage(t) {
727
895
  const e = t.uuid, s = this.textureRefCounts.get(e) || 0;
728
- this.textureRefCounts.set(e, s + 1), f === "verbose" && console.log(`[gltf-progressive] Track texture ${e}, refCount: ${s} → ${s + 1}`);
896
+ this.textureRefCounts.set(e, s + 1), h === "verbose" && console.log(`[gltf-progressive] Track texture ${e}, refCount: ${s} → ${s + 1}`);
729
897
  }
730
898
  /**
731
899
  * Untrack texture usage by decrementing reference count.
@@ -735,182 +903,196 @@ ${t}`), e instanceof WeakRef && (e.deref() || (m.cache.delete(t), (f || K) && co
735
903
  static untrackTextureUsage(t) {
736
904
  const e = t.uuid, s = this.textureRefCounts.get(e);
737
905
  if (!s)
738
- return (f === "verbose" || K) && o("[gltf-progressive] Memory: Untrack untracked texture (dispose immediately)", 0), t.dispose(), !0;
906
+ return (h === "verbose" || se) && n("[gltf-progressive] Memory: Untrack untracked texture (dispose immediately)", 0), t.dispose(), !0;
739
907
  const r = s - 1;
740
908
  if (r <= 0)
741
- return this.textureRefCounts.delete(e), (f || K) && o("[gltf-progressive] Memory: Dispose texture", r), t.dispose(), !0;
742
- return this.textureRefCounts.set(e, r), f === "verbose" && o("[gltf-progressive] Memory: Untrack texture", r), !1;
743
- function o(n, l) {
744
- let { width: a, height: u } = xe(t);
745
- const c = a && u ? `${a}x${u}` : "N/A";
746
- let y = "N/A";
747
- a && u && (y = `~${(pt(t) / (1024 * 1024)).toFixed(2)} MB`), console.log(`${n} — ${t.name} ${c} (${y}), refCount: ${s} → ${l}
909
+ return this.textureRefCounts.delete(e), (h || se) && n("[gltf-progressive] Memory: Dispose texture", r), t.dispose(), !0;
910
+ return this.textureRefCounts.set(e, r), h === "verbose" && n("[gltf-progressive] Memory: Untrack texture", r), !1;
911
+ function n(o, a) {
912
+ let { width: l, height: u } = Le(t);
913
+ const f = l && u ? `${l}x${u}` : "N/A";
914
+ let c = "N/A";
915
+ l && u && (c = `~${(vt(t) / (1024 * 1024)).toFixed(2)} MB`), console.log(`${o} — ${t.name} ${f} (${c}), refCount: ${s} → ${a}
748
916
  ${e}`);
749
917
  }
750
918
  }
751
919
  static workers = [];
752
920
  static _workersIndex = 0;
753
- static async getOrLoadLOD(t, e) {
754
- const s = f == "verbose", r = this.getAssignedLODInformation(t);
755
- if (!r)
756
- return f && console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`, t), null;
757
- const o = r?.key;
758
- let n;
921
+ static async getOrLoadLOD(t, e, s) {
922
+ const r = h == "verbose", n = this.getAssignedLODInformation(t);
923
+ if (!n)
924
+ return h && console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`, t), null;
925
+ const o = n?.key;
926
+ let a;
759
927
  if (t.isTexture === !0) {
760
- const a = t;
761
- a.source && a.source[ue] && (n = a.source[ue]);
928
+ const u = t;
929
+ u.source && u.source[de] && (a = u.source[de]);
762
930
  }
763
- if (n || (n = m.lodInfos.get(o)), !n)
764
- f && console.warn(`Can not load LOD ${e}: no LOD info found for "${o}" ${t.name}`, t.type, m.lodInfos);
931
+ if (a || (a = g.lodInfos.get(o)), !a)
932
+ h && console.warn(`Can not load LOD ${e}: no LOD info found for "${o}" ${t.name}`, t.type, g.lodInfos);
765
933
  else {
766
934
  if (e > 0) {
767
935
  let c = !1;
768
- const y = Array.isArray(n.lods);
769
- if (y && e >= n.lods.length ? c = !0 : y || (c = !0), c) {
770
- const w = this.lowresCache.get(o);
771
- if (w) {
772
- const _ = w.deref();
773
- if (_) return _;
774
- this.lowresCache.delete(o), f && console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${o}`);
936
+ const d = Array.isArray(a.lods);
937
+ if (d && e >= a.lods.length ? c = !0 : d || (c = !0), c) {
938
+ const p = this.lowresCache.get(o);
939
+ if (p) {
940
+ const b = p.deref();
941
+ if (b) return b;
942
+ this.lowresCache.delete(o), h && console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${o}`);
775
943
  }
776
944
  return null;
777
945
  }
778
946
  }
779
- const a = Array.isArray(n.lods) ? n.lods[e]?.path : n.lods;
780
- if (!a)
781
- return f && !n["missing:uri"] && (n["missing:uri"] = !0, console.warn("Missing uri for progressive asset for LOD " + e, n)), null;
782
- const u = ht(r.url, a);
783
- if (u.endsWith(".glb") || u.endsWith(".gltf")) {
784
- if (!n.guid)
785
- return console.warn("missing pointer for glb/gltf texture", n), null;
786
- const c = u + "_" + n.guid, y = await this.queue.slot(u), w = this.cache.get(c);
787
- if (w !== void 0)
788
- if (s && console.log(`LOD ${e} was already loading/loaded: ${c}`), w instanceof WeakRef) {
789
- const d = w.deref();
790
- if (d) {
791
- let g = d, b = !1;
792
- if (g instanceof F && t instanceof F ? De(g.image) || ye(g) ? g = this.copySettings(t, g) : b = !0 : g instanceof E && t instanceof E && (g.attributes.position?.array || (b = !0)), !b)
793
- return g;
794
- }
795
- this.cache.delete(c), f && console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${c}`);
796
- } else {
797
- let d = await w.catch((b) => (console.error(`Error loading LOD ${e} from ${u}
798
- `, b), null)), g = !1;
799
- if (d == null || (d instanceof F && t instanceof F ? De(d.image) || ye(d) ? d = this.copySettings(t, d) : (g = !0, this.cache.delete(c)) : d instanceof E && t instanceof E && (d.attributes.position?.array || (g = !0, this.cache.delete(c)))), !g)
800
- return d;
801
- }
802
- if (!y.use)
803
- return f && console.log(`LOD ${e} was aborted: ${u}`), null;
804
- const _ = n, R = new Promise(async (d, g) => {
805
- if (Dt) {
806
- const x = await (await bt({})).load(u);
807
- if (x.textures.length > 0)
808
- for (const h of x.textures) {
809
- let p = h.texture;
810
- return m.assignLODInformation(r.url, p, o, e, void 0), t instanceof F && (p = this.copySettings(t, p)), p && (p.guid = _.guid), d(p);
947
+ const u = Array.isArray(a.lods) ? a.lods[e]?.path : a.lods;
948
+ if (!u)
949
+ return h && !a["missing:uri"] && (a["missing:uri"] = !0, console.warn("Missing uri for progressive asset for LOD " + e, a)), null;
950
+ const f = wt(n.url, u);
951
+ if (f.endsWith(".glb") || f.endsWith(".gltf")) {
952
+ if (!a.guid)
953
+ return console.warn("missing pointer for glb/gltf texture", a), null;
954
+ const c = f + "_" + a.guid, d = await this.tryResolveLODCacheEntry(this.cache.get(c), c, f, t, e, r);
955
+ if (d.found) return d.value;
956
+ if (s?.isCurrent?.() === !1)
957
+ return r && console.log(`Skipping stale LOD ${e} request before queue: ${f}`), null;
958
+ const p = await this.queue.slot(f);
959
+ if (s?.isCurrent?.() === !1)
960
+ return r && console.log(`Skipping stale LOD ${e} request after queue: ${f}`), null;
961
+ const b = await this.tryResolveLODCacheEntry(this.cache.get(c), c, f, t, e, r);
962
+ if (b.found) return b.value;
963
+ if (!p.use)
964
+ return h && console.log(`LOD ${e} was aborted: ${f}`), null;
965
+ const y = a, x = new Promise(async (w, z) => {
966
+ if (Ct) {
967
+ const m = await (await Rt({})).load(f);
968
+ if (m.textures.length > 0)
969
+ for (const D of m.textures) {
970
+ let M = D.texture;
971
+ return g.assignLODInformation(n.url, M, o, e, void 0), t instanceof q && (M = this.copySettings(t, M)), M && (M.guid = y.guid), w(M);
811
972
  }
812
- if (x.geometries.length > 0) {
813
- const h = new Array();
814
- for (const p of x.geometries) {
815
- const O = p.geometry;
816
- m.assignLODInformation(r.url, O, o, e, p.primitiveIndex), h.push(O);
973
+ if (m.geometries.length > 0) {
974
+ const D = new Array();
975
+ for (const M of m.geometries) {
976
+ const G = M.geometry;
977
+ g.assignLODInformation(n.url, G, o, e, M.primitiveIndex), D.push(G);
817
978
  }
818
- return d(h);
979
+ return w(D);
819
980
  }
820
- return d(null);
981
+ return w(null);
821
982
  }
822
- const b = new _e();
823
- Re(b), f && (await new Promise((L) => setTimeout(L, 1e3)), s && console.warn("Start loading (delayed) " + u, _.guid));
824
- let B = u;
825
- if (_ && Array.isArray(_.lods)) {
826
- const L = _.lods[e];
827
- L.hash && (B += "?v=" + L.hash);
983
+ const v = new De();
984
+ Ge(v), h && (await new Promise((_) => setTimeout(_, 1e3)), r && console.warn("Start loading (delayed) " + f, y.guid));
985
+ let T = f;
986
+ if (y && Array.isArray(y.lods)) {
987
+ const _ = y.lods[e];
988
+ _.hash && (T += "?v=" + _.hash);
828
989
  }
829
- const D = await b.loadAsync(B).catch((L) => (console.error(`Error loading LOD ${e} from ${u}
830
- `, L), d(null)));
831
- if (!D)
832
- return d(null);
833
- const q = D.parser;
834
- s && console.log("Loading finished " + u, _.guid);
835
- let T = 0;
836
- if (D.parser.json.textures) {
837
- let L = !1;
838
- for (const x of D.parser.json.textures) {
839
- if (x?.extensions) {
840
- const h = x?.extensions[W];
841
- if (h?.guid && h.guid === _.guid) {
842
- L = !0;
990
+ const O = await v.loadAsync(T).catch((_) => (console.error(`Error loading LOD ${e} from ${f}
991
+ `, _), w(null)));
992
+ if (!O)
993
+ return w(null);
994
+ const P = O.parser;
995
+ r && console.log("Loading finished " + f, y.guid);
996
+ let S = 0;
997
+ if (O.parser.json.textures) {
998
+ let _ = !1;
999
+ for (const m of O.parser.json.textures) {
1000
+ if (m?.extensions) {
1001
+ const D = m?.extensions[U];
1002
+ if (D?.guid && D.guid === y.guid) {
1003
+ _ = !0;
843
1004
  break;
844
1005
  }
845
1006
  }
846
- T++;
1007
+ S++;
847
1008
  }
848
- if (L) {
849
- let x = await q.getDependency("texture", T);
850
- return x && m.assignLODInformation(r.url, x, o, e, void 0), s && console.log('change "' + t.name + '" → "' + x.name + '"', u, T, x, c), t instanceof F && (x = this.copySettings(t, x)), x && (x.guid = _.guid), d(x);
851
- } else f && console.warn("Could not find texture with guid", _.guid, D.parser.json);
1009
+ if (_) {
1010
+ let m = await P.getDependency("texture", S);
1011
+ return m && g.assignLODInformation(n.url, m, o, e, void 0), r && console.log('change "' + t.name + '" → "' + m.name + '"', f, S, m, c), t instanceof q && (m = this.copySettings(t, m)), m && (m.guid = y.guid), w(m);
1012
+ } else h && console.warn("Could not find texture with guid", y.guid, O.parser.json);
852
1013
  }
853
- if (T = 0, D.parser.json.meshes) {
854
- let L = !1;
855
- for (const x of D.parser.json.meshes) {
856
- if (x?.extensions) {
857
- const h = x?.extensions[W];
858
- if (h?.guid && h.guid === _.guid) {
859
- L = !0;
1014
+ if (S = 0, O.parser.json.meshes) {
1015
+ let _ = !1;
1016
+ for (const m of O.parser.json.meshes) {
1017
+ if (m?.extensions) {
1018
+ const D = m?.extensions[U];
1019
+ if (D?.guid && D.guid === y.guid) {
1020
+ _ = !0;
860
1021
  break;
861
1022
  }
862
1023
  }
863
- T++;
1024
+ S++;
864
1025
  }
865
- if (L) {
866
- const x = await q.getDependency("mesh", T);
867
- if (s && console.log(`Loaded Mesh "${x.name}"`, u, T, x, c), x.isMesh === !0) {
868
- const h = x.geometry;
869
- return m.assignLODInformation(r.url, h, o, e, 0), d(h);
1026
+ if (_) {
1027
+ const m = await P.getDependency("mesh", S);
1028
+ if (r && console.log(`Loaded Mesh "${m.name}"`, f, S, m, c), m.isMesh === !0) {
1029
+ const D = m.geometry;
1030
+ return g.assignLODInformation(n.url, D, o, e, 0), w(D);
870
1031
  } else {
871
- const h = new Array();
872
- for (let p = 0; p < x.children.length; p++) {
873
- const O = x.children[p];
874
- if (O.isMesh === !0) {
875
- const S = O.geometry;
876
- m.assignLODInformation(r.url, S, o, e, p), h.push(S);
1032
+ const D = new Array();
1033
+ for (let M = 0; M < m.children.length; M++) {
1034
+ const G = m.children[M];
1035
+ if (G.isMesh === !0) {
1036
+ const ie = G.geometry;
1037
+ g.assignLODInformation(n.url, ie, o, e, M), D.push(ie);
877
1038
  }
878
1039
  }
879
- return d(h);
1040
+ return w(D);
880
1041
  }
881
- } else f && console.warn("Could not find mesh with guid", _.guid, D.parser.json);
1042
+ } else h && console.warn("Could not find mesh with guid", y.guid, O.parser.json);
882
1043
  }
883
- return d(null);
1044
+ return w(null);
884
1045
  });
885
- this.cache.set(c, R), y.use(R);
886
- const M = await R;
887
- return M != null ? M instanceof F ? (this.cache.set(c, new WeakRef(M)), m._resourceRegistry.register(M, c)) : Array.isArray(M) ? this.cache.set(c, Promise.resolve(M)) : this.cache.set(c, Promise.resolve(M)) : this.cache.set(c, Promise.resolve(null)), M;
888
- } else if (t instanceof F) {
889
- s && console.log("Load texture from uri: " + u);
890
- const y = await new Je().loadAsync(u);
891
- return y ? (y.guid = n.guid, y.flipY = !1, y.needsUpdate = !0, y.colorSpace = t.colorSpace, s && console.log(n, y)) : f && console.warn("failed loading", u), y;
1046
+ this.cache.set(c, x), p.use(x);
1047
+ const L = await x;
1048
+ return L != null ? L instanceof q ? (this.cache.set(c, new WeakRef(L)), g._resourceRegistry.register(L, c)) : Array.isArray(L) ? this.cache.set(c, Promise.resolve(L)) : this.cache.set(c, Promise.resolve(L)) : this.cache.set(c, Promise.resolve(null)), L;
1049
+ } else if (t instanceof q) {
1050
+ if (s?.isCurrent?.() === !1)
1051
+ return r && console.log(`Skipping stale texture LOD ${e} request: ${f}`), null;
1052
+ r && console.log("Load texture from uri: " + f);
1053
+ const d = await new it().loadAsync(f);
1054
+ return s?.isCurrent?.() === !1 ? (d?.dispose(), null) : (d ? (d.guid = a.guid, d.flipY = !1, d.needsUpdate = !0, d.colorSpace = t.colorSpace, r && console.log(a, d)) : h && console.warn("failed loading", f), d);
892
1055
  }
893
1056
  }
894
1057
  return null;
895
1058
  }
896
- static queue = new gt(ve() ? 20 : 50, { debug: f != !1 });
897
- static assignLODInformation(t, e, s, r, o) {
1059
+ static async tryResolveLODCacheEntry(t, e, s, r, n, o) {
1060
+ if (t === void 0)
1061
+ return { found: !1 };
1062
+ if (o && console.log(`LOD ${n} was already loading/loaded: ${e}`), t instanceof WeakRef) {
1063
+ const u = t.deref();
1064
+ if (u) {
1065
+ let f = u, c = !1;
1066
+ if (f instanceof q && r instanceof q ? Te(f.image) || we(f) ? f = this.copySettings(r, f) : c = !0 : f instanceof j && r instanceof j && (f.attributes.position?.array || (c = !0)), !c)
1067
+ return { found: !0, value: f };
1068
+ }
1069
+ return this.cache.delete(e), h && console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${e}`), { found: !1 };
1070
+ }
1071
+ let a = await t.catch((u) => (console.error(`Error loading LOD ${n} from ${s}
1072
+ `, u), null)), l = !1;
1073
+ return a == null || (a instanceof q && r instanceof q ? Te(a.image) || we(a) ? a = this.copySettings(r, a) : (l = !0, this.cache.delete(e)) : a instanceof j && r instanceof j && (a.attributes.position?.array || (l = !0, this.cache.delete(e)))), l ? { found: !1 } : { found: !0, value: a };
1074
+ }
1075
+ static _queue;
1076
+ static get queue() {
1077
+ return this._queue ??= new Lt(Me() ? 20 : 50, { debug: h != !1 });
1078
+ }
1079
+ static assignLODInformation(t, e, s, r, n) {
898
1080
  if (!e) return;
899
1081
  e.userData || (e.userData = {});
900
- const n = new Ot(t, s, r, o);
901
- e.userData.LODS = n, "source" in e && typeof e.source == "object" && (e.source.LODS = n);
1082
+ const o = new kt(t, s, r, n);
1083
+ e.userData.LODS = o, "source" in e && typeof e.source == "object" && (e.source.LODS = o);
902
1084
  }
903
1085
  static getAssignedLODInformation(t) {
904
1086
  return t ? t.userData?.LODS ? t.userData.LODS : "source" in t && t.source?.LODS ? t.source.LODS : null : null;
905
1087
  }
906
1088
  // private static readonly _copiedTextures: WeakMap<Texture, Texture> = new Map();
907
1089
  static copySettings(t, e) {
908
- return e ? (f === "verbose" && console.debug(`Copy texture settings
1090
+ return e ? (h === "verbose" && console.debug(`Copy texture settings
909
1091
  `, t.uuid, `
910
1092
  `, e.uuid), e = e.clone(), e.offset = t.offset, e.repeat = t.repeat, e.colorSpace = t.colorSpace, e.magFilter = t.magFilter, e.minFilter = t.minFilter, e.wrapS = t.wrapS, e.wrapT = t.wrapT, e.flipY = t.flipY, e.anisotropy = t.anisotropy, e.mipmaps || (e.generateMipmaps = t.generateMipmaps), e) : t;
911
1093
  }
912
1094
  }
913
- class Ot {
1095
+ class kt {
914
1096
  url;
915
1097
  /** the key to lookup the LOD information */
916
1098
  key;
@@ -921,10 +1103,10 @@ class Ot {
921
1103
  this.url = t, this.key = e, this.level = s, r != null && (this.index = r);
922
1104
  }
923
1105
  }
924
- class ce {
1106
+ class oe {
925
1107
  static addPromise = (t, e, s, r) => {
926
- r.forEach((o) => {
927
- o.add(t, e, s);
1108
+ r.forEach((n) => {
1109
+ n.add(t, e, s);
928
1110
  });
929
1111
  };
930
1112
  ready;
@@ -955,8 +1137,8 @@ class ce {
955
1137
  _maxPromisesPerObject = 1;
956
1138
  constructor(t, e) {
957
1139
  const r = Math.max(e.frames ?? 2, 2);
958
- this._frame_start = e.waitForFirstCapture ? void 0 : t, this._frames_to_capture = r, this.ready = new Promise((o) => {
959
- this._resolve = o;
1140
+ this._frame_start = e.waitForFirstCapture ? void 0 : t, this._frames_to_capture = r, this.ready = new Promise((n) => {
1141
+ this._resolve = n;
960
1142
  }), this.ready.finally(() => {
961
1143
  this._resolved = !0, this._awaiting.length = 0;
962
1144
  }), this._signal = e.signal, this._signal?.addEventListener("abort", () => {
@@ -970,15 +1152,15 @@ class ce {
970
1152
  _seen = /* @__PURE__ */ new WeakMap();
971
1153
  add(t, e, s) {
972
1154
  if (this._resolved) {
973
- f && console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");
1155
+ h && console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");
974
1156
  return;
975
1157
  }
976
1158
  if (!(this._frame_start !== void 0 && this._currentFrame > this._frame_start + this._frames_to_capture)) {
977
1159
  if (this._maxPromisesPerObject >= 1)
978
1160
  if (this._seen.has(e)) {
979
- let r = this._seen.get(e);
1161
+ const r = this._seen.get(e);
980
1162
  if (r >= this._maxPromisesPerObject) {
981
- f && console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");
1163
+ h && console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");
982
1164
  return;
983
1165
  }
984
1166
  this._seen.set(e, r + 1);
@@ -997,22 +1179,122 @@ class ce {
997
1179
  });
998
1180
  }
999
1181
  }
1000
- const k = N("debugprogressive"), St = N("noprogressive"), de = /* @__PURE__ */ Symbol("Needle:LODSManager"), fe = /* @__PURE__ */ Symbol("Needle:LODState"), U = /* @__PURE__ */ Symbol("Needle:CurrentLOD"), P = { mesh_lod: -1, texture_lod: -1 };
1001
- class v {
1182
+ const A = V("debugprogressive"), At = A === "colors", It = V("noprogressive"), he = /* @__PURE__ */ Symbol("Needle:LODSManager"), ge = /* @__PURE__ */ Symbol("Needle:LODState"), E = /* @__PURE__ */ Symbol("Needle:CurrentLOD"), Oe = Xe, R = { mesh_lod: -1, texture_lod: -1 }, $t = new Ie(), Se = [
1183
+ 3526751,
1184
+ 11065402,
1185
+ 15978811,
1186
+ 15897394,
1187
+ 15749691,
1188
+ 11032304,
1189
+ 4827122,
1190
+ 3332036,
1191
+ 16739229,
1192
+ 7306743,
1193
+ 14053330,
1194
+ 3516499,
1195
+ 12035359,
1196
+ 14703919,
1197
+ 3963096,
1198
+ 42662,
1199
+ 14100029,
1200
+ 8344319,
1201
+ 4633680,
1202
+ 16229681,
1203
+ 3120096,
1204
+ 12076434,
1205
+ 9083434,
1206
+ 2060171,
1207
+ 15751837,
1208
+ 10182117,
1209
+ 48121,
1210
+ 62932,
1211
+ 16704576,
1212
+ 15817653,
1213
+ 5083278,
1214
+ 5592405
1215
+ ];
1216
+ function Bt() {
1217
+ const i = Oe.Timer || Oe.Clock;
1218
+ return new i();
1219
+ }
1220
+ const pe = new ue(), N = new ue(), Re = new ue(), Gt = new k(), qt = new k(), Wt = new $e(), W = new k(), Y = new k(), Q = new k(), J = new k();
1221
+ function Ft(i, t) {
1222
+ const e = i.min, s = i.max, r = (e.x + s.x) * 0.5, n = (e.y + s.y) * 0.5;
1223
+ return W.set(r, n, e.z).applyMatrix4(t).z < 0;
1224
+ }
1225
+ function Ut(i) {
1226
+ const {
1227
+ geometry: t,
1228
+ matrixWorld: e,
1229
+ camera: s,
1230
+ projectionScreenMatrix: r,
1231
+ desiredDensity: n,
1232
+ canvasHeight: o = 0,
1233
+ currentLevel: a = -1,
1234
+ xrEnabled: l = !1,
1235
+ debugDrawLine: u,
1236
+ warnMissingPrimitiveDensities: f = !1
1237
+ } = i, c = g.getMeshLODExtension(t)?.lods, d = g.getPrimitiveIndex(t), p = i.target ?? {
1238
+ level: a,
1239
+ primitiveIndex: d,
1240
+ screenCoverage: 0,
1241
+ screenspaceVolume: new k(),
1242
+ centrality: 1
1243
+ };
1244
+ p.level = a, p.primitiveIndex = d, p.screenCoverage = 0, p.screenspaceVolume.set(0, 0, 0), p.centrality = 1;
1245
+ let b = i.boundingBox ?? t.boundingBox;
1246
+ if (b || (t.computeBoundingBox(), b = t.boundingBox), !b) return p;
1247
+ if (pe.copy(b).applyMatrix4(e), s.isPerspectiveCamera && Ft(pe, r))
1248
+ return p.level = 0, p.screenCoverage = 1 / 0, p.screenspaceVolume.set(1 / 0, 1 / 0, 1 / 0), p;
1249
+ if (N.copy(pe).applyMatrix4(r), l && s.isPerspectiveCamera && s.fov > 70) {
1250
+ const v = N.min, T = N.max;
1251
+ let O = v.x, P = v.y, S = T.x, _ = T.y;
1252
+ const m = 2, D = 1.5, M = (v.x + T.x) * 0.5, G = (v.y + T.y) * 0.5;
1253
+ O = (O - M) * m + M, P = (P - G) * m + G, S = (S - M) * m + M, _ = (_ - G) * m + G;
1254
+ const ie = O < 0 && S > 0 ? 0 : Math.min(Math.abs(v.x), Math.abs(T.x)), Ve = P < 0 && _ > 0 ? 0 : Math.min(Math.abs(v.y), Math.abs(T.y)), ce = Math.max(ie, Ve);
1255
+ p.centrality = (D - ce) * (D - ce) * (D - ce);
1256
+ }
1257
+ const y = N.getSize(Gt);
1258
+ y.multiplyScalar(0.5), globalThis.screen?.availHeight > 0 && o > 0 && y.multiplyScalar(o / globalThis.screen.availHeight), s.isPerspectiveCamera && (y.x *= s.aspect), Re.copy(b).applyMatrix4(e).applyMatrix4(s.matrixWorldInverse);
1259
+ const x = Re.getSize(qt), L = Math.max(y.x, y.y), w = Math.max(x.x, x.y);
1260
+ L !== 0 && w !== 0 && (y.z = x.z / w * L);
1261
+ const z = Math.max(y.x, y.y, y.z) * p.centrality;
1262
+ if (p.screenCoverage = z, p.screenspaceVolume.copy(y), z <= 0) return p;
1263
+ if (u) {
1264
+ const v = Wt.copy(r);
1265
+ v.invert(), W.copy(N.min), Y.copy(N.max), Y.x = W.x, Q.copy(N.max), Q.y = W.y, J.copy(N.max);
1266
+ const T = (W.z + J.z) * 0.5;
1267
+ W.z = Y.z = Q.z = J.z = T, W.applyMatrix4(v), Y.applyMatrix4(v), Q.applyMatrix4(v), J.applyMatrix4(v), u(W, Y, 255), u(W, Q, 255), u(Y, J, 255), u(Q, J, 255);
1268
+ }
1269
+ if (c?.length)
1270
+ for (let v = 0; v < c.length; v++) {
1271
+ const T = c[v], O = T.densities?.[d] || T.density || 1e-5;
1272
+ if (d > 0 && f && Fe() && !T.densities && !globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] && (globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] = !0, console.warn("[Needle Progressive] Detected usage of mesh without primitive densities. This might cause incorrect LOD level selection: Consider re-optimizing your model by updating your Needle Integration, Needle glTF Pipeline or running optimization again on Needle Cloud.")), O / z < n) {
1273
+ p.level = v;
1274
+ break;
1275
+ }
1276
+ }
1277
+ return p;
1278
+ }
1279
+ class I {
1002
1280
  /**
1003
1281
  * Assign a function to draw debug lines for the LODs. This function will be called with the start and end position of the line and the color of the line when the `debugprogressive` query parameter is set.
1004
1282
  */
1005
1283
  static debugDrawLine;
1006
1284
  /** @internal */
1007
1285
  static getObjectLODState(t) {
1008
- return t[fe];
1286
+ return t[ge];
1009
1287
  }
1010
1288
  static addPlugin(t) {
1011
- z.push(t);
1289
+ F.push(t);
1012
1290
  }
1013
1291
  static removePlugin(t) {
1014
- const e = z.indexOf(t);
1015
- e >= 0 && z.splice(e, 1);
1292
+ const e = F.indexOf(t);
1293
+ e >= 0 && F.splice(e, 1);
1294
+ }
1295
+ /** Read-only snapshot of the currently registered plugins, for inspection. Use {@link addPlugin} / {@link removePlugin} to modify the registry. */
1296
+ static getPlugins() {
1297
+ return F;
1016
1298
  }
1017
1299
  /**
1018
1300
  * Gets the LODsManager for the given renderer. If the LODsManager does not exist yet, it will be created.
@@ -1020,20 +1302,20 @@ class v {
1020
1302
  * @returns The LODsManager instance.
1021
1303
  */
1022
1304
  static get(t, e) {
1023
- if (t[de])
1024
- return console.debug("[gltf-progressive] LODsManager already exists for this renderer"), t[de];
1025
- const s = new v(t, {
1305
+ if (t[he])
1306
+ return console.debug("[gltf-progressive] LODsManager already exists for this renderer"), t[he];
1307
+ const s = new I(t, {
1026
1308
  engine: "unknown",
1027
1309
  ...e
1028
1310
  });
1029
- return t[de] = s, s;
1311
+ return t[he] = s, s;
1030
1312
  }
1031
1313
  renderer;
1032
1314
  context;
1033
- projectionScreenMatrix = new Me();
1315
+ projectionScreenMatrix = new $e();
1034
1316
  /** @deprecated use static `LODsManager.addPlugin()` method. This getter will be removed in later versions */
1035
1317
  get plugins() {
1036
- return z;
1318
+ return F;
1037
1319
  }
1038
1320
  /**
1039
1321
  * Force override the LOD level for all objects (meshes + textures) rendered in the scene
@@ -1071,45 +1353,99 @@ class v {
1071
1353
  _newPromiseGroups = [];
1072
1354
  _promiseGroupIds = 0;
1073
1355
  /**
1074
- * Call to await LODs loading during the next render cycle.
1356
+ * Returns a promise that resolves once all LOD requests initiated during the next render cycles have finished loading.
1357
+ * This is useful for hiding low-resolution placeholders (e.g. with a loading overlay or CSS blur) until high-quality assets are ready.
1358
+ *
1359
+ * By default, the returned promise captures LOD loading requests for 2 frames and resolves when all of them complete.
1360
+ * Use `waitForFirstCapture` if no LOD requests may happen immediately (e.g. after a scene switch).
1361
+ *
1362
+ * @param opts - Optional configuration for how long to capture and what to wait for. See {@link PromiseGroupOptions}.
1363
+ * @returns A promise that resolves with `{ cancelled, awaited_count, resolved_count }` once all captured LOD loads complete (or the signal aborts).
1364
+ *
1365
+ * @example
1366
+ * ```ts
1367
+ * // Wait for initial LODs to finish loading, then remove a blur overlay
1368
+ * const result = await lodsManager.awaitLoading({
1369
+ * frames: 5,
1370
+ * signal: AbortSignal.timeout(10_000),
1371
+ * });
1372
+ * console.log(`Loaded ${result.resolved_count} of ${result.awaited_count} LODs`);
1373
+ * document.querySelector('.blur-overlay')?.remove();
1374
+ * ```
1375
+ *
1376
+ * @example
1377
+ * ```ts
1378
+ * // Wait until at least one LOD starts loading before resolving
1379
+ * await lodsManager.awaitLoading({ waitForFirstCapture: true });
1380
+ * ```
1075
1381
  */
1076
1382
  awaitLoading(t) {
1077
- const e = this._promiseGroupIds++, s = new ce(this.#r, { ...t });
1383
+ const e = this._promiseGroupIds++, s = new oe(this.#r, { ...t });
1078
1384
  this._newPromiseGroups.push(s);
1079
1385
  const r = performance.now();
1080
1386
  return s.ready.finally(() => {
1081
- const o = this._newPromiseGroups.indexOf(s);
1082
- o >= 0 && (this._newPromiseGroups.splice(o, 1), Oe() && performance.measure("LODsManager:awaitLoading", {
1387
+ const n = this._newPromiseGroups.indexOf(s);
1388
+ n >= 0 && (this._newPromiseGroups.splice(n, 1), Fe() && performance.measure("LODsManager:awaitLoading", {
1083
1389
  start: r,
1084
1390
  detail: { id: e, name: t?.name, awaited: s.awaitedCount, resolved: s.resolvedCount }
1085
1391
  }));
1086
1392
  }), s.ready;
1087
1393
  }
1394
+ /** Track LOD work started outside this manager so {@link awaitLoading} waits for it too. */
1395
+ trackLoadingPromise(t, e, s) {
1396
+ return oe.addPromise(t, e, s, this._newPromiseGroups), s;
1397
+ }
1088
1398
  _postprocessPromiseGroups() {
1089
1399
  if (this._newPromiseGroups.length !== 0)
1090
1400
  for (let t = this._newPromiseGroups.length - 1; t >= 0; t--)
1091
1401
  this._newPromiseGroups[t].update(this.#r);
1092
1402
  }
1093
1403
  _lodchangedlisteners = [];
1404
+ /**
1405
+ * Register a listener that is called whenever a mesh or texture LOD level has finished loading and has been applied.
1406
+ * The listener receives the type of asset (`"mesh"` or `"texture"`), the new LOD level, and the affected object.
1407
+ *
1408
+ * @param evt - The event type. Currently only `"changed"` is supported.
1409
+ * @param listener - Callback invoked after a LOD swap completes.
1410
+ * @return A function to unregister the listener.
1411
+ *
1412
+ * @example
1413
+ * ```ts
1414
+ * lodsManager.addEventListener("changed", ({ type, level, object }) => {
1415
+ * console.log(`${type} LOD changed to level ${level}`, object);
1416
+ * });
1417
+ * ```
1418
+ */
1094
1419
  addEventListener(t, e) {
1095
- t === "changed" && this._lodchangedlisteners.push(e);
1420
+ return t === "changed" ? (this._lodchangedlisteners.push(e), () => {
1421
+ this.removeEventListener(t, e);
1422
+ }) : () => {
1423
+ };
1096
1424
  }
1425
+ /**
1426
+ * Remove a previously registered `"changed"` event listener.
1427
+ * @param evt - The event type (`"changed"`).
1428
+ * @param listener - The listener to remove.
1429
+ * @return `true` if the listener was found and removed, `false` otherwise.
1430
+ */
1097
1431
  removeEventListener(t, e) {
1432
+ let s = !1;
1098
1433
  if (t === "changed") {
1099
- const s = this._lodchangedlisteners.indexOf(e);
1100
- s >= 0 && this._lodchangedlisteners.splice(s, 1);
1434
+ const r = this._lodchangedlisteners.indexOf(e);
1435
+ r >= 0 && (this._lodchangedlisteners.splice(r, 1), s = !0);
1101
1436
  }
1437
+ return s;
1102
1438
  }
1103
1439
  // readonly plugins: NEEDLE_progressive_plugin[] = [];
1104
1440
  constructor(t, e) {
1105
1441
  this.renderer = t, this.context = { ...e };
1106
1442
  }
1107
1443
  #t;
1108
- #n = new Ze();
1109
1444
  #r = 0;
1445
+ #n = 0;
1110
1446
  #o = 0;
1111
- #i = 0;
1112
1447
  #s = 0;
1448
+ #i = Bt();
1113
1449
  _fpsBuffer = [60, 60, 60, 60, 60];
1114
1450
  /**
1115
1451
  * Enable the LODsManager. This will replace the render method of the renderer with a method that updates the LODs.
@@ -1120,29 +1456,46 @@ class v {
1120
1456
  let t = 0;
1121
1457
  this.#t = this.renderer.render;
1122
1458
  const e = this;
1123
- Le(this.renderer), this.renderer.render = function(s, r) {
1124
- const o = e.renderer.getRenderTarget();
1125
- (o == null || "isXRRenderTarget" in o && o.isXRRenderTarget) && (t = 0, e.#r += 1, e.#n.update(), e.#o = e.#n.getDelta(), e.#i += e.#o, e._fpsBuffer.shift(), e._fpsBuffer.push(1 / e.#o), e.#s = e._fpsBuffer.reduce((l, a) => l + a) / e._fpsBuffer.length, k && e.#r % 200 === 0 && console.log("FPS", Math.round(e.#s), "Interval:", e.#e));
1126
- const n = t++;
1127
- e.#t.call(this, s, r), e.onAfterRender(s, r, n);
1459
+ _e(this.renderer), this.renderer.render = function(s, r) {
1460
+ const n = e.renderer.getRenderTarget();
1461
+ (n == null || "isXRRenderTarget" in n && n.isXRRenderTarget) && (t = 0, e.#r += 1, e.#i.update?.(), e.#n = Math.max(e.#i.getDelta(), 1 / 1e3), e.#o += e.#n, e._fpsBuffer.shift(), e._fpsBuffer.push(1 / e.#n), e.#s = e._fpsBuffer.reduce((a, l) => a + l) / e._fpsBuffer.length, A && e.#r % 200 === 0 && console.log("FPS", Math.round(e.#s), "Interval:", e.#e));
1462
+ const o = t++;
1463
+ e.#t.call(this, s, r), e.onAfterRender(s, r, o);
1128
1464
  };
1129
1465
  }
1130
1466
  disable() {
1131
1467
  this.#t && (console.debug("[gltf-progressive] Disabling LODsManager for renderer"), this.renderer.render = this.#t, this.#t = void 0);
1132
1468
  }
1469
+ /**
1470
+ * Manually trigger a LOD update for a scene and camera.
1471
+ * Only needed when {@link manual} is set to `true` — otherwise LOD updates happen automatically on each render call.
1472
+ *
1473
+ * @param scene - The scene containing objects with progressive LODs.
1474
+ * @param camera - The camera used to determine screen coverage and LOD levels.
1475
+ *
1476
+ * @example
1477
+ * ```ts
1478
+ * const lodsManager = LODsManager.get(renderer);
1479
+ * lodsManager.manual = true;
1480
+ * // ... later, trigger an update at a specific point:
1481
+ * lodsManager.update(scene, camera);
1482
+ * ```
1483
+ */
1133
1484
  update(t, e) {
1134
1485
  this.internalUpdate(t, e);
1135
1486
  }
1136
1487
  onAfterRender(t, e, s) {
1137
1488
  if (this.pause) return;
1138
- const o = this.renderer.renderLists.get(t, 0).opaque;
1139
- let n = !0;
1140
- if (o.length === 1) {
1141
- const l = o[0].material;
1142
- (l.name === "EffectMaterial" || l.name === "CopyShader") && (n = !1);
1489
+ const r = this.getRenderList(t, e, s);
1490
+ if (!r) return;
1491
+ const n = r.opaque;
1492
+ let o = !0;
1493
+ if (n.length === 1) {
1494
+ const a = n[0].material;
1495
+ (a.name === "EffectMaterial" || a.name === "CopyShader") && (o = !1);
1143
1496
  }
1144
- if ((e.parent && e.parent.type === "CubeCamera" || s >= 1 && e.type === "OrthographicCamera") && (n = !1), n) {
1145
- if (St || (this.updateInterval === "auto" ? this.#s < 40 && this.#e < 10 ? (this.#e += 1, k && console.warn("↓ Reducing LOD updates", this.#e, this.#s.toFixed(0))) : this.#s >= 60 && this.#e > 1 && (this.#e -= 1, k && console.warn("↑ Increasing LOD updates", this.#e, this.#s.toFixed(0))) : this.#e = this.updateInterval, this.#e > 0 && this.#r % this.#e != 0))
1497
+ if ((e.parent && e.parent.type === "CubeCamera" || s >= 1 && e.type === "OrthographicCamera") && (o = !1), o) {
1498
+ if (It || (this.updateInterval === "auto" ? this.#s < 40 && this.#e < 10 ? (this.#e += 1, A && console.warn("↓ Reducing LOD updates", this.#e, this.#s.toFixed(0))) : this.#s >= 60 && this.#e > 1 && (this.#e -= 1, A && console.warn("↑ Increasing LOD updates", this.#e, this.#s.toFixed(0))) : this.#e = this.updateInterval, this.#e > 0 && this.#r % this.#e != 0))
1146
1499
  return;
1147
1500
  this.internalUpdate(t, e), this._postprocessPromiseGroups();
1148
1501
  }
@@ -1151,15 +1504,17 @@ class v {
1151
1504
  * Update LODs in a scene
1152
1505
  */
1153
1506
  internalUpdate(t, e) {
1154
- const s = this.renderer.renderLists.get(t, 0), r = s.opaque;
1507
+ const s = this.getRenderList(t, e, 0);
1508
+ if (!s) return;
1509
+ const r = s.opaque;
1155
1510
  this.projectionScreenMatrix.multiplyMatrices(e.projectionMatrix, e.matrixWorldInverse);
1156
- const o = this.targetTriangleDensity;
1157
- for (const a of r) {
1158
- if (a.material && (a.geometry?.type === "BoxGeometry" || a.geometry?.type === "BufferGeometry") && (a.material.name === "SphericalGaussianBlur" || a.material.name == "BackgroundCubeMaterial" || a.material.name === "CubemapFromEquirect" || a.material.name === "EquirectangularToCubeUV")) {
1159
- k && (a.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"] || (a.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"] = !0, console.warn("Ignoring skybox or BLIT object", a, a.material.name, a.material.type)));
1511
+ const n = this.targetTriangleDensity;
1512
+ for (const l of r) {
1513
+ if (l.material && (l.geometry?.type === "BoxGeometry" || l.geometry?.type === "BufferGeometry") && (l.material.name === "SphericalGaussianBlur" || l.material.name == "BackgroundCubeMaterial" || l.material.name === "CubemapFromEquirect" || l.material.name === "EquirectangularToCubeUV")) {
1514
+ A && (l.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"] || (l.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"] = !0, console.warn("Ignoring skybox or BLIT object", l, l.material.name, l.material.type)));
1160
1515
  continue;
1161
1516
  }
1162
- switch (a.material.type) {
1517
+ switch (l.material.type) {
1163
1518
  case "LineBasicMaterial":
1164
1519
  case "LineDashedMaterial":
1165
1520
  case "PointsMaterial":
@@ -1168,38 +1523,52 @@ class v {
1168
1523
  case "MeshDepthMaterial":
1169
1524
  continue;
1170
1525
  }
1171
- if (k === "color" && a.material && !a.object.progressive_debug_color) {
1172
- a.object.progressive_debug_color = !0;
1173
- const c = Math.random() * 16777215, y = new et({ color: c });
1174
- a.object.material = y;
1175
- }
1176
- const u = a.object;
1177
- (u instanceof j || u.isMesh) && this.updateLODs(t, e, u, o);
1526
+ const u = l.object;
1527
+ (u instanceof K || u.isMesh) && this.updateLODs(t, e, u, n);
1178
1528
  }
1179
- const n = s.transparent;
1180
- for (const a of n) {
1181
- const u = a.object;
1182
- (u instanceof j || u.isMesh) && this.updateLODs(t, e, u, o);
1529
+ const o = s.transparent;
1530
+ for (const l of o) {
1531
+ const u = l.object;
1532
+ (u instanceof K || u.isMesh) && this.updateLODs(t, e, u, n);
1183
1533
  }
1184
- const l = s.transmissive;
1185
- for (const a of l) {
1186
- const u = a.object;
1187
- (u instanceof j || u.isMesh) && this.updateLODs(t, e, u, o);
1534
+ const a = s.transmissive;
1535
+ for (const l of a) {
1536
+ const u = l.object;
1537
+ (u instanceof K || u.isMesh) && this.updateLODs(t, e, u, n);
1188
1538
  }
1189
1539
  }
1540
+ getRenderList(t, e, s) {
1541
+ const r = this.renderer;
1542
+ let n = null;
1543
+ if (r.isWebGPURenderer === !0) {
1544
+ const o = r._renderLists;
1545
+ if (!o) return null;
1546
+ n = o.get(t, e);
1547
+ } else if (r.isWebGLRenderer === !0) {
1548
+ const o = r.renderLists;
1549
+ if (!o) return null;
1550
+ n = o.get(t, s);
1551
+ }
1552
+ return n ? {
1553
+ opaque: n.opaque || [],
1554
+ transparent: n.transparent || [],
1555
+ transmissive: n.transmissive || n.transparentDoublePass || [],
1556
+ transparentDoublePass: n.transparentDoublePass || []
1557
+ } : null;
1558
+ }
1190
1559
  /** Update the LOD levels for the renderer. */
1191
1560
  updateLODs(t, e, s, r) {
1192
1561
  s.userData || (s.userData = {});
1193
- let o = s[fe];
1194
- if (o || (o = new Tt(), s[fe] = o), o.frames++ < 2)
1562
+ let n = s[ge];
1563
+ if (n || (n = new zt(), s[ge] = n), n.frames++ < 2)
1195
1564
  return;
1196
- for (const l of z)
1197
- l.onBeforeUpdateLOD?.(this.renderer, t, e, s);
1198
- const n = this.overrideLodLevel !== void 0 ? this.overrideLodLevel : V;
1199
- n >= 0 ? (P.mesh_lod = n, P.texture_lod = n) : (this.calculateLodLevel(e, s, o, r, P), P.mesh_lod = Math.round(P.mesh_lod), P.texture_lod = Math.round(P.texture_lod)), P.mesh_lod >= 0 && this.loadProgressiveMeshes(s, P.mesh_lod), s.material && P.texture_lod >= 0 && this.loadProgressiveTextures(s.material, P.texture_lod, n), f && s.material && !s.isGizmo && $e(s.material);
1200
- for (const l of z)
1201
- l.onAfterUpdatedLOD?.(this.renderer, t, e, s, P);
1202
- o.lastLodLevel_Mesh = P.mesh_lod, o.lastLodLevel_Texture = P.texture_lod;
1565
+ for (const a of F)
1566
+ a.onBeforeUpdateLOD?.(this.renderer, t, e, s);
1567
+ const o = this.overrideLodLevel !== void 0 ? this.overrideLodLevel : X;
1568
+ o >= 0 ? (R.mesh_lod = o, R.texture_lod = o) : (this.calculateLodLevel(e, s, n, r, R), R.mesh_lod = Math.round(R.mesh_lod), R.texture_lod = Math.round(R.texture_lod)), R.mesh_lod >= 0 && this.loadProgressiveMeshes(s, R.mesh_lod), s.material && R.texture_lod >= 0 && this.loadProgressiveTextures(s.material, R.texture_lod, o), h && s.material && !s.isGizmo && Ue(s.material), At && s.material && !s.isGizmo && !s.isBatchedMesh && ze(s.material, R.mesh_lod);
1569
+ for (const a of F)
1570
+ a.onAfterUpdatedLOD?.(this.renderer, t, e, s, R);
1571
+ n.lastLodLevel_Mesh = R.mesh_lod, n.lastLodLevel_Texture = R.texture_lod;
1203
1572
  }
1204
1573
  /** Load progressive textures for the given material
1205
1574
  * @param material the material to load the textures for
@@ -1210,16 +1579,18 @@ class v {
1210
1579
  if (!t) return;
1211
1580
  if (Array.isArray(t)) {
1212
1581
  for (const o of t)
1213
- this.loadProgressiveTextures(o, e);
1582
+ this.loadProgressiveTextures(o, e, s);
1214
1583
  return;
1215
1584
  }
1216
1585
  let r = !1;
1217
- if ((t[U] === void 0 || e < t[U]) && (r = !0), s !== void 0 && s >= 0 && (r = t[U] != s, e = s), r) {
1218
- t[U] = e;
1219
- const o = m.assignTextureLOD(t, e).then((n) => {
1220
- this._lodchangedlisteners.forEach((l) => l({ type: "texture", level: e, object: t }));
1586
+ (t[E] === void 0 || e < t[E]) && (r = !0);
1587
+ const n = s !== void 0 && s >= 0;
1588
+ if (n && (r = t[E] != s, e = s), r) {
1589
+ t[E] = e;
1590
+ const o = n ? { force: !0 } : void 0, a = g.assignTextureLOD(t, e, o).then((l) => {
1591
+ this._lodchangedlisteners.forEach((u) => u({ type: "texture", level: e, object: t }));
1221
1592
  });
1222
- ce.addPromise("texture", t, o, this._newPromiseGroups);
1593
+ oe.addPromise("texture", t, a, this._newPromiseGroups);
1223
1594
  }
1224
1595
  }
1225
1596
  /** Load progressive meshes for the given mesh
@@ -1230,143 +1601,110 @@ class v {
1230
1601
  */
1231
1602
  loadProgressiveMeshes(t, e) {
1232
1603
  if (!t) return Promise.resolve(null);
1233
- let s = t[U] !== e;
1604
+ let s = t[E] !== e;
1234
1605
  const r = t["DEBUG:LOD"];
1235
- if (r != null && (s = t[U] != r, e = r), s) {
1236
- t[U] = e;
1237
- const o = t.geometry, n = m.assignMeshLOD(t, e).then((l) => (l && t[U] == e && o != t.geometry && this._lodchangedlisteners.forEach((a) => a({ type: "mesh", level: e, object: t })), l));
1238
- return ce.addPromise("mesh", t, n, this._newPromiseGroups), n;
1606
+ if (r != null && (s = t[E] != r, e = r), s) {
1607
+ t[E] = e;
1608
+ const n = t.geometry, o = g.assignMeshLOD(t, e).then((a) => (a && t[E] == e && n != t.geometry && this._lodchangedlisteners.forEach((l) => l({ type: "mesh", level: e, object: t })), a));
1609
+ return oe.addPromise("mesh", t, o, this._newPromiseGroups), o;
1239
1610
  }
1240
1611
  return Promise.resolve(null);
1241
1612
  }
1242
1613
  // private testIfLODLevelsAreAvailable() {
1243
- _sphere = new Ce();
1244
- _tempBox = new ge();
1245
- _tempBox2 = new ge();
1246
- tempMatrix = new Me();
1247
- _tempWorldPosition = new A();
1248
- _tempBoxSize = new A();
1249
- _tempBox2Size = new A();
1250
- static corner0 = new A();
1251
- static corner1 = new A();
1252
- static corner2 = new A();
1253
- static corner3 = new A();
1254
- static _tempPtInside = new A();
1255
- static isInside(t, e) {
1256
- const s = t.min, r = t.max, o = (s.x + r.x) * 0.5, n = (s.y + r.y) * 0.5;
1257
- return this._tempPtInside.set(o, n, s.z).applyMatrix4(e).z < 0;
1258
- }
1614
+ _sphere = new Ae();
1615
+ _tempWorldPosition = new k();
1259
1616
  static skinnedMeshBoundsFrameOffsetCounter = 0;
1260
1617
  static $skinnedMeshBoundsOffset = /* @__PURE__ */ Symbol("gltf-progressive-skinnedMeshBoundsOffset");
1261
1618
  // #region calculateLodLevel
1262
- calculateLodLevel(t, e, s, r, o) {
1619
+ calculateLodLevel(t, e, s, r, n) {
1263
1620
  if (!e) {
1264
- o.mesh_lod = -1, o.texture_lod = -1;
1621
+ n.mesh_lod = -1, n.texture_lod = -1;
1265
1622
  return;
1266
1623
  }
1267
1624
  if (!t) {
1268
- o.mesh_lod = -1, o.texture_lod = -1;
1625
+ n.mesh_lod = -1, n.texture_lod = -1;
1269
1626
  return;
1270
1627
  }
1271
- let l = 10 + 1, a = !1;
1272
- if (k && e["DEBUG:LOD"] != null)
1628
+ let a = 10 + 1, l = !1;
1629
+ if (A && e["DEBUG:LOD"] != null)
1273
1630
  return e["DEBUG:LOD"];
1274
- const u = m.getMeshLODExtension(e.geometry)?.lods, c = m.getPrimitiveIndex(e.geometry), y = u && u.length > 0, w = m.getMaterialMinMaxLODsCount(e.material), _ = w.min_count !== 1 / 0 && w.min_count >= 0 && w.max_count >= 0;
1275
- if (!y && !_) {
1276
- o.mesh_lod = 0, o.texture_lod = 0;
1631
+ const u = g.getMeshLODExtension(e.geometry)?.lods, f = g.getPrimitiveIndex(e.geometry), c = u && u.length > 0, d = g.getMaterialMinMaxLODsCount(e.material), p = d.min_count !== 1 / 0 && d.min_count >= 0 && d.max_count >= 0;
1632
+ if (!c && !p) {
1633
+ n.mesh_lod = 0, n.texture_lod = 0;
1277
1634
  return;
1278
1635
  }
1279
- y || (a = !0, l = 0);
1280
- const R = this.renderer.domElement.clientHeight || this.renderer.domElement.height;
1281
- let M = e.geometry.boundingBox;
1636
+ c || (l = !0, a = 0);
1637
+ const b = this.renderer.domElement.clientHeight || this.renderer.domElement.height;
1638
+ let y = e.geometry.boundingBox;
1282
1639
  if (e.type === "SkinnedMesh") {
1283
- const d = e;
1284
- if (!d.boundingBox)
1285
- d.computeBoundingBox();
1640
+ const x = e;
1641
+ if (!x.boundingBox)
1642
+ x.computeBoundingBox();
1286
1643
  else if (this.skinnedMeshAutoUpdateBoundsInterval > 0) {
1287
- if (!d[v.$skinnedMeshBoundsOffset]) {
1288
- const b = v.skinnedMeshBoundsFrameOffsetCounter++;
1289
- d[v.$skinnedMeshBoundsOffset] = b;
1644
+ if (!x[I.$skinnedMeshBoundsOffset]) {
1645
+ const w = I.skinnedMeshBoundsFrameOffsetCounter++;
1646
+ x[I.$skinnedMeshBoundsOffset] = w;
1290
1647
  }
1291
- const g = d[v.$skinnedMeshBoundsOffset];
1292
- if ((s.frames + g) % this.skinnedMeshAutoUpdateBoundsInterval === 0) {
1293
- const b = ee(d), B = d.geometry;
1294
- b && (d.geometry = b), d.computeBoundingBox(), d.geometry = B;
1648
+ const L = x[I.$skinnedMeshBoundsOffset];
1649
+ if ((s.frames + L) % this.skinnedMeshAutoUpdateBoundsInterval === 0) {
1650
+ const w = ne(x), z = x.geometry;
1651
+ w && (x.geometry = w), x.computeBoundingBox(), x.geometry = z;
1295
1652
  }
1296
1653
  }
1297
- M = d.boundingBox;
1654
+ y = x.boundingBox;
1298
1655
  }
1299
- if (M) {
1300
- const d = t;
1656
+ if (y) {
1301
1657
  if (e.geometry.attributes.color && e.geometry.attributes.color.count < 100 && e.geometry.boundingSphere) {
1302
1658
  this._sphere.copy(e.geometry.boundingSphere), this._sphere.applyMatrix4(e.matrixWorld);
1303
- const h = t.getWorldPosition(this._tempWorldPosition);
1304
- if (this._sphere.containsPoint(h)) {
1305
- o.mesh_lod = 0, o.texture_lod = 0;
1659
+ const w = t.getWorldPosition(this._tempWorldPosition);
1660
+ if (this._sphere.containsPoint(w)) {
1661
+ n.mesh_lod = 0, n.texture_lod = 0;
1306
1662
  return;
1307
1663
  }
1308
1664
  }
1309
- if (this._tempBox.copy(M), this._tempBox.applyMatrix4(e.matrixWorld), d.isPerspectiveCamera && v.isInside(this._tempBox, this.projectionScreenMatrix)) {
1310
- o.mesh_lod = 0, o.texture_lod = 0;
1665
+ const x = Ut({
1666
+ geometry: e.geometry,
1667
+ matrixWorld: e.matrixWorld,
1668
+ camera: t,
1669
+ projectionScreenMatrix: this.projectionScreenMatrix,
1670
+ desiredDensity: r,
1671
+ canvasHeight: b,
1672
+ currentLevel: s.lastLodLevel_Mesh,
1673
+ boundingBox: y,
1674
+ xrEnabled: this.renderer.xr.enabled,
1675
+ debugDrawLine: A ? I.debugDrawLine : void 0,
1676
+ warnMissingPrimitiveDensities: !0
1677
+ });
1678
+ if (s.lastCentrality = x.centrality, s.lastScreenCoverage = x.screenCoverage, s.lastScreenspaceVolume.copy(x.screenspaceVolume), x.screenCoverage === 1 / 0) {
1679
+ n.mesh_lod = 0, n.texture_lod = 0;
1311
1680
  return;
1312
1681
  }
1313
- if (this._tempBox.applyMatrix4(this.projectionScreenMatrix), this.renderer.xr.enabled && d.isPerspectiveCamera && d.fov > 70) {
1314
- const h = this._tempBox.min, p = this._tempBox.max;
1315
- let O = h.x, S = h.y, G = p.x, Y = p.y;
1316
- const te = 2, ie = 1.5, se = (h.x + p.x) * 0.5, re = (h.y + p.y) * 0.5;
1317
- O = (O - se) * te + se, S = (S - re) * te + re, G = (G - se) * te + se, Y = (Y - re) * te + re;
1318
- const Fe = O < 0 && G > 0 ? 0 : Math.min(Math.abs(h.x), Math.abs(p.x)), We = S < 0 && Y > 0 ? 0 : Math.min(Math.abs(h.y), Math.abs(p.y)), ae = Math.max(Fe, We);
1319
- s.lastCentrality = (ie - ae) * (ie - ae) * (ie - ae);
1320
- } else
1321
- s.lastCentrality = 1;
1322
- const g = this._tempBox.getSize(this._tempBoxSize);
1323
- g.multiplyScalar(0.5), screen.availHeight > 0 && R > 0 && g.multiplyScalar(R / screen.availHeight), t.isPerspectiveCamera ? g.x *= t.aspect : t.isOrthographicCamera;
1324
- const b = t.matrixWorldInverse, B = this._tempBox2;
1325
- B.copy(M), B.applyMatrix4(e.matrixWorld), B.applyMatrix4(b);
1326
- const D = B.getSize(this._tempBox2Size), q = Math.max(D.x, D.y);
1327
- if (Math.max(g.x, g.y) != 0 && q != 0 && (g.z = D.z / Math.max(D.x, D.y) * Math.max(g.x, g.y)), s.lastScreenCoverage = Math.max(g.x, g.y, g.z), s.lastScreenspaceVolume.copy(g), s.lastScreenCoverage *= s.lastCentrality, k && v.debugDrawLine) {
1328
- const h = this.tempMatrix.copy(this.projectionScreenMatrix);
1329
- h.invert();
1330
- const p = v.corner0, O = v.corner1, S = v.corner2, G = v.corner3;
1331
- p.copy(this._tempBox.min), O.copy(this._tempBox.max), O.x = p.x, S.copy(this._tempBox.max), S.y = p.y, G.copy(this._tempBox.max);
1332
- const Y = (p.z + G.z) * 0.5;
1333
- p.z = O.z = S.z = G.z = Y, p.applyMatrix4(h), O.applyMatrix4(h), S.applyMatrix4(h), G.applyMatrix4(h), v.debugDrawLine(p, O, 255), v.debugDrawLine(p, S, 255), v.debugDrawLine(O, G, 255), v.debugDrawLine(S, G, 255);
1334
- }
1335
- let L = 999;
1336
- if (u && s.lastScreenCoverage > 0)
1337
- for (let h = 0; h < u.length; h++) {
1338
- const p = u[h], S = (p.densities?.[c] || p.density || 1e-5) / s.lastScreenCoverage;
1339
- if (c > 0 && Oe() && !p.densities && !globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] && (window["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] = !0, console.warn("[Needle Progressive] Detected usage of mesh without primitive densities. This might cause incorrect LOD level selection: Consider re-optimizing your model by updating your Needle Integration, Needle glTF Pipeline or running optimization again on Needle Cloud.")), S < r) {
1340
- L = h;
1341
- break;
1342
- }
1343
- }
1344
- L < l && (l = L, a = !0);
1682
+ x.level >= 0 && x.level < a && (a = x.level, l = !0);
1345
1683
  }
1346
- if (a ? o.mesh_lod = l : o.mesh_lod = s.lastLodLevel_Mesh, k && o.mesh_lod != s.lastLodLevel_Mesh) {
1347
- const g = u?.[o.mesh_lod];
1348
- g && console.log(`Mesh LOD changed: ${s.lastLodLevel_Mesh} → ${o.mesh_lod} (density: ${g.densities?.[c].toFixed(0)}) | ${e.name}`);
1684
+ if (l ? n.mesh_lod = a : n.mesh_lod = s.lastLodLevel_Mesh, A && n.mesh_lod != s.lastLodLevel_Mesh) {
1685
+ const L = u?.[n.mesh_lod];
1686
+ L && console.log(`Mesh LOD changed: ${s.lastLodLevel_Mesh} → ${n.mesh_lod} (density: ${L.densities?.[f].toFixed(0)}) | ${e.name}`);
1349
1687
  }
1350
- if (_) {
1351
- const d = "saveData" in globalThis.navigator && globalThis.navigator.saveData === !0;
1688
+ if (p) {
1689
+ const x = "saveData" in globalThis.navigator && globalThis.navigator.saveData === !0;
1352
1690
  if (s.lastLodLevel_Texture < 0) {
1353
- if (o.texture_lod = w.max_count - 1, k) {
1354
- const g = w.lods[w.max_count - 1];
1355
- k && console.log(`First Texture LOD ${o.texture_lod} (${g.max_height}px) - ${e.name}`);
1691
+ if (n.texture_lod = d.max_count - 1, A) {
1692
+ const L = d.lods[d.max_count - 1];
1693
+ A && console.log(`First Texture LOD ${n.texture_lod} (${L.max_height}px) - ${e.name}`);
1356
1694
  }
1357
1695
  } else {
1358
- const g = s.lastScreenspaceVolume.x + s.lastScreenspaceVolume.y + s.lastScreenspaceVolume.z;
1359
- let b = s.lastScreenCoverage * 4;
1360
- this.context?.engine === "model-viewer" && (b *= 1.5);
1361
- const D = R / window.devicePixelRatio * b;
1362
- let q = !1;
1363
- for (let T = w.lods.length - 1; T >= 0; T--) {
1364
- const L = w.lods[T];
1365
- if (!(d && L.max_height >= 2048) && !(ve() && L.max_height > 4096) && (L.max_height > D || !q && T === 0)) {
1366
- if (q = !0, o.texture_lod = T, k && o.texture_lod < s.lastLodLevel_Texture) {
1367
- const x = L.max_height;
1368
- console.log(`Texture LOD changed: ${s.lastLodLevel_Texture} → ${o.texture_lod} = ${x}px
1369
- Screensize: ${D.toFixed(0)}px, Coverage: ${(100 * s.lastScreenCoverage).toFixed(2)}%, Volume ${g.toFixed(1)}
1696
+ const L = s.lastScreenspaceVolume.x + s.lastScreenspaceVolume.y + s.lastScreenspaceVolume.z;
1697
+ let w = s.lastScreenCoverage * 4;
1698
+ this.context?.engine === "model-viewer" && (w *= 1.5);
1699
+ const z = this.renderer.getPixelRatio?.() || globalThis.devicePixelRatio || 1, T = b / z * w;
1700
+ let O = !1;
1701
+ for (let P = d.lods.length - 1; P >= 0; P--) {
1702
+ const S = d.lods[P];
1703
+ if (!(x && S.max_height >= 2048) && !(Me() && S.max_height > 4096) && (S.max_height > T || !O && P === 0)) {
1704
+ if (O = !0, n.texture_lod = P, A && n.texture_lod < s.lastLodLevel_Texture) {
1705
+ const _ = S.max_height;
1706
+ console.log(`Texture LOD changed: ${s.lastLodLevel_Texture} → ${n.texture_lod} = ${_}px
1707
+ Screensize: ${T.toFixed(0)}px, Coverage: ${(100 * s.lastScreenCoverage).toFixed(2)}%, Volume ${L.toFixed(1)}
1370
1708
  ${e.name}`);
1371
1709
  }
1372
1710
  break;
@@ -1374,62 +1712,77 @@ ${e.name}`);
1374
1712
  }
1375
1713
  }
1376
1714
  } else
1377
- o.texture_lod = 0;
1715
+ n.texture_lod = 0;
1378
1716
  }
1379
1717
  }
1380
- class Tt {
1718
+ class zt {
1381
1719
  frames = 0;
1382
1720
  lastLodLevel_Mesh = -1;
1383
1721
  lastLodLevel_Texture = -1;
1384
1722
  lastScreenCoverage = 0;
1385
- lastScreenspaceVolume = new A();
1723
+ lastScreenspaceVolume = new k();
1386
1724
  lastCentrality = 0;
1387
1725
  }
1388
- const Se = /* @__PURE__ */ Symbol("NEEDLE_mesh_lod"), oe = /* @__PURE__ */ Symbol("NEEDLE_texture_lod");
1389
- let he = null;
1390
- function Be() {
1391
- const i = Pt();
1726
+ function ze(i, t) {
1727
+ if (!(t < 0)) {
1728
+ if (Array.isArray(i)) {
1729
+ for (const e of i)
1730
+ ze(e, t);
1731
+ return;
1732
+ }
1733
+ "color" in i && i.color instanceof Ie && (i.color.copy(Et(t, $t)), i.needsUpdate = !0);
1734
+ }
1735
+ }
1736
+ function Et(i, t) {
1737
+ const e = Math.max(0, Math.min(Se.length - 1, Math.floor(i)));
1738
+ return t.setHex(Se[e]);
1739
+ }
1740
+ const Pe = /* @__PURE__ */ Symbol("NEEDLE_mesh_lod"), ae = /* @__PURE__ */ Symbol("NEEDLE_texture_lod");
1741
+ let me = null;
1742
+ function Ee() {
1743
+ const i = Nt();
1392
1744
  i && (i.mapURLs(function(t) {
1393
- return Te(), t;
1394
- }), Te(), he?.disconnect(), he = new MutationObserver((t) => {
1745
+ return Ce(), t;
1746
+ }), Ce(), me?.disconnect(), me = new MutationObserver((t) => {
1395
1747
  t.forEach((e) => {
1396
1748
  e.addedNodes.forEach((s) => {
1397
- s instanceof HTMLElement && s.tagName.toLowerCase() === "model-viewer" && Ge(s);
1749
+ s instanceof HTMLElement && s.tagName.toLowerCase() === "model-viewer" && Ne(s);
1398
1750
  });
1399
1751
  });
1400
- }), he.observe(document, { childList: !0, subtree: !0 }));
1752
+ }), me.observe(document, { childList: !0, subtree: !0 }));
1401
1753
  }
1402
- function Pt() {
1754
+ function Nt() {
1403
1755
  if (typeof customElements > "u") return null;
1404
1756
  const i = customElements.get("model-viewer");
1405
1757
  return i || (customElements.whenDefined("model-viewer").then(() => {
1406
- console.debug("[gltf-progressive] model-viewer defined"), Be();
1758
+ console.debug("[gltf-progressive] model-viewer defined"), Ee();
1407
1759
  }), null);
1408
1760
  }
1409
- function Te() {
1761
+ function Ce() {
1410
1762
  if (typeof document > "u") return;
1411
1763
  document.querySelectorAll("model-viewer").forEach((t) => {
1412
- Ge(t);
1764
+ Ne(t);
1413
1765
  });
1414
1766
  }
1415
- const Pe = /* @__PURE__ */ new WeakSet();
1416
- let Ct = 0;
1417
- function Ge(i) {
1418
- if (!i || Pe.has(i))
1767
+ const ke = /* @__PURE__ */ new WeakSet();
1768
+ let Vt = 0;
1769
+ function Ne(i) {
1770
+ if (!i || ke.has(i))
1419
1771
  return null;
1420
- Pe.add(i), console.debug("[gltf-progressive] found new model-viewer..." + ++Ct + `
1772
+ ke.add(i), console.debug("[gltf-progressive] found new model-viewer..." + ++Vt + `
1421
1773
  `, i.getAttribute("src"));
1422
1774
  let t = null, e = null, s = null;
1423
1775
  for (let r = i; r != null; r = Object.getPrototypeOf(r)) {
1424
- const o = Object.getOwnPropertySymbols(r), n = o.find((u) => u.toString() == "Symbol(renderer)"), l = o.find((u) => u.toString() == "Symbol(scene)"), a = o.find((u) => u.toString() == "Symbol(needsRender)");
1425
- !t && n != null && (t = i[n].threeRenderer), !e && l != null && (e = i[l]), !s && a != null && (s = i[a]);
1776
+ const n = Object.getOwnPropertySymbols(r), o = n.find((u) => u.toString() == "Symbol(renderer)"), a = n.find((u) => u.toString() == "Symbol(scene)"), l = n.find((u) => u.toString() == "Symbol(needsRender)");
1777
+ !t && o != null && (t = i[o].threeRenderer), !e && a != null && (e = i[a]), !s && l != null && (s = i[l]);
1426
1778
  }
1427
1779
  if (t && e) {
1428
1780
  let r = function() {
1429
1781
  if (s) {
1430
- let n = 0, l = setInterval(() => {
1431
- if (n++ > 5) {
1432
- clearInterval(l);
1782
+ let o = 0;
1783
+ const a = setInterval(() => {
1784
+ if (o++ > 5) {
1785
+ clearInterval(a);
1433
1786
  return;
1434
1787
  }
1435
1788
  s?.call(i);
@@ -1437,20 +1790,20 @@ function Ge(i) {
1437
1790
  }
1438
1791
  };
1439
1792
  console.debug("[gltf-progressive] setup model-viewer");
1440
- const o = v.get(t, { engine: "model-viewer" });
1441
- return v.addPlugin(new At()), o.enable(), o.addEventListener("changed", () => {
1793
+ const n = I.get(t, { engine: "model-viewer" });
1794
+ return I.addPlugin(new Xt()), n.enable(), n.addEventListener("changed", () => {
1442
1795
  s?.call(i);
1443
- }), i.addEventListener("model-visibility", (n) => {
1444
- n.detail.visible && s?.call(i);
1796
+ }), i.addEventListener("model-visibility", (o) => {
1797
+ o.detail.visible && s?.call(i);
1445
1798
  }), i.addEventListener("load", () => {
1446
1799
  r();
1447
1800
  }), () => {
1448
- o.disable();
1801
+ n.disable();
1449
1802
  };
1450
1803
  }
1451
1804
  return null;
1452
1805
  }
1453
- class At {
1806
+ class Xt {
1454
1807
  _didWarnAboutMissingUrl = !1;
1455
1808
  onBeforeUpdateLOD(t, e, s, r) {
1456
1809
  this.tryParseMeshLOD(e, r), this.tryParseTextureLOD(e, r);
@@ -1468,50 +1821,50 @@ class At {
1468
1821
  return t.element;
1469
1822
  }
1470
1823
  tryParseTextureLOD(t, e) {
1471
- if (e[oe] == !0) return;
1472
- e[oe] = !0;
1473
- const s = this.tryGetCurrentGLTF(t), r = this.tryGetCurrentModelViewer(t), o = this.getUrl(r);
1474
- if (o && s && e.material) {
1475
- let n = function(a) {
1476
- if (a[oe] == !0) return;
1477
- a[oe] = !0, a.userData && (a.userData.LOD = -1);
1478
- const u = Object.keys(a);
1479
- for (let c = 0; c < u.length; c++) {
1480
- const y = u[c], w = a[y];
1481
- if (w?.isTexture === !0) {
1482
- const _ = w.userData?.associations?.textures;
1483
- if (_ == null) continue;
1484
- const R = s.parser.json.textures[_];
1485
- if (!R) {
1486
- console.warn("Texture data not found for texture index " + _);
1824
+ if (e[ae] == !0) return;
1825
+ e[ae] = !0;
1826
+ const s = this.tryGetCurrentGLTF(t), r = this.tryGetCurrentModelViewer(t), n = this.getUrl(r);
1827
+ if (n && s && e.material) {
1828
+ let o = function(l) {
1829
+ if (l[ae] == !0) return;
1830
+ l[ae] = !0, l.userData && (l.userData.LOD = -1);
1831
+ const u = Object.keys(l);
1832
+ for (let f = 0; f < u.length; f++) {
1833
+ const c = u[f], d = l[c];
1834
+ if (d?.isTexture === !0) {
1835
+ const p = d.userData?.associations?.textures;
1836
+ if (p == null) continue;
1837
+ const b = s.parser.json.textures[p];
1838
+ if (!b) {
1839
+ console.warn("Texture data not found for texture index " + p);
1487
1840
  continue;
1488
1841
  }
1489
- if (R?.extensions?.[W]) {
1490
- const M = R.extensions[W];
1491
- M && o && m.registerTexture(o, w, M.lods.length, _, M);
1842
+ if (b?.extensions?.[U]) {
1843
+ const y = b.extensions[U];
1844
+ y && n && g.registerTexture(n, d, y.lods.length, p, y);
1492
1845
  }
1493
1846
  }
1494
1847
  }
1495
1848
  };
1496
- const l = e.material;
1497
- if (Array.isArray(l)) for (const a of l) n(a);
1498
- else n(l);
1849
+ const a = e.material;
1850
+ if (Array.isArray(a)) for (const l of a) o(l);
1851
+ else o(a);
1499
1852
  }
1500
1853
  }
1501
1854
  tryParseMeshLOD(t, e) {
1502
- if (e[Se] == !0) return;
1503
- e[Se] = !0;
1855
+ if (e[Pe] == !0) return;
1856
+ e[Pe] = !0;
1504
1857
  const s = this.tryGetCurrentModelViewer(t), r = this.getUrl(s);
1505
1858
  if (!r)
1506
1859
  return;
1507
- const o = e.userData?.gltfExtensions?.[W];
1508
- if (o && r) {
1509
- const n = e.uuid;
1510
- m.registerMesh(r, n, e, 0, o.lods.length, o);
1860
+ const n = e.userData?.gltfExtensions?.[U];
1861
+ if (n && r) {
1862
+ const o = e.uuid;
1863
+ g.registerMesh(r, o, e, 0, n.lods.length, n);
1511
1864
  }
1512
1865
  }
1513
1866
  }
1514
- function Rt(...i) {
1867
+ function jt(...i) {
1515
1868
  let t, e, s, r;
1516
1869
  switch (i.length) {
1517
1870
  case 2:
@@ -1526,22 +1879,22 @@ function Rt(...i) {
1526
1879
  default:
1527
1880
  throw new Error("Invalid arguments");
1528
1881
  }
1529
- Le(e), Re(s), Ie(s, {
1882
+ _e(e), Ge(s), We(s, {
1530
1883
  progressive: !0,
1531
1884
  ...r?.hints
1532
- }), s.register((n) => new m(n));
1533
- const o = v.get(e);
1534
- return r?.enableLODsManager !== !1 && o.enable(), o;
1885
+ }), s.register((o) => new g(o));
1886
+ const n = I.get(e);
1887
+ return r?.enableLODsManager !== !1 && n.enable(), n;
1535
1888
  }
1536
- Be();
1537
- if (!yt) {
1889
+ Ee();
1890
+ if (!_t) {
1538
1891
  const i = {
1539
1892
  gltfProgressive: {
1540
- useNeedleProgressive: Rt,
1541
- LODsManager: v,
1542
- configureLoader: Ie,
1543
- getRaycastMesh: ee,
1544
- useRaycastMeshes: wt
1893
+ useNeedleProgressive: jt,
1894
+ LODsManager: I,
1895
+ configureLoader: We,
1896
+ getRaycastMesh: ne,
1897
+ useRaycastMeshes: bt
1545
1898
  }
1546
1899
  };
1547
1900
  if (!globalThis.Needle)
@@ -1551,18 +1904,21 @@ if (!yt) {
1551
1904
  globalThis.Needle[t] = i[t];
1552
1905
  }
1553
1906
  export {
1554
- W as EXTENSION_NAME,
1555
- v as LODsManager,
1556
- m as NEEDLE_progressive,
1557
- ot as VERSION,
1558
- Re as addDracoAndKTX2Loaders,
1559
- Ie as configureLoader,
1560
- Le as createLoaders,
1561
- ee as getRaycastMesh,
1562
- Be as patchModelViewer,
1563
- xt as registerRaycastMesh,
1564
- lt as setDracoDecoderLocation,
1565
- ut as setKTX2TranscoderLocation,
1566
- Rt as useNeedleProgressive,
1567
- wt as useRaycastMeshes
1907
+ U as EXTENSION_NAME,
1908
+ I as LODsManager,
1909
+ g as NEEDLE_progressive,
1910
+ ut as VERSION,
1911
+ Ge as addDracoAndKTX2Loaders,
1912
+ Ut as calculateMeshLODLevel,
1913
+ We as configureLoader,
1914
+ _e as createLoaders,
1915
+ Et as getLODColor,
1916
+ ne as getRaycastMesh,
1917
+ Se as lodDebugColors,
1918
+ Ee as patchModelViewer,
1919
+ Mt as registerRaycastMesh,
1920
+ ht as setDracoDecoderLocation,
1921
+ gt as setKTX2TranscoderLocation,
1922
+ jt as useNeedleProgressive,
1923
+ bt as useRaycastMeshes
1568
1924
  };