@needle-tools/gltf-progressive 3.1.0-next.f550970 → 3.1.1-next.80f25bb

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/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
- export const EXTENSION_NAME = "NEEDLE_progressive";
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) {
@@ -310,7 +317,7 @@ export class NEEDLE_progressive {
310
317
  return NEEDLE_progressive.getOrLoadLOD(current, level).then(tex => {
311
318
  // this can currently not happen
312
319
  if (Array.isArray(tex)) {
313
- console.warn("Progressive: Got an array of textures for a texture slot, this should not happen", tex);
320
+ console.warn("Progressive: Got an array of textures for a texture slot, this should not happen...");
314
321
  return null;
315
322
  }
316
323
  if (tex?.isTexture === true) {
@@ -328,6 +335,15 @@ export class NEEDLE_progressive {
328
335
  }
329
336
  // assigned.dispose();
330
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
+ }
331
347
  material[slot] = tex;
332
348
  }
333
349
  // check if the old texture is still used by other objects
@@ -510,6 +526,8 @@ export class NEEDLE_progressive {
510
526
  static previouslyLoaded = new Map();
511
527
  /** this contains the geometry/textures that were originally loaded */
512
528
  static lowresCache = new Map();
529
+ static workers = [];
530
+ static _workersIndex = 0;
513
531
  static async getOrLoadLOD(current, level) {
514
532
  const debugverbose = debug == "verbose";
515
533
  /** this key is used to lookup the LOD information */
@@ -521,8 +539,9 @@ export class NEEDLE_progressive {
521
539
  }
522
540
  const LODKEY = LOD?.key;
523
541
  let lodInfo;
542
+ const isTextureRequest = current.isTexture === true;
524
543
  // See https://github.com/needle-tools/needle-engine-support/issues/133
525
- if (current.isTexture === true) {
544
+ if (isTextureRequest) {
526
545
  const tex = current;
527
546
  if (tex.source && tex.source[$progressiveTextureExtension])
528
547
  lodInfo = tex.source[$progressiveTextureExtension];
@@ -564,6 +583,7 @@ export class NEEDLE_progressive {
564
583
  }
565
584
  // check if the requested file has already been loaded
566
585
  const KEY = lod_url + "_" + lodInfo.guid;
586
+ const slot = await this.queue.slot(lod_url);
567
587
  // check if the requested file is currently being loaded
568
588
  const existing = this.previouslyLoaded.get(KEY);
569
589
  if (existing !== undefined) {
@@ -602,7 +622,7 @@ export class NEEDLE_progressive {
602
622
  return res;
603
623
  }
604
624
  }
605
- const slot = await this.queue.slot(lod_url);
625
+ // #region loading
606
626
  if (!slot.use) {
607
627
  if (debug)
608
628
  console.log(`LOD ${level} was aborted: ${lod_url}`);
@@ -610,6 +630,39 @@ export class NEEDLE_progressive {
610
630
  }
611
631
  const ext = lodInfo;
612
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
613
666
  const loader = new GLTFLoader();
614
667
  addDracoAndKTX2Loaders(loader);
615
668
  if (debug) {
@@ -686,7 +739,6 @@ export class NEEDLE_progressive {
686
739
  }
687
740
  if (found) {
688
741
  const mesh = await parser.getDependency("mesh", index);
689
- const meshExt = ext;
690
742
  if (debugverbose)
691
743
  console.log(`Loaded Mesh \"${mesh.name}\"`, lod_url, index, mesh, KEY);
692
744
  if (mesh.isMesh === true) {
@@ -745,7 +797,8 @@ export class NEEDLE_progressive {
745
797
  }
746
798
  return null;
747
799
  }
748
- static queue = new PromiseQueue(100, { debug: debug != false });
800
+ static maxConcurrent = 50;
801
+ static queue = new PromiseQueue(NEEDLE_progressive.maxConcurrent, { debug: debug != false });
749
802
  static assignLODInformation(url, res, key, level, index) {
750
803
  if (!res)
751
804
  return;
@@ -782,8 +835,8 @@ export class NEEDLE_progressive {
782
835
  // This should only happen once ever for every texture
783
836
  // const original = target;
784
837
  {
785
- if (debug)
786
- console.warn("Copy texture settings\n", source.uuid, "\n", target.uuid);
838
+ if (debug === "verbose")
839
+ console.debug("Copy texture settings\n", source.uuid, "\n", target.uuid);
787
840
  target = target.clone();
788
841
  }
789
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);
@@ -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
- const debugLevel = material["DEBUG:LOD"];
398
- if (debugLevel != undefined) {
399
- update = material[$currentLOD] != debugLevel;
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?.min_count != Infinity && texture_lods_minmax.min_count > 0 && texture_lods_minmax.max_count > 0;
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;
@@ -675,7 +683,7 @@ export class LODsManager {
675
683
  if (changed) {
676
684
  const level = mesh_lods?.[result.mesh_lod];
677
685
  if (level) {
678
- console.log(`Mesh LOD changed: ${state.lastLodLevel_Mesh} → ${result.mesh_lod} (${level.density.toFixed(0)}) - ${mesh.name}`);
686
+ console.debug(`Mesh LOD changed: ${state.lastLodLevel_Mesh} → ${result.mesh_lod} (density: ${level.densities?.[primitive_index].toFixed(0)}) | ${mesh.name}`);
679
687
  }
680
688
  }
681
689
  }
@@ -710,10 +718,11 @@ export class LODsManager {
710
718
  if (lod.max_height > pixelSizeOnScreen || (!foundLod && i === 0)) {
711
719
  foundLod = true;
712
720
  result.texture_lod = i;
713
- if (result.texture_lod < state.lastLodLevel_Texture) {
714
- const lod_pixel_height = lod.max_height;
715
- if (debugProgressiveLoading)
721
+ if (debugProgressiveLoading) {
722
+ if (result.texture_lod < state.lastLodLevel_Texture) {
723
+ const lod_pixel_height = lod.max_height;
716
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
+ }
717
726
  }
718
727
  break;
719
728
  }
@@ -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<any>) => void);
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/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // replaced at build time
2
- export const version = "3.1.0";
2
+ export const version = "3.1.1";
3
3
  globalThis["GLTF_PROGRESSIVE_VERSION"] = version;
4
4
  console.debug(`[gltf-progressive] version ${version || "-"}`);
@@ -0,0 +1,46 @@
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
+ }
@@ -0,0 +1,201 @@
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-progressive",
3
- "version": "3.1.0-next.f550970",
3
+ "version": "3.1.1-next.80f25bb",
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": {