@needle-tools/gltf-progressive 3.0.0 → 3.1.0-next.f550970
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 +6 -1
- package/gltf-progressive.js +776 -605
- package/gltf-progressive.min.js +7 -7
- package/gltf-progressive.umd.cjs +7 -7
- package/lib/extension.d.ts +1 -0
- package/lib/extension.js +65 -27
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/{lods_manager.d.ts → lods.manager.d.ts} +17 -3
- package/lib/{lods_manager.js → lods.manager.js} +64 -20
- package/lib/lods.promise.d.ts +61 -0
- package/lib/lods.promise.js +101 -0
- package/lib/plugins/modelviewer.js +1 -1
- package/lib/utils.internal.d.ts +21 -0
- package/lib/utils.internal.js +57 -0
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/lib/extension.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BufferGeometry, Mesh, Texture, TextureLoader } from "three";
|
|
2
2
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
3
|
import { addDracoAndKTX2Loaders } from "./loaders.js";
|
|
4
|
-
import { resolveUrl } from "./utils.internal.js";
|
|
4
|
+
import { PromiseQueue, resolveUrl } from "./utils.internal.js";
|
|
5
5
|
import { getRaycastMesh, registerRaycastMesh } from "./utils.js";
|
|
6
6
|
// All of this has to be removed
|
|
7
7
|
// import { getRaycastMesh, setRaycastMesh } from "../../engine_physics.js";
|
|
@@ -309,8 +309,10 @@ export class NEEDLE_progressive {
|
|
|
309
309
|
}
|
|
310
310
|
return NEEDLE_progressive.getOrLoadLOD(current, level).then(tex => {
|
|
311
311
|
// this can currently not happen
|
|
312
|
-
if (Array.isArray(tex))
|
|
312
|
+
if (Array.isArray(tex)) {
|
|
313
|
+
console.warn("Progressive: Got an array of textures for a texture slot, this should not happen", tex);
|
|
313
314
|
return null;
|
|
315
|
+
}
|
|
314
316
|
if (tex?.isTexture === true) {
|
|
315
317
|
if (tex != current) {
|
|
316
318
|
if (material && slot) {
|
|
@@ -375,6 +377,20 @@ export class NEEDLE_progressive {
|
|
|
375
377
|
return mesh;
|
|
376
378
|
});
|
|
377
379
|
};
|
|
380
|
+
// private _isLoadingTexture;
|
|
381
|
+
// loadTexture = (textureIndex: number) => {
|
|
382
|
+
// if (this._isLoadingTexture) return null;
|
|
383
|
+
// const ext = this.parser.json.textures[textureIndex]?.extensions?.[EXTENSION_NAME] as NEEDLE_ext_progressive_texture;
|
|
384
|
+
// if (!ext) return null;
|
|
385
|
+
// this._isLoadingTexture = true;
|
|
386
|
+
// return this.parser.getDependency("texture", textureIndex).then(tex => {
|
|
387
|
+
// this._isLoadingTexture = false;
|
|
388
|
+
// if (tex) {
|
|
389
|
+
// NEEDLE_progressive.registerTexture(this.url, tex as Texture, ext.lods?.length, textureIndex, ext);
|
|
390
|
+
// }
|
|
391
|
+
// return tex;
|
|
392
|
+
// });
|
|
393
|
+
// }
|
|
378
394
|
afterRoot(gltf) {
|
|
379
395
|
if (debug)
|
|
380
396
|
console.log("AFTER", this.url, gltf);
|
|
@@ -440,13 +456,16 @@ export class NEEDLE_progressive {
|
|
|
440
456
|
* Register a texture with LOD information
|
|
441
457
|
*/
|
|
442
458
|
static registerTexture = (url, tex, level, index, ext) => {
|
|
443
|
-
if (debug)
|
|
444
|
-
console.log("> Progressive: register texture", index, tex.name, tex.uuid, tex, ext);
|
|
445
459
|
if (!tex) {
|
|
446
460
|
if (debug)
|
|
447
|
-
console.error("gltf-progressive:
|
|
461
|
+
console.error("gltf-progressive: Called register texture without texture");
|
|
448
462
|
return;
|
|
449
463
|
}
|
|
464
|
+
if (debug) {
|
|
465
|
+
const width = tex.image?.width || tex.source?.data?.width || 0;
|
|
466
|
+
const height = tex.image?.height || tex.source?.data?.height || 0;
|
|
467
|
+
console.log(`> Progressive: register texture[${index}] "${tex.name || tex.uuid}", Current: ${width}x${height}, Max: ${ext.lods[0]?.width}x${ext.lods[0]?.height}, uuid: ${tex.uuid}`, ext, tex);
|
|
468
|
+
}
|
|
450
469
|
// Put the extension info into the source (seems like tiled textures are cloned and the userdata etc is not properly copied BUT the source of course is not cloned)
|
|
451
470
|
// see https://github.com/needle-tools/needle-engine-support/issues/133
|
|
452
471
|
if (tex.source)
|
|
@@ -494,25 +513,27 @@ export class NEEDLE_progressive {
|
|
|
494
513
|
static async getOrLoadLOD(current, level) {
|
|
495
514
|
const debugverbose = debug == "verbose";
|
|
496
515
|
/** this key is used to lookup the LOD information */
|
|
497
|
-
const LOD = current
|
|
516
|
+
const LOD = this.getAssignedLODInformation(current);
|
|
498
517
|
if (!LOD) {
|
|
518
|
+
if (debug)
|
|
519
|
+
console.warn(`[gltf-progressive] No LOD information found: ${current.name}, uuid: ${current.uuid}, type: ${current.type}`, current);
|
|
499
520
|
return null;
|
|
500
521
|
}
|
|
501
522
|
const LODKEY = LOD?.key;
|
|
502
|
-
let
|
|
523
|
+
let lodInfo;
|
|
503
524
|
// See https://github.com/needle-tools/needle-engine-support/issues/133
|
|
504
525
|
if (current.isTexture === true) {
|
|
505
526
|
const tex = current;
|
|
506
527
|
if (tex.source && tex.source[$progressiveTextureExtension])
|
|
507
|
-
|
|
528
|
+
lodInfo = tex.source[$progressiveTextureExtension];
|
|
508
529
|
}
|
|
509
|
-
if (!
|
|
510
|
-
|
|
511
|
-
if (
|
|
530
|
+
if (!lodInfo)
|
|
531
|
+
lodInfo = NEEDLE_progressive.lodInfos.get(LODKEY);
|
|
532
|
+
if (lodInfo) {
|
|
512
533
|
if (level > 0) {
|
|
513
534
|
let useLowRes = false;
|
|
514
|
-
const hasMultipleLevels = Array.isArray(
|
|
515
|
-
if (hasMultipleLevels && level >=
|
|
535
|
+
const hasMultipleLevels = Array.isArray(lodInfo.lods);
|
|
536
|
+
if (hasMultipleLevels && level >= lodInfo.lods.length) {
|
|
516
537
|
useLowRes = true;
|
|
517
538
|
}
|
|
518
539
|
else if (!hasMultipleLevels) {
|
|
@@ -524,12 +545,12 @@ export class NEEDLE_progressive {
|
|
|
524
545
|
}
|
|
525
546
|
}
|
|
526
547
|
/** the unresolved LOD url */
|
|
527
|
-
const unresolved_lod_url = Array.isArray(
|
|
548
|
+
const unresolved_lod_url = Array.isArray(lodInfo.lods) ? lodInfo.lods[level]?.path : lodInfo.lods;
|
|
528
549
|
// check if we have a uri
|
|
529
550
|
if (!unresolved_lod_url) {
|
|
530
|
-
if (debug && !
|
|
531
|
-
|
|
532
|
-
console.warn("Missing uri for progressive asset for LOD " + level,
|
|
551
|
+
if (debug && !lodInfo["missing:uri"]) {
|
|
552
|
+
lodInfo["missing:uri"] = true;
|
|
553
|
+
console.warn("Missing uri for progressive asset for LOD " + level, lodInfo);
|
|
533
554
|
}
|
|
534
555
|
return null;
|
|
535
556
|
}
|
|
@@ -537,12 +558,12 @@ export class NEEDLE_progressive {
|
|
|
537
558
|
const lod_url = resolveUrl(LOD.url, unresolved_lod_url);
|
|
538
559
|
// check if the requested file needs to be loaded via a GLTFLoader
|
|
539
560
|
if (lod_url.endsWith(".glb") || lod_url.endsWith(".gltf")) {
|
|
540
|
-
if (!
|
|
541
|
-
console.warn("missing pointer for glb/gltf texture",
|
|
561
|
+
if (!lodInfo.guid) {
|
|
562
|
+
console.warn("missing pointer for glb/gltf texture", lodInfo);
|
|
542
563
|
return null;
|
|
543
564
|
}
|
|
544
565
|
// check if the requested file has already been loaded
|
|
545
|
-
const KEY = lod_url + "_" +
|
|
566
|
+
const KEY = lod_url + "_" + lodInfo.guid;
|
|
546
567
|
// check if the requested file is currently being loaded
|
|
547
568
|
const existing = this.previouslyLoaded.get(KEY);
|
|
548
569
|
if (existing !== undefined) {
|
|
@@ -581,7 +602,13 @@ export class NEEDLE_progressive {
|
|
|
581
602
|
return res;
|
|
582
603
|
}
|
|
583
604
|
}
|
|
584
|
-
const
|
|
605
|
+
const slot = await this.queue.slot(lod_url);
|
|
606
|
+
if (!slot.use) {
|
|
607
|
+
if (debug)
|
|
608
|
+
console.log(`LOD ${level} was aborted: ${lod_url}`);
|
|
609
|
+
return null; // the request was aborted, we don't load it again
|
|
610
|
+
}
|
|
611
|
+
const ext = lodInfo;
|
|
585
612
|
const request = new Promise(async (resolve, _) => {
|
|
586
613
|
const loader = new GLTFLoader();
|
|
587
614
|
addDracoAndKTX2Loaders(loader);
|
|
@@ -599,10 +626,11 @@ export class NEEDLE_progressive {
|
|
|
599
626
|
}
|
|
600
627
|
const gltf = await loader.loadAsync(url).catch(err => {
|
|
601
628
|
console.error(`Error loading LOD ${level} from ${lod_url}\n`, err);
|
|
602
|
-
return null;
|
|
629
|
+
return resolve(null);
|
|
603
630
|
});
|
|
604
|
-
if (!gltf)
|
|
605
|
-
return null;
|
|
631
|
+
if (!gltf) {
|
|
632
|
+
return resolve(null);
|
|
633
|
+
}
|
|
606
634
|
const parser = gltf.parser;
|
|
607
635
|
if (debugverbose)
|
|
608
636
|
console.log("Loading finished " + lod_url, ext.guid);
|
|
@@ -687,6 +715,7 @@ export class NEEDLE_progressive {
|
|
|
687
715
|
return resolve(null);
|
|
688
716
|
});
|
|
689
717
|
this.previouslyLoaded.set(KEY, request);
|
|
718
|
+
slot.use(request);
|
|
690
719
|
const res = await request;
|
|
691
720
|
return res;
|
|
692
721
|
}
|
|
@@ -697,12 +726,12 @@ export class NEEDLE_progressive {
|
|
|
697
726
|
const loader = new TextureLoader();
|
|
698
727
|
const tex = await loader.loadAsync(lod_url);
|
|
699
728
|
if (tex) {
|
|
700
|
-
tex.guid =
|
|
729
|
+
tex.guid = lodInfo.guid;
|
|
701
730
|
tex.flipY = false;
|
|
702
731
|
tex.needsUpdate = true;
|
|
703
732
|
tex.colorSpace = current.colorSpace;
|
|
704
733
|
if (debugverbose)
|
|
705
|
-
console.log(
|
|
734
|
+
console.log(lodInfo, tex);
|
|
706
735
|
}
|
|
707
736
|
else if (debug)
|
|
708
737
|
console.warn("failed loading", lod_url);
|
|
@@ -716,6 +745,7 @@ export class NEEDLE_progressive {
|
|
|
716
745
|
}
|
|
717
746
|
return null;
|
|
718
747
|
}
|
|
748
|
+
static queue = new PromiseQueue(100, { debug: debug != false });
|
|
719
749
|
static assignLODInformation(url, res, key, level, index) {
|
|
720
750
|
if (!res)
|
|
721
751
|
return;
|
|
@@ -723,9 +753,17 @@ export class NEEDLE_progressive {
|
|
|
723
753
|
res.userData = {};
|
|
724
754
|
const info = new LODInformation(url, key, level, index);
|
|
725
755
|
res.userData.LODS = info;
|
|
756
|
+
if ("source" in res && typeof res.source === "object")
|
|
757
|
+
res.source.LODS = info; // for tiled textures
|
|
726
758
|
}
|
|
727
759
|
static getAssignedLODInformation(res) {
|
|
728
|
-
|
|
760
|
+
if (!res)
|
|
761
|
+
return null;
|
|
762
|
+
if (res.userData?.LODS)
|
|
763
|
+
return res.userData.LODS;
|
|
764
|
+
if ("source" in res && res.source?.LODS)
|
|
765
|
+
return res.source.LODS;
|
|
766
|
+
return null;
|
|
729
767
|
}
|
|
730
768
|
// private static readonly _copiedTextures: WeakMap<Texture, Texture> = new Map();
|
|
731
769
|
static copySettings(source, target) {
|
package/lib/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export { version as VERSION } from "./version.js";
|
|
2
2
|
export * from "./extension.js";
|
|
3
3
|
export * from "./plugins/index.js";
|
|
4
|
-
export { LODsManager, type LOD_Results } from "./
|
|
4
|
+
export { LODsManager, type LOD_Results } from "./lods.manager.js";
|
|
5
5
|
export { setDracoDecoderLocation, setKTX2TranscoderLocation, createLoaders, addDracoAndKTX2Loaders, configureLoader } from "./loaders.js";
|
|
6
6
|
export { getRaycastMesh, registerRaycastMesh, useRaycastMeshes } from "./utils.js";
|
|
7
7
|
import { WebGLRenderer } from "three";
|
|
8
8
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
9
9
|
import { SmartLoadingHints } from "./loaders.js";
|
|
10
|
-
import { LODsManager } from "./
|
|
10
|
+
import { LODsManager } from "./lods.manager.js";
|
|
11
11
|
declare type UseNeedleGLTFProgressiveOptions = {
|
|
12
12
|
/**
|
|
13
13
|
* When set to true the LODs manager will automatically be enabled
|
package/lib/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export { version as VERSION } from "./version.js";
|
|
2
2
|
export * from "./extension.js";
|
|
3
3
|
export * from "./plugins/index.js";
|
|
4
|
-
export { LODsManager } from "./
|
|
4
|
+
export { LODsManager } from "./lods.manager.js";
|
|
5
5
|
export { setDracoDecoderLocation, setKTX2TranscoderLocation, createLoaders, addDracoAndKTX2Loaders, configureLoader } from "./loaders.js";
|
|
6
6
|
export { getRaycastMesh, registerRaycastMesh, useRaycastMeshes } from "./utils.js";
|
|
7
7
|
import { addDracoAndKTX2Loaders, configureLoader, createLoaders } from "./loaders.js";
|
|
8
8
|
import { NEEDLE_progressive } from "./extension.js";
|
|
9
|
-
import { LODsManager } from "./
|
|
9
|
+
import { LODsManager } from "./lods.manager.js";
|
|
10
10
|
/** Use this function to enable progressive loading of gltf models.
|
|
11
11
|
* @param url The url of the gltf model.
|
|
12
12
|
* @param renderer The renderer of the scene.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Camera, Material,
|
|
1
|
+
import { Camera, Material, Object3D, Scene, Texture, Vector3, WebGLRenderer } from "three";
|
|
2
2
|
import { NEEDLE_progressive_plugin } from "./plugins/plugin.js";
|
|
3
|
+
import { PromiseGroupOptions } from "./lods.promise.js";
|
|
3
4
|
export type LODManagerContext = {
|
|
4
5
|
engine: "three" | "needle-engine" | "model-viewer" | "react-three-fiber" | "unknown";
|
|
5
6
|
};
|
|
@@ -61,9 +62,9 @@ export declare class LODsManager {
|
|
|
61
62
|
* @returns The LODsManager instance.
|
|
62
63
|
*/
|
|
63
64
|
static get(renderer: WebGLRenderer, context?: LODManagerContext): LODsManager;
|
|
64
|
-
private readonly context;
|
|
65
65
|
readonly renderer: WebGLRenderer;
|
|
66
|
-
readonly
|
|
66
|
+
private readonly context;
|
|
67
|
+
private readonly projectionScreenMatrix;
|
|
67
68
|
/** @deprecated use static `LODsManager.addPlugin()` method. This getter will be removed in later versions */
|
|
68
69
|
get plugins(): NEEDLE_progressive_plugin[];
|
|
69
70
|
/**
|
|
@@ -93,6 +94,17 @@ export declare class LODsManager {
|
|
|
93
94
|
* @default false
|
|
94
95
|
*/
|
|
95
96
|
manual: boolean;
|
|
97
|
+
private readonly _newPromiseGroups;
|
|
98
|
+
private _promiseGroupIds;
|
|
99
|
+
/**
|
|
100
|
+
* Call to await LODs loading during the next render cycle.
|
|
101
|
+
*/
|
|
102
|
+
awaitLoading(opts?: PromiseGroupOptions): Promise<{
|
|
103
|
+
cancelled: boolean;
|
|
104
|
+
awaited_count: number;
|
|
105
|
+
resolved_count: number;
|
|
106
|
+
}>;
|
|
107
|
+
private _postprocessPromiseGroups;
|
|
96
108
|
private readonly _lodchangedlisteners;
|
|
97
109
|
addEventListener(evt: "changed", listener: LODChangedEventListener): void;
|
|
98
110
|
removeEventListener(evt: "changed", listener: LODChangedEventListener): void;
|
|
@@ -137,6 +149,8 @@ export declare class LODsManager {
|
|
|
137
149
|
private static corner3;
|
|
138
150
|
private static readonly _tempPtInside;
|
|
139
151
|
private static isInside;
|
|
152
|
+
private static skinnedMeshBoundsFrameOffsetCounter;
|
|
153
|
+
private static $skinnedMeshBoundsOffset;
|
|
140
154
|
private calculateLodLevel;
|
|
141
155
|
}
|
|
142
156
|
declare class LOD_state {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Box3, Clock, Matrix4, Mesh, MeshStandardMaterial, Sphere, Vector3 } from "three";
|
|
2
2
|
import { NEEDLE_progressive } from "./extension.js";
|
|
3
3
|
import { createLoaders } from "./loaders.js";
|
|
4
|
-
import { getParam, isMobileDevice } from "./utils.internal.js";
|
|
4
|
+
import { getParam, isDevelopmentServer, isMobileDevice } from "./utils.internal.js";
|
|
5
5
|
import { plugins } from "./plugins/plugin.js";
|
|
6
6
|
import { getRaycastMesh } from "./utils.js";
|
|
7
7
|
import { applyDebugSettings, debug, debug_OverrideLodLevel } from "./lods.debug.js";
|
|
8
|
+
import { PromiseGroup } from "./lods.promise.js";
|
|
8
9
|
const debugProgressiveLoading = getParam("debugprogressive");
|
|
9
10
|
const suppressProgressiveLoading = getParam("noprogressive");
|
|
10
11
|
const $lodsManager = Symbol("Needle:LODSManager");
|
|
@@ -78,8 +79,8 @@ export class LODsManager {
|
|
|
78
79
|
renderer[$lodsManager] = lodsManager;
|
|
79
80
|
return lodsManager;
|
|
80
81
|
}
|
|
81
|
-
context;
|
|
82
82
|
renderer;
|
|
83
|
+
context;
|
|
83
84
|
projectionScreenMatrix = new Matrix4();
|
|
84
85
|
/** @deprecated use static `LODsManager.addPlugin()` method. This getter will be removed in later versions */
|
|
85
86
|
get plugins() { return plugins; }
|
|
@@ -111,6 +112,37 @@ export class LODsManager {
|
|
|
111
112
|
* @default false
|
|
112
113
|
*/
|
|
113
114
|
manual = false;
|
|
115
|
+
_newPromiseGroups = [];
|
|
116
|
+
_promiseGroupIds = 0;
|
|
117
|
+
/**
|
|
118
|
+
* Call to await LODs loading during the next render cycle.
|
|
119
|
+
*/
|
|
120
|
+
awaitLoading(opts) {
|
|
121
|
+
const id = this._promiseGroupIds++;
|
|
122
|
+
const newGroup = new PromiseGroup(this.#frame, { ...opts, });
|
|
123
|
+
this._newPromiseGroups.push(newGroup);
|
|
124
|
+
const start = performance.now();
|
|
125
|
+
newGroup.ready.finally(() => {
|
|
126
|
+
const index = this._newPromiseGroups.indexOf(newGroup);
|
|
127
|
+
if (index >= 0) {
|
|
128
|
+
this._newPromiseGroups.splice(index, 1);
|
|
129
|
+
if (isDevelopmentServer())
|
|
130
|
+
performance.measure("LODsManager:awaitLoading", {
|
|
131
|
+
start,
|
|
132
|
+
detail: { id, name: opts?.name, awaited: newGroup.awaitedCount, resolved: newGroup.resolvedCount }
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return newGroup.ready;
|
|
137
|
+
}
|
|
138
|
+
_postprocessPromiseGroups() {
|
|
139
|
+
if (this._newPromiseGroups.length === 0)
|
|
140
|
+
return;
|
|
141
|
+
for (let i = this._newPromiseGroups.length - 1; i >= 0; i--) {
|
|
142
|
+
const group = this._newPromiseGroups[i];
|
|
143
|
+
group.update(this.#frame);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
114
146
|
_lodchangedlisteners = [];
|
|
115
147
|
addEventListener(evt, listener) {
|
|
116
148
|
if (evt === "changed") {
|
|
@@ -232,6 +264,7 @@ export class LODsManager {
|
|
|
232
264
|
return;
|
|
233
265
|
}
|
|
234
266
|
this.internalUpdate(scene, camera);
|
|
267
|
+
this._postprocessPromiseGroups();
|
|
235
268
|
}
|
|
236
269
|
}
|
|
237
270
|
/**
|
|
@@ -368,9 +401,10 @@ export class LODsManager {
|
|
|
368
401
|
}
|
|
369
402
|
if (update) {
|
|
370
403
|
material[$currentLOD] = level;
|
|
371
|
-
NEEDLE_progressive.assignTextureLOD(material, level).then(_ => {
|
|
404
|
+
const promise = NEEDLE_progressive.assignTextureLOD(material, level).then(_ => {
|
|
372
405
|
this._lodchangedlisteners.forEach(l => l({ type: "texture", level, object: material }));
|
|
373
406
|
});
|
|
407
|
+
PromiseGroup.addPromise("texture", material, promise, this._newPromiseGroups);
|
|
374
408
|
}
|
|
375
409
|
}
|
|
376
410
|
/** Load progressive meshes for the given mesh
|
|
@@ -391,19 +425,14 @@ export class LODsManager {
|
|
|
391
425
|
if (update) {
|
|
392
426
|
mesh[$currentLOD] = level;
|
|
393
427
|
const originalGeometry = mesh.geometry;
|
|
394
|
-
|
|
428
|
+
const promise = NEEDLE_progressive.assignMeshLOD(mesh, level).then(res => {
|
|
395
429
|
if (res && mesh[$currentLOD] == level && originalGeometry != mesh.geometry) {
|
|
396
430
|
this._lodchangedlisteners.forEach(l => l({ type: "mesh", level, object: mesh }));
|
|
397
|
-
// if (this.handles) {
|
|
398
|
-
// for (const inst of this.handles) {
|
|
399
|
-
// // if (inst["LOD"] < level) continue;
|
|
400
|
-
// // inst["LOD"] = level;
|
|
401
|
-
// inst.setGeometry(mesh.geometry);
|
|
402
|
-
// }
|
|
403
|
-
// }
|
|
404
431
|
}
|
|
405
432
|
return res;
|
|
406
433
|
});
|
|
434
|
+
PromiseGroup.addPromise("mesh", mesh, promise, this._newPromiseGroups);
|
|
435
|
+
return promise;
|
|
407
436
|
}
|
|
408
437
|
return Promise.resolve(null);
|
|
409
438
|
}
|
|
@@ -428,6 +457,8 @@ export class LODsManager {
|
|
|
428
457
|
const pt1 = this._tempPtInside.set(centerx, centery, min.z).applyMatrix4(matrix);
|
|
429
458
|
return pt1.z < 0;
|
|
430
459
|
}
|
|
460
|
+
static skinnedMeshBoundsFrameOffsetCounter = 0;
|
|
461
|
+
static $skinnedMeshBoundsOffset = Symbol("gltf-progressive-skinnedMeshBoundsOffset");
|
|
431
462
|
calculateLodLevel(camera, mesh, state, desiredDensity, result) {
|
|
432
463
|
if (!mesh) {
|
|
433
464
|
result.mesh_lod = -1;
|
|
@@ -473,16 +504,25 @@ export class LODsManager {
|
|
|
473
504
|
skinnedMesh.computeBoundingBox();
|
|
474
505
|
}
|
|
475
506
|
// Fix: https://linear.app/needle/issue/NE-5264
|
|
476
|
-
else if (this.skinnedMeshAutoUpdateBoundsInterval > 0
|
|
477
|
-
|
|
478
|
-
//
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
skinnedMesh
|
|
507
|
+
else if (this.skinnedMeshAutoUpdateBoundsInterval > 0) {
|
|
508
|
+
// Save a frame offset per object to stagger updates of skinned meshes across multiple frames
|
|
509
|
+
// This isn't a perfect solution to improve perf impact of skinned mesh updates (e.g. large skinned meshes would still be costly)
|
|
510
|
+
// But for many smaller meshes it helps to avoid spikes in performance
|
|
511
|
+
if (!skinnedMesh[LODsManager.$skinnedMeshBoundsOffset]) {
|
|
512
|
+
const offset = LODsManager.skinnedMeshBoundsFrameOffsetCounter++;
|
|
513
|
+
skinnedMesh[LODsManager.$skinnedMeshBoundsOffset] = offset;
|
|
514
|
+
}
|
|
515
|
+
const frameOffset = skinnedMesh[LODsManager.$skinnedMeshBoundsOffset];
|
|
516
|
+
if ((state.frames + frameOffset) % this.skinnedMeshAutoUpdateBoundsInterval === 0) {
|
|
517
|
+
// use lowres geometry for bounding box calculation
|
|
518
|
+
const raycastmesh = getRaycastMesh(skinnedMesh);
|
|
519
|
+
const originalGeometry = skinnedMesh.geometry;
|
|
520
|
+
if (raycastmesh) {
|
|
521
|
+
skinnedMesh.geometry = raycastmesh;
|
|
522
|
+
}
|
|
523
|
+
skinnedMesh.computeBoundingBox();
|
|
524
|
+
skinnedMesh.geometry = originalGeometry;
|
|
483
525
|
}
|
|
484
|
-
skinnedMesh.computeBoundingBox();
|
|
485
|
-
skinnedMesh.geometry = originalGeometry;
|
|
486
526
|
}
|
|
487
527
|
boundingBox = skinnedMesh.boundingBox;
|
|
488
528
|
}
|
|
@@ -608,6 +648,10 @@ export class LODsManager {
|
|
|
608
648
|
const lod = mesh_lods[l];
|
|
609
649
|
const densityForThisLevel = lod.densities?.[primitive_index] || lod.density || .00001;
|
|
610
650
|
const resultingDensity = densityForThisLevel / state.lastScreenCoverage;
|
|
651
|
+
if (primitive_index > 0 && isDevelopmentServer() && !lod.densities && !globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]) {
|
|
652
|
+
window["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] = true;
|
|
653
|
+
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.`);
|
|
654
|
+
}
|
|
611
655
|
if (resultingDensity < desiredDensity) {
|
|
612
656
|
expectedLevel = l;
|
|
613
657
|
break;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type PromiseType = "texture" | "mesh";
|
|
2
|
+
export type PromiseGroupOptions = {
|
|
3
|
+
/** Name for debugging purposes */
|
|
4
|
+
name?: string;
|
|
5
|
+
/** Define many frames new LOD promises will be captured and awaited. The group will resolve after all promises captured during this time have resolved (or when the abort signal is triggered).
|
|
6
|
+
* @default 2 frames, which means the group will capture promises for 2 frames before resolving.
|
|
7
|
+
*/
|
|
8
|
+
frames?: number;
|
|
9
|
+
/** An optional signal to abort the promise */
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
/**
|
|
12
|
+
* If set to true, the group will only await one promise per object.
|
|
13
|
+
* @default 1
|
|
14
|
+
*/
|
|
15
|
+
maxPromisesPerObject?: number;
|
|
16
|
+
};
|
|
17
|
+
type PromiseGroupResolveResult = {
|
|
18
|
+
/**
|
|
19
|
+
* `true` if the group was cancelled, `false` if it was resolved normally.
|
|
20
|
+
*/
|
|
21
|
+
cancelled: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The number of promises that started to being awaited
|
|
24
|
+
*/
|
|
25
|
+
awaited_count: number;
|
|
26
|
+
/**
|
|
27
|
+
* The number of promises that were resolved
|
|
28
|
+
*/
|
|
29
|
+
resolved_count: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A group of promises that can be awaited together.
|
|
33
|
+
* This is used for awaiting LOD
|
|
34
|
+
*/
|
|
35
|
+
export declare class PromiseGroup {
|
|
36
|
+
static readonly addPromise: (type: PromiseType, object: object, promise: Promise<any>, groups: PromiseGroup[]) => void;
|
|
37
|
+
readonly frame_start: number;
|
|
38
|
+
readonly frame_capture_end: number;
|
|
39
|
+
readonly ready: Promise<PromiseGroupResolveResult>;
|
|
40
|
+
private _resolve;
|
|
41
|
+
private readonly _signal?;
|
|
42
|
+
/**
|
|
43
|
+
* The number of promises that have been added to this group so far.
|
|
44
|
+
*/
|
|
45
|
+
get awaitedCount(): number;
|
|
46
|
+
get resolvedCount(): number;
|
|
47
|
+
get currentlyAwaiting(): number;
|
|
48
|
+
private _resolved;
|
|
49
|
+
private _addedCount;
|
|
50
|
+
private _resolvedCount;
|
|
51
|
+
/** These promises are currently being awaited */
|
|
52
|
+
private readonly _awaiting;
|
|
53
|
+
private _maxPromisesPerObject;
|
|
54
|
+
constructor(frame: number, options: PromiseGroupOptions);
|
|
55
|
+
private _currentFrame;
|
|
56
|
+
update(frame: number): void;
|
|
57
|
+
private readonly _seen;
|
|
58
|
+
private add;
|
|
59
|
+
private resolveNow;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { debug } from "./lods.debug";
|
|
2
|
+
/**
|
|
3
|
+
* A group of promises that can be awaited together.
|
|
4
|
+
* This is used for awaiting LOD
|
|
5
|
+
*/
|
|
6
|
+
export class PromiseGroup {
|
|
7
|
+
static addPromise = (type, object, promise, groups) => {
|
|
8
|
+
groups.forEach(group => {
|
|
9
|
+
group.add(type, object, promise);
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
frame_start;
|
|
13
|
+
frame_capture_end;
|
|
14
|
+
ready;
|
|
15
|
+
_resolve;
|
|
16
|
+
_signal;
|
|
17
|
+
/**
|
|
18
|
+
* The number of promises that have been added to this group so far.
|
|
19
|
+
*/
|
|
20
|
+
get awaitedCount() {
|
|
21
|
+
return this._addedCount;
|
|
22
|
+
}
|
|
23
|
+
get resolvedCount() {
|
|
24
|
+
return this._resolvedCount;
|
|
25
|
+
}
|
|
26
|
+
get currentlyAwaiting() {
|
|
27
|
+
return this._awaiting.length;
|
|
28
|
+
}
|
|
29
|
+
_resolved = false;
|
|
30
|
+
_addedCount = 0;
|
|
31
|
+
_resolvedCount = 0;
|
|
32
|
+
/** These promises are currently being awaited */
|
|
33
|
+
_awaiting = [];
|
|
34
|
+
_maxPromisesPerObject = 1;
|
|
35
|
+
constructor(frame, options) {
|
|
36
|
+
const minFrames = 2; // wait at least 2 frames to capture promises
|
|
37
|
+
const framesToCapture = Math.max(options.frames ?? minFrames, minFrames); // default to 2 frames and make sure it's at least 2 frames
|
|
38
|
+
this.frame_start = frame;
|
|
39
|
+
this.frame_capture_end = frame + framesToCapture;
|
|
40
|
+
this.ready = new Promise((resolve) => {
|
|
41
|
+
this._resolve = resolve;
|
|
42
|
+
});
|
|
43
|
+
this.ready.finally(() => {
|
|
44
|
+
this._resolved = true;
|
|
45
|
+
this._awaiting.length = 0;
|
|
46
|
+
});
|
|
47
|
+
this._signal = options.signal;
|
|
48
|
+
this._signal?.addEventListener("abort", () => {
|
|
49
|
+
this.resolveNow();
|
|
50
|
+
});
|
|
51
|
+
this._maxPromisesPerObject = Math.max(1, options.maxPromisesPerObject ?? 1);
|
|
52
|
+
}
|
|
53
|
+
_currentFrame = 0;
|
|
54
|
+
update(frame) {
|
|
55
|
+
this._currentFrame = frame;
|
|
56
|
+
// If we've passes the frame capture end frame and didn't add any promises, we resolve immediately
|
|
57
|
+
if (this._signal?.aborted || (this._currentFrame > this.frame_capture_end && this._awaiting.length === 0)) {
|
|
58
|
+
this.resolveNow();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
_seen = new WeakMap();
|
|
62
|
+
add(_type, object, promise) {
|
|
63
|
+
if (this._resolved) {
|
|
64
|
+
if (debug)
|
|
65
|
+
console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (this._currentFrame > this.frame_capture_end) {
|
|
69
|
+
return; // we are not capturing any more promises
|
|
70
|
+
}
|
|
71
|
+
if (this._maxPromisesPerObject >= 1) {
|
|
72
|
+
if (this._seen.has(object)) {
|
|
73
|
+
let count = this._seen.get(object);
|
|
74
|
+
if (count >= this._maxPromisesPerObject) {
|
|
75
|
+
if (debug)
|
|
76
|
+
console.warn(`PromiseGroup: Already awaiting object ignoring new promise for it.`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this._seen.set(object, count + 1);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this._seen.set(object, 1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this._awaiting.push(promise);
|
|
86
|
+
this._addedCount++;
|
|
87
|
+
promise.finally(() => {
|
|
88
|
+
this._resolvedCount++;
|
|
89
|
+
this._awaiting.splice(this._awaiting.indexOf(promise), 1);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
resolveNow() {
|
|
93
|
+
if (this._resolved)
|
|
94
|
+
return;
|
|
95
|
+
this._resolve?.({
|
|
96
|
+
awaited_count: this._addedCount,
|
|
97
|
+
resolved_count: this._resolvedCount,
|
|
98
|
+
cancelled: this._signal?.aborted ?? false,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LODsManager } from "../
|
|
1
|
+
import { LODsManager } from "../lods.manager.js";
|
|
2
2
|
import { EXTENSION_NAME, NEEDLE_progressive } from "../extension.js";
|
|
3
3
|
const $meshLODSymbol = Symbol("NEEDLE_mesh_lod");
|
|
4
4
|
const $textureLODSymbol = Symbol("NEEDLE_texture_lod");
|
package/lib/utils.internal.d.ts
CHANGED
|
@@ -3,3 +3,24 @@ export declare function getParam(name: string): boolean | string;
|
|
|
3
3
|
export declare function resolveUrl(source: string | undefined, uri: string): string;
|
|
4
4
|
/** @returns `true` if it's a phone or tablet */
|
|
5
5
|
export declare function isMobileDevice(): boolean;
|
|
6
|
+
export declare function isDevelopmentServer(): boolean;
|
|
7
|
+
type SlotReturnValue = {
|
|
8
|
+
use?: ((promise: Promise<any>) => void);
|
|
9
|
+
};
|
|
10
|
+
export declare class PromiseQueue {
|
|
11
|
+
readonly maxConcurrent: number;
|
|
12
|
+
private readonly _running;
|
|
13
|
+
private readonly _queue;
|
|
14
|
+
debug: boolean;
|
|
15
|
+
constructor(maxConcurrent?: number, opts?: {
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
});
|
|
18
|
+
private tick;
|
|
19
|
+
/**
|
|
20
|
+
* Request a slot for a promise with a specific key. This function returns a promise with a `use` method that can be called to add the promise to the queue.
|
|
21
|
+
*/
|
|
22
|
+
slot(key: string): Promise<SlotReturnValue>;
|
|
23
|
+
private add;
|
|
24
|
+
private internalUpdate;
|
|
25
|
+
}
|
|
26
|
+
export {};
|