@needle-tools/engine 4.8.3 → 4.8.4-experimental.c93e134
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 +10 -0
- package/README.md +5 -0
- package/components.needle.json +1 -1
- package/dist/{gltf-progressive-Do1XJNMG.js → gltf-progressive-B3JW4cAu.js} +247 -248
- package/dist/gltf-progressive-DorC035H.min.js +8 -0
- package/dist/{gltf-progressive-CHV7_60B.umd.cjs → gltf-progressive-PB_58h1b.umd.cjs} +6 -6
- package/dist/{needle-engine.bundle-Drb5H9t2.min.js → needle-engine.bundle-BLVnT5UY.min.js} +140 -130
- package/dist/{needle-engine.bundle-CiIS3tNq.js → needle-engine.bundle-Bx5wfOs0.js} +4938 -4704
- package/dist/{needle-engine.bundle-d8rUkpZi.umd.cjs → needle-engine.bundle-DhD3EKds.umd.cjs} +134 -124
- package/dist/needle-engine.js +411 -409
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/dist/{vendor-CGONwIc0.js → vendor-B_ytQUuR.js} +6 -6
- package/dist/{vendor-BlSxe9JJ.min.js → vendor-C31T0mYm.min.js} +2 -2
- package/dist/{vendor-Cty8Dnri.umd.cjs → vendor-D51IT5ns.umd.cjs} +9 -9
- package/lib/engine/engine_context.d.ts +1 -1
- package/lib/engine/engine_context.js +5 -12
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_networking_streams.js +13 -2
- package/lib/engine/engine_networking_streams.js.map +1 -1
- package/lib/engine/extensions/KHR_materials_variants.d.ts +25 -0
- package/lib/engine/extensions/KHR_materials_variants.js +113 -0
- package/lib/engine/extensions/KHR_materials_variants.js.map +1 -0
- package/lib/engine/extensions/extensions.js +2 -0
- package/lib/engine/extensions/extensions.js.map +1 -1
- package/lib/engine/extensions/index.d.ts +1 -0
- package/lib/engine/extensions/index.js +1 -0
- package/lib/engine/extensions/index.js.map +1 -1
- package/lib/engine/webcomponents/buttons.js +6 -2
- package/lib/engine/webcomponents/buttons.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +10 -0
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js +1 -1
- package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
- package/lib/engine-components/MaterialVariants.d.ts +52 -0
- package/lib/engine-components/MaterialVariants.js +210 -0
- package/lib/engine-components/MaterialVariants.js.map +1 -0
- package/lib/engine-components/OrbitControls.d.ts +1 -0
- package/lib/engine-components/OrbitControls.js +6 -0
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +1 -0
- package/lib/engine-components/codegen/components.js +1 -0
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/package.json +4 -4
- package/plugins/vite/peer.js +60 -2
- package/plugins/vite/poster-client.js +35 -51
- package/plugins/vite/poster.js +2 -3
- package/src/engine/engine_context.ts +7 -12
- package/src/engine/engine_networking_streams.ts +17 -8
- package/src/engine/extensions/KHR_materials_variants.ts +179 -0
- package/src/engine/extensions/extensions.ts +2 -0
- package/src/engine/extensions/index.ts +1 -0
- package/src/engine/webcomponents/buttons.ts +6 -2
- package/src/engine/webcomponents/needle menu/needle-menu.ts +10 -0
- package/src/engine/webcomponents/needle-engine.loading.ts +1 -1
- package/src/engine-components/MaterialVariants.ts +231 -0
- package/src/engine-components/OrbitControls.ts +5 -0
- package/src/engine-components/codegen/components.ts +1 -0
- package/dist/gltf-progressive-B--ZfCTJ.min.js +0 -8
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { type GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
2
|
+
import { MaterialVariants } from "../../engine-components/MaterialVariants.js";
|
|
3
|
+
import { addComponent } from "../engine_components.js";
|
|
4
|
+
import { InstantiateIdProvider } from "../engine_networking_instantiate.js";
|
|
5
|
+
import { GLTF } from "../engine_types.js";
|
|
6
|
+
import { getParam } from "../engine_utils.js";
|
|
7
|
+
|
|
8
|
+
const debug = getParam("debugmaterialvariants");
|
|
9
|
+
|
|
10
|
+
export interface MaterialVariant {
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RootMaterialVariantsExtension {
|
|
15
|
+
variants: MaterialVariant[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PrimitiveMaterialVariantsExtension {
|
|
19
|
+
mappings: Array<{
|
|
20
|
+
variants: number[];
|
|
21
|
+
material: number;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class NEEDLE_material_variants implements GLTFLoaderPlugin {
|
|
26
|
+
get name() { return "KHR_materials_variants"; }
|
|
27
|
+
|
|
28
|
+
private parser: GLTFParser;
|
|
29
|
+
private variants: MaterialVariant[] = [];
|
|
30
|
+
private loadingMesh = false;
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
constructor(parser: GLTFParser) {
|
|
34
|
+
this.parser = parser;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
beforeRoot(): Promise<void> | null {
|
|
38
|
+
const parser = this.parser;
|
|
39
|
+
const json = parser.json;
|
|
40
|
+
|
|
41
|
+
if (!json.extensions || !json.extensions[this.name]) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const extension = json.extensions[this.name] as RootMaterialVariantsExtension;
|
|
46
|
+
this.variants = extension.variants || [];
|
|
47
|
+
|
|
48
|
+
if (debug) {
|
|
49
|
+
console.log("KHR_materials_variants found variants:", this.variants);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
afterRoot(result: GLTF): Promise<void> | null {
|
|
56
|
+
if (this.variants.length > 0) {
|
|
57
|
+
// Store variants data on the GLTF result for components to access
|
|
58
|
+
if (!result.userData) result.userData = {};
|
|
59
|
+
result.userData.materialVariants = {
|
|
60
|
+
variants: this.variants,
|
|
61
|
+
parser: this.parser
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (debug) {
|
|
65
|
+
console.log("KHR_materials_variants stored variants data:", result.userData.materialVariants);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
loadMesh(index: number): Promise<any> | null {
|
|
74
|
+
// Prevent infinite recursion
|
|
75
|
+
if (this.loadingMesh) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const json = this.parser.json;
|
|
80
|
+
const meshDef = json.meshes[index];
|
|
81
|
+
|
|
82
|
+
if (!meshDef || !meshDef.primitives) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if any primitive has variant mappings
|
|
87
|
+
const allMappings: Array<{ variants: number[]; material: number; primitiveIndex: number; }> = [];
|
|
88
|
+
|
|
89
|
+
meshDef.primitives.forEach((primitive: any, primitiveIndex: number) => {
|
|
90
|
+
if (primitive.extensions && primitive.extensions[this.name]) {
|
|
91
|
+
const extension = primitive.extensions[this.name] as PrimitiveMaterialVariantsExtension;
|
|
92
|
+
|
|
93
|
+
extension.mappings.forEach(mapping => {
|
|
94
|
+
allMappings.push({
|
|
95
|
+
...mapping,
|
|
96
|
+
primitiveIndex
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// If no variants, let normal loading proceed
|
|
103
|
+
if (allMappings.length === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Load the mesh with recursion protection
|
|
108
|
+
this.loadingMesh = true;
|
|
109
|
+
|
|
110
|
+
return this.parser.loadMesh(index).then(mesh => {
|
|
111
|
+
if (mesh) {
|
|
112
|
+
// Attach variant data directly to the loaded mesh
|
|
113
|
+
this.attachVariantDataToMesh(mesh, index, allMappings);
|
|
114
|
+
|
|
115
|
+
if (debug) {
|
|
116
|
+
console.log(`KHR_materials_variants processed mesh ${index} with ${allMappings.length} variant mappings`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return mesh;
|
|
120
|
+
}).finally(() => {
|
|
121
|
+
this.loadingMesh = false;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
private attachVariantDataToMesh(mesh: any, index: number, mappings: Array<{ variants: number[]; material: number; primitiveIndex: number; }>): void {
|
|
127
|
+
// Store variant mapping data on the mesh
|
|
128
|
+
if (!mesh.userData) mesh.userData = {};
|
|
129
|
+
mesh.userData.materialVariants = {
|
|
130
|
+
mappings: mappings,
|
|
131
|
+
variants: this.variants,
|
|
132
|
+
parser: this.parser
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const idProvider = new InstantiateIdProvider(index);
|
|
136
|
+
|
|
137
|
+
// Automatically add the MaterialVariants component
|
|
138
|
+
try {
|
|
139
|
+
const component = addComponent(mesh, MaterialVariants, {}, { callAwake: false });
|
|
140
|
+
component.guid = idProvider.generateUUID();
|
|
141
|
+
if (debug) {
|
|
142
|
+
console.log("KHR_materials_variants added MaterialVariants component to mesh:", mesh.name);
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.warn("Failed to add MaterialVariants component to mesh:", mesh.name, error);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (debug) {
|
|
149
|
+
console.log("KHR_materials_variants processed mesh with variants:", mesh.name, {
|
|
150
|
+
variants: this.variants,
|
|
151
|
+
mappings: mappings
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
// /**
|
|
160
|
+
// * Get all available variant names
|
|
161
|
+
// */
|
|
162
|
+
// getVariantNames(): string[] {
|
|
163
|
+
// return this.variants.map(variant => variant.name);
|
|
164
|
+
// }
|
|
165
|
+
|
|
166
|
+
// /**
|
|
167
|
+
// * Get variant by name
|
|
168
|
+
// */
|
|
169
|
+
// getVariantByName(name: string): MaterialVariant | undefined {
|
|
170
|
+
// return this.variants.find(variant => variant.name === name);
|
|
171
|
+
// }
|
|
172
|
+
|
|
173
|
+
// /**
|
|
174
|
+
// * Get variant by index
|
|
175
|
+
// */
|
|
176
|
+
// getVariantByIndex(index: number): MaterialVariant | undefined {
|
|
177
|
+
// return this.variants[index];
|
|
178
|
+
// }
|
|
179
|
+
}
|
|
@@ -9,6 +9,7 @@ import { type ConstructorConcrete, GLTF, type SourceIdentifier } from "../engine
|
|
|
9
9
|
import { getParam } from "../engine_utils.js";
|
|
10
10
|
import { NEEDLE_lightmaps } from "../extensions/NEEDLE_lightmaps.js";
|
|
11
11
|
import { EXT_texture_exr } from "./EXT_texture_exr.js";
|
|
12
|
+
import { NEEDLE_material_variants } from "./KHR_materials_variants.js";
|
|
12
13
|
import { NEEDLE_components } from "./NEEDLE_components.js";
|
|
13
14
|
import { NEEDLE_gameobject_data } from "./NEEDLE_gameobject_data.js";
|
|
14
15
|
import { NEEDLE_lighting_settings } from "./NEEDLE_lighting_settings.js";
|
|
@@ -105,6 +106,7 @@ export async function registerExtensions(loader: GLTFLoader, context: Context, u
|
|
|
105
106
|
loader.register(p => new NEEDLE_techniques_webgl(p, url));
|
|
106
107
|
loader.register(p => new NEEDLE_render_objects(p, url));
|
|
107
108
|
loader.register(p => new NEEDLE_progressive(p));
|
|
109
|
+
loader.register(p => new NEEDLE_material_variants(p));
|
|
108
110
|
loader.register(p => new EXT_texture_exr(p));
|
|
109
111
|
if (isResourceTrackingEnabled()) loader.register(p => new InternalUsageTrackerPlugin(p))
|
|
110
112
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { compareAssociation } from "./extension_utils.js"
|
|
2
2
|
export * from "./extensions.js"
|
|
3
|
+
export * from "./KHR_materials_variants.js"
|
|
3
4
|
export * from "./NEEDLE_animator_controller_model.js"
|
|
4
5
|
export { SceneLightSettings } from "./NEEDLE_lighting_settings.js"
|
|
5
6
|
export * from "./NEEDLE_progressive.js"
|
|
@@ -218,7 +218,11 @@ export class ButtonsFactory {
|
|
|
218
218
|
await generateAndInsertQRCode();
|
|
219
219
|
// TODO: make sure it doesnt overflow the screen
|
|
220
220
|
// we need to add the qrCodeContainer to the body to get the correct size
|
|
221
|
-
|
|
221
|
+
// TODO: we would need to search for the right engine element to insert this into if there are more
|
|
222
|
+
// Insert the QR code overlay inside the needle-engine element
|
|
223
|
+
const engine_element = document.body.querySelector("needle-engine");
|
|
224
|
+
const parent = engine_element || document.body;
|
|
225
|
+
parent.appendChild(qrCodeContainer);
|
|
222
226
|
const containerRect = qrCodeElement.getBoundingClientRect();
|
|
223
227
|
const buttonRect = qrCodeButton.getBoundingClientRect();
|
|
224
228
|
qrCodeContainer.style.left = (buttonRect.left + buttonRect.width * .5 - containerRect.width * .5) + "px";
|
|
@@ -245,7 +249,7 @@ export class ButtonsFactory {
|
|
|
245
249
|
document.fullscreenElement.appendChild(qrCodeContainer);
|
|
246
250
|
}
|
|
247
251
|
else
|
|
248
|
-
|
|
252
|
+
parent.appendChild(qrCodeContainer);
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
/** hides to QRCode overlay and unsubscribes from events */
|
|
@@ -287,6 +287,16 @@ export class NeedleMenuElement extends HTMLElement {
|
|
|
287
287
|
// TODO: make host full size again and move the buttons to a wrapper so that we can later easily open e.g. foldouts/dropdowns / use the whole canvas space
|
|
288
288
|
template.innerHTML = `<style>
|
|
289
289
|
|
|
290
|
+
/** Styling attributes that ensure the nested menu z-index does not cause it to overlay elements outside of <needle-engine> */
|
|
291
|
+
:host {
|
|
292
|
+
position: absolute;
|
|
293
|
+
width: 100%;
|
|
294
|
+
height: 100%;
|
|
295
|
+
z-index: 0;
|
|
296
|
+
top: 0;
|
|
297
|
+
pointer-events: none;
|
|
298
|
+
}
|
|
299
|
+
|
|
290
300
|
#root {
|
|
291
301
|
position: absolute;
|
|
292
302
|
width: auto;
|
|
@@ -206,7 +206,7 @@ export class EngineLoadingView implements ILoadingViewHandler {
|
|
|
206
206
|
this._loadingElement.style.display = "flex";
|
|
207
207
|
this._loadingElement.style.alignItems = "center";
|
|
208
208
|
this._loadingElement.style.justifyContent = "center";
|
|
209
|
-
this._loadingElement.style.zIndex =
|
|
209
|
+
this._loadingElement.style.zIndex = "0";
|
|
210
210
|
this._loadingElement.style.flexDirection = "column";
|
|
211
211
|
this._loadingElement.style.pointerEvents = "none";
|
|
212
212
|
this._loadingElement.style.color = "white";
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Material, Mesh } from "three";
|
|
2
|
+
import { syncField } from "../engine/engine_networking_auto.js";
|
|
3
|
+
|
|
4
|
+
import { serializable } from "../engine/engine_serialization_decorator.js";
|
|
5
|
+
import { type MaterialVariant } from "../engine/extensions/KHR_materials_variants.js";
|
|
6
|
+
import { Behaviour } from "./Component.js";
|
|
7
|
+
|
|
8
|
+
export class MaterialVariants extends Behaviour {
|
|
9
|
+
|
|
10
|
+
@syncField()
|
|
11
|
+
@serializable()
|
|
12
|
+
selectedVariant: string = "";
|
|
13
|
+
|
|
14
|
+
@serializable()
|
|
15
|
+
createMenu: boolean = true;
|
|
16
|
+
|
|
17
|
+
private _menu: HTMLElement | null = null;
|
|
18
|
+
|
|
19
|
+
private _variants: MaterialVariant[] = [];
|
|
20
|
+
private _variantMappings: Array<{ variants: number[]; material: number; primitiveIndex: number; }> = [];
|
|
21
|
+
private _originalMaterials: Material[] = [];
|
|
22
|
+
private _mesh: Mesh | null = null;
|
|
23
|
+
private _materialCache = new Map<string, Material>();
|
|
24
|
+
|
|
25
|
+
awake() {
|
|
26
|
+
this._mesh = this.gameObject as any;
|
|
27
|
+
if (!this._mesh || !this._mesh.isMesh) {
|
|
28
|
+
console.warn("MaterialVariants component must be attached to a Mesh object", this.gameObject);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this._initializeVariantData();
|
|
33
|
+
this._cacheOriginalMaterials();
|
|
34
|
+
|
|
35
|
+
if (this.createMenu) this._createVariantSelectionMenu();
|
|
36
|
+
}
|
|
37
|
+
onDestroy(): void {
|
|
38
|
+
this._menu?.remove();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private _createVariantSelectionMenu() {
|
|
42
|
+
const selectElement = document.createElement("select");
|
|
43
|
+
selectElement.addEventListener("change", (event) => {
|
|
44
|
+
const selectedVariant = (event.target as HTMLSelectElement).value;
|
|
45
|
+
this.selectVariant(selectedVariant);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Add options for each variant
|
|
49
|
+
this._variants.forEach(variant => {
|
|
50
|
+
const option = document.createElement("option");
|
|
51
|
+
option.value = variant.name;
|
|
52
|
+
option.textContent = this.name + ": " + variantNameToDisplayName(variant.name);
|
|
53
|
+
selectElement.appendChild(option);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this._menu = selectElement;
|
|
57
|
+
this.context.menu.appendChild(selectElement);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async start() {
|
|
61
|
+
if (this.selectedVariant && this.selectedVariant.length > 0) {
|
|
62
|
+
await this.selectVariant(this.selectedVariant);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private _initializeVariantData() {
|
|
67
|
+
if (!this._mesh) return;
|
|
68
|
+
|
|
69
|
+
// Get variant data from the mesh userData (set by the extension)
|
|
70
|
+
const variantData = this._mesh.userData.materialVariants;
|
|
71
|
+
if (variantData) {
|
|
72
|
+
this._variants = variantData.variants || [];
|
|
73
|
+
this._variantMappings = variantData.mappings || [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Also check if the root GLTF has variant data
|
|
77
|
+
let current = this._mesh.parent;
|
|
78
|
+
while (current) {
|
|
79
|
+
if (current.userData.materialVariants) {
|
|
80
|
+
this._variants = current.userData.materialVariants.variants || [];
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
current = current.parent;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private _cacheOriginalMaterials() {
|
|
88
|
+
if (!this._mesh) return;
|
|
89
|
+
|
|
90
|
+
if (Array.isArray(this._mesh.material)) {
|
|
91
|
+
this._originalMaterials = [...this._mesh.material];
|
|
92
|
+
} else {
|
|
93
|
+
this._originalMaterials = [this._mesh.material];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get all available variant names
|
|
99
|
+
*/
|
|
100
|
+
getVariantNames(): string[] {
|
|
101
|
+
return this._variants.map(variant => variant.name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the currently selected variant name
|
|
106
|
+
*/
|
|
107
|
+
getCurrentVariant(): string {
|
|
108
|
+
return this.selectedVariant;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Select a variant by name
|
|
113
|
+
* @param variantName The name of the variant to select
|
|
114
|
+
* @returns Promise that resolves to true if the variant was successfully selected, false otherwise
|
|
115
|
+
*/
|
|
116
|
+
async selectVariant(variantName: string): Promise<boolean> {
|
|
117
|
+
if (!this._mesh || !variantName) return false;
|
|
118
|
+
|
|
119
|
+
// Find the variant by name
|
|
120
|
+
const variantIndex = this._variants.findIndex(v => v.name === variantName);
|
|
121
|
+
if (variantIndex === -1) {
|
|
122
|
+
console.warn(`Material variant "${variantName}" not found. Available variants:`, this.getVariantNames());
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return await this.selectVariantByIndex(variantIndex);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Select a variant by index
|
|
131
|
+
* @param variantIndex The index of the variant to select
|
|
132
|
+
* @returns Promise that resolves to true if the variant was successfully selected, false otherwise
|
|
133
|
+
*/
|
|
134
|
+
async selectVariantByIndex(variantIndex: number): Promise<boolean> {
|
|
135
|
+
if (!this._mesh || variantIndex < 0 || variantIndex >= this._variants.length) return false;
|
|
136
|
+
|
|
137
|
+
const variant = this._variants[variantIndex];
|
|
138
|
+
|
|
139
|
+
// Find mappings that include this variant
|
|
140
|
+
const applicableMappings = this._variantMappings.filter(mapping =>
|
|
141
|
+
mapping.variants.includes(variantIndex)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (applicableMappings.length === 0) {
|
|
145
|
+
console.warn(`No material mappings found for variant "${variant.name}"`);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Apply the variant materials to specific primitives
|
|
150
|
+
for (const mapping of applicableMappings) {
|
|
151
|
+
const materialIndex = mapping.material;
|
|
152
|
+
const primitiveIndex = mapping.primitiveIndex;
|
|
153
|
+
const material = await this._getMaterial(materialIndex);
|
|
154
|
+
|
|
155
|
+
if (material) {
|
|
156
|
+
if (Array.isArray(this._mesh.material)) {
|
|
157
|
+
// For multi-material meshes, update the specific primitive's material
|
|
158
|
+
if (primitiveIndex < this._mesh.material.length) {
|
|
159
|
+
this._mesh.material[primitiveIndex] = material;
|
|
160
|
+
} else {
|
|
161
|
+
console.warn(`Primitive index ${primitiveIndex} out of bounds for material array of length ${this._mesh.material.length}`);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
// Single material mesh - replace the entire material
|
|
165
|
+
this._mesh.material = material;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.selectedVariant = variant.name;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Reset to the original materials
|
|
176
|
+
*/
|
|
177
|
+
resetToOriginal(): void {
|
|
178
|
+
if (!this._mesh) return;
|
|
179
|
+
|
|
180
|
+
if (Array.isArray(this._mesh.material)) {
|
|
181
|
+
this._mesh.material = [...this._originalMaterials];
|
|
182
|
+
} else {
|
|
183
|
+
this._mesh.material = this._originalMaterials[0];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.selectedVariant = "";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async _getMaterial(materialIndex: number): Promise<Material | null> {
|
|
190
|
+
// First check if we have it cached
|
|
191
|
+
const cacheKey = `material_${materialIndex}`;
|
|
192
|
+
if (this._materialCache.has(cacheKey)) {
|
|
193
|
+
return this._materialCache.get(cacheKey)!;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Try to get the material from the GLTF parser
|
|
197
|
+
const variantData = this._mesh?.userData.materialVariants;
|
|
198
|
+
if (variantData && variantData.parser) {
|
|
199
|
+
try {
|
|
200
|
+
const material = await variantData.parser.getDependency('material', materialIndex);
|
|
201
|
+
this._materialCache.set(cacheKey, material);
|
|
202
|
+
return material;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.warn(`Failed to load material at index ${materialIndex}:`, error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Check if a variant exists
|
|
213
|
+
* @param variantName The name of the variant to check
|
|
214
|
+
* @returns True if the variant exists
|
|
215
|
+
*/
|
|
216
|
+
hasVariant(variantName: string): boolean {
|
|
217
|
+
return this._variants.some(v => v.name === variantName);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get the number of available variants
|
|
222
|
+
*/
|
|
223
|
+
getVariantCount(): number {
|
|
224
|
+
return this._variants.length;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function variantNameToDisplayName(name: string) {
|
|
229
|
+
// Make uppercase after space and beginning (unless number etc)
|
|
230
|
+
return name.replace(/(?:^|\s)\S/g, (match) => match.toUpperCase());
|
|
231
|
+
}
|
|
@@ -565,6 +565,11 @@ export class OrbitControls extends Behaviour implements ICameraController {
|
|
|
565
565
|
}
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
+
onPausedChanged(isPaused: boolean): void {
|
|
569
|
+
if (!this._controls) return;
|
|
570
|
+
if (isPaused) this._controls.enabled = false;
|
|
571
|
+
}
|
|
572
|
+
|
|
568
573
|
|
|
569
574
|
/** @internal */
|
|
570
575
|
onBeforeRender() {
|
|
@@ -86,6 +86,7 @@ export { Light } from "../Light.js";
|
|
|
86
86
|
export { LODModel } from "../LODGroup.js";
|
|
87
87
|
export { LODGroup } from "../LODGroup.js";
|
|
88
88
|
export { LookAtConstraint } from "../LookAtConstraint.js";
|
|
89
|
+
export { MaterialVariants } from "../MaterialVariants.js";
|
|
89
90
|
export { NeedleMenu } from "../NeedleMenu.js";
|
|
90
91
|
export { NestedGltf } from "../NestedGltf.js";
|
|
91
92
|
export { Networking } from "../Networking.js";
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{BufferGeometry as V,Mesh as j,Box3 as ie,Vector3 as I,Sphere as ve,CompressedTexture as Re,Texture as N,Matrix3 as Ge,InterleavedBuffer as je,InterleavedBufferAttribute as Ne,BufferAttribute as Ue,TextureLoader as Fe,Matrix4 as Le,Clock as We,MeshStandardMaterial as ze}from"./three-DuDKwKB8.min.js";import{DRACOLoader as qe,KTX2Loader as Ve,MeshoptDecoder as Xe,GLTFLoader as ae}from"./three-examples-D2zemuAM.min.js";const be="3.3.0";globalThis.GLTF_PROGRESSIVE_VERSION=be,console.debug(`[gltf-progressive] version ${be}`);let k="https://www.gstatic.com/draco/versioned/decoders/1.5.7/",U="https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/";const Ke=k,He=U,_e=new URL(k+"draco_decoder.js");_e.searchParams.append("range","true"),fetch(_e,{method:"GET",headers:{Range:"bytes=0-1"}}).catch(i=>{console.debug(`Failed to fetch remote Draco decoder from ${k} (offline: ${typeof navigator<"u"?navigator.onLine:"unknown"})`),k===Ke&&De("./include/draco/"),U===He&&Me("./include/ktx2/")}).finally(()=>{Oe()});const Ye=()=>({dracoDecoderPath:k,ktx2TranscoderPath:U});function De(i){k=i,P&&P[ue]!=k?(console.debug("Updating Draco decoder path to "+i),P[ue]=k,P.setDecoderPath(k),P.preload()):console.debug("Setting Draco decoder path to "+i)}function Me(i){U=i,C&&C.transcoderPath!=U?(console.debug("Updating KTX2 transcoder path to "+i),C.setTranscoderPath(U),C.init()):console.debug("Setting KTX2 transcoder path to "+i)}function Z(i){return Oe(),i?C.detectSupport(i):i!==null&&console.warn("No renderer provided to detect ktx2 support - loading KTX2 textures might fail"),{dracoLoader:P,ktx2Loader:C,meshoptDecoder:ee}}function le(i){i.dracoLoader||i.setDRACOLoader(P),i.ktx2Loader||i.setKTX2Loader(C),i.meshoptDecoder||i.setMeshoptDecoder(ee)}const ue=Symbol("dracoDecoderPath");let P,ee,C;function Oe(){P||(P=new qe,P[ue]=k,P.setDecoderPath(k),P.setDecoderConfig({type:"js"}),P.preload()),C||(C=new Ve,C.setTranscoderPath(U),C.init()),ee||(ee=Xe)}const de=new WeakMap;function ce(i,t){let e=de.get(i);e?e=Object.assign(e,t):e=t,de.set(i,e)}const Qe=ae.prototype.load;function Je(...i){const t=de.get(this);let e=i[0];const r=new URL(e,window.location.href);if(r.hostname.endsWith("needle.tools")){const n=t?.progressive!==void 0?t.progressive:!0,s=t?.usecase?t.usecase:"default";n?this.requestHeader.Accept=`*/*;progressive=allowed;usecase=${s}`:this.requestHeader.Accept=`*/*;usecase=${s}`,e=r.toString()}return i[0]=e,Qe?.call(this,...i)}ae.prototype.load=Je,F("debugprogressive");function F(i){if(typeof window>"u")return!1;const t=new URL(window.location.href).searchParams.get(i);return t==null||t==="0"||t==="false"?!1:t===""?!0:t}function Ze(i,t){if(t===void 0||t.startsWith("./")||t.startsWith("http")||i===void 0)return t;const e=i.lastIndexOf("/");if(e>=0){const r=i.substring(0,e+1);for(;r.endsWith("/")&&t.startsWith("/");)t=t.substring(1);return r+t}return t}let te;function Se(){return te!==void 0||(te=/iPhone|iPad|iPod|Android|IEMobile/i.test(navigator.userAgent),F("debugprogressive")&&console.log("[glTF Progressive]: isMobileDevice",te)),te}function Pe(){if(typeof window>"u")return!1;const i=new URL(window.location.href),t=i.hostname==="localhost"||/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(i.hostname);return i.hostname==="127.0.0.1"||t}class et{maxConcurrent;_running=new Map;_queue=[];debug=!1;constructor(t=100,e={}){this.maxConcurrent=t,this.debug=e.debug??!1,window.requestAnimationFrame(this.tick)}tick=()=>{this.internalUpdate(),setTimeout(this.tick,10)};slot(t){return this.debug&&console.debug(`[PromiseQueue]: Requesting slot for key ${t}, running: ${this._running.size}, waiting: ${this._queue.length}`),new Promise(e=>{this._queue.push({key:t,resolve:e})})}add(t,e){this._running.has(t)||(this._running.set(t,e),e.finally(()=>{this._running.delete(t),this.debug&&console.debug(`[PromiseQueue]: Promise finished now running: ${this._running.size}, waiting: ${this._queue.length}. (finished ${t})`)}),this.debug&&console.debug(`[PromiseQueue]: Added new promise, now running: ${this._running.size}, waiting: ${this._queue.length}. (added ${t})`))}internalUpdate(){const t=this.maxConcurrent-this._running.size;for(let e=0;e<t&&this._queue.length>0;e++){this.debug&&console.debug(`[PromiseQueue]: Running ${this._running.size} promises, waiting for ${this._queue.length} more.`);const{key:r,resolve:n}=this._queue.shift();n({use:s=>this.add(r,s)})}}}const tt=typeof window>"u"&&typeof document>"u",he=Symbol("needle:raycast-mesh");function z(i){return i?.[he]instanceof V?i[he]:null}function rt(i,t){if((i.type==="Mesh"||i.type==="SkinnedMesh")&&!z(i)){const e=nt(t);e.userData={isRaycastMesh:!0},i[he]=e}}function st(i=!0){if(i){if(X)return;const t=X=j.prototype.raycast;j.prototype.raycast=function(e,r){const n=this,s=z(n);let o;s&&n.isMesh&&(o=n.geometry,n.geometry=s),t.call(this,e,r),o&&(n.geometry=o)}}else{if(!X)return;j.prototype.raycast=X,X=null}}let X=null;function nt(i){const t=new V;for(const e in i.attributes)t.setAttribute(e,i.getAttribute(e));return t.setIndex(i.getIndex()),t}const R=new Array,h=F("debugprogressive");let re,W=-1;if(h){let i=function(){W+=1,W>=t&&(W=-1),console.log(`Toggle LOD level [${W}]`)},t=6;window.addEventListener("keyup",e=>{e.key==="p"&&i(),e.key==="w"&&(re=!re,console.log(`Toggle wireframe [${re}]`));const r=parseInt(e.key);!isNaN(r)&&r>=0&&(W=r,console.log(`Set LOD level to [${W}]`))})}function Te(i){if(h)if(Array.isArray(i))for(const t of i)Te(t);else i&&"wireframe"in i&&(i.wireframe=re===!0)}const K=new Array;let ot=0;const it=Se()?2:10;function at(i){if(K.length<it){const e=K.length;h&&console.warn(`[Worker] Creating new worker #${e}`);const r=ye.createWorker(i||{});return K.push(r),r}const t=ot++%K.length;return K[t]}class ye{worker;static async createWorker(t){const e=new Worker(new URL("/loader.worker-CrU5fNbR.js",import.meta.url),{type:"module"});return new ye(e,t)}_running=[];_webglRenderer=null;async load(t,e){const r=Ye();let n=e?.renderer;n||(this._webglRenderer??=(async()=>{const{WebGLRenderer:l}=await import("./three-DuDKwKB8.min.js").then(a=>a.THREE);return new l})(),n=await this._webglRenderer);const s=Z(n).ktx2Loader.workerConfig;t instanceof URL?t=t.toString():t.startsWith("file:")?t=URL.createObjectURL(new Blob([t])):!t.startsWith("blob:")&&!t.startsWith("http:")&&!t.startsWith("https:")&&(t=new URL(t,window.location.href).toString());const o={type:"load",url:t,dracoDecoderPath:r.dracoDecoderPath,ktx2TranscoderPath:r.ktx2TranscoderPath,ktx2LoaderConfig:s};return this._debug&&console.debug("[Worker] Sending load request",o),this.worker.postMessage(o),new Promise(l=>{this._running.push({url:t.toString(),resolve:l})})}_debug=!1;constructor(t,e){this.worker=t,this._debug=e.debug??!1,t.onmessage=r=>{const n=r.data;switch(this._debug&&console.log("[Worker] EVENT",n),n.type){case"loaded-gltf":for(const s of this._running)if(s.url===n.result.url){lt(n.result),s.resolve(n.result);const o=s.url;o.startsWith("blob:")&&URL.revokeObjectURL(o)}}},t.onerror=r=>{console.error("[Worker] Error in gltf-progressive worker:",r)},t.postMessage({type:"init"})}}function lt(i){for(const t of i.geometries){const e=t.geometry,r=new V;if(r.name=e.name||"",e.index){const n=e.index;r.setIndex(ge(n))}for(const n in e.attributes){const s=e.attributes[n],o=ge(s);r.setAttribute(n,o)}if(e.morphAttributes)for(const n in e.morphAttributes){const s=e.morphAttributes[n].map(o=>ge(o));r.morphAttributes[n]=s}if(r.morphTargetsRelative=e.morphTargetsRelative??!1,r.boundingBox=new ie,r.boundingBox.min=new I(e.boundingBox?.min.x,e.boundingBox?.min.y,e.boundingBox?.min.z),r.boundingBox.max=new I(e.boundingBox?.max.x,e.boundingBox?.max.y,e.boundingBox?.max.z),r.boundingSphere=new ve(new I(e.boundingSphere?.center.x,e.boundingSphere?.center.y,e.boundingSphere?.center.z),e.boundingSphere?.radius),e.groups)for(const n of e.groups)r.addGroup(n.start,n.count,n.materialIndex);e.userData&&(r.userData=e.userData),t.geometry=r}for(const t of i.textures){const e=t.texture;let r=null;if(e.isCompressedTexture){const n=e.mipmaps,s=e.image?.width||e.source?.data?.width||-1,o=e.image?.height||e.source?.data?.height||-1;r=new Re(n,s,o,e.format,e.type,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.anisotropy,e.colorSpace)}else r=new N(e.image,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.colorSpace),r.mipmaps=e.mipmaps,r.channel=e.channel,r.source.data=e.source.data,r.flipY=e.flipY,r.premultiplyAlpha=e.premultiplyAlpha,r.unpackAlignment=e.unpackAlignment,r.matrix=new Ge(...e.matrix.elements);if(!r){console.error("[Worker] Failed to create new texture from received data. Texture is not a CompressedTexture or Texture.");continue}t.texture=r}return i}function ge(i){let t=i;if("isInterleavedBufferAttribute"in i&&i.isInterleavedBufferAttribute){const e=i.data,r=e.array,n=new je(r,e.stride);t=new Ne(n,i.itemSize,r.byteOffset,i.normalized),t.offset=i.offset}else"isBufferAttribute"in i&&i.isBufferAttribute&&(t=new Ue(i.array,i.itemSize,i.normalized),t.usage=i.usage,t.gpuType=i.gpuType,t.updateRanges=i.updateRanges);return t}const ut=F("gltf-progressive-worker"),dt=F("gltf-progressive-reduce-mipmaps"),fe=Symbol("needle-progressive-texture"),$="NEEDLE_progressive";class g{get name(){return $}static getMeshLODExtension(t){const e=this.getAssignedLODInformation(t);return e?.key?this.lodInfos.get(e.key):null}static getPrimitiveIndex(t){return this.getAssignedLODInformation(t)?.index??-1}static getMaterialMinMaxLODsCount(t,e){const r=this,n="LODS:minmax",s=t[n];if(s!=null)return s;if(e||(e={min_count:1/0,max_count:0,lods:[]}),Array.isArray(t)){for(const l of t)this.getMaterialMinMaxLODsCount(l,e);return t[n]=e,e}if(h==="verbose"&&console.log("getMaterialMinMaxLODsCount",t),t.type==="ShaderMaterial"||t.type==="RawShaderMaterial"){const l=t;for(const a of Object.keys(l.uniforms)){const u=l.uniforms[a].value;u?.isTexture===!0&&o(u,e)}}else if(t.isMaterial)for(const l of Object.keys(t)){const a=t[l];a?.isTexture===!0&&o(a,e)}else h&&console.warn(`[getMaterialMinMaxLODsCount] Unsupported material type: ${t.type}`);return t[n]=e,e;function o(l,a){const u=r.getAssignedLODInformation(l);if(u){const f=r.lodInfos.get(u.key);if(f&&f.lods){a.min_count=Math.min(a.min_count,f.lods.length),a.max_count=Math.max(a.max_count,f.lods.length);for(let w=0;w<f.lods.length;w++){const v=f.lods[w];v.width&&(a.lods[w]=a.lods[w]||{min_height:1/0,max_height:0},a.lods[w].min_height=Math.min(a.lods[w].min_height,v.height),a.lods[w].max_height=Math.max(a.lods[w].max_height,v.height))}}}}}static hasLODLevelAvailable(t,e){if(Array.isArray(t)){for(const s of t)if(this.hasLODLevelAvailable(s,e))return!0;return!1}if(t.isMaterial===!0){for(const s of Object.keys(t)){const o=t[s];if(o&&o.isTexture&&this.hasLODLevelAvailable(o,e))return!0}return!1}else if(t.isGroup===!0){for(const s of t.children)if(s.isMesh===!0&&this.hasLODLevelAvailable(s,e))return!0}let r,n;if(t.isMesh?r=t.geometry:(t.isBufferGeometry||t.isTexture)&&(r=t),r&&r?.userData?.LODS){const s=r.userData.LODS;if(n=this.lodInfos.get(s.key),e===void 0)return n!=null;if(n)return Array.isArray(n.lods)?e<n.lods.length:e===0}return!1}static assignMeshLOD(t,e){if(!t)return Promise.resolve(null);if(t instanceof j||t.isMesh===!0){const r=t.geometry,n=this.getAssignedLODInformation(r);if(!n)return Promise.resolve(null);for(const s of R)s.onBeforeGetLODMesh?.(t,e);return t["LOD:requested level"]=e,g.getOrLoadLOD(r,e).then(s=>{if(Array.isArray(s)){const o=n.index||0;s=s[o]}return t["LOD:requested level"]===e&&(delete t["LOD:requested level"],s&&r!=s&&(s?.isBufferGeometry?t.geometry=s:h&&console.error("Invalid LOD geometry",s))),s}).catch(s=>(console.error("Error loading mesh LOD",t,s),null))}else h&&console.error("Invalid call to assignMeshLOD: Request mesh LOD but the object is not a mesh",t);return Promise.resolve(null)}static assignTextureLOD(t,e=0){if(!t)return Promise.resolve(null);if(t.isMesh===!0){const r=t;if(Array.isArray(r.material)){const n=new Array;for(const s of r.material){const o=this.assignTextureLOD(s,e);n.push(o)}return Promise.all(n).then(s=>{const o=new Array;for(const l of s)Array.isArray(l)&&o.push(...l);return o})}else return this.assignTextureLOD(r.material,e)}if(t.isMaterial===!0){const r=t,n=[],s=new Array;if(r.uniforms&&(r.isRawShaderMaterial||r.isShaderMaterial===!0)){const o=r;for(const l of Object.keys(o.uniforms)){const a=o.uniforms[l].value;if(a?.isTexture===!0){const u=this.assignTextureLODForSlot(a,e,r,l).then(f=>(f&&o.uniforms[l].value!=f&&(o.uniforms[l].value=f,o.uniformsNeedUpdate=!0),f));n.push(u),s.push(l)}}}else for(const o of Object.keys(r)){const l=r[o];if(l?.isTexture===!0){const a=this.assignTextureLODForSlot(l,e,r,o);n.push(a),s.push(o)}}return Promise.all(n).then(o=>{const l=new Array;for(let a=0;a<o.length;a++){const u=o[a],f=s[a];u&&u.isTexture===!0?l.push({material:r,slot:f,texture:u,level:e}):l.push({material:r,slot:f,texture:null,level:e})}return l})}if(t instanceof N||t.isTexture===!0){const r=t;return this.assignTextureLODForSlot(r,e,null,null)}return Promise.resolve(null)}static assignTextureLODForSlot(t,e,r,n){return t?.isTexture!==!0?Promise.resolve(null):n==="glyphMap"?Promise.resolve(t):g.getOrLoadLOD(t,e).then(s=>{if(Array.isArray(s))return console.warn("Progressive: Got an array of textures for a texture slot, this should not happen..."),null;if(s?.isTexture===!0){if(s!=t&&r&&n){const o=r[n];if(o&&!h){const l=this.getAssignedLODInformation(o);if(l&&l?.level<e)return h==="verbose"&&console.warn("Assigned texture level is already higher: ",l.level,e,r,o,s),null}if(dt&&s.mipmaps){const l=s.mipmaps.length;s.mipmaps.length=Math.min(s.mipmaps.length,3),l!==s.mipmaps.length&&h&&console.debug(`Reduced mipmap count from ${l} to ${s.mipmaps.length} for ${s.uuid}: ${s.image?.width}x${s.image?.height}.`)}r[n]=s}return s}else h=="verbose"&&console.warn("No LOD found for",t,e);return null}).catch(s=>(console.error("Error loading LOD",t,s),null))}parser;url;constructor(t){const e=t.options.path;h&&console.log("Progressive extension registered for",e),this.parser=t,this.url=e}_isLoadingMesh;loadMesh=t=>{if(this._isLoadingMesh)return null;const e=this.parser.json.meshes[t]?.extensions?.[$];return e?(this._isLoadingMesh=!0,this.parser.getDependency("mesh",t).then(r=>(this._isLoadingMesh=!1,r&&g.registerMesh(this.url,e.guid,r,e.lods?.length,0,e),r))):null};afterRoot(t){return h&&console.log("AFTER",this.url,t),this.parser.json.textures?.forEach((e,r)=>{if(e?.extensions){const n=e?.extensions[$];if(n){if(!n.lods){h&&console.warn("Texture has no LODs",n);return}let s=!1;for(const o of this.parser.associations.keys())o.isTexture===!0&&this.parser.associations.get(o)?.textures===r&&(s=!0,g.registerTexture(this.url,o,n.lods?.length,r,n));s||this.parser.getDependency("texture",r).then(o=>{o&&g.registerTexture(this.url,o,n.lods?.length,r,n)})}}}),this.parser.json.meshes?.forEach((e,r)=>{if(e?.extensions){const n=e?.extensions[$];if(n&&n.lods){for(const s of this.parser.associations.keys())if(s.isMesh){const o=this.parser.associations.get(s);o?.meshes===r&&g.registerMesh(this.url,n.guid,s,n.lods.length,o.primitives,n)}}}}),null}static registerTexture=(t,e,r,n,s)=>{if(!e){h&&console.error("gltf-progressive: Called register texture without texture");return}if(h){const l=e.image?.width||e.source?.data?.width||0,a=e.image?.height||e.source?.data?.height||0;console.log(`> Progressive: register texture[${n}] "${e.name||e.uuid}", Current: ${l}x${a}, Max: ${s.lods[0]?.width}x${s.lods[0]?.height}, uuid: ${e.uuid}`,s,e)}e.source&&(e.source[fe]=s);const o=s.guid;g.assignLODInformation(t,e,o,r,n),g.lodInfos.set(o,s),g.lowresCache.set(o,e)};static registerMesh=(t,e,r,n,s,o)=>{const l=r.geometry;if(!l){h&&console.warn("gltf-progressive: Register mesh without geometry");return}l.userData||(l.userData={}),h&&console.log("> Progressive: register mesh "+r.name,{index:s,uuid:r.uuid},o,r),g.assignLODInformation(t,l,e,n,s),g.lodInfos.set(e,o);let a=g.lowresCache.get(e);a?a.push(r.geometry):a=[r.geometry],g.lowresCache.set(e,a),n>0&&!z(r)&&rt(r,l);for(const u of R)u.onRegisteredNewMesh?.(r,o)};static lodInfos=new Map;static previouslyLoaded=new Map;static lowresCache=new Map;static workers=[];static _workersIndex=0;static async getOrLoadLOD(t,e){const r=h=="verbose",n=this.getAssignedLODInformation(t);if(!n)return h&&console.warn(`[gltf-progressive] No LOD information found: ${t.name}, uuid: ${t.uuid}, type: ${t.type}`,t),null;const s=n?.key;let o;if(t.isTexture===!0){const l=t;l.source&&l.source[fe]&&(o=l.source[fe])}if(o||(o=g.lodInfos.get(s)),o){if(e>0){let u=!1;const f=Array.isArray(o.lods);if(f&&e>=o.lods.length?u=!0:f||(u=!0),u)return this.lowresCache.get(s)}const l=Array.isArray(o.lods)?o.lods[e]?.path:o.lods;if(!l)return h&&!o["missing:uri"]&&(o["missing:uri"]=!0,console.warn("Missing uri for progressive asset for LOD "+e,o)),null;const a=Ze(n.url,l);if(a.endsWith(".glb")||a.endsWith(".gltf")){if(!o.guid)return console.warn("missing pointer for glb/gltf texture",o),null;const u=a+"_"+o.guid,f=await this.queue.slot(a),w=this.previouslyLoaded.get(u);if(w!==void 0){r&&console.log(`LOD ${e} was already loading/loaded: ${u}`);let p=await w.catch(y=>(console.error(`Error loading LOD ${e} from ${a}
|
|
2
|
-
`,y),null)),m=!1;if(p==null||(p instanceof N&&t instanceof N?p.image?.data||p.source?.data?p=this.copySettings(t,p):(m=!0,this.previouslyLoaded.delete(u)):p instanceof V&&t instanceof V&&(p.attributes.position?.array||(m=!0,this.previouslyLoaded.delete(u)))),!m)return p}if(!f.use)return h&&console.log(`LOD ${e} was aborted: ${a}`),null;const v=o,T=new Promise(async(p,m)=>{if(ut){const x=await(await at({})).load(a);if(x.textures.length>0)for(const d of x.textures){let c=d.texture;return g.assignLODInformation(n.url,c,s,e,void 0),t instanceof N&&(c=this.copySettings(t,c)),c&&(c.guid=v.guid),p(c)}if(x.geometries.length>0){const d=new Array;for(const c of x.geometries){const L=c.geometry;g.assignLODInformation(n.url,L,s,e,c.primitiveIndex),d.push(L)}return p(d)}return p(null)}const y=new ae;le(y),h&&(await new Promise(x=>setTimeout(x,1e3)),r&&console.warn("Start loading (delayed) "+a,v.guid));let A=a;if(v&&Array.isArray(v.lods)){const x=v.lods[e];x.hash&&(A+="?v="+x.hash)}const b=await y.loadAsync(A).catch(x=>(console.error(`Error loading LOD ${e} from ${a}
|
|
3
|
-
`,x),p(null)));if(!b)return p(null);const E=b.parser;r&&console.log("Loading finished "+a,v.guid);let D=0;if(b.parser.json.textures){let x=!1;for(const d of b.parser.json.textures){if(d?.extensions){const c=d?.extensions[$];if(c?.guid&&c.guid===v.guid){x=!0;break}}D++}if(x){let d=await E.getDependency("texture",D);return d&&g.assignLODInformation(n.url,d,s,e,void 0),r&&console.log('change "'+t.name+'" \u2192 "'+d.name+'"',a,D,d,u),t instanceof N&&(d=this.copySettings(t,d)),d&&(d.guid=v.guid),p(d)}else h&&console.warn("Could not find texture with guid",v.guid,b.parser.json)}if(D=0,b.parser.json.meshes){let x=!1;for(const d of b.parser.json.meshes){if(d?.extensions){const c=d?.extensions[$];if(c?.guid&&c.guid===v.guid){x=!0;break}}D++}if(x){const d=await E.getDependency("mesh",D);if(r&&console.log(`Loaded Mesh "${d.name}"`,a,D,d,u),d.isMesh===!0){const c=d.geometry;return g.assignLODInformation(n.url,c,s,e,0),p(c)}else{const c=new Array;for(let L=0;L<d.children.length;L++){const O=d.children[L];if(O.isMesh===!0){const S=O.geometry;g.assignLODInformation(n.url,S,s,e,L),c.push(S)}}return p(c)}}else h&&console.warn("Could not find mesh with guid",v.guid,b.parser.json)}return p(null)});return this.previouslyLoaded.set(u,T),f.use(T),await T}else if(t instanceof N){r&&console.log("Load texture from uri: "+a);const u=await new Fe().loadAsync(a);return u?(u.guid=o.guid,u.flipY=!1,u.needsUpdate=!0,u.colorSpace=t.colorSpace,r&&console.log(o,u)):h&&console.warn("failed loading",a),u}}else h&&console.warn(`Can not load LOD ${e}: no LOD info found for "${s}" ${t.name}`,t.type);return null}static maxConcurrent=50;static queue=new et(g.maxConcurrent,{debug:h!=!1});static assignLODInformation(t,e,r,n,s){if(!e)return;e.userData||(e.userData={});const o=new ct(t,r,n,s);e.userData.LODS=o,"source"in e&&typeof e.source=="object"&&(e.source.LODS=o)}static getAssignedLODInformation(t){return t?t.userData?.LODS?t.userData.LODS:"source"in t&&t.source?.LODS?t.source.LODS:null:null}static copySettings(t,e){return e?(h==="verbose"&&console.debug(`Copy texture settings
|
|
4
|
-
`,t.uuid,`
|
|
5
|
-
`,e.uuid),e=e.clone(),e.offset=t.offset,e.repeat=t.repeat,e.colorSpace=t.colorSpace,e.magFilter=t.magFilter,e.minFilter=t.minFilter,e.wrapS=t.wrapS,e.wrapT=t.wrapT,e.flipY=t.flipY,e.anisotropy=t.anisotropy,e.mipmaps||(e.generateMipmaps=t.generateMipmaps),e):t}}class ct{url;key;level;index;constructor(t,e,r,n){this.url=t,this.key=e,this.level=r,n!=null&&(this.index=n)}}class me{static addPromise=(t,e,r,n)=>{n.forEach(s=>{s.add(t,e,r)})};frame_start;frame_capture_end;ready;_resolve;_signal;get awaitedCount(){return this._addedCount}get resolvedCount(){return this._resolvedCount}get currentlyAwaiting(){return this._awaiting.length}_resolved=!1;_addedCount=0;_resolvedCount=0;_awaiting=[];_maxPromisesPerObject=1;constructor(t,e){const r=Math.max(e.frames??2,2);this.frame_start=t,this.frame_capture_end=t+r,this.ready=new Promise(n=>{this._resolve=n}),this.ready.finally(()=>{this._resolved=!0,this._awaiting.length=0}),this._signal=e.signal,this._signal?.addEventListener("abort",()=>{this.resolveNow()}),this._maxPromisesPerObject=Math.max(1,e.maxPromisesPerObject??1)}_currentFrame=0;update(t){this._currentFrame=t,(this._signal?.aborted||this._currentFrame>this.frame_capture_end&&this._awaiting.length===0)&&this.resolveNow()}_seen=new WeakMap;add(t,e,r){if(this._resolved){h&&console.warn("PromiseGroup: Trying to add a promise to a resolved group, ignoring.");return}if(!(this._currentFrame>this.frame_capture_end)){if(this._maxPromisesPerObject>=1)if(this._seen.has(e)){let n=this._seen.get(e);if(n>=this._maxPromisesPerObject){h&&console.warn("PromiseGroup: Already awaiting object ignoring new promise for it.");return}this._seen.set(e,n+1)}else this._seen.set(e,1);this._awaiting.push(r),this._addedCount++,r.finally(()=>{this._resolvedCount++,this._awaiting.splice(this._awaiting.indexOf(r),1)})}}resolveNow(){this._resolved||this._resolve?.({awaited_count:this._addedCount,resolved_count:this._resolvedCount,cancelled:this._signal?.aborted??!1})}}const B=F("debugprogressive"),ht=F("noprogressive"),pe=Symbol("Needle:LODSManager"),xe=Symbol("Needle:LODState"),G=Symbol("Needle:CurrentLOD"),M={mesh_lod:-1,texture_lod:-1};let H=class _{static debugDrawLine;static getObjectLODState(t){return t[xe]}static addPlugin(t){R.push(t)}static removePlugin(t){const e=R.indexOf(t);e>=0&&R.splice(e,1)}static get(t,e){if(t[pe])return console.debug("[gltf-progressive] LODsManager already exists for this renderer"),t[pe];const r=new _(t,{engine:"unknown",...e});return t[pe]=r,r}renderer;context;projectionScreenMatrix=new Le;get plugins(){return R}overrideLodLevel=void 0;targetTriangleDensity=2e5;skinnedMeshAutoUpdateBoundsInterval=30;updateInterval="auto";#e=1;pause=!1;manual=!1;_newPromiseGroups=[];_promiseGroupIds=0;awaitLoading(t){const e=this._promiseGroupIds++,r=new me(this.#s,{...t});this._newPromiseGroups.push(r);const n=performance.now();return r.ready.finally(()=>{const s=this._newPromiseGroups.indexOf(r);s>=0&&(this._newPromiseGroups.splice(s,1),Pe()&&performance.measure("LODsManager:awaitLoading",{start:n,detail:{id:e,name:t?.name,awaited:r.awaitedCount,resolved:r.resolvedCount}}))}),r.ready}_postprocessPromiseGroups(){if(this._newPromiseGroups.length!==0)for(let t=this._newPromiseGroups.length-1;t>=0;t--)this._newPromiseGroups[t].update(this.#s)}_lodchangedlisteners=[];addEventListener(t,e){t==="changed"&&this._lodchangedlisteners.push(e)}removeEventListener(t,e){if(t==="changed"){const r=this._lodchangedlisteners.indexOf(e);r>=0&&this._lodchangedlisteners.splice(r,1)}}constructor(t,e){this.renderer=t,this.context={...e}}#t;#o=new We;#s=0;#n=0;#i=0;#r=0;_fpsBuffer=[60,60,60,60,60];enable(){if(this.#t)return;console.debug("[gltf-progressive] Enabling LODsManager for renderer");let t=0;this.#t=this.renderer.render;const e=this;Z(this.renderer),this.renderer.render=function(r,n){const s=e.renderer.getRenderTarget();(s==null||"isXRRenderTarget"in s&&s.isXRRenderTarget)&&(t=0,e.#s+=1,e.#n=e.#o.getDelta(),e.#i+=e.#n,e._fpsBuffer.shift(),e._fpsBuffer.push(1/e.#n),e.#r=e._fpsBuffer.reduce((l,a)=>l+a)/e._fpsBuffer.length,B&&e.#s%200===0&&console.log("FPS",Math.round(e.#r),"Interval:",e.#e));const o=t++;e.#t.call(this,r,n),e.onAfterRender(r,n,o)}}disable(){this.#t&&(console.debug("[gltf-progressive] Disabling LODsManager for renderer"),this.renderer.render=this.#t,this.#t=void 0)}update(t,e){this.internalUpdate(t,e)}onAfterRender(t,e,r){if(this.pause)return;const n=this.renderer.renderLists.get(t,0).opaque;let s=!0;if(n.length===1){const o=n[0].material;(o.name==="EffectMaterial"||o.name==="CopyShader")&&(s=!1)}if((e.parent&&e.parent.type==="CubeCamera"||r>=1&&e.type==="OrthographicCamera")&&(s=!1),s){if(ht||(this.updateInterval==="auto"?this.#r<40&&this.#e<10?(this.#e+=1,B&&console.warn("\u2193 Reducing LOD updates",this.#e,this.#r.toFixed(0))):this.#r>=60&&this.#e>1&&(this.#e-=1,B&&console.warn("\u2191 Increasing LOD updates",this.#e,this.#r.toFixed(0))):this.#e=this.updateInterval,this.#e>0&&this.#s%this.#e!=0))return;this.internalUpdate(t,e),this._postprocessPromiseGroups()}}internalUpdate(t,e){const r=this.renderer.renderLists.get(t,0),n=r.opaque;this.projectionScreenMatrix.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse);const s=this.targetTriangleDensity;for(const a of n){if(a.material&&(a.geometry?.type==="BoxGeometry"||a.geometry?.type==="BufferGeometry")&&(a.material.name==="SphericalGaussianBlur"||a.material.name=="BackgroundCubeMaterial"||a.material.name==="CubemapFromEquirect"||a.material.name==="EquirectangularToCubeUV")){B&&(a.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"]||(a.material["NEEDLE_PROGRESSIVE:IGNORE-WARNING"]=!0,console.warn("Ignoring skybox or BLIT object",a,a.material.name,a.material.type)));continue}switch(a.material.type){case"LineBasicMaterial":case"LineDashedMaterial":case"PointsMaterial":case"ShadowMaterial":case"MeshDistanceMaterial":case"MeshDepthMaterial":continue}if(B==="color"&&a.material&&!a.object.progressive_debug_color){a.object.progressive_debug_color=!0;const f=Math.random()*16777215,w=new ze({color:f});a.object.material=w}const u=a.object;(u instanceof j||u.isMesh)&&this.updateLODs(t,e,u,s)}const o=r.transparent;for(const a of o){const u=a.object;(u instanceof j||u.isMesh)&&this.updateLODs(t,e,u,s)}const l=r.transmissive;for(const a of l){const u=a.object;(u instanceof j||u.isMesh)&&this.updateLODs(t,e,u,s)}}updateLODs(t,e,r,n){r.userData||(r.userData={});let s=r[xe];if(s||(s=new gt,r[xe]=s),s.frames++<2)return;for(const l of R)l.onBeforeUpdateLOD?.(this.renderer,t,e,r);const o=this.overrideLodLevel!==void 0?this.overrideLodLevel:W;o>=0?(M.mesh_lod=o,M.texture_lod=o):(this.calculateLodLevel(e,r,s,n,M),M.mesh_lod=Math.round(M.mesh_lod),M.texture_lod=Math.round(M.texture_lod)),M.mesh_lod>=0&&this.loadProgressiveMeshes(r,M.mesh_lod),r.material&&M.texture_lod>=0&&this.loadProgressiveTextures(r.material,M.texture_lod,o),h&&r.material&&!r.isGizmo&&Te(r.material);for(const l of R)l.onAfterUpdatedLOD?.(this.renderer,t,e,r,M);s.lastLodLevel_Mesh=M.mesh_lod,s.lastLodLevel_Texture=M.texture_lod}loadProgressiveTextures(t,e,r){if(!t)return;if(Array.isArray(t)){for(const s of t)this.loadProgressiveTextures(s,e);return}let n=!1;if((t[G]===void 0||e<t[G])&&(n=!0),r!==void 0&&r>=0&&(n=t[G]!=r,e=r),n){t[G]=e;const s=g.assignTextureLOD(t,e).then(o=>{this._lodchangedlisteners.forEach(l=>l({type:"texture",level:e,object:t}))});me.addPromise("texture",t,s,this._newPromiseGroups)}}loadProgressiveMeshes(t,e){if(!t)return Promise.resolve(null);let r=t[G]!==e;const n=t["DEBUG:LOD"];if(n!=null&&(r=t[G]!=n,e=n),r){t[G]=e;const s=t.geometry,o=g.assignMeshLOD(t,e).then(l=>(l&&t[G]==e&&s!=t.geometry&&this._lodchangedlisteners.forEach(a=>a({type:"mesh",level:e,object:t})),l));return me.addPromise("mesh",t,o,this._newPromiseGroups),o}return Promise.resolve(null)}_sphere=new ve;_tempBox=new ie;_tempBox2=new ie;tempMatrix=new Le;_tempWorldPosition=new I;_tempBoxSize=new I;_tempBox2Size=new I;static corner0=new I;static corner1=new I;static corner2=new I;static corner3=new I;static _tempPtInside=new I;static isInside(t,e){const r=t.min,n=t.max,s=(r.x+n.x)*.5,o=(r.y+n.y)*.5;return this._tempPtInside.set(s,o,r.z).applyMatrix4(e).z<0}static skinnedMeshBoundsFrameOffsetCounter=0;static $skinnedMeshBoundsOffset=Symbol("gltf-progressive-skinnedMeshBoundsOffset");calculateLodLevel(t,e,r,n,s){if(!e){s.mesh_lod=-1,s.texture_lod=-1;return}if(!t){s.mesh_lod=-1,s.texture_lod=-1;return}let o=10+1,l=!1;if(B&&e["DEBUG:LOD"]!=null)return e["DEBUG:LOD"];const a=g.getMeshLODExtension(e.geometry)?.lods,u=g.getPrimitiveIndex(e.geometry),f=a&&a.length>0,w=g.getMaterialMinMaxLODsCount(e.material),v=w.min_count!==1/0&&w.min_count>=0&&w.max_count>=0;if(!f&&!v){s.mesh_lod=0,s.texture_lod=0;return}f||(l=!0,o=0);const T=this.renderer.domElement.clientHeight||this.renderer.domElement.height;let p=e.geometry.boundingBox;if(e.type==="SkinnedMesh"){const m=e;if(!m.boundingBox)m.computeBoundingBox();else if(this.skinnedMeshAutoUpdateBoundsInterval>0){if(!m[_.$skinnedMeshBoundsOffset]){const A=_.skinnedMeshBoundsFrameOffsetCounter++;m[_.$skinnedMeshBoundsOffset]=A}const y=m[_.$skinnedMeshBoundsOffset];if((r.frames+y)%this.skinnedMeshAutoUpdateBoundsInterval===0){const A=z(m),b=m.geometry;A&&(m.geometry=A),m.computeBoundingBox(),m.geometry=b}}p=m.boundingBox}if(p){const m=t;if(e.geometry.attributes.color&&e.geometry.attributes.color.count<100&&e.geometry.boundingSphere){this._sphere.copy(e.geometry.boundingSphere),this._sphere.applyMatrix4(e.matrixWorld);const d=t.getWorldPosition(this._tempWorldPosition);if(this._sphere.containsPoint(d)){s.mesh_lod=0,s.texture_lod=0;return}}if(this._tempBox.copy(p),this._tempBox.applyMatrix4(e.matrixWorld),m.isPerspectiveCamera&&_.isInside(this._tempBox,this.projectionScreenMatrix)){s.mesh_lod=0,s.texture_lod=0;return}if(this._tempBox.applyMatrix4(this.projectionScreenMatrix),this.renderer.xr.enabled&&m.isPerspectiveCamera&&m.fov>70){const d=this._tempBox.min,c=this._tempBox.max;let L=d.x,O=d.y,S=c.x,q=c.y;const Y=2,ne=1.5,Q=(d.x+c.x)*.5,J=(d.y+c.y)*.5;L=(L-Q)*Y+Q,O=(O-J)*Y+J,S=(S-Q)*Y+Q,q=(q-J)*Y+J;const Be=L<0&&S>0?0:Math.min(Math.abs(d.x),Math.abs(c.x)),$e=O<0&&q>0?0:Math.min(Math.abs(d.y),Math.abs(c.y)),oe=Math.max(Be,$e);r.lastCentrality=(ne-oe)*(ne-oe)*(ne-oe)}else r.lastCentrality=1;const y=this._tempBox.getSize(this._tempBoxSize);y.multiplyScalar(.5),screen.availHeight>0&&T>0&&y.multiplyScalar(T/screen.availHeight),t.isPerspectiveCamera?y.x*=t.aspect:t.isOrthographicCamera;const A=t.matrixWorldInverse,b=this._tempBox2;b.copy(p),b.applyMatrix4(e.matrixWorld),b.applyMatrix4(A);const E=b.getSize(this._tempBox2Size),D=Math.max(E.x,E.y);if(Math.max(y.x,y.y)!=0&&D!=0&&(y.z=E.z/Math.max(E.x,E.y)*Math.max(y.x,y.y)),r.lastScreenCoverage=Math.max(y.x,y.y,y.z),r.lastScreenspaceVolume.copy(y),r.lastScreenCoverage*=r.lastCentrality,B&&_.debugDrawLine){const d=this.tempMatrix.copy(this.projectionScreenMatrix);d.invert();const c=_.corner0,L=_.corner1,O=_.corner2,S=_.corner3;c.copy(this._tempBox.min),L.copy(this._tempBox.max),L.x=c.x,O.copy(this._tempBox.max),O.y=c.y,S.copy(this._tempBox.max);const q=(c.z+S.z)*.5;c.z=L.z=O.z=S.z=q,c.applyMatrix4(d),L.applyMatrix4(d),O.applyMatrix4(d),S.applyMatrix4(d),_.debugDrawLine(c,L,255),_.debugDrawLine(c,O,255),_.debugDrawLine(L,S,255),_.debugDrawLine(O,S,255)}let x=999;if(a&&r.lastScreenCoverage>0)for(let d=0;d<a.length;d++){const c=a[d],L=(c.densities?.[u]||c.density||1e-5)/r.lastScreenCoverage;if(u>0&&Pe()&&!c.densities&&!globalThis["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]&&(window["NEEDLE:MISSING_LOD_PRIMITIVE_DENSITIES"]=!0,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.")),L<n){x=d;break}}x<o&&(o=x,l=!0)}if(l?s.mesh_lod=o:s.mesh_lod=r.lastLodLevel_Mesh,B&&s.mesh_lod!=r.lastLodLevel_Mesh){const m=a?.[s.mesh_lod];m&&console.debug(`Mesh LOD changed: ${r.lastLodLevel_Mesh} \u2192 ${s.mesh_lod} (density: ${m.densities?.[u].toFixed(0)}) | ${e.name}`)}if(v){const m="saveData"in globalThis.navigator&&globalThis.navigator.saveData===!0;if(r.lastLodLevel_Texture<0){if(s.texture_lod=w.max_count-1,B){const y=w.lods[w.max_count-1];B&&console.log(`First Texture LOD ${s.texture_lod} (${y.max_height}px) - ${e.name}`)}}else{const y=r.lastScreenspaceVolume.x+r.lastScreenspaceVolume.y+r.lastScreenspaceVolume.z;let A=r.lastScreenCoverage*4;this.context?.engine==="model-viewer"&&(A*=1.5);const b=T/window.devicePixelRatio*A;let E=!1;for(let D=w.lods.length-1;D>=0;D--){const x=w.lods[D];if(!(m&&x.max_height>=2048)&&!(Se()&&x.max_height>4096)&&(x.max_height>b||!E&&D===0)){if(E=!0,s.texture_lod=D,B&&s.texture_lod<r.lastLodLevel_Texture){const d=x.max_height;console.log(`Texture LOD changed: ${r.lastLodLevel_Texture} \u2192 ${s.texture_lod} = ${d}px
|
|
6
|
-
Screensize: ${b.toFixed(0)}px, Coverage: ${(100*r.lastScreenCoverage).toFixed(2)}%, Volume ${y.toFixed(1)}
|
|
7
|
-
${e.name}`)}break}}}}else s.texture_lod=0}};class gt{frames=0;lastLodLevel_Mesh=-1;lastLodLevel_Texture=-1;lastScreenCoverage=0;lastScreenspaceVolume=new I;lastCentrality=0}const Ae=Symbol("NEEDLE_mesh_lod"),se=Symbol("NEEDLE_texture_lod");let we=null;function Ie(){const i=ft();i&&(i.mapURLs(function(t){return Ee(),t}),Ee(),we?.disconnect(),we=new MutationObserver(t=>{t.forEach(e=>{e.addedNodes.forEach(r=>{r instanceof HTMLElement&&r.tagName.toLowerCase()==="model-viewer"&&Ce(r)})})}),we.observe(document,{childList:!0,subtree:!0}))}function ft(){return typeof customElements>"u"?null:customElements.get("model-viewer")||(customElements.whenDefined("model-viewer").then(()=>{console.debug("[gltf-progressive] model-viewer defined"),Ie()}),null)}function Ee(){typeof document>"u"||document.querySelectorAll("model-viewer").forEach(i=>{Ce(i)})}const ke=new WeakSet;let mt=0;function Ce(i){if(!i||ke.has(i))return null;ke.add(i),console.debug("[gltf-progressive] found new model-viewer..."+ ++mt+`
|
|
8
|
-
`,i.getAttribute("src"));let t=null,e=null,r=null;for(let n=i;n!=null;n=Object.getPrototypeOf(n)){const s=Object.getOwnPropertySymbols(n),o=s.find(u=>u.toString()=="Symbol(renderer)"),l=s.find(u=>u.toString()=="Symbol(scene)"),a=s.find(u=>u.toString()=="Symbol(needsRender)");!t&&o!=null&&(t=i[o].threeRenderer),!e&&l!=null&&(e=i[l]),!r&&a!=null&&(r=i[a])}if(t&&e){let n=function(){if(r){let o=0,l=setInterval(()=>{if(o++>5){clearInterval(l);return}r?.call(i)},300)}};console.debug("[gltf-progressive] setup model-viewer");const s=H.get(t,{engine:"model-viewer"});return H.addPlugin(new pt),s.enable(),s.addEventListener("changed",()=>{r?.call(i)}),i.addEventListener("model-visibility",o=>{o.detail.visible&&r?.call(i)}),i.addEventListener("load",()=>{n()}),()=>{s.disable()}}return null}class pt{_didWarnAboutMissingUrl=!1;onBeforeUpdateLOD(t,e,r,n){this.tryParseMeshLOD(e,n),this.tryParseTextureLOD(e,n)}getUrl(t){if(!t)return null;let e=t.getAttribute("src");return e||(e=t.src),e||(this._didWarnAboutMissingUrl||console.warn("No url found in modelviewer",t),this._didWarnAboutMissingUrl=!0),e}tryGetCurrentGLTF(t){return t._currentGLTF}tryGetCurrentModelViewer(t){return t.element}tryParseTextureLOD(t,e){if(e[se]==!0)return;e[se]=!0;const r=this.tryGetCurrentGLTF(t),n=this.tryGetCurrentModelViewer(t),s=this.getUrl(n);if(s&&r&&e.material){let o=function(a){if(a[se]==!0)return;a[se]=!0,a.userData&&(a.userData.LOD=-1);const u=Object.keys(a);for(let f=0;f<u.length;f++){const w=u[f],v=a[w];if(v?.isTexture===!0){const T=v.userData?.associations?.textures;if(T==null)continue;const p=r.parser.json.textures[T];if(!p){console.warn("Texture data not found for texture index "+T);continue}if(p?.extensions?.[$]){const m=p.extensions[$];m&&s&&g.registerTexture(s,v,m.lods.length,T,m)}}}};const l=e.material;if(Array.isArray(l))for(const a of l)o(a);else o(l)}}tryParseMeshLOD(t,e){if(e[Ae]==!0)return;e[Ae]=!0;const r=this.tryGetCurrentModelViewer(t),n=this.getUrl(r);if(!n)return;const s=e.userData?.gltfExtensions?.[$];if(s&&n){const o=e.uuid;g.registerMesh(n,o,e,0,s.lods.length,s)}}}function xt(...i){let t,e,r,n;switch(i.length){case 2:[r,e]=i,n={};break;case 3:[r,e,n]=i;break;case 4:[t,e,r,n]=i;break;default:throw new Error("Invalid arguments")}Z(e),le(r),ce(r,{progressive:!0,...n?.hints}),r.register(o=>new g(o));const s=H.get(e);return n?.enableLODsManager!==!1&&s.enable(),s}if(Ie(),!tt){const i={gltfProgressive:{useNeedleProgressive:xt,LODsManager:H,configureLoader:ce,getRaycastMesh:z,useRaycastMeshes:st}};if(!globalThis.Needle)globalThis.Needle=i;else for(const t in i)globalThis.Needle[t]=i[t]}export{H as LODsManager,g as NEEDLE_progressive,le as addDracoAndKTX2Loaders,ce as configureLoader,Z as createLoaders,z as getRaycastMesh,De as setDracoDecoderLocation,Me as setKTX2TranscoderLocation};
|