@needle-tools/gltf-progressive 1.0.0-alpha → 1.0.0-alpha.2
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/dist/@needle-tools-gltf-progressive.js +651 -0
- package/dist/@needle-tools-gltf-progressive.min.js +3 -0
- package/dist/@needle-tools-gltf-progressive.umd.cjs +3 -0
- package/package.json +14 -11
- package/types/extension.d.ts +109 -0
- package/types/index.d.ts +10 -0
- package/types/loaders.d.ts +4 -0
- package/types/lods_manager.d.ts +51 -0
- package/types/plugins/index.d.ts +2 -0
- package/types/plugins/modelviewer.d.ts +15 -0
- package/types/plugins/plugin.d.ts +14 -0
- package/types/utils.d.ts +2 -0
- package/examples/@needle-tools-gltf-progressive.min.js +0 -3
- package/examples/modelviewer.html +0 -27
- package/src/extension.ts +0 -768
- package/src/index.ts +0 -14
- package/src/loaders.ts +0 -49
- package/src/lods_manager.ts +0 -387
- package/src/plugins/index.ts +0 -2
- package/src/plugins/modelviewer.ts +0 -113
- package/src/plugins/plugin.ts +0 -21
- package/src/utils.ts +0 -41
- package/tsconfig.json +0 -33
- package/vite.config.ts +0 -69
package/src/extension.ts
DELETED
|
@@ -1,768 +0,0 @@
|
|
|
1
|
-
import { BufferGeometry, Group, Material, Mesh, RawShaderMaterial, Texture, TextureLoader } from "three";
|
|
2
|
-
import { type GLTF, GLTFLoader, type GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
|
-
|
|
4
|
-
import { addDracoAndKTX2Loaders } from "./loaders.js";
|
|
5
|
-
import { getParam, resolveUrl } from "./utils.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// All of this has to be removed
|
|
10
|
-
// import { getRaycastMesh, setRaycastMesh } from "../../engine_physics.js";
|
|
11
|
-
// import { PromiseAllWithErrors, resolveUrl } from "../../engine_utils.js";
|
|
12
|
-
import { plugins } from "./plugins/plugin.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export const EXTENSION_NAME = "NEEDLE_progressive";
|
|
17
|
-
|
|
18
|
-
const debug = getParam("debugprogressive");
|
|
19
|
-
const $progressiveTextureExtension = Symbol("needle-progressive-texture");
|
|
20
|
-
|
|
21
|
-
/** Removes the readonly attribute from all properties of an object */
|
|
22
|
-
type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const debug_toggle_maps: Map<object, { keys: string[], sourceId: string }> = new Map();
|
|
26
|
-
const debug_materials: Set<Material> = new Set();
|
|
27
|
-
if (debug) {
|
|
28
|
-
let currentDebugLodLevel = -1;
|
|
29
|
-
let maxLevel = 2;
|
|
30
|
-
let wireframe = false;
|
|
31
|
-
function debugToggleProgressive() {
|
|
32
|
-
currentDebugLodLevel += 1;
|
|
33
|
-
console.log("Toggle LOD level", currentDebugLodLevel, debug_toggle_maps);
|
|
34
|
-
debug_toggle_maps.forEach((arr, obj) => {
|
|
35
|
-
for (const key of arr.keys) {
|
|
36
|
-
const cur = obj[key];
|
|
37
|
-
if ((cur as BufferGeometry).isBufferGeometry === true) {
|
|
38
|
-
const info = NEEDLE_progressive.getMeshLODInformation(cur);
|
|
39
|
-
const level = !info ? 0 : Math.min(currentDebugLodLevel, info.lods.length);
|
|
40
|
-
obj["DEBUG:LOD"] = level;
|
|
41
|
-
NEEDLE_progressive.assignMeshLOD(obj as Mesh, level);
|
|
42
|
-
if (info) maxLevel = Math.max(maxLevel, info.lods.length - 1);
|
|
43
|
-
}
|
|
44
|
-
else if ((obj as Material).isMaterial === true) {
|
|
45
|
-
obj["DEBUG:LOD"] = currentDebugLodLevel;
|
|
46
|
-
NEEDLE_progressive.assignTextureLOD(obj as Material, currentDebugLodLevel);
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
if (currentDebugLodLevel >= maxLevel) {
|
|
52
|
-
currentDebugLodLevel = -1;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
window.addEventListener("keyup", evt => {
|
|
56
|
-
if (evt.key === "p") debugToggleProgressive();
|
|
57
|
-
if (evt.key === "w") {
|
|
58
|
-
wireframe = !wireframe;
|
|
59
|
-
if (debug_materials) {
|
|
60
|
-
debug_materials.forEach(mat => {
|
|
61
|
-
if ("wireframe" in mat)
|
|
62
|
-
mat.wireframe = wireframe;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
function registerDebug(obj: object, key: string, sourceId: string,) {
|
|
69
|
-
if (!debug) return;
|
|
70
|
-
if (!debug_toggle_maps.has(obj)) {
|
|
71
|
-
debug_toggle_maps.set(obj, { keys: [], sourceId });
|
|
72
|
-
}
|
|
73
|
-
const existing = debug_toggle_maps.get(obj);
|
|
74
|
-
if (existing?.keys?.includes(key) == false) {
|
|
75
|
-
existing.keys.push(key);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
declare type NEEDLE_progressive_model_LOD = {
|
|
80
|
-
path: string,
|
|
81
|
-
hash?: string
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** This is the data structure we have in the NEEDLE_progressive extension */
|
|
85
|
-
declare type NEEDLE_progressive_model = {
|
|
86
|
-
guid: string,
|
|
87
|
-
lods: Array<NEEDLE_progressive_model_LOD>
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export declare type NEEDLE_progressive_texture_model = NEEDLE_progressive_model & {
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
export declare type NEEDLE_progressive_mesh_model = NEEDLE_progressive_model & {
|
|
94
|
-
density: number;
|
|
95
|
-
lods: Array<NEEDLE_progressive_model_LOD & {
|
|
96
|
-
density: number,
|
|
97
|
-
indexCount: number;
|
|
98
|
-
vertexCount: number;
|
|
99
|
-
}>
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* This is the result of a progressive texture loading event for a material's texture slot in {@link NEEDLE_progressive.assignTextureLOD}
|
|
104
|
-
* @internal
|
|
105
|
-
*/
|
|
106
|
-
export declare type ProgressiveMaterialTextureLoadingResult = {
|
|
107
|
-
/** the material the progressive texture was loaded for */
|
|
108
|
-
material: Material,
|
|
109
|
-
/** the slot in the material where the texture was loaded */
|
|
110
|
-
slot: string,
|
|
111
|
-
/** the texture that was loaded (if any) */
|
|
112
|
-
texture: Texture | null;
|
|
113
|
-
/** the level of detail that was loaded */
|
|
114
|
-
level: number;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* The NEEDLE_progressive extension for the GLTFLoader is responsible for loading progressive LODs for meshes and textures.
|
|
119
|
-
* This extension can be used to load different resolutions of a mesh or texture at runtime (e.g. for LODs or progressive textures).
|
|
120
|
-
* @example
|
|
121
|
-
* ```javascript
|
|
122
|
-
* const loader = new GLTFLoader();
|
|
123
|
-
* loader.register(new NEEDLE_progressive());
|
|
124
|
-
* loader.load("model.glb", (gltf) => {
|
|
125
|
-
* const mesh = gltf.scene.children[0] as Mesh;
|
|
126
|
-
* NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
|
|
127
|
-
* console.log("Mesh with LOD level 1 loaded", mesh);
|
|
128
|
-
* });
|
|
129
|
-
* });
|
|
130
|
-
* ```
|
|
131
|
-
*/
|
|
132
|
-
export class NEEDLE_progressive implements GLTFLoaderPlugin {
|
|
133
|
-
|
|
134
|
-
/** The name of the extension */
|
|
135
|
-
get name(): string {
|
|
136
|
-
return EXTENSION_NAME;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
static getMeshLODInformation(geo: BufferGeometry) {
|
|
140
|
-
const info = this.getAssignedLODInformation(geo);
|
|
141
|
-
if (info?.key) {
|
|
142
|
-
return this.lodInfos.get(info.key) as NEEDLE_progressive_mesh_model;
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** Check if a LOD level is available for a mesh or a texture
|
|
148
|
-
* @param obj the mesh or texture to check
|
|
149
|
-
* @param level the level of detail to check for (0 is the highest resolution). If undefined, the function checks if any LOD level is available
|
|
150
|
-
* @returns true if the LOD level is available (or if any LOD level is available if level is undefined)
|
|
151
|
-
*/
|
|
152
|
-
static hasLODLevelAvailable(obj: Mesh | Texture | Material, level?: number): boolean {
|
|
153
|
-
|
|
154
|
-
if (obj instanceof Material) {
|
|
155
|
-
for (const slot of Object.keys(obj)) {
|
|
156
|
-
const val = obj[slot];
|
|
157
|
-
if (val instanceof Texture) {
|
|
158
|
-
if (this.hasLODLevelAvailable(val, level)) return true;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
else if (obj instanceof Group) {
|
|
164
|
-
for (const child of obj.children) {
|
|
165
|
-
if (child instanceof Mesh) {
|
|
166
|
-
if (this.hasLODLevelAvailable(child, level)) return true;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
let lodObject: ObjectThatMightHaveLODs | undefined;
|
|
173
|
-
let lodInformation: NEEDLE_progressive_model | undefined;
|
|
174
|
-
|
|
175
|
-
if (obj instanceof Mesh) {
|
|
176
|
-
lodObject = obj.geometry as BufferGeometry;
|
|
177
|
-
}
|
|
178
|
-
else if (obj instanceof Texture) {
|
|
179
|
-
lodObject = obj;
|
|
180
|
-
}
|
|
181
|
-
if (lodObject) {
|
|
182
|
-
if (lodObject?.userData?.LODS) {
|
|
183
|
-
const lods = lodObject.userData.LODS;
|
|
184
|
-
lodInformation = this.lodInfos.get(lods.key);
|
|
185
|
-
if (level === undefined) return lodInformation != undefined;
|
|
186
|
-
if (lodInformation) {
|
|
187
|
-
if (Array.isArray(lodInformation.lods)) {
|
|
188
|
-
return level < lodInformation.lods.length;
|
|
189
|
-
}
|
|
190
|
-
return level === 0;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** Load a different resolution of a mesh (if available)
|
|
199
|
-
* @param context the context
|
|
200
|
-
* @param source the sourceid of the file from which the mesh is loaded (this is usually the component's sourceId)
|
|
201
|
-
* @param mesh the mesh to load the LOD for
|
|
202
|
-
* @param level the level of detail to load (0 is the highest resolution)
|
|
203
|
-
* @returns a promise that resolves to the mesh with the requested LOD level
|
|
204
|
-
* @example
|
|
205
|
-
* ```javascript
|
|
206
|
-
* const mesh = this.gameObject as Mesh;
|
|
207
|
-
* NEEDLE_progressive.assignMeshLOD(context, sourceId, mesh, 1).then(mesh => {
|
|
208
|
-
* console.log("Mesh with LOD level 1 loaded", mesh);
|
|
209
|
-
* });
|
|
210
|
-
* ```
|
|
211
|
-
*/
|
|
212
|
-
static assignMeshLOD(mesh: Mesh, level: number): Promise<BufferGeometry | null> {
|
|
213
|
-
|
|
214
|
-
if (!mesh) return Promise.resolve(null);
|
|
215
|
-
|
|
216
|
-
if (mesh instanceof Mesh || (mesh as any).isMesh === true) {
|
|
217
|
-
|
|
218
|
-
const currentGeometry = mesh.geometry;
|
|
219
|
-
const lodinfo = this.getAssignedLODInformation(currentGeometry);
|
|
220
|
-
if (!lodinfo) {
|
|
221
|
-
return Promise.resolve(null);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
for (const plugin of plugins) {
|
|
225
|
-
plugin.onBeforeGetLODMesh?.(mesh, level);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// const info = this.onProgressiveLoadStart(context, source, mesh, null);
|
|
229
|
-
mesh["LOD:requested level"] = level;
|
|
230
|
-
return NEEDLE_progressive.getOrLoadLOD<BufferGeometry>(currentGeometry, level).then(geo => {
|
|
231
|
-
if (mesh["LOD:requested level"] === level) {
|
|
232
|
-
delete mesh["LOD:requested level"];
|
|
233
|
-
if (Array.isArray(geo)) {
|
|
234
|
-
const index = lodinfo.index || 0;
|
|
235
|
-
geo = geo[index];
|
|
236
|
-
}
|
|
237
|
-
if (geo && currentGeometry != geo) {
|
|
238
|
-
// if (debug == "verbose") console.log("Progressive Mesh " + mesh.name + " loaded", currentGeometry, "→", geo, "\n", mesh)
|
|
239
|
-
if (geo instanceof BufferGeometry) {
|
|
240
|
-
mesh.geometry = geo;
|
|
241
|
-
if (debug) registerDebug(mesh, "geometry", lodinfo.url);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// this.onProgressiveLoadEnd(info);
|
|
246
|
-
return geo;
|
|
247
|
-
|
|
248
|
-
}).catch(err => {
|
|
249
|
-
// this.onProgressiveLoadEnd(info);
|
|
250
|
-
console.error("Error loading mesh LOD", mesh, err);
|
|
251
|
-
return null;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
else if (debug) {
|
|
255
|
-
console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh", mesh);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return Promise.resolve(null);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/** Load a different resolution of a texture (if available)
|
|
262
|
-
* @param context the context
|
|
263
|
-
* @param source the sourceid of the file from which the texture is loaded (this is usually the component's sourceId)
|
|
264
|
-
* @param materialOrTexture the material or texture to load the LOD for (if passing in a material all textures in the material will be loaded)
|
|
265
|
-
* @param level the level of detail to load (0 is the highest resolution) - currently only 0 is supported
|
|
266
|
-
* @returns a promise that resolves to the material or texture with the requested LOD level
|
|
267
|
-
*/
|
|
268
|
-
static assignTextureLOD(materialOrTexture: Material | Texture, level: number = 0)
|
|
269
|
-
: Promise<Array<ProgressiveMaterialTextureLoadingResult> | Texture | null> {
|
|
270
|
-
|
|
271
|
-
if (!materialOrTexture) return Promise.resolve(null);
|
|
272
|
-
|
|
273
|
-
if (materialOrTexture instanceof Material || (materialOrTexture as unknown as Material).isMaterial === true) {
|
|
274
|
-
const material = materialOrTexture as Material;
|
|
275
|
-
const promises: Array<Promise<Texture | null>> = [];
|
|
276
|
-
const slots = new Array<string>();
|
|
277
|
-
|
|
278
|
-
if (debug) debug_materials.add(material);
|
|
279
|
-
|
|
280
|
-
if (material instanceof RawShaderMaterial) {
|
|
281
|
-
// iterate uniforms of custom shaders
|
|
282
|
-
for (const slot of Object.keys(material.uniforms)) {
|
|
283
|
-
const val = material.uniforms[slot].value as Texture;
|
|
284
|
-
if (val?.isTexture === true) {
|
|
285
|
-
const task = this.assignTextureLODForSlot(val, level, material, slot);
|
|
286
|
-
promises.push(task);
|
|
287
|
-
slots.push(slot);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
for (const slot of Object.keys(material)) {
|
|
293
|
-
const val = material[slot] as Texture;
|
|
294
|
-
if (val?.isTexture === true) {
|
|
295
|
-
const task = this.assignTextureLODForSlot(val, level, material, slot);
|
|
296
|
-
promises.push(task);
|
|
297
|
-
slots.push(slot);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return Promise.all(promises).then(res => {
|
|
302
|
-
const textures = new Array<ProgressiveMaterialTextureLoadingResult>();
|
|
303
|
-
for (let i = 0; i < res.length; i++) {
|
|
304
|
-
const tex = res[i];
|
|
305
|
-
const slot = slots[i];
|
|
306
|
-
if (tex instanceof Texture) {
|
|
307
|
-
textures.push({ material, slot, texture: tex, level });
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
textures.push({ material, slot, texture: null, level });
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return textures;
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (materialOrTexture instanceof Texture || (materialOrTexture as Texture).isTexture === true) {
|
|
318
|
-
const texture = materialOrTexture;
|
|
319
|
-
return this.assignTextureLODForSlot(texture, level, null, null);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return Promise.resolve(null);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
private static assignTextureLODForSlot(current: Texture, level: number, material: Material | null, slot: string | null): Promise<Texture | null> {
|
|
326
|
-
if (current?.isTexture !== true) return Promise.resolve(null);
|
|
327
|
-
|
|
328
|
-
// if (debug) console.log("-----------\n", "FIND", material?.name, slot, current?.name, current?.userData, current, material);
|
|
329
|
-
|
|
330
|
-
// const info = this.onProgressiveLoadStart(context, source, material, slot);
|
|
331
|
-
return NEEDLE_progressive.getOrLoadLOD<Texture>(current, level).then(tex => {
|
|
332
|
-
|
|
333
|
-
// this can currently not happen
|
|
334
|
-
if (Array.isArray(tex)) return null;
|
|
335
|
-
|
|
336
|
-
if (tex?.isTexture === true) {
|
|
337
|
-
if (tex != current) {
|
|
338
|
-
// if (debug) console.warn("Assign LOD", material?.name, slot, tex.name, tex["guid"], material, "Prev:", current, "Now:", tex, "\n--------------");
|
|
339
|
-
|
|
340
|
-
// tex.needsUpdate = true;
|
|
341
|
-
|
|
342
|
-
if (material && slot) {
|
|
343
|
-
material[slot] = tex;
|
|
344
|
-
// material.needsUpdate = true;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (debug && slot && material) {
|
|
348
|
-
const lodinfo = this.getAssignedLODInformation(current);
|
|
349
|
-
if (lodinfo) registerDebug(material, slot, lodinfo.url);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// check if the old texture is still used by other objects
|
|
353
|
-
// if not we dispose it...
|
|
354
|
-
// this could also be handled elsewhere and not be done immediately
|
|
355
|
-
// const users = getResourceUserCount(current);
|
|
356
|
-
// if (!users) {
|
|
357
|
-
// if (debug) console.log("Progressive: Dispose texture", current.name, current.source.data, current.uuid);
|
|
358
|
-
// current?.dispose();
|
|
359
|
-
// }
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// this.onProgressiveLoadEnd(info);
|
|
363
|
-
return tex;
|
|
364
|
-
}
|
|
365
|
-
else if (debug == "verbose") {
|
|
366
|
-
console.warn("No LOD found for", current, level);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// this.onProgressiveLoadEnd(info);
|
|
370
|
-
return null;
|
|
371
|
-
|
|
372
|
-
}).catch(err => {
|
|
373
|
-
// this.onProgressiveLoadEnd(info);
|
|
374
|
-
console.error("Error loading LOD", current, err);
|
|
375
|
-
return null;
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
private readonly parser: GLTFParser;
|
|
383
|
-
private readonly url: string;
|
|
384
|
-
|
|
385
|
-
constructor(parser: GLTFParser, url: string) {
|
|
386
|
-
this.parser = parser;
|
|
387
|
-
this.url = url;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
afterRoot(gltf: GLTF): null {
|
|
391
|
-
if (debug)
|
|
392
|
-
console.log("AFTER", this.url, gltf);
|
|
393
|
-
|
|
394
|
-
this.parser.json.textures?.forEach((textureInfo, index) => {
|
|
395
|
-
if (textureInfo?.extensions) {
|
|
396
|
-
const ext: NEEDLE_progressive_texture_model = textureInfo?.extensions[EXTENSION_NAME];
|
|
397
|
-
if (ext) {
|
|
398
|
-
for (const key of this.parser.associations.keys()) {
|
|
399
|
-
if (key instanceof Texture) {
|
|
400
|
-
const val = this.parser.associations.get(key) as { textures: number };
|
|
401
|
-
if (val.textures === index) {
|
|
402
|
-
NEEDLE_progressive.registerTexture(this.url, key, index, ext);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
this.parser.json.meshes?.forEach((meshInfo, index: number) => {
|
|
411
|
-
if (meshInfo?.extensions) {
|
|
412
|
-
const ext = meshInfo?.extensions[EXTENSION_NAME] as NEEDLE_progressive_mesh_model;
|
|
413
|
-
if (ext && ext.lods) {
|
|
414
|
-
for (const entry of this.parser.associations.keys()) {
|
|
415
|
-
if (entry instanceof Mesh) {
|
|
416
|
-
const val = this.parser.associations.get(entry) as { meshes: number, primitives: number };
|
|
417
|
-
if (val.meshes === index) {
|
|
418
|
-
if (entry instanceof Mesh) {
|
|
419
|
-
NEEDLE_progressive.registerMesh(this.url, entry.uuid, entry, ext.lods.length, val.primitives, ext);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Register a texture with LOD information
|
|
434
|
-
*/
|
|
435
|
-
static registerTexture = (url: string, tex: Texture, index: number, ext: NEEDLE_progressive_texture_model) => {
|
|
436
|
-
if (debug) console.log("> Progressive: register texture", index, tex.name, tex.uuid, tex, ext);
|
|
437
|
-
// 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)
|
|
438
|
-
// see https://github.com/needle-tools/needle-engine-support/issues/133
|
|
439
|
-
if (tex.source)
|
|
440
|
-
tex.source[$progressiveTextureExtension] = ext;
|
|
441
|
-
const LODKEY = tex.uuid;
|
|
442
|
-
NEEDLE_progressive.assignLODInformation(url, tex, LODKEY, 0, 0, undefined);
|
|
443
|
-
NEEDLE_progressive.lodInfos.set(LODKEY, ext);
|
|
444
|
-
NEEDLE_progressive.lowresCache.set(LODKEY, tex);
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Register a mesh with LOD information
|
|
449
|
-
*/
|
|
450
|
-
static registerMesh = (url: string, key: string, mesh: Mesh, level: number, index: number | undefined, ext: NEEDLE_progressive_mesh_model) => {
|
|
451
|
-
if (debug) console.log("> Progressive: register mesh", index, mesh.name, ext, mesh.uuid, mesh);
|
|
452
|
-
|
|
453
|
-
const geometry = mesh.geometry as BufferGeometry;
|
|
454
|
-
if (!geometry.userData) geometry.userData = {};
|
|
455
|
-
NEEDLE_progressive.assignLODInformation(url, geometry, key, level, index, ext.density);
|
|
456
|
-
|
|
457
|
-
NEEDLE_progressive.lodInfos.set(key, ext);
|
|
458
|
-
|
|
459
|
-
let existing = NEEDLE_progressive.lowresCache.get(key) as unknown as BufferGeometry[] | undefined;
|
|
460
|
-
if (existing) existing.push(mesh.geometry as BufferGeometry);
|
|
461
|
-
else existing = [mesh.geometry as BufferGeometry];
|
|
462
|
-
NEEDLE_progressive.lowresCache.set(key, existing);
|
|
463
|
-
|
|
464
|
-
for (const plugin of plugins) {
|
|
465
|
-
plugin.onRegisteredMesh?.(mesh, ext);
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
/** A map of key = asset uuid and value = LOD information */
|
|
471
|
-
private static readonly lodInfos = new Map<string, NEEDLE_progressive_model>();
|
|
472
|
-
/** cache of already loaded mesh lods */
|
|
473
|
-
private static readonly previouslyLoaded: Map<string, Promise<null | Texture | BufferGeometry | BufferGeometry[]>> = new Map();
|
|
474
|
-
/** this contains the geometry/textures that were originally loaded */
|
|
475
|
-
private static readonly lowresCache: Map<string, Texture | BufferGeometry[]> = new Map();
|
|
476
|
-
|
|
477
|
-
private static async getOrLoadLOD<T extends Texture | BufferGeometry>(current: T & ObjectThatMightHaveLODs, level: number): Promise<T | null> {
|
|
478
|
-
|
|
479
|
-
const debugverbose = debug == "verbose";
|
|
480
|
-
|
|
481
|
-
/** this key is used to lookup the LOD information */
|
|
482
|
-
const LOD: LODInformation | undefined = current.userData.LODS;
|
|
483
|
-
|
|
484
|
-
if (!LOD) {
|
|
485
|
-
return null;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const LODKEY = LOD?.key;
|
|
489
|
-
|
|
490
|
-
let progressiveInfo: NEEDLE_progressive_model | undefined;
|
|
491
|
-
|
|
492
|
-
// See https://github.com/needle-tools/needle-engine-support/issues/133
|
|
493
|
-
if (current instanceof Texture) {
|
|
494
|
-
if (current.source && current.source[$progressiveTextureExtension])
|
|
495
|
-
progressiveInfo = current.source[$progressiveTextureExtension];
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (!progressiveInfo) progressiveInfo = NEEDLE_progressive.lodInfos.get(LODKEY);
|
|
500
|
-
|
|
501
|
-
if (progressiveInfo) {
|
|
502
|
-
|
|
503
|
-
if (level > 0) {
|
|
504
|
-
let useLowRes = false;
|
|
505
|
-
const hasMultipleLevels = Array.isArray(progressiveInfo.lods);
|
|
506
|
-
if (hasMultipleLevels && level >= progressiveInfo.lods.length) {
|
|
507
|
-
useLowRes = true;
|
|
508
|
-
}
|
|
509
|
-
else if (!hasMultipleLevels) {
|
|
510
|
-
useLowRes = true;
|
|
511
|
-
}
|
|
512
|
-
if (useLowRes) {
|
|
513
|
-
const lowres = this.lowresCache.get(LODKEY) as T;
|
|
514
|
-
return lowres;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/** the unresolved LOD url */
|
|
519
|
-
const unresolved_lod_url = Array.isArray(progressiveInfo.lods) ? progressiveInfo.lods[level].path : progressiveInfo.lods;
|
|
520
|
-
|
|
521
|
-
// check if we have a uri
|
|
522
|
-
if (!unresolved_lod_url) {
|
|
523
|
-
if (debug && !progressiveInfo["missing:uri"]) {
|
|
524
|
-
progressiveInfo["missing:uri"] = true;
|
|
525
|
-
console.warn("Missing uri for progressive asset for LOD " + level, progressiveInfo);
|
|
526
|
-
}
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/** the resolved LOD url */
|
|
531
|
-
const lod_url = resolveUrl(LOD.url, unresolved_lod_url);
|
|
532
|
-
|
|
533
|
-
// check if the requested file needs to be loaded via a GLTFLoader
|
|
534
|
-
if (lod_url.endsWith(".glb") || lod_url.endsWith(".gltf")) {
|
|
535
|
-
if (!progressiveInfo.guid) {
|
|
536
|
-
console.warn("missing pointer for glb/gltf texture", progressiveInfo);
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
// check if the requested file has already been loaded
|
|
540
|
-
const KEY = lod_url + "_" + progressiveInfo.guid;
|
|
541
|
-
|
|
542
|
-
// check if the requested file is currently being loaded
|
|
543
|
-
const existing = this.previouslyLoaded.get(KEY);
|
|
544
|
-
if (existing !== undefined) {
|
|
545
|
-
if (debugverbose) console.log(`LOD ${level} was already loading/loaded: ${KEY}`);
|
|
546
|
-
let res = await existing.catch(err => {
|
|
547
|
-
console.error(`Error loading LOD ${level} from ${lod_url}\n`, err);
|
|
548
|
-
return null;
|
|
549
|
-
});
|
|
550
|
-
let resouceIsDisposed = false;
|
|
551
|
-
if (res == null) {
|
|
552
|
-
// if the resource is null the last loading result didnt succeed (maybe because the url doesnt exist)
|
|
553
|
-
// in which case we don't attempt to load it again
|
|
554
|
-
}
|
|
555
|
-
else if (res instanceof Texture && current instanceof Texture) {
|
|
556
|
-
// check if the texture has been disposed or not
|
|
557
|
-
if (res.image?.data || res.source?.data) {
|
|
558
|
-
res = this.copySettings(current, res);
|
|
559
|
-
}
|
|
560
|
-
// if it has been disposed we need to load it again
|
|
561
|
-
else {
|
|
562
|
-
resouceIsDisposed = true;
|
|
563
|
-
this.previouslyLoaded.delete(KEY);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
else if (res instanceof BufferGeometry && current instanceof BufferGeometry) {
|
|
567
|
-
if (res.attributes.position?.array) {
|
|
568
|
-
// the geometry is OK
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
resouceIsDisposed = true;
|
|
572
|
-
this.previouslyLoaded.delete(KEY);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
if (!resouceIsDisposed) {
|
|
576
|
-
return res as T;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
const ext = progressiveInfo;
|
|
581
|
-
const request = new Promise<null | Texture | BufferGeometry | BufferGeometry[]>(async (resolve, _) => {
|
|
582
|
-
|
|
583
|
-
const loader = new GLTFLoader();
|
|
584
|
-
addDracoAndKTX2Loaders(loader);
|
|
585
|
-
|
|
586
|
-
if (debug) {
|
|
587
|
-
await new Promise<void>(resolve => setTimeout(resolve, 1000));
|
|
588
|
-
if (debugverbose) console.warn("Start loading (delayed) " + lod_url, ext.guid);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
let url = lod_url;
|
|
592
|
-
if (Array.isArray(progressiveInfo.lods)) {
|
|
593
|
-
const lodinfo = progressiveInfo.lods[level];
|
|
594
|
-
if (lodinfo.hash) {
|
|
595
|
-
url += "?v=" + lodinfo.hash;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
const gltf = await loader.loadAsync(url).catch(err => {
|
|
599
|
-
console.error(`Error loading LOD ${level} from ${lod_url}\n`, err);
|
|
600
|
-
return null;
|
|
601
|
-
});
|
|
602
|
-
if (!gltf) return null;
|
|
603
|
-
|
|
604
|
-
const parser = gltf.parser;
|
|
605
|
-
if (debugverbose) console.log("Loading finished " + lod_url, ext.guid);
|
|
606
|
-
let index = 0;
|
|
607
|
-
|
|
608
|
-
if (gltf.parser.json.textures) {
|
|
609
|
-
let found = false;
|
|
610
|
-
for (const tex of gltf.parser.json.textures) {
|
|
611
|
-
// find the texture index
|
|
612
|
-
if (tex?.extensions) {
|
|
613
|
-
const other: NEEDLE_progressive_model = tex?.extensions[EXTENSION_NAME];
|
|
614
|
-
if (other?.guid) {
|
|
615
|
-
if (other.guid === ext.guid) {
|
|
616
|
-
found = true;
|
|
617
|
-
break;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
index++;
|
|
622
|
-
}
|
|
623
|
-
if (found) {
|
|
624
|
-
let tex = await parser.getDependency("texture", index) as Texture;
|
|
625
|
-
if (debugverbose) console.log("change \"" + current.name + "\" → \"" + tex.name + "\"", lod_url, index, tex, KEY);
|
|
626
|
-
if (current instanceof Texture)
|
|
627
|
-
tex = this.copySettings(current, tex);
|
|
628
|
-
if (tex) {
|
|
629
|
-
(tex as any).guid = ext.guid;
|
|
630
|
-
}
|
|
631
|
-
return resolve(tex);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
index = 0;
|
|
636
|
-
|
|
637
|
-
if (gltf.parser.json.meshes) {
|
|
638
|
-
let found = false;
|
|
639
|
-
for (const mesh of gltf.parser.json.meshes) {
|
|
640
|
-
// find the mesh index
|
|
641
|
-
if (mesh?.extensions) {
|
|
642
|
-
const other: NEEDLE_progressive_model = mesh?.extensions[EXTENSION_NAME];
|
|
643
|
-
if (other?.guid) {
|
|
644
|
-
if (other.guid === ext.guid) {
|
|
645
|
-
found = true;
|
|
646
|
-
break;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
index++;
|
|
651
|
-
}
|
|
652
|
-
if (found) {
|
|
653
|
-
const mesh = await parser.getDependency("mesh", index) as Mesh | Group;
|
|
654
|
-
|
|
655
|
-
const meshExt = ext as NEEDLE_progressive_mesh_model;
|
|
656
|
-
|
|
657
|
-
if (debugverbose) console.log(`Loaded Mesh \"${mesh.name}\"`, lod_url, index, mesh, KEY);
|
|
658
|
-
|
|
659
|
-
if (mesh instanceof Mesh) {
|
|
660
|
-
const geo = mesh.geometry as BufferGeometry;
|
|
661
|
-
NEEDLE_progressive.assignLODInformation(LOD.url, geo, LODKEY, level, undefined, meshExt.density);
|
|
662
|
-
return resolve(geo);
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
const geometries = new Array<BufferGeometry>();
|
|
666
|
-
for (let i = 0; i < mesh.children.length; i++) {
|
|
667
|
-
const child = mesh.children[i];
|
|
668
|
-
if (child instanceof Mesh) {
|
|
669
|
-
const geo = child.geometry as BufferGeometry;
|
|
670
|
-
NEEDLE_progressive.assignLODInformation(LOD.url, geo, LODKEY, level, i, meshExt.density);
|
|
671
|
-
geometries.push(geo);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
return resolve(geometries);
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// we could not find a texture or mesh with the given guid
|
|
680
|
-
return resolve(null);
|
|
681
|
-
});
|
|
682
|
-
this.previouslyLoaded.set(KEY, request);
|
|
683
|
-
const res = await request;
|
|
684
|
-
return res as T;
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
if (current instanceof Texture) {
|
|
688
|
-
if (debugverbose) console.log("Load texture from uri: " + lod_url);
|
|
689
|
-
const loader = new TextureLoader();
|
|
690
|
-
const tex = await loader.loadAsync(lod_url);
|
|
691
|
-
if (tex) {
|
|
692
|
-
(tex as any).guid = progressiveInfo.guid;
|
|
693
|
-
tex.flipY = false;
|
|
694
|
-
tex.needsUpdate = true;
|
|
695
|
-
tex.colorSpace = current.colorSpace;
|
|
696
|
-
if (debugverbose)
|
|
697
|
-
console.log(progressiveInfo, tex);
|
|
698
|
-
}
|
|
699
|
-
else if (debug) console.warn("failed loading", lod_url);
|
|
700
|
-
return tex as T;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
if (debug)
|
|
706
|
-
console.warn(`Can not load LOD ${level}: no LOD info found for \"${LODKEY}\" ${current.name}`, current.type);
|
|
707
|
-
}
|
|
708
|
-
return null;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
private static assignLODInformation(url: string, res: DeepWriteable<ObjectThatMightHaveLODs>, key: string, level: number, index?: number, density?: number) {
|
|
712
|
-
if (!res) return;
|
|
713
|
-
if (!res.userData) res.userData = {};
|
|
714
|
-
const info: LODInformation = new LODInformation(url, key, level, index, density);
|
|
715
|
-
res.userData.LODS = info;
|
|
716
|
-
res.userData.LOD = level;
|
|
717
|
-
}
|
|
718
|
-
private static getAssignedLODInformation(res: ObjectThatMightHaveLODs | null | undefined): null | LODInformation {
|
|
719
|
-
return res?.userData?.LODS || null;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
private static readonly _copiedTextures: WeakMap<Texture, Texture> = new Map();
|
|
723
|
-
|
|
724
|
-
private static copySettings(source: Texture, target: Texture): Texture {
|
|
725
|
-
// don't copy again if the texture was processed before
|
|
726
|
-
const existingClone = this._copiedTextures.get(source);
|
|
727
|
-
if (existingClone) {
|
|
728
|
-
return existingClone;
|
|
729
|
-
}
|
|
730
|
-
// We need to clone e.g. when the same texture is used multiple times (but with e.g. different wrap settings)
|
|
731
|
-
// This is relatively cheap since it only stores settings
|
|
732
|
-
// This should only happen once ever for every texture
|
|
733
|
-
target = target.clone();
|
|
734
|
-
this._copiedTextures.set(source, target);
|
|
735
|
-
// we re-use the offset and repeat settings because it might be animated
|
|
736
|
-
target.offset = source.offset;
|
|
737
|
-
target.repeat = source.repeat;
|
|
738
|
-
target.colorSpace = source.colorSpace;
|
|
739
|
-
return target;
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
declare type ObjectThatMightHaveLODs = { userData?: { LODS?: LODInformation, readonly LOD?: number } };
|
|
745
|
-
|
|
746
|
-
// declare type GetLODInformation = () => LODInformation | null;
|
|
747
|
-
|
|
748
|
-
class LODInformation {
|
|
749
|
-
readonly url: string;
|
|
750
|
-
/** the key to lookup the LOD information */
|
|
751
|
-
readonly key: string;
|
|
752
|
-
readonly level: number;
|
|
753
|
-
/** For multi objects (e.g. a group of meshes) this is the index of the object */
|
|
754
|
-
readonly index?: number;
|
|
755
|
-
/** the mesh density */
|
|
756
|
-
readonly density?: number;
|
|
757
|
-
|
|
758
|
-
constructor(url: string, key: string, level: number, index?: number, density?: number) {
|
|
759
|
-
this.url = url;
|
|
760
|
-
this.key = key;
|
|
761
|
-
this.level = level;
|
|
762
|
-
if (index != undefined)
|
|
763
|
-
this.index = index;
|
|
764
|
-
if (density != undefined)
|
|
765
|
-
this.density = density;
|
|
766
|
-
}
|
|
767
|
-
};
|
|
768
|
-
|