@needle-tools/gltf-progressive 4.0.0-alpha → 4.0.0-alpha.2

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