@needle-tools/materialx 1.0.1-next.df0e959 → 1.0.2-next.81f48e0

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.
@@ -1,85 +1,86 @@
1
1
  import { Context } from "@needle-tools/engine";
2
- import { ShaderMaterial, Material, MeshStandardMaterial, LoadingManager, TextureLoader, Texture, NearestFilter, Matrix4, GLSL3, AddEquation, OneMinusSrcAlphaFactor, SrcAlphaFactor, DoubleSide, Matrix3, Vector3, Object3D, Camera } from "three";
3
- import { GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
4
- import { getUniformValues } from "../helper.js";
2
+ import { Material, MeshStandardMaterial, Texture, NearestFilter, CompressedTexture, DoubleSide, FrontSide } from "three";
3
+ import { GLTF, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
5
4
  import { ready, MaterialXEnvironment, state } from "../materialx.js";
6
5
  import { debug } from "../utils.js";
6
+ import { MaterialXMaterial } from "../materialx.material.js";
7
7
 
8
8
  // TypeScript interfaces matching the C# data structures
9
- interface MaterialX_root_extension {
9
+ export type MaterialX_root_extension = {
10
10
  /** e.g. 1.39 */
11
11
  version: string;
12
12
  /** e.g. "Material" */
13
13
  name: string;
14
14
  /** MaterialX xml content */
15
15
  mtlx: string;
16
+ /** MaterialX texture pointers */
17
+ textures: Array<{ name: string, pointer: string }>;
16
18
  }
17
19
 
18
- interface MaterialX_material_extension {
20
+ export type MaterialX_material_extension = {
19
21
  name: string; // Material name reference
20
22
  }
21
23
 
24
+ type MaterialDefinition = {
25
+ name?: string; // Optional name for the material
26
+ doubleSided?: boolean; // Whether the material is double-sided
27
+ extensions?: {
28
+ [key: string]: any; // Extensions for the material, including MaterialX
29
+ },
30
+ }
31
+
22
32
  // MaterialX loader extension for js GLTFLoader
23
33
  export class MaterialXLoader implements GLTFLoaderPlugin {
24
- name = "NEEDLE_materials_mtlx";
25
-
26
- private rootMaterialXData: MaterialX_root_extension | null = null;
27
- private parsedDocument: any = null;
28
- private documentParsePromise: Promise<any> | null = null;
29
- private rootDataInitialized = false;
30
- private environmentInitialized = false;
31
- private generatedMaterials: ShaderMaterial[] = [];
34
+ readonly name = "NEEDLE_materials_mtlx";
32
35
 
33
- constructor(private parser: GLTFParser, private context: Context) {
34
- if (debug) console.log("MaterialXLoader created for parser");
35
- // Initialize MaterialX environment after MaterialX is ready
36
- this.initializeEnvironment();
37
- }
36
+ private readonly _generatedMaterials: MaterialXMaterial[] = [];
38
37
 
39
- // Initialize MaterialX environment - called once after MaterialX is ready
40
- private async initializeEnvironment(): Promise<void> {
41
- if (this.environmentInitialized) return;
38
+ private _documentReadyPromise: Promise<any> | null = null;
42
39
 
43
- if (debug) console.log("[MaterialX] MaterialXLoader: Initializing MaterialX environment...");
40
+ get materialX_root_data() {
41
+ return this.parser.json.extensions?.[this.name] as MaterialX_root_extension | null;
42
+ }
44
43
 
45
- // Ensure MaterialX is initialized first
46
- await ready();
44
+ /** Generated materialX materials */
45
+ get materials() {
46
+ return this._generatedMaterials;
47
+ }
47
48
 
48
- // Set up environment with context
49
- const environment = state.materialXEnvironment;
50
- // Initialize the environment from context (properly awaited)
51
- try {
52
- await environment.initializeFromContext(this.context);
53
- this.environmentInitialized = true;
54
- if (debug) console.log("[MaterialX] MaterialXLoader: Environment initialized successfully");
55
- } catch (error) {
56
- console.warn("[MaterialX] MaterialXLoader: Failed to initialize MaterialX environment:", error);
49
+ /**
50
+ * MaterialXLoader constructor
51
+ * @param parser The GLTFParser instance
52
+ * @param url The URL of the GLTF file
53
+ * @param context The context for the GLTF loading process
54
+ */
55
+ constructor(private parser: GLTFParser, private url: string, private context: Context) {
56
+ if (debug) console.log("MaterialXLoader created for parser");
57
+ // Start loading of MaterialX environment if the root extension exists
58
+ if (this.materialX_root_data) {
59
+ ready();
57
60
  }
58
61
  }
59
62
 
60
- // Initialize root data from parser.json.extensions once
61
- private initializeRootData(): void {
62
- if (this.rootDataInitialized) return;
63
-
64
- const gltfExtensions = this.parser.json.extensions;
65
- if (gltfExtensions?.[this.name]) {
66
- if (debug) console.log("[MaterialX] extension found in root:", gltfExtensions[this.name]);
67
-
68
- const materialXExtension = gltfExtensions[this.name];
69
- this.rootMaterialXData = materialXExtension as MaterialX_root_extension;
63
+ loadMaterial(materialIndex: number): Promise<Material> | null {
64
+ const materialDef = this.parser.json.materials?.[materialIndex];
65
+ if (!materialDef?.extensions?.[this.name]) {
66
+ return null;
70
67
  }
71
- this.rootDataInitialized = true;
68
+ // Wrap the async implementation
69
+ return this._loadMaterialAsync(materialIndex);
70
+ }
71
+
72
+ afterRoot = async (_gltf: GLTF) => {
73
+ // Initialize MaterialX lighting system with scene data
74
+ const environment = state.materialXEnvironment;
75
+ environment.initialize(this.context);
72
76
  }
73
77
 
74
78
  // Parse the MaterialX document once and cache it
75
- private async parseRootDocument(): Promise<any> {
76
- if (this.documentParsePromise) {
77
- return this.documentParsePromise;
79
+ private async _materialXDocumentReady(): Promise<any> {
80
+ if (this._documentReadyPromise) {
81
+ return this._documentReadyPromise;
78
82
  }
79
-
80
- this.documentParsePromise = (async () => {
81
- if (this.parsedDocument) return this.parsedDocument;
82
-
83
+ return this._documentReadyPromise = (async () => {
83
84
  if (debug) console.log("[MaterialX] Parsing MaterialX root document...");
84
85
 
85
86
  // Ensure MaterialX is initialized
@@ -94,43 +95,27 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
94
95
  doc.setDataLibrary(state.materialXStdLib);
95
96
 
96
97
  // Parse all MaterialX XML strings from the root data
97
- if (this.rootMaterialXData) {
98
- if (debug) console.log(`[MaterialX] Parsing XML for: ${this.rootMaterialXData.name}`);
99
- await state.materialXModule.readFromXmlString(doc, this.rootMaterialXData.mtlx, "");
98
+ const root = this.materialX_root_data
99
+ if (root) {
100
+ if (debug) console.log(`[MaterialX] Parsing XML for: ${root.name}`);
101
+ await state.materialXModule.readFromXmlString(doc, root.mtlx, "");
100
102
  }
101
103
 
102
104
  if (debug) console.log("[MaterialX] root document parsed successfully");
103
-
104
- this.parsedDocument = doc;
105
105
  return doc;
106
106
  })();
107
-
108
- return this.documentParsePromise;
109
- }
110
-
111
- loadMaterial(materialIndex: number): Promise<Material> | null {
112
- const materialDef = this.parser.json.materials?.[materialIndex];
113
- if (!materialDef?.extensions?.[this.name]) {
114
- return null;
115
- }
116
-
117
- // Wrap the async implementation
118
- return this._loadMaterialAsync(materialIndex);
119
107
  }
120
108
 
121
109
  private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
122
- // Initialize root data first
123
- this.initializeRootData();
124
110
 
125
- const materialDef = this.parser.json.materials?.[materialIndex];
126
- if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions[this.name]);
111
+ const materialDef = this.parser.json.materials?.[materialIndex] as MaterialDefinition;
112
+ if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions?.[this.name]);
127
113
 
128
114
  // Handle different types of MaterialX data
129
- const dataIndex = materialDef.extensions[this.name] as MaterialX_material_extension;
115
+ const ext = materialDef.extensions?.[this.name] as MaterialX_material_extension;
130
116
 
131
- if (dataIndex) {
132
- // Create a new material and process MaterialX - AWAIT THIS!
133
- return await this.createMaterialXMaterial(dataIndex);
117
+ if (ext) {
118
+ return this._createMaterialXMaterial(materialDef, ext);
134
119
  }
135
120
 
136
121
  // Return fallback material instead of null
@@ -139,29 +124,22 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
139
124
  return fallbackMaterial;
140
125
  }
141
126
 
142
- private rootDocument: Promise<any> | null = null;
143
- private async createMaterialXMaterial(materialXData: MaterialX_material_extension): Promise<Material> {
127
+ private async _createMaterialXMaterial(material_def: MaterialDefinition, material_extension: MaterialX_material_extension): Promise<Material> {
144
128
  try {
145
- if (debug) console.log(`Creating MaterialX material: ${materialXData.name}`);
146
-
147
- // Ensure MaterialX is initialized and document is parsed
148
- await ready();
129
+ if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
149
130
 
150
- if (!this.rootDocument) {
151
- this.rootDocument = this.parseRootDocument();
152
- }
153
- const doc = await this.rootDocument;
131
+ const doc = await this._materialXDocumentReady();
154
132
 
155
133
  if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
156
134
  console.warn("[MaterialX] WASM module not ready, returning fallback material");
157
135
  const fallbackMaterial = new MeshStandardMaterial();
158
- fallbackMaterial.userData.materialX = materialXData;
159
- fallbackMaterial.name = `MaterialX_Fallback_${materialXData.name}`;
136
+ fallbackMaterial.userData.materialX = material_extension;
137
+ fallbackMaterial.name = `MaterialX_Fallback_${material_extension.name}`;
160
138
  return fallbackMaterial;
161
139
  }
162
140
 
163
141
  // Find the renderable element following MaterialX example pattern exactly
164
- let renderableElement = null;
142
+ let renderableElement: any = null;
165
143
  let foundRenderable = false;
166
144
 
167
145
  if (debug) console.log("[MaterialX] document", doc);
@@ -179,7 +157,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
179
157
  if (debug) console.log('[MaterialX] Scan material: ', i, materialName);
180
158
 
181
159
  // Find the matching material
182
- if (materialName == materialXData.name) {
160
+ if (materialName == material_extension.name) {
183
161
  renderableElement = materialNode;
184
162
  foundRenderable = true;
185
163
  if (debug) console.log('[MaterialX] -- add material: ', materialName);
@@ -236,17 +214,18 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
236
214
  */
237
215
 
238
216
  if (!renderableElement) {
239
- console.warn("[MaterialX] No renderable element found in MaterialX document");
217
+ console.warn(`[MaterialX] No renderable element found in MaterialX document (${material_extension.name})`);
240
218
  const fallbackMaterial = new MeshStandardMaterial();
241
- fallbackMaterial.userData.materialX = materialXData;
242
- fallbackMaterial.name = `MaterialX_NoRenderable_${materialXData.name}`;
219
+ fallbackMaterial.color.set(0xff00ff);
220
+ fallbackMaterial.userData.materialX = material_extension;
221
+ fallbackMaterial.name = `MaterialX_NoRenderable_${material_extension.name}`;
243
222
  return fallbackMaterial;
244
223
  }
245
224
 
246
225
  if (debug) console.log("[MaterialX] Using renderable element for shader generation");
247
226
 
248
227
  // Check transparency and set context options like the reference
249
- let isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
228
+ const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
250
229
  state.materialXGenContext.getOptions().hwTransparency = isTransparent;
251
230
 
252
231
  // Generate shaders using the element's name path
@@ -254,317 +233,68 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
254
233
  const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
255
234
 
256
235
  const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
257
-
258
- // Get vertex and fragment shader source, and remove #version directive for newer js.
259
- // It's added by three.js glslVersion.
260
- let vertexShader = shader.getSourceCode("vertex").replace(/^#version.*$/gm, '').trim();
261
- let fragmentShader = shader.getSourceCode("pixel").replace(/^#version.*$/gm, '').trim();
262
-
263
- // MaterialX uses different attribute names than js defaults,
264
- // so we patch the MaterialX shaders to match the js standard names.
265
- // Otherwise, we'd have to modify the mesh attributes (see original MaterialX for reference).
266
-
267
- // Patch vertexShader
268
- vertexShader = vertexShader.replace(/\bi_position\b/g, 'position');
269
- vertexShader = vertexShader.replace(/\bi_normal\b/g, 'normal');
270
- vertexShader = vertexShader.replace(/\bi_texcoord_0\b/g, 'uv');
271
- vertexShader = vertexShader.replace(/\bi_texcoord_1\b/g, 'uv1');
272
- vertexShader = vertexShader.replace(/\bi_tangent\b/g, 'tangent');
273
- vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
274
-
275
- // Patch fragmentShader
276
- fragmentShader = fragmentShader.replace(/\bi_position\b/g, 'position');
277
- fragmentShader = fragmentShader.replace(/\bi_normal\b/g, 'normal');
278
- fragmentShader = fragmentShader.replace(/\bi_texcoord_0\b/g, 'uv');
279
- fragmentShader = fragmentShader.replace(/\bi_texcoord_1\b/g, 'uv1');
280
- fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
281
- fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
282
-
283
- // Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
284
- vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
285
- vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
286
- vertexShader = vertexShader.replace(/in\s+vec3\s+uv;/g, '');
287
- vertexShader = vertexShader.replace(/in\s+vec3\s+uv1;/g, '');
288
- vertexShader = vertexShader.replace(/in\s+vec4\s+tangent;/g, '');
289
- vertexShader = vertexShader.replace(/in\s+vec4\s+color;/g, '');
290
-
291
- // Patch uv 2-component to 3-component (`texcoord_0 = uv;` needs to be replaced with `texcoord_0 = vec3(uv, 0.0);`)
292
- // TODO what if we actually have a 3-component UV? Not sure what three.js does then
293
- vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
294
-
295
- // Add tonemapping and colorspace handling
296
- // Replace `out vec4 out1;` with `out vec4 gl_FragColor;`
297
- fragmentShader = fragmentShader.replace(/out\s+vec4\s+out1;/, 'layout(location = 0) out vec4 pc_fragColor;\n#define gl_FragColor pc_fragColor');
298
- // Replace `out1 = vec4(<CAPTURE>)` with `gl_FragColor = vec4(<CAPTURE>)` and tonemapping/colorspace handling
299
- fragmentShader = fragmentShader.replace(/^\s*out1\s*=\s*vec4\((.*)\);/gm,
300
- `
301
- gl_FragColor = vec4($1);
302
- #include <tonemapping_fragment>
303
- #include <colorspace_fragment>`);
304
-
305
- if (debug) {
306
- console.group("[MaterialX]: ", materialXData.name);
307
- console.log("Vertex shader length:", vertexShader.length, vertexShader);
308
- console.log("Fragment shader length:", fragmentShader.length, fragmentShader);
309
- console.groupEnd();
310
- }
311
-
312
- // Extract uniforms from both vertex and pixel stages
313
- const loadingManager = new LoadingManager();
314
- loadingManager.setURLModifier((url: string) => {
315
- if (debug) console.log("Loading URL:", url);
316
- // Find the texture in the textures in the parser and load it from there
317
- return url;
318
- });
319
- let textureLoader = new TextureLoader(loadingManager);
320
-
321
- // Override the textureLoader.load method to use the parser's loadTexture directly,
322
- // since we want to load the textures from the glTF document and not from disk.
323
- // That loader does its own caching too.
324
- textureLoader.load = (url: string, onLoad?: (texture: Texture) => void, onProgress?: (event: ProgressEvent) => void, onError?: (err: Error) => void): Texture => {
325
-
326
- // Return a checkerboard texture for now
327
- const checkerboardTexture = new Texture();
328
- checkerboardTexture.image = new Image();
329
- checkerboardTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAG0lEQVR4nGJqWH9q9e8XjA/VrL8UfQIEAAD//zn2CCX5UcsdAAAAAElFTkSuQmCC";
330
- checkerboardTexture.needsUpdate = true;
331
- // Pixelated filtering
332
- checkerboardTexture.magFilter = NearestFilter;
333
- checkerboardTexture.minFilter = NearestFilter;
334
-
335
- new Promise(() => {
336
- // Find the index of the texture in the parser
337
- const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
338
-
339
- // Find in the root extension; there's the textures dictionary:
340
- // a mapping from the texture name used in MaterialX to the glTF texture index
341
- const ext = this.parser.json.extensions?.[this.name];
342
- const textures = ext?.textures || [];
343
-
344
- const index = textures.findIndex(tex => {
345
- if (debug) console.log("[MaterialX] Checking texture:", tex.name, "against URL:", filenameWithoutExt);
346
- return tex.name === filenameWithoutExt;
347
- });
348
-
349
- if (index < 0) {
350
- console.warn("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
351
- onError?.(new Error(`Texture not found: ${filenameWithoutExt}`));
352
- return;
353
- }
354
- this.parser.getDependency("texture", index).then(tex => {
355
- if (debug) console.log("[MaterialX] Texture loaded:", tex);
356
- // update the checkerboard texture with the loaded texture
357
- checkerboardTexture.image = tex.image;
358
- checkerboardTexture.needsUpdate = true;
359
- onLoad?.(tex);
360
- }).catch(err => {
361
- onError?.(err);
362
- });
363
- });
364
-
365
- return checkerboardTexture;
366
- };
367
-
368
- const searchPath = ""; // Could be derived from the asset path if needed
369
- const flipV = false; // Set based on your geometry requirements
370
-
371
- let uniforms = {
372
- ...getUniformValues(shader.getStage('vertex'), textureLoader, searchPath, flipV),
373
- ...getUniformValues(shader.getStage('pixel'), textureLoader, searchPath, flipV),
374
- };
375
-
376
- // Get lighting and environment data from MaterialX environment
377
- const environment = state.materialXEnvironment;
378
- // const lights = environment.getLights() || [];
379
- const lightData = environment.getLightData() || null;
380
- const radianceTexture = environment.getRadianceTexture() || null;
381
- const irradianceTexture = environment.getIrradianceTexture() || null;
382
- const getLightRotation = () => {
383
- // Placeholder for light rotation logic, if needed
384
- return new Matrix4();
385
- };
386
-
387
- if (debug) console.log({ lightData, radianceTexture, irradianceTexture });
388
-
389
- Object.assign(uniforms, {
390
- u_numActiveLightSources: { value: lightData?.length || 0, type: 'i' },
391
- });
392
-
393
- if (lightData?.length) {
394
- Object.assign(uniforms, {
395
- u_lightData: { value: lightData, type: 'v4v' },
396
- });
397
- }
398
-
399
- const mips = Math.trunc(Math.log2(Math.max(radianceTexture?.width ?? 0, radianceTexture?.height ?? 0))) + 1;
400
- if (debug) console.log("[MaterialX] Radiance texture mips:", mips, "for texture size:", radianceTexture?.width, "x", radianceTexture?.height);
401
- Object.assign(uniforms, {
402
- u_envMatrix: { value: getLightRotation() },
403
- u_envRadiance: { value: radianceTexture, type: 't' },
404
- u_envRadianceMips: { value: 8, type: 'i' },
405
- // TODO we need to figure out how we can set a PMREM here... doing many texture samples is prohibitively expensive
406
- u_envRadianceSamples: { value: 8, type: 'i' },
407
- u_envIrradiance: { value: irradianceTexture, type: 't' },
408
- u_refractionEnv: { value: true },
409
- });
410
-
411
- // console.log("NEW MATERIAL UNIFORMS", uniforms);
412
-
413
- // Debug: Log the actual shaders to see what we're working with
414
- // console.log("Generated vertex shader:", vertexShader.substring(0, 500) + "...");
415
- // console.log("Generated fragment shader:", fragmentShader.substring(0, 500) + "...");
416
-
417
- // Create js RawShaderMaterial (with GLSL3 for MaterialX compatibility)
418
- const shaderMaterial = new ShaderMaterial({
419
- uniforms: uniforms,
420
- vertexShader: vertexShader,
421
- fragmentShader: fragmentShader,
422
- glslVersion: GLSL3,
236
+ const shaderMaterial = new MaterialXMaterial({
237
+ name: material_extension.name,
238
+ shader,
423
239
  transparent: isTransparent,
424
- // Only set blend settings if actually transparent
425
- ...(isTransparent && {
426
- blendEquation: AddEquation,
427
- blendSrc: OneMinusSrcAlphaFactor,
428
- blendDst: SrcAlphaFactor,
429
- }),
430
- side: DoubleSide,
431
- // Add some debug settings
432
- depthTest: true,
433
- depthWrite: !isTransparent
434
- });
435
-
436
- // Add helper matrices for uniform updates (similar to MaterialX example)
437
- const normalMat = new Matrix3();
438
- const viewProjMat = new Matrix4();
439
- const worldViewPos = new Vector3();
440
-
441
- // Store helper objects on the material for uniform updates
442
- shaderMaterial.userData.materialX = materialXData;
443
- shaderMaterial.userData.updateUniforms = (object: Object3D, camera: Camera) => {
444
- const uniforms = shaderMaterial.uniforms;
445
-
446
- if (!uniforms) return;
447
-
448
- // TODO remove. Not sure why this is needed, but without it
449
- // we currently get some "swimming" where matrices are not up to date.
450
- camera.updateMatrixWorld(true);
451
-
452
- // Update standard transformation matrices
453
- if (uniforms.u_worldMatrix) {
454
- if (!uniforms.u_worldMatrix.value?.isMatrix4) uniforms.u_worldMatrix.value = new Matrix4();
455
- uniforms.u_worldMatrix.value = object.matrixWorld;
456
- }
457
-
458
- if (uniforms.u_viewProjectionMatrix) {
459
- if (!uniforms.u_viewProjectionMatrix.value?.isMatrix4) uniforms.u_viewProjectionMatrix.value = new Matrix4();
460
- uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
461
- }
462
-
463
- if (uniforms.u_viewPosition) {
464
- if (!uniforms.u_viewPosition.value?.isVector3) uniforms.u_viewPosition.value = new Vector3();
465
- uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
466
- }
467
-
468
- if (uniforms.u_worldInverseTransposeMatrix) {
469
- if (!uniforms.u_worldInverseTransposeMatrix.value?.isMatrix4) uniforms.u_worldInverseTransposeMatrix.value = new Matrix4();
470
- uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
471
- }
240
+ side: material_def.doubleSided ? DoubleSide : FrontSide,
241
+ loaders: {
242
+ cacheKey: this.url,
243
+ getTexture: async url => {
244
+ // Find the index of the texture in the parser
245
+ const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
246
+
247
+ // Resolve the texture from the MaterialX root extension
248
+ const ext = this.materialX_root_data;
249
+ if (ext) {
250
+ const textures = ext.textures || [];
251
+ let index = -1;
252
+ for (const texture of textures) {
253
+ // Find the texture by name and use the pointer string to get the index
254
+ if (texture.name === filenameWithoutExt) {
255
+ const ptr = texture.pointer;
256
+ const indexStr = ptr.substring("/textures/".length);
257
+ index = parseInt(indexStr);
258
+
259
+ if (isNaN(index) || index < 0) {
260
+ console.error("[MaterialX] Invalid texture index in pointer:", ptr);
261
+ return;
262
+ }
263
+ else {
264
+ if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
265
+ }
266
+ }
267
+ }
472
268
 
473
- // Update time uniforms
474
- const time = performance.now() / 1000.0;
475
- if (uniforms.u_time) {
476
- uniforms.u_time.value = time;
477
- }
478
- if (uniforms.u_frame) {
479
- uniforms.u_frame.value = time;
269
+ if (index < 0) {
270
+ console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
271
+ return;
272
+ }
273
+ return this.parser.getDependency("texture", index).then(tex => {
274
+ if (debug) console.log("[MaterialX] Texture loaded:" + tex.name, tex);
275
+ return tex;
276
+ });
277
+ }
278
+ return null;
279
+ }
480
280
  }
481
-
482
- // Mark uniforms as needing update
483
- shaderMaterial.uniformsNeedUpdate = true;
484
- };
485
-
486
- shaderMaterial.name = `MaterialX_Generated_${materialXData.name}`;
487
-
488
- // Add debugging to see if the material compiles correctly
489
- if (debug) console.log("[MaterialX] material created successfully:", shaderMaterial.name);
490
- // if (debug) console.log("Material uniforms keys:", Object.keys(shaderMaterial.uniforms || {}));
491
- // if (debug) console.log("Material transparent:", shaderMaterial.transparent);
492
- // if (debug) console.log("Material side:", shaderMaterial.side);
493
-
281
+ });
494
282
  // Track this material for later lighting updates
495
- this.generatedMaterials.push(shaderMaterial);
283
+ this._generatedMaterials.push(shaderMaterial);
496
284
 
285
+ // Add debugging to see if the material compiles correctly
286
+ if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
497
287
  return shaderMaterial;
498
288
 
499
289
  } catch (error) {
500
290
  // This is a wasm error (an int) that we need to resolve
501
- console.error("[MaterialX] Error creating MaterialX material:", error);
291
+ console.error(`[MaterialX] Error creating MaterialX material (${material_extension.name}):`, error);
502
292
  // Return a fallback material with stored MaterialX data
503
293
  const fallbackMaterial = new MeshStandardMaterial();
504
- fallbackMaterial.userData.materialX = materialXData;
505
- fallbackMaterial.name = `MaterialX_Error_${materialXData.name}`;
294
+ fallbackMaterial.color.set(0xff00ff);
295
+ fallbackMaterial.userData.materialX = material_extension;
296
+ fallbackMaterial.name = `MaterialX_Error_${material_extension.name}`;
506
297
  return fallbackMaterial;
507
298
  }
508
299
  }
509
-
510
- private identityMatrix: Matrix4 = new Matrix4().identity();
511
- // Update lighting uniforms for all generated MaterialX materials
512
- updateLightingFromEnvironment(environment: MaterialXEnvironment): void {
513
-
514
- // Get lighting data from environment
515
- const lightData = environment.getLightData() || null;
516
- const lightCount = environment.getLightCount() || 0;
517
- const radianceTexture = environment.getRadianceTexture() || null;
518
- const irradianceTexture = environment.getIrradianceTexture() || null;
519
-
520
- if (debug) console.log(`[MaterialX] Updating lighting for ${this.generatedMaterials.length} MaterialX materials`, {
521
- lightData, radianceTexture, irradianceTexture,
522
- });
523
-
524
- // Update each generated material's lighting uniforms
525
- this.generatedMaterials.forEach((material, _index) => {
526
- if (!material.uniforms) return;
527
-
528
- // Update light count
529
- if (material.uniforms.u_numActiveLightSources && lightCount >= 0) {
530
- material.uniforms.u_numActiveLightSources.value = lightCount;
531
- }
532
-
533
- // Update light data if we have lights
534
- if (lightData) {
535
- if (!material.uniforms.u_lightData) {
536
- material.uniforms.u_lightData = { value: null };
537
- }
538
- material.uniforms.u_lightData.value = lightData;
539
- if (debug) console.log("[MaterialX] Updated light data for material", material.name, lightData, material.uniforms,);
540
- }
541
- else if(debug) console.warn("[MaterialX] No light data available to update uniforms for material", material.name);
542
-
543
- // Update environment uniforms
544
- if (material.uniforms.u_envMatrix) {
545
- material.uniforms.u_envMatrix.value = this.identityMatrix;
546
- }
547
- if (material.uniforms.u_envRadiance) {
548
- material.uniforms.u_envRadiance.value = radianceTexture;
549
- }
550
- if (material.uniforms.u_envRadianceMips) {
551
- material.uniforms.u_envRadianceMips.value = Math.trunc(Math.log2(Math.max(radianceTexture?.source.data.width ?? 0, radianceTexture?.source.data.height ?? 0))) + 1;
552
- }
553
- if (material.uniforms.u_envIrradiance) {
554
- material.uniforms.u_envIrradiance.value = irradianceTexture;
555
- }
556
-
557
- // Mark uniforms as needing update
558
- // console.log("Light data in uniforms", material.uniforms, material.fragmentShader);
559
- material.uniformsNeedUpdate = true;
560
- });
561
-
562
- /* TODO not working yet
563
- this.generatedMaterials.forEach((material, index) => {
564
- console.log("Regenerating shaders for MaterialX material:", index, material.name);
565
- material.userData.regenerateShaders();
566
- resetShaders(this.context);
567
- });
568
- */
569
- }
570
300
  }