@needle-tools/gltf-progressive 3.6.0-alpha.2 → 3.6.0-canary.027a4c9
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 +3 -0
- package/gltf-progressive.js +643 -539
- package/gltf-progressive.min.js +9 -9
- package/gltf-progressive.umd.cjs +9 -9
- package/lib/extension.d.ts +6 -0
- package/lib/extension.js +85 -16
- package/lib/loaders.d.ts +1 -8
- package/lib/loaders.js +15 -2
- package/lib/lods.manager.d.ts +1 -0
- package/lib/lods.manager.js +40 -5
- package/lib/utils.d.ts +1 -1
- package/lib/utils.internal.d.ts +27 -0
- package/lib/utils.internal.js +50 -19
- package/lib/version.js +1 -1
- package/lib/worker/loader.mainthread.js +6 -4
- package/package.json +9 -4
package/lib/extension.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 { determineTextureMemoryInBytes, getParam, isMobileDevice, PromiseQueue, resolveUrl } from "./utils.internal.js";
|
|
4
|
+
import { determineTextureMemoryInBytes, getParam, getSourceData, getTextureDimensions, hasPixelData, isMobileDevice, PromiseQueue, 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";
|
|
@@ -223,13 +223,19 @@ export class NEEDLE_progressive {
|
|
|
223
223
|
}
|
|
224
224
|
// const info = this.onProgressiveLoadStart(context, source, mesh, null);
|
|
225
225
|
mesh["LOD:requested level"] = level;
|
|
226
|
-
|
|
226
|
+
const shouldLoad = () => mesh["LOD:requested level"] === level || this.shouldApplyStaleMeshLOD(mesh, level);
|
|
227
|
+
return NEEDLE_progressive.getOrLoadLOD(currentGeometry, level, {
|
|
228
|
+
isCurrent: shouldLoad,
|
|
229
|
+
}).then(geo => {
|
|
227
230
|
if (Array.isArray(geo)) {
|
|
228
231
|
const index = lodinfo.index || 0;
|
|
229
232
|
geo = geo[index];
|
|
230
233
|
}
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
const isLatestRequest = mesh["LOD:requested level"] === level;
|
|
235
|
+
if (isLatestRequest || this.shouldApplyStaleMeshLOD(mesh, level)) {
|
|
236
|
+
if (isLatestRequest) {
|
|
237
|
+
delete mesh["LOD:requested level"];
|
|
238
|
+
}
|
|
233
239
|
if (geo && currentGeometry != geo) {
|
|
234
240
|
const isGeometry = geo?.isBufferGeometry;
|
|
235
241
|
// if (debug == "verbose") console.log("Progressive Mesh " + mesh.name + " loaded", currentGeometry, "→", geo, "\n", mesh)
|
|
@@ -373,7 +379,14 @@ export class NEEDLE_progressive {
|
|
|
373
379
|
return pending.promise;
|
|
374
380
|
}
|
|
375
381
|
}
|
|
376
|
-
const
|
|
382
|
+
const requestId = material && slot ? this.nextTextureSlotRequestId(material, slot, level, force) : 0;
|
|
383
|
+
const isCurrentRequest = () => !material || !slot || this.getLatestTextureSlotRequest(material, slot)?.id === requestId;
|
|
384
|
+
const shouldLoad = () => isCurrentRequest() || this.shouldApplyStaleTextureSlotLOD(material, slot, level, force);
|
|
385
|
+
const promise = NEEDLE_progressive.getOrLoadLOD(current, level, {
|
|
386
|
+
isCurrent: shouldLoad,
|
|
387
|
+
}).then(tex => {
|
|
388
|
+
if (!isCurrentRequest() && !this.shouldApplyStaleTextureSlotLOD(material, slot, level, force))
|
|
389
|
+
return null;
|
|
377
390
|
// this can currently not happen
|
|
378
391
|
if (Array.isArray(tex)) {
|
|
379
392
|
console.warn("Progressive: Got an array of textures for a texture slot, this should not happen...");
|
|
@@ -415,7 +428,7 @@ export class NEEDLE_progressive {
|
|
|
415
428
|
return null;
|
|
416
429
|
});
|
|
417
430
|
if (material && slot) {
|
|
418
|
-
this.setPendingTextureSlotRequest(material, slot, level, force, promise);
|
|
431
|
+
this.setPendingTextureSlotRequest(material, slot, level, force, requestId, promise);
|
|
419
432
|
}
|
|
420
433
|
return promise;
|
|
421
434
|
}
|
|
@@ -423,6 +436,8 @@ export class NEEDLE_progressive {
|
|
|
423
436
|
// referenced by many slots and should only be disposed after every slot moved away.
|
|
424
437
|
static trackedTextureSlots = new WeakMap();
|
|
425
438
|
static pendingTextureSlotRequests = new WeakMap();
|
|
439
|
+
static latestTextureSlotRequests = new WeakMap();
|
|
440
|
+
static textureSlotRequestId = 0;
|
|
426
441
|
static trackCurrentMaterialTextureSlots(material) {
|
|
427
442
|
if (material.uniforms && (material.isRawShaderMaterial || material.isShaderMaterial === true)) {
|
|
428
443
|
const shaderMaterial = material;
|
|
@@ -444,17 +459,52 @@ export class NEEDLE_progressive {
|
|
|
444
459
|
static getPendingTextureSlotRequest(material, slot) {
|
|
445
460
|
return this.pendingTextureSlotRequests.get(material)?.get(slot);
|
|
446
461
|
}
|
|
447
|
-
static
|
|
462
|
+
static nextTextureSlotRequestId(material, slot, level, force) {
|
|
463
|
+
let slots = this.latestTextureSlotRequests.get(material);
|
|
464
|
+
if (!slots) {
|
|
465
|
+
slots = new Map();
|
|
466
|
+
this.latestTextureSlotRequests.set(material, slots);
|
|
467
|
+
}
|
|
468
|
+
const id = ++this.textureSlotRequestId;
|
|
469
|
+
slots.set(slot, { id, level, force });
|
|
470
|
+
return id;
|
|
471
|
+
}
|
|
472
|
+
static getLatestTextureSlotRequest(material, slot) {
|
|
473
|
+
return this.latestTextureSlotRequests.get(material)?.get(slot);
|
|
474
|
+
}
|
|
475
|
+
static shouldApplyStaleTextureSlotLOD(material, slot, level, force) {
|
|
476
|
+
if (!material || !slot)
|
|
477
|
+
return false;
|
|
478
|
+
const latest = this.getLatestTextureSlotRequest(material, slot);
|
|
479
|
+
const assigned = this.getMaterialTextureSlot(material, slot);
|
|
480
|
+
const assignedLODLevel = this.getAssignedLODInformation(assigned)?.level ?? Infinity;
|
|
481
|
+
if (level >= assignedLODLevel)
|
|
482
|
+
return false;
|
|
483
|
+
if (force) {
|
|
484
|
+
if (!latest)
|
|
485
|
+
return false;
|
|
486
|
+
return level >= latest.level;
|
|
487
|
+
}
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
static shouldApplyStaleMeshLOD(mesh, level) {
|
|
491
|
+
const latestLevel = mesh["LOD:requested level"];
|
|
492
|
+
if (typeof latestLevel !== "number")
|
|
493
|
+
return false;
|
|
494
|
+
const assignedLODLevel = this.getAssignedLODInformation(mesh.geometry)?.level ?? Infinity;
|
|
495
|
+
return level < assignedLODLevel && level >= latestLevel;
|
|
496
|
+
}
|
|
497
|
+
static setPendingTextureSlotRequest(material, slot, level, force, id, promise) {
|
|
448
498
|
let slots = this.pendingTextureSlotRequests.get(material);
|
|
449
499
|
if (!slots) {
|
|
450
500
|
slots = new Map();
|
|
451
501
|
this.pendingTextureSlotRequests.set(material, slots);
|
|
452
502
|
}
|
|
453
|
-
const request = { level, force, promise };
|
|
503
|
+
const request = { level, force, id, promise };
|
|
454
504
|
slots.set(slot, request);
|
|
455
505
|
promise.finally(() => {
|
|
456
506
|
const current = slots.get(slot);
|
|
457
|
-
if (current ===
|
|
507
|
+
if (current?.id === id) {
|
|
458
508
|
slots.delete(slot);
|
|
459
509
|
}
|
|
460
510
|
});
|
|
@@ -656,8 +706,7 @@ export class NEEDLE_progressive {
|
|
|
656
706
|
return;
|
|
657
707
|
}
|
|
658
708
|
if (debug) {
|
|
659
|
-
const width
|
|
660
|
-
const height = tex.image?.height || tex.source?.data?.height || 0;
|
|
709
|
+
const { width, height } = getTextureDimensions(tex);
|
|
661
710
|
console.log(`> gltf-progressive: register texture[${index}] "${tex.name || tex.uuid}", Current: ${width}x${height}, Max: ${ext.lods[0]?.width}x${ext.lods[0]?.height}, uuid: ${tex.uuid}`, ext, tex);
|
|
662
711
|
}
|
|
663
712
|
// Put the extension info into the source (seems like tiled textures are cloned and the userdata etc is not properly copied BUT the source of course is not cloned)
|
|
@@ -771,6 +820,8 @@ export class NEEDLE_progressive {
|
|
|
771
820
|
this.textureRefCounts.clear();
|
|
772
821
|
this.trackedTextureSlots = new WeakMap();
|
|
773
822
|
this.pendingTextureSlotRequests = new WeakMap();
|
|
823
|
+
this.latestTextureSlotRequests = new WeakMap();
|
|
824
|
+
this.textureSlotRequestId = 0;
|
|
774
825
|
}
|
|
775
826
|
}
|
|
776
827
|
/** Dispose a single cache entry's three.js resource(s) to free GPU memory. */
|
|
@@ -877,8 +928,7 @@ export class NEEDLE_progressive {
|
|
|
877
928
|
return false;
|
|
878
929
|
}
|
|
879
930
|
function logDebugInfo(prefix, newCount) {
|
|
880
|
-
|
|
881
|
-
const height = texture.image?.height || texture.source?.data?.height || 0;
|
|
931
|
+
let { width, height } = getTextureDimensions(texture);
|
|
882
932
|
const textureSize = width && height ? `${width}x${height}` : "N/A";
|
|
883
933
|
let memorySize = "N/A";
|
|
884
934
|
if (width && height) {
|
|
@@ -889,7 +939,7 @@ export class NEEDLE_progressive {
|
|
|
889
939
|
}
|
|
890
940
|
static workers = [];
|
|
891
941
|
static _workersIndex = 0;
|
|
892
|
-
static async getOrLoadLOD(current, level) {
|
|
942
|
+
static async getOrLoadLOD(current, level, options) {
|
|
893
943
|
const debugverbose = debug == "verbose";
|
|
894
944
|
/** this key is used to lookup the LOD information */
|
|
895
945
|
const LOD = this.getAssignedLODInformation(current);
|
|
@@ -962,7 +1012,17 @@ export class NEEDLE_progressive {
|
|
|
962
1012
|
const cached = await this.tryResolveLODCacheEntry(this.cache.get(KEY), KEY, lod_url, current, level, debugverbose);
|
|
963
1013
|
if (cached.found)
|
|
964
1014
|
return cached.value;
|
|
1015
|
+
if (options?.isCurrent?.() === false) {
|
|
1016
|
+
if (debugverbose)
|
|
1017
|
+
console.log(`Skipping stale LOD ${level} request before queue: ${lod_url}`);
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
965
1020
|
const slot = await this.queue.slot(lod_url);
|
|
1021
|
+
if (options?.isCurrent?.() === false) {
|
|
1022
|
+
if (debugverbose)
|
|
1023
|
+
console.log(`Skipping stale LOD ${level} request after queue: ${lod_url}`);
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
966
1026
|
// Another request can fill the cache while this one waits for a queue slot.
|
|
967
1027
|
// Re-checking here avoids duplicate loads for heavily instanced assets.
|
|
968
1028
|
const cachedAfterQueue = await this.tryResolveLODCacheEntry(this.cache.get(KEY), KEY, lod_url, current, level, debugverbose);
|
|
@@ -1143,10 +1203,19 @@ export class NEEDLE_progressive {
|
|
|
1143
1203
|
}
|
|
1144
1204
|
else {
|
|
1145
1205
|
if (current instanceof Texture) {
|
|
1206
|
+
if (options?.isCurrent?.() === false) {
|
|
1207
|
+
if (debugverbose)
|
|
1208
|
+
console.log(`Skipping stale texture LOD ${level} request: ${lod_url}`);
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1146
1211
|
if (debugverbose)
|
|
1147
1212
|
console.log("Load texture from uri: " + lod_url);
|
|
1148
1213
|
const loader = new TextureLoader();
|
|
1149
1214
|
const tex = await loader.loadAsync(lod_url);
|
|
1215
|
+
if (options?.isCurrent?.() === false) {
|
|
1216
|
+
tex?.dispose();
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1150
1219
|
if (tex) {
|
|
1151
1220
|
tex.guid = lodInfo.guid;
|
|
1152
1221
|
tex.flipY = false;
|
|
@@ -1175,7 +1244,7 @@ export class NEEDLE_progressive {
|
|
|
1175
1244
|
let res = derefed;
|
|
1176
1245
|
let resourceIsDisposed = false;
|
|
1177
1246
|
if (res instanceof Texture && current instanceof Texture) {
|
|
1178
|
-
if (res.image
|
|
1247
|
+
if (hasPixelData(res.image) || getSourceData(res)) {
|
|
1179
1248
|
res = this.copySettings(current, res);
|
|
1180
1249
|
}
|
|
1181
1250
|
else {
|
|
@@ -1205,7 +1274,7 @@ export class NEEDLE_progressive {
|
|
|
1205
1274
|
// Failed loads stay cached as null so we don't retry the same missing resource forever.
|
|
1206
1275
|
}
|
|
1207
1276
|
else if (res instanceof Texture && current instanceof Texture) {
|
|
1208
|
-
if (res.image
|
|
1277
|
+
if (hasPixelData(res.image) || getSourceData(res)) {
|
|
1209
1278
|
res = this.copySettings(current, res);
|
|
1210
1279
|
}
|
|
1211
1280
|
else {
|
package/lib/loaders.d.ts
CHANGED
|
@@ -28,14 +28,7 @@ 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:
|
|
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
|
-
};
|
|
31
|
+
meshoptDecoder: any;
|
|
39
32
|
};
|
|
40
33
|
export declare function addDracoAndKTX2Loaders(loader: GLTFLoader): void;
|
|
41
34
|
/**
|
package/lib/loaders.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-ignore @types/three re-exports from meshoptimizer which may not resolve in all configs
|
|
1
2
|
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
|
|
2
3
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
3
4
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
@@ -142,8 +143,8 @@ const originalLoadFunction = GLTFLoader.prototype.load;
|
|
|
142
143
|
function onLoad(...args) {
|
|
143
144
|
const config = gltfLoaderConfigurations.get(this);
|
|
144
145
|
let url_str = args[0];
|
|
145
|
-
const url =
|
|
146
|
-
if (url
|
|
146
|
+
const url = tryResolveUrl(url_str);
|
|
147
|
+
if (url?.hostname.endsWith("needle.tools")) {
|
|
147
148
|
const progressive = config?.progressive !== undefined ? config.progressive : true;
|
|
148
149
|
const usecase = config?.usecase ? config.usecase : "default";
|
|
149
150
|
if (progressive) {
|
|
@@ -159,3 +160,15 @@ function onLoad(...args) {
|
|
|
159
160
|
return res;
|
|
160
161
|
}
|
|
161
162
|
GLTFLoader.prototype.load = onLoad;
|
|
163
|
+
function tryResolveUrl(url) {
|
|
164
|
+
try {
|
|
165
|
+
if (url instanceof URL) {
|
|
166
|
+
return url;
|
|
167
|
+
}
|
|
168
|
+
const base = globalThis.location?.href;
|
|
169
|
+
return base ? new URL(url, base) : new URL(url);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
package/lib/lods.manager.d.ts
CHANGED
|
@@ -207,6 +207,7 @@ export declare class LODsManager {
|
|
|
207
207
|
* Update LODs in a scene
|
|
208
208
|
*/
|
|
209
209
|
private internalUpdate;
|
|
210
|
+
private getRenderList;
|
|
210
211
|
/** Update the LOD levels for the renderer. */
|
|
211
212
|
private updateLODs;
|
|
212
213
|
/** Load progressive textures for the given material
|
package/lib/lods.manager.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { Box3, Color, Matrix4, Mesh, Sphere, Vector3 } from "three";
|
|
2
3
|
import { NEEDLE_progressive } from "./extension.js";
|
|
3
4
|
import { createLoaders } from "./loaders.js";
|
|
4
5
|
import { getParam, isDevelopmentServer, isMobileDevice } from "./utils.internal.js";
|
|
@@ -12,6 +13,7 @@ const suppressProgressiveLoading = getParam("noprogressive");
|
|
|
12
13
|
const $lodsManager = Symbol("Needle:LODSManager");
|
|
13
14
|
const $lodstate = Symbol("Needle:LODState");
|
|
14
15
|
const $currentLOD = Symbol("Needle:CurrentLOD");
|
|
16
|
+
const ThreeRuntime = THREE;
|
|
15
17
|
const levels = { mesh_lod: -1, texture_lod: -1 };
|
|
16
18
|
const debugLODColor = new Color();
|
|
17
19
|
export const lodDebugColors = [
|
|
@@ -48,6 +50,10 @@ export const lodDebugColors = [
|
|
|
48
50
|
0x4d908e,
|
|
49
51
|
0x555555,
|
|
50
52
|
];
|
|
53
|
+
function createLODTimer() {
|
|
54
|
+
const Timer = ThreeRuntime.Timer || ThreeRuntime.Clock;
|
|
55
|
+
return new Timer();
|
|
56
|
+
}
|
|
51
57
|
const _meshLODWorldBox = new Box3();
|
|
52
58
|
const _meshLODProjectedBox = new Box3();
|
|
53
59
|
const _meshLODCameraSpaceBox = new Box3();
|
|
@@ -390,11 +396,11 @@ export class LODsManager {
|
|
|
390
396
|
// })
|
|
391
397
|
}
|
|
392
398
|
#originalRender;
|
|
393
|
-
#clock = new Clock();
|
|
394
399
|
#frame = 0;
|
|
395
400
|
#delta = 0;
|
|
396
401
|
#time = 0;
|
|
397
402
|
#fps = 0;
|
|
403
|
+
#clock = createLODTimer();
|
|
398
404
|
_fpsBuffer = [60, 60, 60, 60, 60];
|
|
399
405
|
/**
|
|
400
406
|
* Enable the LODsManager. This will replace the render method of the renderer with a method that updates the LODs.
|
|
@@ -416,7 +422,8 @@ export class LODsManager {
|
|
|
416
422
|
if (renderTarget == null || ("isXRRenderTarget" in renderTarget && renderTarget.isXRRenderTarget)) {
|
|
417
423
|
stack = 0;
|
|
418
424
|
self.#frame += 1;
|
|
419
|
-
self.#
|
|
425
|
+
self.#clock.update?.();
|
|
426
|
+
self.#delta = Math.max(self.#clock.getDelta(), 1 / 1000);
|
|
420
427
|
self.#time += self.#delta;
|
|
421
428
|
self._fpsBuffer.shift();
|
|
422
429
|
self._fpsBuffer.push(1 / self.#delta);
|
|
@@ -457,7 +464,9 @@ export class LODsManager {
|
|
|
457
464
|
onAfterRender(scene, camera, _stack) {
|
|
458
465
|
if (this.pause)
|
|
459
466
|
return;
|
|
460
|
-
const renderList = this.
|
|
467
|
+
const renderList = this.getRenderList(scene, camera, _stack);
|
|
468
|
+
if (!renderList)
|
|
469
|
+
return;
|
|
461
470
|
const opaque = renderList.opaque;
|
|
462
471
|
let updateLODs = true;
|
|
463
472
|
// check if we're rendering a postprocessing pass
|
|
@@ -514,7 +523,9 @@ export class LODsManager {
|
|
|
514
523
|
* Update LODs in a scene
|
|
515
524
|
*/
|
|
516
525
|
internalUpdate(scene, camera) {
|
|
517
|
-
const renderList = this.
|
|
526
|
+
const renderList = this.getRenderList(scene, camera, 0);
|
|
527
|
+
if (!renderList)
|
|
528
|
+
return;
|
|
518
529
|
const opaque = renderList.opaque;
|
|
519
530
|
this.projectionScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
520
531
|
const desiredDensity = this.targetTriangleDensity;
|
|
@@ -560,6 +571,30 @@ export class LODsManager {
|
|
|
560
571
|
}
|
|
561
572
|
}
|
|
562
573
|
}
|
|
574
|
+
getRenderList(scene, camera, stack) {
|
|
575
|
+
const renderer = this.renderer;
|
|
576
|
+
let renderList = null;
|
|
577
|
+
if (renderer.isWebGPURenderer === true) {
|
|
578
|
+
const renderLists = renderer._renderLists;
|
|
579
|
+
if (!renderLists)
|
|
580
|
+
return null;
|
|
581
|
+
renderList = renderLists.get(scene, camera);
|
|
582
|
+
}
|
|
583
|
+
else if (renderer.isWebGLRenderer === true) {
|
|
584
|
+
const renderLists = renderer.renderLists;
|
|
585
|
+
if (!renderLists)
|
|
586
|
+
return null;
|
|
587
|
+
renderList = renderLists.get(scene, stack);
|
|
588
|
+
}
|
|
589
|
+
if (!renderList)
|
|
590
|
+
return null;
|
|
591
|
+
return {
|
|
592
|
+
opaque: renderList.opaque || [],
|
|
593
|
+
transparent: renderList.transparent || [],
|
|
594
|
+
transmissive: renderList.transmissive || renderList.transparentDoublePass || [],
|
|
595
|
+
transparentDoublePass: renderList.transparentDoublePass || [],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
563
598
|
/** Update the LOD levels for the renderer. */
|
|
564
599
|
updateLODs(scene, camera, object, desiredDensity) {
|
|
565
600
|
if (!object.userData) {
|
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> | null;
|
|
8
|
+
export declare function getRaycastMesh(obj: Object3D): BufferGeometry<any, 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.d.ts
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { Texture } from "three";
|
|
2
|
+
/** Represents the possible shapes of texture image/source data in three.js.
|
|
3
|
+
* Source.data is typed as `{}` in r183 but at runtime can be ImageBitmap, HTMLImageElement, etc. */
|
|
4
|
+
export type TextureImageData = {
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
depth?: number;
|
|
8
|
+
data?: ArrayBufferView | null;
|
|
9
|
+
};
|
|
10
|
+
/** Check if a value has image-like dimensions (width/height) */
|
|
11
|
+
export declare function hasImageDimensions(value: unknown): value is {
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
};
|
|
15
|
+
/** Check if a value has pixel data (e.g. typed array from a DataTexture) */
|
|
16
|
+
export declare function hasPixelData(value: unknown): value is {
|
|
17
|
+
data: ArrayBufferView;
|
|
18
|
+
};
|
|
19
|
+
/** Get the source data of a texture, typed for dimension/data access */
|
|
20
|
+
export declare function getSourceData(tex: Texture): TextureImageData | null;
|
|
21
|
+
/** Get the image of a texture, typed for dimension/data access.
|
|
22
|
+
* In r183, Texture.image is typed as `{}` but at runtime is an ImageBitmap, HTMLImageElement, etc. */
|
|
23
|
+
export declare function getTextureImage(tex: Texture): TextureImageData | null;
|
|
24
|
+
/** Get width/height of a texture from image or source data */
|
|
25
|
+
export declare function getTextureDimensions(tex: Texture): {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
};
|
|
2
29
|
export declare function isDebugMode(): string | boolean;
|
|
3
30
|
export declare function getParam(name: string): boolean | string;
|
|
4
31
|
export declare function resolveUrl(source: string | undefined, uri: string): string;
|
package/lib/utils.internal.js
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
import { RedFormat, RedIntegerFormat, RGFormat, RGIntegerFormat, RGBFormat, RGBAFormat, RGBAIntegerFormat } from "three";
|
|
2
|
+
/** Check if a value has image-like dimensions (width/height) */
|
|
3
|
+
export function hasImageDimensions(value) {
|
|
4
|
+
return value != null && typeof value.width === 'number' && typeof value.height === 'number';
|
|
5
|
+
}
|
|
6
|
+
/** Check if a value has pixel data (e.g. typed array from a DataTexture) */
|
|
7
|
+
export function hasPixelData(value) {
|
|
8
|
+
return value != null && value.data != null;
|
|
9
|
+
}
|
|
10
|
+
/** Get the source data of a texture, typed for dimension/data access */
|
|
11
|
+
export function getSourceData(tex) {
|
|
12
|
+
const data = tex.source?.data;
|
|
13
|
+
return data != null && typeof data === 'object' ? data : null;
|
|
14
|
+
}
|
|
15
|
+
/** Get the image of a texture, typed for dimension/data access.
|
|
16
|
+
* In r183, Texture.image is typed as `{}` but at runtime is an ImageBitmap, HTMLImageElement, etc. */
|
|
17
|
+
export function getTextureImage(tex) {
|
|
18
|
+
const img = tex.image;
|
|
19
|
+
return img != null && typeof img === 'object' ? img : null;
|
|
20
|
+
}
|
|
21
|
+
/** Get width/height of a texture from image or source data */
|
|
22
|
+
export function getTextureDimensions(tex) {
|
|
23
|
+
const img = getTextureImage(tex);
|
|
24
|
+
const src = getSourceData(tex);
|
|
25
|
+
return {
|
|
26
|
+
width: img?.width || src?.width || 0,
|
|
27
|
+
height: img?.height || src?.height || 0,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
1
30
|
const debug = getParam("debugprogressive");
|
|
2
31
|
export function isDebugMode() {
|
|
3
32
|
return debug;
|
|
@@ -44,7 +73,8 @@ export function resolveUrl(source, uri) {
|
|
|
44
73
|
export function isMobileDevice() {
|
|
45
74
|
if (_ismobile !== undefined)
|
|
46
75
|
return _ismobile;
|
|
47
|
-
|
|
76
|
+
const userAgent = globalThis.navigator?.userAgent || "";
|
|
77
|
+
_ismobile = /iPhone|iPad|iPod|Android|IEMobile/i.test(userAgent);
|
|
48
78
|
if (getParam("debugprogressive"))
|
|
49
79
|
console.log("[glTF Progressive]: isMobileDevice", _ismobile);
|
|
50
80
|
return _ismobile;
|
|
@@ -118,9 +148,10 @@ export class PromiseQueue {
|
|
|
118
148
|
}
|
|
119
149
|
// #region Texture Memory
|
|
120
150
|
export function determineTextureMemoryInBytes(texture) {
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
151
|
+
const img = texture.image;
|
|
152
|
+
const width = img?.width ?? 0;
|
|
153
|
+
const height = img?.height ?? 0;
|
|
154
|
+
const depth = img?.depth ?? 1;
|
|
124
155
|
const mipLevels = Math.floor(Math.log2(Math.max(width, height, depth))) + 1;
|
|
125
156
|
const bytesPerPixel = getBytesPerPixel(texture);
|
|
126
157
|
const totalBytes = (width * height * depth * bytesPerPixel * (1 - Math.pow(0.25, mipLevels))) / (1 - 0.25);
|
|
@@ -130,22 +161,22 @@ function getBytesPerPixel(texture) {
|
|
|
130
161
|
// Determine channel count from format
|
|
131
162
|
let channels = 4; // Default RGBA
|
|
132
163
|
const format = texture.format;
|
|
133
|
-
if (format ===
|
|
134
|
-
channels = 1;
|
|
135
|
-
else if (format ===
|
|
136
|
-
channels = 1;
|
|
137
|
-
else if (format ===
|
|
138
|
-
channels = 2;
|
|
139
|
-
else if (format ===
|
|
140
|
-
channels = 2;
|
|
141
|
-
else if (format ===
|
|
142
|
-
channels = 3;
|
|
164
|
+
if (format === RedFormat)
|
|
165
|
+
channels = 1;
|
|
166
|
+
else if (format === RedIntegerFormat)
|
|
167
|
+
channels = 1;
|
|
168
|
+
else if (format === RGFormat)
|
|
169
|
+
channels = 2;
|
|
170
|
+
else if (format === RGIntegerFormat)
|
|
171
|
+
channels = 2;
|
|
172
|
+
else if (format === RGBFormat)
|
|
173
|
+
channels = 3;
|
|
143
174
|
else if (format === 1029)
|
|
144
|
-
channels = 3; // RGBIntegerFormat
|
|
145
|
-
else if (format ===
|
|
146
|
-
channels = 4;
|
|
147
|
-
else if (format ===
|
|
148
|
-
channels = 4;
|
|
175
|
+
channels = 3; // RGBIntegerFormat (not exported in r183)
|
|
176
|
+
else if (format === RGBAFormat)
|
|
177
|
+
channels = 4;
|
|
178
|
+
else if (format === RGBAIntegerFormat)
|
|
179
|
+
channels = 4;
|
|
149
180
|
// Determine bytes per channel from type
|
|
150
181
|
let bytesPerChannel = 1; // UnsignedByteType default
|
|
151
182
|
const type = texture.type;
|
package/lib/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Box3, BufferAttribute, BufferGeometry, CompressedTexture, InterleavedBuffer, InterleavedBufferAttribute, Matrix3, Sphere, Texture, Vector3 } from "three";
|
|
2
2
|
import { createLoaders, GET_LOADER_LOCATION_CONFIG } from "../loaders.js";
|
|
3
|
-
import { isMobileDevice } from "../utils.internal.js";
|
|
3
|
+
import { getTextureDimensions, isMobileDevice } from "../utils.internal.js";
|
|
4
4
|
import { debug } from "../lods.debug.js";
|
|
5
5
|
const workers = new Array();
|
|
6
6
|
let getWorkerId = 0;
|
|
@@ -51,7 +51,10 @@ class GLTFLoaderWorker {
|
|
|
51
51
|
url = URL.createObjectURL(new Blob([url]));
|
|
52
52
|
}
|
|
53
53
|
else if (!url.startsWith("blob:") && !url.startsWith("http:") && !url.startsWith("https:")) {
|
|
54
|
-
|
|
54
|
+
const base = globalThis.location?.href;
|
|
55
|
+
if (base) {
|
|
56
|
+
url = new URL(url, base).toString();
|
|
57
|
+
}
|
|
55
58
|
}
|
|
56
59
|
const options = {
|
|
57
60
|
type: "load",
|
|
@@ -152,8 +155,7 @@ function processReceivedData(data) {
|
|
|
152
155
|
let newTexture = null;
|
|
153
156
|
if (texture.isCompressedTexture) {
|
|
154
157
|
const mipmaps = texture.mipmaps;
|
|
155
|
-
const width
|
|
156
|
-
const height = texture.image?.height || texture.source?.data?.height || -1;
|
|
158
|
+
const { width, height } = getTextureDimensions(texture);
|
|
157
159
|
newTexture = new CompressedTexture(mipmaps, width, height, texture.format, texture.type, texture.mapping, texture.wrapS, texture.wrapT, texture.magFilter, texture.minFilter, texture.anisotropy, texture.colorSpace);
|
|
158
160
|
}
|
|
159
161
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-progressive",
|
|
3
|
-
"version": "3.6.0-
|
|
3
|
+
"version": "3.6.0-canary.027a4c9",
|
|
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": {
|
|
@@ -59,10 +59,13 @@
|
|
|
59
59
|
"three": ">= 0.160.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
+
"@needle-tools/engine": "^5.1.0-alpha.6",
|
|
63
|
+
"@needle-tools/three-test-matrix": "^0.1.0",
|
|
62
64
|
"@stylistic/eslint-plugin-ts": "^1.5.4",
|
|
63
|
-
"@types/three": "0.
|
|
65
|
+
"@types/three": "0.183.1",
|
|
64
66
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
|
65
67
|
"@typescript-eslint/parser": "^6.2.0",
|
|
68
|
+
"@vitest/browser-playwright": "^4.1.8",
|
|
66
69
|
"eslint": "^8.56.0",
|
|
67
70
|
"eslint-plugin-import": "^2.29.1",
|
|
68
71
|
"eslint-plugin-no-secrets": "^0.8.9",
|
|
@@ -71,8 +74,10 @@
|
|
|
71
74
|
"eslint-plugin-xss": "^0.1.12",
|
|
72
75
|
"nodemon": "^3.1.4",
|
|
73
76
|
"npm-watch": "^0.13.0",
|
|
77
|
+
"playwright": "^1.60.0",
|
|
74
78
|
"three": ">= 0.160.0",
|
|
75
|
-
"vite": "7"
|
|
79
|
+
"vite": "7",
|
|
80
|
+
"vitest": "^4.1.8"
|
|
76
81
|
},
|
|
77
82
|
"types": "./lib/index.d.ts"
|
|
78
|
-
}
|
|
83
|
+
}
|