@needle-tools/gltf-progressive 3.6.0-canary.5401de9 → 3.6.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 +10 -0
- package/README.md +16 -14
- package/examples/modelviewer-multiple.html +4 -4
- package/examples/modelviewer.html +4 -4
- package/examples/offscreen/main.js +39 -7
- package/examples/react-three-fiber/package-lock.json +482 -484
- package/examples/react-three-fiber/package.json +4 -4
- package/examples/react-three-fiber/src/App.tsx +28 -14
- package/examples/shared/example-utils.js +75 -3
- package/examples/shared/example.css +17 -0
- package/examples/shared/runtime.js +2 -1
- package/examples/threejs/index.html +4 -4
- package/examples/webgpu/main.js +42 -7
- package/examples/worker-rendering/main.js +19 -1
- package/examples/worker-rendering/worker.js +108 -19
- package/gltf-progressive.js +508 -494
- package/gltf-progressive.min.js +9 -9
- package/gltf-progressive.umd.cjs +9 -9
- package/lib/extension.d.ts +2 -0
- package/lib/extension.js +29 -1
- package/lib/loaders.d.ts +8 -1
- package/lib/lods.manager.js +16 -12
- package/lib/utils.d.ts +1 -1
- package/lib/utils.internal.js +8 -2
- package/lib/version.js +1 -1
- package/package.json +3 -3
package/lib/extension.js
CHANGED
|
@@ -796,6 +796,8 @@ export class NEEDLE_progressive {
|
|
|
796
796
|
}
|
|
797
797
|
}
|
|
798
798
|
else {
|
|
799
|
+
// Requests already waiting for a queue slot must not populate a new cache lifetime.
|
|
800
|
+
this.cacheGeneration++;
|
|
799
801
|
this.lodInfos.clear();
|
|
800
802
|
for (const [, entryRef] of this.lowresCache) {
|
|
801
803
|
const entry = entryRef.deref();
|
|
@@ -840,6 +842,7 @@ export class NEEDLE_progressive {
|
|
|
840
842
|
else {
|
|
841
843
|
// Promise — may be in-flight or already resolved.
|
|
842
844
|
// Attach disposal to run after resolution.
|
|
845
|
+
this.disposedRequests.add(entry);
|
|
843
846
|
entry.then(resource => {
|
|
844
847
|
if (resource) {
|
|
845
848
|
if (Array.isArray(resource)) {
|
|
@@ -938,8 +941,11 @@ export class NEEDLE_progressive {
|
|
|
938
941
|
}
|
|
939
942
|
}
|
|
940
943
|
static workers = [];
|
|
944
|
+
static cacheGeneration = 0;
|
|
945
|
+
static disposedRequests = new WeakSet();
|
|
941
946
|
static _workersIndex = 0;
|
|
942
947
|
static async getOrLoadLOD(current, level, options) {
|
|
948
|
+
const generation = this.cacheGeneration;
|
|
943
949
|
const debugverbose = debug == "verbose";
|
|
944
950
|
/** this key is used to lookup the LOD information */
|
|
945
951
|
const LOD = this.getAssignedLODInformation(current);
|
|
@@ -1010,6 +1016,8 @@ export class NEEDLE_progressive {
|
|
|
1010
1016
|
const KEY = lod_url + "_" + lodInfo.guid;
|
|
1011
1017
|
// check if the requested file is currently being loaded or was previously loaded
|
|
1012
1018
|
const cached = await this.tryResolveLODCacheEntry(this.cache.get(KEY), KEY, lod_url, current, level, debugverbose);
|
|
1019
|
+
if (generation !== this.cacheGeneration)
|
|
1020
|
+
return null;
|
|
1013
1021
|
if (cached.found)
|
|
1014
1022
|
return cached.value;
|
|
1015
1023
|
if (options?.isCurrent?.() === false) {
|
|
@@ -1018,6 +1026,8 @@ export class NEEDLE_progressive {
|
|
|
1018
1026
|
return null;
|
|
1019
1027
|
}
|
|
1020
1028
|
const slot = await this.queue.slot(lod_url);
|
|
1029
|
+
if (generation !== this.cacheGeneration)
|
|
1030
|
+
return null;
|
|
1021
1031
|
if (options?.isCurrent?.() === false) {
|
|
1022
1032
|
if (debugverbose)
|
|
1023
1033
|
console.log(`Skipping stale LOD ${level} request after queue: ${lod_url}`);
|
|
@@ -1026,8 +1036,20 @@ export class NEEDLE_progressive {
|
|
|
1026
1036
|
// Another request can fill the cache while this one waits for a queue slot.
|
|
1027
1037
|
// Re-checking here avoids duplicate loads for heavily instanced assets.
|
|
1028
1038
|
const cachedAfterQueue = await this.tryResolveLODCacheEntry(this.cache.get(KEY), KEY, lod_url, current, level, debugverbose);
|
|
1039
|
+
if (generation !== this.cacheGeneration)
|
|
1040
|
+
return null;
|
|
1029
1041
|
if (cachedAfterQueue.found)
|
|
1030
1042
|
return cachedAfterQueue.value;
|
|
1043
|
+
// Even a cache miss above yields a microtask. Another slot holder can
|
|
1044
|
+
// start this same request before that continuation resumes.
|
|
1045
|
+
const startedMeanwhile = this.cache.get(KEY);
|
|
1046
|
+
if (startedMeanwhile !== undefined) {
|
|
1047
|
+
const cached = await this.tryResolveLODCacheEntry(startedMeanwhile, KEY, lod_url, current, level, debugverbose);
|
|
1048
|
+
if (generation !== this.cacheGeneration)
|
|
1049
|
+
return null;
|
|
1050
|
+
if (cached.found)
|
|
1051
|
+
return cached.value;
|
|
1052
|
+
}
|
|
1031
1053
|
// #region loading
|
|
1032
1054
|
if (!slot.use) {
|
|
1033
1055
|
if (debug)
|
|
@@ -1175,6 +1197,10 @@ export class NEEDLE_progressive {
|
|
|
1175
1197
|
this.cache.set(KEY, request);
|
|
1176
1198
|
slot.use(request);
|
|
1177
1199
|
const res = await request;
|
|
1200
|
+
// dispose() owns cleanup of removed pending entries. Do not return their
|
|
1201
|
+
// disposed resources to callers or overwrite a newer request for this key.
|
|
1202
|
+
if (generation !== this.cacheGeneration || this.cache.get(KEY) !== request)
|
|
1203
|
+
return null;
|
|
1178
1204
|
// Optimize cache entry: replace loading promise with lightweight reference.
|
|
1179
1205
|
// This releases closure variables captured during the loading function.
|
|
1180
1206
|
if (res != null) {
|
|
@@ -1212,7 +1238,7 @@ export class NEEDLE_progressive {
|
|
|
1212
1238
|
console.log("Load texture from uri: " + lod_url);
|
|
1213
1239
|
const loader = new TextureLoader();
|
|
1214
1240
|
const tex = await loader.loadAsync(lod_url);
|
|
1215
|
-
if (options?.isCurrent?.() === false) {
|
|
1241
|
+
if (generation !== this.cacheGeneration || options?.isCurrent?.() === false) {
|
|
1216
1242
|
tex?.dispose();
|
|
1217
1243
|
return null;
|
|
1218
1244
|
}
|
|
@@ -1269,6 +1295,8 @@ export class NEEDLE_progressive {
|
|
|
1269
1295
|
console.error(`Error loading LOD ${level} from ${lodUrl}\n`, err);
|
|
1270
1296
|
return null;
|
|
1271
1297
|
});
|
|
1298
|
+
if (this.disposedRequests.has(existing))
|
|
1299
|
+
return { found: true, value: null };
|
|
1272
1300
|
let resourceIsDisposed = false;
|
|
1273
1301
|
if (res == null) {
|
|
1274
1302
|
// Failed loads stay cached as null so we don't retry the same missing resource forever.
|
package/lib/loaders.d.ts
CHANGED
|
@@ -28,7 +28,14 @@ export declare function setKTX2TranscoderLocation(location: string): void;
|
|
|
28
28
|
export declare function createLoaders(renderer: WebGLRenderer | null): {
|
|
29
29
|
dracoLoader: DRACOLoader;
|
|
30
30
|
ktx2Loader: KTX2Loader;
|
|
31
|
-
meshoptDecoder:
|
|
31
|
+
meshoptDecoder: {
|
|
32
|
+
supported: boolean;
|
|
33
|
+
ready: Promise<void>;
|
|
34
|
+
decodeVertexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, filter?: string) => void;
|
|
35
|
+
decodeIndexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void;
|
|
36
|
+
decodeIndexSequence: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void;
|
|
37
|
+
decodeGltfBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, mode: string, filter?: string) => void;
|
|
38
|
+
};
|
|
32
39
|
};
|
|
33
40
|
export declare function addDracoAndKTX2Loaders(loader: GLTFLoader): void;
|
|
34
41
|
/**
|
package/lib/lods.manager.js
CHANGED
|
@@ -88,8 +88,10 @@ export function calculateMeshLODLevel(options) {
|
|
|
88
88
|
result.screenCoverage = 0;
|
|
89
89
|
result.screenspaceVolume.set(0, 0, 0);
|
|
90
90
|
result.centrality = 1;
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
// Note: we intentionally do NOT early-return when there are no mesh LODs.
|
|
92
|
+
// The screen coverage / screenspace volume computed below is also consumed by
|
|
93
|
+
// the texture LOD selection, which must keep working for meshes that only have
|
|
94
|
+
// texture LODs (no mesh LODs). Only the mesh LOD-level selection loop is skipped.
|
|
93
95
|
let boundingBox = options.boundingBox ?? geometry.boundingBox;
|
|
94
96
|
if (!boundingBox) {
|
|
95
97
|
geometry.computeBoundingBox();
|
|
@@ -165,16 +167,18 @@ export function calculateMeshLODLevel(options) {
|
|
|
165
167
|
debugDrawLine(_meshLODCorner1, _meshLODCorner3, 0x0000ff);
|
|
166
168
|
debugDrawLine(_meshLODCorner2, _meshLODCorner3, 0x0000ff);
|
|
167
169
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
170
|
+
if (meshLods?.length) {
|
|
171
|
+
for (let i = 0; i < meshLods.length; i++) {
|
|
172
|
+
const lod = meshLods[i];
|
|
173
|
+
const density = lod.densities?.[primitiveIndex] || lod.density || .00001;
|
|
174
|
+
if (primitiveIndex > 0 && warnMissingPrimitiveDensities && isDevelopmentServer() && !lod.densities && !globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]) {
|
|
175
|
+
globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] = true;
|
|
176
|
+
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.`);
|
|
177
|
+
}
|
|
178
|
+
if (density / screenCoverage < desiredDensity) {
|
|
179
|
+
result.level = i;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
return result;
|
package/lib/utils.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const isSSR: boolean;
|
|
|
5
5
|
* @param obj the object to get the raycast mesh from
|
|
6
6
|
* @returns the raycast mesh or null if not set
|
|
7
7
|
*/
|
|
8
|
-
export declare function getRaycastMesh(obj: Object3D): BufferGeometry<any
|
|
8
|
+
export declare function getRaycastMesh(obj: Object3D): BufferGeometry<any> | null;
|
|
9
9
|
/**
|
|
10
10
|
* Set the raycast mesh for an object.
|
|
11
11
|
* The raycast mesh is a low poly version of the mesh used for raycasting. It is set when a mesh that has LOD level with more vertices is discovered for the first time
|
package/lib/utils.internal.js
CHANGED
|
@@ -106,8 +106,14 @@ export class PromiseQueue {
|
|
|
106
106
|
constructor(maxConcurrent, opts = {}) {
|
|
107
107
|
this.maxConcurrent = maxConcurrent;
|
|
108
108
|
this.debug = opts.debug ?? false;
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
// Dedicated workers can have requestAnimationFrame when they are owned by a window.
|
|
110
|
+
// Other worker-like scopes do not, so keep the frame-based tick when available and fall back to timers otherwise.
|
|
111
|
+
if (typeof globalThis.requestAnimationFrame === "function") {
|
|
112
|
+
globalThis.requestAnimationFrame(this.tick);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
setTimeout(this.tick, 0);
|
|
116
|
+
}
|
|
111
117
|
}
|
|
112
118
|
tick = () => {
|
|
113
119
|
this.internalUpdate();
|
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.6.
|
|
3
|
+
"version": "3.6.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": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@needle-tools/engine": "^5.1.0-alpha.6",
|
|
63
63
|
"@needle-tools/three-test-matrix": "^0.1.0",
|
|
64
64
|
"@stylistic/eslint-plugin-ts": "^1.5.4",
|
|
65
|
-
"@types/three": "0.
|
|
65
|
+
"@types/three": "0.169.0",
|
|
66
66
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
|
67
67
|
"@typescript-eslint/parser": "^6.2.0",
|
|
68
68
|
"@vitest/browser-playwright": "^4.1.8",
|
|
@@ -80,4 +80,4 @@
|
|
|
80
80
|
"vitest": "^4.1.8"
|
|
81
81
|
},
|
|
82
82
|
"types": "./lib/index.d.ts"
|
|
83
|
-
}
|
|
83
|
+
}
|