@needle-tools/gltf-progressive 2.1.6 → 3.0.0-alpha.2

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/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 { getParam, resolveUrl } from "./utils.internal.js";
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 getMeshLODInformation(geo) {
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, undefined, ext);
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, undefined);
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
- NEEDLE_progressive.assignLODInformation(url, geometry, key, level, index, ext.density);
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, 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, undefined, meshExt.density);
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, meshExt.density);
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, density) {
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, density);
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
- /** the mesh density */
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 "./lods_manager.js";
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 "./lods_manager.js";
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 "./lods_manager.js";
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 "./lods_manager.js";
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,4 @@
1
+ import { Material } from "three";
2
+ export declare const debug: string | boolean;
3
+ export declare let debug_OverrideLodLevel: number;
4
+ export declare function applyDebugSettings(material: Material | Array<Material>): void;
@@ -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
+ }
@@ -1,5 +1,6 @@
1
- import { Camera, Material, Matrix4, Object3D, Scene, Texture, Vector3, WebGLRenderer } from "three";
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
  };
@@ -43,9 +44,14 @@ declare type LODChangedEventListener = (args: {
43
44
  */
44
45
  export declare class LODsManager {
45
46
  #private;
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.
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 projectionScreenMatrix: Matrix4;
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.promise.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
- /** 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.
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
- this.calculateLodLevel(camera, object, state, desiredDensity, levels);
309
- levels.mesh_lod = Math.round(levels.mesh_lod);
310
- levels.texture_lod = Math.round(levels.texture_lod);
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
- let textureLOD = levels.texture_lod;
317
- if (object.material && textureLOD >= 0) {
318
- this.loadProgressiveTextures(object.material, textureLOD);
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
- return NEEDLE_progressive.assignMeshLOD(mesh, level).then(res => {
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 mesh_lods_info = NEEDLE_progressive.getMeshLODInformation(mesh.geometry);
439
- const mesh_lods = mesh_lods_info?.lods;
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 densityForThisLevel = mesh_lods[l].density;
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;
@@ -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 = 2; // wait at least 2 frames to capture promises
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,4 +1,4 @@
1
- import { LODsManager } from "../lods_manager.js";
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");