@needle-tools/engine 5.1.0-alpha.9 → 5.1.0-beta.1

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 (69) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/gltf-progressive-BGQ-cjly.umd.cjs +10 -0
  3. package/dist/gltf-progressive-BvQHrERj.min.js +10 -0
  4. package/dist/{gltf-progressive-CH7aqHHr.js → gltf-progressive-CtYnVfLc.js} +420 -351
  5. package/dist/{needle-engine.bundle-mtqHZ45d.js → needle-engine.bundle-CFo3xWPu.js} +3686 -3627
  6. package/dist/{needle-engine.bundle-DagaNrZK.umd.cjs → needle-engine.bundle-CWiO9cA8.umd.cjs} +133 -133
  7. package/dist/{needle-engine.bundle-BotdnwVF.min.js → needle-engine.bundle-DxJaiP7e.min.js} +139 -139
  8. package/dist/needle-engine.d.ts +67 -26
  9. package/dist/needle-engine.js +602 -600
  10. package/dist/needle-engine.min.js +1 -1
  11. package/dist/needle-engine.umd.cjs +1 -1
  12. package/dist/three.js +3 -0
  13. package/dist/three.min.js +22 -22
  14. package/dist/three.umd.cjs +24 -24
  15. package/lib/engine/api.d.ts +1 -1
  16. package/lib/engine/api.js +1 -1
  17. package/lib/engine/api.js.map +1 -1
  18. package/lib/engine/engine_init.js +2 -2
  19. package/lib/engine/engine_license.d.ts +7 -7
  20. package/lib/engine/engine_license.js +72 -72
  21. package/lib/engine/engine_license.js.map +1 -1
  22. package/lib/engine/engine_networking_blob.js +3 -3
  23. package/lib/engine/engine_networking_blob.js.map +1 -1
  24. package/lib/engine/engine_utils_qrcode.js +2 -2
  25. package/lib/engine/engine_utils_qrcode.js.map +1 -1
  26. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  27. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  28. package/lib/engine/webcomponents/needle menu/needle-menu.js +5 -5
  29. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  30. package/lib/engine/webcomponents/needle-engine.js +2 -2
  31. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  32. package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
  33. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  34. package/lib/engine/xr/TempXRContext.js +2 -2
  35. package/lib/engine/xr/TempXRContext.js.map +1 -1
  36. package/lib/engine-components/Component.js +2 -1
  37. package/lib/engine-components/Component.js.map +1 -1
  38. package/lib/engine-components/SceneSwitcher.d.ts +2 -0
  39. package/lib/engine-components/SceneSwitcher.js +26 -14
  40. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  41. package/lib/engine-components/SeeThrough.d.ts +39 -0
  42. package/lib/engine-components/SeeThrough.js +69 -1
  43. package/lib/engine-components/SeeThrough.js.map +1 -1
  44. package/lib/engine-components/api.d.ts +1 -0
  45. package/lib/engine-components/api.js +1 -0
  46. package/lib/engine-components/api.js.map +1 -1
  47. package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
  48. package/lib/engine-components/export/usdz/USDZExporter.js.map +1 -1
  49. package/package.json +2 -2
  50. package/plugins/common/license.js +4 -4
  51. package/plugins/vite/license.js +4 -4
  52. package/src/engine/api.ts +1 -1
  53. package/src/engine/engine_gameobject.ts +1 -1
  54. package/src/engine/engine_init.ts +2 -2
  55. package/src/engine/engine_license.ts +69 -69
  56. package/src/engine/engine_networking_blob.ts +3 -3
  57. package/src/engine/engine_utils_qrcode.ts +2 -2
  58. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
  59. package/src/engine/webcomponents/needle menu/needle-menu.ts +5 -5
  60. package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
  61. package/src/engine/webcomponents/needle-engine.ts +2 -2
  62. package/src/engine/xr/TempXRContext.ts +2 -2
  63. package/src/engine-components/Component.ts +2 -1
  64. package/src/engine-components/SceneSwitcher.ts +23 -13
  65. package/src/engine-components/SeeThrough.ts +75 -1
  66. package/src/engine-components/api.ts +1 -0
  67. package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
  68. package/dist/gltf-progressive-9tlS5iCx.umd.cjs +0 -10
  69. package/dist/gltf-progressive-CVg80COO.min.js +0 -10
@@ -1,7 +1,7 @@
1
1
  import { FileLoader } from "three";
2
2
 
3
3
  import { showBalloonWarning } from "./debug/index.js";
4
- import { _imGyfK } from "./engine_license.js";
4
+ import { __Aale } from "./engine_license.js";
5
5
  import { delay } from "./engine_utils.js";
6
6
  import { md5AsBytes, md5Hex, sha256Base64 } from "./engine_utils_hash.js";
7
7
 
@@ -45,7 +45,7 @@ export namespace BlobStorage {
45
45
  */
46
46
  export function canUpload(info: { filesize: number }) {
47
47
  const sizeInMB = info.filesize / 1024 / 1024;
48
- if (_imGyfK()) {
48
+ if (__Aale()) {
49
49
  return sizeInMB < maxSizeInMB;
50
50
  }
51
51
  return sizeInMB < maxFreeSizeInMB;
@@ -100,7 +100,7 @@ export namespace BlobStorage {
100
100
  console.warn(`Your file is too large for uploading (${filesizeInMB.toFixed(1)}MB). Max allowed size is ${maxSizeInMB}MB`);
101
101
  return null;
102
102
  }
103
- else if (!_imGyfK() && filesizeInMB > maxFreeSizeInMB) {
103
+ else if (!__Aale() && filesizeInMB > maxFreeSizeInMB) {
104
104
  if (opts?.silent !== true) showBalloonWarning(`File is too large for uploading. Please get a <a href=\"https://needle.tools/pricing\" target=\"_blank\">commercial license</a> to upload files larger than 5MB`);
105
105
  console.warn(`Your file is too large for uploading (${filesizeInMB.toFixed(1)}MB). Max size is 5MB for non-commercial users. Please get a commercial license at https://needle.tools/pricing for larger files (up to 50MB)`);
106
106
  return null;
@@ -4,7 +4,7 @@ import { Quaternion, Vector2, Vector3, Vector4 } from "three";
4
4
 
5
5
  import { needleLogoOnlySVG } from "./assets/index.js";
6
6
  import { isDevEnvironment } from "./debug/debug.js";
7
- import { _imGyfK } from "./engine_license.js";
7
+ import { __Aale } from "./engine_license.js";
8
8
  import { InternalAttributeUtils } from "./engine_utils_attributes.js";
9
9
  import type { NeedleEngineWebComponent } from "./webcomponents/needle-engine.js";
10
10
 
@@ -135,7 +135,7 @@ async function internalRenderQRCodeOverlays(canvas: HTMLCanvasElement, args: { s
135
135
  console.debug("[QR Code] No web component found")
136
136
  }
137
137
 
138
- const canUseCustomLogo = _imGyfK();
138
+ const canUseCustomLogo = __Aale();
139
139
 
140
140
  // Query logo src from needle-engine attribute.
141
141
  // For any supported attribute it's possible to use "falsey" values (e.g. "0" or "false" to disable the logo in the QR code)
@@ -2,7 +2,7 @@ import { Mesh, Object3D, TextureLoader, Vector4 } from "three";
2
2
  import ThreeMeshUI from "three-mesh-ui";
3
3
 
4
4
  import { addNewComponent } from "../../engine_components.js";
5
- import { rkHTARX } from "../../engine_license.js";
5
+ import { $KhjWEFh } from "../../engine_license.js";
6
6
  import { OneEuroFilterXYZ } from "../../engine_math.js";
7
7
  import type { Context } from "../../engine_setup.js";
8
8
  import { lookAtObject } from "../../engine_three_utils.js";
@@ -321,7 +321,7 @@ export class NeedleSpatialMenu {
321
321
  }
322
322
  if (this.menu) {
323
323
  const index = this.menu.children.indexOf(this._poweredByNeedleElement as any);
324
- if (!this._showNeedleLogo && rkHTARX()) {
324
+ if (!this._showNeedleLogo && $KhjWEFh()) {
325
325
  if (index >= 0) {
326
326
  this._poweredByNeedleElement.removeFromParent();
327
327
  this.markDirty();
@@ -1,6 +1,6 @@
1
1
  import { showBalloonMessage } from "../../debug/debug.js";
2
2
  import type { Context } from "../../engine_context.js";
3
- import { _imGyfK, $ECTXQRP, Telemetry } from "../../engine_license.js";
3
+ import { __Aale, ytUNHgWl, Telemetry } from "../../engine_license.js";
4
4
  import { isLocalNetwork } from "../../engine_networking_utils.js";
5
5
  import { HTMLElementBase } from "../../engine_ssr.js";
6
6
  import { DeviceUtilities, getParam } from "../../engine_utils.js";
@@ -778,8 +778,8 @@ export class NeedleMenuElement extends HTMLElementBase {
778
778
  try {
779
779
  // if the user has a license then we CAN hide the needle logo
780
780
  // calling this method immediately will cause an issue with vite bundling tho
781
- window.requestAnimationFrame(() => $ECTXQRP(res => {
782
- if (res == true && _imGyfK() && !debugNonCommercial) {
781
+ window.requestAnimationFrame(() => ytUNHgWl(res => {
782
+ if (res == true && __Aale() && !debugNonCommercial) {
783
783
  let visible = this._userRequestedLogoVisible;
784
784
  if (visible === undefined) visible = false;
785
785
  this.___onSetLogoVisible(visible);
@@ -830,7 +830,7 @@ export class NeedleMenuElement extends HTMLElementBase {
830
830
  // ensure the menu is not hidden or removed
831
831
  const requiredParent = this?.parentNode;
832
832
  if (this.style.display != "flex" || this.style.visibility != "visible" || this.style.opacity != "1" || requiredParent != this._domElement?.shadowRoot) {
833
- if (!_imGyfK()) {
833
+ if (!__Aale()) {
834
834
  const change = changeEventCounter++;
835
835
  // if a user doesn't have a local pro license *but* for development the menu is hidden then we show a warning
836
836
  if (isLocalNetwork() && this._userRequestedMenuVisible === false) {
@@ -918,7 +918,7 @@ export class NeedleMenuElement extends HTMLElementBase {
918
918
  showNeedleLogo(visible: boolean) {
919
919
  this._userRequestedLogoVisible = visible;
920
920
  if (!visible) {
921
- if (!_imGyfK() || debugNonCommercial) {
921
+ if (!__Aale() || debugNonCommercial) {
922
922
  console.warn("[Needle Engine] You need a PRO license to hide the Needle Engine logo in production.");
923
923
  const localNetwork = isLocalNetwork()
924
924
  if (!localNetwork) return;
@@ -1,6 +1,6 @@
1
1
  import { needleLogoOnlySVG } from "../assets/index.js"
2
2
  import { isDevEnvironment, showBalloonWarning } from "../debug/index.js";
3
- import { _imGyfK, rkHTARX, $hQhmn } from "../engine_license.js";
3
+ import { __Aale, $KhjWEFh, _AaROCyv } from "../engine_license.js";
4
4
  import { Mathf } from "../engine_math.js";
5
5
  import { LoadingProgressArgs } from "../engine_setup.js";
6
6
  import { getParam } from "../engine_utils.js";
@@ -205,7 +205,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
205
205
  }
206
206
 
207
207
 
208
- const hasLicense = rkHTARX();
208
+ const hasLicense = $KhjWEFh();
209
209
  if (!existing) {
210
210
  this._loadingElement.style.position = "absolute";
211
211
  this._loadingElement.style.width = "100%";
@@ -373,7 +373,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
373
373
 
374
374
  // private async handleRuntimeLicense(loadingElement: HTMLElement) {
375
375
  // // First check if we have a commercial license
376
- // let commercialLicense = _imGyfK();
376
+ // let commercialLicense = __Aale();
377
377
  // // if it's the case then we don't need to perform a runtime check
378
378
  // if (commercialLicense) return;
379
379
 
@@ -393,10 +393,10 @@ export class EngineLoadingView implements ILoadingViewHandler {
393
393
  // loadingElement.appendChild(nonCommercialContainer);
394
394
 
395
395
  // // Use the runtime license check
396
- // if (!isDevEnvironment() && $hQhmn) {
396
+ // if (!isDevEnvironment() && _AaROCyv) {
397
397
  // if (debugLicense) console.log("Waiting for runtime license check");
398
- // await $hQhmn;
399
- // commercialLicense = _imGyfK();
398
+ // await _AaROCyv;
399
+ // commercialLicense = __Aale();
400
400
  // }
401
401
  // if (commercialLicense) return;
402
402
  // nonCommercialContainer.style.transition = "opacity .5s ease-in-out";
@@ -3,7 +3,7 @@ import type { Texture } from "three";
3
3
  import { isDevEnvironment, showBalloonWarning } from "../debug/index.js";
4
4
  import { PUBLIC_KEY, VERSION } from "../engine_constants.js";
5
5
  import { ContextEvent, ContextRegistry } from "../engine_context_registry.js";
6
- import { _imGyfK } from "../engine_license.js";
6
+ import { __Aale } from "../engine_license.js";
7
7
  import { onStart } from "../engine_lifecycle_api.js";
8
8
  import { setDracoDecoderPath, setDracoDecoderType, setKtx2TranscoderPath } from "../engine_loaders.gltf.js";
9
9
  import { Context, ContextCreateArgs } from "../engine_setup.js";
@@ -579,7 +579,7 @@ export class NeedleEngineWebComponent extends HTMLElementBase implements INeedle
579
579
 
580
580
 
581
581
  // Loading start events
582
- const allowOverridingDefaultLoading = _imGyfK();
582
+ const allowOverridingDefaultLoading = __Aale();
583
583
  // default loading can be overriden by calling preventDefault in the onload start event
584
584
  this.ensureLoadStartIsRegistered();
585
585
  let useDefaultLoading = this.dispatchEvent(new CustomEvent("loadstart", {
@@ -3,7 +3,7 @@ import { ArrayCamera, AxesHelper, Camera, Color, DirectionalLight, Fog, GridHelp
3
3
  import { needleLogoOnlySVG } from "../assets/index.js";
4
4
  import { isDevEnvironment } from "../debug/index.js";
5
5
  import { ObjectUtils, PrimitiveType } from "../engine_create_objects.js";
6
- import { _imGyfK } from "../engine_license.js";
6
+ import { __Aale } from "../engine_license.js";
7
7
  import { Mathf } from "../engine_math.js";
8
8
  import { delay, DeviceUtilities } from "../engine_utils.js";
9
9
 
@@ -215,7 +215,7 @@ export class TemporaryXRContext {
215
215
  this._scene.background = new Color(0x000000);
216
216
 
217
217
  let logoSrc = needleLogoOnlySVG;
218
- if (_imGyfK()) {
218
+ if (__Aale()) {
219
219
  const htmlComponent = document.querySelector("needle-engine");
220
220
  if (htmlComponent) {
221
221
  const licenseLogo = htmlComponent.getAttribute("logo-src");
@@ -1107,7 +1107,8 @@ export abstract class Component implements IComponent, EventTarget,
1107
1107
  this.__didCompleteStart = false;
1108
1108
  this.__inEnableOrDisableCallback = false;
1109
1109
  this.__didEnable = false;
1110
- this.__isEnabled = undefined;
1110
+ // Preserve copied/constructor-assigned enabled state while resetting lifecycle flags.
1111
+ // Cloned disabled components must not fall back to the default enabled=true state.
1111
1112
  this.__destroyed = false;
1112
1113
  this._internalInit(init);
1113
1114
  return this;
@@ -614,7 +614,8 @@ export class SceneSwitcher extends Behaviour {
614
614
  this._currentScene = undefined;
615
615
  if (current) {
616
616
  if (debug) console.log("[SceneSwitcher] UNLOAD", current.url, "HasURL?: " + current.hasUrl)
617
- const sceneListener = this.tryGetSceneEventListener(current.asset as any as Object3D);
617
+ const activeSceneInstance = this._currentSceneAsset;
618
+ const sceneListener = this.tryGetSceneEventListener(activeSceneInstance as any as Object3D);
618
619
  if (sceneListener?.sceneClosing) {
619
620
  const res = sceneListener.sceneClosing();
620
621
  if (res instanceof Promise) await res;
@@ -624,20 +625,24 @@ export class SceneSwitcher extends Behaviour {
624
625
  // // then we unload it
625
626
  if (current.hasUrl) {
626
627
  current.unload();
627
- if (this._currentSceneAsset) destroy(this._currentSceneAsset, true, false);
628
+ if (activeSceneInstance) destroy(activeSceneInstance, true, false);
628
629
  }
629
630
  else {
630
- GameObject.remove(this._currentSceneAsset);
631
+ GameObject.remove(activeSceneInstance);
631
632
  }
633
+ this._currentSceneAsset = undefined;
632
634
  }
633
635
  }
634
636
 
635
637
  private _currentlyLoadingScene?: AssetReference;
638
+ private _loadingRequestId: number = 0;
639
+ private _activeLoadingRequestId: number = 0;
636
640
  /** @internal */
637
641
  private async __internalSwitchScene(scene: AssetReference): Promise<boolean> {
638
642
 
639
643
  await this.__unloadCurrentScene();
640
644
  const index = this._currentIndex = this.scenes?.indexOf(scene) ?? -1;
645
+ const loadingRequestId = ++this._loadingRequestId;
641
646
 
642
647
  try {
643
648
  if (debug) console.debug(`${Date.now()} [SceneSwitcher] Loading scene start: ${scene.url} (index: ${index})`);
@@ -647,9 +652,10 @@ export class SceneSwitcher extends Behaviour {
647
652
  const loadStartEvt = new CustomEvent<LoadSceneEvent>("loadscene-start", { detail: { scene: scene, switcher: this, index: index } })
648
653
  this.dispatchEvent(loadStartEvt);
649
654
  this.sceneLoadingStart?.invoke(loadStartEvt.detail);
650
- await this.onStartLoading();
655
+ await this.onStartLoading(loadingRequestId);
651
656
  // start loading and wait for the scene to be loaded
652
657
  await scene.loadAssetAsync((_, prog) => {
658
+ if (loadingRequestId !== this._activeLoadingRequestId) return;
653
659
  if (debug) {
654
660
  const t01 = prog.loaded / prog.total;
655
661
  const progressBarString = "[" + "=".repeat(Math.floor(t01 * 20)) + "-".repeat(20 - Math.floor(t01 * 20)) + "]";
@@ -659,12 +665,14 @@ export class SceneSwitcher extends Behaviour {
659
665
  this.dispatchEvent(prog);
660
666
  this.sceneLoadingProgress?.invoke(prog);
661
667
  }).catch(console.error);
662
- await this.onEndLoading();
668
+ await this.onEndLoading(loadingRequestId);
663
669
  const finishedEvt = new CustomEvent<LoadSceneEvent>("loadscene-finished", { detail: { scene: scene, switcher: this, index: index } });
664
670
  this.dispatchEvent(finishedEvt);
665
671
 
666
- this._currentLoadingProgress = undefined;
667
- this._currentlyLoadingScene = undefined;
672
+ if (loadingRequestId === this._activeLoadingRequestId) {
673
+ this._currentLoadingProgress = undefined;
674
+ this._currentlyLoadingScene = undefined;
675
+ }
668
676
 
669
677
  if (finishedEvt.defaultPrevented) {
670
678
  if (debug) console.warn("[SceneSwitcher] Adding loaded scene prevented:", scene, finishedEvt);
@@ -737,8 +745,8 @@ export class SceneSwitcher extends Behaviour {
737
745
  history.pushState(stateName, "unused", location.href);
738
746
  }
739
747
  }
740
- // Call SceneListener opened callback (if a SceneListener is in the scene)
741
- const sceneListener = this.tryGetSceneEventListener(scene.asset as any as Object3D);
748
+ // Call SceneListener opened callback on the scene instance that was actually added.
749
+ const sceneListener = this.tryGetSceneEventListener(this._currentSceneAsset as any as Object3D);
742
750
  if (sceneListener?.sceneOpened) {
743
751
  const res = sceneListener.sceneOpened(this);
744
752
  if (res instanceof Promise) await res;
@@ -826,7 +834,8 @@ export class SceneSwitcher extends Behaviour {
826
834
  private _isCurrentlyLoading: boolean = false;
827
835
  private _currentLoadingProgress: ProgressEvent | undefined = undefined;
828
836
 
829
- private async onStartLoading() {
837
+ private async onStartLoading(loadingRequestId: number) {
838
+ this._activeLoadingRequestId = loadingRequestId;
830
839
  this._isCurrentlyLoading = true;
831
840
  if (this.loadingScene) {
832
841
  // save the last loading scene reference so that it can be changed at runtime
@@ -839,7 +848,7 @@ export class SceneSwitcher extends Behaviour {
839
848
  this._loadingScenePromise = this.loadingScene?.loadAssetAsync().then(res => res != null);
840
849
  }
841
850
  await this._loadingScenePromise;
842
- if (this._isCurrentlyLoading && this.loadingScene?.asset) {
851
+ if (loadingRequestId === this._activeLoadingRequestId && this._isCurrentlyLoading && this.loadingScene?.asset) {
843
852
  if (debug) console.log("Add loading scene", this.loadingScene.url, this.loadingScene.asset)
844
853
  const loadingScene = this.loadingScene.asset as any as Object3D;
845
854
  GameObject.add(loadingScene, this.gameObject);
@@ -851,7 +860,7 @@ export class SceneSwitcher extends Behaviour {
851
860
  }
852
861
  }
853
862
  // Invoke the event on a loading listener on the same object as the scene switcher
854
- if (this._isCurrentlyLoading) {
863
+ if (loadingRequestId === this._activeLoadingRequestId && this._isCurrentlyLoading) {
855
864
  const listener = this.tryGetSceneEventListener(this.gameObject);
856
865
  if (listener) {
857
866
  if (listener.sceneOpened) {
@@ -861,7 +870,8 @@ export class SceneSwitcher extends Behaviour {
861
870
  }
862
871
  }
863
872
  }
864
- private async onEndLoading() {
873
+ private async onEndLoading(loadingRequestId: number) {
874
+ if (loadingRequestId !== this._activeLoadingRequestId) return;
865
875
  this._isCurrentlyLoading = false;
866
876
  if (this.loadingScene?.asset) {
867
877
  if (debug) console.log("Remove loading scene", this.loadingScene.url);
@@ -11,7 +11,7 @@ import { Behaviour } from "./Component.js";
11
11
  import { IUSDExporterExtension } from "./export/usdz/Extension.js";
12
12
  import { USDZExporter } from "./export/usdz/USDZExporter.js";
13
13
  import type { OrbitControls } from "./OrbitControls.js";
14
- import { Renderer } from "./Renderer.js";
14
+ import { Renderer, ShadowCastingMode } from "./Renderer.js";
15
15
 
16
16
  const debugSeeThrough = getParam("debugseethrough");
17
17
 
@@ -164,6 +164,35 @@ export class SeeThrough extends Behaviour {
164
164
  @serializable()
165
165
  autoUpdate: boolean = true;
166
166
 
167
+ /**
168
+ * The alpha (opacity) the object is currently faded to, in the `[0, 1]` range.
169
+ * `1` means fully visible, `0` means fully hidden. This is the live, animated value
170
+ * (it lerps toward the target over {@link fadeDuration}), updated every frame the fade runs.
171
+ *
172
+ * External components can read this to react to the see-through state — e.g. toggling a
173
+ * collider, muting audio, or driving a UI prompt as the object fades in or out.
174
+ *
175
+ * @example React to the object becoming (almost) hidden
176
+ * ```ts
177
+ * if (seeThrough.currentAlpha < 0.1) {
178
+ * // object is essentially see-through now
179
+ * }
180
+ * ```
181
+ * @default 1
182
+ */
183
+ get currentAlpha(): number {
184
+ return this._currentAlpha;
185
+ }
186
+
187
+
188
+ /**
189
+ * Below this alpha the object stops casting a shadow. The shadow map only stores depth, so a
190
+ * faded object would otherwise keep casting a fully solid shadow (three.js does not fade shadows
191
+ * by opacity). Toggling shadow casting off near-invisible is the cleanest fix; we drive it via
192
+ * the {@link Renderer.shadowCastingMode} so it cooperates with the per-frame shadow settings the
193
+ * Renderer re-applies. Note this is a hard on/off, not a fade.
194
+ */
195
+ public static readonly shadowHideThreshold = 0.05;
167
196
 
168
197
  private readonly _referencePointVector: Vector3 = new Vector3();
169
198
  private readonly _referencePointDir: Vector3 = new Vector3();
@@ -171,6 +200,9 @@ export class SeeThrough extends Behaviour {
171
200
  private _renderer: Renderer[] | null = null;
172
201
  private _needsUpdate = true;
173
202
  private _id = i++;
203
+ private _currentAlpha: number = 1;
204
+ /** Renderers whose shadow casting we turned off while hidden, mapped to their original mode for restore. */
205
+ private readonly _suppressedShadowCasting = new Map<Renderer, ShadowCastingMode>();
174
206
 
175
207
  /** * @internal */
176
208
  onEnable() {
@@ -178,6 +210,12 @@ export class SeeThrough extends Behaviour {
178
210
  this._renderer = null;
179
211
  }
180
212
 
213
+ /** @internal */
214
+ onDestroy() {
215
+ // Don't leave renderers permanently non-casting if this component is removed.
216
+ this.restoreSuppressedShadowCasting();
217
+ }
218
+
181
219
  /**
182
220
  * @internal
183
221
  */
@@ -258,13 +296,49 @@ export class SeeThrough extends Behaviour {
258
296
  if (newAlpha >= 0.99) newAlpha = 1;
259
297
  else if (newAlpha <= 0.01) newAlpha = 0;
260
298
 
299
+ // Expose the live fade value so external components can react to it (see `currentAlpha`).
300
+ // Renderers are driven by the same target/duration, so the last applied value is representative.
301
+ this._currentAlpha = newAlpha;
302
+
261
303
  // const currentTransparent = (block.getOverride("transparent")?.value ?? materials[0].transparent ?? false) as boolean;
262
304
 
263
305
  block.setOverride("alphaHash", this.useAlphaHash);
264
306
  block.setOverride("opacity", newAlpha);
265
307
  block.setOverride("transparent", newAlpha >= 0.99999 ? false : !this.useAlphaHash);
308
+
309
+ // Stop casting a shadow once the object is essentially invisible (and restore it when it
310
+ // comes back). A faded object otherwise keeps a fully solid shadow — see shadowHideThreshold.
311
+ this.updateShadowCasting(renderer, newAlpha);
266
312
  });
267
313
  }
268
314
 
315
+ /**
316
+ * Suppress or restore a renderer's shadow casting based on its current alpha.
317
+ * Driving {@link Renderer.shadowCastingMode} (rather than the raw `mesh.castShadow`) is required
318
+ * because the Renderer re-applies that setting every frame; we remember the original mode so it
319
+ * is restored exactly when the object fades back in.
320
+ */
321
+ private updateShadowCasting(renderer: Renderer, alpha: number) {
322
+ const shouldHideShadow = alpha < SeeThrough.shadowHideThreshold;
323
+ if (shouldHideShadow) {
324
+ if (renderer.shadowCastingMode !== ShadowCastingMode.Off) {
325
+ this._suppressedShadowCasting.set(renderer, renderer.shadowCastingMode);
326
+ renderer.shadowCastingMode = ShadowCastingMode.Off;
327
+ }
328
+ }
329
+ else if (this._suppressedShadowCasting.has(renderer)) {
330
+ renderer.shadowCastingMode = this._suppressedShadowCasting.get(renderer)!;
331
+ this._suppressedShadowCasting.delete(renderer);
332
+ }
333
+ }
334
+
335
+ /** Restore every renderer we suppressed shadow casting on, then clear the bookkeeping. */
336
+ private restoreSuppressedShadowCasting() {
337
+ for (const [renderer, mode] of this._suppressedShadowCasting) {
338
+ if (!renderer.destroyed) renderer.shadowCastingMode = mode;
339
+ }
340
+ this._suppressedShadowCasting.clear();
341
+ }
342
+
269
343
  }
270
344
 
@@ -44,6 +44,7 @@ export { Behaviour, Component, GameObject } from "./Component.js";
44
44
  export { AudioRolloffMode } from "./AudioSource.js";
45
45
  export { ClearFlags } from "./Camera.js"
46
46
  export { DragMode } from "./DragControls.js";
47
+ export { ShadowCastingMode, ReflectionProbeUsage } from "./Renderer.js";
47
48
  export type { DropListenerNetworkEventArguments, DropListenerOnDropArguments } from "./DropListener.js";
48
49
  export * from "./export/index.js"
49
50
  export * from "./particlesystem/api.js"
@@ -3,7 +3,7 @@ import { Euler, Material, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "t
3
3
 
4
4
  import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
5
5
  import { findObjectOfType } from "../../../engine/engine_components.js";
6
- import { rkHTARX } from "../../../engine/engine_license.js";
6
+ import { $KhjWEFh } from "../../../engine/engine_license.js";
7
7
  import { serializable } from "../../../engine/engine_serialization.js";
8
8
  import { getFormattedDate, Progress } from "../../../engine/engine_time_utils.js";
9
9
  import { DeviceUtilities, getParam } from "../../../engine/engine_utils.js";
@@ -277,7 +277,7 @@ export class USDZExporter extends Behaviour {
277
277
  let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
278
278
  name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
279
279
 
280
- if (!rkHTARX()) {
280
+ if (!$KhjWEFh()) {
281
281
  if (name !== "") name += "-";
282
282
  name += "MadeWithNeedle";
283
283
  }
@@ -682,7 +682,7 @@ export class USDZExporter extends Behaviour {
682
682
  if (debug)
683
683
  showBalloonMessage("Quicklook url: " + callToActionURL);
684
684
  if (callToActionURL) {
685
- if (!rkHTARX()) {
685
+ if (!$KhjWEFh()) {
686
686
  console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL)
687
687
  }
688
688
  else {
@@ -697,7 +697,7 @@ export class USDZExporter extends Behaviour {
697
697
  private buildQuicklookOverlay(): CustomBranding {
698
698
  const obj: CustomBranding = {};
699
699
  if (this.customBranding) Object.assign(obj, this.customBranding);
700
- if (!rkHTARX()) {
700
+ if (!$KhjWEFh()) {
701
701
  console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
702
702
  obj.callToAction = "Close";
703
703
  obj.checkoutTitle = "🌵 Made with Needle";
@@ -1,10 +0,0 @@
1
- "use strict";const f=require("./three.umd.cjs"),Y=require("./three-examples.umd.cjs");var ce=typeof document<"u"?document.currentScript:null;const Fe="";globalThis.GLTF_PROGRESSIVE_VERSION=Fe;console.debug("[gltf-progressive] version -");let I="https://www.gstatic.com/draco/versioned/decoders/1.5.7/",X="https://cdn.needle.tools/static/three/0.179.1/basis2/";const ze=I,Ve=X,Ce=new URL(I+"draco_decoder.js");Ce.searchParams.append("range","true");fetch(Ce,{method:"GET",headers:{Range:"bytes=0-1"}}).catch(o=>{console.debug(`Failed to fetch remote Draco decoder from ${I} (offline: ${typeof navigator<"u"?navigator.onLine:"unknown"})`),I===ze&&Pe("./include/draco/"),X===Ve&&ke("./include/ktx2/")}).finally(()=>{Re()});const Ee=()=>({dracoDecoderPath:I,ktx2TranscoderPath:X});function Pe(o){I=o,k&&k[ye]!=I?(console.debug("Updating Draco decoder path to "+o),k[ye]=I,k.setDecoderPath(I),k.preload()):console.debug("Setting Draco decoder path to "+o)}function ke(o){X=o,$&&$.transcoderPath!=X?(console.debug("Updating KTX2 transcoder path to "+o),$.setTranscoderPath(X),$.init()):console.debug("Setting KTX2 transcoder path to "+o)}function le(o){return Re(),o?$.detectSupport(o):o!==null&&console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"),{dracoLoader:k,ktx2Loader:$,meshoptDecoder:ae}}function Le(o){o.dracoLoader||o.setDRACOLoader(k),o.ktx2Loader||o.setKTX2Loader($),o.meshoptDecoder||o.setMeshoptDecoder(ae)}const ye=Symbol("dracoDecoderPath");let k,ae,$;function Re(){k||(k=new Y.DRACOLoader,k[ye]=I,k.setDecoderPath(I),k.setDecoderConfig({type:"js"}),k.preload()),$||($=new Y.KTX2Loader,$.setTranscoderPath(X),$.init()),ae||(ae=Y.MeshoptDecoder)}const xe=new WeakMap;function Me(o,t){let e=xe.get(o);e?e=Object.assign(e,t):e=t,xe.set(o,e)}const Ne=Y.GLTFLoader.prototype.load;function Xe(...o){const t=xe.get(this);let e=o[0];const s=new URL(e,window.location.href);if(s.hostname.endsWith("needle.tools")){const n=t?.progressive!==void 0?t.progressive:!0,i=t?.usecase?t.usecase:"default";n?this.requestHeader.Accept=`*/*;progressive=allowed;usecase=${i}`:this.requestHeader.Accept=`*/*;usecase=${i}`,e=s.toString()}return o[0]=e,Ne?.call(this,...o)}Y.GLTFLoader.prototype.load=Xe;z("debugprogressive");function z(o){if(typeof window>"u")return!1;const e=new URL(window.location.href).searchParams.get(o);return e==null||e==="0"||e==="false"?!1:e===""?!0:e}function Ke(o,t){if(t===void 0||o===void 0||t.startsWith("./")||t.startsWith("http")||t.startsWith("data:")||t.startsWith("blob:"))return t;const e=o.lastIndexOf("/");if(e>=0){const s=o.substring(0,e+1);for(;s.endsWith("/")&&t.startsWith("/");)t=t.substring(1);return s+t}return t}function ve(){return J!==void 0||(J=/iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent),z("debugprogressive")&&console.log("[glTF Progressive]: isMobileDevice",J)),J}let J;function Ae(){if(typeof window>"u")return!1;const o=new URL(window.location.href),t=o.hostname==="localhost"||/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(o.hostname);return o.hostname==="127.0.0.1"||t}class je{constructor(t,e={}){this.maxConcurrent=t,this.debug=e.debug??!1,typeof window<"u"&&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:n=>this.add(s,n)})}}}function He(o){const t=o.image?.width??0,e=o.image?.height??0,s=o.image?.depth??1,r=Math.floor(Math.log2(Math.max(t,e,s)))+1,n=Ye(o);return t*e*s*n*(1-Math.pow(.25,r))/(1-.25)}function Ye(o){let t=4;const e=o.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=o.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 Qe=typeof window>"u"&&typeof document>"u",we=Symbol("needle:raycast-mesh");function Q(o){return o?.[we]instanceof f.BufferGeometry?o[we]:null}function Je(o,t){if((o.type==="Mesh"||o.type==="SkinnedMesh")&&!Q(o)){const s=et(t);s.userData={isRaycastMesh:!0},o[we]=s}}function Ze(o=!0){if(o){if(Z)return;const t=Z=f.Mesh.prototype.raycast;f.Mesh.prototype.raycast=function(e,s){const r=this,n=Q(r);let i;n&&r.isMesh&&(i=r.geometry,r.geometry=n),t.call(this,e,s),i&&(r.geometry=i)}}else{if(!Z)return;f.Mesh.prototype.raycast=Z,Z=null}}let Z=null;function et(o){const t=new f.BufferGeometry;for(const e in o.attributes)t.setAttribute(e,o.getAttribute(e));return t.setIndex(o.getIndex()),t}const q=new Array,g=z("debugprogressive");let se,E=-1;if(g){let o=function(){E+=1,E>=t&&(E=-1),console.log(`Toggle LOD level [${E}]`)};const t=6;window.addEventListener("keyup",e=>{e.key==="p"&&o(),e.key==="w"&&(se=!se,console.log(`Toggle wireframe [${se}]`));const s=parseInt(e.key);!isNaN(s)&&s>=0&&(E=s,console.log(`Set LOD level to [${E}]`))})}function Ie(o){if(g&&se!==void 0)if(Array.isArray(o))for(const t of o)Ie(t);else o&&"wireframe"in o&&(o.wireframe=se===!0)}const ee=new Array;let tt=0;const st=ve()?2:10;function rt(o){if(ee.length<st){const s=ee.length;g&&console.warn(`[Worker] Creating new worker #${s}`);const r=De.createWorker(o||{});return ee.push(r),r}const t=tt++%ee.length;return ee[t]}class De{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 n of this._running)if(n.url===r.result.url){nt(r.result),n.resolve(r.result);const i=n.url;i.startsWith("blob:")&&URL.revokeObjectURL(i)}}},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("/gltf-progressive.worker-BqODMeeW.js",typeof document>"u"?require("url").pathToFileURL(__filename).href:ce&&ce.tagName.toUpperCase()==="SCRIPT"&&ce.src||new URL("gltf-progressive-9tlS5iCx.umd.cjs",document.baseURI).href),{type:"module"});return new De(e,t)}_running=[];_webglRenderer=null;async load(t,e){const s=Ee();let r=e?.renderer;r||(this._webglRenderer??=(async()=>{const{WebGLRenderer:u}=await Promise.resolve().then(()=>require("./three.umd.cjs")).then(c=>c.THREE);return new u})(),r=await this._webglRenderer);const a=le(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 l={type:"load",url:t,dracoDecoderPath:s.dracoDecoderPath,ktx2TranscoderPath:s.ktx2TranscoderPath,ktx2LoaderConfig:a};return this._debug&&console.debug("[Worker] Sending load request",l),this.worker.postMessage(l),new Promise(u=>{this._running.push({url:t.toString(),resolve:u})})}_debug=!1}function nt(o){for(const t of o.geometries){const e=t.geometry,s=new f.BufferGeometry;if(s.name=e.name||"",e.index){const r=e.index;s.setIndex(de(r))}for(const r in e.attributes){const n=e.attributes[r],i=de(n);s.setAttribute(r,i)}if(e.morphAttributes)for(const r in e.morphAttributes){const i=e.morphAttributes[r].map(a=>de(a));s.morphAttributes[r]=i}if(s.morphTargetsRelative=e.morphTargetsRelative??!1,s.boundingBox=new f.Box3,s.boundingBox.min=new f.Vector3(e.boundingBox?.min.x,e.boundingBox?.min.y,e.boundingBox?.min.z),s.boundingBox.max=new f.Vector3(e.boundingBox?.max.x,e.boundingBox?.max.y,e.boundingBox?.max.z),s.boundingSphere=new f.Sphere(new f.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 o.textures){const e=t.texture;let s=null;if(e.isCompressedTexture){const r=e.mipmaps,n=e.image?.width||e.source?.data?.width||-1,i=e.image?.height||e.source?.data?.height||-1;s=new f.CompressedTexture(r,n,i,e.format,e.type,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.anisotropy,e.colorSpace)}else s=new f.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 f.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 o}function de(o){let t=o;if("isInterleavedBufferAttribute"in o&&o.isInterleavedBufferAttribute){const e=o.data,s=e.array,r=new f.InterleavedBuffer(s,e.stride);t=new f.InterleavedBufferAttribute(r,o.itemSize,s.byteOffset,o.normalized),t.offset=o.offset}else"isBufferAttribute"in o&&o.isBufferAttribute&&(t=new f.BufferAttribute(o.array,o.itemSize,o.normalized),t.usage=o.usage,t.gpuType=o.gpuType,t.updateRanges=o.updateRanges);return t}const ot=z("gltf-progressive-worker");z("gltf-progressive-reduce-mipmaps");const te=z("gltf-progressive-gc"),fe=Symbol("needle-progressive-texture"),W="NEEDLE_progressive";class p{get name(){return W}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",n=t[r];if(n!=null)return n;if(e||(e={min_count:1/0,max_count:0,lods:[]}),Array.isArray(t)){for(const a of t)this.getMaterialMinMaxLODsCount(a,e);return t[r]=e,e}if(g==="verbose"&&console.log("getMaterialMinMaxLODsCount",t),t.type==="ShaderMaterial"||t.type==="RawShaderMaterial"){const a=t;for(const l of Object.keys(a.uniforms)){const u=a.uniforms[l].value;u?.isTexture===!0&&i(u,e)}}else if(t.isMaterial)for(const a of Object.keys(t)){const l=t[a];l?.isTexture===!0&&i(l,e)}else g&&console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);return t[r]=e,e;function i(a,l){const u=s.getAssignedLODInformation(a);if(u){const c=s.lodInfos.get(u.key);if(c&&c.lods){l.min_count=Math.min(l.min_count,c.lods.length),l.max_count=Math.max(l.max_count,c.lods.length);for(let d=0;d<c.lods.length;d++){const h=c.lods[d];h.width&&(l.lods[d]=l.lods[d]||{min_height:1/0,max_height:0},l.lods[d].min_height=Math.min(l.lods[d].min_height,h.height),l.lods[d].max_height=Math.max(l.lods[d].max_height,h.height))}}}}}static hasLODLevelAvailable(t,e){if(Array.isArray(t)){for(const n of t)if(this.hasLODLevelAvailable(n,e))return!0;return!1}if(t.isMaterial===!0){for(const n of Object.keys(t)){const i=t[n];if(i&&i.isTexture&&this.hasLODLevelAvailable(i,e))return!0}return!1}else if(t.isGroup===!0){for(const n of t.children)if(n.isMesh===!0&&this.hasLODLevelAvailable(n,e))return!0}let s,r;if(t.isMesh?s=t.geometry:(t.isBufferGeometry||t.isTexture)&&(s=t),s&&s?.userData?.LODS){const n=s.userData.LODS;if(r=this.lodInfos.get(n.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,s){if(!t)return Promise.resolve(null);if(t instanceof f.Mesh||t.isMesh===!0){const r=t.geometry,n=this.getAssignedLODInformation(r);if(!n)return Promise.resolve(null);for(const a of q)a.onBeforeGetLODMesh?.(t,e);t["LOD:requested level"]=e;const i=()=>t["LOD:requested level"]===e||this.shouldApplyStaleMeshLOD(t,e);return p.getOrLoadLOD(r,e,{isCurrent:i}).then(a=>{if(Array.isArray(a)){const u=n.index||0;a=a[u]}const l=t["LOD:requested level"]===e;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):g&&console.error("Invalid LOD geometry",a))),a}).catch(a=>(console.error("Error loading mesh LOD",t,a),null))}else g&&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,s){if(!t)return Promise.resolve(null);const r=s?.force===!0;if(t.isMesh===!0){const n=t;if(Array.isArray(n.material)){const i=new Array;for(const a of n.material){const l=this.assignTextureLOD(a,e,s);i.push(l)}return Promise.all(i).then(a=>{const l=new Array;for(const u of a)Array.isArray(u)&&l.push(...u);return l})}else return this.assignTextureLOD(n.material,e,s)}if(t.isMaterial===!0){const n=t,i=[],a=new Array;if(this.trackCurrentMaterialTextureSlots(n),n.uniforms&&(n.isRawShaderMaterial||n.isShaderMaterial===!0)){const l=n;for(const u of Object.keys(l.uniforms)){const c=l.uniforms[u].value;if(c?.isTexture===!0){const d=this.assignTextureLODForSlot(c,e,n,u,r).then(h=>(h&&l.uniforms[u].value!=h&&(l.uniforms[u].value=h,l.uniformsNeedUpdate=!0),h));i.push(d),a.push(u)}}}else for(const l of Object.keys(n)){const u=n[l];if(u?.isTexture===!0){const c=this.assignTextureLODForSlot(u,e,n,l,r);i.push(c),a.push(l)}}return Promise.all(i).then(l=>{const u=new Array;for(let c=0;c<l.length;c++){const d=l[c],h=a[c];d&&d.isTexture===!0?u.push({material:n,slot:h,texture:d,level:e}):u.push({material:n,slot:h,texture:null,level:e})}return u})}if(t instanceof f.Texture||t.isTexture===!0){const n=t;return this.assignTextureLODForSlot(n,e,null,null,r)}return Promise.resolve(null)}static set maxConcurrentLoadingTasks(t){p.queue.maxConcurrent=t}static get maxConcurrentLoadingTasks(){return p.queue.maxConcurrent}static assignTextureLODForSlot(t,e,s,r,n){if(t?.isTexture!==!0)return Promise.resolve(null);if(r==="glyphMap")return Promise.resolve(t);const i=this.getAssignedLODInformation(t);if(i&&(i.level===e||!n&&i.level<e))return Promise.resolve(t);if(s&&r){const d=this.getPendingTextureSlotRequest(s,r);if(d&&d.level===e&&d.force===n)return d.promise}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),c=p.getOrLoadLOD(t,e,{isCurrent:u}).then(d=>{if(!l()&&!this.shouldApplyStaleTextureSlotLOD(s,r,e,n))return null;if(Array.isArray(d))return console.warn("Progressive: Got an array of textures for a texture slot, this should not happen..."),null;if(d?.isTexture===!0){if(d!=t&&s&&r){const h=this.getMaterialTextureSlot(s,r)??t;if(h&&!n){const m=this.getAssignedLODInformation(h);if(m&&m?.level<e)return g==="verbose"&&console.warn("Assigned texture level is already higher: ",m.level,e,s,h,d),null}this.assignTrackedTextureSlot(s,r,d)}return d}else g=="verbose"&&console.warn("No LOD found for",t,e);return null}).catch(d=>(console.error("Error loading LOD",t,d),null));return s&&r&&this.setPendingTextureSlotRequest(s,r,e,n,a,c),c}static trackedTextureSlots=new WeakMap;static pendingTextureSlotRequests=new WeakMap;static latestTextureSlotRequests=new WeakMap;static textureSlotRequestId=0;static trackCurrentMaterialTextureSlots(t){if(t.uniforms&&(t.isRawShaderMaterial||t.isShaderMaterial===!0)){const e=t;for(const s of Object.keys(e.uniforms)){const r=e.uniforms[s].value;r?.isTexture===!0&&this.ensureTrackedTextureSlot(t,s,r)}return}for(const e of Object.keys(t)){const s=t[e];s?.isTexture===!0&&this.ensureTrackedTextureSlot(t,e,s)}}static getPendingTextureSlotRequest(t,e){return this.pendingTextureSlotRequests.get(t)?.get(e)}static nextTextureSlotRequestId(t,e,s,r){let n=this.latestTextureSlotRequests.get(t);n||(n=new Map,this.latestTextureSlotRequests.set(t,n));const i=++this.textureSlotRequestId;return n.set(e,{id:i,level:s,force:r}),i}static getLatestTextureSlotRequest(t,e){return this.latestTextureSlotRequests.get(t)?.get(e)}static shouldApplyStaleTextureSlotLOD(t,e,s,r){if(!t||!e)return!1;const n=this.getLatestTextureSlotRequest(t,e),i=this.getMaterialTextureSlot(t,e),a=this.getAssignedLODInformation(i)?.level??1/0;return s>=a?!1:r?n?s>=n.level:!1:!0}static shouldApplyStaleMeshLOD(t,e){const s=t["LOD:requested level"];if(typeof s!="number")return!1;const r=this.getAssignedLODInformation(t.geometry)?.level??1/0;return e<r&&e>=s}static setPendingTextureSlotRequest(t,e,s,r,n,i){let a=this.pendingTextureSlotRequests.get(t);a||(a=new Map,this.pendingTextureSlotRequests.set(t,a));const l={level:s,force:r,id:n,promise:i};a.set(e,l),i.finally(()=>{a.get(e)?.id===n&&a.delete(e)})}static getMaterialTextureSlot(t,e){const r=t.uniforms?.[e];if(r?.value?.isTexture===!0)return r.value;const n=t[e];return n?.isTexture===!0?n:null}static setMaterialTextureSlot(t,e,s){const n=t.uniforms?.[e];if(n?.value?.isTexture===!0){n.value=s,t.uniformsNeedUpdate=!0;return}t[e]=s}static assignTrackedTextureSlot(t,e,s){let r=this.trackedTextureSlots.get(t);r||(r=new Map,this.trackedTextureSlots.set(t,r));const n=this.getMaterialTextureSlot(t,e);let i=r.get(e);!i&&n?i=this.ensureTrackedTextureSlot(t,e,n):i&&n&&i!==n&&(this.releaseTrackedTextureSlot(t,e,i),i=this.ensureTrackedTextureSlot(t,e,n)),!(i===s&&n===s)&&(i&&i!==s&&this.releaseTrackedTextureSlot(t,e,i),i!==s&&(this.trackTextureUsage(s),r.set(e,s)),n!==s&&this.setMaterialTextureSlot(t,e,s))}static ensureTrackedTextureSlot(t,e,s){let r=this.trackedTextureSlots.get(t);r||(r=new Map,this.trackedTextureSlots.set(t,r));const n=r.get(e);return n===s?n:(n&&this.releaseTrackedTextureSlot(t,e,n),this.trackTextureUsage(s),r.set(e,s),s)}static releaseTrackedTextureSlot(t,e,s){const r=this.trackedTextureSlots.get(t);if(r?.get(e)===s&&r.delete(e),this.untrackTextureUsage(s)&&(g||te)){const i=this.getAssignedLODInformation(s);console.log(`[gltf-progressive] Disposed old texture LOD ${i?.level??"?"} for ${t.name||t.type}.${e}`,s.uuid)}}parser;url;constructor(t){const e=t.options.path;g&&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?.[W];return e?(this._isLoadingMesh=!0,this.parser.getDependency("mesh",t).then(s=>(this._isLoadingMesh=!1,s&&p.registerMesh(this.url,e.guid,s,e.lods?.length,0,e),s))):null};afterRoot(t){return g&&console.log("AFTER",this.url,t),this.parser.json.textures?.forEach((e,s)=>{if(e?.extensions){const r=e?.extensions[W];if(r){if(!r.lods){g&&console.warn("Texture has no LODs",r);return}let n=!1;for(const i of this.parser.associations.keys())i.isTexture===!0&&this.parser.associations.get(i)?.textures===s&&(n=!0,p.registerTexture(this.url,i,r.lods?.length,s,r));n||this.parser.getDependency("texture",s).then(i=>{i&&p.registerTexture(this.url,i,r.lods?.length,s,r)})}}}),this.parser.json.meshes?.forEach((e,s)=>{if(e?.extensions){const r=e?.extensions[W];if(r&&r.lods){for(const n of this.parser.associations.keys())if(n.isMesh){const i=this.parser.associations.get(n);i?.meshes===s&&p.registerMesh(this.url,r.guid,n,r.lods.length,i.primitives,r)}}}}),null}static registerTexture=(t,e,s,r,n)=>{if(!e){g&&console.error("!! gltf-progressive: Called register texture without texture");return}if(g){const a=e.image?.width||e.source?.data?.width||0,l=e.image?.height||e.source?.data?.height||0;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)}e.source&&(e.source[fe]=n);const i=n.guid;p.assignLODInformation(t,e,i,s,r),p.lodInfos.set(i,n),p.lowresCache.set(i,new WeakRef(e))};static registerMesh=(t,e,s,r,n,i)=>{const a=s.geometry;if(!a){g&&console.warn("gltf-progressive: Register mesh without geometry");return}a.userData||(a.userData={}),g&&console.log("> Progressive: register mesh "+s.name,{index:n,uuid:s.uuid},i,s),p.assignLODInformation(t,a,e,r,n),p.lodInfos.set(e,i);let u=p.lowresCache.get(e)?.deref();u?u.push(s.geometry):u=[s.geometry],p.lowresCache.set(e,new WeakRef(u)),r>0&&!Q(s)&&Je(s,a);for(const c of q)c.onRegisteredNewMesh?.(s,i)};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(),this.trackedTextureSlots=new WeakMap,this.pendingTextureSlotRequests=new WeakMap,this.latestTextureSlotRequests=new WeakMap,this.textureSlotRequestId=0}}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=p.cache.get(t);(g||te)&&console.debug(`[gltf-progressive] Memory: Resource GC'd
2
- ${t}`),e instanceof WeakRef&&(e.deref()||(p.cache.delete(t),(g||te)&&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),g==="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(g==="verbose"||te)&&n("[gltf-progressive] Memory: Untrack untracked texture (dispose immediately)",0),t.dispose(),!0;const r=s-1;if(r<=0)return this.textureRefCounts.delete(e),(g||te)&&n("[gltf-progressive] Memory: Dispose texture",r),t.dispose(),!0;return this.textureRefCounts.set(e,r),g==="verbose"&&n("[gltf-progressive] Memory: Untrack texture",r),!1;function n(i,a){const l=t.image?.width||t.source?.data?.width||0,u=t.image?.height||t.source?.data?.height||0,c=l&&u?`${l}x${u}`:"N/A";let d="N/A";l&&u&&(d=`~${(He(t)/(1024*1024)).toFixed(2)} MB`),console.log(`${i} — ${t.name} ${c} (${d}), refCount: ${s} → ${a}
3
- ${e}`)}}static workers=[];static _workersIndex=0;static async getOrLoadLOD(t,e,s){const r=g=="verbose",n=this.getAssignedLODInformation(t);if(!n)return g&&console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`,t),null;const i=n?.key;let a;if(t.isTexture===!0){const u=t;u.source&&u.source[fe]&&(a=u.source[fe])}if(a||(a=p.lodInfos.get(i)),!a)g&&console.warn(`Can not load LOD ${e}: no LOD info found for "${i}" ${t.name}`,t.type,p.lodInfos);else{if(e>0){let d=!1;const h=Array.isArray(a.lods);if(h&&e>=a.lods.length?d=!0:h||(d=!0),d){const m=this.lowresCache.get(i);if(m){const T=m.deref();if(T)return T;this.lowresCache.delete(i),g&&console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${i}`)}return null}}const u=Array.isArray(a.lods)?a.lods[e]?.path:a.lods;if(!u)return g&&!a["missing:uri"]&&(a["missing:uri"]=!0,console.warn("Missing uri for progressive asset for LOD "+e,a)),null;const c=Ke(n.url,u);if(c.endsWith(".glb")||c.endsWith(".gltf")){if(!a.guid)return console.warn("missing pointer for glb/gltf texture",a),null;const d=c+"_"+a.guid,h=await this.tryResolveLODCacheEntry(this.cache.get(d),d,c,t,e,r);if(h.found)return h.value;if(s?.isCurrent?.()===!1)return r&&console.log(`Skipping stale LOD ${e} request before queue: ${c}`),null;const m=await this.queue.slot(c);if(s?.isCurrent?.()===!1)return r&&console.log(`Skipping stale LOD ${e} request after queue: ${c}`),null;const T=await this.tryResolveLODCacheEntry(this.cache.get(d),d,c,t,e,r);if(T.found)return T.value;if(!m.use)return g&&console.log(`LOD ${e} was aborted: ${c}`),null;const x=a,w=new Promise(async(M,V)=>{if(ot){const y=await(await rt({})).load(c);if(y.textures.length>0)for(const _ of y.textures){let O=_.texture;return p.assignLODInformation(n.url,O,i,e,void 0),t instanceof f.Texture&&(O=this.copySettings(t,O)),O&&(O.guid=x.guid),M(O)}if(y.geometries.length>0){const _=new Array;for(const O of y.geometries){const B=O.geometry;p.assignLODInformation(n.url,B,i,e,O.primitiveIndex),_.push(B)}return M(_)}return M(null)}const L=new Y.GLTFLoader;Le(L),g&&(await new Promise(b=>setTimeout(b,1e3)),r&&console.warn("Start loading (delayed) "+c,x.guid));let S=c;if(x&&Array.isArray(x.lods)){const b=x.lods[e];b.hash&&(S+="?v="+b.hash)}const D=await L.loadAsync(S).catch(b=>(console.error(`Error loading LOD ${e} from ${c}
4
- `,b),M(null)));if(!D)return M(null);const R=D.parser;r&&console.log("Loading finished "+c,x.guid);let P=0;if(D.parser.json.textures){let b=!1;for(const y of D.parser.json.textures){if(y?.extensions){const _=y?.extensions[W];if(_?.guid&&_.guid===x.guid){b=!0;break}}P++}if(b){let y=await R.getDependency("texture",P);return y&&p.assignLODInformation(n.url,y,i,e,void 0),r&&console.log('change "'+t.name+'" → "'+y.name+'"',c,P,y,d),t instanceof f.Texture&&(y=this.copySettings(t,y)),y&&(y.guid=x.guid),M(y)}else g&&console.warn("Could not find texture with guid",x.guid,D.parser.json)}if(P=0,D.parser.json.meshes){let b=!1;for(const y of D.parser.json.meshes){if(y?.extensions){const _=y?.extensions[W];if(_?.guid&&_.guid===x.guid){b=!0;break}}P++}if(b){const y=await R.getDependency("mesh",P);if(r&&console.log(`Loaded Mesh "${y.name}"`,c,P,y,d),y.isMesh===!0){const _=y.geometry;return p.assignLODInformation(n.url,_,i,e,0),M(_)}else{const _=new Array;for(let O=0;O<y.children.length;O++){const B=y.children[O];if(B.isMesh===!0){const ne=B.geometry;p.assignLODInformation(n.url,ne,i,e,O),_.push(ne)}}return M(_)}}else g&&console.warn("Could not find mesh with guid",x.guid,D.parser.json)}return M(null)});this.cache.set(d,w),m.use(w);const v=await w;return v!=null?v instanceof f.Texture?(this.cache.set(d,new WeakRef(v)),p._resourceRegistry.register(v,d)):Array.isArray(v)?this.cache.set(d,Promise.resolve(v)):this.cache.set(d,Promise.resolve(v)):this.cache.set(d,Promise.resolve(null)),v}else if(t instanceof f.Texture){if(s?.isCurrent?.()===!1)return r&&console.log(`Skipping stale texture LOD ${e} request: ${c}`),null;r&&console.log("Load texture from uri: "+c);const h=await new f.TextureLoader().loadAsync(c);return s?.isCurrent?.()===!1?(h?.dispose(),null):(h?(h.guid=a.guid,h.flipY=!1,h.needsUpdate=!0,h.colorSpace=t.colorSpace,r&&console.log(a,h)):g&&console.warn("failed loading",c),h)}}return null}static async tryResolveLODCacheEntry(t,e,s,r,n,i){if(t===void 0)return{found:!1};if(i&&console.log(`LOD ${n} was already loading/loaded: ${e}`),t instanceof WeakRef){const u=t.deref();if(u){let c=u,d=!1;if(c instanceof f.Texture&&r instanceof f.Texture?c.image?.data||c.source?.data?c=this.copySettings(r,c):d=!0:c instanceof f.BufferGeometry&&r instanceof f.BufferGeometry&&(c.attributes.position?.array||(d=!0)),!d)return{found:!0,value:c}}return this.cache.delete(e),g&&console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${e}`),{found:!1}}let a=await t.catch(u=>(console.error(`Error loading LOD ${n} from ${s}
5
- `,u),null)),l=!1;return a==null||(a instanceof f.Texture&&r instanceof f.Texture?a.image?.data||a.source?.data?a=this.copySettings(r,a):(l=!0,this.cache.delete(e)):a instanceof f.BufferGeometry&&r instanceof f.BufferGeometry&&(a.attributes.position?.array||(l=!0,this.cache.delete(e)))),l?{found:!1}:{found:!0,value:a}}static _queue;static get queue(){return this._queue??=new je(ve()?20:50,{debug:g!=!1})}static assignLODInformation(t,e,s,r,n){if(!e)return;e.userData||(e.userData={});const i=new it(t,s,r,n);e.userData.LODS=i,"source"in e&&typeof e.source=="object"&&(e.source.LODS=i)}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?(g==="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 it{url;key;level;index;constructor(t,e,s,r){this.url=t,this.key=e,this.level=s,r!=null&&(this.index=r)}}class oe{static addPromise=(t,e,s,r)=>{r.forEach(n=>{n.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(n=>{this._resolve=n}),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){g&&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)){const r=this._seen.get(e);if(r>=this._maxPromisesPerObject){g&&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 A=z("debugprogressive"),at=A==="colors",lt=z("noprogressive"),he=Symbol("Needle:LODSManager"),ge=Symbol("Needle:LODState"),U=Symbol("Needle:CurrentLOD"),C={mesh_lod:-1,texture_lod:-1},ut=new f.Color,_e=[3526751,11065402,15978811,15897394,15749691,11032304,4827122,3332036,16739229,7306743,14053330,3516499,12035359,14703919,3963096,42662,14100029,8344319,4633680,16229681,3120096,12076434,9083434,2060171,15751837,10182117,48121,62932,16704576,15817653,5083278,5592405],pe=new f.Box3,F=new f.Box3,Oe=new f.Box3,ct=new f.Vector3,dt=new f.Vector3,ft=new f.Matrix4,G=new f.Vector3,K=new f.Vector3,j=new f.Vector3,H=new f.Vector3;function ht(o,t){const e=o.min,s=o.max,r=(e.x+s.x)*.5,n=(e.y+s.y)*.5;return G.set(r,n,e.z).applyMatrix4(t).z<0}function $e(o){const{geometry:t,matrixWorld:e,camera:s,projectionScreenMatrix:r,desiredDensity:n,canvasHeight:i=0,currentLevel:a=-1,xrEnabled:l=!1,debugDrawLine:u,warnMissingPrimitiveDensities:c=!1}=o,d=p.getMeshLODExtension(t)?.lods,h=p.getPrimitiveIndex(t),m=o.target??{level:a,primitiveIndex:h,screenCoverage:0,screenspaceVolume:new f.Vector3,centrality:1};if(m.level=a,m.primitiveIndex=h,m.screenCoverage=0,m.screenspaceVolume.set(0,0,0),m.centrality=1,!d?.length)return m;let T=o.boundingBox??t.boundingBox;if(T||(t.computeBoundingBox(),T=t.boundingBox),!T)return m;if(pe.copy(T).applyMatrix4(e),s.isPerspectiveCamera&&ht(pe,r))return m.level=0,m.screenCoverage=1/0,m.screenspaceVolume.set(1/0,1/0,1/0),m;if(F.copy(pe).applyMatrix4(r),l&&s.isPerspectiveCamera&&s.fov>70){const L=F.min,S=F.max;let D=L.x,R=L.y,P=S.x,b=S.y;const y=2,_=1.5,O=(L.x+S.x)*.5,B=(L.y+S.y)*.5;D=(D-O)*y+O,R=(R-B)*y+B,P=(P-O)*y+O,b=(b-B)*y+B;const ne=D<0&&P>0?0:Math.min(Math.abs(L.x),Math.abs(S.x)),Ue=R<0&&b>0?0:Math.min(Math.abs(L.y),Math.abs(S.y)),ue=Math.max(ne,Ue);m.centrality=(_-ue)*(_-ue)*(_-ue)}const x=F.getSize(ct);x.multiplyScalar(.5),globalThis.screen?.availHeight>0&&i>0&&x.multiplyScalar(i/globalThis.screen.availHeight),s.isPerspectiveCamera&&(x.x*=s.aspect),Oe.copy(T).applyMatrix4(e).applyMatrix4(s.matrixWorldInverse);const w=Oe.getSize(dt),v=Math.max(x.x,x.y),M=Math.max(w.x,w.y);v!==0&&M!==0&&(x.z=w.z/M*v);const V=Math.max(x.x,x.y,x.z)*m.centrality;if(m.screenCoverage=V,m.screenspaceVolume.copy(x),V<=0)return m;if(u){const L=ft.copy(r);L.invert(),G.copy(F.min),K.copy(F.max),K.x=G.x,j.copy(F.max),j.y=G.y,H.copy(F.max);const S=(G.z+H.z)*.5;G.z=K.z=j.z=H.z=S,G.applyMatrix4(L),K.applyMatrix4(L),j.applyMatrix4(L),H.applyMatrix4(L),u(G,K,255),u(G,j,255),u(K,H,255),u(j,H,255)}for(let L=0;L<d.length;L++){const S=d[L],D=S.densities?.[h]||S.density||1e-5;if(h>0&&c&&Ae()&&!S.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.")),D/V<n){m.level=L;break}}return m}let re=class N{static debugDrawLine;static getObjectLODState(t){return t[ge]}static addPlugin(t){q.push(t)}static removePlugin(t){const e=q.indexOf(t);e>=0&&q.splice(e,1)}static getPlugins(){return q}static get(t,e){if(t[he])return console.debug("[gltf-progressive] LODsManager already exists for this renderer"),t[he];const s=new N(t,{engine:"unknown",...e});return t[he]=s,s}renderer;context;projectionScreenMatrix=new f.Matrix4;get plugins(){return q}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 oe(this.#r,{...t});this._newPromiseGroups.push(s);const r=performance.now();return s.ready.finally(()=>{const n=this._newPromiseGroups.indexOf(s);n>=0&&(this._newPromiseGroups.splice(n,1),Ae()&&performance.measure("LODsManager:awaitLoading",{start:r,detail:{id:e,name:t?.name,awaited:s.awaitedCount,resolved:s.resolvedCount}}))}),s.ready}trackLoadingPromise(t,e,s){return oe.addPromise(t,e,s,this._newPromiseGroups),s}_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){return t==="changed"?(this._lodchangedlisteners.push(e),()=>{this.removeEventListener(t,e)}):()=>{}}removeEventListener(t,e){let s=!1;if(t==="changed"){const r=this._lodchangedlisteners.indexOf(e);r>=0&&(this._lodchangedlisteners.splice(r,1),s=!0)}return s}constructor(t,e){this.renderer=t,this.context={...e}}#t;#o=new f.Clock;#r=0;#n=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;le(this.renderer),this.renderer.render=function(s,r){const n=e.renderer.getRenderTarget();(n==null||"isXRRenderTarget"in n&&n.isXRRenderTarget)&&(t=0,e.#r+=1,e.#n=e.#o.getDelta(),e.#i+=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));const i=t++;e.#t.call(this,s,r),e.onAfterRender(s,r,i)}}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 n=this.renderer.renderLists.get(t,0).opaque;let i=!0;if(n.length===1){const a=n[0].material;(a.name==="EffectMaterial"||a.name==="CopyShader")&&(i=!1)}if((e.parent&&e.parent.type==="CubeCamera"||s>=1&&e.type==="OrthographicCamera")&&(i=!1),i){if(lt||(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))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 n=this.targetTriangleDensity;for(const l of r){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")){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)));continue}switch(l.material.type){case"LineBasicMaterial":case"LineDashedMaterial":case"PointsMaterial":case"ShadowMaterial":case"MeshDistanceMaterial":case"MeshDepthMaterial":continue}const u=l.object;(u instanceof f.Mesh||u.isMesh)&&this.updateLODs(t,e,u,n)}const i=s.transparent;for(const l of i){const u=l.object;(u instanceof f.Mesh||u.isMesh)&&this.updateLODs(t,e,u,n)}const a=s.transmissive;for(const l of a){const u=l.object;(u instanceof f.Mesh||u.isMesh)&&this.updateLODs(t,e,u,n)}}updateLODs(t,e,s,r){s.userData||(s.userData={});let n=s[ge];if(n||(n=new gt,s[ge]=n),n.frames++<2)return;for(const a of q)a.onBeforeUpdateLOD?.(this.renderer,t,e,s);const i=this.overrideLodLevel!==void 0?this.overrideLodLevel:E;i>=0?(C.mesh_lod=i,C.texture_lod=i):(this.calculateLodLevel(e,s,n,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,i),g&&s.material&&!s.isGizmo&&Ie(s.material),at&&s.material&&!s.isGizmo&&!s.isBatchedMesh&&Be(s.material,C.mesh_lod);for(const a of q)a.onAfterUpdatedLOD?.(this.renderer,t,e,s,C);n.lastLodLevel_Mesh=C.mesh_lod,n.lastLodLevel_Texture=C.texture_lod}loadProgressiveTextures(t,e,s){if(!t)return;if(Array.isArray(t)){for(const i of t)this.loadProgressiveTextures(i,e,s);return}let r=!1;(t[U]===void 0||e<t[U])&&(r=!0);const n=s!==void 0&&s>=0;if(n&&(r=t[U]!=s,e=s),r){t[U]=e;const i=n?{force:!0}:void 0,a=p.assignTextureLOD(t,e,i).then(l=>{this._lodchangedlisteners.forEach(u=>u({type:"texture",level:e,object:t}))});oe.addPromise("texture",t,a,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 n=t.geometry,i=p.assignMeshLOD(t,e).then(a=>(a&&t[U]==e&&n!=t.geometry&&this._lodchangedlisteners.forEach(l=>l({type:"mesh",level:e,object:t})),a));return oe.addPromise("mesh",t,i,this._newPromiseGroups),i}return Promise.resolve(null)}_sphere=new f.Sphere;_tempWorldPosition=new f.Vector3;static skinnedMeshBoundsFrameOffsetCounter=0;static $skinnedMeshBoundsOffset=Symbol("gltf-progressive-skinnedMeshBoundsOffset");calculateLodLevel(t,e,s,r,n){if(!e){n.mesh_lod=-1,n.texture_lod=-1;return}if(!t){n.mesh_lod=-1,n.texture_lod=-1;return}let a=10+1,l=!1;if(A&&e["DEBUG:LOD"]!=null)return e["DEBUG:LOD"];const u=p.getMeshLODExtension(e.geometry)?.lods,c=p.getPrimitiveIndex(e.geometry),d=u&&u.length>0,h=p.getMaterialMinMaxLODsCount(e.material),m=h.min_count!==1/0&&h.min_count>=0&&h.max_count>=0;if(!d&&!m){n.mesh_lod=0,n.texture_lod=0;return}d||(l=!0,a=0);const T=this.renderer.domElement.clientHeight||this.renderer.domElement.height;let x=e.geometry.boundingBox;if(e.type==="SkinnedMesh"){const w=e;if(!w.boundingBox)w.computeBoundingBox();else if(this.skinnedMeshAutoUpdateBoundsInterval>0){if(!w[N.$skinnedMeshBoundsOffset]){const M=N.skinnedMeshBoundsFrameOffsetCounter++;w[N.$skinnedMeshBoundsOffset]=M}const v=w[N.$skinnedMeshBoundsOffset];if((s.frames+v)%this.skinnedMeshAutoUpdateBoundsInterval===0){const M=Q(w),V=w.geometry;M&&(w.geometry=M),w.computeBoundingBox(),w.geometry=V}}x=w.boundingBox}if(x){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 M=t.getWorldPosition(this._tempWorldPosition);if(this._sphere.containsPoint(M)){n.mesh_lod=0,n.texture_lod=0;return}}const w=$e({geometry:e.geometry,matrixWorld:e.matrixWorld,camera:t,projectionScreenMatrix:this.projectionScreenMatrix,desiredDensity:r,canvasHeight:T,currentLevel:s.lastLodLevel_Mesh,boundingBox:x,xrEnabled:this.renderer.xr.enabled,debugDrawLine:A?N.debugDrawLine:void 0,warnMissingPrimitiveDensities:!0});if(s.lastCentrality=w.centrality,s.lastScreenCoverage=w.screenCoverage,s.lastScreenspaceVolume.copy(w.screenspaceVolume),w.screenCoverage===1/0){n.mesh_lod=0,n.texture_lod=0;return}w.level>=0&&w.level<a&&(a=w.level,l=!0)}if(l?n.mesh_lod=a:n.mesh_lod=s.lastLodLevel_Mesh,A&&n.mesh_lod!=s.lastLodLevel_Mesh){const v=u?.[n.mesh_lod];v&&console.log(`Mesh LOD changed: ${s.lastLodLevel_Mesh} → ${n.mesh_lod} (density: ${v.densities?.[c].toFixed(0)}) | ${e.name}`)}if(m){const w="saveData"in globalThis.navigator&&globalThis.navigator.saveData===!0;if(s.lastLodLevel_Texture<0){if(n.texture_lod=h.max_count-1,A){const v=h.lods[h.max_count-1];A&&console.log(`First Texture LOD ${n.texture_lod} (${v.max_height}px) - ${e.name}`)}}else{const v=s.lastScreenspaceVolume.x+s.lastScreenspaceVolume.y+s.lastScreenspaceVolume.z;let M=s.lastScreenCoverage*4;this.context?.engine==="model-viewer"&&(M*=1.5);const L=T/window.devicePixelRatio*M;let S=!1;for(let D=h.lods.length-1;D>=0;D--){const R=h.lods[D];if(!(w&&R.max_height>=2048)&&!(ve()&&R.max_height>4096)&&(R.max_height>L||!S&&D===0)){if(S=!0,n.texture_lod=D,A&&n.texture_lod<s.lastLodLevel_Texture){const P=R.max_height;console.log(`Texture LOD changed: ${s.lastLodLevel_Texture} → ${n.texture_lod} = ${P}px
8
- Screensize: ${L.toFixed(0)}px, Coverage: ${(100*s.lastScreenCoverage).toFixed(2)}%, Volume ${v.toFixed(1)}
9
- ${e.name}`)}break}}}}else n.texture_lod=0}};class gt{frames=0;lastLodLevel_Mesh=-1;lastLodLevel_Texture=-1;lastScreenCoverage=0;lastScreenspaceVolume=new f.Vector3;lastCentrality=0}function Be(o,t){if(!(t<0)){if(Array.isArray(o)){for(const e of o)Be(e,t);return}"color"in o&&o.color instanceof f.Color&&(o.color.copy(Ge(t,ut)),o.needsUpdate=!0)}}function Ge(o,t){const e=Math.max(0,Math.min(_e.length-1,Math.floor(o)));return t.setHex(_e[e])}const Te=Symbol("NEEDLE_mesh_lod"),ie=Symbol("NEEDLE_texture_lod");let me=null;function qe(){const o=pt();o&&(o.mapURLs(function(t){return Se(),t}),Se(),me?.disconnect(),me=new MutationObserver(t=>{t.forEach(e=>{e.addedNodes.forEach(s=>{s instanceof HTMLElement&&s.tagName.toLowerCase()==="model-viewer"&&We(s)})})}),me.observe(document,{childList:!0,subtree:!0}))}function pt(){if(typeof customElements>"u")return null;const o=customElements.get("model-viewer");return o||(customElements.whenDefined("model-viewer").then(()=>{console.debug("[gltf-progressive] model-viewer defined"),qe()}),null)}function Se(){if(typeof document>"u")return;document.querySelectorAll("model-viewer").forEach(t=>{We(t)})}const be=new WeakSet;let mt=0;function We(o){if(!o||be.has(o))return null;be.add(o),console.debug("[gltf-progressive] found new model-viewer..."+ ++mt+`
10
- `,o.getAttribute("src"));let t=null,e=null,s=null;for(let r=o;r!=null;r=Object.getPrototypeOf(r)){const n=Object.getOwnPropertySymbols(r),i=n.find(u=>u.toString()=="Symbol(renderer)"),a=n.find(u=>u.toString()=="Symbol(scene)"),l=n.find(u=>u.toString()=="Symbol(needsRender)");!t&&i!=null&&(t=o[i].threeRenderer),!e&&a!=null&&(e=o[a]),!s&&l!=null&&(s=o[l])}if(t&&e){let r=function(){if(s){let i=0;const a=setInterval(()=>{if(i++>5){clearInterval(a);return}s?.call(o)},300)}};console.debug("[gltf-progressive] setup model-viewer");const n=re.get(t,{engine:"model-viewer"});return re.addPlugin(new yt),n.enable(),n.addEventListener("changed",()=>{s?.call(o)}),o.addEventListener("model-visibility",i=>{i.detail.visible&&s?.call(o)}),o.addEventListener("load",()=>{r()}),()=>{n.disable()}}return null}class yt{_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[ie]==!0)return;e[ie]=!0;const s=this.tryGetCurrentGLTF(t),r=this.tryGetCurrentModelViewer(t),n=this.getUrl(r);if(n&&s&&e.material){let i=function(l){if(l[ie]==!0)return;l[ie]=!0,l.userData&&(l.userData.LOD=-1);const u=Object.keys(l);for(let c=0;c<u.length;c++){const d=u[c],h=l[d];if(h?.isTexture===!0){const m=h.userData?.associations?.textures;if(m==null)continue;const T=s.parser.json.textures[m];if(!T){console.warn("Texture data not found for texture index "+m);continue}if(T?.extensions?.[W]){const x=T.extensions[W];x&&n&&p.registerTexture(n,h,x.lods.length,m,x)}}}};const a=e.material;if(Array.isArray(a))for(const l of a)i(l);else i(a)}}tryParseMeshLOD(t,e){if(e[Te]==!0)return;e[Te]=!0;const s=this.tryGetCurrentModelViewer(t),r=this.getUrl(s);if(!r)return;const n=e.userData?.gltfExtensions?.[W];if(n&&r){const i=e.uuid;p.registerMesh(r,i,e,0,n.lods.length,n)}}}function xt(...o){let t,e,s,r;switch(o.length){case 2:[s,e]=o,r={};break;case 3:[s,e,r]=o;break;case 4:[t,e,s,r]=o;break;default:throw new Error("Invalid arguments")}le(e),Le(s),Me(s,{progressive:!0,...r?.hints}),s.register(i=>new p(i));const n=re.get(e);return r?.enableLODsManager!==!1&&n.enable(),n}qe();if(!Qe){const o={gltfProgressive:{useNeedleProgressive:xt,LODsManager:re,configureLoader:Me,getRaycastMesh:Q,useRaycastMeshes:Ze}};if(!globalThis.Needle)globalThis.Needle=o;else for(const t in o)globalThis.Needle[t]=o[t]}exports.LODsManager=re;exports.NEEDLE_progressive=p;exports.addDracoAndKTX2Loaders=Le;exports.calculateMeshLODLevel=$e;exports.configureLoader=Me;exports.createLoaders=le;exports.getLODColor=Ge;exports.getRaycastMesh=Q;exports.setDracoDecoderLocation=Pe;exports.setKTX2TranscoderLocation=ke;