@needle-tools/gltf-progressive 3.1.0 → 3.1.1-next.50e45f8
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 +4 -0
- package/README.md +79 -39
- package/gltf-progressive.js +916 -720
- package/gltf-progressive.min.js +7 -7
- package/gltf-progressive.umd.cjs +8 -8
- package/lib/extension.d.ts +3 -0
- package/lib/extension.js +96 -14
- package/lib/loaders.d.ts +8 -0
- package/lib/loaders.js +33 -22
- package/lib/lods.manager.js +24 -11
- package/lib/utils.internal.d.ts +4 -5
- package/lib/utils.internal.js +2 -2
- package/lib/version.js +1 -1
- package/lib/worker/loader.mainthread.d.ts +45 -0
- package/lib/worker/loader.mainthread.js +193 -0
- package/package.json +2 -2
package/lib/extension.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
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 { PromiseQueue, resolveUrl } from "./utils.internal.js";
|
|
4
|
+
import { getParam, 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";
|
|
8
8
|
// import { PromiseAllWithErrors, resolveUrl } from "../../engine_utils.js";
|
|
9
9
|
import { plugins } from "./plugins/plugin.js";
|
|
10
10
|
import { debug } from "./lods.debug.js";
|
|
11
|
-
|
|
11
|
+
import { getWorker } from "./worker/loader.mainthread.js";
|
|
12
|
+
const useWorker = getParam("gltf-progressive-worker");
|
|
13
|
+
const reduceMipmaps = getParam("gltf-progressive-reduce-mipmaps");
|
|
12
14
|
const $progressiveTextureExtension = Symbol("needle-progressive-texture");
|
|
15
|
+
export const EXTENSION_NAME = "NEEDLE_progressive";
|
|
13
16
|
/**
|
|
14
17
|
* The NEEDLE_progressive extension for the GLTFLoader is responsible for loading progressive LODs for meshes and textures.
|
|
15
18
|
* This extension can be used to load different resolutions of a mesh or texture at runtime (e.g. for LODs or progressive textures).
|
|
@@ -83,6 +86,10 @@ export class NEEDLE_progressive {
|
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
}
|
|
89
|
+
else {
|
|
90
|
+
if (debug)
|
|
91
|
+
console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${material.type}`);
|
|
92
|
+
}
|
|
86
93
|
material[cacheKey] = minmax;
|
|
87
94
|
return minmax;
|
|
88
95
|
function processTexture(tex, minmax) {
|
|
@@ -309,8 +316,10 @@ export class NEEDLE_progressive {
|
|
|
309
316
|
}
|
|
310
317
|
return NEEDLE_progressive.getOrLoadLOD(current, level).then(tex => {
|
|
311
318
|
// this can currently not happen
|
|
312
|
-
if (Array.isArray(tex))
|
|
319
|
+
if (Array.isArray(tex)) {
|
|
320
|
+
console.warn("Progressive: Got an array of textures for a texture slot, this should not happen...");
|
|
313
321
|
return null;
|
|
322
|
+
}
|
|
314
323
|
if (tex?.isTexture === true) {
|
|
315
324
|
if (tex != current) {
|
|
316
325
|
if (material && slot) {
|
|
@@ -326,6 +335,15 @@ export class NEEDLE_progressive {
|
|
|
326
335
|
}
|
|
327
336
|
// assigned.dispose();
|
|
328
337
|
}
|
|
338
|
+
// Since we're switching LOD level for the texture based on distance we can avoid uploading all the mipmaps
|
|
339
|
+
if (reduceMipmaps && tex.mipmaps) {
|
|
340
|
+
const prevCount = tex.mipmaps.length;
|
|
341
|
+
tex.mipmaps.length = Math.min(tex.mipmaps.length, 3);
|
|
342
|
+
if (prevCount !== tex.mipmaps.length) {
|
|
343
|
+
if (debug)
|
|
344
|
+
console.debug(`Reduced mipmap count from ${prevCount} to ${tex.mipmaps.length} for ${tex.uuid}: ${tex.image?.width}x${tex.image?.height}.`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
329
347
|
material[slot] = tex;
|
|
330
348
|
}
|
|
331
349
|
// check if the old texture is still used by other objects
|
|
@@ -375,6 +393,20 @@ export class NEEDLE_progressive {
|
|
|
375
393
|
return mesh;
|
|
376
394
|
});
|
|
377
395
|
};
|
|
396
|
+
// private _isLoadingTexture;
|
|
397
|
+
// loadTexture = (textureIndex: number) => {
|
|
398
|
+
// if (this._isLoadingTexture) return null;
|
|
399
|
+
// const ext = this.parser.json.textures[textureIndex]?.extensions?.[EXTENSION_NAME] as NEEDLE_ext_progressive_texture;
|
|
400
|
+
// if (!ext) return null;
|
|
401
|
+
// this._isLoadingTexture = true;
|
|
402
|
+
// return this.parser.getDependency("texture", textureIndex).then(tex => {
|
|
403
|
+
// this._isLoadingTexture = false;
|
|
404
|
+
// if (tex) {
|
|
405
|
+
// NEEDLE_progressive.registerTexture(this.url, tex as Texture, ext.lods?.length, textureIndex, ext);
|
|
406
|
+
// }
|
|
407
|
+
// return tex;
|
|
408
|
+
// });
|
|
409
|
+
// }
|
|
378
410
|
afterRoot(gltf) {
|
|
379
411
|
if (debug)
|
|
380
412
|
console.log("AFTER", this.url, gltf);
|
|
@@ -440,13 +472,16 @@ export class NEEDLE_progressive {
|
|
|
440
472
|
* Register a texture with LOD information
|
|
441
473
|
*/
|
|
442
474
|
static registerTexture = (url, tex, level, index, ext) => {
|
|
443
|
-
if (debug)
|
|
444
|
-
console.log("> Progressive: register texture", index, tex.name, tex.uuid, tex, ext);
|
|
445
475
|
if (!tex) {
|
|
446
476
|
if (debug)
|
|
447
|
-
console.error("gltf-progressive:
|
|
477
|
+
console.error("gltf-progressive: Called register texture without texture");
|
|
448
478
|
return;
|
|
449
479
|
}
|
|
480
|
+
if (debug) {
|
|
481
|
+
const width = tex.image?.width || tex.source?.data?.width || 0;
|
|
482
|
+
const height = tex.image?.height || tex.source?.data?.height || 0;
|
|
483
|
+
console.log(`> 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);
|
|
484
|
+
}
|
|
450
485
|
// 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)
|
|
451
486
|
// see https://github.com/needle-tools/needle-engine-support/issues/133
|
|
452
487
|
if (tex.source)
|
|
@@ -491,17 +526,22 @@ export class NEEDLE_progressive {
|
|
|
491
526
|
static previouslyLoaded = new Map();
|
|
492
527
|
/** this contains the geometry/textures that were originally loaded */
|
|
493
528
|
static lowresCache = new Map();
|
|
529
|
+
static workers = [];
|
|
530
|
+
static _workersIndex = 0;
|
|
494
531
|
static async getOrLoadLOD(current, level) {
|
|
495
532
|
const debugverbose = debug == "verbose";
|
|
496
533
|
/** this key is used to lookup the LOD information */
|
|
497
|
-
const LOD = current
|
|
534
|
+
const LOD = this.getAssignedLODInformation(current);
|
|
498
535
|
if (!LOD) {
|
|
536
|
+
if (debug)
|
|
537
|
+
console.warn(`[gltf-progressive] No LOD information found: ${current.name}, uuid: ${current.uuid}, type: ${current.type}`, current);
|
|
499
538
|
return null;
|
|
500
539
|
}
|
|
501
540
|
const LODKEY = LOD?.key;
|
|
502
541
|
let lodInfo;
|
|
542
|
+
const isTextureRequest = current.isTexture === true;
|
|
503
543
|
// See https://github.com/needle-tools/needle-engine-support/issues/133
|
|
504
|
-
if (
|
|
544
|
+
if (isTextureRequest) {
|
|
505
545
|
const tex = current;
|
|
506
546
|
if (tex.source && tex.source[$progressiveTextureExtension])
|
|
507
547
|
lodInfo = tex.source[$progressiveTextureExtension];
|
|
@@ -543,6 +583,7 @@ export class NEEDLE_progressive {
|
|
|
543
583
|
}
|
|
544
584
|
// check if the requested file has already been loaded
|
|
545
585
|
const KEY = lod_url + "_" + lodInfo.guid;
|
|
586
|
+
const slot = await this.queue.slot(lod_url);
|
|
546
587
|
// check if the requested file is currently being loaded
|
|
547
588
|
const existing = this.previouslyLoaded.get(KEY);
|
|
548
589
|
if (existing !== undefined) {
|
|
@@ -581,7 +622,7 @@ export class NEEDLE_progressive {
|
|
|
581
622
|
return res;
|
|
582
623
|
}
|
|
583
624
|
}
|
|
584
|
-
|
|
625
|
+
// #region loading
|
|
585
626
|
if (!slot.use) {
|
|
586
627
|
if (debug)
|
|
587
628
|
console.log(`LOD ${level} was aborted: ${lod_url}`);
|
|
@@ -589,6 +630,39 @@ export class NEEDLE_progressive {
|
|
|
589
630
|
}
|
|
590
631
|
const ext = lodInfo;
|
|
591
632
|
const request = new Promise(async (resolve, _) => {
|
|
633
|
+
// const useWorker = true;
|
|
634
|
+
if (useWorker) {
|
|
635
|
+
const worker = await getWorker({});
|
|
636
|
+
const res = await worker.load(lod_url);
|
|
637
|
+
if (res.textures.length > 0) {
|
|
638
|
+
// const textures = new Array<Texture>();
|
|
639
|
+
for (const entry of res.textures) {
|
|
640
|
+
let texture = entry.texture;
|
|
641
|
+
NEEDLE_progressive.assignLODInformation(LOD.url, texture, LODKEY, level, undefined);
|
|
642
|
+
if (current instanceof Texture) {
|
|
643
|
+
texture = this.copySettings(current, texture);
|
|
644
|
+
}
|
|
645
|
+
if (texture)
|
|
646
|
+
texture.guid = ext.guid;
|
|
647
|
+
// textures.push(texture);
|
|
648
|
+
return resolve(texture);
|
|
649
|
+
}
|
|
650
|
+
// if (textures.length > 0) {
|
|
651
|
+
// return resolve(textures);
|
|
652
|
+
// }
|
|
653
|
+
}
|
|
654
|
+
if (res.geometries.length > 0) {
|
|
655
|
+
const geometries = new Array();
|
|
656
|
+
for (const entry of res.geometries) {
|
|
657
|
+
const newGeo = entry.geometry;
|
|
658
|
+
NEEDLE_progressive.assignLODInformation(LOD.url, newGeo, LODKEY, level, entry.primitiveIndex);
|
|
659
|
+
geometries.push(newGeo);
|
|
660
|
+
}
|
|
661
|
+
return resolve(geometries);
|
|
662
|
+
}
|
|
663
|
+
return resolve(null);
|
|
664
|
+
}
|
|
665
|
+
// Old loading
|
|
592
666
|
const loader = new GLTFLoader();
|
|
593
667
|
addDracoAndKTX2Loaders(loader);
|
|
594
668
|
if (debug) {
|
|
@@ -665,7 +739,6 @@ export class NEEDLE_progressive {
|
|
|
665
739
|
}
|
|
666
740
|
if (found) {
|
|
667
741
|
const mesh = await parser.getDependency("mesh", index);
|
|
668
|
-
const meshExt = ext;
|
|
669
742
|
if (debugverbose)
|
|
670
743
|
console.log(`Loaded Mesh \"${mesh.name}\"`, lod_url, index, mesh, KEY);
|
|
671
744
|
if (mesh.isMesh === true) {
|
|
@@ -724,7 +797,8 @@ export class NEEDLE_progressive {
|
|
|
724
797
|
}
|
|
725
798
|
return null;
|
|
726
799
|
}
|
|
727
|
-
static
|
|
800
|
+
static maxConcurrent = 50;
|
|
801
|
+
static queue = new PromiseQueue(NEEDLE_progressive.maxConcurrent, { debug: debug != false });
|
|
728
802
|
static assignLODInformation(url, res, key, level, index) {
|
|
729
803
|
if (!res)
|
|
730
804
|
return;
|
|
@@ -732,9 +806,17 @@ export class NEEDLE_progressive {
|
|
|
732
806
|
res.userData = {};
|
|
733
807
|
const info = new LODInformation(url, key, level, index);
|
|
734
808
|
res.userData.LODS = info;
|
|
809
|
+
if ("source" in res && typeof res.source === "object")
|
|
810
|
+
res.source.LODS = info; // for tiled textures
|
|
735
811
|
}
|
|
736
812
|
static getAssignedLODInformation(res) {
|
|
737
|
-
|
|
813
|
+
if (!res)
|
|
814
|
+
return null;
|
|
815
|
+
if (res.userData?.LODS)
|
|
816
|
+
return res.userData.LODS;
|
|
817
|
+
if ("source" in res && res.source?.LODS)
|
|
818
|
+
return res.source.LODS;
|
|
819
|
+
return null;
|
|
738
820
|
}
|
|
739
821
|
// private static readonly _copiedTextures: WeakMap<Texture, Texture> = new Map();
|
|
740
822
|
static copySettings(source, target) {
|
|
@@ -753,8 +835,8 @@ export class NEEDLE_progressive {
|
|
|
753
835
|
// This should only happen once ever for every texture
|
|
754
836
|
// const original = target;
|
|
755
837
|
{
|
|
756
|
-
if (debug)
|
|
757
|
-
console.
|
|
838
|
+
if (debug === "verbose")
|
|
839
|
+
console.debug("Copy texture settings\n", source.uuid, "\n", target.uuid);
|
|
758
840
|
target = target.clone();
|
|
759
841
|
}
|
|
760
842
|
// else {
|
package/lib/loaders.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import { WebGLRenderer } from 'three';
|
|
|
2
2
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
3
3
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
4
4
|
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
5
|
+
/**
|
|
6
|
+
* Return the current loader configuration.
|
|
7
|
+
* These paths can be changed using `setDracoDecoderLocation` and `setKTX2TranscoderLocation`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const GET_LOADER_LOCATION_CONFIG: () => {
|
|
10
|
+
dracoDecoderPath: string;
|
|
11
|
+
ktx2TranscoderPath: string;
|
|
12
|
+
};
|
|
5
13
|
/**
|
|
6
14
|
* Set the location of the Draco decoder. If a draco loader has already been created, it will be updated.
|
|
7
15
|
* @default 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/'
|
package/lib/loaders.js
CHANGED
|
@@ -6,6 +6,7 @@ let DEFAULT_DRACO_DECODER_LOCATION = 'https://www.gstatic.com/draco/versioned/de
|
|
|
6
6
|
let DEFAULT_KTX2_TRANSCODER_LOCATION = 'https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/';
|
|
7
7
|
const defaultDraco = DEFAULT_DRACO_DECODER_LOCATION;
|
|
8
8
|
const defaultKTX2 = DEFAULT_KTX2_TRANSCODER_LOCATION;
|
|
9
|
+
// #region Online check
|
|
9
10
|
const _remoteDracoDecoderUrl = new URL(DEFAULT_DRACO_DECODER_LOCATION + "draco_decoder.js");
|
|
10
11
|
// if (typeof window !== "undefined") {
|
|
11
12
|
// if (!window.navigator.onLine) {
|
|
@@ -40,6 +41,15 @@ fetch(_remoteDracoDecoderUrl, {
|
|
|
40
41
|
.finally(() => {
|
|
41
42
|
prepareLoaders();
|
|
42
43
|
});
|
|
44
|
+
// #region Loader Configuration
|
|
45
|
+
/**
|
|
46
|
+
* Return the current loader configuration.
|
|
47
|
+
* These paths can be changed using `setDracoDecoderLocation` and `setKTX2TranscoderLocation`.
|
|
48
|
+
*/
|
|
49
|
+
export const GET_LOADER_LOCATION_CONFIG = () => ({
|
|
50
|
+
dracoDecoderPath: DEFAULT_DRACO_DECODER_LOCATION,
|
|
51
|
+
ktx2TranscoderPath: DEFAULT_KTX2_TRANSCODER_LOCATION
|
|
52
|
+
});
|
|
43
53
|
/**
|
|
44
54
|
* Set the location of the Draco decoder. If a draco loader has already been created, it will be updated.
|
|
45
55
|
* @default 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/'
|
|
@@ -72,28 +82,6 @@ export function setKTX2TranscoderLocation(location) {
|
|
|
72
82
|
console.debug("Setting KTX2 transcoder path to " + location);
|
|
73
83
|
}
|
|
74
84
|
}
|
|
75
|
-
const $dracoDecoderPath = Symbol("dracoDecoderPath");
|
|
76
|
-
let dracoLoader;
|
|
77
|
-
let meshoptDecoder;
|
|
78
|
-
let ktx2Loader;
|
|
79
|
-
/** Used to create and load loaders */
|
|
80
|
-
function prepareLoaders() {
|
|
81
|
-
if (!dracoLoader) {
|
|
82
|
-
dracoLoader = new DRACOLoader();
|
|
83
|
-
dracoLoader[$dracoDecoderPath] = DEFAULT_DRACO_DECODER_LOCATION;
|
|
84
|
-
dracoLoader.setDecoderPath(DEFAULT_DRACO_DECODER_LOCATION);
|
|
85
|
-
dracoLoader.setDecoderConfig({ type: 'js' });
|
|
86
|
-
dracoLoader.preload();
|
|
87
|
-
}
|
|
88
|
-
if (!ktx2Loader) {
|
|
89
|
-
ktx2Loader = new KTX2Loader();
|
|
90
|
-
ktx2Loader.setTranscoderPath(DEFAULT_KTX2_TRANSCODER_LOCATION);
|
|
91
|
-
ktx2Loader.init();
|
|
92
|
-
}
|
|
93
|
-
if (!meshoptDecoder) {
|
|
94
|
-
meshoptDecoder = MeshoptDecoder;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
85
|
/**
|
|
98
86
|
* Create loaders/decoders for Draco, KTX2 and Meshopt to be used with GLTFLoader.
|
|
99
87
|
* @param renderer - Provide a renderer to detect KTX2 support.
|
|
@@ -116,6 +104,29 @@ export function addDracoAndKTX2Loaders(loader) {
|
|
|
116
104
|
if (!loader.meshoptDecoder)
|
|
117
105
|
loader.setMeshoptDecoder(meshoptDecoder);
|
|
118
106
|
}
|
|
107
|
+
// #region internal
|
|
108
|
+
const $dracoDecoderPath = Symbol("dracoDecoderPath");
|
|
109
|
+
let dracoLoader;
|
|
110
|
+
let meshoptDecoder;
|
|
111
|
+
let ktx2Loader;
|
|
112
|
+
/** Used to create and load loaders */
|
|
113
|
+
function prepareLoaders() {
|
|
114
|
+
if (!dracoLoader) {
|
|
115
|
+
dracoLoader = new DRACOLoader();
|
|
116
|
+
dracoLoader[$dracoDecoderPath] = DEFAULT_DRACO_DECODER_LOCATION;
|
|
117
|
+
dracoLoader.setDecoderPath(DEFAULT_DRACO_DECODER_LOCATION);
|
|
118
|
+
dracoLoader.setDecoderConfig({ type: 'js' });
|
|
119
|
+
dracoLoader.preload();
|
|
120
|
+
}
|
|
121
|
+
if (!ktx2Loader) {
|
|
122
|
+
ktx2Loader = new KTX2Loader();
|
|
123
|
+
ktx2Loader.setTranscoderPath(DEFAULT_KTX2_TRANSCODER_LOCATION);
|
|
124
|
+
ktx2Loader.init();
|
|
125
|
+
}
|
|
126
|
+
if (!meshoptDecoder) {
|
|
127
|
+
meshoptDecoder = MeshoptDecoder;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
119
130
|
const gltfLoaderConfigurations = new WeakMap();
|
|
120
131
|
export function configureLoader(loader, opts) {
|
|
121
132
|
let config = gltfLoaderConfigurations.get(loader);
|
package/lib/lods.manager.js
CHANGED
|
@@ -160,6 +160,14 @@ export class LODsManager {
|
|
|
160
160
|
constructor(renderer, context) {
|
|
161
161
|
this.renderer = renderer;
|
|
162
162
|
this.context = { ...context };
|
|
163
|
+
// createGLTFLoaderWorker().then(res => {
|
|
164
|
+
// res.load("https://cloud.needle.tools/-/assets/Z23hmXBZ20RjNk-Z20RjNk-optimized/file").then(res2 => {
|
|
165
|
+
// console.log("DONE", res2);
|
|
166
|
+
// })
|
|
167
|
+
// res.load("https://cloud.needle.tools/-/assets/Z23hmXBZ20RjNk-Z20RjNk-world/file").then(res2 => {
|
|
168
|
+
// console.log("DONE2", res2);
|
|
169
|
+
// })
|
|
170
|
+
// })
|
|
163
171
|
}
|
|
164
172
|
#originalRender;
|
|
165
173
|
#clock = new Clock();
|
|
@@ -360,7 +368,7 @@ export class LODsManager {
|
|
|
360
368
|
}
|
|
361
369
|
// TODO: we currently can not switch texture lods because we need better caching for the textures internally (see copySettings in progressive + NE-4431)
|
|
362
370
|
if (object.material && levels.texture_lod >= 0) {
|
|
363
|
-
this.loadProgressiveTextures(object.material, levels.texture_lod);
|
|
371
|
+
this.loadProgressiveTextures(object.material, levels.texture_lod, debugLodLevel);
|
|
364
372
|
}
|
|
365
373
|
if (debug && object.material && !object["isGizmo"]) {
|
|
366
374
|
applyDebugSettings(object.material);
|
|
@@ -376,7 +384,7 @@ export class LODsManager {
|
|
|
376
384
|
* @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
|
|
377
385
|
* @returns Promise with true if the LOD was loaded, false if not
|
|
378
386
|
*/
|
|
379
|
-
loadProgressiveTextures(material, level) {
|
|
387
|
+
loadProgressiveTextures(material, level, overrideLodLevel) {
|
|
380
388
|
if (!material)
|
|
381
389
|
return;
|
|
382
390
|
if (Array.isArray(material)) {
|
|
@@ -394,10 +402,9 @@ export class LODsManager {
|
|
|
394
402
|
else if (level < material[$currentLOD]) {
|
|
395
403
|
update = true;
|
|
396
404
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
level = debugLevel;
|
|
405
|
+
if (overrideLodLevel !== undefined && overrideLodLevel >= 0) {
|
|
406
|
+
update = material[$currentLOD] != overrideLodLevel;
|
|
407
|
+
level = overrideLodLevel;
|
|
401
408
|
}
|
|
402
409
|
if (update) {
|
|
403
410
|
material[$currentLOD] = level;
|
|
@@ -459,6 +466,7 @@ export class LODsManager {
|
|
|
459
466
|
}
|
|
460
467
|
static skinnedMeshBoundsFrameOffsetCounter = 0;
|
|
461
468
|
static $skinnedMeshBoundsOffset = Symbol("gltf-progressive-skinnedMeshBoundsOffset");
|
|
469
|
+
// #region calculateLodLevel
|
|
462
470
|
calculateLodLevel(camera, mesh, state, desiredDensity, result) {
|
|
463
471
|
if (!mesh) {
|
|
464
472
|
result.mesh_lod = -1;
|
|
@@ -485,7 +493,7 @@ export class LODsManager {
|
|
|
485
493
|
const primitive_index = NEEDLE_progressive.getPrimitiveIndex(mesh.geometry);
|
|
486
494
|
const has_mesh_lods = mesh_lods && mesh_lods.length > 0;
|
|
487
495
|
const texture_lods_minmax = NEEDLE_progressive.getMaterialMinMaxLODsCount(mesh.material);
|
|
488
|
-
const has_texture_lods = texture_lods_minmax
|
|
496
|
+
const has_texture_lods = texture_lods_minmax.min_count !== Infinity && texture_lods_minmax.min_count >= 0 && texture_lods_minmax.max_count >= 0;
|
|
489
497
|
// We can skip all this if we dont have any LOD information
|
|
490
498
|
if (!has_mesh_lods && !has_texture_lods) {
|
|
491
499
|
result.mesh_lod = 0;
|
|
@@ -648,6 +656,10 @@ export class LODsManager {
|
|
|
648
656
|
const lod = mesh_lods[l];
|
|
649
657
|
const densityForThisLevel = lod.densities?.[primitive_index] || lod.density || .00001;
|
|
650
658
|
const resultingDensity = densityForThisLevel / state.lastScreenCoverage;
|
|
659
|
+
if (primitive_index > 0 && isDevelopmentServer() && !lod.densities && !globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]) {
|
|
660
|
+
window["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] = true;
|
|
661
|
+
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.`);
|
|
662
|
+
}
|
|
651
663
|
if (resultingDensity < desiredDensity) {
|
|
652
664
|
expectedLevel = l;
|
|
653
665
|
break;
|
|
@@ -671,7 +683,7 @@ export class LODsManager {
|
|
|
671
683
|
if (changed) {
|
|
672
684
|
const level = mesh_lods?.[result.mesh_lod];
|
|
673
685
|
if (level) {
|
|
674
|
-
console.
|
|
686
|
+
console.debug(`Mesh LOD changed: ${state.lastLodLevel_Mesh} → ${result.mesh_lod} (density: ${level.densities?.[primitive_index].toFixed(0)}) | ${mesh.name}`);
|
|
675
687
|
}
|
|
676
688
|
}
|
|
677
689
|
}
|
|
@@ -706,10 +718,11 @@ export class LODsManager {
|
|
|
706
718
|
if (lod.max_height > pixelSizeOnScreen || (!foundLod && i === 0)) {
|
|
707
719
|
foundLod = true;
|
|
708
720
|
result.texture_lod = i;
|
|
709
|
-
if (
|
|
710
|
-
|
|
711
|
-
|
|
721
|
+
if (debugProgressiveLoading) {
|
|
722
|
+
if (result.texture_lod < state.lastLodLevel_Texture) {
|
|
723
|
+
const lod_pixel_height = lod.max_height;
|
|
712
724
|
console.log(`Texture LOD changed: ${state.lastLodLevel_Texture} → ${result.texture_lod} = ${lod_pixel_height}px \nScreensize: ${pixelSizeOnScreen.toFixed(0)}px, Coverage: ${(100 * state.lastScreenCoverage).toFixed(2)}%, Volume ${volume.toFixed(1)} \n${mesh.name}`);
|
|
725
|
+
}
|
|
713
726
|
}
|
|
714
727
|
break;
|
|
715
728
|
}
|
package/lib/utils.internal.d.ts
CHANGED
|
@@ -4,10 +4,10 @@ export declare function resolveUrl(source: string | undefined, uri: string): str
|
|
|
4
4
|
/** @returns `true` if it's a phone or tablet */
|
|
5
5
|
export declare function isMobileDevice(): boolean;
|
|
6
6
|
export declare function isDevelopmentServer(): boolean;
|
|
7
|
-
type SlotReturnValue = {
|
|
8
|
-
use?: ((promise: Promise<
|
|
7
|
+
export type SlotReturnValue<T = any> = {
|
|
8
|
+
use?: ((promise: Promise<T>) => void);
|
|
9
9
|
};
|
|
10
|
-
export declare class PromiseQueue {
|
|
10
|
+
export declare class PromiseQueue<T = any> {
|
|
11
11
|
readonly maxConcurrent: number;
|
|
12
12
|
private readonly _running;
|
|
13
13
|
private readonly _queue;
|
|
@@ -19,8 +19,7 @@ export declare class PromiseQueue {
|
|
|
19
19
|
/**
|
|
20
20
|
* Request a slot for a promise with a specific key. This function returns a promise with a `use` method that can be called to add the promise to the queue.
|
|
21
21
|
*/
|
|
22
|
-
slot(key: string): Promise<SlotReturnValue
|
|
22
|
+
slot(key: string): Promise<SlotReturnValue<T>>;
|
|
23
23
|
private add;
|
|
24
24
|
private internalUpdate;
|
|
25
25
|
}
|
|
26
|
-
export {};
|
package/lib/utils.internal.js
CHANGED
|
@@ -89,10 +89,10 @@ export class PromiseQueue {
|
|
|
89
89
|
promise.finally(() => {
|
|
90
90
|
this._running.delete(key);
|
|
91
91
|
if (this.debug)
|
|
92
|
-
console.debug(`[PromiseQueue]: Promise
|
|
92
|
+
console.debug(`[PromiseQueue]: Promise finished now running: ${this._running.size}, waiting: ${this._queue.length}. (finished ${key})`);
|
|
93
93
|
});
|
|
94
94
|
if (this.debug)
|
|
95
|
-
console.debug(`[PromiseQueue]:
|
|
95
|
+
console.debug(`[PromiseQueue]: Added new promise, now running: ${this._running.size}, waiting: ${this._queue.length}. (added ${key})`);
|
|
96
96
|
}
|
|
97
97
|
internalUpdate() {
|
|
98
98
|
// Run for as many free slots as we can
|
package/lib/version.js
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BufferGeometry, Texture, WebGLRenderer } from "three";
|
|
2
|
+
import type { KTX2LoaderWorkerConfig } from "three/examples/jsm/loaders/KTX2Loader.js";
|
|
3
|
+
type GLTFLoaderWorkerOptions = {
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
};
|
|
6
|
+
type WorkerLoadResult = {
|
|
7
|
+
url: string;
|
|
8
|
+
geometries: Array<{
|
|
9
|
+
geometry: BufferGeometry;
|
|
10
|
+
meshIndex: number;
|
|
11
|
+
primitiveIndex: number;
|
|
12
|
+
extensions: Record<string, any>;
|
|
13
|
+
}>;
|
|
14
|
+
textures: Array<{
|
|
15
|
+
texture: Texture;
|
|
16
|
+
textureIndex: number;
|
|
17
|
+
extensions: Record<string, any>;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
export declare function getWorker(opts?: GLTFLoaderWorkerOptions): Promise<GLTFLoaderWorker>;
|
|
21
|
+
export type { GLTFLoaderWorker, GLTFLoaderWorkerOptions, WorkerLoadResult };
|
|
22
|
+
/** @internal */
|
|
23
|
+
export type GLTFLoaderWorker_Message = {
|
|
24
|
+
type: 'init';
|
|
25
|
+
} | {
|
|
26
|
+
type: 'load';
|
|
27
|
+
url: string;
|
|
28
|
+
dracoDecoderPath: string;
|
|
29
|
+
ktx2TranscoderPath: string;
|
|
30
|
+
ktx2LoaderConfig: KTX2LoaderWorkerConfig;
|
|
31
|
+
} | {
|
|
32
|
+
type: "loaded-gltf";
|
|
33
|
+
result: WorkerLoadResult;
|
|
34
|
+
};
|
|
35
|
+
declare class GLTFLoaderWorker {
|
|
36
|
+
private readonly worker;
|
|
37
|
+
static createWorker(opts: GLTFLoaderWorkerOptions): Promise<GLTFLoaderWorker>;
|
|
38
|
+
private _running;
|
|
39
|
+
private _webglRenderer;
|
|
40
|
+
load(url: string | URL, opts?: {
|
|
41
|
+
renderer?: WebGLRenderer;
|
|
42
|
+
}): Promise<WorkerLoadResult>;
|
|
43
|
+
private _debug;
|
|
44
|
+
private constructor();
|
|
45
|
+
}
|