@needle-tools/gltf-progressive 2.1.6 → 3.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/gltf-progressive.js +753 -668
- package/gltf-progressive.min.js +7 -7
- package/gltf-progressive.umd.cjs +8 -8
- package/lib/extension.d.ts +10 -6
- package/lib/extension.js +20 -94
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/lods.debug.d.ts +4 -0
- package/lib/lods.debug.js +41 -0
- package/lib/lods.loading.d.ts +50 -0
- package/lib/lods.loading.js +82 -0
- package/lib/{lods_manager.d.ts → lods.manager.d.ts} +21 -4
- package/lib/{lods_manager.js → lods.manager.js} +66 -21
- package/lib/plugins/modelviewer.js +1 -1
- package/lib/plugins/plugin.d.ts +2 -2
- package/lib/utils.internal.d.ts +1 -0
- package/lib/utils.internal.js +8 -0
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/lib/extension.js
CHANGED
|
@@ -1,80 +1,15 @@
|
|
|
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 {
|
|
4
|
+
import { 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";
|
|
8
8
|
// import { PromiseAllWithErrors, resolveUrl } from "../../engine_utils.js";
|
|
9
9
|
import { plugins } from "./plugins/plugin.js";
|
|
10
|
+
import { debug } from "./lods.debug.js";
|
|
10
11
|
export const EXTENSION_NAME = "NEEDLE_progressive";
|
|
11
|
-
const debug = getParam("debugprogressive");
|
|
12
12
|
const $progressiveTextureExtension = Symbol("needle-progressive-texture");
|
|
13
|
-
const debug_toggle_maps = new Map();
|
|
14
|
-
const debug_materials = new Set();
|
|
15
|
-
if (debug) {
|
|
16
|
-
let currentDebugLodLevel = -1;
|
|
17
|
-
let maxLevel = 2;
|
|
18
|
-
let wireframe = false;
|
|
19
|
-
function debugToggleProgressive() {
|
|
20
|
-
currentDebugLodLevel += 1;
|
|
21
|
-
console.log("Toggle LOD level", currentDebugLodLevel, debug_toggle_maps);
|
|
22
|
-
debug_toggle_maps.forEach((arr, obj) => {
|
|
23
|
-
for (const key of arr.keys) {
|
|
24
|
-
const cur = obj[key];
|
|
25
|
-
// if it's null or undefined we skip it
|
|
26
|
-
if (cur == null) {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (cur.isBufferGeometry === true) {
|
|
30
|
-
const info = NEEDLE_progressive.getMeshLODInformation(cur);
|
|
31
|
-
const level = !info ? 0 : Math.min(currentDebugLodLevel, info.lods.length);
|
|
32
|
-
obj["DEBUG:LOD"] = level;
|
|
33
|
-
// NEEDLE_progressive.assignMeshLOD(obj as Mesh, level);
|
|
34
|
-
if (info)
|
|
35
|
-
maxLevel = Math.max(maxLevel, info.lods.length - 1);
|
|
36
|
-
}
|
|
37
|
-
else if (obj.isMaterial === true) {
|
|
38
|
-
obj["DEBUG:LOD"] = currentDebugLodLevel;
|
|
39
|
-
// NEEDLE_progressive.assignTextureLOD(obj as Material, currentDebugLodLevel);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
if (currentDebugLodLevel >= maxLevel) {
|
|
44
|
-
currentDebugLodLevel = -1;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
window.addEventListener("keyup", evt => {
|
|
48
|
-
if (evt.key === "p")
|
|
49
|
-
debugToggleProgressive();
|
|
50
|
-
if (evt.key === "w") {
|
|
51
|
-
wireframe = !wireframe;
|
|
52
|
-
if (debug_materials) {
|
|
53
|
-
debug_materials.forEach(mat => {
|
|
54
|
-
// we don't want to change the skybox material
|
|
55
|
-
if (mat.name == "BackgroundCubeMaterial")
|
|
56
|
-
return;
|
|
57
|
-
if (mat["glyphMap"] != undefined)
|
|
58
|
-
return;
|
|
59
|
-
if ("wireframe" in mat) {
|
|
60
|
-
mat.wireframe = wireframe;
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function registerDebug(obj, key, sourceId) {
|
|
68
|
-
if (!debug)
|
|
69
|
-
return;
|
|
70
|
-
if (!debug_toggle_maps.has(obj)) {
|
|
71
|
-
debug_toggle_maps.set(obj, { keys: [], sourceId });
|
|
72
|
-
}
|
|
73
|
-
const existing = debug_toggle_maps.get(obj);
|
|
74
|
-
if (existing?.keys?.includes(key) == false) {
|
|
75
|
-
existing.keys.push(key);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
13
|
/**
|
|
79
14
|
* The NEEDLE_progressive extension for the GLTFLoader is responsible for loading progressive LODs for meshes and textures.
|
|
80
15
|
* This extension can be used to load different resolutions of a mesh or texture at runtime (e.g. for LODs or progressive textures).
|
|
@@ -95,13 +30,19 @@ export class NEEDLE_progressive {
|
|
|
95
30
|
get name() {
|
|
96
31
|
return EXTENSION_NAME;
|
|
97
32
|
}
|
|
98
|
-
static
|
|
33
|
+
static getMeshLODExtension(geo) {
|
|
99
34
|
const info = this.getAssignedLODInformation(geo);
|
|
100
35
|
if (info?.key) {
|
|
101
36
|
return this.lodInfos.get(info.key);
|
|
102
37
|
}
|
|
103
38
|
return null;
|
|
104
39
|
}
|
|
40
|
+
static getPrimitiveIndex(geo) {
|
|
41
|
+
const index = this.getAssignedLODInformation(geo)?.index;
|
|
42
|
+
if (index === undefined || index === null)
|
|
43
|
+
return -1;
|
|
44
|
+
return index;
|
|
45
|
+
}
|
|
105
46
|
static getMaterialMinMaxLODsCount(material, minmax) {
|
|
106
47
|
const self = this;
|
|
107
48
|
// we can cache this material min max data because it wont change at runtime
|
|
@@ -261,8 +202,6 @@ export class NEEDLE_progressive {
|
|
|
261
202
|
// if (debug == "verbose") console.log("Progressive Mesh " + mesh.name + " loaded", currentGeometry, "→", geo, "\n", mesh)
|
|
262
203
|
if (isGeometry) {
|
|
263
204
|
mesh.geometry = geo;
|
|
264
|
-
if (debug)
|
|
265
|
-
registerDebug(mesh, "geometry", lodinfo.url);
|
|
266
205
|
}
|
|
267
206
|
else if (debug) {
|
|
268
207
|
console.error("Invalid LOD geometry", geo);
|
|
@@ -311,8 +250,6 @@ export class NEEDLE_progressive {
|
|
|
311
250
|
const material = materialOrTexture;
|
|
312
251
|
const promises = [];
|
|
313
252
|
const slots = new Array();
|
|
314
|
-
if (debug)
|
|
315
|
-
debug_materials.add(material);
|
|
316
253
|
// Handle custom shaders / uniforms progressive textures. This includes support for VRM shaders
|
|
317
254
|
if (material.uniforms && (material.isRawShaderMaterial || material.isShaderMaterial === true)) {
|
|
318
255
|
// iterate uniforms of custom shaders
|
|
@@ -391,13 +328,6 @@ export class NEEDLE_progressive {
|
|
|
391
328
|
}
|
|
392
329
|
material[slot] = tex;
|
|
393
330
|
}
|
|
394
|
-
if (debug && slot && material) {
|
|
395
|
-
const lodinfo = this.getAssignedLODInformation(current);
|
|
396
|
-
if (lodinfo)
|
|
397
|
-
registerDebug(material, slot, lodinfo.url);
|
|
398
|
-
else
|
|
399
|
-
console.warn("No LOD info for texture", current);
|
|
400
|
-
}
|
|
401
331
|
// check if the old texture is still used by other objects
|
|
402
332
|
// if not we dispose it...
|
|
403
333
|
// this could also be handled elsewhere and not be done immediately
|
|
@@ -440,7 +370,7 @@ export class NEEDLE_progressive {
|
|
|
440
370
|
return this.parser.getDependency("mesh", meshIndex).then(mesh => {
|
|
441
371
|
this._isLoadingMesh = false;
|
|
442
372
|
if (mesh) {
|
|
443
|
-
NEEDLE_progressive.registerMesh(this.url, ext.guid, mesh, ext.lods?.length,
|
|
373
|
+
NEEDLE_progressive.registerMesh(this.url, ext.guid, mesh, ext.lods?.length, 0, ext);
|
|
444
374
|
}
|
|
445
375
|
return mesh;
|
|
446
376
|
});
|
|
@@ -522,7 +452,7 @@ export class NEEDLE_progressive {
|
|
|
522
452
|
if (tex.source)
|
|
523
453
|
tex.source[$progressiveTextureExtension] = ext;
|
|
524
454
|
const LODKEY = ext.guid;
|
|
525
|
-
NEEDLE_progressive.assignLODInformation(url, tex, LODKEY, level, index
|
|
455
|
+
NEEDLE_progressive.assignLODInformation(url, tex, LODKEY, level, index);
|
|
526
456
|
NEEDLE_progressive.lodInfos.set(LODKEY, ext);
|
|
527
457
|
NEEDLE_progressive.lowresCache.set(LODKEY, tex);
|
|
528
458
|
};
|
|
@@ -530,8 +460,6 @@ export class NEEDLE_progressive {
|
|
|
530
460
|
* Register a mesh with LOD information
|
|
531
461
|
*/
|
|
532
462
|
static registerMesh = (url, key, mesh, level, index, ext) => {
|
|
533
|
-
if (debug)
|
|
534
|
-
console.log("> Progressive: register mesh", index, mesh.name, ext, mesh.uuid, mesh);
|
|
535
463
|
const geometry = mesh.geometry;
|
|
536
464
|
if (!geometry) {
|
|
537
465
|
if (debug)
|
|
@@ -540,7 +468,9 @@ export class NEEDLE_progressive {
|
|
|
540
468
|
}
|
|
541
469
|
if (!geometry.userData)
|
|
542
470
|
geometry.userData = {};
|
|
543
|
-
|
|
471
|
+
if (debug)
|
|
472
|
+
console.log("> Progressive: register mesh " + mesh.name, { index, uuid: mesh.uuid }, ext, mesh);
|
|
473
|
+
NEEDLE_progressive.assignLODInformation(url, geometry, key, level, index);
|
|
544
474
|
NEEDLE_progressive.lodInfos.set(key, ext);
|
|
545
475
|
let existing = NEEDLE_progressive.lowresCache.get(key);
|
|
546
476
|
if (existing)
|
|
@@ -695,7 +625,7 @@ export class NEEDLE_progressive {
|
|
|
695
625
|
if (found) {
|
|
696
626
|
let tex = await parser.getDependency("texture", index);
|
|
697
627
|
if (tex) {
|
|
698
|
-
NEEDLE_progressive.assignLODInformation(LOD.url, tex, LODKEY, level, undefined
|
|
628
|
+
NEEDLE_progressive.assignLODInformation(LOD.url, tex, LODKEY, level, undefined);
|
|
699
629
|
}
|
|
700
630
|
if (debugverbose)
|
|
701
631
|
console.log("change \"" + current.name + "\" → \"" + tex.name + "\"", lod_url, index, tex, KEY);
|
|
@@ -733,7 +663,7 @@ export class NEEDLE_progressive {
|
|
|
733
663
|
console.log(`Loaded Mesh \"${mesh.name}\"`, lod_url, index, mesh, KEY);
|
|
734
664
|
if (mesh.isMesh === true) {
|
|
735
665
|
const geo = mesh.geometry;
|
|
736
|
-
NEEDLE_progressive.assignLODInformation(LOD.url, geo, LODKEY, level,
|
|
666
|
+
NEEDLE_progressive.assignLODInformation(LOD.url, geo, LODKEY, level, 0);
|
|
737
667
|
return resolve(geo);
|
|
738
668
|
}
|
|
739
669
|
else {
|
|
@@ -742,7 +672,7 @@ export class NEEDLE_progressive {
|
|
|
742
672
|
const child = mesh.children[i];
|
|
743
673
|
if (child.isMesh === true) {
|
|
744
674
|
const geo = child.geometry;
|
|
745
|
-
NEEDLE_progressive.assignLODInformation(LOD.url, geo, LODKEY, level, i
|
|
675
|
+
NEEDLE_progressive.assignLODInformation(LOD.url, geo, LODKEY, level, i);
|
|
746
676
|
geometries.push(geo);
|
|
747
677
|
}
|
|
748
678
|
}
|
|
@@ -786,12 +716,12 @@ export class NEEDLE_progressive {
|
|
|
786
716
|
}
|
|
787
717
|
return null;
|
|
788
718
|
}
|
|
789
|
-
static assignLODInformation(url, res, key, level, index
|
|
719
|
+
static assignLODInformation(url, res, key, level, index) {
|
|
790
720
|
if (!res)
|
|
791
721
|
return;
|
|
792
722
|
if (!res.userData)
|
|
793
723
|
res.userData = {};
|
|
794
|
-
const info = new LODInformation(url, key, level, index
|
|
724
|
+
const info = new LODInformation(url, key, level, index);
|
|
795
725
|
res.userData.LODS = info;
|
|
796
726
|
}
|
|
797
727
|
static getAssignedLODInformation(res) {
|
|
@@ -848,16 +778,12 @@ class LODInformation {
|
|
|
848
778
|
level;
|
|
849
779
|
/** For multi objects (e.g. a group of meshes) this is the index of the object */
|
|
850
780
|
index;
|
|
851
|
-
|
|
852
|
-
density;
|
|
853
|
-
constructor(url, key, level, index, density) {
|
|
781
|
+
constructor(url, key, level, index) {
|
|
854
782
|
this.url = url;
|
|
855
783
|
this.key = key;
|
|
856
784
|
this.level = level;
|
|
857
785
|
if (index != undefined)
|
|
858
786
|
this.index = index;
|
|
859
|
-
if (density != undefined)
|
|
860
|
-
this.density = density;
|
|
861
787
|
}
|
|
862
788
|
}
|
|
863
789
|
;
|
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.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getParam } from "./utils.internal.js";
|
|
2
|
+
export const debug = getParam("debugprogressive");
|
|
3
|
+
let debug_RenderWireframe;
|
|
4
|
+
export let debug_OverrideLodLevel = -1; // -1 is automatic
|
|
5
|
+
if (debug) {
|
|
6
|
+
let maxLevel = 6;
|
|
7
|
+
function debugToggleProgressive() {
|
|
8
|
+
debug_OverrideLodLevel += 1;
|
|
9
|
+
if (debug_OverrideLodLevel >= maxLevel) {
|
|
10
|
+
debug_OverrideLodLevel = -1;
|
|
11
|
+
}
|
|
12
|
+
console.log(`Toggle LOD level [${debug_OverrideLodLevel}]`);
|
|
13
|
+
}
|
|
14
|
+
window.addEventListener("keyup", evt => {
|
|
15
|
+
if (evt.key === "p")
|
|
16
|
+
debugToggleProgressive();
|
|
17
|
+
if (evt.key === "w") {
|
|
18
|
+
debug_RenderWireframe = !debug_RenderWireframe;
|
|
19
|
+
console.log(`Toggle wireframe [${debug_RenderWireframe}]`);
|
|
20
|
+
}
|
|
21
|
+
const pressedNumber = parseInt(evt.key);
|
|
22
|
+
if (!isNaN(pressedNumber) && pressedNumber >= 0) {
|
|
23
|
+
debug_OverrideLodLevel = pressedNumber;
|
|
24
|
+
console.log(`Set LOD level to [${debug_OverrideLodLevel}]`);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function applyDebugSettings(material) {
|
|
29
|
+
if (!debug)
|
|
30
|
+
return;
|
|
31
|
+
if (Array.isArray(material)) {
|
|
32
|
+
for (const mat of material) {
|
|
33
|
+
applyDebugSettings(mat);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (material) {
|
|
37
|
+
if ("wireframe" in material) {
|
|
38
|
+
material.wireframe = debug_RenderWireframe === true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
type PromiseType = "texture" | "mesh";
|
|
2
|
+
export type PromiseGroupOptions = {
|
|
3
|
+
name?: string;
|
|
4
|
+
/** How many renderer frames can requests be captured to be awaited */
|
|
5
|
+
frames?: number;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
};
|
|
8
|
+
type PromiseGroupResolveResult = {
|
|
9
|
+
/**
|
|
10
|
+
* `true` if the group was cancelled, `false` if it was resolved normally.
|
|
11
|
+
*/
|
|
12
|
+
cancelled: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The number of promises that started to being awaited
|
|
15
|
+
*/
|
|
16
|
+
awaited_count: number;
|
|
17
|
+
/**
|
|
18
|
+
* The number of promises that were resolved
|
|
19
|
+
*/
|
|
20
|
+
resolved_count: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A group of promises that can be awaited together.
|
|
24
|
+
* This is used for awaiting LOD
|
|
25
|
+
*/
|
|
26
|
+
export declare class PromiseGroup {
|
|
27
|
+
static readonly addPromise: (type: PromiseType, promise: Promise<any>, groups: PromiseGroup[]) => void;
|
|
28
|
+
readonly frame_start: number;
|
|
29
|
+
readonly frame_capture_end: number;
|
|
30
|
+
readonly ready: Promise<PromiseGroupResolveResult>;
|
|
31
|
+
private _resolve;
|
|
32
|
+
private readonly _signal?;
|
|
33
|
+
/**
|
|
34
|
+
* The number of promises that have been added to this group so far.
|
|
35
|
+
*/
|
|
36
|
+
get awaitedCount(): number;
|
|
37
|
+
get resolvedCount(): number;
|
|
38
|
+
get currentlyAwaiting(): number;
|
|
39
|
+
private _resolved;
|
|
40
|
+
private _addedCount;
|
|
41
|
+
private _resolvedCount;
|
|
42
|
+
/** These promises are currently being awaited */
|
|
43
|
+
private readonly _awaiting;
|
|
44
|
+
constructor(frame: number, options: PromiseGroupOptions);
|
|
45
|
+
private _currentFrame;
|
|
46
|
+
update(frame: number): void;
|
|
47
|
+
private add;
|
|
48
|
+
private resolveNow;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A group of promises that can be awaited together.
|
|
3
|
+
* This is used for awaiting LOD
|
|
4
|
+
*/
|
|
5
|
+
export class PromiseGroup {
|
|
6
|
+
static addPromise = (type, promise, groups) => {
|
|
7
|
+
groups.forEach(group => {
|
|
8
|
+
group.add(type, promise);
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
frame_start;
|
|
12
|
+
frame_capture_end;
|
|
13
|
+
ready;
|
|
14
|
+
_resolve;
|
|
15
|
+
_signal;
|
|
16
|
+
/**
|
|
17
|
+
* The number of promises that have been added to this group so far.
|
|
18
|
+
*/
|
|
19
|
+
get awaitedCount() {
|
|
20
|
+
return this._addedCount;
|
|
21
|
+
}
|
|
22
|
+
get resolvedCount() {
|
|
23
|
+
return this._resolvedCount;
|
|
24
|
+
}
|
|
25
|
+
get currentlyAwaiting() {
|
|
26
|
+
return this._awaiting.length;
|
|
27
|
+
}
|
|
28
|
+
_resolved = false;
|
|
29
|
+
_addedCount = 0;
|
|
30
|
+
_resolvedCount = 0;
|
|
31
|
+
/** These promises are currently being awaited */
|
|
32
|
+
_awaiting = [];
|
|
33
|
+
constructor(frame, options) {
|
|
34
|
+
const minFrames = frame === 0 ? 2 : 1; // if we are at frame 0, we need at least 2 frames to capture
|
|
35
|
+
const framesToCapture = Math.max(options.frames ?? minFrames, minFrames); // default to 2 frames and make sure it's at least 2 frames
|
|
36
|
+
this.frame_start = frame;
|
|
37
|
+
this.frame_capture_end = frame + framesToCapture;
|
|
38
|
+
this.ready = new Promise((resolve) => {
|
|
39
|
+
this._resolve = resolve;
|
|
40
|
+
});
|
|
41
|
+
this.ready.finally(() => {
|
|
42
|
+
this._resolved = true;
|
|
43
|
+
this._awaiting.length = 0;
|
|
44
|
+
});
|
|
45
|
+
this._signal = options.signal;
|
|
46
|
+
this._signal?.addEventListener("abort", () => {
|
|
47
|
+
this.resolveNow();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
_currentFrame = 0;
|
|
51
|
+
update(frame) {
|
|
52
|
+
this._currentFrame = frame;
|
|
53
|
+
// If we've passes the frame capture end frame and didn't add any promises, we resolve immediately
|
|
54
|
+
if (this._signal?.aborted || (this._currentFrame > this.frame_capture_end && this._awaiting.length === 0)) {
|
|
55
|
+
this.resolveNow();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
add(_type, promise) {
|
|
59
|
+
if (this._resolved) {
|
|
60
|
+
console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (this._currentFrame > this.frame_capture_end) {
|
|
64
|
+
return; // we are not capturing any more promises
|
|
65
|
+
}
|
|
66
|
+
this._awaiting.push(promise);
|
|
67
|
+
this._addedCount++;
|
|
68
|
+
promise.finally(() => {
|
|
69
|
+
this._resolvedCount++;
|
|
70
|
+
this._awaiting.splice(this._awaiting.indexOf(promise), 1);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
resolveNow() {
|
|
74
|
+
if (this._resolved)
|
|
75
|
+
return;
|
|
76
|
+
this._resolve?.({
|
|
77
|
+
awaited_count: this._addedCount,
|
|
78
|
+
resolved_count: this._resolvedCount,
|
|
79
|
+
cancelled: this._signal?.aborted ?? false,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -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.loading.js";
|
|
3
4
|
export type LODManagerContext = {
|
|
4
5
|
engine: "three" | "needle-engine" | "model-viewer" | "react-three-fiber" | "unknown";
|
|
5
6
|
};
|
|
@@ -43,9 +44,14 @@ declare type LODChangedEventListener = (args: {
|
|
|
43
44
|
*/
|
|
44
45
|
export declare class LODsManager {
|
|
45
46
|
#private;
|
|
46
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Assign a function to draw debug lines for the LODs. This function will be called with the start and end position of the line and the color of the line when the `debugprogressive` query parameter is set.
|
|
47
49
|
*/
|
|
48
50
|
static debugDrawLine?: (a: Vector3, b: Vector3, color: number) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Force override the LOD level for all objects in the scene
|
|
53
|
+
*/
|
|
54
|
+
static overrideGlobalLodLevel?: number;
|
|
49
55
|
/** @internal */
|
|
50
56
|
static getObjectLODState(object: Object3D): LOD_state | undefined;
|
|
51
57
|
static addPlugin(plugin: NEEDLE_progressive_plugin): void;
|
|
@@ -56,9 +62,9 @@ export declare class LODsManager {
|
|
|
56
62
|
* @returns The LODsManager instance.
|
|
57
63
|
*/
|
|
58
64
|
static get(renderer: WebGLRenderer, context?: LODManagerContext): LODsManager;
|
|
59
|
-
private readonly context;
|
|
60
65
|
readonly renderer: WebGLRenderer;
|
|
61
|
-
readonly
|
|
66
|
+
private readonly context;
|
|
67
|
+
private readonly projectionScreenMatrix;
|
|
62
68
|
/** @deprecated use static `LODsManager.addPlugin()` method. This getter will be removed in later versions */
|
|
63
69
|
get plugins(): NEEDLE_progressive_plugin[];
|
|
64
70
|
/**
|
|
@@ -88,6 +94,17 @@ export declare class LODsManager {
|
|
|
88
94
|
* @default false
|
|
89
95
|
*/
|
|
90
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;
|
|
91
108
|
private readonly _lodchangedlisteners;
|
|
92
109
|
addEventListener(evt: "changed", listener: LODChangedEventListener): void;
|
|
93
110
|
removeEventListener(evt: "changed", listener: LODChangedEventListener): void;
|
|
@@ -1,9 +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
|
+
import { applyDebugSettings, debug, debug_OverrideLodLevel } from "./lods.debug.js";
|
|
8
|
+
import { PromiseGroup } from "./lods.loading.js";
|
|
7
9
|
const debugProgressiveLoading = getParam("debugprogressive");
|
|
8
10
|
const suppressProgressiveLoading = getParam("noprogressive");
|
|
9
11
|
const $lodsManager = Symbol("Needle:LODSManager");
|
|
@@ -40,9 +42,14 @@ const levels = { mesh_lod: -1, texture_lod: -1 };
|
|
|
40
42
|
* ```
|
|
41
43
|
*/
|
|
42
44
|
export class LODsManager {
|
|
43
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* Assign a function to draw debug lines for the LODs. This function will be called with the start and end position of the line and the color of the line when the `debugprogressive` query parameter is set.
|
|
44
47
|
*/
|
|
45
48
|
static debugDrawLine;
|
|
49
|
+
/**
|
|
50
|
+
* Force override the LOD level for all objects in the scene
|
|
51
|
+
*/
|
|
52
|
+
static overrideGlobalLodLevel;
|
|
46
53
|
/** @internal */
|
|
47
54
|
static getObjectLODState(object) {
|
|
48
55
|
return object[$lodstate];
|
|
@@ -72,8 +79,8 @@ export class LODsManager {
|
|
|
72
79
|
renderer[$lodsManager] = lodsManager;
|
|
73
80
|
return lodsManager;
|
|
74
81
|
}
|
|
75
|
-
context;
|
|
76
82
|
renderer;
|
|
83
|
+
context;
|
|
77
84
|
projectionScreenMatrix = new Matrix4();
|
|
78
85
|
/** @deprecated use static `LODsManager.addPlugin()` method. This getter will be removed in later versions */
|
|
79
86
|
get plugins() { return plugins; }
|
|
@@ -105,6 +112,37 @@ export class LODsManager {
|
|
|
105
112
|
* @default false
|
|
106
113
|
*/
|
|
107
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
|
+
}
|
|
108
146
|
_lodchangedlisteners = [];
|
|
109
147
|
addEventListener(evt, listener) {
|
|
110
148
|
if (evt === "changed") {
|
|
@@ -226,6 +264,7 @@ export class LODsManager {
|
|
|
226
264
|
return;
|
|
227
265
|
}
|
|
228
266
|
this.internalUpdate(scene, camera);
|
|
267
|
+
this._postprocessPromiseGroups();
|
|
229
268
|
}
|
|
230
269
|
}
|
|
231
270
|
/**
|
|
@@ -305,17 +344,26 @@ export class LODsManager {
|
|
|
305
344
|
for (const plugin of plugins) {
|
|
306
345
|
plugin.onBeforeUpdateLOD?.(this.renderer, scene, camera, object);
|
|
307
346
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
347
|
+
const debugLodLevel = LODsManager.overrideGlobalLodLevel !== undefined ? LODsManager.overrideGlobalLodLevel : debug_OverrideLodLevel;
|
|
348
|
+
if (debugLodLevel >= 0) {
|
|
349
|
+
levels.mesh_lod = debugLodLevel;
|
|
350
|
+
levels.texture_lod = debugLodLevel;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
this.calculateLodLevel(camera, object, state, desiredDensity, levels);
|
|
354
|
+
levels.mesh_lod = Math.round(levels.mesh_lod);
|
|
355
|
+
levels.texture_lod = Math.round(levels.texture_lod);
|
|
356
|
+
}
|
|
311
357
|
// we currently only support auto LOD changes for meshes
|
|
312
358
|
if (levels.mesh_lod >= 0) {
|
|
313
359
|
this.loadProgressiveMeshes(object, levels.mesh_lod);
|
|
314
360
|
}
|
|
315
361
|
// TODO: we currently can not switch texture lods because we need better caching for the textures internally (see copySettings in progressive + NE-4431)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
362
|
+
if (object.material && levels.texture_lod >= 0) {
|
|
363
|
+
this.loadProgressiveTextures(object.material, levels.texture_lod);
|
|
364
|
+
}
|
|
365
|
+
if (debug && object.material && !object["isGizmo"]) {
|
|
366
|
+
applyDebugSettings(object.material);
|
|
319
367
|
}
|
|
320
368
|
for (const plugin of plugins) {
|
|
321
369
|
plugin.onAfterUpdatedLOD?.(this.renderer, scene, camera, object, levels);
|
|
@@ -353,9 +401,10 @@ export class LODsManager {
|
|
|
353
401
|
}
|
|
354
402
|
if (update) {
|
|
355
403
|
material[$currentLOD] = level;
|
|
356
|
-
NEEDLE_progressive.assignTextureLOD(material, level).then(_ => {
|
|
404
|
+
const promise = NEEDLE_progressive.assignTextureLOD(material, level).then(_ => {
|
|
357
405
|
this._lodchangedlisteners.forEach(l => l({ type: "texture", level, object: material }));
|
|
358
406
|
});
|
|
407
|
+
PromiseGroup.addPromise("texture", promise, this._newPromiseGroups);
|
|
359
408
|
}
|
|
360
409
|
}
|
|
361
410
|
/** Load progressive meshes for the given mesh
|
|
@@ -376,19 +425,14 @@ export class LODsManager {
|
|
|
376
425
|
if (update) {
|
|
377
426
|
mesh[$currentLOD] = level;
|
|
378
427
|
const originalGeometry = mesh.geometry;
|
|
379
|
-
|
|
428
|
+
const promise = NEEDLE_progressive.assignMeshLOD(mesh, level).then(res => {
|
|
380
429
|
if (res && mesh[$currentLOD] == level && originalGeometry != mesh.geometry) {
|
|
381
430
|
this._lodchangedlisteners.forEach(l => l({ type: "mesh", level, object: mesh }));
|
|
382
|
-
// if (this.handles) {
|
|
383
|
-
// for (const inst of this.handles) {
|
|
384
|
-
// // if (inst["LOD"] < level) continue;
|
|
385
|
-
// // inst["LOD"] = level;
|
|
386
|
-
// inst.setGeometry(mesh.geometry);
|
|
387
|
-
// }
|
|
388
|
-
// }
|
|
389
431
|
}
|
|
390
432
|
return res;
|
|
391
433
|
});
|
|
434
|
+
PromiseGroup.addPromise("mesh", promise, this._newPromiseGroups);
|
|
435
|
+
return promise;
|
|
392
436
|
}
|
|
393
437
|
return Promise.resolve(null);
|
|
394
438
|
}
|
|
@@ -435,8 +479,8 @@ export class LODsManager {
|
|
|
435
479
|
return mesh["DEBUG:LOD"];
|
|
436
480
|
}
|
|
437
481
|
// The mesh info contains also the density for all available LOD level so we can use this for selecting which level to show
|
|
438
|
-
const
|
|
439
|
-
const
|
|
482
|
+
const mesh_lods = NEEDLE_progressive.getMeshLODExtension(mesh.geometry)?.lods;
|
|
483
|
+
const primitive_index = NEEDLE_progressive.getPrimitiveIndex(mesh.geometry);
|
|
440
484
|
const has_mesh_lods = mesh_lods && mesh_lods.length > 0;
|
|
441
485
|
const texture_lods_minmax = NEEDLE_progressive.getMaterialMinMaxLODsCount(mesh.material);
|
|
442
486
|
const has_texture_lods = texture_lods_minmax?.min_count != Infinity && texture_lods_minmax.min_count > 0 && texture_lods_minmax.max_count > 0;
|
|
@@ -590,7 +634,8 @@ export class LODsManager {
|
|
|
590
634
|
// const framerate = this.context.time.smoothedFps;
|
|
591
635
|
if (mesh_lods && state.lastScreenCoverage > 0) {
|
|
592
636
|
for (let l = 0; l < mesh_lods.length; l++) {
|
|
593
|
-
const
|
|
637
|
+
const lod = mesh_lods[l];
|
|
638
|
+
const densityForThisLevel = lod.densities?.[primitive_index] || lod.density || .00001;
|
|
594
639
|
const resultingDensity = densityForThisLevel / state.lastScreenCoverage;
|
|
595
640
|
if (resultingDensity < desiredDensity) {
|
|
596
641
|
expectedLevel = l;
|
|
@@ -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");
|