@needle-tools/engine 5.1.0-next.1780579670.dramatic-pierogi.b8fa941 → 5.1.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.
- package/CHANGELOG.md +119 -0
- package/components.needle.json +1 -1
- package/dist/gltf-progressive-BGQ-cjly.umd.cjs +10 -0
- package/dist/gltf-progressive-BvQHrERj.min.js +10 -0
- package/dist/{gltf-progressive-CH7aqHHr.js → gltf-progressive-CtYnVfLc.js} +420 -351
- package/dist/{materialx-B85WjP7E.min.js → materialx-B5gKeM65.min.js} +1 -1
- package/dist/{materialx-B47Bz-xs.umd.cjs → materialx-CFRcCYWy.umd.cjs} +1 -1
- package/dist/{materialx-jkHmVPez.js → materialx-DqGmteNO.js} +1 -1
- package/dist/{needle-engine.bundle-BSPk6RkD.min.js → needle-engine.bundle-BUT4ua7U.min.js} +162 -150
- package/dist/{needle-engine.bundle-DCPHDPAd.umd.cjs → needle-engine.bundle-Cb39lTJn.umd.cjs} +158 -146
- package/dist/{needle-engine.bundle-WQeWxmZD.js → needle-engine.bundle-Dv-p5m4z.js} +6956 -6782
- package/dist/needle-engine.d.ts +111 -43
- package/dist/needle-engine.js +611 -609
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/three.js +3 -0
- package/dist/three.min.js +22 -22
- package/dist/three.umd.cjs +24 -24
- package/dist/{vendor-BQ2Vuntm.min.js → vendor-2--bD1c9.min.js} +19 -19
- package/dist/{vendor-BslSKZPo.js → vendor-BNtcIZkv.js} +1141 -1138
- package/dist/{vendor-1UvpPPSB.umd.cjs → vendor-vbe9sv1t.umd.cjs} +20 -20
- package/lib/engine/api.d.ts +1 -1
- package/lib/engine/api.js +1 -1
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/engine_context.js +16 -3
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_init.js +2 -2
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_license.d.ts +7 -7
- package/lib/engine/engine_license.js +72 -72
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_networking_blob.js +3 -3
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_networking_peer.js +19 -13
- package/lib/engine/engine_networking_peer.js.map +1 -1
- package/lib/engine/engine_utils_qrcode.js +2 -2
- package/lib/engine/engine_utils_qrcode.js.map +1 -1
- package/lib/engine/postprocessing/postprocessing.d.ts +2 -1
- package/lib/engine/postprocessing/postprocessing.js +3 -2
- package/lib/engine/postprocessing/postprocessing.js.map +1 -1
- package/lib/engine/webcomponents/WebXRButtons.js +5 -6
- package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +2 -2
- package/lib/engine/webcomponents/needle menu/needle-menu.js +9 -9
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.js +2 -2
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +30 -0
- package/lib/engine/xr/NeedleXRSession.js +121 -17
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/TempXRContext.js +2 -2
- package/lib/engine/xr/TempXRContext.js.map +1 -1
- package/lib/engine/xr/init.js +2 -3
- package/lib/engine/xr/init.js.map +1 -1
- package/lib/engine-components/Component.js +14 -1
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/ContactShadows.d.ts +7 -2
- package/lib/engine-components/ContactShadows.js +65 -45
- package/lib/engine-components/ContactShadows.js.map +1 -1
- package/lib/engine-components/GroundProjection.d.ts +6 -0
- package/lib/engine-components/GroundProjection.js +69 -7
- package/lib/engine-components/GroundProjection.js.map +1 -1
- package/lib/engine-components/NeedleMenu.d.ts +1 -1
- package/lib/engine-components/NeedleMenu.js +5 -2
- package/lib/engine-components/NeedleMenu.js.map +1 -1
- package/lib/engine-components/SceneSwitcher.d.ts +2 -0
- package/lib/engine-components/SceneSwitcher.js +26 -14
- package/lib/engine-components/SceneSwitcher.js.map +1 -1
- package/lib/engine-components/SeeThrough.d.ts +39 -0
- package/lib/engine-components/SeeThrough.js +69 -1
- package/lib/engine-components/SeeThrough.js.map +1 -1
- package/lib/engine-components/Skybox.js +9 -0
- package/lib/engine-components/Skybox.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -0
- package/lib/engine-components/api.js +1 -0
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js +9 -5
- package/lib/engine-components/export/usdz/extensions/behavior/BehaviourComponents.js.map +1 -1
- package/lib/engine-components/particlesystem/ParticleSystem.js +4 -0
- package/lib/engine-components/particlesystem/ParticleSystem.js.map +1 -1
- package/lib/engine-components/postprocessing/Volume.d.ts +1 -9
- package/lib/engine-components/postprocessing/Volume.js +2 -14
- package/lib/engine-components/postprocessing/Volume.js.map +1 -1
- package/lib/engine-components/webxr/WebXR.js +25 -14
- package/lib/engine-components/webxr/WebXR.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +3 -0
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +4 -4
- package/plugins/common/license.js +4 -4
- package/plugins/types/userconfig.d.ts +10 -0
- package/plugins/vite/build-pipeline.d.ts +19 -0
- package/plugins/vite/build-pipeline.js +67 -5
- package/plugins/vite/license.js +4 -4
- package/src/engine/api.ts +1 -1
- package/src/engine/engine_context.ts +15 -2
- package/src/engine/engine_gameobject.ts +1 -1
- package/src/engine/engine_init.ts +2 -2
- package/src/engine/engine_license.ts +69 -69
- package/src/engine/engine_networking_blob.ts +3 -3
- package/src/engine/engine_networking_peer.ts +12 -13
- package/src/engine/engine_utils_qrcode.ts +2 -2
- package/src/engine/postprocessing/postprocessing.ts +3 -2
- package/src/engine/webcomponents/WebXRButtons.ts +5 -6
- package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +10 -10
- package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
- package/src/engine/webcomponents/needle-engine.ts +2 -2
- package/src/engine/xr/NeedleXRSession.ts +124 -20
- package/src/engine/xr/TempXRContext.ts +2 -2
- package/src/engine/xr/init.ts +2 -3
- package/src/engine-components/Component.ts +13 -1
- package/src/engine-components/ContactShadows.ts +75 -49
- package/src/engine-components/GroundProjection.ts +71 -7
- package/src/engine-components/NeedleMenu.ts +6 -3
- package/src/engine-components/SceneSwitcher.ts +23 -13
- package/src/engine-components/SeeThrough.ts +75 -1
- package/src/engine-components/Skybox.ts +8 -0
- package/src/engine-components/api.ts +1 -0
- package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
- package/src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +12 -5
- package/src/engine-components/particlesystem/ParticleSystem.ts +4 -0
- package/src/engine-components/postprocessing/Volume.ts +2 -13
- package/src/engine-components/webxr/WebXR.ts +23 -15
- package/src/engine-components/webxr/WebXRImageTracking.ts +1 -0
- package/dist/gltf-progressive-9tlS5iCx.umd.cjs +0 -10
- package/dist/gltf-progressive-CVg80COO.min.js +0 -10
|
@@ -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
|
|
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 (
|
|
628
|
+
if (activeSceneInstance) destroy(activeSceneInstance, true, false);
|
|
628
629
|
}
|
|
629
630
|
else {
|
|
630
|
-
GameObject.remove(
|
|
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.
|
|
667
|
-
|
|
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
|
|
741
|
-
const sceneListener = this.tryGetSceneEventListener(
|
|
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
|
|
|
@@ -56,6 +56,14 @@ function applyRotationAttribute(attr: string, target: { set(x: number, y: number
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function applyTexture(context: IContext, texture: Texture, isBackground: boolean, isEnvironment: boolean) {
|
|
59
|
+
// Textures load asynchronously. The context can be disposed/torn down while a
|
|
60
|
+
// load is in flight (element removed, context disposed, scene switched) — at
|
|
61
|
+
// which point context.scene is set to null on dispose. Bail instead of
|
|
62
|
+
// dereferencing it (the rest of this function also touches context.scene).
|
|
63
|
+
if (!context.scene) {
|
|
64
|
+
if (debug) console.warn("Skybox: skipping applyTexture — context was torn down before the texture finished loading");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
59
67
|
if (!(texture instanceof CubeTexture || texture instanceof CompressedCubeTexture)
|
|
60
68
|
&& texture.mapping !== CubeUVReflectionMapping) {
|
|
61
69
|
texture.mapping = EquirectangularRefractionMapping;
|
|
@@ -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 {
|
|
6
|
+
import { __dSkNu } 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 (!
|
|
280
|
+
if (!__dSkNu()) {
|
|
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 (!
|
|
685
|
+
if (!__dSkNu()) {
|
|
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 (!
|
|
700
|
+
if (!__dSkNu()) {
|
|
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";
|
|
@@ -11,6 +11,7 @@ import { Animation } from "../../../../Animation.js";
|
|
|
11
11
|
import { Animator } from "../../../../Animator.js";
|
|
12
12
|
import { AudioSource } from "../../../../AudioSource.js";
|
|
13
13
|
import { Behaviour, GameObject } from "../../../../Component.js";
|
|
14
|
+
import type { ComponentInit } from "../../../../../engine/engine_types.js";
|
|
14
15
|
import { Rigidbody } from "../../../../RigidBody.js";
|
|
15
16
|
import type { IPointerClickHandler, PointerEventData } from "../../../../ui/PointerEvents.js";
|
|
16
17
|
import { makeNameSafeForUSD, USDDocument, USDObject, USDZExporterContext } from "../../ThreeUSDZExporter.js";
|
|
@@ -811,13 +812,16 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
811
812
|
|
|
812
813
|
ensureAudioSource() {
|
|
813
814
|
if (!this.target) {
|
|
814
|
-
const
|
|
815
|
+
const initializeAudioSource: ComponentInit<AudioSource> = {
|
|
816
|
+
spatialBlend: 1,
|
|
817
|
+
volume: 1,
|
|
818
|
+
loop: false,
|
|
819
|
+
clip: this.clip,
|
|
820
|
+
preload: true,
|
|
821
|
+
};
|
|
822
|
+
const newAudioSource = this.gameObject.addComponent(AudioSource, initializeAudioSource);
|
|
815
823
|
if (newAudioSource) {
|
|
816
824
|
this.target = newAudioSource;
|
|
817
|
-
newAudioSource.spatialBlend = 1;
|
|
818
|
-
newAudioSource.volume = 1;
|
|
819
|
-
newAudioSource.loop = false;
|
|
820
|
-
newAudioSource.preload = true;
|
|
821
825
|
}
|
|
822
826
|
}
|
|
823
827
|
}
|
|
@@ -828,6 +832,9 @@ export class PlayAudioOnClick extends Behaviour implements IPointerClickHandler,
|
|
|
828
832
|
label: "Play audio: " + (this.clip || this.target?.clip || "unknown clip"),
|
|
829
833
|
hidden: false,
|
|
830
834
|
})
|
|
835
|
+
|
|
836
|
+
this.ensureAudioSource();
|
|
837
|
+
|
|
831
838
|
}
|
|
832
839
|
onDisable(): void {
|
|
833
840
|
this.context.accessibility.updateElement(this, {
|
|
@@ -593,6 +593,10 @@ class ColorBehaviour extends ParticleSystemBaseBehaviour {
|
|
|
593
593
|
col.copy(this.system.main.startColor.evaluate(Math.random()));
|
|
594
594
|
if (materialColor?.color) {
|
|
595
595
|
tempColor.copy(materialColor.color);
|
|
596
|
+
// THREE.Color has no alpha — the material's color alpha is stored in
|
|
597
|
+
// material.opacity. Without this, a particle material's alpha/opacity
|
|
598
|
+
// was ignored (reported externally). multiply() propagates alpha.
|
|
599
|
+
tempColor.alpha = materialColor.opacity ?? 1;
|
|
596
600
|
col.multiply(tempColor)
|
|
597
601
|
}
|
|
598
602
|
col.convertLinearToSRGB();
|
|
@@ -79,16 +79,6 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
|
|
|
79
79
|
@serializeable()
|
|
80
80
|
multisampling: "auto" | number = "auto";
|
|
81
81
|
|
|
82
|
-
/** When enabled, the device pixel ratio will be gradually reduced when FPS is low
|
|
83
|
-
* and restored when performance recovers. This helps maintain smooth frame rates
|
|
84
|
-
* on devices where full retina resolution is too expensive for postprocessing.
|
|
85
|
-
* Pushed to `context.postprocessing.adaptiveResolution` when this Volume is active.
|
|
86
|
-
* Disable this if you need a fixed resolution and prefer consistent quality over frame rate.
|
|
87
|
-
* @default true
|
|
88
|
-
*/
|
|
89
|
-
@serializeable()
|
|
90
|
-
adaptiveResolution: boolean = true;
|
|
91
|
-
|
|
92
82
|
/**
|
|
93
83
|
* Add a post processing effect to this Volume and push it to the core postprocessing stack.
|
|
94
84
|
*/
|
|
@@ -195,7 +185,7 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
|
|
|
195
185
|
this.pushEffectsToCore();
|
|
196
186
|
this._isDirty = false;
|
|
197
187
|
}
|
|
198
|
-
// Push config changes (user may change multisampling
|
|
188
|
+
// Push config changes (user may change multisampling at runtime)
|
|
199
189
|
this.syncConfigToCore();
|
|
200
190
|
|
|
201
191
|
// Process queued editor modifications after the handler is ready
|
|
@@ -242,11 +232,10 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
|
|
|
242
232
|
this._activeEffects.length = 0;
|
|
243
233
|
}
|
|
244
234
|
|
|
245
|
-
/** Push multisampling
|
|
235
|
+
/** Push multisampling config to the core stack */
|
|
246
236
|
private syncConfigToCore() {
|
|
247
237
|
const pp = this.context.postprocessing;
|
|
248
238
|
pp.multisampling = this.multisampling;
|
|
249
|
-
pp.adaptiveResolution = this.adaptiveResolution;
|
|
250
239
|
}
|
|
251
240
|
|
|
252
241
|
private _applyPostQueue() {
|
|
@@ -237,22 +237,30 @@ export class WebXR extends Behaviour {
|
|
|
237
237
|
// Showing the QuickLook button depends on whether we're on iOS or visionOS –
|
|
238
238
|
// on iOS we have AppClip support, so we don't need QuickLook button there,
|
|
239
239
|
// while on visionOS we use QuickLook for AR experiences for now (unless it happens to have WebXR support by then).
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this._usdzExporter = GameObject.addComponent(this.gameObject, USDZExporter);
|
|
250
|
-
this._usdzExporter.objectToExport = this.context.scene;
|
|
251
|
-
this._usdzExporter.autoExportAnimations = true;
|
|
252
|
-
this._usdzExporter.autoExportAudioSources = true;
|
|
253
|
-
}
|
|
240
|
+
const addUsdzExporterIfMissing = () => {
|
|
241
|
+
const existingUSDZExporter = GameObject.findObjectOfType(USDZExporter);
|
|
242
|
+
if (!existingUSDZExporter) {
|
|
243
|
+
// if no USDZ Exporter is found we add one and assign the scene to be exported
|
|
244
|
+
if (debug) console.log("WebXR: Adding USDZExporter");
|
|
245
|
+
this._usdzExporter = GameObject.addComponent(this.gameObject, USDZExporter);
|
|
246
|
+
this._usdzExporter.objectToExport = this.context.scene;
|
|
247
|
+
this._usdzExporter.autoExportAnimations = true;
|
|
248
|
+
this._usdzExporter.autoExportAudioSources = true;
|
|
254
249
|
}
|
|
255
|
-
}
|
|
250
|
+
};
|
|
251
|
+
// QuickLook is added when explicitly requested, or as the visionOS fallback when
|
|
252
|
+
// WebXR AR isn't available. Only probe XR support on visionOS (the only case that
|
|
253
|
+
// consumes the result) and go through the cached NeedleXRSession.isARSupported() so
|
|
254
|
+
// we don't eagerly hit navigator.xr on every enable — that probe trips the
|
|
255
|
+
// `xr-spatial-tracking` Permissions-Policy in embeds that weren't granted it.
|
|
256
|
+
if (this.useQuicklookExport) {
|
|
257
|
+
addUsdzExporterIfMissing();
|
|
258
|
+
}
|
|
259
|
+
else if (DeviceUtilities.isVisionOS()) {
|
|
260
|
+
NeedleXRSession.isARSupported().then((arSupported) => {
|
|
261
|
+
if (!arSupported) addUsdzExporterIfMissing();
|
|
262
|
+
});
|
|
263
|
+
}
|
|
256
264
|
|
|
257
265
|
this.handleCreatingHTML();
|
|
258
266
|
this.handleOfferSession();
|
|
@@ -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;
|