@needle-tools/materialx 1.0.0 → 1.0.1-next.19d0723

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,91 +1,79 @@
1
1
  import { Context } from "@needle-tools/engine";
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";
2
+ import { Material, MeshStandardMaterial, Texture, NearestFilter, CompressedTexture } from "three";
3
+ import { GLTF, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
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 MaterialXData {
10
- version: string; // e.g. "1.39"
11
- name: string; // e.g. "Material"
12
- mtlx: string; // MaterialX XML content
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 }>;
13
18
  }
14
19
 
15
- interface MaterialXDataIndex {
16
- name: string; // Material name reference
20
+ export type MaterialX_material_extension = {
21
+ name: string; // Material name reference
17
22
  }
18
23
 
19
24
  // MaterialX loader extension for js GLTFLoader
20
25
  export class MaterialXLoader implements GLTFLoaderPlugin {
21
- name = "NEEDLE_materials_mtlx";
26
+ readonly name = "NEEDLE_materials_mtlx";
22
27
 
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[] = [];
28
+ private readonly _generatedMaterials: MaterialXMaterial[] = [];
29
29
 
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();
34
- }
30
+ private _documentReadyPromise: Promise<any> | null = null;
35
31
 
36
- // Initialize MaterialX environment - called once after MaterialX is ready
37
- private async initializeEnvironment(): Promise<void> {
38
- if (this.environmentInitialized) return;
39
-
40
- if (debug) console.log("MaterialXLoader: Initializing MaterialX environment...");
41
-
42
- // Ensure MaterialX is initialized first
43
- await initializeMaterialX();
32
+ get materialX_root_data() {
33
+ return this.parser.json.extensions?.[this.name] as MaterialX_root_extension | null;
34
+ }
44
35
 
45
- // Set up environment with context
46
- const environment = state.materialXEnvironment;
47
- environment.setContext(this.context);
36
+ /** Generated materialX materials */
37
+ get materials() {
38
+ return this._generatedMaterials;
39
+ }
48
40
 
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);
41
+ constructor(private parser: GLTFParser, private context: Context) {
42
+ if (debug) console.log("MaterialXLoader created for parser");
43
+ // Start loading of MaterialX environment if the root extension exists
44
+ if (this.materialX_root_data) {
45
+ ready();
56
46
  }
57
47
  }
58
48
 
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;
49
+ loadMaterial(materialIndex: number): Promise<Material> | null {
50
+ const materialDef = this.parser.json.materials?.[materialIndex];
51
+ if (!materialDef?.extensions?.[this.name]) {
52
+ return null;
69
53
  }
70
- this.rootDataInitialized = true;
54
+ // Wrap the async implementation
55
+ return this._loadMaterialAsync(materialIndex);
56
+ }
57
+
58
+ afterRoot = async (_gltf: GLTF) => {
59
+ // Initialize MaterialX lighting system with scene data
60
+ const environment = state.materialXEnvironment;
61
+ environment.initialize(this.context);
71
62
  }
72
63
 
73
64
  // Parse the MaterialX document once and cache it
74
- private async parseRootDocument(): Promise<any> {
75
- if (this.documentParsePromise) {
76
- return this.documentParsePromise;
65
+ private async _materialXDocumentReady(): Promise<any> {
66
+ if (this._documentReadyPromise) {
67
+ return this._documentReadyPromise;
77
68
  }
78
-
79
- this.documentParsePromise = (async () => {
80
- if (this.parsedDocument) return this.parsedDocument;
81
-
82
- if (debug) console.log("Parsing MaterialX root document...");
69
+ return this._documentReadyPromise = (async () => {
70
+ if (debug) console.log("[MaterialX] Parsing MaterialX root document...");
83
71
 
84
72
  // Ensure MaterialX is initialized
85
- await initializeMaterialX();
73
+ await ready();
86
74
 
87
75
  if (!state.materialXModule) {
88
- throw new Error("MaterialX module failed to initialize");
76
+ throw new Error("[MaterialX] module failed to initialize");
89
77
  }
90
78
 
91
79
  // Create MaterialX document and parse ALL the XML data from root
@@ -93,43 +81,28 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
93
81
  doc.setDataLibrary(state.materialXStdLib);
94
82
 
95
83
  // Parse all MaterialX XML strings from the root data
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, "");
84
+ const root = this.materialX_root_data
85
+ if (root) {
86
+ if (debug) console.log(`[MaterialX] Parsing XML for: ${root.name}`);
87
+ await state.materialXModule.readFromXmlString(doc, root.mtlx, "");
99
88
  }
100
89
 
101
- if (debug) console.log("MaterialX root document parsed successfully");
102
-
103
- this.parsedDocument = doc;
90
+ if (debug) console.log("[MaterialX] root document parsed successfully");
104
91
  return doc;
105
92
  })();
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);
118
93
  }
119
94
 
120
95
  private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
121
- // Initialize root data first
122
- this.initializeRootData();
123
96
 
124
97
  const materialDef = this.parser.json.materials?.[materialIndex];
125
- if (debug) console.log("MaterialX extension found in material:", materialDef.extensions[this.name]);
98
+ if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions[this.name]);
126
99
 
127
100
  // Handle different types of MaterialX data
128
- const dataIndex = materialDef.extensions[this.name] as MaterialXDataIndex;
101
+ const dataIndex = materialDef.extensions[this.name] as MaterialX_material_extension;
129
102
 
130
103
  if (dataIndex) {
131
104
  // Create a new material and process MaterialX - AWAIT THIS!
132
- return await this.createMaterialXMaterial(dataIndex);
105
+ return await this._createMaterialXMaterial(dataIndex);
133
106
  }
134
107
 
135
108
  // Return fallback material instead of null
@@ -138,36 +111,29 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
138
111
  return fallbackMaterial;
139
112
  }
140
113
 
141
- private rootDocument: Promise<any> | null = null;
142
- private async createMaterialXMaterial(materialXData: MaterialXDataIndex): Promise<Material> {
114
+ private async _createMaterialXMaterial(material_extension: MaterialX_material_extension): Promise<Material> {
143
115
  try {
144
- if (debug) console.log(`Creating MaterialX material: ${materialXData.name}`);
145
-
146
- // Ensure MaterialX is initialized and document is parsed
147
- await initializeMaterialX();
116
+ if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
148
117
 
149
- if (!this.rootDocument) {
150
- this.rootDocument = this.parseRootDocument();
151
- }
152
- const doc = await this.rootDocument;
118
+ const doc = await this._materialXDocumentReady();
153
119
 
154
120
  if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
155
- console.warn("MaterialX WASM module not ready, returning fallback material");
121
+ console.warn("[MaterialX] WASM module not ready, returning fallback material");
156
122
  const fallbackMaterial = new MeshStandardMaterial();
157
- fallbackMaterial.userData.materialX = materialXData;
158
- fallbackMaterial.name = `MaterialX_Fallback_${materialXData.name}`;
123
+ fallbackMaterial.userData.materialX = material_extension;
124
+ fallbackMaterial.name = `MaterialX_Fallback_${material_extension.name}`;
159
125
  return fallbackMaterial;
160
126
  }
161
127
 
162
128
  // Find the renderable element following MaterialX example pattern exactly
163
- let renderableElement = null;
129
+ let renderableElement: any = null;
164
130
  let foundRenderable = false;
165
131
 
166
- if (debug) console.log("Mtlx doc", doc);
132
+ if (debug) console.log("[MaterialX] document", doc);
167
133
 
168
134
  // Search for material nodes first (following the reference pattern)
169
135
  const materialNodes = doc.getMaterialNodes();
170
- if (debug) console.log(`Found ${materialNodes.length} material nodes in document`, materialNodes);
136
+ if (debug) console.log(`[MaterialX] Found ${materialNodes.length} material nodes in document`, materialNodes);
171
137
 
172
138
  // Handle both array and vector-like APIs
173
139
  const materialNodesLength = materialNodes.length;
@@ -175,13 +141,13 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
175
141
  const materialNode = materialNodes[i];
176
142
  if (materialNode) {
177
143
  const materialName = materialNode.getNamePath();
178
- if (debug) console.log('Scan material: ', i, materialName);
144
+ if (debug) console.log('[MaterialX] Scan material: ', i, materialName);
179
145
 
180
146
  // Find the matching material
181
- if (materialName == materialXData.name) {
147
+ if (materialName == material_extension.name) {
182
148
  renderableElement = materialNode;
183
149
  foundRenderable = true;
184
- if (debug) console.log('-- add material: ', materialName);
150
+ if (debug) console.log('[MaterialX] -- add material: ', materialName);
185
151
  break;
186
152
  }
187
153
  }
@@ -235,321 +201,112 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
235
201
  */
236
202
 
237
203
  if (!renderableElement) {
238
- console.warn("No renderable element found in MaterialX document");
204
+ console.warn("[MaterialX] No renderable element found in MaterialX document");
239
205
  const fallbackMaterial = new MeshStandardMaterial();
240
- fallbackMaterial.userData.materialX = materialXData;
241
- fallbackMaterial.name = `MaterialX_NoRenderable_${materialXData.name}`;
206
+ fallbackMaterial.userData.materialX = material_extension;
207
+ fallbackMaterial.name = `MaterialX_NoRenderable_${material_extension.name}`;
242
208
  return fallbackMaterial;
243
209
  }
244
210
 
245
- if (debug) console.log("Using renderable element for shader generation");
211
+ if (debug) console.log("[MaterialX] Using renderable element for shader generation");
246
212
 
247
213
  // Check transparency and set context options like the reference
248
- let isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
214
+ const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
249
215
  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
252
216
 
253
217
  // Generate shaders using the element's name path
254
- if (debug) console.log("Generating MaterialX shaders...");
218
+ if (debug) console.log("[MaterialX] Generating MaterialX shaders...");
255
219
  const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
256
220
 
257
221
  const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
258
-
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;
340
- }
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' },
378
- });
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,
222
+ const shaderMaterial = new MaterialXMaterial({
223
+ name: material_extension.name,
224
+ shader,
410
225
  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;
226
+ loaders: {
227
+ getTexture: url => {
228
+ // Return a checkerboard texture for now
229
+ const defaultTexture = new Texture();
230
+ defaultTexture.image = new Image();
231
+ defaultTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAG0lEQVR4nGJqWH9q9e8XjA/VrL8UfQIEAAD//zn2CCX5UcsdAAAAAElFTkSuQmCC";
232
+ defaultTexture.needsUpdate = true;
233
+ // Pixelated filtering
234
+ defaultTexture.magFilter = NearestFilter;
235
+ defaultTexture.minFilter = NearestFilter;
236
+ new Promise(() => {
237
+ // Find the index of the texture in the parser
238
+ const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
239
+
240
+ // Resolve the texture from the MaterialX root extension
241
+ const ext = this.materialX_root_data;
242
+ if (ext) {
243
+ const textures = ext.textures || [];
244
+ let index = -1;
245
+ for (const texture of textures) {
246
+ // Find the texture by name and use the pointer string to get the index
247
+ if (texture.name === filenameWithoutExt) {
248
+ const ptr = texture.pointer;
249
+ const indexStr = ptr.substring("/textures/".length);
250
+ index = parseInt(indexStr);
251
+
252
+ if (isNaN(index) || index < 0) {
253
+ console.error("[MaterialX] Invalid texture index in pointer:", ptr);
254
+ return;
255
+ }
256
+ else {
257
+ if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
258
+ }
259
+ }
260
+ }
261
+
262
+ if (index < 0) {
263
+ console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
264
+ return;
265
+ }
266
+ this.parser.getDependency("texture", index).then(tex => {
267
+ if (debug) console.log("[MaterialX] Texture loaded:" + tex.name, tex);
268
+
269
+ // update the default texture with the loaded texture
270
+ defaultTexture.image = tex.image;
271
+ defaultTexture.needsUpdate = true;
272
+ defaultTexture.userData = tex.userData || {}; // needed for LODs
273
+ defaultTexture.format = tex.format;
274
+ defaultTexture.source = tex.source || defaultTexture.source;
275
+ defaultTexture.type = tex.type;
276
+ defaultTexture.format = tex.format;
277
+ defaultTexture.magFilter = tex.magFilter;
278
+ defaultTexture.minFilter = tex.minFilter;
279
+ defaultTexture.wrapS = tex.wrapS;
280
+ defaultTexture.wrapT = tex.wrapT;
281
+ defaultTexture.generateMipmaps = tex.generateMipmaps;
282
+ defaultTexture.mipmaps = tex.mipmaps || defaultTexture.mipmaps;
283
+ defaultTexture.unpackAlignment = tex.unpackAlignment;
284
+
285
+ if (!(defaultTexture instanceof CompressedTexture) && tex instanceof CompressedTexture) {
286
+ (defaultTexture as any).isCompressedTexture = true;
287
+ }
288
+ });
289
+ }
290
+ });
291
+ return defaultTexture;
292
+ }
467
293
  }
468
-
469
- // Mark uniforms as needing update
470
- shaderMaterial.uniformsNeedUpdate = true;
471
- };
472
-
473
- shaderMaterial.name = `MaterialX_Generated_${materialXData.name}`;
474
-
475
- // Add debugging to see if the material compiles correctly
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
-
294
+ });
481
295
  // Track this material for later lighting updates
482
- this.generatedMaterials.push(shaderMaterial);
296
+ this._generatedMaterials.push(shaderMaterial);
483
297
 
298
+ // Add debugging to see if the material compiles correctly
299
+ if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
484
300
  return shaderMaterial;
485
301
 
486
302
  } catch (error) {
487
303
  // This is a wasm error (an int) that we need to resolve
488
- console.error("Error creating MaterialX material:", error);
304
+ console.error("[MaterialX] Error creating MaterialX material:", error);
489
305
  // Return a fallback material with stored MaterialX data
490
306
  const fallbackMaterial = new MeshStandardMaterial();
491
- fallbackMaterial.userData.materialX = materialXData;
492
- fallbackMaterial.name = `MaterialX_Error_${materialXData.name}`;
307
+ fallbackMaterial.userData.materialX = material_extension;
308
+ fallbackMaterial.name = `MaterialX_Error_${material_extension.name}`;
493
309
  return fallbackMaterial;
494
310
  }
495
311
  }
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
- }
555
312
  }