@needle-tools/gltf-progressive 3.5.0-rc → 3.6.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.
@@ -1,4 +1,4 @@
1
- import { Box3, Clock, Matrix4, Mesh, MeshStandardMaterial, Sphere, Vector3 } from "three";
1
+ import { Box3, Clock, Color, Matrix4, Mesh, Sphere, Vector3 } from "three";
2
2
  import { NEEDLE_progressive } from "./extension.js";
3
3
  import { createLoaders } from "./loaders.js";
4
4
  import { getParam, isDevelopmentServer, isMobileDevice } from "./utils.internal.js";
@@ -7,11 +7,172 @@ import { getRaycastMesh } from "./utils.js";
7
7
  import { applyDebugSettings, debug, debug_OverrideLodLevel } from "./lods.debug.js";
8
8
  import { PromiseGroup } from "./lods.promise.js";
9
9
  const debugProgressiveLoading = getParam("debugprogressive");
10
+ const debugProgressiveLODColors = debugProgressiveLoading === "colors";
10
11
  const suppressProgressiveLoading = getParam("noprogressive");
11
12
  const $lodsManager = Symbol("Needle:LODSManager");
12
13
  const $lodstate = Symbol("Needle:LODState");
13
14
  const $currentLOD = Symbol("Needle:CurrentLOD");
14
15
  const levels = { mesh_lod: -1, texture_lod: -1 };
16
+ const debugLODColor = new Color();
17
+ export const lodDebugColors = [
18
+ 0x35d05f,
19
+ 0xa8d83a,
20
+ 0xf3d13b,
21
+ 0xf29332,
22
+ 0xf0523b,
23
+ 0xa856f0,
24
+ 0x49a7f2,
25
+ 0x32d7c4,
26
+ 0xff6b9d,
27
+ 0x6f7df7,
28
+ 0xd66fd2,
29
+ 0x35a853,
30
+ 0xb7a51f,
31
+ 0xe05d2f,
32
+ 0x3c78d8,
33
+ 0x00a6a6,
34
+ 0xd7263d,
35
+ 0x7f52ff,
36
+ 0x46b450,
37
+ 0xf7a531,
38
+ 0x2f9be0,
39
+ 0xb84592,
40
+ 0x8a9a2a,
41
+ 0x1f6f8b,
42
+ 0xf05a9d,
43
+ 0x9b5de5,
44
+ 0x00bbf9,
45
+ 0x00f5d4,
46
+ 0xfee440,
47
+ 0xf15bb5,
48
+ 0x4d908e,
49
+ 0x555555,
50
+ ];
51
+ const _meshLODWorldBox = new Box3();
52
+ const _meshLODProjectedBox = new Box3();
53
+ const _meshLODCameraSpaceBox = new Box3();
54
+ const _meshLODBoxSize = new Vector3();
55
+ const _meshLODCameraSpaceBoxSize = new Vector3();
56
+ const _meshLODProjectionInverse = new Matrix4();
57
+ const _meshLODCorner0 = new Vector3();
58
+ const _meshLODCorner1 = new Vector3();
59
+ const _meshLODCorner2 = new Vector3();
60
+ const _meshLODCorner3 = new Vector3();
61
+ function isInsideProjectedBox(box, projectionScreenMatrix) {
62
+ const min = box.min;
63
+ const max = box.max;
64
+ const centerx = (min.x + max.x) * 0.5;
65
+ const centery = (min.y + max.y) * 0.5;
66
+ const point = _meshLODCorner0.set(centerx, centery, min.z).applyMatrix4(projectionScreenMatrix);
67
+ return point.z < 0;
68
+ }
69
+ export function calculateMeshLODLevel(options) {
70
+ const { geometry, matrixWorld, camera, projectionScreenMatrix, desiredDensity, canvasHeight = 0, currentLevel = -1, xrEnabled = false, debugDrawLine, warnMissingPrimitiveDensities = false, } = options;
71
+ const meshLods = NEEDLE_progressive.getMeshLODExtension(geometry)?.lods;
72
+ const primitiveIndex = NEEDLE_progressive.getPrimitiveIndex(geometry);
73
+ const result = options.target ?? {
74
+ level: currentLevel,
75
+ primitiveIndex,
76
+ screenCoverage: 0,
77
+ screenspaceVolume: new Vector3(),
78
+ centrality: 1,
79
+ };
80
+ result.level = currentLevel;
81
+ result.primitiveIndex = primitiveIndex;
82
+ result.screenCoverage = 0;
83
+ result.screenspaceVolume.set(0, 0, 0);
84
+ result.centrality = 1;
85
+ if (!meshLods?.length)
86
+ return result;
87
+ let boundingBox = options.boundingBox ?? geometry.boundingBox;
88
+ if (!boundingBox) {
89
+ geometry.computeBoundingBox();
90
+ boundingBox = geometry.boundingBox;
91
+ }
92
+ if (!boundingBox)
93
+ return result;
94
+ _meshLODWorldBox.copy(boundingBox).applyMatrix4(matrixWorld);
95
+ if (camera.isPerspectiveCamera && isInsideProjectedBox(_meshLODWorldBox, projectionScreenMatrix)) {
96
+ result.level = 0;
97
+ result.screenCoverage = Infinity;
98
+ result.screenspaceVolume.set(Infinity, Infinity, Infinity);
99
+ return result;
100
+ }
101
+ _meshLODProjectedBox.copy(_meshLODWorldBox).applyMatrix4(projectionScreenMatrix);
102
+ if (xrEnabled && camera.isPerspectiveCamera && camera.fov > 70) {
103
+ const min = _meshLODProjectedBox.min;
104
+ const max = _meshLODProjectedBox.max;
105
+ let minX = min.x;
106
+ let minY = min.y;
107
+ let maxX = max.x;
108
+ let maxY = max.y;
109
+ const enlargementFactor = 2.0;
110
+ const centerBoost = 1.5;
111
+ const centerX = (min.x + max.x) * 0.5;
112
+ const centerY = (min.y + max.y) * 0.5;
113
+ minX = (minX - centerX) * enlargementFactor + centerX;
114
+ minY = (minY - centerY) * enlargementFactor + centerY;
115
+ maxX = (maxX - centerX) * enlargementFactor + centerX;
116
+ maxY = (maxY - centerY) * enlargementFactor + centerY;
117
+ const xCentrality = minX < 0 && maxX > 0 ? 0 : Math.min(Math.abs(min.x), Math.abs(max.x));
118
+ const yCentrality = minY < 0 && maxY > 0 ? 0 : Math.min(Math.abs(min.y), Math.abs(max.y));
119
+ const centrality = Math.max(xCentrality, yCentrality);
120
+ result.centrality = (centerBoost - centrality) * (centerBoost - centrality) * (centerBoost - centrality);
121
+ }
122
+ const boxSize = _meshLODProjectedBox.getSize(_meshLODBoxSize);
123
+ boxSize.multiplyScalar(0.5);
124
+ if (globalThis.screen?.availHeight > 0 && canvasHeight > 0) {
125
+ boxSize.multiplyScalar(canvasHeight / globalThis.screen.availHeight);
126
+ }
127
+ if (camera.isPerspectiveCamera) {
128
+ boxSize.x *= camera.aspect;
129
+ }
130
+ _meshLODCameraSpaceBox.copy(boundingBox).applyMatrix4(matrixWorld).applyMatrix4(camera.matrixWorldInverse);
131
+ const cameraSpaceSize = _meshLODCameraSpaceBox.getSize(_meshLODCameraSpaceBoxSize);
132
+ const screenMax = Math.max(boxSize.x, boxSize.y);
133
+ const cameraSpaceMax = Math.max(cameraSpaceSize.x, cameraSpaceSize.y);
134
+ if (screenMax !== 0 && cameraSpaceMax !== 0) {
135
+ boxSize.z = cameraSpaceSize.z / cameraSpaceMax * screenMax;
136
+ }
137
+ const screenCoverage = Math.max(boxSize.x, boxSize.y, boxSize.z) * result.centrality;
138
+ result.screenCoverage = screenCoverage;
139
+ result.screenspaceVolume.copy(boxSize);
140
+ if (screenCoverage <= 0)
141
+ return result;
142
+ if (debugDrawLine) {
143
+ const mat = _meshLODProjectionInverse.copy(projectionScreenMatrix);
144
+ mat.invert();
145
+ _meshLODCorner0.copy(_meshLODProjectedBox.min);
146
+ _meshLODCorner1.copy(_meshLODProjectedBox.max);
147
+ _meshLODCorner1.x = _meshLODCorner0.x;
148
+ _meshLODCorner2.copy(_meshLODProjectedBox.max);
149
+ _meshLODCorner2.y = _meshLODCorner0.y;
150
+ _meshLODCorner3.copy(_meshLODProjectedBox.max);
151
+ const z = (_meshLODCorner0.z + _meshLODCorner3.z) * 0.5;
152
+ _meshLODCorner0.z = _meshLODCorner1.z = _meshLODCorner2.z = _meshLODCorner3.z = z;
153
+ _meshLODCorner0.applyMatrix4(mat);
154
+ _meshLODCorner1.applyMatrix4(mat);
155
+ _meshLODCorner2.applyMatrix4(mat);
156
+ _meshLODCorner3.applyMatrix4(mat);
157
+ debugDrawLine(_meshLODCorner0, _meshLODCorner1, 0x0000ff);
158
+ debugDrawLine(_meshLODCorner0, _meshLODCorner2, 0x0000ff);
159
+ debugDrawLine(_meshLODCorner1, _meshLODCorner3, 0x0000ff);
160
+ debugDrawLine(_meshLODCorner2, _meshLODCorner3, 0x0000ff);
161
+ }
162
+ for (let i = 0; i < meshLods.length; i++) {
163
+ const lod = meshLods[i];
164
+ const density = lod.densities?.[primitiveIndex] || lod.density || .00001;
165
+ if (primitiveIndex > 0 && warnMissingPrimitiveDensities && isDevelopmentServer() && !lod.densities && !globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]) {
166
+ globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] = true;
167
+ 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.`);
168
+ }
169
+ if (density / screenCoverage < desiredDensity) {
170
+ result.level = i;
171
+ break;
172
+ }
173
+ }
174
+ return result;
175
+ }
15
176
  /**
16
177
  * The LODsManager class is responsible for managing the LODs and progressive assets in the scene. It will automatically update the LODs based on the camera position, screen coverage and mesh density of the objects.
17
178
  * It must be enabled by calling the `enable` method.
@@ -116,7 +277,31 @@ export class LODsManager {
116
277
  _newPromiseGroups = [];
117
278
  _promiseGroupIds = 0;
118
279
  /**
119
- * Call to await LODs loading during the next render cycle.
280
+ * Returns a promise that resolves once all LOD requests initiated during the next render cycles have finished loading.
281
+ * This is useful for hiding low-resolution placeholders (e.g. with a loading overlay or CSS blur) until high-quality assets are ready.
282
+ *
283
+ * By default, the returned promise captures LOD loading requests for 2 frames and resolves when all of them complete.
284
+ * Use `waitForFirstCapture` if no LOD requests may happen immediately (e.g. after a scene switch).
285
+ *
286
+ * @param opts - Optional configuration for how long to capture and what to wait for. See {@link PromiseGroupOptions}.
287
+ * @returns A promise that resolves with `{ cancelled, awaited_count, resolved_count }` once all captured LOD loads complete (or the signal aborts).
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * // Wait for initial LODs to finish loading, then remove a blur overlay
292
+ * const result = await lodsManager.awaitLoading({
293
+ * frames: 5,
294
+ * signal: AbortSignal.timeout(10_000),
295
+ * });
296
+ * console.log(`Loaded ${result.resolved_count} of ${result.awaited_count} LODs`);
297
+ * document.querySelector('.blur-overlay')?.remove();
298
+ * ```
299
+ *
300
+ * @example
301
+ * ```ts
302
+ * // Wait until at least one LOD starts loading before resolving
303
+ * await lodsManager.awaitLoading({ waitForFirstCapture: true });
304
+ * ```
120
305
  */
121
306
  awaitLoading(opts) {
122
307
  const id = this._promiseGroupIds++;
@@ -136,6 +321,11 @@ export class LODsManager {
136
321
  });
137
322
  return newGroup.ready;
138
323
  }
324
+ /** Track LOD work started outside this manager so {@link awaitLoading} waits for it too. */
325
+ trackLoadingPromise(type, object, promise) {
326
+ PromiseGroup.addPromise(type, object, promise, this._newPromiseGroups);
327
+ return promise;
328
+ }
139
329
  _postprocessPromiseGroups() {
140
330
  if (this._newPromiseGroups.length === 0)
141
331
  return;
@@ -145,17 +335,46 @@ export class LODsManager {
145
335
  }
146
336
  }
147
337
  _lodchangedlisteners = [];
338
+ /**
339
+ * Register a listener that is called whenever a mesh or texture LOD level has finished loading and has been applied.
340
+ * The listener receives the type of asset (`"mesh"` or `"texture"`), the new LOD level, and the affected object.
341
+ *
342
+ * @param evt - The event type. Currently only `"changed"` is supported.
343
+ * @param listener - Callback invoked after a LOD swap completes.
344
+ * @return A function to unregister the listener.
345
+ *
346
+ * @example
347
+ * ```ts
348
+ * lodsManager.addEventListener("changed", ({ type, level, object }) => {
349
+ * console.log(`${type} LOD changed to level ${level}`, object);
350
+ * });
351
+ * ```
352
+ */
148
353
  addEventListener(evt, listener) {
149
354
  if (evt === "changed") {
150
355
  this._lodchangedlisteners.push(listener);
356
+ return () => {
357
+ this.removeEventListener(evt, listener);
358
+ };
151
359
  }
360
+ return () => { };
152
361
  }
362
+ /**
363
+ * Remove a previously registered `"changed"` event listener.
364
+ * @param evt - The event type (`"changed"`).
365
+ * @param listener - The listener to remove.
366
+ * @return `true` if the listener was found and removed, `false` otherwise.
367
+ */
153
368
  removeEventListener(evt, listener) {
369
+ let removed = false;
154
370
  if (evt === "changed") {
155
371
  const index = this._lodchangedlisteners.indexOf(listener);
156
- if (index >= 0)
372
+ if (index >= 0) {
157
373
  this._lodchangedlisteners.splice(index, 1);
374
+ removed = true;
375
+ }
158
376
  }
377
+ return removed;
159
378
  }
160
379
  // readonly plugins: NEEDLE_progressive_plugin[] = [];
161
380
  constructor(renderer, context) {
@@ -217,6 +436,21 @@ export class LODsManager {
217
436
  this.renderer.render = this.#originalRender;
218
437
  this.#originalRender = undefined;
219
438
  }
439
+ /**
440
+ * Manually trigger a LOD update for a scene and camera.
441
+ * Only needed when {@link manual} is set to `true` — otherwise LOD updates happen automatically on each render call.
442
+ *
443
+ * @param scene - The scene containing objects with progressive LODs.
444
+ * @param camera - The camera used to determine screen coverage and LOD levels.
445
+ *
446
+ * @example
447
+ * ```ts
448
+ * const lodsManager = LODsManager.get(renderer);
449
+ * lodsManager.manual = true;
450
+ * // ... later, trigger an update at a specific point:
451
+ * lodsManager.update(scene, camera);
452
+ * ```
453
+ */
220
454
  update(scene, camera) {
221
455
  this.internalUpdate(scene, camera);
222
456
  }
@@ -306,16 +540,6 @@ export class LODsManager {
306
540
  case "MeshDepthMaterial":
307
541
  continue;
308
542
  }
309
- if (debugProgressiveLoading === "color") {
310
- if (entry.material) {
311
- if (!entry.object["progressive_debug_color"]) {
312
- entry.object["progressive_debug_color"] = true;
313
- const randomColor = Math.random() * 0xffffff;
314
- const newMaterial = new MeshStandardMaterial({ color: randomColor });
315
- entry.object.material = newMaterial;
316
- }
317
- }
318
- }
319
543
  const object = entry.object;
320
544
  if (object instanceof Mesh || (object.isMesh)) {
321
545
  this.updateLODs(scene, camera, object, desiredDensity);
@@ -374,6 +598,9 @@ export class LODsManager {
374
598
  if (debug && object.material && !object["isGizmo"]) {
375
599
  applyDebugSettings(object.material);
376
600
  }
601
+ if (debugProgressiveLODColors && object.material && !object["isGizmo"] && !object["isBatchedMesh"]) {
602
+ applyLODColor(object.material, levels.mesh_lod);
603
+ }
377
604
  for (const plugin of plugins) {
378
605
  plugin.onAfterUpdatedLOD?.(this.renderer, scene, camera, object, levels);
379
606
  }
@@ -390,7 +617,7 @@ export class LODsManager {
390
617
  return;
391
618
  if (Array.isArray(material)) {
392
619
  for (const mat of material) {
393
- this.loadProgressiveTextures(mat, level);
620
+ this.loadProgressiveTextures(mat, level, overrideLodLevel);
394
621
  }
395
622
  return;
396
623
  }
@@ -403,13 +630,15 @@ export class LODsManager {
403
630
  else if (level < material[$currentLOD]) {
404
631
  update = true;
405
632
  }
406
- if (overrideLodLevel !== undefined && overrideLodLevel >= 0) {
633
+ const forceExactTextureLOD = overrideLodLevel !== undefined && overrideLodLevel >= 0;
634
+ if (forceExactTextureLOD) {
407
635
  update = material[$currentLOD] != overrideLodLevel;
408
636
  level = overrideLodLevel;
409
637
  }
410
638
  if (update) {
411
639
  material[$currentLOD] = level;
412
- const promise = NEEDLE_progressive.assignTextureLOD(material, level).then(_ => {
640
+ const options = forceExactTextureLOD ? { force: true } : undefined;
641
+ const promise = NEEDLE_progressive.assignTextureLOD(material, level, options).then(_ => {
413
642
  this._lodchangedlisteners.forEach(l => l({ type: "texture", level, object: material }));
414
643
  });
415
644
  PromiseGroup.addPromise("texture", material, promise, this._newPromiseGroups);
@@ -446,25 +675,7 @@ export class LODsManager {
446
675
  }
447
676
  // private testIfLODLevelsAreAvailable() {
448
677
  _sphere = new Sphere();
449
- _tempBox = new Box3();
450
- _tempBox2 = new Box3();
451
- tempMatrix = new Matrix4();
452
678
  _tempWorldPosition = new Vector3();
453
- _tempBoxSize = new Vector3();
454
- _tempBox2Size = new Vector3();
455
- static corner0 = new Vector3();
456
- static corner1 = new Vector3();
457
- static corner2 = new Vector3();
458
- static corner3 = new Vector3();
459
- static _tempPtInside = new Vector3();
460
- static isInside(box, matrix) {
461
- const min = box.min;
462
- const max = box.max;
463
- const centerx = (min.x + max.x) * 0.5;
464
- const centery = (min.y + max.y) * 0.5;
465
- const pt1 = this._tempPtInside.set(centerx, centery, min.z).applyMatrix4(matrix);
466
- return pt1.z < 0;
467
- }
468
679
  static skinnedMeshBoundsFrameOffsetCounter = 0;
469
680
  static $skinnedMeshBoundsOffset = Symbol("gltf-progressive-skinnedMeshBoundsOffset");
470
681
  // #region calculateLodLevel
@@ -536,7 +747,6 @@ export class LODsManager {
536
747
  boundingBox = skinnedMesh.boundingBox;
537
748
  }
538
749
  if (boundingBox) {
539
- const cam = camera;
540
750
  // hack: if the mesh has vertex colors, has less than 100 vertices we always select the highest LOD
541
751
  if (mesh.geometry.attributes.color && mesh.geometry.attributes.color.count < 100) {
542
752
  if (mesh.geometry.boundingSphere) {
@@ -550,126 +760,30 @@ export class LODsManager {
550
760
  }
551
761
  }
552
762
  }
553
- // calculate size on screen
554
- this._tempBox.copy(boundingBox);
555
- this._tempBox.applyMatrix4(mesh.matrixWorld);
556
- // Converting into projection space has the disadvantage that objects further to the side
557
- // will have a much larger coverage, especially with high-field-of-view situations like in VR.
558
- // Alternatively, we could attempt to calculate angular coverage (some kind of polar coordinates maybe?)
559
- // or introduce a correction factor based on "expected distortion" of the object.
560
- // High distortions would lead to lower LOD levels.
561
- // "Centrality" of the calculated screen-space bounding box could be a factor here –
562
- // what's the distance of the bounding box to the center of the screen?
563
- if (cam.isPerspectiveCamera && LODsManager.isInside(this._tempBox, this.projectionScreenMatrix)) {
763
+ const selection = calculateMeshLODLevel({
764
+ geometry: mesh.geometry,
765
+ matrixWorld: mesh.matrixWorld,
766
+ camera,
767
+ projectionScreenMatrix: this.projectionScreenMatrix,
768
+ desiredDensity,
769
+ canvasHeight,
770
+ currentLevel: state.lastLodLevel_Mesh,
771
+ boundingBox,
772
+ xrEnabled: this.renderer.xr.enabled,
773
+ debugDrawLine: debugProgressiveLoading ? LODsManager.debugDrawLine : undefined,
774
+ warnMissingPrimitiveDensities: true,
775
+ });
776
+ state.lastCentrality = selection.centrality;
777
+ state.lastScreenCoverage = selection.screenCoverage;
778
+ state.lastScreenspaceVolume.copy(selection.screenspaceVolume);
779
+ if (selection.screenCoverage === Infinity) {
564
780
  result.mesh_lod = 0;
565
781
  result.texture_lod = 0;
566
782
  return;
567
783
  }
568
- this._tempBox.applyMatrix4(this.projectionScreenMatrix);
569
- // TODO might need to be adjusted for cameras that are rendered during an XR session but are
570
- // actually not XR cameras (e.g. a render texture)
571
- if (this.renderer.xr.enabled && (cam.isPerspectiveCamera) && cam.fov > 70) {
572
- // calculate centrality of the bounding box - how close is it to the screen center
573
- const min = this._tempBox.min;
574
- const max = this._tempBox.max;
575
- let minX = min.x;
576
- let minY = min.y;
577
- let maxX = max.x;
578
- let maxY = max.y;
579
- // enlarge
580
- const enlargementFactor = 2.0;
581
- const centerBoost = 1.5;
582
- const centerX = (min.x + max.x) * 0.5;
583
- const centerY = (min.y + max.y) * 0.5;
584
- minX = (minX - centerX) * enlargementFactor + centerX;
585
- minY = (minY - centerY) * enlargementFactor + centerY;
586
- maxX = (maxX - centerX) * enlargementFactor + centerX;
587
- maxY = (maxY - centerY) * enlargementFactor + centerY;
588
- const xCentrality = minX < 0 && maxX > 0 ? 0 : Math.min(Math.abs(min.x), Math.abs(max.x));
589
- const yCentrality = minY < 0 && maxY > 0 ? 0 : Math.min(Math.abs(min.y), Math.abs(max.y));
590
- const centrality = Math.max(xCentrality, yCentrality);
591
- // heuristically determined to lower quality for objects at the edges of vision
592
- state.lastCentrality = (centerBoost - centrality) * (centerBoost - centrality) * (centerBoost - centrality);
593
- }
594
- else {
595
- state.lastCentrality = 1;
596
- }
597
- const boxSize = this._tempBox.getSize(this._tempBoxSize);
598
- boxSize.multiplyScalar(0.5); // goes from -1..1, we want -0.5..0.5 for coverage in percent
599
- if (screen.availHeight > 0) {
600
- // correct for size of context on screen
601
- if (canvasHeight > 0)
602
- boxSize.multiplyScalar(canvasHeight / screen.availHeight);
603
- }
604
- if (camera.isPerspectiveCamera) {
605
- boxSize.x *= camera.aspect;
606
- }
607
- else if (camera.isOrthographicCamera) {
608
- // const cam = camera as OrthographicCamera;
609
- // boxSize.x *= cam.zoom * .01;
610
- }
611
- const matView = camera.matrixWorldInverse;
612
- const box2 = this._tempBox2;
613
- box2.copy(boundingBox);
614
- box2.applyMatrix4(mesh.matrixWorld);
615
- box2.applyMatrix4(matView);
616
- const boxSize2 = box2.getSize(this._tempBox2Size);
617
- // approximate depth coverage in relation to screenspace size
618
- const max2 = Math.max(boxSize2.x, boxSize2.y);
619
- const max1 = Math.max(boxSize.x, boxSize.y);
620
- if (max1 != 0 && max2 != 0)
621
- boxSize.z = boxSize2.z / Math.max(boxSize2.x, boxSize2.y) * Math.max(boxSize.x, boxSize.y);
622
- state.lastScreenCoverage = Math.max(boxSize.x, boxSize.y, boxSize.z);
623
- state.lastScreenspaceVolume.copy(boxSize);
624
- state.lastScreenCoverage *= state.lastCentrality;
625
- // draw screen size box
626
- if (debugProgressiveLoading && LODsManager.debugDrawLine) {
627
- const mat = this.tempMatrix.copy(this.projectionScreenMatrix);
628
- mat.invert();
629
- const corner0 = LODsManager.corner0;
630
- const corner1 = LODsManager.corner1;
631
- const corner2 = LODsManager.corner2;
632
- const corner3 = LODsManager.corner3;
633
- // get box corners, transform with camera space, and draw as quad lines
634
- corner0.copy(this._tempBox.min);
635
- corner1.copy(this._tempBox.max);
636
- corner1.x = corner0.x;
637
- corner2.copy(this._tempBox.max);
638
- corner2.y = corner0.y;
639
- corner3.copy(this._tempBox.max);
640
- // draw outlines at the center of the box
641
- const z = (corner0.z + corner3.z) * 0.5;
642
- // all outlines should have the same depth in screen space
643
- corner0.z = corner1.z = corner2.z = corner3.z = z;
644
- corner0.applyMatrix4(mat);
645
- corner1.applyMatrix4(mat);
646
- corner2.applyMatrix4(mat);
647
- corner3.applyMatrix4(mat);
648
- LODsManager.debugDrawLine(corner0, corner1, 0x0000ff);
649
- LODsManager.debugDrawLine(corner0, corner2, 0x0000ff);
650
- LODsManager.debugDrawLine(corner1, corner3, 0x0000ff);
651
- LODsManager.debugDrawLine(corner2, corner3, 0x0000ff);
652
- }
653
- let expectedLevel = 999;
654
- // const framerate = this.context.time.smoothedFps;
655
- if (mesh_lods && state.lastScreenCoverage > 0) {
656
- for (let l = 0; l < mesh_lods.length; l++) {
657
- const lod = mesh_lods[l];
658
- const densityForThisLevel = lod.densities?.[primitive_index] || lod.density || .00001;
659
- const resultingDensity = densityForThisLevel / state.lastScreenCoverage;
660
- if (primitive_index > 0 && isDevelopmentServer() && !lod.densities && !globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]) {
661
- window["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] = true;
662
- 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.`);
663
- }
664
- if (resultingDensity < desiredDensity) {
665
- expectedLevel = l;
666
- break;
667
- }
668
- }
669
- }
670
- const isLowerLod = expectedLevel < mesh_level;
784
+ const isLowerLod = selection.level >= 0 && selection.level < mesh_level;
671
785
  if (isLowerLod) {
672
- mesh_level = expectedLevel;
786
+ mesh_level = selection.level;
673
787
  mesh_level_calculated = true;
674
788
  }
675
789
  }
@@ -745,3 +859,21 @@ class LOD_state {
745
859
  lastScreenspaceVolume = new Vector3();
746
860
  lastCentrality = 0;
747
861
  }
862
+ function applyLODColor(material, level) {
863
+ if (level < 0)
864
+ return;
865
+ if (Array.isArray(material)) {
866
+ for (const mat of material) {
867
+ applyLODColor(mat, level);
868
+ }
869
+ return;
870
+ }
871
+ if ("color" in material && material.color instanceof Color) {
872
+ material.color.copy(getLODColor(level, debugLODColor));
873
+ material.needsUpdate = true;
874
+ }
875
+ }
876
+ export function getLODColor(level, target) {
877
+ const index = Math.max(0, Math.min(lodDebugColors.length - 1, Math.floor(level)));
878
+ return target.setHex(lodDebugColors[index]);
879
+ }
@@ -77,7 +77,7 @@ export class PromiseGroup {
77
77
  }
78
78
  if (this._maxPromisesPerObject >= 1) {
79
79
  if (this._seen.has(object)) {
80
- let count = this._seen.get(object);
80
+ const count = this._seen.get(object);
81
81
  if (count >= this._maxPromisesPerObject) {
82
82
  if (debug)
83
83
  console.warn(`PromiseGroup: Already awaiting object ignoring new promise for it.`);
@@ -108,7 +108,7 @@ function _patchModelViewer(modelviewer) {
108
108
  function renderFrames() {
109
109
  if (needsRender) {
110
110
  let forcedFrames = 0;
111
- let interval = setInterval(() => {
111
+ const interval = setInterval(() => {
112
112
  if (forcedFrames++ > 5) {
113
113
  clearInterval(interval);
114
114
  return;
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // replaced at build time
2
- export const version = "3.5.0-rc";
2
+ export const version = "3.6.0-alpha.2";
3
3
  globalThis["GLTF_PROGRESSIVE_VERSION"] = version;
4
4
  console.debug(`[gltf-progressive] version ${version || "-"}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-progressive",
3
- "version": "3.5.0-rc",
3
+ "version": "3.6.0-alpha.2",
4
4
  "description": "three.js support for loading glTF or GLB files that contain progressive loading data",
5
5
  "homepage": "https://needle.tools",
6
6
  "author": {
@@ -12,6 +12,7 @@
12
12
  "type": "git",
13
13
  "url": "git+https://github.com/needle-tools/gltf-progressive"
14
14
  },
15
+ "license": "MIT",
15
16
  "readme": "README.md",
16
17
  "keywords": [
17
18
  "three.js",
@@ -41,7 +42,8 @@
41
42
  "gltf-progressive.js",
42
43
  "gltf-progressive.min.js",
43
44
  "gltf-progressive.umd.cjs",
44
- "NEEDLE_progressive"
45
+ "NEEDLE_progressive",
46
+ "LICENSE"
45
47
  ],
46
48
  "watch": {
47
49
  "build:lib": {