@needle-tools/materialx 1.0.1-next.9e2a592 → 1.0.1-next.b9638d9

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