@needle-tools/engine 4.16.11 → 4.16.13

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 (84) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/SKILL.md +64 -4
  3. package/dist/gltf-progressive-BT_4GMMF.js +1927 -0
  4. package/dist/gltf-progressive-C4zH6OZo.min.js +10 -0
  5. package/dist/gltf-progressive-KjDKGnBJ.umd.cjs +10 -0
  6. package/dist/{needle-engine.bundle-Du3vf5L1.js → needle-engine.bundle-B_o07sw9.js} +7924 -7774
  7. package/dist/{needle-engine.bundle-DOChN3At.umd.cjs → needle-engine.bundle-BiPi1Ix_.umd.cjs} +139 -139
  8. package/dist/needle-engine.bundle-C7hgmyY2.min.js +1654 -0
  9. package/dist/needle-engine.d.ts +13 -1
  10. package/dist/needle-engine.js +3 -3
  11. package/dist/needle-engine.min.js +1 -1
  12. package/dist/needle-engine.umd.cjs +1 -1
  13. package/dist/three-examples.js +52 -52
  14. package/dist/three-examples.min.js +4 -4
  15. package/dist/three-examples.umd.cjs +2 -2
  16. package/dist/three.js +3 -0
  17. package/dist/three.min.js +22 -22
  18. package/dist/three.umd.cjs +24 -24
  19. package/lib/engine/api.d.ts +2 -1
  20. package/lib/engine/api.js +1 -6
  21. package/lib/engine/api.js.map +1 -1
  22. package/lib/engine/engine_addressables.d.ts +32 -1
  23. package/lib/engine/engine_addressables.js +71 -9
  24. package/lib/engine/engine_addressables.js.map +1 -1
  25. package/lib/engine/engine_application.d.ts +2 -0
  26. package/lib/engine/engine_application.js +7 -3
  27. package/lib/engine/engine_application.js.map +1 -1
  28. package/lib/engine/engine_context.js +28 -1
  29. package/lib/engine/engine_context.js.map +1 -1
  30. package/lib/engine/engine_input.js +1 -1
  31. package/lib/engine/engine_input.js.map +1 -1
  32. package/lib/engine/engine_license.d.ts +1 -1
  33. package/lib/engine/engine_license.js +31 -5
  34. package/lib/engine/engine_license.js.map +1 -1
  35. package/lib/engine/engine_lifecycle_functions_internal.js +20 -2
  36. package/lib/engine/engine_lifecycle_functions_internal.js.map +1 -1
  37. package/lib/engine/engine_loaders.gltf.d.ts +3 -1
  38. package/lib/engine/engine_loaders.gltf.js +6 -1
  39. package/lib/engine/engine_loaders.gltf.js.map +1 -1
  40. package/lib/engine/engine_networking_streams.d.ts +5 -1
  41. package/lib/engine/engine_networking_streams.js +64 -17
  42. package/lib/engine/engine_networking_streams.js.map +1 -1
  43. package/lib/engine/engine_physics.d.ts +2 -0
  44. package/lib/engine/engine_physics.js +23 -1
  45. package/lib/engine/engine_physics.js.map +1 -1
  46. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +5 -0
  47. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  48. package/lib/engine/webcomponents/fonts.d.ts +1 -1
  49. package/lib/engine/webcomponents/fonts.js +7 -0
  50. package/lib/engine/webcomponents/fonts.js.map +1 -1
  51. package/lib/engine/webcomponents/icons.js +8 -1
  52. package/lib/engine/webcomponents/icons.js.map +1 -1
  53. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +5 -0
  54. package/lib/engine/webcomponents/needle menu/needle-menu.js +31 -13
  55. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  56. package/lib/engine/webcomponents/needle-engine.d.ts +1 -0
  57. package/lib/engine/webcomponents/needle-engine.js +16 -4
  58. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  59. package/lib/engine/webcomponents/needle-engine.loading.d.ts +2 -0
  60. package/lib/engine/webcomponents/needle-engine.loading.js +10 -0
  61. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  62. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  63. package/package.json +2 -2
  64. package/src/engine/api.ts +3 -1
  65. package/src/engine/engine_addressables.ts +75 -9
  66. package/src/engine/engine_application.ts +7 -2
  67. package/src/engine/engine_context.ts +28 -1
  68. package/src/engine/engine_input.ts +2 -2
  69. package/src/engine/engine_license.ts +28 -5
  70. package/src/engine/engine_lifecycle_functions_internal.ts +21 -2
  71. package/src/engine/engine_loaders.gltf.ts +8 -2
  72. package/src/engine/engine_networking_streams.ts +64 -19
  73. package/src/engine/engine_physics.ts +22 -1
  74. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +8 -1
  75. package/src/engine/webcomponents/fonts.ts +6 -0
  76. package/src/engine/webcomponents/icons.ts +9 -2
  77. package/src/engine/webcomponents/needle menu/needle-menu.ts +30 -12
  78. package/src/engine/webcomponents/needle-engine.loading.ts +13 -1
  79. package/src/engine/webcomponents/needle-engine.ts +15 -5
  80. package/dist/gltf-progressive-BacJPTD6.umd.cjs +0 -10
  81. package/dist/gltf-progressive-BmXoz_HR.min.js +0 -10
  82. package/dist/gltf-progressive-CzxjNmG6.js +0 -1537
  83. package/dist/needle-engine.bundle-fP5tOMI1.min.js +0 -1654
  84. /package/dist/{loader.worker-BqODMeeW.js → gltf-progressive.worker-BqODMeeW.js} +0 -0
@@ -1,4 +1,5 @@
1
- import Peer, { CallOption, MediaConnection } from "peerjs"
1
+ import type Peer from "peerjs";
2
+ import type { CallOption, MediaConnection } from "peerjs";
2
3
  import { EventDispatcher } from "three";
3
4
 
4
5
  import { RoomEvents } from "../engine/engine_networking.js";
@@ -7,6 +8,7 @@ import { getPeerjsInstance } from "../engine/engine_networking_peer.js";
7
8
  import { showBalloonMessage } from "./debug/index.js";
8
9
  import { Application } from "./engine_application.js";
9
10
  import { Context } from "./engine_context.js";
11
+ import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
10
12
  import type { IModel } from "./engine_networking_types.js";
11
13
  import { type IComponent, isComponent } from "./engine_types.js";
12
14
  import { getParam } from "./engine_utils.js";
@@ -120,17 +122,31 @@ function applySdpTransform(sdp) {
120
122
 
121
123
  export class PeerHandle extends EventDispatcher<any> {
122
124
 
123
- private static readonly instances: Map<string, PeerHandle> = new Map();
125
+ // Weak ownership prevents this cache from rooting a context and its scene indefinitely.
126
+ // IDs are scoped to contexts: separate engine elements may use the same stream ID.
127
+ private static readonly instances = new WeakMap<Context, Map<string, PeerHandle>>();
128
+
129
+ static {
130
+ ContextRegistry.registerCallback(ContextEvent.ContextDestroying, ({ context }) => {
131
+ const handles = PeerHandle.instances.get(context as Context);
132
+ if (!handles) return;
133
+ PeerHandle.instances.delete(context as Context);
134
+ for (const handle of handles.values()) handle.dispose();
135
+ handles.clear();
136
+ });
137
+ }
124
138
 
125
139
  static getOrCreate(context: Context, guid: string): PeerHandle {
126
- // if (id === undefined) {
127
- // // randomId
128
- // id = Math.random().toFixed(5);
129
- // }
130
- if (PeerHandle.instances.has(guid))
131
- return PeerHandle.instances.get(guid)!;
132
- const peer = new PeerHandle(context, guid);
133
- PeerHandle.instances.set(guid, peer);
140
+ let handles = PeerHandle.instances.get(context);
141
+ if (!handles) {
142
+ handles = new Map();
143
+ PeerHandle.instances.set(context, handles);
144
+ }
145
+ let peer = handles.get(guid);
146
+ if (!peer) {
147
+ peer = new PeerHandle(context, guid);
148
+ handles.set(guid, peer);
149
+ }
134
150
  return peer;
135
151
  }
136
152
 
@@ -150,6 +166,7 @@ export class PeerHandle extends EventDispatcher<any> {
150
166
  }
151
167
 
152
168
  makeCall(peerId: string, stream: MediaStream): CallHandle | undefined {
169
+ if (this._disposed) return undefined;
153
170
  if (!stream?.id) {
154
171
  if (debug) console.warn("Can not make a call: mediastream has no id or is undefined");
155
172
  else console.debug("Can not make a call: mediastream has no id or is undefined");
@@ -178,10 +195,8 @@ export class PeerHandle extends EventDispatcher<any> {
178
195
  }
179
196
 
180
197
  closeAll() {
181
- for (const call of this._incomingCalls) {
182
- call.close();
183
- }
184
- for (const call of this._outgoingCalls) {
198
+ // Closing a call synchronously removes it from the original array.
199
+ for (const call of [...this._incomingCalls, ...this._outgoingCalls]) {
185
200
  call.close();
186
201
  }
187
202
  this.updateCalls();
@@ -252,16 +267,33 @@ export class PeerHandle extends EventDispatcher<any> {
252
267
  }
253
268
  }
254
269
 
270
+ private _disposed = false;
271
+ private readonly _callIntervals = new Set<ReturnType<typeof setInterval>>();
272
+
273
+ private dispose() {
274
+ if (this._disposed) return;
275
+ this._disposed = true;
276
+ this.disable();
277
+ this.unsubscribePeerEvents();
278
+ for (const interval of this._callIntervals) clearInterval(interval);
279
+ this._callIntervals.clear();
280
+ this.closeAll();
281
+ this._incomingCalls.length = 0;
282
+ this._outgoingCalls.length = 0;
283
+ this._peer?.destroy();
284
+ this._peer = undefined;
285
+ }
286
+
255
287
  private _enabled: boolean = false;
256
288
  private _enabledPeer: boolean = false;
257
- private onConnectRoomFn: Function = this.onConnectRoom.bind(this);
289
+ private onConnectRoomFn: () => void = this.onConnectRoom.bind(this);
258
290
  // private onUserJoinedOrLeftRoomFn: Function = this.onUserJoinedOrLeftRoom.bind(this);
259
291
  // private onPeerConnectFn: (id) => void = this.onPeerConnect.bind(this);
260
292
  // private onPeerReceiveCallFn: (call) => void = this.onPeerReceivingCall.bind(this);
261
293
  // private _connectionPeerIdMap : Map<string, string> = new Map();
262
294
 
263
295
  enable() {
264
- if (this._enabled) return;
296
+ if (this._disposed || this._enabled) return;
265
297
  this._enabled = true;
266
298
  this.context.connection.beginListen(RoomEvents.JoinedRoom, this.onConnectRoomFn);
267
299
  // this.context.connection.beginListen(RoomEvents.UserJoinedRoom, this.onUserJoinedOrLeftRoomFn);
@@ -286,13 +318,19 @@ export class PeerHandle extends EventDispatcher<any> {
286
318
  // };
287
319
 
288
320
  private setupPeer() {
289
- if (!this.context.connection.connectionId) return;
321
+ if (this._disposed || !this.context.connection.connectionId) return;
290
322
  if (this._enabledPeer) return;
291
323
  this._enabledPeer = true;
292
324
  if (!this._peer) {
293
325
  const peerId = this.getMyPeerId();
294
- if (peerId)
295
- this._peer = getPeerjsInstance(peerId);
326
+ if (peerId) {
327
+ const peer = getPeerjsInstance(peerId);
328
+ if (this._disposed) {
329
+ peer.destroy();
330
+ return;
331
+ }
332
+ this._peer = peer;
333
+ }
296
334
  else console.error("Failed to setup peerjs because we dont have a connection id", this.context.connection.connectionId);
297
335
  }
298
336
  if (this._enabled)
@@ -414,6 +452,7 @@ export class PeerHandle extends EventDispatcher<any> {
414
452
  });
415
453
 
416
454
  call.on("stream", () => {
455
+ if (this._disposed) return;
417
456
  if (debug) console.log("Received stream for call", call.metadata);
418
457
  // workaround for https://github.com/peers/peerjs/issues/636
419
458
  let intervalCounter = 0;
@@ -423,9 +462,15 @@ export class PeerHandle extends EventDispatcher<any> {
423
462
  if (debug) console.warn("Close call because stream is not active", call.metadata);
424
463
  intervalCounter += 1;
425
464
  clearInterval(closeInterval);
465
+ this._callIntervals.delete(closeInterval);
426
466
  handle.close();
427
467
  }
428
468
  }, 2000);
469
+ this._callIntervals.add(closeInterval);
470
+ call.on("close", () => {
471
+ clearInterval(closeInterval);
472
+ this._callIntervals.delete(closeInterval);
473
+ });
429
474
  });
430
475
  }
431
476
  return handle;
@@ -175,6 +175,11 @@ export class SphereIntersection implements Intersection {
175
175
  */
176
176
  export class Physics {
177
177
 
178
+ /** Release the shared BVH worker pool after the last engine context is destroyed. */
179
+ static disposeMeshBVHWorkers() {
180
+ NEMeshBVH.disposeWorkers();
181
+ }
182
+
178
183
  private static _raycasting: number = 0;
179
184
  /**
180
185
  * Returns true if raycasting is currently in progress
@@ -738,7 +743,13 @@ namespace NEMeshBVH {
738
743
  })
739
744
  .finally(() => {
740
745
  if (debugPhysics) console.log(">>>>> worker done", name, { hasBoundsTre: geom.boundsTree != undefined });
741
- availableWorkers.push(workerInstance);
746
+ if (retiredWorkers.has(workerInstance)) {
747
+ retiredWorkers.delete(workerInstance);
748
+ workerInstance.dispose();
749
+ }
750
+ else {
751
+ availableWorkers.push(workerInstance);
752
+ }
742
753
  copy.dispose();
743
754
  performance.mark("bvh.create.end");
744
755
  performance.measure("bvh.create (worker)", "bvh.create.start", "bvh.create.end");
@@ -859,6 +870,16 @@ namespace NEMeshBVH {
859
870
  const canUseWorkerSymbol = Symbol("Needle:MeshBVH-CanUseWorker");
860
871
  const workerInstances: GenerateMeshBVHWorker[] = [];
861
872
  const availableWorkers: GenerateMeshBVHWorker[] = [];
873
+ const retiredWorkers = new Set<GenerateMeshBVHWorker>();
874
+
875
+ export function disposeWorkers() {
876
+ for (const worker of workerInstances) {
877
+ if (worker.running) retiredWorkers.add(worker);
878
+ else worker.dispose();
879
+ }
880
+ workerInstances.length = 0;
881
+ availableWorkers.length = 0;
882
+ }
862
883
 
863
884
  function internalSetupWorker() {
864
885
  didSetupWorker = true;
@@ -120,7 +120,14 @@ export class GenerateMeshBVHWorker extends WorkerBase {
120
120
 
121
121
  }, transferable.map( arr => arr.buffer ).filter( v => ( typeof SharedArrayBuffer === 'undefined' ) || ! ( v instanceof SharedArrayBuffer ) ) );
122
122
 
123
- } );
123
+ } ).finally( () => {
124
+
125
+ // Pooled workers outlive each job. Both handlers capture the job scope,
126
+ // including its geometry, even after the promise has settled.
127
+ worker.onmessage = null;
128
+ worker.onerror = null;
129
+
130
+ } );
124
131
 
125
132
  }
126
133
 
@@ -27,6 +27,12 @@ export function loadFont(url: string, opts?: LoadFontOptions) {
27
27
  }
28
28
  }
29
29
  }
30
+ // The link lives in document.head and can outlive the component using this callback.
31
+ return () => {
32
+ if (opts?.loadedCallback) {
33
+ for (const link of elements) link.removeEventListener("load", opts.loadedCallback);
34
+ }
35
+ };
30
36
  }
31
37
 
32
38
 
@@ -20,7 +20,15 @@ export function getIconElement(str: string): HTMLElement {
20
20
  span.setAttribute("role", "img");
21
21
  span.setAttribute("aria-label", str + " icon");
22
22
  span.setAttribute("aria-hidden", "true");
23
+ updateIconWhenFontReady(new WeakRef(span), str);
24
+ return span;
25
+ }
26
+
27
+ // Pending font loads must not retain detached menu trees.
28
+ function updateIconWhenFontReady(reference: WeakRef<HTMLSpanElement>, str: string) {
23
29
  fontReady(fontname).then(res => {
30
+ const span = reference.deref();
31
+ if (!span) return;
24
32
  if (res) span.style.visibility = "";
25
33
  else {
26
34
  if (str === "more_vert") {
@@ -31,8 +39,7 @@ export function getIconElement(str: string): HTMLElement {
31
39
  span.style.display = "none";
32
40
  }
33
41
  }
34
- })
35
- return span;
42
+ });
36
43
  }
37
44
 
38
45
  /**@returns true if the element is an needle engine icon element */
@@ -3,7 +3,7 @@ import type { Context } from "../../engine_context.js";
3
3
  import { hasCommercialLicense, onLicenseCheckResultChanged, Telemetry } from "../../engine_license.js";
4
4
  import { isLocalNetwork } from "../../engine_networking_utils.js";
5
5
  import { DeviceUtilities, getParam } from "../../engine_utils.js";
6
- import { onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
6
+ import { offXRSessionStart, onXRSessionStart, XRSessionEventArgs } from "../../xr/events.js";
7
7
  import { ButtonsFactory } from "../buttons.js";
8
8
  import { ensureFonts, iconFontUrl, loadFont } from "../fonts.js";
9
9
  import { getIconElement } from "../icons.js";
@@ -138,7 +138,9 @@ export class NeedleMenu {
138
138
 
139
139
  /** @ignore internal method */
140
140
  onDestroy() {
141
+ offXRSessionStart(this.onStartXR);
141
142
  window.removeEventListener("message", this.onPostMessage);
143
+ this._menu.dispose();
142
144
  this._menu.remove();
143
145
  this._spatialMenu.onDestroy();
144
146
  }
@@ -338,6 +340,19 @@ export class NeedleMenuElement extends HTMLElement {
338
340
 
339
341
  private _domElement: HTMLElement | null = null;
340
342
  private _context: Context | null = null;
343
+ private _unsubscribeFont?: () => void;
344
+ private _licenseFrame?: number;
345
+ private _unsubscribeLicense?: () => void;
346
+
347
+ /** @internal Release font and license subscriptions when the owning context is destroyed. */
348
+ dispose() {
349
+ this._unsubscribeFont?.();
350
+ this._unsubscribeFont = undefined;
351
+ if (this._licenseFrame !== undefined) window.cancelAnimationFrame(this._licenseFrame);
352
+ this._licenseFrame = undefined;
353
+ this._unsubscribeLicense?.();
354
+ this._unsubscribeLicense = undefined;
355
+ }
341
356
  private _didInitialize = false;
342
357
 
343
358
  constructor() {
@@ -748,7 +763,7 @@ export class NeedleMenuElement extends HTMLElement {
748
763
  // we need to add the icons to both the shadow dom as well as the HEAD to work
749
764
  // https://github.com/google/material-design-icons/issues/1165
750
765
  ensureFonts();
751
- loadFont(iconFontUrl, { loadedCallback: () => { this.handleSizeChange() } });
766
+ this._unsubscribeFont = loadFont(iconFontUrl, { loadedCallback: () => { this.handleSizeChange() } });
752
767
  loadFont(iconFontUrl, { element: shadow });
753
768
 
754
769
  const content = template.content.cloneNode(true) as DocumentFragment;
@@ -777,16 +792,19 @@ export class NeedleMenuElement extends HTMLElement {
777
792
  try {
778
793
  // if the user has a license then we CAN hide the needle logo
779
794
  // calling this method immediately will cause an issue with vite bundling tho
780
- window.requestAnimationFrame(() => onLicenseCheckResultChanged(res => {
781
- if (res == true && hasCommercialLicense() && !debugNonCommercial) {
782
- let visible = this._userRequestedLogoVisible;
783
- if (visible === undefined) visible = false;
784
- this.___onSetLogoVisible(visible);
785
- }
786
- else {
787
- this.___onSetLogoVisible(true);
788
- }
789
- }));
795
+ this._licenseFrame = window.requestAnimationFrame(() => {
796
+ this._licenseFrame = undefined;
797
+ this._unsubscribeLicense = onLicenseCheckResultChanged(res => {
798
+ if (res == true && hasCommercialLicense() && !debugNonCommercial) {
799
+ let visible = this._userRequestedLogoVisible;
800
+ if (visible === undefined) visible = false;
801
+ this.___onSetLogoVisible(visible);
802
+ }
803
+ else {
804
+ this.___onSetLogoVisible(true);
805
+ }
806
+ });
807
+ });
790
808
  } catch (e) {
791
809
  console.error("[Needle Menu] License check failed.", e);
792
810
  }
@@ -19,6 +19,7 @@ export class LoadingElementOptions {
19
19
 
20
20
  /** @internal */
21
21
  export interface ILoadingViewHandler {
22
+ dispose?(): void;
22
23
  onLoadingBegin(message?: string)
23
24
  onLoadingUpdate(progress: LoadingProgressArgs | number, message?: string);
24
25
  onLoadingFinished(message?: string);
@@ -79,6 +80,17 @@ export class EngineLoadingView implements ILoadingViewHandler {
79
80
  this._loadingElementOptions = opts;
80
81
  }
81
82
 
83
+ dispose() {
84
+ // Removal can happen before ready stops the progress interval.
85
+ clearInterval(this._progressLoop);
86
+ this._progressLoop = null;
87
+ this._loadingElement?.remove();
88
+ this._loadingElement = undefined;
89
+ this._loadingTextContainer = null;
90
+ this._loadingBar = null;
91
+ this._messageContainer = null;
92
+ }
93
+
82
94
  async onLoadingBegin(message?: string) {
83
95
  const _element = this._element.shadowRoot || this._element;
84
96
  if (debug) console.warn("Begin Loading")
@@ -402,4 +414,4 @@ export class EngineLoadingView implements ILoadingViewHandler {
402
414
  // nonCommercialContainer.style.transition = "opacity .5s ease-in-out";
403
415
  // nonCommercialContainer.style.opacity = "1";
404
416
  // }
405
- }
417
+ }
@@ -172,6 +172,7 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
172
172
  private _overlay_ar!: AROverlayHandler;
173
173
  private _loadingProgress01: number = 0;
174
174
  private _loadingView?: ILoadingViewHandler;
175
+ private _cancelLoadingBlur?: () => void;
175
176
  private _previousSrc: string | null | string[] = null;
176
177
  /** @private set to true after <needle-engine> did load completely at least once. Set to false when < to false when <needle-engine> is removed from the document removed from the document */
177
178
  private _didFullyLoad: boolean = false;
@@ -335,6 +336,9 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
335
336
  const dispose = keepAlive == undefined || (keepAlive?.length > 0 && keepAlive !== "true" && keepAlive !== "1");
336
337
  if (debug) console.warn("<needle-engine> disconnected, keep-alive: \"" + keepAlive + "\"", typeof keepAlive, "Dispose=", dispose);
337
338
  if (dispose) {
339
+ this._cancelLoadingBlur?.();
340
+ this._cancelLoadingBlur = undefined;
341
+ this._loadingView?.dispose?.();
338
342
  if (debug)
339
343
  console.warn("<needle-engine> dispose");
340
344
  this._context?.dispose();
@@ -583,7 +587,8 @@ export class NeedleEngineWebComponent extends HTMLElement implements INeedleEngi
583
587
 
584
588
 
585
589
  // Experimental loading blur
586
- handleLoadingBlur(this);
590
+ this._cancelLoadingBlur?.();
591
+ this._cancelLoadingBlur = handleLoadingBlur(this);
587
592
 
588
593
  const currentHash = this.getAttribute("hash");
589
594
  if (currentHash !== null && currentHash !== undefined)
@@ -1243,8 +1248,12 @@ function getDisplayName(str: string) {
1243
1248
 
1244
1249
 
1245
1250
  function handleLoadingBlur(needleEngineElement: NeedleEngineWebComponent) {
1246
- onStart((ctx) => {
1247
- const userBlurSetting = needleEngineElement.getAttribute("loading-blur");
1251
+ const userBlurSetting = needleEngineElement.getAttribute("loading-blur");
1252
+ if (userBlurSetting === null || userBlurSetting === "0") return undefined;
1253
+ const unregister = onStart((ctx) => {
1254
+ // A different context starting must not consume this element's callback.
1255
+ if (ctx.domElement !== needleEngineElement) return;
1256
+ unregister();
1248
1257
  if (userBlurSetting !== null && userBlurSetting !== "0") {
1249
1258
  if (ctx.domElement === needleEngineElement) {
1250
1259
 
@@ -1297,5 +1306,6 @@ function handleLoadingBlur(needleEngineElement: NeedleEngineWebComponent) {
1297
1306
  }
1298
1307
  }
1299
1308
  }
1300
- }, { once: true });
1301
- }
1309
+ });
1310
+ return unregister;
1311
+ }
@@ -1,10 +0,0 @@
1
- "use strict";const c=require("./three.umd.cjs"),X=require("./three-examples.umd.cjs");var le=typeof document<"u"?document.currentScript:null;const $e="";globalThis.GLTF_PROGRESSIVE_VERSION=$e;console.debug("[gltf-progressive] version -");let I="https://www.gstatic.com/draco/versioned/decoders/1.5.7/",N="https://cdn.needle.tools/static/three/0.179.1/basis2/";const Be=I,Ge=N,Oe=new URL(I+"draco_decoder.js");Oe.searchParams.append("range","true");fetch(Oe,{method:"GET",headers:{Range:"bytes=0-1"}}).catch(i=>{console.debug(`Failed to fetch remote Draco decoder from ${I} (offline: ${typeof navigator<"u"?navigator.onLine:"unknown"})`),I===Be&&Te("./include/draco/"),N===Ge&&Se("./include/ktx2/")}).finally(()=>{Pe()});const Fe=()=>({dracoDecoderPath:I,ktx2TranscoderPath:N});function Te(i){I=i,A&&A[pe]!=I?(console.debug("Updating Draco decoder path to "+i),A[pe]=I,A.setDecoderPath(I),A.preload()):console.debug("Setting Draco decoder path to "+i)}function Se(i){N=i,$&&$.transcoderPath!=N?(console.debug("Updating KTX2 transcoder path to "+i),$.setTranscoderPath(N),$.init()):console.debug("Setting KTX2 transcoder path to "+i)}function ne(i){return Pe(),i?$.detectSupport(i):i!==null&&console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"),{dracoLoader:A,ktx2Loader:$,meshoptDecoder:oe}}function xe(i){i.dracoLoader||i.setDRACOLoader(A),i.ktx2Loader||i.setKTX2Loader($),i.meshoptDecoder||i.setMeshoptDecoder(oe)}const pe=Symbol("dracoDecoderPath");let A,oe,$;function Pe(){A||(A=new X.DRACOLoader,A[pe]=I,A.setDecoderPath(I),A.setDecoderConfig({type:"js"}),A.preload()),$||($=new X.KTX2Loader,$.setTranscoderPath(N),$.init()),oe||(oe=X.MeshoptDecoder)}const me=new WeakMap;function we(i,t){let e=me.get(i);e?e=Object.assign(e,t):e=t,me.set(i,e)}const Ue=X.GLTFLoader.prototype.load;function We(...i){const t=me.get(this);let e=i[0];const s=new URL(e,window.location.href);if(s.hostname.endsWith("needle.tools")){const o=t?.progressive!==void 0?t.progressive:!0,n=t?.usecase?t.usecase:"default";o?this.requestHeader.Accept=`*/*;progressive=allowed;usecase=${n}`:this.requestHeader.Accept=`*/*;usecase=${n}`,e=s.toString()}return i[0]=e,Ue?.call(this,...i)}X.GLTFLoader.prototype.load=We;z("debugprogressive");function z(i){if(typeof window>"u")return!1;const e=new URL(window.location.href).searchParams.get(i);return e==null||e==="0"||e==="false"?!1:e===""?!0:e}function ze(i,t){if(t===void 0||i===void 0||t.startsWith("./")||t.startsWith("http")||t.startsWith("data:")||t.startsWith("blob:"))return t;const e=i.lastIndexOf("/");if(e>=0){const s=i.substring(0,e+1);for(;s.endsWith("/")&&t.startsWith("/");)t=t.substring(1);return s+t}return t}function Le(){return Y!==void 0||(Y=/iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent),z("debugprogressive")&&console.log("[glTF Progressive]: isMobileDevice",Y)),Y}let Y;function Me(){if(typeof window>"u")return!1;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);return i.hostname==="127.0.0.1"||t}class Ve{constructor(t,e={}){this.maxConcurrent=t,this.debug=e.debug??!1,window.requestAnimationFrame(this.tick)}_running=new Map;_queue=[];debug=!1;tick=()=>{this.internalUpdate(),setTimeout(this.tick,10)};slot(t){return this.debug&&console.debug(`[PromiseQueue]: Requesting slot for key ${t}, running: ${this._running.size}, waiting: ${this._queue.length}`),new Promise(e=>{this._queue.push({key:t,resolve:e})})}add(t,e){this._running.has(t)||(this._running.set(t,e),e.finally(()=>{this._running.delete(t),this.debug&&console.debug(`[PromiseQueue]: Promise finished now running: ${this._running.size}, waiting: ${this._queue.length}. (finished ${t})`)}),this.debug&&console.debug(`[PromiseQueue]: Added new promise, now running: ${this._running.size}, waiting: ${this._queue.length}. (added ${t})`))}internalUpdate(){const t=this.maxConcurrent-this._running.size;for(let e=0;e<t&&this._queue.length>0;e++){this.debug&&console.debug(`[PromiseQueue]: Running ${this._running.size} promises, waiting for ${this._queue.length} more.`);const{key:s,resolve:r}=this._queue.shift();r({use:o=>this.add(s,o)})}}}function qe(i){const t=i.image?.width??0,e=i.image?.height??0,s=i.image?.depth??1,r=Math.floor(Math.log2(Math.max(t,e,s)))+1,o=Ne(i);return t*e*s*o*(1-Math.pow(.25,r))/(1-.25)}function Ne(i){let t=4;const e=i.format;e===1024||e===1025?t=1:e===1026||e===1027?t=2:e===1022||e===1029?t=3:(e===1023||e===1033)&&(t=4);let s=1;const r=i.type;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}const Ee=typeof window>"u"&&typeof document>"u",ye=Symbol("needle:raycast-mesh");function j(i){return i?.[ye]instanceof c.BufferGeometry?i[ye]:null}function Xe(i,t){if((i.type==="Mesh"||i.type==="SkinnedMesh")&&!j(i)){const s=Ke(t);s.userData={isRaycastMesh:!0},i[ye]=s}}function je(i=!0){if(i){if(H)return;const t=H=c.Mesh.prototype.raycast;c.Mesh.prototype.raycast=function(e,s){const r=this,o=j(r);let n;o&&r.isMesh&&(n=r.geometry,r.geometry=o),t.call(this,e,s),n&&(r.geometry=n)}}else{if(!H)return;c.Mesh.prototype.raycast=H,H=null}}let H=null;function Ke(i){const t=new c.BufferGeometry;for(const e in i.attributes)t.setAttribute(e,i.getAttribute(e));return t.setIndex(i.getIndex()),t}const W=new Array,h=z("debugprogressive");let J,q=-1;if(h){let i=function(){q+=1,q>=t&&(q=-1),console.log(`Toggle LOD level [${q}]`)},t=6;window.addEventListener("keyup",e=>{e.key==="p"&&i(),e.key==="w"&&(J=!J,console.log(`Toggle wireframe [${J}]`));const s=parseInt(e.key);!isNaN(s)&&s>=0&&(q=s,console.log(`Set LOD level to [${q}]`))})}function Ce(i){if(h&&J!==void 0)if(Array.isArray(i))for(const t of i)Ce(t);else i&&"wireframe"in i&&(i.wireframe=J===!0)}const Q=new Array;let Ye=0;const He=Le()?2:10;function Qe(i){if(Q.length<He){const s=Q.length;h&&console.warn(`[Worker] Creating new worker #${s}`);const r=_e.createWorker(i||{});return Q.push(r),r}const t=Ye++%Q.length;return Q[t]}class _e{constructor(t,e){this.worker=t,this._debug=e.debug??!1,t.onmessage=s=>{const r=s.data;switch(this._debug&&console.log("[Worker] EVENT",r),r.type){case"loaded-gltf":for(const o of this._running)if(o.url===r.result.url){Je(r.result),o.resolve(r.result);const n=o.url;n.startsWith("blob:")&&URL.revokeObjectURL(n)}}},t.onerror=s=>{console.error("[Worker] Error in gltf-progressive worker:",s)},t.postMessage({type:"init"})}static async createWorker(t){const e=new Worker(new URL("/loader.worker-BqODMeeW.js",typeof document>"u"?require("url").pathToFileURL(__filename).href:le&&le.tagName.toUpperCase()==="SCRIPT"&&le.src||new URL("gltf-progressive-BacJPTD6.umd.cjs",document.baseURI).href),{type:"module"});return new _e(e,t)}_running=[];_webglRenderer=null;async load(t,e){const s=Fe();let r=e?.renderer;r||(this._webglRenderer??=(async()=>{const{WebGLRenderer:u}=await Promise.resolve().then(()=>require("./three.umd.cjs")).then(d=>d.THREE);return new u})(),r=await this._webglRenderer);const l=ne(r).ktx2Loader.workerConfig;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());const a={type:"load",url:t,dracoDecoderPath:s.dracoDecoderPath,ktx2TranscoderPath:s.ktx2TranscoderPath,ktx2LoaderConfig:l};return this._debug&&console.debug("[Worker] Sending load request",a),this.worker.postMessage(a),new Promise(u=>{this._running.push({url:t.toString(),resolve:u})})}_debug=!1}function Je(i){for(const t of i.geometries){const e=t.geometry,s=new c.BufferGeometry;if(s.name=e.name||"",e.index){const r=e.index;s.setIndex(ue(r))}for(const r in e.attributes){const o=e.attributes[r],n=ue(o);s.setAttribute(r,n)}if(e.morphAttributes)for(const r in e.morphAttributes){const n=e.morphAttributes[r].map(l=>ue(l));s.morphAttributes[r]=n}if(s.morphTargetsRelative=e.morphTargetsRelative??!1,s.boundingBox=new c.Box3,s.boundingBox.min=new c.Vector3(e.boundingBox?.min.x,e.boundingBox?.min.y,e.boundingBox?.min.z),s.boundingBox.max=new c.Vector3(e.boundingBox?.max.x,e.boundingBox?.max.y,e.boundingBox?.max.z),s.boundingSphere=new c.Sphere(new c.Vector3(e.boundingSphere?.center.x,e.boundingSphere?.center.y,e.boundingSphere?.center.z),e.boundingSphere?.radius),e.groups)for(const r of e.groups)s.addGroup(r.start,r.count,r.materialIndex);e.userData&&(s.userData=e.userData),t.geometry=s}for(const t of i.textures){const e=t.texture;let s=null;if(e.isCompressedTexture){const r=e.mipmaps,o=e.image?.width||e.source?.data?.width||-1,n=e.image?.height||e.source?.data?.height||-1;s=new c.CompressedTexture(r,o,n,e.format,e.type,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.anisotropy,e.colorSpace)}else s=new c.Texture(e.image,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.colorSpace),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 c.Matrix3(...e.matrix.elements);if(!s){console.error("[Worker] Failed to create new texture from received data. Texture is not a CompressedTexture or Texture.");continue}t.texture=s}return i}function ue(i){let t=i;if("isInterleavedBufferAttribute"in i&&i.isInterleavedBufferAttribute){const e=i.data,s=e.array,r=new c.InterleavedBuffer(s,e.stride);t=new c.InterleavedBufferAttribute(r,i.itemSize,s.byteOffset,i.normalized),t.offset=i.offset}else"isBufferAttribute"in i&&i.isBufferAttribute&&(t=new c.BufferAttribute(i.array,i.itemSize,i.normalized),t.usage=i.usage,t.gpuType=i.gpuType,t.updateRanges=i.updateRanges);return t}const Ze=z("gltf-progressive-worker");z("gltf-progressive-reduce-mipmaps");const E=z("gltf-progressive-gc"),ce=Symbol("needle-progressive-texture"),F="NEEDLE_progressive";class m{get name(){return F}static getMeshLODExtension(t){const e=this.getAssignedLODInformation(t);return e?.key?this.lodInfos.get(e.key):null}static getPrimitiveIndex(t){const e=this.getAssignedLODInformation(t)?.index;return e??-1}static getMaterialMinMaxLODsCount(t,e){const s=this,r="LODS:minmax",o=t[r];if(o!=null)return o;if(e||(e={min_count:1/0,max_count:0,lods:[]}),Array.isArray(t)){for(const l of t)this.getMaterialMinMaxLODsCount(l,e);return t[r]=e,e}if(h==="verbose"&&console.log("getMaterialMinMaxLODsCount",t),t.type==="ShaderMaterial"||t.type==="RawShaderMaterial"){const l=t;for(const a of Object.keys(l.uniforms)){const u=l.uniforms[a].value;u?.isTexture===!0&&n(u,e)}}else if(t.isMaterial)for(const l of Object.keys(t)){const a=t[l];a?.isTexture===!0&&n(a,e)}else h&&console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);return t[r]=e,e;function n(l,a){const u=s.getAssignedLODInformation(l);if(u){const d=s.lodInfos.get(u.key);if(d&&d.lods){a.min_count=Math.min(a.min_count,d.lods.length),a.max_count=Math.max(a.max_count,d.lods.length);for(let x=0;x<d.lods.length;x++){const L=d.lods[x];L.width&&(a.lods[x]=a.lods[x]||{min_height:1/0,max_height:0},a.lods[x].min_height=Math.min(a.lods[x].min_height,L.height),a.lods[x].max_height=Math.max(a.lods[x].max_height,L.height))}}}}}static hasLODLevelAvailable(t,e){if(Array.isArray(t)){for(const o of t)if(this.hasLODLevelAvailable(o,e))return!0;return!1}if(t.isMaterial===!0){for(const o of Object.keys(t)){const n=t[o];if(n&&n.isTexture&&this.hasLODLevelAvailable(n,e))return!0}return!1}else if(t.isGroup===!0){for(const o of t.children)if(o.isMesh===!0&&this.hasLODLevelAvailable(o,e))return!0}let s,r;if(t.isMesh?s=t.geometry:(t.isBufferGeometry||t.isTexture)&&(s=t),s&&s?.userData?.LODS){const o=s.userData.LODS;if(r=this.lodInfos.get(o.key),e===void 0)return r!=null;if(r)return Array.isArray(r.lods)?e<r.lods.length:e===0}return!1}static assignMeshLOD(t,e){if(!t)return Promise.resolve(null);if(t instanceof c.Mesh||t.isMesh===!0){const s=t.geometry,r=this.getAssignedLODInformation(s);if(!r)return Promise.resolve(null);for(const o of W)o.onBeforeGetLODMesh?.(t,e);return t["LOD:requested level"]=e,m.getOrLoadLOD(s,e).then(o=>{if(Array.isArray(o)){const n=r.index||0;o=o[n]}return t["LOD:requested level"]===e&&(delete t["LOD:requested level"],o&&s!=o&&(o?.isBufferGeometry?t.geometry=o:h&&console.error("Invalid LOD geometry",o))),o}).catch(o=>(console.error("Error loading mesh LOD",t,o),null))}else h&&console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh",t);return Promise.resolve(null)}static assignTextureLOD(t,e=0){if(!t)return Promise.resolve(null);if(t.isMesh===!0){const s=t;if(Array.isArray(s.material)){const r=new Array;for(const o of s.material){const n=this.assignTextureLOD(o,e);r.push(n)}return Promise.all(r).then(o=>{const n=new Array;for(const l of o)Array.isArray(l)&&n.push(...l);return n})}else return this.assignTextureLOD(s.material,e)}if(t.isMaterial===!0){const s=t,r=[],o=new Array;if(s.uniforms&&(s.isRawShaderMaterial||s.isShaderMaterial===!0)){const n=s;for(const l of Object.keys(n.uniforms)){const a=n.uniforms[l].value;if(a?.isTexture===!0){const u=this.assignTextureLODForSlot(a,e,s,l).then(d=>(d&&n.uniforms[l].value!=d&&(n.uniforms[l].value=d,n.uniformsNeedUpdate=!0),d));r.push(u),o.push(l)}}}else for(const n of Object.keys(s)){const l=s[n];if(l?.isTexture===!0){const a=this.assignTextureLODForSlot(l,e,s,n);r.push(a),o.push(n)}}return Promise.all(r).then(n=>{const l=new Array;for(let a=0;a<n.length;a++){const u=n[a],d=o[a];u&&u.isTexture===!0?l.push({material:s,slot:d,texture:u,level:e}):l.push({material:s,slot:d,texture:null,level:e})}return l})}if(t instanceof c.Texture||t.isTexture===!0){const s=t;return this.assignTextureLODForSlot(s,e,null,null)}return Promise.resolve(null)}static set maxConcurrentLoadingTasks(t){m.queue.maxConcurrent=t}static get maxConcurrentLoadingTasks(){return m.queue.maxConcurrent}static assignTextureLODForSlot(t,e,s,r){return t?.isTexture!==!0?Promise.resolve(null):r==="glyphMap"?Promise.resolve(t):m.getOrLoadLOD(t,e).then(o=>{if(Array.isArray(o))return console.warn("Progressive: Got an array of textures for a texture slot, this should not happen..."),null;if(o?.isTexture===!0){if(o!=t&&s&&r){const n=s[r];if(n&&!h){const l=this.getAssignedLODInformation(n);if(l&&l?.level<e)return h==="verbose"&&console.warn("Assigned texture level is already higher: ",l.level,e,s,n,o),o&&o!==n&&((h||E)&&console.log(`[gltf-progressive] Disposing rejected lower-quality texture LOD ${e} (assigned is ${l.level})`,o.uuid),o.dispose()),null}if(this.trackTextureUsage(o),n&&n!==o&&this.untrackTextureUsage(n)&&(h||E)){const a=this.getAssignedLODInformation(n);console.log(`[gltf-progressive] Disposed old texture LOD ${a?.level??"?"} → ${e} for ${s.name||s.type}.${r}`,n.uuid)}s[r]=o}return o}else h=="verbose"&&console.warn("No LOD found for",t,e);return null}).catch(o=>(console.error("Error loading LOD",t,o),null))}parser;url;constructor(t){const e=t.options.path;h&&console.log("Progressive extension registered for",e),this.parser=t,this.url=e}_isLoadingMesh;loadMesh=t=>{if(this._isLoadingMesh)return null;const e=this.parser.json.meshes[t]?.extensions?.[F];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};afterRoot(t){return h&&console.log("AFTER",this.url,t),this.parser.json.textures?.forEach((e,s)=>{if(e?.extensions){const r=e?.extensions[F];if(r){if(!r.lods){h&&console.warn("Texture has no LODs",r);return}let o=!1;for(const n of this.parser.associations.keys())n.isTexture===!0&&this.parser.associations.get(n)?.textures===s&&(o=!0,m.registerTexture(this.url,n,r.lods?.length,s,r));o||this.parser.getDependency("texture",s).then(n=>{n&&m.registerTexture(this.url,n,r.lods?.length,s,r)})}}}),this.parser.json.meshes?.forEach((e,s)=>{if(e?.extensions){const r=e?.extensions[F];if(r&&r.lods){for(const o of this.parser.associations.keys())if(o.isMesh){const n=this.parser.associations.get(o);n?.meshes===s&&m.registerMesh(this.url,r.guid,o,r.lods.length,n.primitives,r)}}}}),null}static registerTexture=(t,e,s,r,o)=>{if(!e){h&&console.error("!! gltf-progressive: Called register texture without texture");return}if(h){const l=e.image?.width||e.source?.data?.width||0,a=e.image?.height||e.source?.data?.height||0;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)}e.source&&(e.source[ce]=o);const n=o.guid;m.assignLODInformation(t,e,n,s,r),m.lodInfos.set(n,o),m.lowresCache.set(n,new WeakRef(e))};static registerMesh=(t,e,s,r,o,n)=>{const l=s.geometry;if(!l){h&&console.warn("gltf-progressive: Register mesh without geometry");return}l.userData||(l.userData={}),h&&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);let u=m.lowresCache.get(e)?.deref();u?u.push(s.geometry):u=[s.geometry],m.lowresCache.set(e,new WeakRef(u)),r>0&&!j(s)&&Xe(s,l);for(const d of W)d.onRegisteredNewMesh?.(s,n)};static dispose(t){if(t){this.lodInfos.delete(t);const e=this.lowresCache.get(t);if(e){const s=e.deref();if(s){if(s.isTexture){const r=s;this.textureRefCounts.delete(r.uuid),r.dispose()}else if(Array.isArray(s))for(const r of s)r.dispose()}this.lowresCache.delete(t)}for(const[s,r]of this.cache)s.includes(t)&&(this._disposeCacheEntry(r),this.cache.delete(s))}else{this.lodInfos.clear();for(const[,e]of this.lowresCache){const s=e.deref();if(s){if(s.isTexture){const r=s;this.textureRefCounts.delete(r.uuid),r.dispose()}else if(Array.isArray(s))for(const r of s)r.dispose()}}this.lowresCache.clear();for(const[,e]of this.cache)this._disposeCacheEntry(e);this.cache.clear(),this.textureRefCounts.clear()}}static _disposeCacheEntry(t){if(t instanceof WeakRef){const e=t.deref();e&&(e.isTexture&&this.textureRefCounts.delete(e.uuid),e.dispose())}else t.then(e=>{if(e)if(Array.isArray(e))for(const s of e)s.dispose();else e.isTexture&&this.textureRefCounts.delete(e.uuid),e.dispose()}).catch(()=>{})}static lodInfos=new Map;static cache=new Map;static lowresCache=new Map;static textureRefCounts=new Map;static _resourceRegistry=new FinalizationRegistry(t=>{const e=m.cache.get(t);(h||E)&&console.debug(`[gltf-progressive] Memory: Resource GC'd
2
- ${t}`),e instanceof WeakRef&&(e.deref()||(m.cache.delete(t),(h||E)&&console.log("[gltf-progressive] ↪ Cache entry deleted (GC)")))});static trackTextureUsage(t){const e=t.uuid,s=this.textureRefCounts.get(e)||0;this.textureRefCounts.set(e,s+1),h==="verbose"&&console.log(`[gltf-progressive] Track texture ${e}, refCount: ${s} → ${s+1}`)}static untrackTextureUsage(t){const e=t.uuid,s=this.textureRefCounts.get(e);if(!s)return(h==="verbose"||E)&&o("[gltf-progressive] Memory: Untrack untracked texture (dispose immediately)",0),t.dispose(),!0;const r=s-1;if(r<=0)return this.textureRefCounts.delete(e),(h||E)&&o("[gltf-progressive] Memory: Dispose texture",r),t.dispose(),!0;return this.textureRefCounts.set(e,r),h==="verbose"&&o("[gltf-progressive] Memory: Untrack texture",r),!1;function o(n,l){let a=t.image?.width||t.source?.data?.width||0,u=t.image?.height||t.source?.data?.height||0;const d=a&&u?`${a}x${u}`:"N/A";let x="N/A";a&&u&&(x=`~${(qe(t)/(1024*1024)).toFixed(2)} MB`),console.log(`${n} — ${t.name} ${d} (${x}), refCount: ${s} → ${l}
3
- ${e}`)}}static workers=[];static _workersIndex=0;static async getOrLoadLOD(t,e){const s=h=="verbose",r=this.getAssignedLODInformation(t);if(!r)return h&&console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`,t),null;const o=r?.key;let n;if(t.isTexture===!0){const a=t;a.source&&a.source[ce]&&(n=a.source[ce])}if(n||(n=m.lodInfos.get(o)),!n)h&&console.warn(`Can not load LOD ${e}: no LOD info found for "${o}" ${t.name}`,t.type,m.lodInfos);else{if(e>0){let d=!1;const x=Array.isArray(n.lods);if(x&&e>=n.lods.length?d=!0:x||(d=!0),d){const L=this.lowresCache.get(o);if(L){const _=L.deref();if(_)return _;this.lowresCache.delete(o),h&&console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${o}`)}return null}}const a=Array.isArray(n.lods)?n.lods[e]?.path:n.lods;if(!a)return h&&!n["missing:uri"]&&(n["missing:uri"]=!0,console.warn("Missing uri for progressive asset for LOD "+e,n)),null;const u=ze(r.url,a);if(u.endsWith(".glb")||u.endsWith(".gltf")){if(!n.guid)return console.warn("missing pointer for glb/gltf texture",n),null;const d=u+"_"+n.guid,x=await this.queue.slot(u),L=this.cache.get(d);if(L!==void 0)if(s&&console.log(`LOD ${e} was already loading/loaded: ${d}`),L instanceof WeakRef){const f=L.deref();if(f){let p=f,v=!1;if(p instanceof c.Texture&&t instanceof c.Texture?p.image?.data||p.source?.data?p=this.copySettings(t,p):v=!0:p instanceof c.BufferGeometry&&t instanceof c.BufferGeometry&&(p.attributes.position?.array||(v=!0)),!v)return p}this.cache.delete(d),h&&console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${d}`)}else{let f=await L.catch(v=>(console.error(`Error loading LOD ${e} from ${u}
4
- `,v),null)),p=!1;if(f==null||(f instanceof c.Texture&&t instanceof c.Texture?f.image?.data||f.source?.data?f=this.copySettings(t,f):(p=!0,this.cache.delete(d)):f instanceof c.BufferGeometry&&t instanceof c.BufferGeometry&&(f.attributes.position?.array||(p=!0,this.cache.delete(d)))),!p)return f}if(!x.use)return h&&console.log(`LOD ${e} was aborted: ${u}`),null;const _=n,k=new Promise(async(f,p)=>{if(Ze){const w=await(await Qe({})).load(u);if(w.textures.length>0)for(const g of w.textures){let y=g.texture;return m.assignLODInformation(r.url,y,o,e,void 0),t instanceof c.Texture&&(y=this.copySettings(t,y)),y&&(y.guid=_.guid),f(y)}if(w.geometries.length>0){const g=new Array;for(const y of w.geometries){const O=y.geometry;m.assignLODInformation(r.url,O,o,e,y.primitiveIndex),g.push(O)}return f(g)}return f(null)}const v=new X.GLTFLoader;xe(v),h&&(await new Promise(M=>setTimeout(M,1e3)),s&&console.warn("Start loading (delayed) "+u,_.guid));let B=u;if(_&&Array.isArray(_.lods)){const M=_.lods[e];M.hash&&(B+="?v="+M.hash)}const b=await v.loadAsync(B).catch(M=>(console.error(`Error loading LOD ${e} from ${u}
5
- `,M),f(null)));if(!b)return f(null);const V=b.parser;s&&console.log("Loading finished "+u,_.guid);let P=0;if(b.parser.json.textures){let M=!1;for(const w of b.parser.json.textures){if(w?.extensions){const g=w?.extensions[F];if(g?.guid&&g.guid===_.guid){M=!0;break}}P++}if(M){let w=await V.getDependency("texture",P);return w&&m.assignLODInformation(r.url,w,o,e,void 0),s&&console.log('change "'+t.name+'" → "'+w.name+'"',u,P,w,d),t instanceof c.Texture&&(w=this.copySettings(t,w)),w&&(w.guid=_.guid),f(w)}else h&&console.warn("Could not find texture with guid",_.guid,b.parser.json)}if(P=0,b.parser.json.meshes){let M=!1;for(const w of b.parser.json.meshes){if(w?.extensions){const g=w?.extensions[F];if(g?.guid&&g.guid===_.guid){M=!0;break}}P++}if(M){const w=await V.getDependency("mesh",P);if(s&&console.log(`Loaded Mesh "${w.name}"`,u,P,w,d),w.isMesh===!0){const g=w.geometry;return m.assignLODInformation(r.url,g,o,e,0),f(g)}else{const g=new Array;for(let y=0;y<w.children.length;y++){const O=w.children[y];if(O.isMesh===!0){const S=O.geometry;m.assignLODInformation(r.url,S,o,e,y),g.push(S)}}return f(g)}}else h&&console.warn("Could not find mesh with guid",_.guid,b.parser.json)}return f(null)});this.cache.set(d,k),x.use(k);const D=await k;return D!=null?D instanceof c.Texture?(this.cache.set(d,new WeakRef(D)),m._resourceRegistry.register(D,d)):Array.isArray(D)?this.cache.set(d,Promise.resolve(D)):this.cache.set(d,Promise.resolve(D)):this.cache.set(d,Promise.resolve(null)),D}else if(t instanceof c.Texture){s&&console.log("Load texture from uri: "+u);const x=await new c.TextureLoader().loadAsync(u);return x?(x.guid=n.guid,x.flipY=!1,x.needsUpdate=!0,x.colorSpace=t.colorSpace,s&&console.log(n,x)):h&&console.warn("failed loading",u),x}}return null}static queue=new Ve(Le()?20:50,{debug:h!=!1});static assignLODInformation(t,e,s,r,o){if(!e)return;e.userData||(e.userData={});const n=new et(t,s,r,o);e.userData.LODS=n,"source"in e&&typeof e.source=="object"&&(e.source.LODS=n)}static getAssignedLODInformation(t){return t?t.userData?.LODS?t.userData.LODS:"source"in t&&t.source?.LODS?t.source.LODS:null:null}static copySettings(t,e){return e?(h==="verbose"&&console.debug(`Copy texture settings
6
- `,t.uuid,`
7
- `,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}}class et{url;key;level;index;constructor(t,e,s,r){this.url=t,this.key=e,this.level=s,r!=null&&(this.index=r)}}class de{static addPromise=(t,e,s,r)=>{r.forEach(o=>{o.add(t,e,s)})};ready;get awaitedCount(){return this._addedCount}get resolvedCount(){return this._resolvedCount}get currentlyAwaiting(){return this._awaiting.length}_resolve;_signal;_frame_start;_frames_to_capture;_resolved=!1;_addedCount=0;_resolvedCount=0;_awaiting=[];_maxPromisesPerObject=1;constructor(t,e){const r=Math.max(e.frames??2,2);this._frame_start=e.waitForFirstCapture?void 0:t,this._frames_to_capture=r,this.ready=new Promise(o=>{this._resolve=o}),this.ready.finally(()=>{this._resolved=!0,this._awaiting.length=0}),this._signal=e.signal,this._signal?.addEventListener("abort",()=>{this.resolveNow()}),this._maxPromisesPerObject=Math.max(1,e.maxPromisesPerObject??1)}_currentFrame=0;update(t){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()}_seen=new WeakMap;add(t,e,s){if(this._resolved){h&&console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");return}if(!(this._frame_start!==void 0&&this._currentFrame>this._frame_start+this._frames_to_capture)){if(this._maxPromisesPerObject>=1)if(this._seen.has(e)){let r=this._seen.get(e);if(r>=this._maxPromisesPerObject){h&&console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");return}this._seen.set(e,r+1)}else this._seen.set(e,1);this._awaiting.push(s),this._addedCount++,s.finally(()=>{this._resolvedCount++,this._awaiting.splice(this._awaiting.indexOf(s),1)})}}resolveNow(){this._resolved||this._resolve?.({awaited_count:this._addedCount,resolved_count:this._resolvedCount,cancelled:this._signal?.aborted??!1})}}const R=z("debugprogressive"),tt=z("noprogressive"),fe=Symbol("Needle:LODSManager"),he=Symbol("Needle:LODState"),U=Symbol("Needle:CurrentLOD"),C={mesh_lod:-1,texture_lod:-1};let Z=class T{static debugDrawLine;static getObjectLODState(t){return t[he]}static addPlugin(t){W.push(t)}static removePlugin(t){const e=W.indexOf(t);e>=0&&W.splice(e,1)}static get(t,e){if(t[fe])return console.debug("[gltf-progressive] LODsManager already exists for this renderer"),t[fe];const s=new T(t,{engine:"unknown",...e});return t[fe]=s,s}renderer;context;projectionScreenMatrix=new c.Matrix4;get plugins(){return W}overrideLodLevel=void 0;targetTriangleDensity=2e5;skinnedMeshAutoUpdateBoundsInterval=30;updateInterval="auto";#e=1;pause=!1;manual=!1;_newPromiseGroups=[];_promiseGroupIds=0;awaitLoading(t){const e=this._promiseGroupIds++,s=new de(this.#r,{...t});this._newPromiseGroups.push(s);const r=performance.now();return s.ready.finally(()=>{const o=this._newPromiseGroups.indexOf(s);o>=0&&(this._newPromiseGroups.splice(o,1),Me()&&performance.measure("LODsManager:awaitLoading",{start:r,detail:{id:e,name:t?.name,awaited:s.awaitedCount,resolved:s.resolvedCount}}))}),s.ready}_postprocessPromiseGroups(){if(this._newPromiseGroups.length!==0)for(let t=this._newPromiseGroups.length-1;t>=0;t--)this._newPromiseGroups[t].update(this.#r)}_lodchangedlisteners=[];addEventListener(t,e){t==="changed"&&this._lodchangedlisteners.push(e)}removeEventListener(t,e){if(t==="changed"){const s=this._lodchangedlisteners.indexOf(e);s>=0&&this._lodchangedlisteners.splice(s,1)}}constructor(t,e){this.renderer=t,this.context={...e}}#t;#n=new c.Clock;#r=0;#o=0;#i=0;#s=0;_fpsBuffer=[60,60,60,60,60];enable(){if(this.#t)return;console.debug("[gltf-progressive] Enabling LODsManager for renderer");let t=0;this.#t=this.renderer.render;const e=this;ne(this.renderer),this.renderer.render=function(s,r){const o=e.renderer.getRenderTarget();(o==null||"isXRRenderTarget"in o&&o.isXRRenderTarget)&&(t=0,e.#r+=1,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,R&&e.#r%200===0&&console.log("FPS",Math.round(e.#s),"Interval:",e.#e));const n=t++;e.#t.call(this,s,r),e.onAfterRender(s,r,n)}}disable(){this.#t&&(console.debug("[gltf-progressive] Disabling LODsManager for renderer"),this.renderer.render=this.#t,this.#t=void 0)}update(t,e){this.internalUpdate(t,e)}onAfterRender(t,e,s){if(this.pause)return;const o=this.renderer.renderLists.get(t,0).opaque;let n=!0;if(o.length===1){const l=o[0].material;(l.name==="EffectMaterial"||l.name==="CopyShader")&&(n=!1)}if((e.parent&&e.parent.type==="CubeCamera"||s>=1&&e.type==="OrthographicCamera")&&(n=!1),n){if(tt||(this.updateInterval==="auto"?this.#s<40&&this.#e<10?(this.#e+=1,R&&console.warn("↓ Reducing LOD updates",this.#e,this.#s.toFixed(0))):this.#s>=60&&this.#e>1&&(this.#e-=1,R&&console.warn("↑ Increasing LOD updates",this.#e,this.#s.toFixed(0))):this.#e=this.updateInterval,this.#e>0&&this.#r%this.#e!=0))return;this.internalUpdate(t,e),this._postprocessPromiseGroups()}}internalUpdate(t,e){const s=this.renderer.renderLists.get(t,0),r=s.opaque;this.projectionScreenMatrix.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse);const o=this.targetTriangleDensity;for(const a of r){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")){R&&(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)));continue}switch(a.material.type){case"LineBasicMaterial":case"LineDashedMaterial":case"PointsMaterial":case"ShadowMaterial":case"MeshDistanceMaterial":case"MeshDepthMaterial":continue}if(R==="color"&&a.material&&!a.object.progressive_debug_color){a.object.progressive_debug_color=!0;const d=Math.random()*16777215,x=new c.MeshStandardMaterial({color:d});a.object.material=x}const u=a.object;(u instanceof c.Mesh||u.isMesh)&&this.updateLODs(t,e,u,o)}const n=s.transparent;for(const a of n){const u=a.object;(u instanceof c.Mesh||u.isMesh)&&this.updateLODs(t,e,u,o)}const l=s.transmissive;for(const a of l){const u=a.object;(u instanceof c.Mesh||u.isMesh)&&this.updateLODs(t,e,u,o)}}updateLODs(t,e,s,r){s.userData||(s.userData={});let o=s[he];if(o||(o=new st,s[he]=o),o.frames++<2)return;for(const l of W)l.onBeforeUpdateLOD?.(this.renderer,t,e,s);const n=this.overrideLodLevel!==void 0?this.overrideLodLevel:q;n>=0?(C.mesh_lod=n,C.texture_lod=n):(this.calculateLodLevel(e,s,o,r,C),C.mesh_lod=Math.round(C.mesh_lod),C.texture_lod=Math.round(C.texture_lod)),C.mesh_lod>=0&&this.loadProgressiveMeshes(s,C.mesh_lod),s.material&&C.texture_lod>=0&&this.loadProgressiveTextures(s.material,C.texture_lod,n),h&&s.material&&!s.isGizmo&&Ce(s.material);for(const l of W)l.onAfterUpdatedLOD?.(this.renderer,t,e,s,C);o.lastLodLevel_Mesh=C.mesh_lod,o.lastLodLevel_Texture=C.texture_lod}loadProgressiveTextures(t,e,s){if(!t)return;if(Array.isArray(t)){for(const o of t)this.loadProgressiveTextures(o,e);return}let r=!1;if((t[U]===void 0||e<t[U])&&(r=!0),s!==void 0&&s>=0&&(r=t[U]!=s,e=s),r){t[U]=e;const o=m.assignTextureLOD(t,e).then(n=>{this._lodchangedlisteners.forEach(l=>l({type:"texture",level:e,object:t}))});de.addPromise("texture",t,o,this._newPromiseGroups)}}loadProgressiveMeshes(t,e){if(!t)return Promise.resolve(null);let s=t[U]!==e;const r=t["DEBUG:LOD"];if(r!=null&&(s=t[U]!=r,e=r),s){t[U]=e;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));return de.addPromise("mesh",t,n,this._newPromiseGroups),n}return Promise.resolve(null)}_sphere=new c.Sphere;_tempBox=new c.Box3;_tempBox2=new c.Box3;tempMatrix=new c.Matrix4;_tempWorldPosition=new c.Vector3;_tempBoxSize=new c.Vector3;_tempBox2Size=new c.Vector3;static corner0=new c.Vector3;static corner1=new c.Vector3;static corner2=new c.Vector3;static corner3=new c.Vector3;static _tempPtInside=new c.Vector3;static isInside(t,e){const s=t.min,r=t.max,o=(s.x+r.x)*.5,n=(s.y+r.y)*.5;return this._tempPtInside.set(o,n,s.z).applyMatrix4(e).z<0}static skinnedMeshBoundsFrameOffsetCounter=0;static $skinnedMeshBoundsOffset=Symbol("gltf-progressive-skinnedMeshBoundsOffset");calculateLodLevel(t,e,s,r,o){if(!e){o.mesh_lod=-1,o.texture_lod=-1;return}if(!t){o.mesh_lod=-1,o.texture_lod=-1;return}let l=10+1,a=!1;if(R&&e["DEBUG:LOD"]!=null)return e["DEBUG:LOD"];const u=m.getMeshLODExtension(e.geometry)?.lods,d=m.getPrimitiveIndex(e.geometry),x=u&&u.length>0,L=m.getMaterialMinMaxLODsCount(e.material),_=L.min_count!==1/0&&L.min_count>=0&&L.max_count>=0;if(!x&&!_){o.mesh_lod=0,o.texture_lod=0;return}x||(a=!0,l=0);const k=this.renderer.domElement.clientHeight||this.renderer.domElement.height;let D=e.geometry.boundingBox;if(e.type==="SkinnedMesh"){const f=e;if(!f.boundingBox)f.computeBoundingBox();else if(this.skinnedMeshAutoUpdateBoundsInterval>0){if(!f[T.$skinnedMeshBoundsOffset]){const v=T.skinnedMeshBoundsFrameOffsetCounter++;f[T.$skinnedMeshBoundsOffset]=v}const p=f[T.$skinnedMeshBoundsOffset];if((s.frames+p)%this.skinnedMeshAutoUpdateBoundsInterval===0){const v=j(f),B=f.geometry;v&&(f.geometry=v),f.computeBoundingBox(),f.geometry=B}}D=f.boundingBox}if(D){const f=t;if(e.geometry.attributes.color&&e.geometry.attributes.color.count<100&&e.geometry.boundingSphere){this._sphere.copy(e.geometry.boundingSphere),this._sphere.applyMatrix4(e.matrixWorld);const g=t.getWorldPosition(this._tempWorldPosition);if(this._sphere.containsPoint(g)){o.mesh_lod=0,o.texture_lod=0;return}}if(this._tempBox.copy(D),this._tempBox.applyMatrix4(e.matrixWorld),f.isPerspectiveCamera&&T.isInside(this._tempBox,this.projectionScreenMatrix)){o.mesh_lod=0,o.texture_lod=0;return}if(this._tempBox.applyMatrix4(this.projectionScreenMatrix),this.renderer.xr.enabled&&f.isPerspectiveCamera&&f.fov>70){const g=this._tempBox.min,y=this._tempBox.max;let O=g.x,S=g.y,G=y.x,K=y.y;const ee=2,ie=1.5,te=(g.x+y.x)*.5,se=(g.y+y.y)*.5;O=(O-te)*ee+te,S=(S-se)*ee+se,G=(G-te)*ee+te,K=(K-se)*ee+se;const Re=O<0&&G>0?0:Math.min(Math.abs(g.x),Math.abs(y.x)),Ie=S<0&&K>0?0:Math.min(Math.abs(g.y),Math.abs(y.y)),ae=Math.max(Re,Ie);s.lastCentrality=(ie-ae)*(ie-ae)*(ie-ae)}else s.lastCentrality=1;const p=this._tempBox.getSize(this._tempBoxSize);p.multiplyScalar(.5),screen.availHeight>0&&k>0&&p.multiplyScalar(k/screen.availHeight),t.isPerspectiveCamera?p.x*=t.aspect:t.isOrthographicCamera;const v=t.matrixWorldInverse,B=this._tempBox2;B.copy(D),B.applyMatrix4(e.matrixWorld),B.applyMatrix4(v);const b=B.getSize(this._tempBox2Size),V=Math.max(b.x,b.y);if(Math.max(p.x,p.y)!=0&&V!=0&&(p.z=b.z/Math.max(b.x,b.y)*Math.max(p.x,p.y)),s.lastScreenCoverage=Math.max(p.x,p.y,p.z),s.lastScreenspaceVolume.copy(p),s.lastScreenCoverage*=s.lastCentrality,R&&T.debugDrawLine){const g=this.tempMatrix.copy(this.projectionScreenMatrix);g.invert();const y=T.corner0,O=T.corner1,S=T.corner2,G=T.corner3;y.copy(this._tempBox.min),O.copy(this._tempBox.max),O.x=y.x,S.copy(this._tempBox.max),S.y=y.y,G.copy(this._tempBox.max);const K=(y.z+G.z)*.5;y.z=O.z=S.z=G.z=K,y.applyMatrix4(g),O.applyMatrix4(g),S.applyMatrix4(g),G.applyMatrix4(g),T.debugDrawLine(y,O,255),T.debugDrawLine(y,S,255),T.debugDrawLine(O,G,255),T.debugDrawLine(S,G,255)}let M=999;if(u&&s.lastScreenCoverage>0)for(let g=0;g<u.length;g++){const y=u[g],S=(y.densities?.[d]||y.density||1e-5)/s.lastScreenCoverage;if(d>0&&Me()&&!y.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){M=g;break}}M<l&&(l=M,a=!0)}if(a?o.mesh_lod=l:o.mesh_lod=s.lastLodLevel_Mesh,R&&o.mesh_lod!=s.lastLodLevel_Mesh){const p=u?.[o.mesh_lod];p&&console.log(`Mesh LOD changed: ${s.lastLodLevel_Mesh} → ${o.mesh_lod} (density: ${p.densities?.[d].toFixed(0)}) | ${e.name}`)}if(_){const f="saveData"in globalThis.navigator&&globalThis.navigator.saveData===!0;if(s.lastLodLevel_Texture<0){if(o.texture_lod=L.max_count-1,R){const p=L.lods[L.max_count-1];R&&console.log(`First Texture LOD ${o.texture_lod} (${p.max_height}px) - ${e.name}`)}}else{const p=s.lastScreenspaceVolume.x+s.lastScreenspaceVolume.y+s.lastScreenspaceVolume.z;let v=s.lastScreenCoverage*4;this.context?.engine==="model-viewer"&&(v*=1.5);const b=k/window.devicePixelRatio*v;let V=!1;for(let P=L.lods.length-1;P>=0;P--){const M=L.lods[P];if(!(f&&M.max_height>=2048)&&!(Le()&&M.max_height>4096)&&(M.max_height>b||!V&&P===0)){if(V=!0,o.texture_lod=P,R&&o.texture_lod<s.lastLodLevel_Texture){const w=M.max_height;console.log(`Texture LOD changed: ${s.lastLodLevel_Texture} → ${o.texture_lod} = ${w}px
8
- Screensize: ${b.toFixed(0)}px, Coverage: ${(100*s.lastScreenCoverage).toFixed(2)}%, Volume ${p.toFixed(1)}
9
- ${e.name}`)}break}}}}else o.texture_lod=0}};class st{frames=0;lastLodLevel_Mesh=-1;lastLodLevel_Texture=-1;lastScreenCoverage=0;lastScreenspaceVolume=new c.Vector3;lastCentrality=0}const ve=Symbol("NEEDLE_mesh_lod"),re=Symbol("NEEDLE_texture_lod");let ge=null;function Ae(){const i=rt();i&&(i.mapURLs(function(t){return De(),t}),De(),ge?.disconnect(),ge=new MutationObserver(t=>{t.forEach(e=>{e.addedNodes.forEach(s=>{s instanceof HTMLElement&&s.tagName.toLowerCase()==="model-viewer"&&ke(s)})})}),ge.observe(document,{childList:!0,subtree:!0}))}function rt(){if(typeof customElements>"u")return null;const i=customElements.get("model-viewer");return i||(customElements.whenDefined("model-viewer").then(()=>{console.debug("[gltf-progressive] model-viewer defined"),Ae()}),null)}function De(){if(typeof document>"u")return;document.querySelectorAll("model-viewer").forEach(t=>{ke(t)})}const be=new WeakSet;let ot=0;function ke(i){if(!i||be.has(i))return null;be.add(i),console.debug("[gltf-progressive] found new model-viewer..."+ ++ot+`
10
- `,i.getAttribute("src"));let t=null,e=null,s=null;for(let r=i;r!=null;r=Object.getPrototypeOf(r)){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)");!t&&n!=null&&(t=i[n].threeRenderer),!e&&l!=null&&(e=i[l]),!s&&a!=null&&(s=i[a])}if(t&&e){let r=function(){if(s){let n=0,l=setInterval(()=>{if(n++>5){clearInterval(l);return}s?.call(i)},300)}};console.debug("[gltf-progressive] setup model-viewer");const o=Z.get(t,{engine:"model-viewer"});return Z.addPlugin(new nt),o.enable(),o.addEventListener("changed",()=>{s?.call(i)}),i.addEventListener("model-visibility",n=>{n.detail.visible&&s?.call(i)}),i.addEventListener("load",()=>{r()}),()=>{o.disable()}}return null}class nt{_didWarnAboutMissingUrl=!1;onBeforeUpdateLOD(t,e,s,r){this.tryParseMeshLOD(e,r),this.tryParseTextureLOD(e,r)}getUrl(t){if(!t)return null;let e=t.getAttribute("src");return e||(e=t.src),e||(this._didWarnAboutMissingUrl||console.warn("No url found in modelviewer",t),this._didWarnAboutMissingUrl=!0),e}tryGetCurrentGLTF(t){return t._currentGLTF}tryGetCurrentModelViewer(t){return t.element}tryParseTextureLOD(t,e){if(e[re]==!0)return;e[re]=!0;const s=this.tryGetCurrentGLTF(t),r=this.tryGetCurrentModelViewer(t),o=this.getUrl(r);if(o&&s&&e.material){let n=function(a){if(a[re]==!0)return;a[re]=!0,a.userData&&(a.userData.LOD=-1);const u=Object.keys(a);for(let d=0;d<u.length;d++){const x=u[d],L=a[x];if(L?.isTexture===!0){const _=L.userData?.associations?.textures;if(_==null)continue;const k=s.parser.json.textures[_];if(!k){console.warn("Texture data not found for texture index "+_);continue}if(k?.extensions?.[F]){const D=k.extensions[F];D&&o&&m.registerTexture(o,L,D.lods.length,_,D)}}}};const l=e.material;if(Array.isArray(l))for(const a of l)n(a);else n(l)}}tryParseMeshLOD(t,e){if(e[ve]==!0)return;e[ve]=!0;const s=this.tryGetCurrentModelViewer(t),r=this.getUrl(s);if(!r)return;const o=e.userData?.gltfExtensions?.[F];if(o&&r){const n=e.uuid;m.registerMesh(r,n,e,0,o.lods.length,o)}}}function it(...i){let t,e,s,r;switch(i.length){case 2:[s,e]=i,r={};break;case 3:[s,e,r]=i;break;case 4:[t,e,s,r]=i;break;default:throw new Error("Invalid arguments")}ne(e),xe(s),we(s,{progressive:!0,...r?.hints}),s.register(n=>new m(n));const o=Z.get(e);return r?.enableLODsManager!==!1&&o.enable(),o}Ae();if(!Ee){const i={gltfProgressive:{useNeedleProgressive:it,LODsManager:Z,configureLoader:we,getRaycastMesh:j,useRaycastMeshes:je}};if(!globalThis.Needle)globalThis.Needle=i;else for(const t in i)globalThis.Needle[t]=i[t]}exports.LODsManager=Z;exports.NEEDLE_progressive=m;exports.addDracoAndKTX2Loaders=xe;exports.configureLoader=we;exports.createLoaders=ne;exports.getRaycastMesh=j;exports.setDracoDecoderLocation=Te;exports.setKTX2TranscoderLocation=Se;