@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/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
- return NEEDLE_progressive.getOrLoadLOD(currentGeometry, level).then(geo => {
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
- if (mesh["LOD:requested level"] === level) {
232
- delete mesh["LOD:requested level"];
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 promise = NEEDLE_progressive.getOrLoadLOD(current, level).then(tex => {
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 setPendingTextureSlotRequest(material, slot, level, force, promise) {
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 === request) {
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 = tex.image?.width || tex.source?.data?.width || 0;
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
- const width = texture.image?.width || texture.source?.data?.width || 0;
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?.data || res.source?.data) {
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?.data || res.source?.data) {
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 = new URL(url_str, window.location.href);
146
- if (url.hostname.endsWith("needle.tools")) {
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
+ }
@@ -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
@@ -1,4 +1,5 @@
1
- import { Box3, Clock, Color, Matrix4, Mesh, Sphere, Vector3 } from "three";
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.#delta = self.#clock.getDelta();
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.renderer.renderLists.get(scene, 0);
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.renderer.renderLists.get(scene, 0);
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
@@ -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;
@@ -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
- _ismobile = /iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent);
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 width = texture.image?.width ?? 0;
122
- const height = texture.image?.height ?? 0;
123
- const depth = texture.image?.depth ?? 1;
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 === 1024)
134
- channels = 1; // RedFormat
135
- else if (format === 1025)
136
- channels = 1; // RedIntegerFormat
137
- else if (format === 1026)
138
- channels = 2; // RGFormat
139
- else if (format === 1027)
140
- channels = 2; // RGIntegerFormat
141
- else if (format === 1022)
142
- channels = 3; // RGBFormat
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 === 1023)
146
- channels = 4; // RGBAFormat
147
- else if (format === 1033)
148
- channels = 4; // RGBAIntegerFormat
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,4 +1,4 @@
1
1
  // replaced at build time
2
- export const version = "3.6.0-alpha.2";
2
+ export const version = "3.6.0-alpha.3";
3
3
  globalThis["GLTF_PROGRESSIVE_VERSION"] = version;
4
4
  console.debug(`[gltf-progressive] version ${version || "-"}`);
@@ -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
- url = new URL(url, window.location.href).toString();
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 = texture.image?.width || texture.source?.data?.width || -1;
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-alpha.2",
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.169.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
+ }