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