@needle-tools/materialx 1.0.1-next.df0e959 → 1.0.1

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/README.md CHANGED
@@ -1,10 +1,5 @@
1
1
  # Needle MaterialX
2
2
 
3
- Load and display MaterialX materials in Needle Engine
4
-
5
- ## Installation
6
- `npm i @needle-tools/materialx`
7
-
8
3
  ## How to use
9
4
 
10
5
  To use with Needle Engine simply import the module
@@ -2,7 +2,5 @@
2
2
  import { TypeStore } from "@needle-tools/engine"
3
3
 
4
4
  // Import types
5
- import { MaterialXUniformUpdate } from "../src/loader/loader.needle.js";
6
5
 
7
6
  // Register types
8
- TypeStore.add("MaterialXUniformUpdate", MaterialXUniformUpdate);
package/index.ts CHANGED
@@ -2,4 +2,4 @@ import { registerNeedleLoader } from "./src/loader/loader.needle.js";
2
2
 
3
3
  registerNeedleLoader();
4
4
 
5
- export { ready, getMaterialXEnvironment } from "./src/index.js";
5
+ export { initializeMaterialX, getMaterialXEnvironment } from "./src/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/materialx",
3
- "version": "1.0.1-next.df0e959",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "exports": {
@@ -8,11 +8,7 @@
8
8
  "import": "./index.ts",
9
9
  "require": "./index.js"
10
10
  },
11
- "./package.json": "./package.json",
12
- "./codegen/register_types.ts": {
13
- "import": "./codegen/register_types.ts",
14
- "require": "./codegen/register_types.js"
15
- }
11
+ "./package.json": "./package.json"
16
12
  },
17
13
  "peerDependencies": {
18
14
  "@needle-tools/engine": "4.x",
@@ -27,16 +23,5 @@
27
23
  "publishConfig": {
28
24
  "access": "public",
29
25
  "registry": "https://registry.npmjs.org/"
30
- },
31
- "keywords": [
32
- "needle",
33
- "materialx",
34
- "material",
35
- "shader",
36
- "threejs",
37
- "three.js",
38
- "webgl",
39
- "mtlx",
40
- "rendering"
41
- ]
42
- }
26
+ }
27
+ }
package/src/helper.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  //
5
5
 
6
- import { getParam, getWorldDirection } from '@needle-tools/engine';
6
+ import { getParam } from '@needle-tools/engine';
7
7
  import * as THREE from 'three';
8
8
 
9
9
  const debug = getParam("debugmaterialx");
@@ -88,7 +88,7 @@ function fromMatrix(matrix, dimension)
88
88
  * @param {mx.Uniform.value} value
89
89
  * @param {mx.Uniform.name} name
90
90
  * @param {mx.Uniforms} uniforms
91
- * @param {THREE.TextureLoader} textureLoader
91
+ * @param {THREE.textureLoader} textureLoader
92
92
  * @param {string} searchPath
93
93
  * @param {boolean} flipY
94
94
  */
@@ -301,14 +301,10 @@ export function findLights(doc)
301
301
  export async function registerLights(mx, lights, genContext)
302
302
  {
303
303
  mx.HwShaderGenerator.unbindLightShaders(genContext);
304
- // TODO Remove, not sure why we need that – something resets the value inbetween calls to registerLights
305
- genContext.getOptions().hwMaxActiveLightSources = 4;
306
304
 
307
305
  const lightTypesBound = {};
308
306
  const lightData = [];
309
307
  let lightId = 1;
310
- let lightCount = 0;
311
- const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
312
308
 
313
309
  // All light types so that we have NodeDefs for them
314
310
  const defaultLightRigXml = `<?xml version="1.0"?>
@@ -335,9 +331,6 @@ export async function registerLights(mx, lights, genContext)
335
331
  const defaultLights = findLights(document);
336
332
  if (debug) console.log("Default lights in MaterialX document", defaultLights);
337
333
 
338
- // Loading a document seems to reset this option for some reason, so we set it again
339
- genContext.getOptions().hwMaxActiveLightSources = maxLightCount;
340
-
341
334
  // Register types only – we get these from the default light rig XML above
342
335
  // This is needed to ensure that the light shaders are bound for each light type
343
336
  for (let light of defaultLights)
@@ -421,44 +414,18 @@ export async function registerLights(mx, lights, genContext)
421
414
  mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
422
415
  }
423
416
 
424
- const wp = light.getWorldPosition(new THREE.Vector3());
425
- const wd = getWorldDirection(light, new THREE.Vector3(0,0,-1));
426
417
  lightData.push({
427
418
  type: lightTypesBound[lightDefinitionName],
428
- position: wp.clone(),
429
- direction: wd.clone(),
419
+ direction: light.direction?.clone() || new THREE.Vector3(0, -1, 0),
430
420
  color: new THREE.Vector3().fromArray(light.color.toArray()),
431
- intensity: light.intensity, // Scale intensity for spot lights
432
- decay_rate: 2.0, // physically-based default decay rate
433
- inner_angle: 1.0,
434
- outer_angle: 2.0,
421
+ intensity: light.intensity,
435
422
  });
436
423
  }
437
424
 
438
- // Count the number of lights that are not empty
439
- lightCount = lightData.length;
440
-
441
- // If we don't have enough entries in lightData, fill with empty lights
442
- if (lightData.length < maxLightCount)
443
- {
444
- const emptyLight = {
445
- type: 0, // Default light type
446
- position: new THREE.Vector3(0, 0, 0),
447
- direction: new THREE.Vector3(0, 0, -1),
448
- color: new THREE.Vector3(0, 0, 0),
449
- intensity: 0,
450
- decay_rate: 2,
451
- inner_angle: 0,
452
- outer_angle: 0,
453
- };
454
- while (lightData.length < maxLightCount) {
455
- lightData.push(emptyLight);
456
- }
457
- }
458
-
459
- if (debug) console.log("Registered lights in MaterialX context", lightTypesBound, lightData);
425
+ // Make sure max light count is large enough
426
+ genContext.getOptions().hwMaxActiveLightSources = Math.max(genContext.getOptions().hwMaxActiveLightSources, lightData.length);
460
427
 
461
- return { lightData, lightCount };
428
+ return lightData;
462
429
  }
463
430
 
464
431
  /**
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ready, state } from "./materialx.js";
1
+ import { initializeMaterialX, state } from "./materialx.js";
2
2
 
3
3
  const getMaterialXEnvironment = () => state.materialXEnvironment;
4
4
 
5
- export { ready, getMaterialXEnvironment };
5
+ export { initializeMaterialX, getMaterialXEnvironment };
@@ -52,9 +52,9 @@ export class MaterialXUniformUpdate extends Component {
52
52
  }
53
53
 
54
54
  export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
55
- readonly name = "MaterialXLoaderPlugin";
55
+ name = "MaterialXLoaderPlugin";
56
56
 
57
- private loader: MaterialXLoader | null = null;
57
+ mtlxLoader: MaterialXLoader | null = null;
58
58
 
59
59
  onImport = (loader: GLTFLoader, url: string, context: Context) => {
60
60
  if (debug) console.log("MaterialXLoaderPlugin: Registering MaterialX extension for", url);
@@ -62,13 +62,13 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
62
62
  // Register the MaterialX loader extension
63
63
  // Environment initialization is now handled in the MaterialXLoader constructor
64
64
  loader.register(p => {
65
- this.loader = new MaterialXLoader(p, context);
66
- return this.loader;
65
+ this.mtlxLoader = new MaterialXLoader(p, context);
66
+ return this.mtlxLoader;
67
67
  });
68
68
  };
69
69
 
70
- onLoaded = (url: string, gltf: GLTF, context: Context) => {
71
- if (debug) console.log("[MaterialX] MaterialXLoaderPlugin: glTF loaded", url, gltf.scene);
70
+ onLoaded = (url: string, gltf: GLTF, _context: Context) => {
71
+ if (debug) console.log("MaterialXLoaderPlugin: glTF loaded", url, gltf.scene);
72
72
 
73
73
  // Set up onBeforeRender callbacks for objects with MaterialX materials
74
74
  // This ensures uniforms are updated properly during rendering
@@ -78,23 +78,25 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
78
78
  const material = mesh.material as Material;
79
79
 
80
80
  if (material?.userData?.updateUniforms) {
81
- if (debug) console.log("[MaterialX] Adding MaterialX uniform update component to:", child.name);
81
+ if (debug) console.log("Adding MaterialX uniform update component to:", child.name);
82
82
  child.addComponent(MaterialXUniformUpdate);
83
83
  }
84
84
  }
85
85
  });
86
86
 
87
- if (debug) console.log("[MaterialX] Loaded: ", this.loader);
87
+ if (debug) console.log("Loaded: ", this.mtlxLoader);
88
88
 
89
89
  // Initialize MaterialX lighting system with scene data
90
90
  const environment = state.materialXEnvironment;
91
- environment.initializeFromContext(context).then(() => {
92
- this.loader?.updateLightingFromEnvironment(environment);
91
+ environment.initializeFromContext().then(() => {
92
+ if (this.mtlxLoader) {
93
+ this.mtlxLoader.updateLightingFromEnvironment(environment);
94
+ }
93
95
  });
94
96
  };
95
97
 
96
98
  onExport = (_exporter: GLTFExporter, _context: Context) => {
97
- console.log("[MaterialX] TODO: MaterialXLoaderPlugin: Setting up export extensions");
99
+ console.log("TODO: MaterialXLoaderPlugin: Setting up export extensions");
98
100
  // TODO: Add MaterialX export functionality if needed
99
101
  };
100
102
  }
@@ -1,34 +1,31 @@
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";
2
+ import { RawShaderMaterial, Material, MeshStandardMaterial, LoadingManager, TextureLoader, Texture, NearestFilter, Matrix4, GLSL3, AddEquation, OneMinusSrcAlphaFactor, SrcAlphaFactor, DoubleSide, Matrix3, Vector3, Object3D, Camera } from "three";
3
3
  import { GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
4
4
  import { getUniformValues } from "../helper.js";
5
- import { ready, MaterialXEnvironment, state } from "../materialx.js";
5
+ import { initializeMaterialX, MaterialXEnvironment, state } from "../materialx.js";
6
6
  import { debug } from "../utils.js";
7
7
 
8
8
  // TypeScript interfaces matching the C# data structures
9
- interface 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;
9
+ interface MaterialXData {
10
+ version: string; // e.g. "1.39"
11
+ name: string; // e.g. "Material"
12
+ mtlx: string; // MaterialX XML content
16
13
  }
17
14
 
18
- interface MaterialX_material_extension {
19
- name: string; // Material name reference
15
+ interface MaterialXDataIndex {
16
+ name: string; // Material name reference
20
17
  }
21
18
 
22
19
  // MaterialX loader extension for js GLTFLoader
23
20
  export class MaterialXLoader implements GLTFLoaderPlugin {
24
21
  name = "NEEDLE_materials_mtlx";
25
22
 
26
- private rootMaterialXData: MaterialX_root_extension | null = null;
23
+ private rootMaterialXData: MaterialXData | null = null;
27
24
  private parsedDocument: any = null;
28
25
  private documentParsePromise: Promise<any> | null = null;
29
26
  private rootDataInitialized = false;
30
27
  private environmentInitialized = false;
31
- private generatedMaterials: ShaderMaterial[] = [];
28
+ private generatedMaterials: RawShaderMaterial[] = [];
32
29
 
33
30
  constructor(private parser: GLTFParser, private context: Context) {
34
31
  if (debug) console.log("MaterialXLoader created for parser");
@@ -40,20 +37,22 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
40
37
  private async initializeEnvironment(): Promise<void> {
41
38
  if (this.environmentInitialized) return;
42
39
 
43
- if (debug) console.log("[MaterialX] MaterialXLoader: Initializing MaterialX environment...");
40
+ if (debug) console.log("MaterialXLoader: Initializing MaterialX environment...");
44
41
 
45
42
  // Ensure MaterialX is initialized first
46
- await ready();
43
+ await initializeMaterialX();
47
44
 
48
45
  // Set up environment with context
49
46
  const environment = state.materialXEnvironment;
47
+ environment.setContext(this.context);
48
+
50
49
  // Initialize the environment from context (properly awaited)
51
50
  try {
52
- await environment.initializeFromContext(this.context);
51
+ await environment.initializeFromContext();
53
52
  this.environmentInitialized = true;
54
- if (debug) console.log("[MaterialX] MaterialXLoader: Environment initialized successfully");
53
+ if (debug) console.log("MaterialXLoader: Environment initialized successfully");
55
54
  } catch (error) {
56
- console.warn("[MaterialX] MaterialXLoader: Failed to initialize MaterialX environment:", error);
55
+ console.warn("MaterialXLoader: Failed to initialize MaterialX environment:", error);
57
56
  }
58
57
  }
59
58
 
@@ -63,10 +62,10 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
63
62
 
64
63
  const gltfExtensions = this.parser.json.extensions;
65
64
  if (gltfExtensions?.[this.name]) {
66
- if (debug) console.log("[MaterialX] extension found in root:", gltfExtensions[this.name]);
65
+ if (debug) console.log("MaterialX extension found in root:", gltfExtensions[this.name]);
67
66
 
68
67
  const materialXExtension = gltfExtensions[this.name];
69
- this.rootMaterialXData = materialXExtension as MaterialX_root_extension;
68
+ this.rootMaterialXData = materialXExtension as MaterialXData;
70
69
  }
71
70
  this.rootDataInitialized = true;
72
71
  }
@@ -80,13 +79,13 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
80
79
  this.documentParsePromise = (async () => {
81
80
  if (this.parsedDocument) return this.parsedDocument;
82
81
 
83
- if (debug) console.log("[MaterialX] Parsing MaterialX root document...");
82
+ if (debug) console.log("Parsing MaterialX root document...");
84
83
 
85
84
  // Ensure MaterialX is initialized
86
- await ready();
85
+ await initializeMaterialX();
87
86
 
88
87
  if (!state.materialXModule) {
89
- throw new Error("[MaterialX] module failed to initialize");
88
+ throw new Error("MaterialX module failed to initialize");
90
89
  }
91
90
 
92
91
  // Create MaterialX document and parse ALL the XML data from root
@@ -95,11 +94,11 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
95
94
 
96
95
  // Parse all MaterialX XML strings from the root data
97
96
  if (this.rootMaterialXData) {
98
- if (debug) console.log(`[MaterialX] Parsing XML for: ${this.rootMaterialXData.name}`);
97
+ if (debug) console.log(`Parsing MaterialX XML for: ${this.rootMaterialXData.name}`);
99
98
  await state.materialXModule.readFromXmlString(doc, this.rootMaterialXData.mtlx, "");
100
99
  }
101
100
 
102
- if (debug) console.log("[MaterialX] root document parsed successfully");
101
+ if (debug) console.log("MaterialX root document parsed successfully");
103
102
 
104
103
  this.parsedDocument = doc;
105
104
  return doc;
@@ -123,10 +122,10 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
123
122
  this.initializeRootData();
124
123
 
125
124
  const materialDef = this.parser.json.materials?.[materialIndex];
126
- if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions[this.name]);
125
+ if (debug) console.log("MaterialX extension found in material:", materialDef.extensions[this.name]);
127
126
 
128
127
  // Handle different types of MaterialX data
129
- const dataIndex = materialDef.extensions[this.name] as MaterialX_material_extension;
128
+ const dataIndex = materialDef.extensions[this.name] as MaterialXDataIndex;
130
129
 
131
130
  if (dataIndex) {
132
131
  // Create a new material and process MaterialX - AWAIT THIS!
@@ -140,12 +139,12 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
140
139
  }
141
140
 
142
141
  private rootDocument: Promise<any> | null = null;
143
- private async createMaterialXMaterial(materialXData: MaterialX_material_extension): Promise<Material> {
142
+ private async createMaterialXMaterial(materialXData: MaterialXDataIndex): Promise<Material> {
144
143
  try {
145
144
  if (debug) console.log(`Creating MaterialX material: ${materialXData.name}`);
146
145
 
147
146
  // Ensure MaterialX is initialized and document is parsed
148
- await ready();
147
+ await initializeMaterialX();
149
148
 
150
149
  if (!this.rootDocument) {
151
150
  this.rootDocument = this.parseRootDocument();
@@ -153,7 +152,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
153
152
  const doc = await this.rootDocument;
154
153
 
155
154
  if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
156
- console.warn("[MaterialX] WASM module not ready, returning fallback material");
155
+ console.warn("MaterialX WASM module not ready, returning fallback material");
157
156
  const fallbackMaterial = new MeshStandardMaterial();
158
157
  fallbackMaterial.userData.materialX = materialXData;
159
158
  fallbackMaterial.name = `MaterialX_Fallback_${materialXData.name}`;
@@ -164,11 +163,11 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
164
163
  let renderableElement = null;
165
164
  let foundRenderable = false;
166
165
 
167
- if (debug) console.log("[MaterialX] document", doc);
166
+ if (debug) console.log("Mtlx doc", doc);
168
167
 
169
168
  // Search for material nodes first (following the reference pattern)
170
169
  const materialNodes = doc.getMaterialNodes();
171
- 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);
172
171
 
173
172
  // Handle both array and vector-like APIs
174
173
  const materialNodesLength = materialNodes.length;
@@ -176,13 +175,13 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
176
175
  const materialNode = materialNodes[i];
177
176
  if (materialNode) {
178
177
  const materialName = materialNode.getNamePath();
179
- if (debug) console.log('[MaterialX] Scan material: ', i, materialName);
178
+ if (debug) console.log('Scan material: ', i, materialName);
180
179
 
181
180
  // Find the matching material
182
181
  if (materialName == materialXData.name) {
183
182
  renderableElement = materialNode;
184
183
  foundRenderable = true;
185
- if (debug) console.log('[MaterialX] -- add material: ', materialName);
184
+ if (debug) console.log('-- add material: ', materialName);
186
185
  break;
187
186
  }
188
187
  }
@@ -236,35 +235,35 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
236
235
  */
237
236
 
238
237
  if (!renderableElement) {
239
- console.warn("[MaterialX] No renderable element found in MaterialX document");
238
+ console.warn("No renderable element found in MaterialX document");
240
239
  const fallbackMaterial = new MeshStandardMaterial();
241
240
  fallbackMaterial.userData.materialX = materialXData;
242
241
  fallbackMaterial.name = `MaterialX_NoRenderable_${materialXData.name}`;
243
242
  return fallbackMaterial;
244
243
  }
245
244
 
246
- if (debug) console.log("[MaterialX] Using renderable element for shader generation");
245
+ if (debug) console.log("Using renderable element for shader generation");
247
246
 
248
247
  // Check transparency and set context options like the reference
249
248
  let isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
250
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
251
252
 
252
253
  // Generate shaders using the element's name path
253
- if (debug) console.log("[MaterialX] Generating MaterialX shaders...");
254
+ if (debug) console.log("Generating MaterialX shaders...");
254
255
  const elementName = (renderableElement as any).getNamePath ? (renderableElement as any).getNamePath() : (renderableElement as any).getName();
255
256
 
256
257
  const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
257
258
 
258
- // Get vertex and fragment shader source, and remove #version directive for newer js.
259
- // It's added by three.js glslVersion.
259
+ // Get vertex and fragment shader source
260
+ // Remove #version directive for newer js. It's added by RawShaderMaterial glslVersion.
260
261
  let vertexShader = shader.getSourceCode("vertex").replace(/^#version.*$/gm, '').trim();
261
262
  let fragmentShader = shader.getSourceCode("pixel").replace(/^#version.*$/gm, '').trim();
262
263
 
263
264
  // MaterialX uses different attribute names than js defaults,
264
265
  // 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
266
+ // Otherwise, we'd have to modify the mesh attributes (see below).
268
267
  vertexShader = vertexShader.replace(/\bi_position\b/g, 'position');
269
268
  vertexShader = vertexShader.replace(/\bi_normal\b/g, 'normal');
270
269
  vertexShader = vertexShader.replace(/\bi_texcoord_0\b/g, 'uv');
@@ -272,7 +271,6 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
272
271
  vertexShader = vertexShader.replace(/\bi_tangent\b/g, 'tangent');
273
272
  vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
274
273
 
275
- // Patch fragmentShader
276
274
  fragmentShader = fragmentShader.replace(/\bi_position\b/g, 'position');
277
275
  fragmentShader = fragmentShader.replace(/\bi_normal\b/g, 'normal');
278
276
  fragmentShader = fragmentShader.replace(/\bi_texcoord_0\b/g, 'uv');
@@ -280,30 +278,19 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
280
278
  fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
281
279
  fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
282
280
 
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>`);
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
+ */
304
291
 
305
292
  if (debug) {
306
- console.group("[MaterialX]: ", materialXData.name);
293
+ console.group("Material: ", materialXData.name);
307
294
  console.log("Vertex shader length:", vertexShader.length, vertexShader);
308
295
  console.log("Fragment shader length:", fragmentShader.length, fragmentShader);
309
296
  console.groupEnd();
@@ -342,17 +329,17 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
342
329
  const textures = ext?.textures || [];
343
330
 
344
331
  const index = textures.findIndex(tex => {
345
- if (debug) console.log("[MaterialX] Checking texture:", tex.name, "against URL:", filenameWithoutExt);
332
+ if (debug) console.log("Checking texture:", tex.name, "against URL:", filenameWithoutExt);
346
333
  return tex.name === filenameWithoutExt;
347
334
  });
348
335
 
349
336
  if (index < 0) {
350
- console.warn("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
337
+ console.warn("Texture not found in parser:", filenameWithoutExt, this.parser.json);
351
338
  onError?.(new Error(`Texture not found: ${filenameWithoutExt}`));
352
339
  return;
353
340
  }
354
341
  this.parser.getDependency("texture", index).then(tex => {
355
- if (debug) console.log("[MaterialX] Texture loaded:", tex);
342
+ if (debug) console.log("Texture loaded:", tex);
356
343
  // update the checkerboard texture with the loaded texture
357
344
  checkerboardTexture.image = tex.image;
358
345
  checkerboardTexture.needsUpdate = true;
@@ -397,7 +384,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
397
384
  }
398
385
 
399
386
  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);
387
+ if (debug) console.log("Radiance texture mips:", mips, "for texture size:", radianceTexture?.width, "x", radianceTexture?.height);
401
388
  Object.assign(uniforms, {
402
389
  u_envMatrix: { value: getLightRotation() },
403
390
  u_envRadiance: { value: radianceTexture, type: 't' },
@@ -415,7 +402,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
415
402
  // console.log("Generated fragment shader:", fragmentShader.substring(0, 500) + "...");
416
403
 
417
404
  // Create js RawShaderMaterial (with GLSL3 for MaterialX compatibility)
418
- const shaderMaterial = new ShaderMaterial({
405
+ const shaderMaterial = new RawShaderMaterial({
419
406
  uniforms: uniforms,
420
407
  vertexShader: vertexShader,
421
408
  fragmentShader: fragmentShader,
@@ -486,7 +473,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
486
473
  shaderMaterial.name = `MaterialX_Generated_${materialXData.name}`;
487
474
 
488
475
  // Add debugging to see if the material compiles correctly
489
- if (debug) console.log("[MaterialX] material created successfully:", shaderMaterial.name);
476
+ if (debug) console.log("MaterialX material created successfully:", shaderMaterial.name);
490
477
  // if (debug) console.log("Material uniforms keys:", Object.keys(shaderMaterial.uniforms || {}));
491
478
  // if (debug) console.log("Material transparent:", shaderMaterial.transparent);
492
479
  // if (debug) console.log("Material side:", shaderMaterial.side);
@@ -498,7 +485,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
498
485
 
499
486
  } catch (error) {
500
487
  // This is a wasm error (an int) that we need to resolve
501
- console.error("[MaterialX] Error creating MaterialX material:", error);
488
+ console.error("Error creating MaterialX material:", error);
502
489
  // Return a fallback material with stored MaterialX data
503
490
  const fallbackMaterial = new MeshStandardMaterial();
504
491
  fallbackMaterial.userData.materialX = materialXData;
@@ -512,12 +499,12 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
512
499
  updateLightingFromEnvironment(environment: MaterialXEnvironment): void {
513
500
 
514
501
  // Get lighting data from environment
502
+ // const lights = environment.getLights() || [];
515
503
  const lightData = environment.getLightData() || null;
516
- const lightCount = environment.getLightCount() || 0;
517
504
  const radianceTexture = environment.getRadianceTexture() || null;
518
505
  const irradianceTexture = environment.getIrradianceTexture() || null;
519
506
 
520
- if (debug) console.log(`[MaterialX] Updating lighting for ${this.generatedMaterials.length} MaterialX materials`, {
507
+ if (debug) console.log(`Updating lighting for ${this.generatedMaterials.length} MaterialX materials`, {
521
508
  lightData, radianceTexture, irradianceTexture,
522
509
  });
523
510
 
@@ -526,8 +513,8 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
526
513
  if (!material.uniforms) return;
527
514
 
528
515
  // Update light count
529
- if (material.uniforms.u_numActiveLightSources && lightCount >= 0) {
530
- material.uniforms.u_numActiveLightSources.value = lightCount;
516
+ if (material.uniforms.u_numActiveLightSources && lightData) {
517
+ material.uniforms.u_numActiveLightSources.value = lightData.length;
531
518
  }
532
519
 
533
520
  // Update light data if we have lights
@@ -536,9 +523,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
536
523
  material.uniforms.u_lightData = { value: null };
537
524
  }
538
525
  material.uniforms.u_lightData.value = lightData;
539
- if (debug) console.log("[MaterialX] Updated light data for material", material.name, lightData, material.uniforms,);
540
526
  }
541
- else if(debug) console.warn("[MaterialX] No light data available to update uniforms for material", material.name);
542
527
 
543
528
  // Update environment uniforms
544
529
  if (material.uniforms.u_envMatrix) {
package/src/materialx.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { Context, delay, isDevEnvironment, ObjectUtils, GameObject } from "@needle-tools/engine";
1
+ import { Context, delay, isDevEnvironment, ObjectUtils } from "@needle-tools/engine";
2
2
  import MaterialX from "../bin/JsMaterialXGenShader.js";
3
3
  import { debug } from "./utils.js";
4
4
  import { renderPMREMToEquirect } from "./textureHelper.js";
5
- import { Light, MeshBasicMaterial, Object3D, PMREMGenerator, Texture } from "three";
5
+ import { Light, MeshBasicMaterial, Object3D, PMREMGenerator } from "three";
6
6
  import { registerLights } from "./helper.js";
7
7
 
8
8
 
9
+ // Global MaterialX module instance - initialized lazily
9
10
  export const state = new class {
10
- materialXModule: typeof MaterialX | null = null;
11
+ materialXModule: any = null;
11
12
  materialXGenerator: any = null;
12
13
  materialXGenContext: any = null;
13
14
  materialXStdLib: any = null;
@@ -21,14 +22,14 @@ export const state = new class {
21
22
  }
22
23
  }
23
24
 
24
- /** Initialize the MaterialX module. Must be awaited before trying to create materials */
25
- export async function ready(): Promise<void> {
25
+ // Initialize MaterialX WASM module lazily
26
+ export async function initializeMaterialX(): Promise<void> {
26
27
  if (state.materialXInitPromise) {
27
28
  return state.materialXInitPromise;
28
29
  }
29
30
  return state.materialXInitPromise = (async () => {
30
31
  if (state.materialXModule) return; // Already initialized
31
- if (debug) console.log("[MaterialX] Initializing WASM module...");
32
+ if (debug) console.log("Initializing MaterialX WASM module...");
32
33
  try {
33
34
 
34
35
  const urls: Array<string> = await Promise.all([
@@ -43,7 +44,7 @@ export async function ready(): Promise<void> {
43
44
 
44
45
  const module = await MaterialX({
45
46
  locateFile: (path: string, scriptDirectory: string) => {
46
- if (debug) console.debug("[MaterialX] locateFile called:", { path, scriptDirectory });
47
+ if (debug) console.debug("MaterialX locateFile called:", { path, scriptDirectory });
47
48
 
48
49
  if (path.includes("JsMaterialXCore.wasm")) {
49
50
  return JsMaterialXCore; // Use the URL for the core WASM file
@@ -58,7 +59,7 @@ export async function ready(): Promise<void> {
58
59
  return scriptDirectory + path;
59
60
  },
60
61
  });
61
- if (debug) console.log("[MaterialX] module loaded", module);
62
+ if (debug) console.log("MaterialXLoader module loaded", module);
62
63
  state.materialXModule = module;
63
64
 
64
65
  // Initialize shader generator and context
@@ -70,36 +71,23 @@ export async function ready(): Promise<void> {
70
71
  state.materialXStdLib = module.loadStandardLibraries(state.materialXGenContext);
71
72
  tempDoc.setDataLibrary(state.materialXStdLib);
72
73
 
73
- // TODO ShaderInterfaceType.SHADER_INTERFACE_REDUCED would be better, but doesn't actually seem to be supported in the MaterialX javascript bindings
74
- const options = state.materialXGenContext.getOptions();
75
- state.materialXGenContext.getOptions().shaderInterfaceType = state.materialXModule.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE;
76
-
77
- // SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
78
- // SPECULAR_ENVIRONMENT_FIS: Use Filtered Importance Sampling for specular environment/indirect lighting.
79
- // SPECULAR_ENVIRONMENT_PREFILTER: Use pre-filtered environment maps for specular environment/indirect lighting.
80
- state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_FIS;
81
-
82
- // TRANSMISSION_REFRACTION: Use a refraction approximation for transmission rendering.
83
- // TRANSMISSION_OPACITY: Use opacity for transmission rendering.
84
- // state.materialXGenContext.getOptions().hwTransmissionRenderMethod = state.materialXModule.HwTransmissionRenderMethod.TRANSMISSION_REFRACTION;
85
-
86
- // Turned off because we're doing color space conversion the three.js way
87
- state.materialXGenContext.getOptions().hwSrgbEncodeOutput = false;
88
-
89
- // Enables the generation of a prefiltered environment map.
90
- // TODO Would be great to use but requires setting more uniforms (like u_envPrefilterMip).
91
- // When set to true, the u_envRadiance map is expected to be a prefiltered environment map.
92
- // state.materialXGenContext.getOptions().hwWriteEnvPrefilter = true;
93
-
94
- // Set a reasonable default for max active lights
95
- state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
74
+ // Initialize basic lighting with default light rig
75
+ // const defaultLightRigXml = `<?xml version="1.0"?>
76
+ // <materialx version="1.39">
77
+ // <!-- Default directional light -->
78
+ // <directional_light name="default_light" type="lightshader">
79
+ // <input name="direction" type="vector3" value="0.0, -1.0, -0.5" />
80
+ // <input name="color" type="color3" value="1.0, 1.0, 1.0" />
81
+ // <input name="intensity" type="float" value="1.0" />
82
+ // </directional_light>
83
+ // </materialx>`;
96
84
 
97
85
  // This prewarms the shader generation context to have all light types
98
86
  await registerLights(state.materialXModule, [], state.materialXGenContext);
99
87
 
100
- if (debug) console.log("[MaterialX] generator initialized successfully");
88
+ if (debug) console.log("MaterialX generator initialized successfully");
101
89
  } catch (error) {
102
- console.error("[MaterialX] Failed to load MaterialX module:", error);
90
+ console.error("Failed to load MaterialX module:", error);
103
91
  throw error;
104
92
  }
105
93
  })();
@@ -107,79 +95,67 @@ export async function ready(): Promise<void> {
107
95
 
108
96
  // MaterialX Environment Manager - handles lighting and environment setup
109
97
  export class MaterialXEnvironment {
110
- private _context: Context | null = null;
111
- private _lightData: any = null;
112
- private _lightCount: number = 0;
113
- private _radianceTexture: Texture | null = null;
114
- private _irradianceTexture: Texture | null = null;
115
- private _initializePromise: Promise<boolean> | null = null;
98
+ private lights: any[] = [];
99
+ private lightData: any = null;
100
+ private radianceTexture: any = null;
101
+ private irradianceTexture: any = null;
102
+ private context: Context | null = null;
103
+ private initialized: boolean = false;
116
104
 
117
105
  constructor() {
118
- if (debug) console.log("[MaterialX] Environment created");
106
+ if (debug) console.log("MaterialX Environment created");
119
107
  }
120
108
 
121
- // Initialize with Needle Engine context
122
- async initializeFromContext(context: Context): Promise<boolean> {
109
+ setContext(context: Context) {
110
+ this.context = context;
111
+ }
123
112
 
124
- // Prevent multiple initializations
125
- if (this._initializePromise) {
126
- if (debug) console.log("[MaterialX] environment already initialized, skipping");
127
- return this._initializePromise;
113
+ /*
114
+ // Initialize MaterialX lighting system based on the reference implementation
115
+ async initializeLighting(lightRigXml: string, renderer?: any, radianceTexture?: any, irradianceTexture?: any): Promise<void> {
116
+ if (!materialXModule || !materialXGenContext) {
117
+ console.warn("MaterialX module not initialized, skipping lighting setup");
118
+ return;
128
119
  }
129
120
 
130
- return this._initializePromise = this._initialize(context);
121
+ registerLights(materialXModule, this.lights, materialXGenContext);
131
122
  }
123
+ */
132
124
 
133
- getLightData() { return this._lightData; }
134
- getLightCount() { return this._lightCount; }
135
-
136
- setRadianceTexture(texture: Texture) { this._radianceTexture = texture; }
137
- getRadianceTexture() { return this._radianceTexture; }
138
-
139
- getIrradianceTexture() { return this._irradianceTexture; }
140
- setIrradianceTexture(texture: Texture) { this._irradianceTexture = texture; }
141
-
142
- // Reset the environment to allow re-initialization
143
- reset() {
144
- if (debug) console.log("[MaterialX] Resetting environment");
145
- if (this._radianceTexture) {
146
- this._radianceTexture.dispose();
147
- this._radianceTexture = null;
148
- }
149
- if (this._irradianceTexture) {
150
- this._irradianceTexture.dispose();
151
- this._irradianceTexture = null;
125
+ // Initialize with Needle Engine context
126
+ async initializeFromContext(): Promise<void> {
127
+ if (!this.context) {
128
+ console.warn("No Needle context available for MaterialX environment initialization");
129
+ return;
152
130
  }
153
- this._initializePromise = null;
154
- // this.lights = [];
155
- this._lightData = null;
156
- }
157
-
158
- private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
159
131
 
160
- this._context = context;
132
+ // Prevent multiple initializations
133
+ if (this.initialized) {
134
+ if (debug) console.log("MaterialX environment already initialized, skipping");
135
+ return;
136
+ }
161
137
 
162
138
  // Clean up previous textures if they exist
163
- if (this._radianceTexture) {
164
- if (debug) console.log("[MaterialX] Disposing previous radiance texture");
165
- this._radianceTexture.dispose();
166
- this._radianceTexture = null;
139
+ if (this.radianceTexture) {
140
+ if (debug) console.log("Disposing previous radiance texture");
141
+ this.radianceTexture.dispose();
142
+ this.radianceTexture = null;
167
143
  }
168
- if (this._irradianceTexture) {
169
- if (debug) console.log("[MaterialX] Disposing previous irradiance texture");
170
- this._irradianceTexture.dispose();
171
- this._irradianceTexture = null;
144
+ if (this.irradianceTexture) {
145
+ if (debug) console.log("Disposing previous irradiance texture");
146
+ this.irradianceTexture.dispose();
147
+ this.irradianceTexture = null;
172
148
  }
173
149
 
174
150
  // Get renderer from context
175
- const renderer = this._context.renderer;
151
+ const renderer = this.context.renderer;
176
152
 
177
153
  // TODO remove this delay; we should wait for the scene lighting to be ready
178
154
  // and then update the uniforms
179
- let envMap = this._context.scene.environment;
155
+ let envMap = this.context.scene.environment;
180
156
  while (!envMap) {
181
157
  await delay(200);
182
- envMap = this._context.scene.environment;
158
+ envMap = this.context.scene.environment;
183
159
  }
184
160
  var pmrem = new PMREMGenerator(renderer);
185
161
  const target = pmrem.fromEquirectangular(envMap);
@@ -187,42 +163,66 @@ export class MaterialXEnvironment {
187
163
  const radianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 0.0, 1024, 512, target.height);
188
164
  const irradianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 1.0, 32, 16, target.height);
189
165
 
190
- this._radianceTexture = radianceRenderTarget.texture;
191
- this._irradianceTexture = irradianceRenderTarget.texture;
166
+ this.radianceTexture = radianceRenderTarget.texture;
167
+ this.irradianceTexture = irradianceRenderTarget.texture;
192
168
 
193
169
  // Clean up PMREM generator and its render target
194
170
  target.dispose();
195
171
  pmrem.dispose();
196
172
 
197
173
  if (debug) {
198
- console.log({ radiance: this._radianceTexture, irradiance: this._irradianceTexture });
174
+ console.log({ radiance: this.radianceTexture, irradiance: this.irradianceTexture });
199
175
  // Show both of them on cubes in the scene
200
176
  const unlitMat = new MeshBasicMaterial();
201
- unlitMat.side = 2;
202
177
  const radianceMat = unlitMat.clone();
203
- radianceMat.map = this._radianceTexture;
204
- const radianceCube = ObjectUtils.createPrimitive("Quad", { material: radianceMat });
178
+ radianceMat.map = this.radianceTexture;
179
+ const radianceCube = ObjectUtils.createPrimitive("Cube", { material: radianceMat });
205
180
  const irradianceMat = unlitMat.clone();
206
- irradianceMat.map = this._irradianceTexture;
207
- const irradianceCube = ObjectUtils.createPrimitive("Quad", { material: irradianceMat });
208
- this._context.scene.add(radianceCube);
209
- this._context.scene.add(irradianceCube);
181
+ irradianceMat.map = this.irradianceTexture;
182
+ const irradianceCube = ObjectUtils.createPrimitive("Cube", { material: irradianceMat });
183
+ this.context.scene.add(radianceCube);
184
+ this.context.scene.add(irradianceCube);
210
185
  radianceCube.position.set(2, 0, 0);
186
+ radianceCube.scale.y = 0.00001;
211
187
  irradianceCube.position.set(-2, 0, 0);
188
+ irradianceCube.scale.y = 0.00001;
212
189
  // await this.initializeLighting(defaultLightRigXml, renderer);
213
- console.log("[MaterialX] environment initialized from Needle context", this, this._context.scene);
190
+ console.log("MaterialX environment initialized from Needle context", this, this.context.scene);
214
191
  }
215
192
 
216
193
  // Find lights in scene
217
194
  let lights = new Array<Light>();
218
- this._context.scene.traverse((object: Object3D) => {
219
- if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
220
- lights.push(object as Light);
195
+ this.context.scene.traverse((object: Object3D) => {
196
+ if ((object as Light).isLight) lights.push(object as Light);
221
197
  });
222
198
 
223
- const { lightData, lightCount } = await registerLights(state.materialXModule, lights, state.materialXGenContext);
224
- this._lightData = lightData;
225
- this._lightCount = lightCount;
226
- return true;
199
+ this.lightData = await registerLights(state.materialXModule, lights, state.materialXGenContext);
200
+
201
+ // Mark as initialized
202
+ this.initialized = true;
203
+ }
204
+
205
+ // getLights() { return this.lights; }
206
+ getLightData() { return this.lightData; }
207
+ getRadianceTexture() { return this.radianceTexture; }
208
+ getIrradianceTexture() { return this.irradianceTexture; }
209
+
210
+ setRadianceTexture(texture: any) { this.radianceTexture = texture; }
211
+ setIrradianceTexture(texture: any) { this.irradianceTexture = texture; }
212
+
213
+ // Reset the environment to allow re-initialization
214
+ reset() {
215
+ if (debug) console.log("Resetting MaterialX environment");
216
+ if (this.radianceTexture) {
217
+ this.radianceTexture.dispose();
218
+ this.radianceTexture = null;
219
+ }
220
+ if (this.irradianceTexture) {
221
+ this.irradianceTexture.dispose();
222
+ this.irradianceTexture = null;
223
+ }
224
+ this.initialized = false;
225
+ // this.lights = [];
226
+ this.lightData = null;
227
227
  }
228
228
  }