@needle-tools/gltf-progressive 4.0.0-alpha → 4.0.0-alpha.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/gltf-progressive.js +1568 -0
- package/gltf-progressive.min.js +10 -0
- package/gltf-progressive.umd.cjs +10 -0
- package/lib/extension.d.ts +142 -0
- package/lib/extension.js +1133 -0
- package/lib/extension.model.d.ts +33 -0
- package/lib/extension.model.js +1 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.js +87 -0
- package/lib/loaders.d.ts +41 -0
- package/lib/loaders.js +162 -0
- package/lib/lods.debug.d.ts +4 -0
- package/lib/lods.debug.js +43 -0
- package/lib/lods.manager.d.ts +165 -0
- package/lib/lods.manager.js +749 -0
- package/lib/lods.promise.d.ts +68 -0
- package/lib/lods.promise.js +108 -0
- package/lib/plugins/index.d.ts +2 -0
- package/lib/plugins/index.js +1 -0
- package/lib/plugins/modelviewer.d.ts +1 -0
- package/lib/plugins/modelviewer.js +223 -0
- package/lib/plugins/plugin.d.ts +23 -0
- package/lib/plugins/plugin.js +5 -0
- package/lib/utils.d.ts +30 -0
- package/lib/utils.internal.d.ts +68 -0
- package/lib/utils.internal.js +239 -0
- package/lib/utils.js +82 -0
- package/lib/version.d.ts +1 -0
- package/lib/version.js +4 -0
- package/lib/worker/gltf-progressive.worker.js +165 -0
- package/lib/worker/loader.mainthread.d.ts +45 -0
- package/lib/worker/loader.mainthread.js +192 -0
- package/package.json +4 -17
|
@@ -0,0 +1,239 @@
|
|
|
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
|
+
}
|
|
30
|
+
const debug = getParam("debugprogressive");
|
|
31
|
+
export function isDebugMode() {
|
|
32
|
+
return debug;
|
|
33
|
+
}
|
|
34
|
+
export function getParam(name) {
|
|
35
|
+
if (typeof window === "undefined")
|
|
36
|
+
return false;
|
|
37
|
+
const url = new URL(window.location.href);
|
|
38
|
+
const param = url.searchParams.get(name);
|
|
39
|
+
if (param == null || param === "0" || param === "false")
|
|
40
|
+
return false;
|
|
41
|
+
if (param === "")
|
|
42
|
+
return true;
|
|
43
|
+
return param;
|
|
44
|
+
}
|
|
45
|
+
export function resolveUrl(source, uri) {
|
|
46
|
+
if (uri === undefined || source === undefined) {
|
|
47
|
+
return uri;
|
|
48
|
+
}
|
|
49
|
+
if (uri.startsWith("./") ||
|
|
50
|
+
uri.startsWith("http") ||
|
|
51
|
+
uri.startsWith("data:") ||
|
|
52
|
+
uri.startsWith("blob:")) {
|
|
53
|
+
return uri;
|
|
54
|
+
}
|
|
55
|
+
// TODO: why not just use new URL(uri, source).href ?
|
|
56
|
+
const pathIndex = source.lastIndexOf("/");
|
|
57
|
+
if (pathIndex >= 0) {
|
|
58
|
+
// Take the source uri as the base path
|
|
59
|
+
const basePath = source.substring(0, pathIndex + 1);
|
|
60
|
+
// make sure we don't have double slashes
|
|
61
|
+
while (basePath.endsWith("/") && uri.startsWith("/"))
|
|
62
|
+
uri = uri.substring(1);
|
|
63
|
+
// Append the relative uri
|
|
64
|
+
const newUri = basePath + uri;
|
|
65
|
+
// newUri = new URL(newUri, globalThis.location.href).href;
|
|
66
|
+
return newUri;
|
|
67
|
+
}
|
|
68
|
+
return uri;
|
|
69
|
+
}
|
|
70
|
+
/** Check if the current device is a mobile device.
|
|
71
|
+
* @returns `true` if it's a phone or tablet
|
|
72
|
+
*/
|
|
73
|
+
export function isMobileDevice() {
|
|
74
|
+
if (_ismobile !== undefined)
|
|
75
|
+
return _ismobile;
|
|
76
|
+
_ismobile = /iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent);
|
|
77
|
+
if (getParam("debugprogressive"))
|
|
78
|
+
console.log("[glTF Progressive]: isMobileDevice", _ismobile);
|
|
79
|
+
return _ismobile;
|
|
80
|
+
}
|
|
81
|
+
let _ismobile;
|
|
82
|
+
/**
|
|
83
|
+
* Check if we are running in a development server (localhost or ip address).
|
|
84
|
+
* @returns `true` if we are running in a development server (localhost or ip address).
|
|
85
|
+
*/
|
|
86
|
+
export function isDevelopmentServer() {
|
|
87
|
+
if (typeof window === "undefined")
|
|
88
|
+
return false;
|
|
89
|
+
const url = new URL(window.location.href);
|
|
90
|
+
const isLocalhostOrIpAddress = url.hostname === "localhost" || /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(url.hostname);
|
|
91
|
+
const isDevelopment = url.hostname === "127.0.0.1" || isLocalhostOrIpAddress;
|
|
92
|
+
return isDevelopment;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A promise queue that limits the number of concurrent promises.
|
|
96
|
+
* Use the `slot` method to request a slot for a promise with a specific key. The returned promise resolves to an object with a `use` method that can be called to add the promise to the queue.
|
|
97
|
+
*/
|
|
98
|
+
export class PromiseQueue {
|
|
99
|
+
maxConcurrent;
|
|
100
|
+
_running = new Map();
|
|
101
|
+
_queue = [];
|
|
102
|
+
debug = false;
|
|
103
|
+
constructor(maxConcurrent, opts = {}) {
|
|
104
|
+
this.maxConcurrent = maxConcurrent;
|
|
105
|
+
this.debug = opts.debug ?? false;
|
|
106
|
+
window.requestAnimationFrame(this.tick);
|
|
107
|
+
}
|
|
108
|
+
tick = () => {
|
|
109
|
+
this.internalUpdate();
|
|
110
|
+
setTimeout(this.tick, 10);
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* 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.
|
|
114
|
+
*/
|
|
115
|
+
slot(key) {
|
|
116
|
+
if (this.debug)
|
|
117
|
+
console.debug(`[PromiseQueue]: Requesting slot for key ${key}, running: ${this._running.size}, waiting: ${this._queue.length}`);
|
|
118
|
+
return new Promise((resolve) => {
|
|
119
|
+
this._queue.push({ key, resolve });
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
add(key, promise) {
|
|
123
|
+
if (this._running.has(key))
|
|
124
|
+
return;
|
|
125
|
+
this._running.set(key, promise);
|
|
126
|
+
promise.finally(() => {
|
|
127
|
+
this._running.delete(key);
|
|
128
|
+
if (this.debug)
|
|
129
|
+
console.debug(`[PromiseQueue]: Promise finished now running: ${this._running.size}, waiting: ${this._queue.length}. (finished ${key})`);
|
|
130
|
+
});
|
|
131
|
+
if (this.debug)
|
|
132
|
+
console.debug(`[PromiseQueue]: Added new promise, now running: ${this._running.size}, waiting: ${this._queue.length}. (added ${key})`);
|
|
133
|
+
}
|
|
134
|
+
internalUpdate() {
|
|
135
|
+
// Run for as many free slots as we can
|
|
136
|
+
const diff = this.maxConcurrent - this._running.size;
|
|
137
|
+
for (let i = 0; i < diff && this._queue.length > 0; i++) {
|
|
138
|
+
if (this.debug)
|
|
139
|
+
console.debug(`[PromiseQueue]: Running ${this._running.size} promises, waiting for ${this._queue.length} more.`);
|
|
140
|
+
const { key, resolve } = this._queue.shift();
|
|
141
|
+
resolve({
|
|
142
|
+
use: (promise) => this.add(key, promise)
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// #region Texture Memory
|
|
148
|
+
export function determineTextureMemoryInBytes(texture) {
|
|
149
|
+
const img = texture.image;
|
|
150
|
+
const width = img?.width ?? 0;
|
|
151
|
+
const height = img?.height ?? 0;
|
|
152
|
+
const depth = img?.depth ?? 1;
|
|
153
|
+
const mipLevels = Math.floor(Math.log2(Math.max(width, height, depth))) + 1;
|
|
154
|
+
const bytesPerPixel = getBytesPerPixel(texture);
|
|
155
|
+
const totalBytes = (width * height * depth * bytesPerPixel * (1 - Math.pow(0.25, mipLevels))) / (1 - 0.25);
|
|
156
|
+
return totalBytes;
|
|
157
|
+
}
|
|
158
|
+
function getBytesPerPixel(texture) {
|
|
159
|
+
// Determine channel count from format
|
|
160
|
+
let channels = 4; // Default RGBA
|
|
161
|
+
const format = texture.format;
|
|
162
|
+
if (format === RedFormat)
|
|
163
|
+
channels = 1;
|
|
164
|
+
else if (format === RedIntegerFormat)
|
|
165
|
+
channels = 1;
|
|
166
|
+
else if (format === RGFormat)
|
|
167
|
+
channels = 2;
|
|
168
|
+
else if (format === RGIntegerFormat)
|
|
169
|
+
channels = 2;
|
|
170
|
+
else if (format === RGBFormat)
|
|
171
|
+
channels = 3;
|
|
172
|
+
else if (format === 1029)
|
|
173
|
+
channels = 3; // RGBIntegerFormat (not exported in r183)
|
|
174
|
+
else if (format === RGBAFormat)
|
|
175
|
+
channels = 4;
|
|
176
|
+
else if (format === RGBAIntegerFormat)
|
|
177
|
+
channels = 4;
|
|
178
|
+
// Determine bytes per channel from type
|
|
179
|
+
let bytesPerChannel = 1; // UnsignedByteType default
|
|
180
|
+
const type = texture.type;
|
|
181
|
+
if (type === 1009)
|
|
182
|
+
bytesPerChannel = 1; // UnsignedByteType
|
|
183
|
+
else if (type === 1010)
|
|
184
|
+
bytesPerChannel = 1; // ByteType
|
|
185
|
+
else if (type === 1011)
|
|
186
|
+
bytesPerChannel = 2; // ShortType
|
|
187
|
+
else if (type === 1012)
|
|
188
|
+
bytesPerChannel = 2; // UnsignedShortType
|
|
189
|
+
else if (type === 1013)
|
|
190
|
+
bytesPerChannel = 4; // IntType
|
|
191
|
+
else if (type === 1014)
|
|
192
|
+
bytesPerChannel = 4; // UnsignedIntType
|
|
193
|
+
else if (type === 1015)
|
|
194
|
+
bytesPerChannel = 4; // FloatType
|
|
195
|
+
else if (type === 1016)
|
|
196
|
+
bytesPerChannel = 2; // HalfFloatType
|
|
197
|
+
const bytesPerPixel = channels * bytesPerChannel;
|
|
198
|
+
return bytesPerPixel;
|
|
199
|
+
}
|
|
200
|
+
// #region GPU
|
|
201
|
+
let rendererInfo;
|
|
202
|
+
/**
|
|
203
|
+
* Detect the GPU memory of the current device. This is a very rough estimate based on the renderer information, and may not be accurate. It returns the estimated memory in MB, or `undefined` if it cannot be detected.
|
|
204
|
+
*/
|
|
205
|
+
export function detectGPUMemory() {
|
|
206
|
+
if (rendererInfo !== undefined) {
|
|
207
|
+
return rendererInfo?.estimatedMemory;
|
|
208
|
+
}
|
|
209
|
+
const canvas = document.createElement('canvas');
|
|
210
|
+
const powerPreference = "high-performance";
|
|
211
|
+
const gl = canvas.getContext('webgl', { powerPreference }) || canvas.getContext('experimental-webgl', { powerPreference });
|
|
212
|
+
if (!gl) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
if ("getExtension" in gl) {
|
|
216
|
+
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
217
|
+
if (debugInfo) {
|
|
218
|
+
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
|
|
219
|
+
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
220
|
+
// Estimate memory based on renderer information (this is a very rough estimate)
|
|
221
|
+
let estimatedMemory = 512;
|
|
222
|
+
if (/NVIDIA/i.test(renderer)) {
|
|
223
|
+
estimatedMemory = 2048;
|
|
224
|
+
}
|
|
225
|
+
else if (/AMD/i.test(renderer)) {
|
|
226
|
+
estimatedMemory = 1024;
|
|
227
|
+
}
|
|
228
|
+
else if (/Intel/i.test(renderer)) {
|
|
229
|
+
estimatedMemory = 512;
|
|
230
|
+
}
|
|
231
|
+
rendererInfo = { vendor, renderer, estimatedMemory };
|
|
232
|
+
return estimatedMemory;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
rendererInfo = null;
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { BufferGeometry, Mesh } from "three";
|
|
2
|
+
export const isSSR = typeof window === "undefined" && typeof document === "undefined";
|
|
3
|
+
const $raycastmesh = Symbol("needle:raycast-mesh");
|
|
4
|
+
/**
|
|
5
|
+
* 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
|
|
6
|
+
* @param obj the object to get the raycast mesh from
|
|
7
|
+
* @returns the raycast mesh or null if not set
|
|
8
|
+
*/
|
|
9
|
+
export function getRaycastMesh(obj) {
|
|
10
|
+
if (obj?.[$raycastmesh] instanceof BufferGeometry) {
|
|
11
|
+
return obj[$raycastmesh];
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Set the raycast mesh for an object.
|
|
17
|
+
* 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
|
|
18
|
+
* @param obj the object to set the raycast mesh for
|
|
19
|
+
* @param geom the raycast mesh
|
|
20
|
+
*/
|
|
21
|
+
export function registerRaycastMesh(obj, geom) {
|
|
22
|
+
if (obj.type === "Mesh" || obj.type === "SkinnedMesh") {
|
|
23
|
+
const existing = getRaycastMesh(obj);
|
|
24
|
+
if (!existing) {
|
|
25
|
+
const clone = shallowCloneGeometry(geom);
|
|
26
|
+
// remove LODs userdata to not update the geometry if the raycast mesh is rendered in the scene
|
|
27
|
+
clone.userData = { isRaycastMesh: true };
|
|
28
|
+
obj[$raycastmesh] = clone;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Call this method to enable raycasting with the lowpoly raycast meshes (if available) for all meshes in the scene.
|
|
34
|
+
* This is useful for performance optimization when the scene contains high poly meshes that are not visible to the camera.
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* // call to enable raycasting with low poly raycast meshes
|
|
38
|
+
* useRaycastMeshes();
|
|
39
|
+
*
|
|
40
|
+
* // then use the raycaster as usual
|
|
41
|
+
* const raycaster = new Raycaster();
|
|
42
|
+
* raycaster.setFromCamera(mouse, camera);
|
|
43
|
+
* const intersects = raycaster.intersectObjects(scene.children, true);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function useRaycastMeshes(enabled = true) {
|
|
47
|
+
if (enabled) {
|
|
48
|
+
// if the method is already patched we don't need to do it again
|
|
49
|
+
if (_originalRaycastMethod)
|
|
50
|
+
return;
|
|
51
|
+
const originalMethod = _originalRaycastMethod = Mesh.prototype.raycast;
|
|
52
|
+
Mesh.prototype.raycast = function (raycaster, intersects) {
|
|
53
|
+
const self = this;
|
|
54
|
+
const raycastMesh = getRaycastMesh(self);
|
|
55
|
+
let prevGeomtry;
|
|
56
|
+
if (raycastMesh && self.isMesh) {
|
|
57
|
+
prevGeomtry = self.geometry;
|
|
58
|
+
self.geometry = raycastMesh;
|
|
59
|
+
}
|
|
60
|
+
originalMethod.call(this, raycaster, intersects);
|
|
61
|
+
if (prevGeomtry) {
|
|
62
|
+
self.geometry = prevGeomtry;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (!_originalRaycastMethod)
|
|
68
|
+
return;
|
|
69
|
+
Mesh.prototype.raycast = _originalRaycastMethod;
|
|
70
|
+
_originalRaycastMethod = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let _originalRaycastMethod = null;
|
|
74
|
+
/** Creates a clone without copying the data */
|
|
75
|
+
function shallowCloneGeometry(geom) {
|
|
76
|
+
const clone = new BufferGeometry();
|
|
77
|
+
for (const key in geom.attributes) {
|
|
78
|
+
clone.setAttribute(key, geom.getAttribute(key));
|
|
79
|
+
}
|
|
80
|
+
clone.setIndex(geom.getIndex());
|
|
81
|
+
return clone;
|
|
82
|
+
}
|
package/lib/version.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const version = "";
|
package/lib/version.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// import { GLTFLoader } from "https://cdn.jsdelivr.net/npm/three@0.179.1/examples/jsm/loaders/GLTFLoader.js";
|
|
2
|
+
|
|
3
|
+
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';
|
|
4
|
+
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
5
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
6
|
+
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
let debug = false;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {import("./loader.mainthread").GLTFLoaderWorker_Message} GLTFLoaderWorker_Message
|
|
15
|
+
**/
|
|
16
|
+
|
|
17
|
+
self.onmessage = (msg) => {
|
|
18
|
+
/** @type {GLTFLoaderWorker_Message} */
|
|
19
|
+
const request = msg.data;
|
|
20
|
+
|
|
21
|
+
switch (request.type) {
|
|
22
|
+
case "init":
|
|
23
|
+
break;
|
|
24
|
+
case "load":
|
|
25
|
+
loadGLTF(request);
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
console.error("[Worker] Unknown message type:", request.type);
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
self.onerror = (error) => {
|
|
34
|
+
console.error("[Worker] Error:", error);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {GLTFLoaderWorker_Message} data
|
|
39
|
+
*/
|
|
40
|
+
function postMessage(data) {
|
|
41
|
+
self.postMessage(data);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @type {GLTFLoader | null} */
|
|
45
|
+
let loader = null;
|
|
46
|
+
|
|
47
|
+
/** @type {DRACOLoader | null} */
|
|
48
|
+
let dracoLoader = null;
|
|
49
|
+
|
|
50
|
+
/** @type {KTX2Loader | null } */
|
|
51
|
+
let ktx2Loader = null;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {GLTFLoaderWorker_Message & { type: "load"}} req
|
|
55
|
+
*/
|
|
56
|
+
async function loadGLTF(req) {
|
|
57
|
+
if(debug) console.debug("[Worker] Loading GLTF from URL:", req.dracoDecoderPath);
|
|
58
|
+
|
|
59
|
+
loader ??= new GLTFLoader();
|
|
60
|
+
|
|
61
|
+
loader.setMeshoptDecoder(MeshoptDecoder);
|
|
62
|
+
|
|
63
|
+
dracoLoader ??= new DRACOLoader();
|
|
64
|
+
dracoLoader.setDecoderConfig({ type: 'js' });
|
|
65
|
+
dracoLoader.setDecoderPath(req.dracoDecoderPath);
|
|
66
|
+
loader.setDRACOLoader(dracoLoader);
|
|
67
|
+
|
|
68
|
+
ktx2Loader ??= new KTX2Loader();
|
|
69
|
+
ktx2Loader.workerConfig = req.ktx2LoaderConfig;
|
|
70
|
+
ktx2Loader.setTranscoderPath(req.ktx2TranscoderPath);
|
|
71
|
+
loader.setKTX2Loader(ktx2Loader);
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
loader.load(req.url, gltf => {
|
|
75
|
+
if(debug) console.log("Loaded", req.url, gltf);
|
|
76
|
+
|
|
77
|
+
/** @type {GLTFLoaderWorker_Message & { type: "loaded-gltf"}} */
|
|
78
|
+
const data = {
|
|
79
|
+
type: "loaded-gltf",
|
|
80
|
+
result: {
|
|
81
|
+
url: req.url,
|
|
82
|
+
geometries: [],
|
|
83
|
+
textures: [],
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
collectData(gltf, data);
|
|
87
|
+
postMessage(data);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {import("three/examples/jsm/loaders/GLTFLoader").GLTF} gltf
|
|
94
|
+
* @param {GLTFLoaderWorker_Message & { type: "loaded-gltf"}} data
|
|
95
|
+
**/
|
|
96
|
+
function collectData(gltf, data) {
|
|
97
|
+
|
|
98
|
+
const { result } = data;
|
|
99
|
+
|
|
100
|
+
for (const key of gltf.parser.associations.keys()) {
|
|
101
|
+
const cache = gltf.parser.associations.get(key);
|
|
102
|
+
|
|
103
|
+
if (!cache) {
|
|
104
|
+
if(debug) console.warn("[Worker] No cache found for key:", key);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if ("isTexture" in key && key.isTexture) {
|
|
109
|
+
const texture = /** @type {import("three").Texture} */ ( /** @type {unknown} */ (key));
|
|
110
|
+
const gltf_texture = gltf.parser.json.textures[cache.textures ?? -1];
|
|
111
|
+
result.textures.push({
|
|
112
|
+
texture: texture,
|
|
113
|
+
textureIndex: cache.textures ?? -1,
|
|
114
|
+
extensions: gltf_texture?.extensions ?? {},
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
else if ("isMesh" in key && key.isMesh) {
|
|
118
|
+
const mesh = /** @type {import("three").Mesh} */ ( /** @type {unknown} */ (key));
|
|
119
|
+
const meshIndex = cache.meshes ?? -1;
|
|
120
|
+
const primitiveIndex = cache.primitives ?? -1;
|
|
121
|
+
const gltf_mesh = gltf.parser.json.meshes[meshIndex];
|
|
122
|
+
result.geometries.push({
|
|
123
|
+
geometry: mesh.geometry,
|
|
124
|
+
meshIndex: meshIndex,
|
|
125
|
+
primitiveIndex: primitiveIndex,
|
|
126
|
+
extensions: gltf_mesh?.extensions ?? {},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else if ("isMaterial" in key && key.isMaterial) {
|
|
130
|
+
// Nothing we need to do here
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// function traverseAndDeleteFunctions(gltf) {
|
|
136
|
+
// const textures = [];
|
|
137
|
+
// gltf.traverse((child) => {
|
|
138
|
+
// if (child.isMesh) {
|
|
139
|
+
// geometries.push(child.geometry);
|
|
140
|
+
// if (child.material) {
|
|
141
|
+
// if (child.material.map) {
|
|
142
|
+
// textures.push(child.material.map);
|
|
143
|
+
// }
|
|
144
|
+
// }
|
|
145
|
+
// }
|
|
146
|
+
// });
|
|
147
|
+
// return {
|
|
148
|
+
// geometries: geometries,
|
|
149
|
+
// textures: textures,
|
|
150
|
+
// }
|
|
151
|
+
// }
|
|
152
|
+
|
|
153
|
+
// function traverseAndDeleteFunctions(obj, seen = new WeakSet()) {
|
|
154
|
+
// if (seen.has(obj)) return;
|
|
155
|
+
// seen.add(obj);
|
|
156
|
+
|
|
157
|
+
// for (const key in obj) {
|
|
158
|
+
// if (typeof obj[key] === "function") {
|
|
159
|
+
// delete obj[key];
|
|
160
|
+
// } else if (typeof obj[key] === "object" && obj[key] !== null) {
|
|
161
|
+
// traverseAndDeleteFunctions(obj[key], seen);
|
|
162
|
+
// }
|
|
163
|
+
// }
|
|
164
|
+
// return obj;
|
|
165
|
+
// }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BufferGeometry, Texture, WebGLRenderer } from "three";
|
|
2
|
+
import type { KTX2LoaderWorkerConfig } from "three/examples/jsm/loaders/KTX2Loader.js";
|
|
3
|
+
type GLTFLoaderWorkerOptions = {
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
};
|
|
6
|
+
type WorkerLoadResult = {
|
|
7
|
+
url: string;
|
|
8
|
+
geometries: Array<{
|
|
9
|
+
geometry: BufferGeometry;
|
|
10
|
+
meshIndex: number;
|
|
11
|
+
primitiveIndex: number;
|
|
12
|
+
extensions: Record<string, any>;
|
|
13
|
+
}>;
|
|
14
|
+
textures: Array<{
|
|
15
|
+
texture: Texture;
|
|
16
|
+
textureIndex: number;
|
|
17
|
+
extensions: Record<string, any>;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
export declare function getWorker(opts?: GLTFLoaderWorkerOptions): Promise<GLTFLoaderWorker>;
|
|
21
|
+
export type { GLTFLoaderWorker, GLTFLoaderWorkerOptions, WorkerLoadResult };
|
|
22
|
+
/** @internal */
|
|
23
|
+
export type GLTFLoaderWorker_Message = {
|
|
24
|
+
type: 'init';
|
|
25
|
+
} | {
|
|
26
|
+
type: 'load';
|
|
27
|
+
url: string;
|
|
28
|
+
dracoDecoderPath: string;
|
|
29
|
+
ktx2TranscoderPath: string;
|
|
30
|
+
ktx2LoaderConfig: KTX2LoaderWorkerConfig;
|
|
31
|
+
} | {
|
|
32
|
+
type: "loaded-gltf";
|
|
33
|
+
result: WorkerLoadResult;
|
|
34
|
+
};
|
|
35
|
+
declare class GLTFLoaderWorker {
|
|
36
|
+
private readonly worker;
|
|
37
|
+
static createWorker(opts: GLTFLoaderWorkerOptions): Promise<GLTFLoaderWorker>;
|
|
38
|
+
private _running;
|
|
39
|
+
private _webglRenderer;
|
|
40
|
+
load(url: string | URL, opts?: {
|
|
41
|
+
renderer?: WebGLRenderer;
|
|
42
|
+
}): Promise<WorkerLoadResult>;
|
|
43
|
+
private _debug;
|
|
44
|
+
private constructor();
|
|
45
|
+
}
|