@needle-tools/gltf-progressive 4.0.0-alpha.1 → 4.0.0-alpha.3

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 +26 -2
  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 +1016 -647
  25. package/gltf-progressive.min.js +9 -9
  26. package/gltf-progressive.umd.cjs +9 -9
  27. package/lib/extension.d.ts +75 -7
  28. package/lib/extension.js +379 -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 H, Mesh as Y, Box3 as fe, Vector3 as A, Sphere as Ie, CompressedTexture as et, Texture as W, Matrix3 as tt, InterleavedBuffer as st, InterleavedBufferAttribute as rt, BufferAttribute as nt, TextureLoader as it, Color as $e, Matrix4 as Ge } from "three";
3
+ import { GLTFLoader as Me } 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 B = "https://www.gstatic.com/draco/versioned/decoders/1.5.7/", Q = "https://cdn.needle.tools/static/three/0.179.1/basis2/";
11
+ const ct = B, ft = Q, Be = new URL(B + "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 ${B} (offline: ${typeof navigator < "u" ? navigator.onLine : "unknown"})`), B === ct && ht("./include/draco/"), Q === ft && gt("./include/ktx2/");
19
20
  }).finally(() => {
20
- ke();
21
+ We();
21
22
  });
22
- const at = () => ({
23
- dracoDecoderPath: I,
24
- ktx2TranscoderPath: X
23
+ const dt = () => ({
24
+ dracoDecoderPath: B,
25
+ ktx2TranscoderPath: Q
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
+ B = i, k && k[xe] != B ? (console.debug("Updating Draco decoder path to " + i), k[xe] = B, k.setDecoderPath(B), k.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
+ Q = i, q && q.transcoderPath != Q ? (console.debug("Updating KTX2 transcoder path to " + i), q.setTranscoderPath(Q), q.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 We(), i ? q.detectSupport(i) : i !== null && console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"), { dracoLoader: k, ktx2Loader: q, meshoptDecoder: ce };
34
35
  }
35
- function Re(i) {
36
- i.dracoLoader || i.setDRACOLoader(C), i.ktx2Loader || i.setKTX2Loader($), i.meshoptDecoder || i.setMeshoptDecoder(ne);
36
+ function qe(i) {
37
+ i.dracoLoader || i.setDRACOLoader(k), i.ktx2Loader || i.setKTX2Loader(q), i.meshoptDecoder || i.setMeshoptDecoder(ce);
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 xe = /* @__PURE__ */ Symbol("dracoDecoderPath");
40
+ let k, ce, q;
41
+ function We() {
42
+ k || (k = new at(), k[xe] = B, k.setDecoderPath(B), k.preload()), q || (q = new lt(), q.setTranscoderPath(Q), q.init()), ce || (ce = 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 we = /* @__PURE__ */ new WeakMap();
45
+ function Fe(i, t) {
46
+ let e = we.get(i);
47
+ e ? e = Object.assign(e, t) : e = t, we.set(i, e);
47
48
  }
48
- const ct = _e.prototype.load;
49
- function dt(...i) {
50
- const t = me.get(this);
49
+ const pt = Me.prototype.load;
50
+ function mt(...i) {
51
+ const t = we.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
+ Me.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 Oe(i) {
61
72
  return i != null && i.data != null;
62
73
  }
63
- function ye(i) {
74
+ function Le(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 ve(i) {
83
+ const t = xt(i), e = Le(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
+ X("debugprogressive");
90
+ function X(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 be() {
108
+ if (se !== void 0) return se;
109
+ const i = globalThis.navigator?.userAgent || "";
110
+ return se = /iPhone|iPad|iPod|Android|IEMobile/i.test(i), X("debugprogressive") && console.log("[glTF Progressive]: isMobileDevice", se), se;
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 se;
113
+ function Ue() {
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,97 +145,98 @@ 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 Mt = typeof window > "u" && typeof document > "u", De = /* @__PURE__ */ Symbol("needle:raycast-mesh");
166
+ function ae(i) {
167
+ return i?.[De] instanceof H ? i[De] : 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 _t(i, t) {
170
+ if ((i.type === "Mesh" || i.type === "SkinnedMesh") && !ae(i)) {
171
+ const s = Tt(t);
172
+ s.userData = { isRaycastMesh: !0 }, i[De] = 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 (re) return;
178
+ const t = re = Y.prototype.raycast;
179
+ Y.prototype.raycast = function(e, s) {
180
+ const r = this, n = ae(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 (!re) return;
186
+ Y.prototype.raycast = re, re = null;
172
187
  }
173
188
  }
174
- let Q = null;
175
- function _t(i) {
176
- const t = new E();
189
+ let re = null;
190
+ function Tt(i) {
191
+ const t = new H();
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 U = new Array(), g = X("debugprogressive");
197
+ let oe, K = -1;
198
+ if (g && 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
+ K += 1, K >= t && (K = -1), console.log(`Toggle LOD level [${K}]`);
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" && (oe = !oe, console.log(`Toggle wireframe [${oe}]`));
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 && (K = s, console.log(`Set LOD level to [${K}]`));
191
207
  });
192
208
  }
193
- function $e(i) {
194
- if (f && Z !== void 0)
209
+ function ze(i) {
210
+ if (g && oe !== 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
+ ze(t);
214
+ else i && "wireframe" in i && (i.wireframe = oe === !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}`);
207
- const r = be.createWorker(i || {});
208
- return J.push(r), r;
209
- }
210
- const t = Lt++ % J.length;
211
- return J[t];
216
+ const ne = new Array();
217
+ let Ot = 0;
218
+ const St = be() ? 2 : 10;
219
+ function Rt(i) {
220
+ if (ne.length < St) {
221
+ const s = ne.length;
222
+ g && console.warn(`[Worker] Creating new worker #${s}`);
223
+ const r = Te.createWorker(i || {});
224
+ return ne.push(r), r;
225
+ }
226
+ const t = Ot++ % ne.length;
227
+ return ne[t];
212
228
  }
213
- class be {
229
+ class Te {
214
230
  constructor(t, e) {
215
231
  this.worker = t, this._debug = e.debug ?? !1, t.onmessage = (s) => {
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) => {
@@ -232,24 +248,31 @@ class be {
232
248
  static async createWorker(t) {
233
249
  const e = new Worker(new URL(
234
250
  /* @vite-ignore */
235
- "/assets/gltf-progressive.worker-CDSrhw-p.js",
251
+ "/assets/gltf-progressive.worker-Cj5rV8iI.js",
236
252
  import.meta.url
237
253
  ), {
238
254
  type: "module"
239
255
  });
240
- return new be(e, t);
256
+ return new Te(e, t);
241
257
  }
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());
267
+ const l = _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
+ }
253
276
  const a = {
254
277
  type: "load",
255
278
  url: t,
@@ -266,23 +289,23 @@ 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 H();
272
295
  if (s.name = e.name || "", e.index) {
273
296
  const r = e.index;
274
- s.setIndex(le(r));
297
+ s.setIndex(de(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 = de(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((l) => de(l));
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 fe(), s.boundingBox.min = new A(
286
309
  e.boundingBox?.min.x,
287
310
  e.boundingBox?.min.y,
288
311
  e.boundingBox?.min.z
@@ -290,7 +313,7 @@ function Mt(i) {
290
313
  e.boundingBox?.max.x,
291
314
  e.boundingBox?.max.y,
292
315
  e.boundingBox?.max.z
293
- ), s.boundingSphere = new Ce(
316
+ ), s.boundingSphere = new Ie(
294
317
  new A(
295
318
  e.boundingSphere?.center.x,
296
319
  e.boundingSphere?.center.y,
@@ -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 } = ve(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 W(
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,34 +365,57 @@ function Mt(i) {
342
365
  }
343
366
  return i;
344
367
  }
345
- function le(i) {
368
+ function de(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 = X("gltf-progressive-worker");
377
+ X("gltf-progressive-reduce-mipmaps");
378
+ const ie = X("gltf-progressive-gc"), he = /* @__PURE__ */ Symbol("needle-progressive-texture"), z = "NEEDLE_progressive";
379
+ class p {
357
380
  /** The name of the extension */
358
381
  get name() {
359
- return W;
382
+ return z;
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,
@@ -379,29 +425,29 @@ class m {
379
425
  this.getMaterialMinMaxLODsCount(l, e);
380
426
  return t[r] = e, e;
381
427
  }
382
- if (f === "verbose" && console.log("getMaterialMinMaxLODsCount", t), t.type === "ShaderMaterial" || t.type === "RawShaderMaterial") {
428
+ if (g === "verbose" && console.log("getMaterialMinMaxLODsCount", t), t.type === "ShaderMaterial" || t.type === "RawShaderMaterial") {
383
429
  const l = t;
384
430
  for (const a of Object.keys(l.uniforms)) {
385
431
  const u = l.uniforms[a].value;
386
- u?.isTexture === !0 && n(u, e);
432
+ u?.isTexture === !0 && o(u, e);
387
433
  }
388
434
  } else if (t.isMaterial)
389
435
  for (const l of Object.keys(t)) {
390
436
  const a = t[l];
391
- a?.isTexture === !0 && n(a, e);
437
+ a?.isTexture === !0 && o(a, e);
392
438
  }
393
439
  else
394
- f && console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);
440
+ g && console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);
395
441
  return t[r] = e, e;
396
- function n(l, a) {
442
+ function o(l, a) {
397
443
  const u = s.getAssignedLODInformation(l);
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 d = s.lodInfos.get(u.key);
446
+ if (d && d.lods) {
447
+ a.min_count = Math.min(a.min_count, d.lods.length), a.max_count = Math.max(a.max_count, d.lods.length);
448
+ for (let c = 0; c < d.lods.length; c++) {
449
+ const f = d.lods[c];
450
+ f.width && (a.lods[c] = a.lods[c] || { min_height: 1 / 0, max_height: 0 }, a.lods[c].min_height = Math.min(a.lods[c].min_height, f.height), a.lods[c].max_height = Math.max(a.lods[c].max_height, f.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 Y || 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 l of U)
509
+ l.onBeforeGetLODMesh?.(t, e);
510
+ t["LOD:requested level"] = e;
511
+ const o = () => t["LOD:requested level"] === e || this.shouldApplyStaleMeshLOD(t, e);
512
+ return p.getOrLoadLOD(r, e, {
513
+ isCurrent: o
514
+ }).then((l) => {
515
+ if (Array.isArray(l)) {
516
+ const u = n.index || 0;
517
+ l = l[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 a = t["LOD:requested level"] === e;
520
+ return (a || this.shouldApplyStaleMeshLOD(t, e)) && (a && delete t["LOD:requested level"], l && r != l && (l?.isBufferGeometry ? typeof s?.apply == "function" ? s.apply(l, e, t) : s?.apply !== !1 && (t.geometry = l) : g && console.error("Invalid LOD geometry", l))), l;
521
+ }).catch((l) => (console.error("Error loading mesh LOD", t, l), null));
522
+ } else g && 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 l of n.material) {
533
+ const a = this.assignTextureLOD(l, e, s);
534
+ o.push(a);
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((l) => {
537
+ const a = new Array();
538
+ for (const u of l)
539
+ Array.isArray(u) && a.push(...u);
540
+ return a;
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 = [], l = new Array();
547
+ if (this.trackCurrentMaterialTextureSlots(n), n.uniforms && (n.isRawShaderMaterial || n.isShaderMaterial === !0)) {
548
+ const a = n;
549
+ for (const u of Object.keys(a.uniforms)) {
550
+ const d = a.uniforms[u].value;
551
+ if (d?.isTexture === !0) {
552
+ const c = this.assignTextureLODForSlot(d, e, n, u, r).then((f) => (f && a.uniforms[u].value != f && (a.uniforms[u].value = f, a.uniformsNeedUpdate = !0), f));
553
+ o.push(c), l.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 a of Object.keys(n)) {
558
+ const u = n[a];
559
+ if (u?.isTexture === !0) {
560
+ const d = this.assignTextureLODForSlot(u, e, n, a, r);
561
+ o.push(d), l.push(a);
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((a) => {
565
+ const u = new Array();
566
+ for (let d = 0; d < a.length; d++) {
567
+ const c = a[d], f = l[d];
568
+ c && c.isTexture === !0 ? u.push({ material: n, slot: f, texture: c, level: e }) : u.push({ material: n, slot: f, 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 W || 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
+ p.queue.maxConcurrent = t;
533
585
  }
534
586
  static get maxConcurrentLoadingTasks() {
535
- return m.queue.maxConcurrent;
587
+ return p.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 l = s && r ? this.nextTextureSlotRequestId(s, r, e, n) : 0, a = () => !s || !r || this.getLatestTextureSlotRequest(s, r)?.id === l, u = () => a() || this.shouldApplyStaleTextureSlotLOD(s, r, e, n), d = p.getOrLoadLOD(t, e, {
604
+ isCurrent: u
605
+ }).then((c) => {
606
+ if (!a() && !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;
549
- }
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);
609
+ if (c?.isTexture === !0) {
610
+ if (c != t && s && r) {
611
+ const f = this.getMaterialTextureSlot(s, r) ?? t;
612
+ if (f && !n) {
613
+ const h = this.getAssignedLODInformation(f);
614
+ if (h && h?.level < e)
615
+ return g === "verbose" && console.warn("Assigned texture level is already higher: ", h.level, e, s, f, c), null;
553
616
  }
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 g == "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, l, d), d;
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), l = this.getAssignedLODInformation(o)?.level ?? 1 / 0;
660
+ return s >= l ? !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 l = this.pendingTextureSlotRequests.get(t);
670
+ l || (l = /* @__PURE__ */ new Map(), this.pendingTextureSlotRequests.set(t, l));
671
+ const a = { level: s, force: r, id: n, promise: o };
672
+ l.set(e, a), o.finally(() => {
673
+ l.get(e)?.id === n && l.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) && (g || ie)) {
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
+ g && 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?.[z];
721
+ return e ? (this._isLoadingMesh = !0, this.parser.getDependency("mesh", t).then((s) => (this._isLoadingMesh = !1, s && p.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 g && 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[z];
591
741
  if (r) {
592
742
  if (!r.lods) {
593
- f && console.warn("Texture has no LODs", r);
743
+ g && 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, p.registerTexture(this.url, o, r.lods?.length, s, r));
749
+ n || this.parser.getDependency("texture", s).then((o) => {
750
+ o && p.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[z];
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 && p.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
+ g && 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 (g) {
784
+ const { width: l, height: a } = ve(e);
785
+ console.log(`> gltf-progressive: register texture[${r}] "${e.name || e.uuid}", Current: ${l}x${a}, 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[he] = n);
788
+ const o = n.guid;
789
+ p.assignLODInformation(t, e, o, s, r), p.lodInfos.set(o, n), p.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) => {
804
+ static registerMesh = (t, e, s, r, n, o) => {
637
805
  const l = s.geometry;
638
806
  if (!l) {
639
- f && console.warn("gltf-progressive: Register mesh without geometry");
807
+ g && 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
+ l.userData || (l.userData = {}), g && console.log("> Progressive: register mesh " + s.name, { index: n, uuid: s.uuid }, o, s), p.assignLODInformation(t, l, e, r, n), p.lodInfos.set(e, o);
811
+ let u = p.lowresCache.get(e)?.deref();
812
+ u ? u.push(s.geometry) : u = [s.geometry], p.lowresCache.set(e, new WeakRef(u)), r > 0 && !ae(s) && _t(s, l);
813
+ for (const d of U)
814
+ d.onRegisteredNewMesh?.(s, o);
647
815
  };
648
816
  /**
649
817
  * Dispose cached resources to free memory.
@@ -670,7 +838,7 @@ class m {
670
838
  for (const [s, r] of this.cache)
671
839
  s.includes(t) && (this._disposeCacheEntry(r), this.cache.delete(s));
672
840
  } else {
673
- this.lodInfos.clear();
841
+ this.cacheGeneration++, this.lodInfos.clear();
674
842
  for (const [, e] of this.lowresCache) {
675
843
  const s = e.deref();
676
844
  if (s) {
@@ -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. */
@@ -693,7 +861,7 @@ class m {
693
861
  const e = t.deref();
694
862
  e && (e.isTexture && this.textureRefCounts.delete(e.uuid), e.dispose());
695
863
  } else
696
- t.then((e) => {
864
+ this.disposedRequests.add(t), t.then((e) => {
697
865
  if (e)
698
866
  if (Array.isArray(e))
699
867
  for (const s of e) s.dispose();
@@ -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 = p.cache.get(t);
888
+ (g || ie) && console.debug(`[gltf-progressive] Memory: Resource GC'd
889
+ ${t}`), e instanceof WeakRef && (e.deref() || (p.cache.delete(t), (g || ie) && 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), g === "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,209 @@ ${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 (g === "verbose" || ie) && 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), (g || ie) && n("[gltf-progressive] Memory: Dispose texture", r), t.dispose(), !0;
910
+ return this.textureRefCounts.set(e, r), g === "verbose" && n("[gltf-progressive] Memory: Untrack texture", r), !1;
911
+ function n(o, l) {
912
+ let { width: a, height: u } = ve(t);
913
+ const d = a && u ? `${a}x${u}` : "N/A";
914
+ let c = "N/A";
915
+ a && u && (c = `~${(vt(t) / (1024 * 1024)).toFixed(2)} MB`), console.log(`${o} — ${t.name} ${d} (${c}), refCount: ${s} → ${l}
748
916
  ${e}`);
749
917
  }
750
918
  }
751
919
  static workers = [];
920
+ static cacheGeneration = 0;
921
+ static disposedRequests = /* @__PURE__ */ new WeakSet();
752
922
  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;
923
+ static async getOrLoadLOD(t, e, s) {
924
+ const r = this.cacheGeneration, n = g == "verbose", o = this.getAssignedLODInformation(t);
925
+ if (!o)
926
+ return g && console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`, t), null;
927
+ const l = o?.key;
928
+ let a;
759
929
  if (t.isTexture === !0) {
760
- const a = t;
761
- a.source && a.source[ue] && (n = a.source[ue]);
930
+ const d = t;
931
+ d.source && d.source[he] && (a = d.source[he]);
762
932
  }
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);
933
+ if (a || (a = p.lodInfos.get(l)), !a)
934
+ g && console.warn(`Can not load LOD ${e}: no LOD info found for "${l}" ${t.name}`, t.type, p.lodInfos);
765
935
  else {
766
936
  if (e > 0) {
767
- 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}`);
937
+ let f = !1;
938
+ const h = Array.isArray(a.lods);
939
+ if (h && e >= a.lods.length ? f = !0 : h || (f = !0), f) {
940
+ const D = this.lowresCache.get(l);
941
+ if (D) {
942
+ const w = D.deref();
943
+ if (w) return w;
944
+ this.lowresCache.delete(l), g && console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${l}`);
775
945
  }
776
946
  return null;
777
947
  }
778
948
  }
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);
949
+ const d = Array.isArray(a.lods) ? a.lods[e]?.path : a.lods;
950
+ if (!d)
951
+ return g && !a["missing:uri"] && (a["missing:uri"] = !0, console.warn("Missing uri for progressive asset for LOD " + e, a)), null;
952
+ const c = wt(o.url, d);
953
+ if (c.endsWith(".glb") || c.endsWith(".gltf")) {
954
+ if (!a.guid)
955
+ return console.warn("missing pointer for glb/gltf texture", a), null;
956
+ const f = c + "_" + a.guid, h = await this.tryResolveLODCacheEntry(this.cache.get(f), f, c, t, e, n);
957
+ if (r !== this.cacheGeneration) return null;
958
+ if (h.found) return h.value;
959
+ if (s?.isCurrent?.() === !1)
960
+ return n && console.log(`Skipping stale LOD ${e} request before queue: ${c}`), null;
961
+ const D = await this.queue.slot(c);
962
+ if (r !== this.cacheGeneration) return null;
963
+ if (s?.isCurrent?.() === !1)
964
+ return n && console.log(`Skipping stale LOD ${e} request after queue: ${c}`), null;
965
+ const w = await this.tryResolveLODCacheEntry(this.cache.get(f), f, c, t, e, n);
966
+ if (r !== this.cacheGeneration) return null;
967
+ if (w.found) return w.value;
968
+ const x = this.cache.get(f);
969
+ if (x !== void 0) {
970
+ const y = await this.tryResolveLODCacheEntry(x, f, c, t, e, n);
971
+ if (r !== this.cacheGeneration) return null;
972
+ if (y.found) return y.value;
973
+ }
974
+ if (!D.use)
975
+ return g && console.log(`LOD ${e} was aborted: ${c}`), null;
976
+ const L = a, M = new Promise(async (y, O) => {
977
+ if (Ct) {
978
+ const m = await (await Rt({})).load(c);
979
+ if (m.textures.length > 0)
980
+ for (const v of m.textures) {
981
+ let S = v.texture;
982
+ return p.assignLODInformation(o.url, S, l, e, void 0), t instanceof W && (S = this.copySettings(t, S)), S && (S.guid = L.guid), y(S);
811
983
  }
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);
984
+ if (m.geometries.length > 0) {
985
+ const v = new Array();
986
+ for (const S of m.geometries) {
987
+ const j = S.geometry;
988
+ p.assignLODInformation(o.url, j, l, e, S.primitiveIndex), v.push(j);
817
989
  }
818
- return d(h);
990
+ return y(v);
819
991
  }
820
- return d(null);
992
+ return y(null);
821
993
  }
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);
994
+ const I = new Me();
995
+ qe(I), g && (await new Promise((b) => setTimeout(b, 1e3)), n && console.warn("Start loading (delayed) " + c, L.guid));
996
+ let P = c;
997
+ if (L && Array.isArray(L.lods)) {
998
+ const b = L.lods[e];
999
+ b.hash && (P += "?v=" + b.hash);
828
1000
  }
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;
1001
+ const _ = await I.loadAsync(P).catch((b) => (console.error(`Error loading LOD ${e} from ${c}
1002
+ `, b), y(null)));
1003
+ if (!_)
1004
+ return y(null);
1005
+ const E = _.parser;
1006
+ n && console.log("Loading finished " + c, L.guid);
1007
+ let C = 0;
1008
+ if (_.parser.json.textures) {
1009
+ let b = !1;
1010
+ for (const m of _.parser.json.textures) {
1011
+ if (m?.extensions) {
1012
+ const v = m?.extensions[z];
1013
+ if (v?.guid && v.guid === L.guid) {
1014
+ b = !0;
843
1015
  break;
844
1016
  }
845
1017
  }
846
- T++;
1018
+ C++;
847
1019
  }
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);
1020
+ if (b) {
1021
+ let m = await E.getDependency("texture", C);
1022
+ return m && p.assignLODInformation(o.url, m, l, e, void 0), n && console.log('change "' + t.name + '" → "' + m.name + '"', c, C, m, f), t instanceof W && (m = this.copySettings(t, m)), m && (m.guid = L.guid), y(m);
1023
+ } else g && console.warn("Could not find texture with guid", L.guid, _.parser.json);
852
1024
  }
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;
1025
+ if (C = 0, _.parser.json.meshes) {
1026
+ let b = !1;
1027
+ for (const m of _.parser.json.meshes) {
1028
+ if (m?.extensions) {
1029
+ const v = m?.extensions[z];
1030
+ if (v?.guid && v.guid === L.guid) {
1031
+ b = !0;
860
1032
  break;
861
1033
  }
862
1034
  }
863
- T++;
1035
+ C++;
864
1036
  }
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);
1037
+ if (b) {
1038
+ const m = await E.getDependency("mesh", C);
1039
+ if (n && console.log(`Loaded Mesh "${m.name}"`, c, C, m, f), m.isMesh === !0) {
1040
+ const v = m.geometry;
1041
+ return p.assignLODInformation(o.url, v, l, e, 0), y(v);
870
1042
  } 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);
1043
+ const v = new Array();
1044
+ for (let S = 0; S < m.children.length; S++) {
1045
+ const j = m.children[S];
1046
+ if (j.isMesh === !0) {
1047
+ const J = j.geometry;
1048
+ p.assignLODInformation(o.url, J, l, e, S), v.push(J);
877
1049
  }
878
1050
  }
879
- return d(h);
1051
+ return y(v);
880
1052
  }
881
- } else f && console.warn("Could not find mesh with guid", _.guid, D.parser.json);
1053
+ } else g && console.warn("Could not find mesh with guid", L.guid, _.parser.json);
882
1054
  }
883
- return d(null);
1055
+ return y(null);
884
1056
  });
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;
1057
+ this.cache.set(f, M), D.use(M);
1058
+ const T = await M;
1059
+ return r !== this.cacheGeneration || this.cache.get(f) !== M ? null : (T != null ? T instanceof W ? (this.cache.set(f, new WeakRef(T)), p._resourceRegistry.register(T, f)) : Array.isArray(T) ? this.cache.set(f, Promise.resolve(T)) : this.cache.set(f, Promise.resolve(T)) : this.cache.set(f, Promise.resolve(null)), T);
1060
+ } else if (t instanceof W) {
1061
+ if (s?.isCurrent?.() === !1)
1062
+ return n && console.log(`Skipping stale texture LOD ${e} request: ${c}`), null;
1063
+ n && console.log("Load texture from uri: " + c);
1064
+ const h = await new it().loadAsync(c);
1065
+ return r !== this.cacheGeneration || s?.isCurrent?.() === !1 ? (h?.dispose(), null) : (h ? (h.guid = a.guid, h.flipY = !1, h.needsUpdate = !0, h.colorSpace = t.colorSpace, n && console.log(a, h)) : g && console.warn("failed loading", c), h);
892
1066
  }
893
1067
  }
894
1068
  return null;
895
1069
  }
896
- static queue = new gt(ve() ? 20 : 50, { debug: f != !1 });
897
- static assignLODInformation(t, e, s, r, o) {
1070
+ static async tryResolveLODCacheEntry(t, e, s, r, n, o) {
1071
+ if (t === void 0)
1072
+ return { found: !1 };
1073
+ if (o && console.log(`LOD ${n} was already loading/loaded: ${e}`), t instanceof WeakRef) {
1074
+ const u = t.deref();
1075
+ if (u) {
1076
+ let d = u, c = !1;
1077
+ if (d instanceof W && r instanceof W ? Oe(d.image) || Le(d) ? d = this.copySettings(r, d) : c = !0 : d instanceof H && r instanceof H && (d.attributes.position?.array || (c = !0)), !c)
1078
+ return { found: !0, value: d };
1079
+ }
1080
+ return this.cache.delete(e), g && console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${e}`), { found: !1 };
1081
+ }
1082
+ let l = await t.catch((u) => (console.error(`Error loading LOD ${n} from ${s}
1083
+ `, u), null));
1084
+ if (this.disposedRequests.has(t)) return { found: !0, value: null };
1085
+ let a = !1;
1086
+ return l == null || (l instanceof W && r instanceof W ? Oe(l.image) || Le(l) ? l = this.copySettings(r, l) : (a = !0, this.cache.delete(e)) : l instanceof H && r instanceof H && (l.attributes.position?.array || (a = !0, this.cache.delete(e)))), a ? { found: !1 } : { found: !0, value: l };
1087
+ }
1088
+ static _queue;
1089
+ static get queue() {
1090
+ return this._queue ??= new Lt(be() ? 20 : 50, { debug: g != !1 });
1091
+ }
1092
+ static assignLODInformation(t, e, s, r, n) {
898
1093
  if (!e) return;
899
1094
  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);
1095
+ const o = new kt(t, s, r, n);
1096
+ e.userData.LODS = o, "source" in e && typeof e.source == "object" && (e.source.LODS = o);
902
1097
  }
903
1098
  static getAssignedLODInformation(t) {
904
1099
  return t ? t.userData?.LODS ? t.userData.LODS : "source" in t && t.source?.LODS ? t.source.LODS : null : null;
905
1100
  }
906
1101
  // private static readonly _copiedTextures: WeakMap<Texture, Texture> = new Map();
907
1102
  static copySettings(t, e) {
908
- return e ? (f === "verbose" && console.debug(`Copy texture settings
1103
+ return e ? (g === "verbose" && console.debug(`Copy texture settings
909
1104
  `, t.uuid, `
910
1105
  `, 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
1106
  }
912
1107
  }
913
- class Ot {
1108
+ class kt {
914
1109
  url;
915
1110
  /** the key to lookup the LOD information */
916
1111
  key;
@@ -921,10 +1116,10 @@ class Ot {
921
1116
  this.url = t, this.key = e, this.level = s, r != null && (this.index = r);
922
1117
  }
923
1118
  }
924
- class ce {
1119
+ class le {
925
1120
  static addPromise = (t, e, s, r) => {
926
- r.forEach((o) => {
927
- o.add(t, e, s);
1121
+ r.forEach((n) => {
1122
+ n.add(t, e, s);
928
1123
  });
929
1124
  };
930
1125
  ready;
@@ -955,8 +1150,8 @@ class ce {
955
1150
  _maxPromisesPerObject = 1;
956
1151
  constructor(t, e) {
957
1152
  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;
1153
+ this._frame_start = e.waitForFirstCapture ? void 0 : t, this._frames_to_capture = r, this.ready = new Promise((n) => {
1154
+ this._resolve = n;
960
1155
  }), this.ready.finally(() => {
961
1156
  this._resolved = !0, this._awaiting.length = 0;
962
1157
  }), this._signal = e.signal, this._signal?.addEventListener("abort", () => {
@@ -970,15 +1165,15 @@ class ce {
970
1165
  _seen = /* @__PURE__ */ new WeakMap();
971
1166
  add(t, e, s) {
972
1167
  if (this._resolved) {
973
- f && console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");
1168
+ g && console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");
974
1169
  return;
975
1170
  }
976
1171
  if (!(this._frame_start !== void 0 && this._currentFrame > this._frame_start + this._frames_to_capture)) {
977
1172
  if (this._maxPromisesPerObject >= 1)
978
1173
  if (this._seen.has(e)) {
979
- let r = this._seen.get(e);
1174
+ const r = this._seen.get(e);
980
1175
  if (r >= this._maxPromisesPerObject) {
981
- f && console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");
1176
+ g && console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");
982
1177
  return;
983
1178
  }
984
1179
  this._seen.set(e, r + 1);
@@ -997,22 +1192,122 @@ class ce {
997
1192
  });
998
1193
  }
999
1194
  }
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 {
1195
+ const $ = X("debugprogressive"), At = $ === "colors", It = X("noprogressive"), ge = /* @__PURE__ */ Symbol("Needle:LODSManager"), pe = /* @__PURE__ */ Symbol("Needle:LODState"), N = /* @__PURE__ */ Symbol("Needle:CurrentLOD"), Se = Xe, R = { mesh_lod: -1, texture_lod: -1 }, $t = new $e(), Re = [
1196
+ 3526751,
1197
+ 11065402,
1198
+ 15978811,
1199
+ 15897394,
1200
+ 15749691,
1201
+ 11032304,
1202
+ 4827122,
1203
+ 3332036,
1204
+ 16739229,
1205
+ 7306743,
1206
+ 14053330,
1207
+ 3516499,
1208
+ 12035359,
1209
+ 14703919,
1210
+ 3963096,
1211
+ 42662,
1212
+ 14100029,
1213
+ 8344319,
1214
+ 4633680,
1215
+ 16229681,
1216
+ 3120096,
1217
+ 12076434,
1218
+ 9083434,
1219
+ 2060171,
1220
+ 15751837,
1221
+ 10182117,
1222
+ 48121,
1223
+ 62932,
1224
+ 16704576,
1225
+ 15817653,
1226
+ 5083278,
1227
+ 5592405
1228
+ ];
1229
+ function Gt() {
1230
+ const i = Se.Timer || Se.Clock;
1231
+ return new i();
1232
+ }
1233
+ const me = new fe(), V = new fe(), Pe = new fe(), Bt = new A(), qt = new A(), Wt = new Ge(), F = new A(), Z = new A(), ee = new A(), te = new A();
1234
+ function Ft(i, t) {
1235
+ const e = i.min, s = i.max, r = (e.x + s.x) * 0.5, n = (e.y + s.y) * 0.5;
1236
+ return F.set(r, n, e.z).applyMatrix4(t).z < 0;
1237
+ }
1238
+ function Ut(i) {
1239
+ const {
1240
+ geometry: t,
1241
+ matrixWorld: e,
1242
+ camera: s,
1243
+ projectionScreenMatrix: r,
1244
+ desiredDensity: n,
1245
+ canvasHeight: o = 0,
1246
+ currentLevel: l = -1,
1247
+ xrEnabled: a = !1,
1248
+ debugDrawLine: u,
1249
+ warnMissingPrimitiveDensities: d = !1
1250
+ } = i, c = p.getMeshLODExtension(t)?.lods, f = p.getPrimitiveIndex(t), h = i.target ?? {
1251
+ level: l,
1252
+ primitiveIndex: f,
1253
+ screenCoverage: 0,
1254
+ screenspaceVolume: new A(),
1255
+ centrality: 1
1256
+ };
1257
+ h.level = l, h.primitiveIndex = f, h.screenCoverage = 0, h.screenspaceVolume.set(0, 0, 0), h.centrality = 1;
1258
+ let D = i.boundingBox ?? t.boundingBox;
1259
+ if (D || (t.computeBoundingBox(), D = t.boundingBox), !D) return h;
1260
+ if (me.copy(D).applyMatrix4(e), s.isPerspectiveCamera && Ft(me, r))
1261
+ return h.level = 0, h.screenCoverage = 1 / 0, h.screenspaceVolume.set(1 / 0, 1 / 0, 1 / 0), h;
1262
+ if (V.copy(me).applyMatrix4(r), a && s.isPerspectiveCamera && s.fov > 70) {
1263
+ const y = V.min, O = V.max;
1264
+ let I = y.x, P = y.y, _ = O.x, E = O.y;
1265
+ const C = 2, b = 1.5, m = (y.x + O.x) * 0.5, v = (y.y + O.y) * 0.5;
1266
+ I = (I - m) * C + m, P = (P - v) * C + v, _ = (_ - m) * C + m, E = (E - v) * C + v;
1267
+ const S = I < 0 && _ > 0 ? 0 : Math.min(Math.abs(y.x), Math.abs(O.x)), j = P < 0 && E > 0 ? 0 : Math.min(Math.abs(y.y), Math.abs(O.y)), J = Math.max(S, j);
1268
+ h.centrality = (b - J) * (b - J) * (b - J);
1269
+ }
1270
+ const w = V.getSize(Bt);
1271
+ w.multiplyScalar(0.5), globalThis.screen?.availHeight > 0 && o > 0 && w.multiplyScalar(o / globalThis.screen.availHeight), s.isPerspectiveCamera && (w.x *= s.aspect), Pe.copy(D).applyMatrix4(e).applyMatrix4(s.matrixWorldInverse);
1272
+ const x = Pe.getSize(qt), L = Math.max(w.x, w.y), M = Math.max(x.x, x.y);
1273
+ L !== 0 && M !== 0 && (w.z = x.z / M * L);
1274
+ const T = Math.max(w.x, w.y, w.z) * h.centrality;
1275
+ if (h.screenCoverage = T, h.screenspaceVolume.copy(w), T <= 0) return h;
1276
+ if (u) {
1277
+ const y = Wt.copy(r);
1278
+ y.invert(), F.copy(V.min), Z.copy(V.max), Z.x = F.x, ee.copy(V.max), ee.y = F.y, te.copy(V.max);
1279
+ const O = (F.z + te.z) * 0.5;
1280
+ F.z = Z.z = ee.z = te.z = O, F.applyMatrix4(y), Z.applyMatrix4(y), ee.applyMatrix4(y), te.applyMatrix4(y), u(F, Z, 255), u(F, ee, 255), u(Z, te, 255), u(ee, te, 255);
1281
+ }
1282
+ if (c?.length)
1283
+ for (let y = 0; y < c.length; y++) {
1284
+ const O = c[y], I = O.densities?.[f] || O.density || 1e-5;
1285
+ if (f > 0 && d && Ue() && !O.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.")), I / T < n) {
1286
+ h.level = y;
1287
+ break;
1288
+ }
1289
+ }
1290
+ return h;
1291
+ }
1292
+ class G {
1002
1293
  /**
1003
1294
  * 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
1295
  */
1005
1296
  static debugDrawLine;
1006
1297
  /** @internal */
1007
1298
  static getObjectLODState(t) {
1008
- return t[fe];
1299
+ return t[pe];
1009
1300
  }
1010
1301
  static addPlugin(t) {
1011
- z.push(t);
1302
+ U.push(t);
1012
1303
  }
1013
1304
  static removePlugin(t) {
1014
- const e = z.indexOf(t);
1015
- e >= 0 && z.splice(e, 1);
1305
+ const e = U.indexOf(t);
1306
+ e >= 0 && U.splice(e, 1);
1307
+ }
1308
+ /** Read-only snapshot of the currently registered plugins, for inspection. Use {@link addPlugin} / {@link removePlugin} to modify the registry. */
1309
+ static getPlugins() {
1310
+ return U;
1016
1311
  }
1017
1312
  /**
1018
1313
  * Gets the LODsManager for the given renderer. If the LODsManager does not exist yet, it will be created.
@@ -1020,20 +1315,20 @@ class v {
1020
1315
  * @returns The LODsManager instance.
1021
1316
  */
1022
1317
  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, {
1318
+ if (t[ge])
1319
+ return console.debug("[gltf-progressive] LODsManager already exists for this renderer"), t[ge];
1320
+ const s = new G(t, {
1026
1321
  engine: "unknown",
1027
1322
  ...e
1028
1323
  });
1029
- return t[de] = s, s;
1324
+ return t[ge] = s, s;
1030
1325
  }
1031
1326
  renderer;
1032
1327
  context;
1033
- projectionScreenMatrix = new Me();
1328
+ projectionScreenMatrix = new Ge();
1034
1329
  /** @deprecated use static `LODsManager.addPlugin()` method. This getter will be removed in later versions */
1035
1330
  get plugins() {
1036
- return z;
1331
+ return U;
1037
1332
  }
1038
1333
  /**
1039
1334
  * Force override the LOD level for all objects (meshes + textures) rendered in the scene
@@ -1071,45 +1366,99 @@ class v {
1071
1366
  _newPromiseGroups = [];
1072
1367
  _promiseGroupIds = 0;
1073
1368
  /**
1074
- * Call to await LODs loading during the next render cycle.
1369
+ * Returns a promise that resolves once all LOD requests initiated during the next render cycles have finished loading.
1370
+ * This is useful for hiding low-resolution placeholders (e.g. with a loading overlay or CSS blur) until high-quality assets are ready.
1371
+ *
1372
+ * By default, the returned promise captures LOD loading requests for 2 frames and resolves when all of them complete.
1373
+ * Use `waitForFirstCapture` if no LOD requests may happen immediately (e.g. after a scene switch).
1374
+ *
1375
+ * @param opts - Optional configuration for how long to capture and what to wait for. See {@link PromiseGroupOptions}.
1376
+ * @returns A promise that resolves with `{ cancelled, awaited_count, resolved_count }` once all captured LOD loads complete (or the signal aborts).
1377
+ *
1378
+ * @example
1379
+ * ```ts
1380
+ * // Wait for initial LODs to finish loading, then remove a blur overlay
1381
+ * const result = await lodsManager.awaitLoading({
1382
+ * frames: 5,
1383
+ * signal: AbortSignal.timeout(10_000),
1384
+ * });
1385
+ * console.log(`Loaded ${result.resolved_count} of ${result.awaited_count} LODs`);
1386
+ * document.querySelector('.blur-overlay')?.remove();
1387
+ * ```
1388
+ *
1389
+ * @example
1390
+ * ```ts
1391
+ * // Wait until at least one LOD starts loading before resolving
1392
+ * await lodsManager.awaitLoading({ waitForFirstCapture: true });
1393
+ * ```
1075
1394
  */
1076
1395
  awaitLoading(t) {
1077
- const e = this._promiseGroupIds++, s = new ce(this.#r, { ...t });
1396
+ const e = this._promiseGroupIds++, s = new le(this.#r, { ...t });
1078
1397
  this._newPromiseGroups.push(s);
1079
1398
  const r = performance.now();
1080
1399
  return s.ready.finally(() => {
1081
- const o = this._newPromiseGroups.indexOf(s);
1082
- o >= 0 && (this._newPromiseGroups.splice(o, 1), Oe() && performance.measure("LODsManager:awaitLoading", {
1400
+ const n = this._newPromiseGroups.indexOf(s);
1401
+ n >= 0 && (this._newPromiseGroups.splice(n, 1), Ue() && performance.measure("LODsManager:awaitLoading", {
1083
1402
  start: r,
1084
1403
  detail: { id: e, name: t?.name, awaited: s.awaitedCount, resolved: s.resolvedCount }
1085
1404
  }));
1086
1405
  }), s.ready;
1087
1406
  }
1407
+ /** Track LOD work started outside this manager so {@link awaitLoading} waits for it too. */
1408
+ trackLoadingPromise(t, e, s) {
1409
+ return le.addPromise(t, e, s, this._newPromiseGroups), s;
1410
+ }
1088
1411
  _postprocessPromiseGroups() {
1089
1412
  if (this._newPromiseGroups.length !== 0)
1090
1413
  for (let t = this._newPromiseGroups.length - 1; t >= 0; t--)
1091
1414
  this._newPromiseGroups[t].update(this.#r);
1092
1415
  }
1093
1416
  _lodchangedlisteners = [];
1417
+ /**
1418
+ * Register a listener that is called whenever a mesh or texture LOD level has finished loading and has been applied.
1419
+ * The listener receives the type of asset (`"mesh"` or `"texture"`), the new LOD level, and the affected object.
1420
+ *
1421
+ * @param evt - The event type. Currently only `"changed"` is supported.
1422
+ * @param listener - Callback invoked after a LOD swap completes.
1423
+ * @return A function to unregister the listener.
1424
+ *
1425
+ * @example
1426
+ * ```ts
1427
+ * lodsManager.addEventListener("changed", ({ type, level, object }) => {
1428
+ * console.log(`${type} LOD changed to level ${level}`, object);
1429
+ * });
1430
+ * ```
1431
+ */
1094
1432
  addEventListener(t, e) {
1095
- t === "changed" && this._lodchangedlisteners.push(e);
1433
+ return t === "changed" ? (this._lodchangedlisteners.push(e), () => {
1434
+ this.removeEventListener(t, e);
1435
+ }) : () => {
1436
+ };
1096
1437
  }
1438
+ /**
1439
+ * Remove a previously registered `"changed"` event listener.
1440
+ * @param evt - The event type (`"changed"`).
1441
+ * @param listener - The listener to remove.
1442
+ * @return `true` if the listener was found and removed, `false` otherwise.
1443
+ */
1097
1444
  removeEventListener(t, e) {
1445
+ let s = !1;
1098
1446
  if (t === "changed") {
1099
- const s = this._lodchangedlisteners.indexOf(e);
1100
- s >= 0 && this._lodchangedlisteners.splice(s, 1);
1447
+ const r = this._lodchangedlisteners.indexOf(e);
1448
+ r >= 0 && (this._lodchangedlisteners.splice(r, 1), s = !0);
1101
1449
  }
1450
+ return s;
1102
1451
  }
1103
1452
  // readonly plugins: NEEDLE_progressive_plugin[] = [];
1104
1453
  constructor(t, e) {
1105
1454
  this.renderer = t, this.context = { ...e };
1106
1455
  }
1107
1456
  #t;
1108
- #n = new Ze();
1109
1457
  #r = 0;
1458
+ #n = 0;
1110
1459
  #o = 0;
1111
- #i = 0;
1112
1460
  #s = 0;
1461
+ #i = Gt();
1113
1462
  _fpsBuffer = [60, 60, 60, 60, 60];
1114
1463
  /**
1115
1464
  * Enable the LODsManager. This will replace the render method of the renderer with a method that updates the LODs.
@@ -1120,29 +1469,46 @@ class v {
1120
1469
  let t = 0;
1121
1470
  this.#t = this.renderer.render;
1122
1471
  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);
1472
+ _e(this.renderer), this.renderer.render = function(s, r) {
1473
+ const n = e.renderer.getRenderTarget();
1474
+ (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((l, a) => l + a) / e._fpsBuffer.length, $ && e.#r % 200 === 0 && console.log("FPS", Math.round(e.#s), "Interval:", e.#e));
1475
+ const o = t++;
1476
+ e.#t.call(this, s, r), e.onAfterRender(s, r, o);
1128
1477
  };
1129
1478
  }
1130
1479
  disable() {
1131
1480
  this.#t && (console.debug("[gltf-progressive] Disabling LODsManager for renderer"), this.renderer.render = this.#t, this.#t = void 0);
1132
1481
  }
1482
+ /**
1483
+ * Manually trigger a LOD update for a scene and camera.
1484
+ * Only needed when {@link manual} is set to `true` — otherwise LOD updates happen automatically on each render call.
1485
+ *
1486
+ * @param scene - The scene containing objects with progressive LODs.
1487
+ * @param camera - The camera used to determine screen coverage and LOD levels.
1488
+ *
1489
+ * @example
1490
+ * ```ts
1491
+ * const lodsManager = LODsManager.get(renderer);
1492
+ * lodsManager.manual = true;
1493
+ * // ... later, trigger an update at a specific point:
1494
+ * lodsManager.update(scene, camera);
1495
+ * ```
1496
+ */
1133
1497
  update(t, e) {
1134
1498
  this.internalUpdate(t, e);
1135
1499
  }
1136
1500
  onAfterRender(t, e, s) {
1137
1501
  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);
1502
+ const r = this.getRenderList(t, e, s);
1503
+ if (!r) return;
1504
+ const n = r.opaque;
1505
+ let o = !0;
1506
+ if (n.length === 1) {
1507
+ const l = n[0].material;
1508
+ (l.name === "EffectMaterial" || l.name === "CopyShader") && (o = !1);
1143
1509
  }
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))
1510
+ if ((e.parent && e.parent.type === "CubeCamera" || s >= 1 && e.type === "OrthographicCamera") && (o = !1), o) {
1511
+ if (It || (this.updateInterval === "auto" ? this.#s < 40 && this.#e < 10 ? (this.#e += 1, $ && console.warn("↓ Reducing LOD updates", this.#e, this.#s.toFixed(0))) : this.#s >= 60 && this.#e > 1 && (this.#e -= 1, $ && console.warn("↑ Increasing LOD updates", this.#e, this.#s.toFixed(0))) : this.#e = this.updateInterval, this.#e > 0 && this.#r % this.#e != 0))
1146
1512
  return;
1147
1513
  this.internalUpdate(t, e), this._postprocessPromiseGroups();
1148
1514
  }
@@ -1151,12 +1517,14 @@ class v {
1151
1517
  * Update LODs in a scene
1152
1518
  */
1153
1519
  internalUpdate(t, e) {
1154
- const s = this.renderer.renderLists.get(t, 0), r = s.opaque;
1520
+ const s = this.getRenderList(t, e, 0);
1521
+ if (!s) return;
1522
+ const r = s.opaque;
1155
1523
  this.projectionScreenMatrix.multiplyMatrices(e.projectionMatrix, e.matrixWorldInverse);
1156
- const o = this.targetTriangleDensity;
1524
+ const n = this.targetTriangleDensity;
1157
1525
  for (const a of r) {
1158
1526
  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)));
1527
+ $ && (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)));
1160
1528
  continue;
1161
1529
  }
1162
1530
  switch (a.material.type) {
@@ -1168,38 +1536,52 @@ class v {
1168
1536
  case "MeshDepthMaterial":
1169
1537
  continue;
1170
1538
  }
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
1539
  const u = a.object;
1177
- (u instanceof j || u.isMesh) && this.updateLODs(t, e, u, o);
1540
+ (u instanceof Y || u.isMesh) && this.updateLODs(t, e, u, n);
1178
1541
  }
1179
- const n = s.transparent;
1180
- for (const a of n) {
1542
+ const o = s.transparent;
1543
+ for (const a of o) {
1181
1544
  const u = a.object;
1182
- (u instanceof j || u.isMesh) && this.updateLODs(t, e, u, o);
1545
+ (u instanceof Y || u.isMesh) && this.updateLODs(t, e, u, n);
1183
1546
  }
1184
1547
  const l = s.transmissive;
1185
1548
  for (const a of l) {
1186
1549
  const u = a.object;
1187
- (u instanceof j || u.isMesh) && this.updateLODs(t, e, u, o);
1550
+ (u instanceof Y || u.isMesh) && this.updateLODs(t, e, u, n);
1188
1551
  }
1189
1552
  }
1553
+ getRenderList(t, e, s) {
1554
+ const r = this.renderer;
1555
+ let n = null;
1556
+ if (r.isWebGPURenderer === !0) {
1557
+ const o = r._renderLists;
1558
+ if (!o) return null;
1559
+ n = o.get(t, e);
1560
+ } else if (r.isWebGLRenderer === !0) {
1561
+ const o = r.renderLists;
1562
+ if (!o) return null;
1563
+ n = o.get(t, s);
1564
+ }
1565
+ return n ? {
1566
+ opaque: n.opaque || [],
1567
+ transparent: n.transparent || [],
1568
+ transmissive: n.transmissive || n.transparentDoublePass || [],
1569
+ transparentDoublePass: n.transparentDoublePass || []
1570
+ } : null;
1571
+ }
1190
1572
  /** Update the LOD levels for the renderer. */
1191
1573
  updateLODs(t, e, s, r) {
1192
1574
  s.userData || (s.userData = {});
1193
- let o = s[fe];
1194
- if (o || (o = new Tt(), s[fe] = o), o.frames++ < 2)
1575
+ let n = s[pe];
1576
+ if (n || (n = new zt(), s[pe] = n), n.frames++ < 2)
1195
1577
  return;
1196
- for (const l of z)
1578
+ for (const l of U)
1197
1579
  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;
1580
+ const o = this.overrideLodLevel !== void 0 ? this.overrideLodLevel : K;
1581
+ 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), g && s.material && !s.isGizmo && ze(s.material), At && s.material && !s.isGizmo && !s.isBatchedMesh && Ee(s.material, R.mesh_lod);
1582
+ for (const l of U)
1583
+ l.onAfterUpdatedLOD?.(this.renderer, t, e, s, R);
1584
+ n.lastLodLevel_Mesh = R.mesh_lod, n.lastLodLevel_Texture = R.texture_lod;
1203
1585
  }
1204
1586
  /** Load progressive textures for the given material
1205
1587
  * @param material the material to load the textures for
@@ -1210,16 +1592,18 @@ class v {
1210
1592
  if (!t) return;
1211
1593
  if (Array.isArray(t)) {
1212
1594
  for (const o of t)
1213
- this.loadProgressiveTextures(o, e);
1595
+ this.loadProgressiveTextures(o, e, s);
1214
1596
  return;
1215
1597
  }
1216
1598
  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 }));
1599
+ (t[N] === void 0 || e < t[N]) && (r = !0);
1600
+ const n = s !== void 0 && s >= 0;
1601
+ if (n && (r = t[N] != s, e = s), r) {
1602
+ t[N] = e;
1603
+ const o = n ? { force: !0 } : void 0, l = p.assignTextureLOD(t, e, o).then((a) => {
1604
+ this._lodchangedlisteners.forEach((u) => u({ type: "texture", level: e, object: t }));
1221
1605
  });
1222
- ce.addPromise("texture", t, o, this._newPromiseGroups);
1606
+ le.addPromise("texture", t, l, this._newPromiseGroups);
1223
1607
  }
1224
1608
  }
1225
1609
  /** Load progressive meshes for the given mesh
@@ -1230,143 +1614,110 @@ class v {
1230
1614
  */
1231
1615
  loadProgressiveMeshes(t, e) {
1232
1616
  if (!t) return Promise.resolve(null);
1233
- let s = t[U] !== e;
1617
+ let s = t[N] !== e;
1234
1618
  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;
1619
+ if (r != null && (s = t[N] != r, e = r), s) {
1620
+ t[N] = e;
1621
+ const n = t.geometry, o = p.assignMeshLOD(t, e).then((l) => (l && t[N] == e && n != t.geometry && this._lodchangedlisteners.forEach((a) => a({ type: "mesh", level: e, object: t })), l));
1622
+ return le.addPromise("mesh", t, o, this._newPromiseGroups), o;
1239
1623
  }
1240
1624
  return Promise.resolve(null);
1241
1625
  }
1242
1626
  // private testIfLODLevelsAreAvailable() {
1243
- _sphere = new Ce();
1244
- _tempBox = new ge();
1245
- _tempBox2 = new ge();
1246
- tempMatrix = new Me();
1627
+ _sphere = new Ie();
1247
1628
  _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
- }
1259
1629
  static skinnedMeshBoundsFrameOffsetCounter = 0;
1260
1630
  static $skinnedMeshBoundsOffset = /* @__PURE__ */ Symbol("gltf-progressive-skinnedMeshBoundsOffset");
1261
1631
  // #region calculateLodLevel
1262
- calculateLodLevel(t, e, s, r, o) {
1632
+ calculateLodLevel(t, e, s, r, n) {
1263
1633
  if (!e) {
1264
- o.mesh_lod = -1, o.texture_lod = -1;
1634
+ n.mesh_lod = -1, n.texture_lod = -1;
1265
1635
  return;
1266
1636
  }
1267
1637
  if (!t) {
1268
- o.mesh_lod = -1, o.texture_lod = -1;
1638
+ n.mesh_lod = -1, n.texture_lod = -1;
1269
1639
  return;
1270
1640
  }
1271
1641
  let l = 10 + 1, a = !1;
1272
- if (k && e["DEBUG:LOD"] != null)
1642
+ if ($ && e["DEBUG:LOD"] != null)
1273
1643
  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;
1644
+ const u = p.getMeshLODExtension(e.geometry)?.lods, d = p.getPrimitiveIndex(e.geometry), c = u && u.length > 0, f = p.getMaterialMinMaxLODsCount(e.material), h = f.min_count !== 1 / 0 && f.min_count >= 0 && f.max_count >= 0;
1645
+ if (!c && !h) {
1646
+ n.mesh_lod = 0, n.texture_lod = 0;
1277
1647
  return;
1278
1648
  }
1279
- y || (a = !0, l = 0);
1280
- const R = this.renderer.domElement.clientHeight || this.renderer.domElement.height;
1281
- let M = e.geometry.boundingBox;
1649
+ c || (a = !0, l = 0);
1650
+ const D = this.renderer.domElement.clientHeight || this.renderer.domElement.height;
1651
+ let w = e.geometry.boundingBox;
1282
1652
  if (e.type === "SkinnedMesh") {
1283
- const d = e;
1284
- if (!d.boundingBox)
1285
- d.computeBoundingBox();
1653
+ const x = e;
1654
+ if (!x.boundingBox)
1655
+ x.computeBoundingBox();
1286
1656
  else if (this.skinnedMeshAutoUpdateBoundsInterval > 0) {
1287
- if (!d[v.$skinnedMeshBoundsOffset]) {
1288
- const b = v.skinnedMeshBoundsFrameOffsetCounter++;
1289
- d[v.$skinnedMeshBoundsOffset] = b;
1657
+ if (!x[G.$skinnedMeshBoundsOffset]) {
1658
+ const M = G.skinnedMeshBoundsFrameOffsetCounter++;
1659
+ x[G.$skinnedMeshBoundsOffset] = M;
1290
1660
  }
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;
1661
+ const L = x[G.$skinnedMeshBoundsOffset];
1662
+ if ((s.frames + L) % this.skinnedMeshAutoUpdateBoundsInterval === 0) {
1663
+ const M = ae(x), T = x.geometry;
1664
+ M && (x.geometry = M), x.computeBoundingBox(), x.geometry = T;
1295
1665
  }
1296
1666
  }
1297
- M = d.boundingBox;
1667
+ w = x.boundingBox;
1298
1668
  }
1299
- if (M) {
1300
- const d = t;
1669
+ if (w) {
1301
1670
  if (e.geometry.attributes.color && e.geometry.attributes.color.count < 100 && e.geometry.boundingSphere) {
1302
1671
  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;
1672
+ const M = t.getWorldPosition(this._tempWorldPosition);
1673
+ if (this._sphere.containsPoint(M)) {
1674
+ n.mesh_lod = 0, n.texture_lod = 0;
1306
1675
  return;
1307
1676
  }
1308
1677
  }
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;
1678
+ const x = Ut({
1679
+ geometry: e.geometry,
1680
+ matrixWorld: e.matrixWorld,
1681
+ camera: t,
1682
+ projectionScreenMatrix: this.projectionScreenMatrix,
1683
+ desiredDensity: r,
1684
+ canvasHeight: D,
1685
+ currentLevel: s.lastLodLevel_Mesh,
1686
+ boundingBox: w,
1687
+ xrEnabled: this.renderer.xr.enabled,
1688
+ debugDrawLine: $ ? G.debugDrawLine : void 0,
1689
+ warnMissingPrimitiveDensities: !0
1690
+ });
1691
+ if (s.lastCentrality = x.centrality, s.lastScreenCoverage = x.screenCoverage, s.lastScreenspaceVolume.copy(x.screenspaceVolume), x.screenCoverage === 1 / 0) {
1692
+ n.mesh_lod = 0, n.texture_lod = 0;
1311
1693
  return;
1312
1694
  }
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);
1695
+ x.level >= 0 && x.level < l && (l = x.level, a = !0);
1345
1696
  }
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}`);
1697
+ if (a ? n.mesh_lod = l : n.mesh_lod = s.lastLodLevel_Mesh, $ && n.mesh_lod != s.lastLodLevel_Mesh) {
1698
+ const L = u?.[n.mesh_lod];
1699
+ L && console.log(`Mesh LOD changed: ${s.lastLodLevel_Mesh} → ${n.mesh_lod} (density: ${L.densities?.[d].toFixed(0)}) | ${e.name}`);
1349
1700
  }
1350
- if (_) {
1351
- const d = "saveData" in globalThis.navigator && globalThis.navigator.saveData === !0;
1701
+ if (h) {
1702
+ const x = "saveData" in globalThis.navigator && globalThis.navigator.saveData === !0;
1352
1703
  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}`);
1704
+ if (n.texture_lod = f.max_count - 1, $) {
1705
+ const L = f.lods[f.max_count - 1];
1706
+ $ && console.log(`First Texture LOD ${n.texture_lod} (${L.max_height}px) - ${e.name}`);
1356
1707
  }
1357
1708
  } 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)}
1709
+ const L = s.lastScreenspaceVolume.x + s.lastScreenspaceVolume.y + s.lastScreenspaceVolume.z;
1710
+ let M = s.lastScreenCoverage * 4;
1711
+ this.context?.engine === "model-viewer" && (M *= 1.5);
1712
+ const T = this.renderer.getPixelRatio?.() || globalThis.devicePixelRatio || 1, O = D / T * M;
1713
+ let I = !1;
1714
+ for (let P = f.lods.length - 1; P >= 0; P--) {
1715
+ const _ = f.lods[P];
1716
+ if (!(x && _.max_height >= 2048) && !(be() && _.max_height > 4096) && (_.max_height > O || !I && P === 0)) {
1717
+ if (I = !0, n.texture_lod = P, $ && n.texture_lod < s.lastLodLevel_Texture) {
1718
+ const E = _.max_height;
1719
+ console.log(`Texture LOD changed: ${s.lastLodLevel_Texture} → ${n.texture_lod} = ${E}px
1720
+ Screensize: ${O.toFixed(0)}px, Coverage: ${(100 * s.lastScreenCoverage).toFixed(2)}%, Volume ${L.toFixed(1)}
1370
1721
  ${e.name}`);
1371
1722
  }
1372
1723
  break;
@@ -1374,10 +1725,10 @@ ${e.name}`);
1374
1725
  }
1375
1726
  }
1376
1727
  } else
1377
- o.texture_lod = 0;
1728
+ n.texture_lod = 0;
1378
1729
  }
1379
1730
  }
1380
- class Tt {
1731
+ class zt {
1381
1732
  frames = 0;
1382
1733
  lastLodLevel_Mesh = -1;
1383
1734
  lastLodLevel_Texture = -1;
@@ -1385,50 +1736,65 @@ class Tt {
1385
1736
  lastScreenspaceVolume = new A();
1386
1737
  lastCentrality = 0;
1387
1738
  }
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();
1739
+ function Ee(i, t) {
1740
+ if (!(t < 0)) {
1741
+ if (Array.isArray(i)) {
1742
+ for (const e of i)
1743
+ Ee(e, t);
1744
+ return;
1745
+ }
1746
+ "color" in i && i.color instanceof $e && (i.color.copy(Et(t, $t)), i.needsUpdate = !0);
1747
+ }
1748
+ }
1749
+ function Et(i, t) {
1750
+ const e = Math.max(0, Math.min(Re.length - 1, Math.floor(i)));
1751
+ return t.setHex(Re[e]);
1752
+ }
1753
+ const Ce = /* @__PURE__ */ Symbol("NEEDLE_mesh_lod"), ue = /* @__PURE__ */ Symbol("NEEDLE_texture_lod");
1754
+ let ye = null;
1755
+ function Ne() {
1756
+ const i = Nt();
1392
1757
  i && (i.mapURLs(function(t) {
1393
- return Te(), t;
1394
- }), Te(), he?.disconnect(), he = new MutationObserver((t) => {
1758
+ return ke(), t;
1759
+ }), ke(), ye?.disconnect(), ye = new MutationObserver((t) => {
1395
1760
  t.forEach((e) => {
1396
1761
  e.addedNodes.forEach((s) => {
1397
- s instanceof HTMLElement && s.tagName.toLowerCase() === "model-viewer" && Ge(s);
1762
+ s instanceof HTMLElement && s.tagName.toLowerCase() === "model-viewer" && Ve(s);
1398
1763
  });
1399
1764
  });
1400
- }), he.observe(document, { childList: !0, subtree: !0 }));
1765
+ }), ye.observe(document, { childList: !0, subtree: !0 }));
1401
1766
  }
1402
- function Pt() {
1767
+ function Nt() {
1403
1768
  if (typeof customElements > "u") return null;
1404
1769
  const i = customElements.get("model-viewer");
1405
1770
  return i || (customElements.whenDefined("model-viewer").then(() => {
1406
- console.debug("[gltf-progressive] model-viewer defined"), Be();
1771
+ console.debug("[gltf-progressive] model-viewer defined"), Ne();
1407
1772
  }), null);
1408
1773
  }
1409
- function Te() {
1774
+ function ke() {
1410
1775
  if (typeof document > "u") return;
1411
1776
  document.querySelectorAll("model-viewer").forEach((t) => {
1412
- Ge(t);
1777
+ Ve(t);
1413
1778
  });
1414
1779
  }
1415
- const Pe = /* @__PURE__ */ new WeakSet();
1416
- let Ct = 0;
1417
- function Ge(i) {
1418
- if (!i || Pe.has(i))
1780
+ const Ae = /* @__PURE__ */ new WeakSet();
1781
+ let Vt = 0;
1782
+ function Ve(i) {
1783
+ if (!i || Ae.has(i))
1419
1784
  return null;
1420
- Pe.add(i), console.debug("[gltf-progressive] found new model-viewer..." + ++Ct + `
1785
+ Ae.add(i), console.debug("[gltf-progressive] found new model-viewer..." + ++Vt + `
1421
1786
  `, i.getAttribute("src"));
1422
1787
  let t = null, e = null, s = null;
1423
1788
  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]);
1789
+ const n = Object.getOwnPropertySymbols(r), o = n.find((u) => u.toString() == "Symbol(renderer)"), l = n.find((u) => u.toString() == "Symbol(scene)"), a = n.find((u) => u.toString() == "Symbol(needsRender)");
1790
+ !t && o != null && (t = i[o].threeRenderer), !e && l != null && (e = i[l]), !s && a != null && (s = i[a]);
1426
1791
  }
1427
1792
  if (t && e) {
1428
1793
  let r = function() {
1429
1794
  if (s) {
1430
- let n = 0, l = setInterval(() => {
1431
- if (n++ > 5) {
1795
+ let o = 0;
1796
+ const l = setInterval(() => {
1797
+ if (o++ > 5) {
1432
1798
  clearInterval(l);
1433
1799
  return;
1434
1800
  }
@@ -1437,20 +1803,20 @@ function Ge(i) {
1437
1803
  }
1438
1804
  };
1439
1805
  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", () => {
1806
+ const n = G.get(t, { engine: "model-viewer" });
1807
+ return G.addPlugin(new Xt()), n.enable(), n.addEventListener("changed", () => {
1442
1808
  s?.call(i);
1443
- }), i.addEventListener("model-visibility", (n) => {
1444
- n.detail.visible && s?.call(i);
1809
+ }), i.addEventListener("model-visibility", (o) => {
1810
+ o.detail.visible && s?.call(i);
1445
1811
  }), i.addEventListener("load", () => {
1446
1812
  r();
1447
1813
  }), () => {
1448
- o.disable();
1814
+ n.disable();
1449
1815
  };
1450
1816
  }
1451
1817
  return null;
1452
1818
  }
1453
- class At {
1819
+ class Xt {
1454
1820
  _didWarnAboutMissingUrl = !1;
1455
1821
  onBeforeUpdateLOD(t, e, s, r) {
1456
1822
  this.tryParseMeshLOD(e, r), this.tryParseTextureLOD(e, r);
@@ -1468,50 +1834,50 @@ class At {
1468
1834
  return t.element;
1469
1835
  }
1470
1836
  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);
1837
+ if (e[ue] == !0) return;
1838
+ e[ue] = !0;
1839
+ const s = this.tryGetCurrentGLTF(t), r = this.tryGetCurrentModelViewer(t), n = this.getUrl(r);
1840
+ if (n && s && e.material) {
1841
+ let o = function(a) {
1842
+ if (a[ue] == !0) return;
1843
+ a[ue] = !0, a.userData && (a.userData.LOD = -1);
1478
1844
  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 " + _);
1845
+ for (let d = 0; d < u.length; d++) {
1846
+ const c = u[d], f = a[c];
1847
+ if (f?.isTexture === !0) {
1848
+ const h = f.userData?.associations?.textures;
1849
+ if (h == null) continue;
1850
+ const D = s.parser.json.textures[h];
1851
+ if (!D) {
1852
+ console.warn("Texture data not found for texture index " + h);
1487
1853
  continue;
1488
1854
  }
1489
- if (R?.extensions?.[W]) {
1490
- const M = R.extensions[W];
1491
- M && o && m.registerTexture(o, w, M.lods.length, _, M);
1855
+ if (D?.extensions?.[z]) {
1856
+ const w = D.extensions[z];
1857
+ w && n && p.registerTexture(n, f, w.lods.length, h, w);
1492
1858
  }
1493
1859
  }
1494
1860
  }
1495
1861
  };
1496
1862
  const l = e.material;
1497
- if (Array.isArray(l)) for (const a of l) n(a);
1498
- else n(l);
1863
+ if (Array.isArray(l)) for (const a of l) o(a);
1864
+ else o(l);
1499
1865
  }
1500
1866
  }
1501
1867
  tryParseMeshLOD(t, e) {
1502
- if (e[Se] == !0) return;
1503
- e[Se] = !0;
1868
+ if (e[Ce] == !0) return;
1869
+ e[Ce] = !0;
1504
1870
  const s = this.tryGetCurrentModelViewer(t), r = this.getUrl(s);
1505
1871
  if (!r)
1506
1872
  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);
1873
+ const n = e.userData?.gltfExtensions?.[z];
1874
+ if (n && r) {
1875
+ const o = e.uuid;
1876
+ p.registerMesh(r, o, e, 0, n.lods.length, n);
1511
1877
  }
1512
1878
  }
1513
1879
  }
1514
- function Rt(...i) {
1880
+ function jt(...i) {
1515
1881
  let t, e, s, r;
1516
1882
  switch (i.length) {
1517
1883
  case 2:
@@ -1526,22 +1892,22 @@ function Rt(...i) {
1526
1892
  default:
1527
1893
  throw new Error("Invalid arguments");
1528
1894
  }
1529
- Le(e), Re(s), Ie(s, {
1895
+ _e(e), qe(s), Fe(s, {
1530
1896
  progressive: !0,
1531
1897
  ...r?.hints
1532
- }), s.register((n) => new m(n));
1533
- const o = v.get(e);
1534
- return r?.enableLODsManager !== !1 && o.enable(), o;
1898
+ }), s.register((o) => new p(o));
1899
+ const n = G.get(e);
1900
+ return r?.enableLODsManager !== !1 && n.enable(), n;
1535
1901
  }
1536
- Be();
1537
- if (!yt) {
1902
+ Ne();
1903
+ if (!Mt) {
1538
1904
  const i = {
1539
1905
  gltfProgressive: {
1540
- useNeedleProgressive: Rt,
1541
- LODsManager: v,
1542
- configureLoader: Ie,
1543
- getRaycastMesh: ee,
1544
- useRaycastMeshes: wt
1906
+ useNeedleProgressive: jt,
1907
+ LODsManager: G,
1908
+ configureLoader: Fe,
1909
+ getRaycastMesh: ae,
1910
+ useRaycastMeshes: bt
1545
1911
  }
1546
1912
  };
1547
1913
  if (!globalThis.Needle)
@@ -1551,18 +1917,21 @@ if (!yt) {
1551
1917
  globalThis.Needle[t] = i[t];
1552
1918
  }
1553
1919
  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
1920
+ z as EXTENSION_NAME,
1921
+ G as LODsManager,
1922
+ p as NEEDLE_progressive,
1923
+ ut as VERSION,
1924
+ qe as addDracoAndKTX2Loaders,
1925
+ Ut as calculateMeshLODLevel,
1926
+ Fe as configureLoader,
1927
+ _e as createLoaders,
1928
+ Et as getLODColor,
1929
+ ae as getRaycastMesh,
1930
+ Re as lodDebugColors,
1931
+ Ne as patchModelViewer,
1932
+ _t as registerRaycastMesh,
1933
+ ht as setDracoDecoderLocation,
1934
+ gt as setKTX2TranscoderLocation,
1935
+ jt as useNeedleProgressive,
1936
+ bt as useRaycastMeshes
1568
1937
  };