@needle-tools/gltf-progressive 3.6.0-alpha.3 → 3.6.0-beta.1
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 +42 -8
- package/examples/modelviewer-multiple.html +4 -4
- package/examples/modelviewer.html +4 -4
- package/examples/offscreen/index.html +15 -0
- package/examples/offscreen/main.js +98 -0
- package/examples/react-three-fiber/index.html +2 -2
- package/examples/react-three-fiber/package-lock.json +482 -484
- package/examples/react-three-fiber/package.json +4 -4
- package/examples/react-three-fiber/src/App.tsx +76 -21
- package/examples/react-three-fiber/src/styles.css +2 -4
- package/examples/react-three-fiber/vite.config.js +2 -2
- package/examples/shared/example-utils.js +297 -0
- package/examples/shared/example.css +44 -0
- package/examples/shared/runtime.js +34 -0
- package/examples/threejs/index.html +6 -10
- package/examples/threejs/main.js +5 -23
- package/examples/webgpu/index.html +15 -0
- package/examples/webgpu/main.js +105 -0
- package/examples/worker-rendering/index.html +15 -0
- package/examples/worker-rendering/main.js +109 -0
- package/examples/worker-rendering/worker.js +166 -0
- package/gltf-progressive.js +429 -355
- package/gltf-progressive.min.js +9 -9
- package/gltf-progressive.umd.cjs +9 -9
- package/lib/extension.js +5 -7
- package/lib/loaders.d.ts +1 -8
- package/lib/loaders.js +15 -2
- package/lib/lods.debug.js +1 -1
- package/lib/lods.manager.d.ts +3 -0
- package/lib/lods.manager.js +62 -18
- package/lib/utils.d.ts +1 -1
- package/lib/utils.internal.d.ts +27 -0
- package/lib/utils.internal.js +68 -25
- package/lib/version.js +1 -1
- package/lib/worker/loader.mainthread.js +6 -4
- package/package.json +8 -3
package/lib/loaders.d.ts
CHANGED
|
@@ -28,14 +28,7 @@ export declare function setKTX2TranscoderLocation(location: string): void;
|
|
|
28
28
|
export declare function createLoaders(renderer: WebGLRenderer | null): {
|
|
29
29
|
dracoLoader: DRACOLoader;
|
|
30
30
|
ktx2Loader: KTX2Loader;
|
|
31
|
-
meshoptDecoder:
|
|
32
|
-
supported: boolean;
|
|
33
|
-
ready: Promise<void>;
|
|
34
|
-
decodeVertexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, filter?: string) => void;
|
|
35
|
-
decodeIndexBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void;
|
|
36
|
-
decodeIndexSequence: (target: Uint8Array, count: number, size: number, source: Uint8Array) => void;
|
|
37
|
-
decodeGltfBuffer: (target: Uint8Array, count: number, size: number, source: Uint8Array, mode: string, filter?: string) => void;
|
|
38
|
-
};
|
|
31
|
+
meshoptDecoder: any;
|
|
39
32
|
};
|
|
40
33
|
export declare function addDracoAndKTX2Loaders(loader: GLTFLoader): void;
|
|
41
34
|
/**
|
package/lib/loaders.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-ignore @types/three re-exports from meshoptimizer which may not resolve in all configs
|
|
1
2
|
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
|
|
2
3
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
3
4
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
@@ -142,8 +143,8 @@ const originalLoadFunction = GLTFLoader.prototype.load;
|
|
|
142
143
|
function onLoad(...args) {
|
|
143
144
|
const config = gltfLoaderConfigurations.get(this);
|
|
144
145
|
let url_str = args[0];
|
|
145
|
-
const url =
|
|
146
|
-
if (url
|
|
146
|
+
const url = tryResolveUrl(url_str);
|
|
147
|
+
if (url?.hostname.endsWith("needle.tools")) {
|
|
147
148
|
const progressive = config?.progressive !== undefined ? config.progressive : true;
|
|
148
149
|
const usecase = config?.usecase ? config.usecase : "default";
|
|
149
150
|
if (progressive) {
|
|
@@ -159,3 +160,15 @@ function onLoad(...args) {
|
|
|
159
160
|
return res;
|
|
160
161
|
}
|
|
161
162
|
GLTFLoader.prototype.load = onLoad;
|
|
163
|
+
function tryResolveUrl(url) {
|
|
164
|
+
try {
|
|
165
|
+
if (url instanceof URL) {
|
|
166
|
+
return url;
|
|
167
|
+
}
|
|
168
|
+
const base = globalThis.location?.href;
|
|
169
|
+
return base ? new URL(url, base) : new URL(url);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
package/lib/lods.debug.js
CHANGED
|
@@ -2,7 +2,7 @@ import { getParam } from "./utils.internal.js";
|
|
|
2
2
|
export const debug = getParam("debugprogressive");
|
|
3
3
|
let debug_RenderWireframe = undefined;
|
|
4
4
|
export let debug_OverrideLodLevel = -1; // -1 is automatic
|
|
5
|
-
if (debug) {
|
|
5
|
+
if (debug && typeof window !== "undefined") {
|
|
6
6
|
const maxLevel = 6;
|
|
7
7
|
function debugToggleProgressive() {
|
|
8
8
|
debug_OverrideLodLevel += 1;
|
package/lib/lods.manager.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export declare class LODsManager {
|
|
|
75
75
|
static getObjectLODState(object: Object3D): LOD_state | undefined;
|
|
76
76
|
static addPlugin(plugin: NEEDLE_progressive_plugin): void;
|
|
77
77
|
static removePlugin(plugin: NEEDLE_progressive_plugin): void;
|
|
78
|
+
/** Read-only snapshot of the currently registered plugins, for inspection. Use {@link addPlugin} / {@link removePlugin} to modify the registry. */
|
|
79
|
+
static getPlugins(): readonly NEEDLE_progressive_plugin[];
|
|
78
80
|
/**
|
|
79
81
|
* Gets the LODsManager for the given renderer. If the LODsManager does not exist yet, it will be created.
|
|
80
82
|
* @param renderer The renderer to get the LODsManager for.
|
|
@@ -207,6 +209,7 @@ export declare class LODsManager {
|
|
|
207
209
|
* Update LODs in a scene
|
|
208
210
|
*/
|
|
209
211
|
private internalUpdate;
|
|
212
|
+
private getRenderList;
|
|
210
213
|
/** Update the LOD levels for the renderer. */
|
|
211
214
|
private updateLODs;
|
|
212
215
|
/** Load progressive textures for the given material
|
package/lib/lods.manager.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import { Box3, Color, Matrix4, Mesh, Sphere, Vector3 } from "three";
|
|
2
3
|
import { NEEDLE_progressive } from "./extension.js";
|
|
3
4
|
import { createLoaders } from "./loaders.js";
|
|
4
5
|
import { getParam, isDevelopmentServer, isMobileDevice } from "./utils.internal.js";
|
|
@@ -12,6 +13,7 @@ const suppressProgressiveLoading = getParam("noprogressive");
|
|
|
12
13
|
const $lodsManager = Symbol("Needle:LODSManager");
|
|
13
14
|
const $lodstate = Symbol("Needle:LODState");
|
|
14
15
|
const $currentLOD = Symbol("Needle:CurrentLOD");
|
|
16
|
+
const ThreeRuntime = THREE;
|
|
15
17
|
const levels = { mesh_lod: -1, texture_lod: -1 };
|
|
16
18
|
const debugLODColor = new Color();
|
|
17
19
|
export const lodDebugColors = [
|
|
@@ -48,6 +50,10 @@ export const lodDebugColors = [
|
|
|
48
50
|
0x4d908e,
|
|
49
51
|
0x555555,
|
|
50
52
|
];
|
|
53
|
+
function createLODTimer() {
|
|
54
|
+
const Timer = ThreeRuntime.Timer || ThreeRuntime.Clock;
|
|
55
|
+
return new Timer();
|
|
56
|
+
}
|
|
51
57
|
const _meshLODWorldBox = new Box3();
|
|
52
58
|
const _meshLODProjectedBox = new Box3();
|
|
53
59
|
const _meshLODCameraSpaceBox = new Box3();
|
|
@@ -82,8 +88,10 @@ export function calculateMeshLODLevel(options) {
|
|
|
82
88
|
result.screenCoverage = 0;
|
|
83
89
|
result.screenspaceVolume.set(0, 0, 0);
|
|
84
90
|
result.centrality = 1;
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
// Note: we intentionally do NOT early-return when there are no mesh LODs.
|
|
92
|
+
// The screen coverage / screenspace volume computed below is also consumed by
|
|
93
|
+
// the texture LOD selection, which must keep working for meshes that only have
|
|
94
|
+
// texture LODs (no mesh LODs). Only the mesh LOD-level selection loop is skipped.
|
|
87
95
|
let boundingBox = options.boundingBox ?? geometry.boundingBox;
|
|
88
96
|
if (!boundingBox) {
|
|
89
97
|
geometry.computeBoundingBox();
|
|
@@ -159,16 +167,18 @@ export function calculateMeshLODLevel(options) {
|
|
|
159
167
|
debugDrawLine(_meshLODCorner1, _meshLODCorner3, 0x0000ff);
|
|
160
168
|
debugDrawLine(_meshLODCorner2, _meshLODCorner3, 0x0000ff);
|
|
161
169
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
if (meshLods?.length) {
|
|
171
|
+
for (let i = 0; i < meshLods.length; i++) {
|
|
172
|
+
const lod = meshLods[i];
|
|
173
|
+
const density = lod.densities?.[primitiveIndex] || lod.density || .00001;
|
|
174
|
+
if (primitiveIndex > 0 && warnMissingPrimitiveDensities && isDevelopmentServer() && !lod.densities && !globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]) {
|
|
175
|
+
globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"] = true;
|
|
176
|
+
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.`);
|
|
177
|
+
}
|
|
178
|
+
if (density / screenCoverage < desiredDensity) {
|
|
179
|
+
result.level = i;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
172
182
|
}
|
|
173
183
|
}
|
|
174
184
|
return result;
|
|
@@ -219,6 +229,10 @@ export class LODsManager {
|
|
|
219
229
|
if (index >= 0)
|
|
220
230
|
plugins.splice(index, 1);
|
|
221
231
|
}
|
|
232
|
+
/** Read-only snapshot of the currently registered plugins, for inspection. Use {@link addPlugin} / {@link removePlugin} to modify the registry. */
|
|
233
|
+
static getPlugins() {
|
|
234
|
+
return plugins;
|
|
235
|
+
}
|
|
222
236
|
/**
|
|
223
237
|
* Gets the LODsManager for the given renderer. If the LODsManager does not exist yet, it will be created.
|
|
224
238
|
* @param renderer The renderer to get the LODsManager for.
|
|
@@ -390,11 +404,11 @@ export class LODsManager {
|
|
|
390
404
|
// })
|
|
391
405
|
}
|
|
392
406
|
#originalRender;
|
|
393
|
-
#clock = new Clock();
|
|
394
407
|
#frame = 0;
|
|
395
408
|
#delta = 0;
|
|
396
409
|
#time = 0;
|
|
397
410
|
#fps = 0;
|
|
411
|
+
#clock = createLODTimer();
|
|
398
412
|
_fpsBuffer = [60, 60, 60, 60, 60];
|
|
399
413
|
/**
|
|
400
414
|
* Enable the LODsManager. This will replace the render method of the renderer with a method that updates the LODs.
|
|
@@ -416,7 +430,8 @@ export class LODsManager {
|
|
|
416
430
|
if (renderTarget == null || ("isXRRenderTarget" in renderTarget && renderTarget.isXRRenderTarget)) {
|
|
417
431
|
stack = 0;
|
|
418
432
|
self.#frame += 1;
|
|
419
|
-
self.#
|
|
433
|
+
self.#clock.update?.();
|
|
434
|
+
self.#delta = Math.max(self.#clock.getDelta(), 1 / 1000);
|
|
420
435
|
self.#time += self.#delta;
|
|
421
436
|
self._fpsBuffer.shift();
|
|
422
437
|
self._fpsBuffer.push(1 / self.#delta);
|
|
@@ -457,7 +472,9 @@ export class LODsManager {
|
|
|
457
472
|
onAfterRender(scene, camera, _stack) {
|
|
458
473
|
if (this.pause)
|
|
459
474
|
return;
|
|
460
|
-
const renderList = this.
|
|
475
|
+
const renderList = this.getRenderList(scene, camera, _stack);
|
|
476
|
+
if (!renderList)
|
|
477
|
+
return;
|
|
461
478
|
const opaque = renderList.opaque;
|
|
462
479
|
let updateLODs = true;
|
|
463
480
|
// check if we're rendering a postprocessing pass
|
|
@@ -514,7 +531,9 @@ export class LODsManager {
|
|
|
514
531
|
* Update LODs in a scene
|
|
515
532
|
*/
|
|
516
533
|
internalUpdate(scene, camera) {
|
|
517
|
-
const renderList = this.
|
|
534
|
+
const renderList = this.getRenderList(scene, camera, 0);
|
|
535
|
+
if (!renderList)
|
|
536
|
+
return;
|
|
518
537
|
const opaque = renderList.opaque;
|
|
519
538
|
this.projectionScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
|
|
520
539
|
const desiredDensity = this.targetTriangleDensity;
|
|
@@ -560,6 +579,30 @@ export class LODsManager {
|
|
|
560
579
|
}
|
|
561
580
|
}
|
|
562
581
|
}
|
|
582
|
+
getRenderList(scene, camera, stack) {
|
|
583
|
+
const renderer = this.renderer;
|
|
584
|
+
let renderList = null;
|
|
585
|
+
if (renderer.isWebGPURenderer === true) {
|
|
586
|
+
const renderLists = renderer._renderLists;
|
|
587
|
+
if (!renderLists)
|
|
588
|
+
return null;
|
|
589
|
+
renderList = renderLists.get(scene, camera);
|
|
590
|
+
}
|
|
591
|
+
else if (renderer.isWebGLRenderer === true) {
|
|
592
|
+
const renderLists = renderer.renderLists;
|
|
593
|
+
if (!renderLists)
|
|
594
|
+
return null;
|
|
595
|
+
renderList = renderLists.get(scene, stack);
|
|
596
|
+
}
|
|
597
|
+
if (!renderList)
|
|
598
|
+
return null;
|
|
599
|
+
return {
|
|
600
|
+
opaque: renderList.opaque || [],
|
|
601
|
+
transparent: renderList.transparent || [],
|
|
602
|
+
transmissive: renderList.transmissive || renderList.transparentDoublePass || [],
|
|
603
|
+
transparentDoublePass: renderList.transparentDoublePass || [],
|
|
604
|
+
};
|
|
605
|
+
}
|
|
563
606
|
/** Update the LOD levels for the renderer. */
|
|
564
607
|
updateLODs(scene, camera, object, desiredDensity) {
|
|
565
608
|
if (!object.userData) {
|
|
@@ -820,7 +863,8 @@ export class LODsManager {
|
|
|
820
863
|
if (this.context?.engine === "model-viewer") {
|
|
821
864
|
factor *= 1.5;
|
|
822
865
|
}
|
|
823
|
-
const
|
|
866
|
+
const devicePixelRatio = this.renderer.getPixelRatio?.() || globalThis.devicePixelRatio || 1;
|
|
867
|
+
const screenSize = canvasHeight / devicePixelRatio;
|
|
824
868
|
const pixelSizeOnScreen = screenSize * factor;
|
|
825
869
|
let foundLod = false;
|
|
826
870
|
for (let i = texture_lods_minmax.lods.length - 1; i >= 0; i--) {
|
package/lib/utils.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const isSSR: boolean;
|
|
|
5
5
|
* @param obj the object to get the raycast mesh from
|
|
6
6
|
* @returns the raycast mesh or null if not set
|
|
7
7
|
*/
|
|
8
|
-
export declare function getRaycastMesh(obj: Object3D): BufferGeometry<any> | null;
|
|
8
|
+
export declare function getRaycastMesh(obj: Object3D): BufferGeometry<any, any> | null;
|
|
9
9
|
/**
|
|
10
10
|
* Set the raycast mesh for an object.
|
|
11
11
|
* The raycast mesh is a low poly version of the mesh used for raycasting. It is set when a mesh that has LOD level with more vertices is discovered for the first time
|
package/lib/utils.internal.d.ts
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { Texture } from "three";
|
|
2
|
+
/** Represents the possible shapes of texture image/source data in three.js.
|
|
3
|
+
* Source.data is typed as `{}` in r183 but at runtime can be ImageBitmap, HTMLImageElement, etc. */
|
|
4
|
+
export type TextureImageData = {
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
depth?: number;
|
|
8
|
+
data?: ArrayBufferView | null;
|
|
9
|
+
};
|
|
10
|
+
/** Check if a value has image-like dimensions (width/height) */
|
|
11
|
+
export declare function hasImageDimensions(value: unknown): value is {
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
};
|
|
15
|
+
/** Check if a value has pixel data (e.g. typed array from a DataTexture) */
|
|
16
|
+
export declare function hasPixelData(value: unknown): value is {
|
|
17
|
+
data: ArrayBufferView;
|
|
18
|
+
};
|
|
19
|
+
/** Get the source data of a texture, typed for dimension/data access */
|
|
20
|
+
export declare function getSourceData(tex: Texture): TextureImageData | null;
|
|
21
|
+
/** Get the image of a texture, typed for dimension/data access.
|
|
22
|
+
* In r183, Texture.image is typed as `{}` but at runtime is an ImageBitmap, HTMLImageElement, etc. */
|
|
23
|
+
export declare function getTextureImage(tex: Texture): TextureImageData | null;
|
|
24
|
+
/** Get width/height of a texture from image or source data */
|
|
25
|
+
export declare function getTextureDimensions(tex: Texture): {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
};
|
|
2
29
|
export declare function isDebugMode(): string | boolean;
|
|
3
30
|
export declare function getParam(name: string): boolean | string;
|
|
4
31
|
export declare function resolveUrl(source: string | undefined, uri: string): string;
|
package/lib/utils.internal.js
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
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;
|
|
4
33
|
}
|
|
5
34
|
export function getParam(name) {
|
|
6
|
-
|
|
35
|
+
const href = globalThis.location?.href;
|
|
36
|
+
if (!href)
|
|
7
37
|
return false;
|
|
8
|
-
const url = new URL(
|
|
38
|
+
const url = new URL(href);
|
|
9
39
|
const param = url.searchParams.get(name);
|
|
10
40
|
if (param == null || param === "0" || param === "false")
|
|
11
41
|
return false;
|
|
@@ -44,7 +74,8 @@ export function resolveUrl(source, uri) {
|
|
|
44
74
|
export function isMobileDevice() {
|
|
45
75
|
if (_ismobile !== undefined)
|
|
46
76
|
return _ismobile;
|
|
47
|
-
|
|
77
|
+
const userAgent = globalThis.navigator?.userAgent || "";
|
|
78
|
+
_ismobile = /iPhone|iPad|iPod|Android|IEMobile/i.test(userAgent);
|
|
48
79
|
if (getParam("debugprogressive"))
|
|
49
80
|
console.log("[glTF Progressive]: isMobileDevice", _ismobile);
|
|
50
81
|
return _ismobile;
|
|
@@ -55,9 +86,10 @@ let _ismobile;
|
|
|
55
86
|
* @returns `true` if we are running in a development server (localhost or ip address).
|
|
56
87
|
*/
|
|
57
88
|
export function isDevelopmentServer() {
|
|
58
|
-
|
|
89
|
+
const href = globalThis.location?.href;
|
|
90
|
+
if (!href)
|
|
59
91
|
return false;
|
|
60
|
-
const url = new URL(
|
|
92
|
+
const url = new URL(href);
|
|
61
93
|
const isLocalhostOrIpAddress = url.hostname === "localhost" || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(url.hostname);
|
|
62
94
|
const isDevelopment = url.hostname === "127.0.0.1" || isLocalhostOrIpAddress;
|
|
63
95
|
return isDevelopment;
|
|
@@ -74,8 +106,14 @@ export class PromiseQueue {
|
|
|
74
106
|
constructor(maxConcurrent, opts = {}) {
|
|
75
107
|
this.maxConcurrent = maxConcurrent;
|
|
76
108
|
this.debug = opts.debug ?? false;
|
|
77
|
-
|
|
78
|
-
|
|
109
|
+
// Dedicated workers can have requestAnimationFrame when they are owned by a window.
|
|
110
|
+
// Other worker-like scopes do not, so keep the frame-based tick when available and fall back to timers otherwise.
|
|
111
|
+
if (typeof globalThis.requestAnimationFrame === "function") {
|
|
112
|
+
globalThis.requestAnimationFrame(this.tick);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
setTimeout(this.tick, 0);
|
|
116
|
+
}
|
|
79
117
|
}
|
|
80
118
|
tick = () => {
|
|
81
119
|
this.internalUpdate();
|
|
@@ -118,9 +156,10 @@ export class PromiseQueue {
|
|
|
118
156
|
}
|
|
119
157
|
// #region Texture Memory
|
|
120
158
|
export function determineTextureMemoryInBytes(texture) {
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
159
|
+
const img = texture.image;
|
|
160
|
+
const width = img?.width ?? 0;
|
|
161
|
+
const height = img?.height ?? 0;
|
|
162
|
+
const depth = img?.depth ?? 1;
|
|
124
163
|
const mipLevels = Math.floor(Math.log2(Math.max(width, height, depth))) + 1;
|
|
125
164
|
const bytesPerPixel = getBytesPerPixel(texture);
|
|
126
165
|
const totalBytes = (width * height * depth * bytesPerPixel * (1 - Math.pow(0.25, mipLevels))) / (1 - 0.25);
|
|
@@ -130,22 +169,22 @@ function getBytesPerPixel(texture) {
|
|
|
130
169
|
// Determine channel count from format
|
|
131
170
|
let channels = 4; // Default RGBA
|
|
132
171
|
const format = texture.format;
|
|
133
|
-
if (format ===
|
|
134
|
-
channels = 1;
|
|
135
|
-
else if (format ===
|
|
136
|
-
channels = 1;
|
|
137
|
-
else if (format ===
|
|
138
|
-
channels = 2;
|
|
139
|
-
else if (format ===
|
|
140
|
-
channels = 2;
|
|
141
|
-
else if (format ===
|
|
142
|
-
channels = 3;
|
|
172
|
+
if (format === RedFormat)
|
|
173
|
+
channels = 1;
|
|
174
|
+
else if (format === RedIntegerFormat)
|
|
175
|
+
channels = 1;
|
|
176
|
+
else if (format === RGFormat)
|
|
177
|
+
channels = 2;
|
|
178
|
+
else if (format === RGIntegerFormat)
|
|
179
|
+
channels = 2;
|
|
180
|
+
else if (format === RGBFormat)
|
|
181
|
+
channels = 3;
|
|
143
182
|
else if (format === 1029)
|
|
144
|
-
channels = 3; // RGBIntegerFormat
|
|
145
|
-
else if (format ===
|
|
146
|
-
channels = 4;
|
|
147
|
-
else if (format ===
|
|
148
|
-
channels = 4;
|
|
183
|
+
channels = 3; // RGBIntegerFormat (not exported in r183)
|
|
184
|
+
else if (format === RGBAFormat)
|
|
185
|
+
channels = 4;
|
|
186
|
+
else if (format === RGBAIntegerFormat)
|
|
187
|
+
channels = 4;
|
|
149
188
|
// Determine bytes per channel from type
|
|
150
189
|
let bytesPerChannel = 1; // UnsignedByteType default
|
|
151
190
|
const type = texture.type;
|
|
@@ -177,6 +216,10 @@ export function detectGPUMemory() {
|
|
|
177
216
|
if (rendererInfo !== undefined) {
|
|
178
217
|
return rendererInfo?.estimatedMemory;
|
|
179
218
|
}
|
|
219
|
+
if (typeof document === "undefined") {
|
|
220
|
+
rendererInfo = null;
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
180
223
|
const canvas = document.createElement('canvas');
|
|
181
224
|
const powerPreference = "high-performance";
|
|
182
225
|
const gl = canvas.getContext('webgl', { powerPreference }) || canvas.getContext('experimental-webgl', { powerPreference });
|
package/lib/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Box3, BufferAttribute, BufferGeometry, CompressedTexture, InterleavedBuffer, InterleavedBufferAttribute, Matrix3, Sphere, Texture, Vector3 } from "three";
|
|
2
2
|
import { createLoaders, GET_LOADER_LOCATION_CONFIG } from "../loaders.js";
|
|
3
|
-
import { isMobileDevice } from "../utils.internal.js";
|
|
3
|
+
import { getTextureDimensions, isMobileDevice } from "../utils.internal.js";
|
|
4
4
|
import { debug } from "../lods.debug.js";
|
|
5
5
|
const workers = new Array();
|
|
6
6
|
let getWorkerId = 0;
|
|
@@ -51,7 +51,10 @@ class GLTFLoaderWorker {
|
|
|
51
51
|
url = URL.createObjectURL(new Blob([url]));
|
|
52
52
|
}
|
|
53
53
|
else if (!url.startsWith("blob:") && !url.startsWith("http:") && !url.startsWith("https:")) {
|
|
54
|
-
|
|
54
|
+
const base = globalThis.location?.href;
|
|
55
|
+
if (base) {
|
|
56
|
+
url = new URL(url, base).toString();
|
|
57
|
+
}
|
|
55
58
|
}
|
|
56
59
|
const options = {
|
|
57
60
|
type: "load",
|
|
@@ -152,8 +155,7 @@ function processReceivedData(data) {
|
|
|
152
155
|
let newTexture = null;
|
|
153
156
|
if (texture.isCompressedTexture) {
|
|
154
157
|
const mipmaps = texture.mipmaps;
|
|
155
|
-
const width
|
|
156
|
-
const height = texture.image?.height || texture.source?.data?.height || -1;
|
|
158
|
+
const { width, height } = getTextureDimensions(texture);
|
|
157
159
|
newTexture = new CompressedTexture(mipmaps, width, height, texture.format, texture.type, texture.mapping, texture.wrapS, texture.wrapT, texture.magFilter, texture.minFilter, texture.anisotropy, texture.colorSpace);
|
|
158
160
|
}
|
|
159
161
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-progressive",
|
|
3
|
-
"version": "3.6.0-
|
|
3
|
+
"version": "3.6.0-beta.1",
|
|
4
4
|
"description": "three.js support for loading glTF or GLB files that contain progressive loading data",
|
|
5
5
|
"homepage": "https://needle.tools",
|
|
6
6
|
"author": {
|
|
@@ -59,10 +59,13 @@
|
|
|
59
59
|
"three": ">= 0.160.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
+
"@needle-tools/engine": "^5.1.0-alpha.6",
|
|
63
|
+
"@needle-tools/three-test-matrix": "^0.1.0",
|
|
62
64
|
"@stylistic/eslint-plugin-ts": "^1.5.4",
|
|
63
|
-
"@types/three": "0.
|
|
65
|
+
"@types/three": "0.183.1",
|
|
64
66
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
|
65
67
|
"@typescript-eslint/parser": "^6.2.0",
|
|
68
|
+
"@vitest/browser-playwright": "^4.1.8",
|
|
66
69
|
"eslint": "^8.56.0",
|
|
67
70
|
"eslint-plugin-import": "^2.29.1",
|
|
68
71
|
"eslint-plugin-no-secrets": "^0.8.9",
|
|
@@ -71,8 +74,10 @@
|
|
|
71
74
|
"eslint-plugin-xss": "^0.1.12",
|
|
72
75
|
"nodemon": "^3.1.4",
|
|
73
76
|
"npm-watch": "^0.13.0",
|
|
77
|
+
"playwright": "^1.60.0",
|
|
74
78
|
"three": ">= 0.160.0",
|
|
75
|
-
"vite": "7"
|
|
79
|
+
"vite": "7",
|
|
80
|
+
"vitest": "^4.1.8"
|
|
76
81
|
},
|
|
77
82
|
"types": "./lib/index.d.ts"
|
|
78
83
|
}
|