@needle-tools/gltf-progressive 3.5.0-rc → 3.6.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 +9 -1
- package/LICENSE +21 -0
- package/gltf-progressive.js +851 -620
- package/gltf-progressive.min.js +9 -9
- package/gltf-progressive.umd.cjs +9 -9
- package/lib/extension.d.ts +61 -5
- package/lib/extension.js +268 -105
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/lods.debug.js +1 -1
- package/lib/lods.manager.d.ts +66 -4
- package/lib/lods.manager.js +133 -16
- package/lib/lods.promise.js +1 -1
- package/lib/plugins/modelviewer.js +1 -1
- package/lib/version.js +1 -1
- package/package.json +4 -2
package/lib/lods.manager.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Camera, Material, Object3D, Scene, Texture, Vector3, WebGLRenderer } from "three";
|
|
1
|
+
import { Camera, Color, Material, Object3D, Scene, Texture, Vector3, WebGLRenderer } from "three";
|
|
2
2
|
import { NEEDLE_progressive_plugin } from "./plugins/plugin.js";
|
|
3
3
|
import { PromiseGroupOptions } from "./lods.promise.js";
|
|
4
4
|
export type LODManagerContext = {
|
|
@@ -8,6 +8,7 @@ export declare type LOD_Results = {
|
|
|
8
8
|
mesh_lod: number;
|
|
9
9
|
texture_lod: number;
|
|
10
10
|
};
|
|
11
|
+
export declare const lodDebugColors: number[];
|
|
11
12
|
declare type LODChangedEventListener = (args: {
|
|
12
13
|
type: "mesh" | "texture";
|
|
13
14
|
level: number;
|
|
@@ -98,7 +99,31 @@ export declare class LODsManager {
|
|
|
98
99
|
private readonly _newPromiseGroups;
|
|
99
100
|
private _promiseGroupIds;
|
|
100
101
|
/**
|
|
101
|
-
*
|
|
102
|
+
* Returns a promise that resolves once all LOD requests initiated during the next render cycles have finished loading.
|
|
103
|
+
* This is useful for hiding low-resolution placeholders (e.g. with a loading overlay or CSS blur) until high-quality assets are ready.
|
|
104
|
+
*
|
|
105
|
+
* By default, the returned promise captures LOD loading requests for 2 frames and resolves when all of them complete.
|
|
106
|
+
* Use `waitForFirstCapture` if no LOD requests may happen immediately (e.g. after a scene switch).
|
|
107
|
+
*
|
|
108
|
+
* @param opts - Optional configuration for how long to capture and what to wait for. See {@link PromiseGroupOptions}.
|
|
109
|
+
* @returns A promise that resolves with `{ cancelled, awaited_count, resolved_count }` once all captured LOD loads complete (or the signal aborts).
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* // Wait for initial LODs to finish loading, then remove a blur overlay
|
|
114
|
+
* const result = await lodsManager.awaitLoading({
|
|
115
|
+
* frames: 5,
|
|
116
|
+
* signal: AbortSignal.timeout(10_000),
|
|
117
|
+
* });
|
|
118
|
+
* console.log(`Loaded ${result.resolved_count} of ${result.awaited_count} LODs`);
|
|
119
|
+
* document.querySelector('.blur-overlay')?.remove();
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* // Wait until at least one LOD starts loading before resolving
|
|
125
|
+
* await lodsManager.awaitLoading({ waitForFirstCapture: true });
|
|
126
|
+
* ```
|
|
102
127
|
*/
|
|
103
128
|
awaitLoading(opts?: PromiseGroupOptions): Promise<{
|
|
104
129
|
cancelled: boolean;
|
|
@@ -107,8 +132,29 @@ export declare class LODsManager {
|
|
|
107
132
|
}>;
|
|
108
133
|
private _postprocessPromiseGroups;
|
|
109
134
|
private readonly _lodchangedlisteners;
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Register a listener that is called whenever a mesh or texture LOD level has finished loading and has been applied.
|
|
137
|
+
* The listener receives the type of asset (`"mesh"` or `"texture"`), the new LOD level, and the affected object.
|
|
138
|
+
*
|
|
139
|
+
* @param evt - The event type. Currently only `"changed"` is supported.
|
|
140
|
+
* @param listener - Callback invoked after a LOD swap completes.
|
|
141
|
+
* @return A function to unregister the listener.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* lodsManager.addEventListener("changed", ({ type, level, object }) => {
|
|
146
|
+
* console.log(`${type} LOD changed to level ${level}`, object);
|
|
147
|
+
* });
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
addEventListener(evt: "changed", listener: LODChangedEventListener): () => void;
|
|
151
|
+
/**
|
|
152
|
+
* Remove a previously registered `"changed"` event listener.
|
|
153
|
+
* @param evt - The event type (`"changed"`).
|
|
154
|
+
* @param listener - The listener to remove.
|
|
155
|
+
* @return `true` if the listener was found and removed, `false` otherwise.
|
|
156
|
+
*/
|
|
157
|
+
removeEventListener(evt: "changed", listener: LODChangedEventListener): boolean;
|
|
112
158
|
private constructor();
|
|
113
159
|
private _fpsBuffer;
|
|
114
160
|
/**
|
|
@@ -116,6 +162,21 @@ export declare class LODsManager {
|
|
|
116
162
|
*/
|
|
117
163
|
enable(): void;
|
|
118
164
|
disable(): void;
|
|
165
|
+
/**
|
|
166
|
+
* Manually trigger a LOD update for a scene and camera.
|
|
167
|
+
* Only needed when {@link manual} is set to `true` — otherwise LOD updates happen automatically on each render call.
|
|
168
|
+
*
|
|
169
|
+
* @param scene - The scene containing objects with progressive LODs.
|
|
170
|
+
* @param camera - The camera used to determine screen coverage and LOD levels.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* const lodsManager = LODsManager.get(renderer);
|
|
175
|
+
* lodsManager.manual = true;
|
|
176
|
+
* // ... later, trigger an update at a specific point:
|
|
177
|
+
* lodsManager.update(scene, camera);
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
119
180
|
update(scene: Scene, camera: Camera): void;
|
|
120
181
|
private onAfterRender;
|
|
121
182
|
/**
|
|
@@ -162,4 +223,5 @@ declare class LOD_state {
|
|
|
162
223
|
readonly lastScreenspaceVolume: Vector3;
|
|
163
224
|
lastCentrality: number;
|
|
164
225
|
}
|
|
226
|
+
export declare function getLODColor(level: number, target: Color): Color;
|
|
165
227
|
export {};
|
package/lib/lods.manager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box3, Clock, Matrix4, Mesh,
|
|
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,47 @@ 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
|
+
];
|
|
15
51
|
/**
|
|
16
52
|
* 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
53
|
* It must be enabled by calling the `enable` method.
|
|
@@ -116,7 +152,31 @@ export class LODsManager {
|
|
|
116
152
|
_newPromiseGroups = [];
|
|
117
153
|
_promiseGroupIds = 0;
|
|
118
154
|
/**
|
|
119
|
-
*
|
|
155
|
+
* Returns a promise that resolves once all LOD requests initiated during the next render cycles have finished loading.
|
|
156
|
+
* This is useful for hiding low-resolution placeholders (e.g. with a loading overlay or CSS blur) until high-quality assets are ready.
|
|
157
|
+
*
|
|
158
|
+
* By default, the returned promise captures LOD loading requests for 2 frames and resolves when all of them complete.
|
|
159
|
+
* Use `waitForFirstCapture` if no LOD requests may happen immediately (e.g. after a scene switch).
|
|
160
|
+
*
|
|
161
|
+
* @param opts - Optional configuration for how long to capture and what to wait for. See {@link PromiseGroupOptions}.
|
|
162
|
+
* @returns A promise that resolves with `{ cancelled, awaited_count, resolved_count }` once all captured LOD loads complete (or the signal aborts).
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* // Wait for initial LODs to finish loading, then remove a blur overlay
|
|
167
|
+
* const result = await lodsManager.awaitLoading({
|
|
168
|
+
* frames: 5,
|
|
169
|
+
* signal: AbortSignal.timeout(10_000),
|
|
170
|
+
* });
|
|
171
|
+
* console.log(`Loaded ${result.resolved_count} of ${result.awaited_count} LODs`);
|
|
172
|
+
* document.querySelector('.blur-overlay')?.remove();
|
|
173
|
+
* ```
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* // Wait until at least one LOD starts loading before resolving
|
|
178
|
+
* await lodsManager.awaitLoading({ waitForFirstCapture: true });
|
|
179
|
+
* ```
|
|
120
180
|
*/
|
|
121
181
|
awaitLoading(opts) {
|
|
122
182
|
const id = this._promiseGroupIds++;
|
|
@@ -145,17 +205,46 @@ export class LODsManager {
|
|
|
145
205
|
}
|
|
146
206
|
}
|
|
147
207
|
_lodchangedlisteners = [];
|
|
208
|
+
/**
|
|
209
|
+
* Register a listener that is called whenever a mesh or texture LOD level has finished loading and has been applied.
|
|
210
|
+
* The listener receives the type of asset (`"mesh"` or `"texture"`), the new LOD level, and the affected object.
|
|
211
|
+
*
|
|
212
|
+
* @param evt - The event type. Currently only `"changed"` is supported.
|
|
213
|
+
* @param listener - Callback invoked after a LOD swap completes.
|
|
214
|
+
* @return A function to unregister the listener.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* lodsManager.addEventListener("changed", ({ type, level, object }) => {
|
|
219
|
+
* console.log(`${type} LOD changed to level ${level}`, object);
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
148
223
|
addEventListener(evt, listener) {
|
|
149
224
|
if (evt === "changed") {
|
|
150
225
|
this._lodchangedlisteners.push(listener);
|
|
226
|
+
return () => {
|
|
227
|
+
this.removeEventListener(evt, listener);
|
|
228
|
+
};
|
|
151
229
|
}
|
|
230
|
+
return () => { };
|
|
152
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Remove a previously registered `"changed"` event listener.
|
|
234
|
+
* @param evt - The event type (`"changed"`).
|
|
235
|
+
* @param listener - The listener to remove.
|
|
236
|
+
* @return `true` if the listener was found and removed, `false` otherwise.
|
|
237
|
+
*/
|
|
153
238
|
removeEventListener(evt, listener) {
|
|
239
|
+
let removed = false;
|
|
154
240
|
if (evt === "changed") {
|
|
155
241
|
const index = this._lodchangedlisteners.indexOf(listener);
|
|
156
|
-
if (index >= 0)
|
|
242
|
+
if (index >= 0) {
|
|
157
243
|
this._lodchangedlisteners.splice(index, 1);
|
|
244
|
+
removed = true;
|
|
245
|
+
}
|
|
158
246
|
}
|
|
247
|
+
return removed;
|
|
159
248
|
}
|
|
160
249
|
// readonly plugins: NEEDLE_progressive_plugin[] = [];
|
|
161
250
|
constructor(renderer, context) {
|
|
@@ -217,6 +306,21 @@ export class LODsManager {
|
|
|
217
306
|
this.renderer.render = this.#originalRender;
|
|
218
307
|
this.#originalRender = undefined;
|
|
219
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Manually trigger a LOD update for a scene and camera.
|
|
311
|
+
* Only needed when {@link manual} is set to `true` — otherwise LOD updates happen automatically on each render call.
|
|
312
|
+
*
|
|
313
|
+
* @param scene - The scene containing objects with progressive LODs.
|
|
314
|
+
* @param camera - The camera used to determine screen coverage and LOD levels.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* const lodsManager = LODsManager.get(renderer);
|
|
319
|
+
* lodsManager.manual = true;
|
|
320
|
+
* // ... later, trigger an update at a specific point:
|
|
321
|
+
* lodsManager.update(scene, camera);
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
220
324
|
update(scene, camera) {
|
|
221
325
|
this.internalUpdate(scene, camera);
|
|
222
326
|
}
|
|
@@ -306,16 +410,6 @@ export class LODsManager {
|
|
|
306
410
|
case "MeshDepthMaterial":
|
|
307
411
|
continue;
|
|
308
412
|
}
|
|
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
413
|
const object = entry.object;
|
|
320
414
|
if (object instanceof Mesh || (object.isMesh)) {
|
|
321
415
|
this.updateLODs(scene, camera, object, desiredDensity);
|
|
@@ -374,6 +468,9 @@ export class LODsManager {
|
|
|
374
468
|
if (debug && object.material && !object["isGizmo"]) {
|
|
375
469
|
applyDebugSettings(object.material);
|
|
376
470
|
}
|
|
471
|
+
if (debugProgressiveLODColors && object.material && !object["isGizmo"] && !object["isBatchedMesh"]) {
|
|
472
|
+
applyLODColor(object.material, levels.mesh_lod);
|
|
473
|
+
}
|
|
377
474
|
for (const plugin of plugins) {
|
|
378
475
|
plugin.onAfterUpdatedLOD?.(this.renderer, scene, camera, object, levels);
|
|
379
476
|
}
|
|
@@ -390,7 +487,7 @@ export class LODsManager {
|
|
|
390
487
|
return;
|
|
391
488
|
if (Array.isArray(material)) {
|
|
392
489
|
for (const mat of material) {
|
|
393
|
-
this.loadProgressiveTextures(mat, level);
|
|
490
|
+
this.loadProgressiveTextures(mat, level, overrideLodLevel);
|
|
394
491
|
}
|
|
395
492
|
return;
|
|
396
493
|
}
|
|
@@ -403,13 +500,15 @@ export class LODsManager {
|
|
|
403
500
|
else if (level < material[$currentLOD]) {
|
|
404
501
|
update = true;
|
|
405
502
|
}
|
|
406
|
-
|
|
503
|
+
const forceExactTextureLOD = overrideLodLevel !== undefined && overrideLodLevel >= 0;
|
|
504
|
+
if (forceExactTextureLOD) {
|
|
407
505
|
update = material[$currentLOD] != overrideLodLevel;
|
|
408
506
|
level = overrideLodLevel;
|
|
409
507
|
}
|
|
410
508
|
if (update) {
|
|
411
509
|
material[$currentLOD] = level;
|
|
412
|
-
const
|
|
510
|
+
const options = forceExactTextureLOD ? { force: true } : undefined;
|
|
511
|
+
const promise = NEEDLE_progressive.assignTextureLOD(material, level, options).then(_ => {
|
|
413
512
|
this._lodchangedlisteners.forEach(l => l({ type: "texture", level, object: material }));
|
|
414
513
|
});
|
|
415
514
|
PromiseGroup.addPromise("texture", material, promise, this._newPromiseGroups);
|
|
@@ -745,3 +844,21 @@ class LOD_state {
|
|
|
745
844
|
lastScreenspaceVolume = new Vector3();
|
|
746
845
|
lastCentrality = 0;
|
|
747
846
|
}
|
|
847
|
+
function applyLODColor(material, level) {
|
|
848
|
+
if (level < 0)
|
|
849
|
+
return;
|
|
850
|
+
if (Array.isArray(material)) {
|
|
851
|
+
for (const mat of material) {
|
|
852
|
+
applyLODColor(mat, level);
|
|
853
|
+
}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if ("color" in material && material.color instanceof Color) {
|
|
857
|
+
material.color.copy(getLODColor(level, debugLODColor));
|
|
858
|
+
material.needsUpdate = true;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
export function getLODColor(level, target) {
|
|
862
|
+
const index = Math.max(0, Math.min(lodDebugColors.length - 1, Math.floor(level)));
|
|
863
|
+
return target.setHex(lodDebugColors[index]);
|
|
864
|
+
}
|
package/lib/lods.promise.js
CHANGED
|
@@ -77,7 +77,7 @@ export class PromiseGroup {
|
|
|
77
77
|
}
|
|
78
78
|
if (this._maxPromisesPerObject >= 1) {
|
|
79
79
|
if (this._seen.has(object)) {
|
|
80
|
-
|
|
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
|
-
|
|
111
|
+
const interval = setInterval(() => {
|
|
112
112
|
if (forcedFrames++ > 5) {
|
|
113
113
|
clearInterval(interval);
|
|
114
114
|
return;
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-progressive",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0-alpha.1",
|
|
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": {
|