@needle-tools/engine 4.8.4-experimental.c93e134 → 4.8.4-next.68d6468
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/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-BLVnT5UY.min.js → needle-engine.bundle-BoN_WoMd.min.js} +129 -129
- package/dist/{needle-engine.bundle-DhD3EKds.umd.cjs → needle-engine.bundle-C0vgs1VP.umd.cjs} +124 -124
- package/dist/{needle-engine.bundle-Bx5wfOs0.js → needle-engine.bundle-D5UIUwHv.js} +4857 -5074
- package/dist/needle-engine.js +409 -411
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/extensions/extensions.js +0 -2
- package/lib/engine/extensions/extensions.js.map +1 -1
- package/lib/engine/extensions/index.d.ts +0 -1
- package/lib/engine/extensions/index.js +0 -1
- package/lib/engine/extensions/index.js.map +1 -1
- package/lib/engine-components/DragControls.js +0 -1
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/TransformGizmo.js +9 -1
- package/lib/engine-components/TransformGizmo.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +0 -1
- package/lib/engine-components/codegen/components.js +0 -1
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/package.json +1 -1
- package/src/engine/extensions/extensions.ts +0 -2
- package/src/engine/extensions/index.ts +0 -1
- package/src/engine-components/DragControls.ts +0 -1
- package/src/engine-components/TransformGizmo.ts +9 -1
- package/src/engine-components/codegen/components.ts +0 -1
- package/lib/engine/extensions/KHR_materials_variants.d.ts +0 -25
- package/lib/engine/extensions/KHR_materials_variants.js +0 -113
- package/lib/engine/extensions/KHR_materials_variants.js.map +0 -1
- package/lib/engine-components/MaterialVariants.d.ts +0 -52
- package/lib/engine-components/MaterialVariants.js +0 -210
- package/lib/engine-components/MaterialVariants.js.map +0 -1
- package/src/engine/extensions/KHR_materials_variants.ts +0 -179
- package/src/engine-components/MaterialVariants.ts +0 -231
|
@@ -1,179 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
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
|
-
}
|