@needle-tools/gltf-progressive 3.1.1-next.80f25bb → 3.1.1-next.ddb979d
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/README.md +1 -2
- package/gltf-progressive.js +781 -966
- package/gltf-progressive.min.js +8 -8
- package/gltf-progressive.umd.cjs +8 -8
- package/lib/extension.d.ts +0 -3
- package/lib/extension.js +9 -62
- package/lib/loaders.d.ts +0 -8
- package/lib/loaders.js +22 -33
- package/lib/lods.manager.js +11 -20
- package/lib/utils.internal.d.ts +5 -4
- package/package.json +1 -1
- package/lib/worker/loader.mainthread.d.ts +0 -46
- package/lib/worker/loader.mainthread.js +0 -201
package/lib/extension.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
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 {
|
|
4
|
+
import { 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
|
-
import { getWorker } from "./worker/loader.mainthread.js";
|
|
12
|
-
const useWorker = getParam("gltf-progressive-worker");
|
|
13
|
-
const reduceMipmaps = getParam("gltf-progressive-reduce-mipmaps");
|
|
14
|
-
const $progressiveTextureExtension = Symbol("needle-progressive-texture");
|
|
15
11
|
export const EXTENSION_NAME = "NEEDLE_progressive";
|
|
12
|
+
const $progressiveTextureExtension = Symbol("needle-progressive-texture");
|
|
16
13
|
/**
|
|
17
14
|
* The NEEDLE_progressive extension for the GLTFLoader is responsible for loading progressive LODs for meshes and textures.
|
|
18
15
|
* This extension can be used to load different resolutions of a mesh or texture at runtime (e.g. for LODs or progressive textures).
|
|
@@ -86,10 +83,6 @@ export class NEEDLE_progressive {
|
|
|
86
83
|
}
|
|
87
84
|
}
|
|
88
85
|
}
|
|
89
|
-
else {
|
|
90
|
-
if (debug)
|
|
91
|
-
console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${material.type}`);
|
|
92
|
-
}
|
|
93
86
|
material[cacheKey] = minmax;
|
|
94
87
|
return minmax;
|
|
95
88
|
function processTexture(tex, minmax) {
|
|
@@ -317,7 +310,7 @@ export class NEEDLE_progressive {
|
|
|
317
310
|
return NEEDLE_progressive.getOrLoadLOD(current, level).then(tex => {
|
|
318
311
|
// this can currently not happen
|
|
319
312
|
if (Array.isArray(tex)) {
|
|
320
|
-
console.warn("Progressive: Got an array of textures for a texture slot, this should not happen
|
|
313
|
+
console.warn("Progressive: Got an array of textures for a texture slot, this should not happen", tex);
|
|
321
314
|
return null;
|
|
322
315
|
}
|
|
323
316
|
if (tex?.isTexture === true) {
|
|
@@ -335,15 +328,6 @@ export class NEEDLE_progressive {
|
|
|
335
328
|
}
|
|
336
329
|
// assigned.dispose();
|
|
337
330
|
}
|
|
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
|
-
}
|
|
347
331
|
material[slot] = tex;
|
|
348
332
|
}
|
|
349
333
|
// check if the old texture is still used by other objects
|
|
@@ -526,8 +510,6 @@ export class NEEDLE_progressive {
|
|
|
526
510
|
static previouslyLoaded = new Map();
|
|
527
511
|
/** this contains the geometry/textures that were originally loaded */
|
|
528
512
|
static lowresCache = new Map();
|
|
529
|
-
static workers = [];
|
|
530
|
-
static _workersIndex = 0;
|
|
531
513
|
static async getOrLoadLOD(current, level) {
|
|
532
514
|
const debugverbose = debug == "verbose";
|
|
533
515
|
/** this key is used to lookup the LOD information */
|
|
@@ -539,9 +521,8 @@ export class NEEDLE_progressive {
|
|
|
539
521
|
}
|
|
540
522
|
const LODKEY = LOD?.key;
|
|
541
523
|
let lodInfo;
|
|
542
|
-
const isTextureRequest = current.isTexture === true;
|
|
543
524
|
// See https://github.com/needle-tools/needle-engine-support/issues/133
|
|
544
|
-
if (
|
|
525
|
+
if (current.isTexture === true) {
|
|
545
526
|
const tex = current;
|
|
546
527
|
if (tex.source && tex.source[$progressiveTextureExtension])
|
|
547
528
|
lodInfo = tex.source[$progressiveTextureExtension];
|
|
@@ -583,7 +564,6 @@ export class NEEDLE_progressive {
|
|
|
583
564
|
}
|
|
584
565
|
// check if the requested file has already been loaded
|
|
585
566
|
const KEY = lod_url + "_" + lodInfo.guid;
|
|
586
|
-
const slot = await this.queue.slot(lod_url);
|
|
587
567
|
// check if the requested file is currently being loaded
|
|
588
568
|
const existing = this.previouslyLoaded.get(KEY);
|
|
589
569
|
if (existing !== undefined) {
|
|
@@ -622,7 +602,7 @@ export class NEEDLE_progressive {
|
|
|
622
602
|
return res;
|
|
623
603
|
}
|
|
624
604
|
}
|
|
625
|
-
|
|
605
|
+
const slot = await this.queue.slot(lod_url);
|
|
626
606
|
if (!slot.use) {
|
|
627
607
|
if (debug)
|
|
628
608
|
console.log(`LOD ${level} was aborted: ${lod_url}`);
|
|
@@ -630,39 +610,6 @@ export class NEEDLE_progressive {
|
|
|
630
610
|
}
|
|
631
611
|
const ext = lodInfo;
|
|
632
612
|
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
|
|
666
613
|
const loader = new GLTFLoader();
|
|
667
614
|
addDracoAndKTX2Loaders(loader);
|
|
668
615
|
if (debug) {
|
|
@@ -739,6 +686,7 @@ export class NEEDLE_progressive {
|
|
|
739
686
|
}
|
|
740
687
|
if (found) {
|
|
741
688
|
const mesh = await parser.getDependency("mesh", index);
|
|
689
|
+
const meshExt = ext;
|
|
742
690
|
if (debugverbose)
|
|
743
691
|
console.log(`Loaded Mesh \"${mesh.name}\"`, lod_url, index, mesh, KEY);
|
|
744
692
|
if (mesh.isMesh === true) {
|
|
@@ -797,8 +745,7 @@ export class NEEDLE_progressive {
|
|
|
797
745
|
}
|
|
798
746
|
return null;
|
|
799
747
|
}
|
|
800
|
-
static
|
|
801
|
-
static queue = new PromiseQueue(NEEDLE_progressive.maxConcurrent, { debug: debug != false });
|
|
748
|
+
static queue = new PromiseQueue(100, { debug: debug != false });
|
|
802
749
|
static assignLODInformation(url, res, key, level, index) {
|
|
803
750
|
if (!res)
|
|
804
751
|
return;
|
|
@@ -835,8 +782,8 @@ export class NEEDLE_progressive {
|
|
|
835
782
|
// This should only happen once ever for every texture
|
|
836
783
|
// const original = target;
|
|
837
784
|
{
|
|
838
|
-
if (debug
|
|
839
|
-
console.
|
|
785
|
+
if (debug)
|
|
786
|
+
console.warn("Copy texture settings\n", source.uuid, "\n", target.uuid);
|
|
840
787
|
target = target.clone();
|
|
841
788
|
}
|
|
842
789
|
// else {
|
package/lib/loaders.d.ts
CHANGED
|
@@ -2,14 +2,6 @@ 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
|
-
};
|
|
13
5
|
/**
|
|
14
6
|
* Set the location of the Draco decoder. If a draco loader has already been created, it will be updated.
|
|
15
7
|
* @default 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/'
|
package/lib/loaders.js
CHANGED
|
@@ -6,7 +6,6 @@ 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
|
|
10
9
|
const _remoteDracoDecoderUrl = new URL(DEFAULT_DRACO_DECODER_LOCATION + "draco_decoder.js");
|
|
11
10
|
// if (typeof window !== "undefined") {
|
|
12
11
|
// if (!window.navigator.onLine) {
|
|
@@ -41,15 +40,6 @@ fetch(_remoteDracoDecoderUrl, {
|
|
|
41
40
|
.finally(() => {
|
|
42
41
|
prepareLoaders();
|
|
43
42
|
});
|
|
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
|
-
});
|
|
53
43
|
/**
|
|
54
44
|
* Set the location of the Draco decoder. If a draco loader has already been created, it will be updated.
|
|
55
45
|
* @default 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/'
|
|
@@ -82,29 +72,6 @@ export function setKTX2TranscoderLocation(location) {
|
|
|
82
72
|
console.debug("Setting KTX2 transcoder path to " + location);
|
|
83
73
|
}
|
|
84
74
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Create loaders/decoders for Draco, KTX2 and Meshopt to be used with GLTFLoader.
|
|
87
|
-
* @param renderer - Provide a renderer to detect KTX2 support.
|
|
88
|
-
* @returns The loaders/decoders.
|
|
89
|
-
*/
|
|
90
|
-
export function createLoaders(renderer) {
|
|
91
|
-
prepareLoaders();
|
|
92
|
-
if (renderer) {
|
|
93
|
-
ktx2Loader.detectSupport(renderer);
|
|
94
|
-
}
|
|
95
|
-
else if (renderer !== null)
|
|
96
|
-
console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail");
|
|
97
|
-
return { dracoLoader, ktx2Loader, meshoptDecoder };
|
|
98
|
-
}
|
|
99
|
-
export function addDracoAndKTX2Loaders(loader) {
|
|
100
|
-
if (!loader.dracoLoader)
|
|
101
|
-
loader.setDRACOLoader(dracoLoader);
|
|
102
|
-
if (!loader.ktx2Loader)
|
|
103
|
-
loader.setKTX2Loader(ktx2Loader);
|
|
104
|
-
if (!loader.meshoptDecoder)
|
|
105
|
-
loader.setMeshoptDecoder(meshoptDecoder);
|
|
106
|
-
}
|
|
107
|
-
// #region internal
|
|
108
75
|
const $dracoDecoderPath = Symbol("dracoDecoderPath");
|
|
109
76
|
let dracoLoader;
|
|
110
77
|
let meshoptDecoder;
|
|
@@ -127,6 +94,28 @@ function prepareLoaders() {
|
|
|
127
94
|
meshoptDecoder = MeshoptDecoder;
|
|
128
95
|
}
|
|
129
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Create loaders/decoders for Draco, KTX2 and Meshopt to be used with GLTFLoader.
|
|
99
|
+
* @param renderer - Provide a renderer to detect KTX2 support.
|
|
100
|
+
* @returns The loaders/decoders.
|
|
101
|
+
*/
|
|
102
|
+
export function createLoaders(renderer) {
|
|
103
|
+
prepareLoaders();
|
|
104
|
+
if (renderer) {
|
|
105
|
+
ktx2Loader.detectSupport(renderer);
|
|
106
|
+
}
|
|
107
|
+
else if (renderer !== null)
|
|
108
|
+
console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail");
|
|
109
|
+
return { dracoLoader, ktx2Loader, meshoptDecoder };
|
|
110
|
+
}
|
|
111
|
+
export function addDracoAndKTX2Loaders(loader) {
|
|
112
|
+
if (!loader.dracoLoader)
|
|
113
|
+
loader.setDRACOLoader(dracoLoader);
|
|
114
|
+
if (!loader.ktx2Loader)
|
|
115
|
+
loader.setKTX2Loader(ktx2Loader);
|
|
116
|
+
if (!loader.meshoptDecoder)
|
|
117
|
+
loader.setMeshoptDecoder(meshoptDecoder);
|
|
118
|
+
}
|
|
130
119
|
const gltfLoaderConfigurations = new WeakMap();
|
|
131
120
|
export function configureLoader(loader, opts) {
|
|
132
121
|
let config = gltfLoaderConfigurations.get(loader);
|
package/lib/lods.manager.js
CHANGED
|
@@ -160,14 +160,6 @@ 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
|
-
// })
|
|
171
163
|
}
|
|
172
164
|
#originalRender;
|
|
173
165
|
#clock = new Clock();
|
|
@@ -368,7 +360,7 @@ export class LODsManager {
|
|
|
368
360
|
}
|
|
369
361
|
// TODO: we currently can not switch texture lods because we need better caching for the textures internally (see copySettings in progressive + NE-4431)
|
|
370
362
|
if (object.material && levels.texture_lod >= 0) {
|
|
371
|
-
this.loadProgressiveTextures(object.material, levels.texture_lod
|
|
363
|
+
this.loadProgressiveTextures(object.material, levels.texture_lod);
|
|
372
364
|
}
|
|
373
365
|
if (debug && object.material && !object["isGizmo"]) {
|
|
374
366
|
applyDebugSettings(object.material);
|
|
@@ -384,7 +376,7 @@ export class LODsManager {
|
|
|
384
376
|
* @param level the LOD level to load. Level 0 is the best quality, higher levels are lower quality
|
|
385
377
|
* @returns Promise with true if the LOD was loaded, false if not
|
|
386
378
|
*/
|
|
387
|
-
loadProgressiveTextures(material, level
|
|
379
|
+
loadProgressiveTextures(material, level) {
|
|
388
380
|
if (!material)
|
|
389
381
|
return;
|
|
390
382
|
if (Array.isArray(material)) {
|
|
@@ -402,9 +394,10 @@ export class LODsManager {
|
|
|
402
394
|
else if (level < material[$currentLOD]) {
|
|
403
395
|
update = true;
|
|
404
396
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
397
|
+
const debugLevel = material["DEBUG:LOD"];
|
|
398
|
+
if (debugLevel != undefined) {
|
|
399
|
+
update = material[$currentLOD] != debugLevel;
|
|
400
|
+
level = debugLevel;
|
|
408
401
|
}
|
|
409
402
|
if (update) {
|
|
410
403
|
material[$currentLOD] = level;
|
|
@@ -466,7 +459,6 @@ export class LODsManager {
|
|
|
466
459
|
}
|
|
467
460
|
static skinnedMeshBoundsFrameOffsetCounter = 0;
|
|
468
461
|
static $skinnedMeshBoundsOffset = Symbol("gltf-progressive-skinnedMeshBoundsOffset");
|
|
469
|
-
// #region calculateLodLevel
|
|
470
462
|
calculateLodLevel(camera, mesh, state, desiredDensity, result) {
|
|
471
463
|
if (!mesh) {
|
|
472
464
|
result.mesh_lod = -1;
|
|
@@ -493,7 +485,7 @@ export class LODsManager {
|
|
|
493
485
|
const primitive_index = NEEDLE_progressive.getPrimitiveIndex(mesh.geometry);
|
|
494
486
|
const has_mesh_lods = mesh_lods && mesh_lods.length > 0;
|
|
495
487
|
const texture_lods_minmax = NEEDLE_progressive.getMaterialMinMaxLODsCount(mesh.material);
|
|
496
|
-
const has_texture_lods = texture_lods_minmax
|
|
488
|
+
const has_texture_lods = texture_lods_minmax?.min_count != Infinity && texture_lods_minmax.min_count > 0 && texture_lods_minmax.max_count > 0;
|
|
497
489
|
// We can skip all this if we dont have any LOD information
|
|
498
490
|
if (!has_mesh_lods && !has_texture_lods) {
|
|
499
491
|
result.mesh_lod = 0;
|
|
@@ -683,7 +675,7 @@ export class LODsManager {
|
|
|
683
675
|
if (changed) {
|
|
684
676
|
const level = mesh_lods?.[result.mesh_lod];
|
|
685
677
|
if (level) {
|
|
686
|
-
console.
|
|
678
|
+
console.log(`Mesh LOD changed: ${state.lastLodLevel_Mesh} → ${result.mesh_lod} (${level.density.toFixed(0)}) - ${mesh.name}`);
|
|
687
679
|
}
|
|
688
680
|
}
|
|
689
681
|
}
|
|
@@ -718,11 +710,10 @@ export class LODsManager {
|
|
|
718
710
|
if (lod.max_height > pixelSizeOnScreen || (!foundLod && i === 0)) {
|
|
719
711
|
foundLod = true;
|
|
720
712
|
result.texture_lod = i;
|
|
721
|
-
if (
|
|
722
|
-
|
|
723
|
-
|
|
713
|
+
if (result.texture_lod < state.lastLodLevel_Texture) {
|
|
714
|
+
const lod_pixel_height = lod.max_height;
|
|
715
|
+
if (debugProgressiveLoading)
|
|
724
716
|
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
|
-
}
|
|
726
717
|
}
|
|
727
718
|
break;
|
|
728
719
|
}
|
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
|
-
|
|
8
|
-
use?: ((promise: Promise<
|
|
7
|
+
type SlotReturnValue = {
|
|
8
|
+
use?: ((promise: Promise<any>) => void);
|
|
9
9
|
};
|
|
10
|
-
export declare class PromiseQueue
|
|
10
|
+
export declare class PromiseQueue {
|
|
11
11
|
readonly maxConcurrent: number;
|
|
12
12
|
private readonly _running;
|
|
13
13
|
private readonly _queue;
|
|
@@ -19,7 +19,8 @@ export declare class PromiseQueue<T = any> {
|
|
|
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>;
|
|
23
23
|
private add;
|
|
24
24
|
private internalUpdate;
|
|
25
25
|
}
|
|
26
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-progressive",
|
|
3
|
-
"version": "3.1.1-next.
|
|
3
|
+
"version": "3.1.1-next.ddb979d",
|
|
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": {
|
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
private static workerUrl;
|
|
38
|
-
static createWorker(opts: GLTFLoaderWorkerOptions): Promise<GLTFLoaderWorker>;
|
|
39
|
-
private _running;
|
|
40
|
-
private _webglRenderer;
|
|
41
|
-
load(url: string | URL, opts?: {
|
|
42
|
-
renderer?: WebGLRenderer;
|
|
43
|
-
}): Promise<WorkerLoadResult>;
|
|
44
|
-
private _debug;
|
|
45
|
-
private constructor();
|
|
46
|
-
}
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { Box3, BufferAttribute, BufferGeometry, CompressedTexture, InterleavedBuffer, InterleavedBufferAttribute, Matrix3, Sphere, Texture, Vector3 } from "three";
|
|
2
|
-
import { createLoaders, GET_LOADER_LOCATION_CONFIG } from "../loaders.js";
|
|
3
|
-
import { isMobileDevice } from "../utils.internal.js";
|
|
4
|
-
import { debug } from "../lods.debug.js";
|
|
5
|
-
const workers = new Array();
|
|
6
|
-
let getWorkerId = 0;
|
|
7
|
-
const maxWorkers = isMobileDevice() ? 2 : 10;
|
|
8
|
-
export function getWorker(opts) {
|
|
9
|
-
if (workers.length < maxWorkers) {
|
|
10
|
-
const index = workers.length;
|
|
11
|
-
if (debug)
|
|
12
|
-
console.warn(`[Worker] Creating new worker #${index}`);
|
|
13
|
-
const worker = GLTFLoaderWorker.createWorker(opts || {});
|
|
14
|
-
workers.push(worker);
|
|
15
|
-
return worker;
|
|
16
|
-
}
|
|
17
|
-
const index = (getWorkerId++) % workers.length;
|
|
18
|
-
const worker = workers[index];
|
|
19
|
-
return worker;
|
|
20
|
-
}
|
|
21
|
-
class GLTFLoaderWorker {
|
|
22
|
-
worker;
|
|
23
|
-
static workerUrl = null;
|
|
24
|
-
static async createWorker(opts) {
|
|
25
|
-
if (!GLTFLoaderWorker.workerUrl) {
|
|
26
|
-
GLTFLoaderWorker.workerUrl = await import(/* @vite-ignore */ `./loader.worker.js?url`).then(m => {
|
|
27
|
-
return (m.default || m).toString();
|
|
28
|
-
});
|
|
29
|
-
if (!GLTFLoaderWorker.workerUrl)
|
|
30
|
-
throw new Error("Failed to load GLTFLoaderWorker worker URL");
|
|
31
|
-
}
|
|
32
|
-
const worker = new Worker(new URL(GLTFLoaderWorker.workerUrl, import.meta.url), {
|
|
33
|
-
type: 'module',
|
|
34
|
-
});
|
|
35
|
-
const instance = new GLTFLoaderWorker(worker, opts);
|
|
36
|
-
return instance;
|
|
37
|
-
}
|
|
38
|
-
_running = [];
|
|
39
|
-
_webglRenderer = null;
|
|
40
|
-
async load(url, opts) {
|
|
41
|
-
const configs = GET_LOADER_LOCATION_CONFIG();
|
|
42
|
-
// Make sure we have a webgl renderer for the KTX transcoder feature detection
|
|
43
|
-
let renderer = opts?.renderer;
|
|
44
|
-
if (!renderer) {
|
|
45
|
-
this._webglRenderer ??= (async () => {
|
|
46
|
-
const { WebGLRenderer } = await import("three");
|
|
47
|
-
return new WebGLRenderer();
|
|
48
|
-
})();
|
|
49
|
-
renderer = await this._webglRenderer;
|
|
50
|
-
}
|
|
51
|
-
const loaders = createLoaders(renderer);
|
|
52
|
-
const ktx2Loader = loaders.ktx2Loader;
|
|
53
|
-
const ktx2LoaderConfig = ktx2Loader.workerConfig;
|
|
54
|
-
if (url instanceof URL) {
|
|
55
|
-
url = url.toString();
|
|
56
|
-
}
|
|
57
|
-
else if (url.startsWith("file:")) {
|
|
58
|
-
// make blob url
|
|
59
|
-
url = URL.createObjectURL(new Blob([url]));
|
|
60
|
-
}
|
|
61
|
-
else if (!url.startsWith("blob:") && !url.startsWith("http:") && !url.startsWith("https:")) {
|
|
62
|
-
url = new URL(url, window.location.href).toString();
|
|
63
|
-
}
|
|
64
|
-
const options = {
|
|
65
|
-
type: "load",
|
|
66
|
-
url: url,
|
|
67
|
-
dracoDecoderPath: configs.dracoDecoderPath,
|
|
68
|
-
ktx2TranscoderPath: configs.ktx2TranscoderPath,
|
|
69
|
-
ktx2LoaderConfig: ktx2LoaderConfig,
|
|
70
|
-
};
|
|
71
|
-
if (this._debug)
|
|
72
|
-
console.debug("[Worker] Sending load request", options);
|
|
73
|
-
this.worker.postMessage(options);
|
|
74
|
-
return new Promise(resolve => {
|
|
75
|
-
this._running.push({
|
|
76
|
-
url: url.toString(),
|
|
77
|
-
resolve,
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
_debug = false;
|
|
82
|
-
constructor(worker, _opts) {
|
|
83
|
-
this.worker = worker;
|
|
84
|
-
this._debug = _opts.debug ?? false;
|
|
85
|
-
worker.onmessage = (event) => {
|
|
86
|
-
const data = event.data;
|
|
87
|
-
if (this._debug)
|
|
88
|
-
console.log("[Worker] EVENT", data);
|
|
89
|
-
switch (data.type) {
|
|
90
|
-
case "loaded-gltf": {
|
|
91
|
-
for (const promise of this._running) {
|
|
92
|
-
if (promise.url === data.result.url) {
|
|
93
|
-
// process received data and resolve
|
|
94
|
-
processReceivedData(data.result);
|
|
95
|
-
promise.resolve(data.result);
|
|
96
|
-
// cleanup
|
|
97
|
-
const url = promise.url;
|
|
98
|
-
if (url.startsWith("blob:")) {
|
|
99
|
-
URL.revokeObjectURL(url);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
worker.onerror = (error) => {
|
|
107
|
-
console.error("[Worker] Error in gltf-progressive worker:", error);
|
|
108
|
-
};
|
|
109
|
-
worker.postMessage({
|
|
110
|
-
type: 'init',
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function processReceivedData(data) {
|
|
115
|
-
for (const res of data.geometries) {
|
|
116
|
-
const worker_geometry = res.geometry;
|
|
117
|
-
// console.log(worker_geometry)
|
|
118
|
-
const geo = new BufferGeometry();
|
|
119
|
-
geo.name = worker_geometry.name || "";
|
|
120
|
-
if (worker_geometry.index) {
|
|
121
|
-
const index = worker_geometry.index;
|
|
122
|
-
geo.setIndex(cloneAttribute(index));
|
|
123
|
-
}
|
|
124
|
-
// geo.drawRange = receivedGeometry.drawRange || { start: 0, count: Infinity };
|
|
125
|
-
for (const attrName in worker_geometry.attributes) {
|
|
126
|
-
const attribute = worker_geometry.attributes[attrName];
|
|
127
|
-
const clonedAttribute = cloneAttribute(attribute);
|
|
128
|
-
geo.setAttribute(attrName, clonedAttribute);
|
|
129
|
-
}
|
|
130
|
-
// handle morph attributes
|
|
131
|
-
// TODO: one slow aspect that could be moved to the worker is updating the morph target textures
|
|
132
|
-
if (worker_geometry.morphAttributes) {
|
|
133
|
-
for (const morphName in worker_geometry.morphAttributes) {
|
|
134
|
-
const morphAttributes = worker_geometry.morphAttributes[morphName];
|
|
135
|
-
const morphArray = morphAttributes.map(attribute => {
|
|
136
|
-
return cloneAttribute(attribute);
|
|
137
|
-
});
|
|
138
|
-
geo.morphAttributes[morphName] = morphArray;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
geo.morphTargetsRelative = worker_geometry.morphTargetsRelative ?? false;
|
|
142
|
-
geo.boundingBox = new Box3();
|
|
143
|
-
geo.boundingBox.min = new Vector3(worker_geometry.boundingBox?.min.x, worker_geometry.boundingBox?.min.y, worker_geometry.boundingBox?.min.z);
|
|
144
|
-
geo.boundingBox.max = new Vector3(worker_geometry.boundingBox?.max.x, worker_geometry.boundingBox?.max.y, worker_geometry.boundingBox?.max.z);
|
|
145
|
-
geo.boundingSphere = new Sphere(new Vector3(worker_geometry.boundingSphere?.center.x, worker_geometry.boundingSphere?.center.y, worker_geometry.boundingSphere?.center.z), worker_geometry.boundingSphere?.radius);
|
|
146
|
-
// // handle groups
|
|
147
|
-
if (worker_geometry.groups) {
|
|
148
|
-
for (const group of worker_geometry.groups) {
|
|
149
|
-
geo.addGroup(group.start, group.count, group.materialIndex);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// // handle user data
|
|
153
|
-
if (worker_geometry.userData) {
|
|
154
|
-
geo.userData = worker_geometry.userData;
|
|
155
|
-
}
|
|
156
|
-
res.geometry = geo;
|
|
157
|
-
}
|
|
158
|
-
for (const res of data.textures) {
|
|
159
|
-
const texture = res.texture;
|
|
160
|
-
let newTexture = null;
|
|
161
|
-
if (texture.isCompressedTexture) {
|
|
162
|
-
const mipmaps = texture.mipmaps;
|
|
163
|
-
const width = texture.image?.width || texture.source?.data?.width || -1;
|
|
164
|
-
const height = texture.image?.height || texture.source?.data?.height || -1;
|
|
165
|
-
newTexture = new CompressedTexture(mipmaps, width, height, texture.format, texture.type, texture.mapping, texture.wrapS, texture.wrapT, texture.magFilter, texture.minFilter, texture.anisotropy, texture.colorSpace);
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
newTexture = new Texture(texture.image, texture.mapping, texture.wrapS, texture.wrapT, texture.magFilter, texture.minFilter, texture.format, texture.type, texture.anisotropy, texture.colorSpace);
|
|
169
|
-
newTexture.mipmaps = texture.mipmaps;
|
|
170
|
-
newTexture.channel = texture.channel;
|
|
171
|
-
newTexture.source.data = texture.source.data;
|
|
172
|
-
newTexture.flipY = texture.flipY;
|
|
173
|
-
newTexture.premultiplyAlpha = texture.premultiplyAlpha;
|
|
174
|
-
newTexture.unpackAlignment = texture.unpackAlignment;
|
|
175
|
-
newTexture.matrix = new Matrix3(...texture.matrix.elements);
|
|
176
|
-
}
|
|
177
|
-
if (!newTexture) {
|
|
178
|
-
console.error("[Worker] Failed to create new texture from received data. Texture is not a CompressedTexture or Texture.");
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
res.texture = newTexture;
|
|
182
|
-
}
|
|
183
|
-
return data;
|
|
184
|
-
}
|
|
185
|
-
function cloneAttribute(attribute) {
|
|
186
|
-
let res = attribute;
|
|
187
|
-
if ("isInterleavedBufferAttribute" in attribute && attribute.isInterleavedBufferAttribute) {
|
|
188
|
-
const data = attribute.data;
|
|
189
|
-
const array = data.array;
|
|
190
|
-
const interleavedBuffer = new InterleavedBuffer(array, data.stride);
|
|
191
|
-
res = new InterleavedBufferAttribute(interleavedBuffer, attribute.itemSize, array.byteOffset, attribute.normalized);
|
|
192
|
-
res.offset = attribute.offset;
|
|
193
|
-
}
|
|
194
|
-
else if ("isBufferAttribute" in attribute && attribute.isBufferAttribute) {
|
|
195
|
-
res = new BufferAttribute(attribute.array, attribute.itemSize, attribute.normalized);
|
|
196
|
-
res.usage = attribute.usage;
|
|
197
|
-
res.gpuType = attribute.gpuType;
|
|
198
|
-
res.updateRanges = attribute.updateRanges;
|
|
199
|
-
}
|
|
200
|
-
return res;
|
|
201
|
-
}
|