@needle-tools/materialx 1.0.1-next.7bd39cb → 1.0.1-next.b9467c8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/materialx",
3
- "version": "1.0.1-next.7bd39cb",
3
+ "version": "1.0.1-next.b9467c8",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "exports": {
@@ -53,7 +53,7 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
53
53
  // Register the MaterialX loader extension
54
54
  // Environment initialization is now handled in the MaterialXLoader constructor
55
55
  loader.register(p => {
56
- this.loader = new MaterialXLoader(p, url, context);
56
+ this.loader = new MaterialXLoader(p, context);
57
57
  return this.loader;
58
58
  });
59
59
  };
@@ -1,5 +1,5 @@
1
1
  import { Context } from "@needle-tools/engine";
2
- import { Material, MeshStandardMaterial, Texture, NearestFilter, CompressedTexture, DoubleSide, FrontSide } from "three";
2
+ import { Material, MeshStandardMaterial, Texture, NearestFilter, CompressedTexture } from "three";
3
3
  import { GLTF, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
4
4
  import { ready, MaterialXEnvironment, state } from "../materialx.js";
5
5
  import { debug } from "../utils.js";
@@ -21,14 +21,6 @@ export type MaterialX_material_extension = {
21
21
  name: string; // Material name reference
22
22
  }
23
23
 
24
- type MaterialDefinition = {
25
- name?: string; // Optional name for the material
26
- doubleSided?: boolean; // Whether the material is double-sided
27
- extensions?: {
28
- [key: string]: any; // Extensions for the material, including MaterialX
29
- },
30
- }
31
-
32
24
  // MaterialX loader extension for js GLTFLoader
33
25
  export class MaterialXLoader implements GLTFLoaderPlugin {
34
26
  readonly name = "NEEDLE_materials_mtlx";
@@ -46,13 +38,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
46
38
  return this._generatedMaterials;
47
39
  }
48
40
 
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) {
41
+ constructor(private parser: GLTFParser, private context: Context) {
56
42
  if (debug) console.log("MaterialXLoader created for parser");
57
43
  // Start loading of MaterialX environment if the root extension exists
58
44
  if (this.materialX_root_data) {
@@ -108,14 +94,15 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
108
94
 
109
95
  private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
110
96
 
111
- const materialDef = this.parser.json.materials?.[materialIndex] as MaterialDefinition;
112
- if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions?.[this.name]);
97
+ const materialDef = this.parser.json.materials?.[materialIndex];
98
+ if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions[this.name]);
113
99
 
114
100
  // Handle different types of MaterialX data
115
- const ext = materialDef.extensions?.[this.name] as MaterialX_material_extension;
101
+ const dataIndex = materialDef.extensions[this.name] as MaterialX_material_extension;
116
102
 
117
- if (ext) {
118
- return this._createMaterialXMaterial(materialDef, ext);
103
+ if (dataIndex) {
104
+ // Create a new material and process MaterialX - AWAIT THIS!
105
+ return await this._createMaterialXMaterial(dataIndex);
119
106
  }
120
107
 
121
108
  // Return fallback material instead of null
@@ -124,7 +111,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
124
111
  return fallbackMaterial;
125
112
  }
126
113
 
127
- private async _createMaterialXMaterial(material_def: MaterialDefinition, material_extension: MaterialX_material_extension): Promise<Material> {
114
+ private async _createMaterialXMaterial(material_extension: MaterialX_material_extension): Promise<Material> {
128
115
  try {
129
116
  if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
130
117
 
@@ -236,45 +223,72 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
236
223
  name: material_extension.name,
237
224
  shader,
238
225
  transparent: isTransparent,
239
- side: material_def.doubleSided ? DoubleSide : FrontSide,
240
226
  loaders: {
241
- cacheKey: this.url,
242
- getTexture: async url => {
243
- // Find the index of the texture in the parser
244
- const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
245
-
246
- // Resolve the texture from the MaterialX root extension
247
- const ext = this.materialX_root_data;
248
- if (ext) {
249
- const textures = ext.textures || [];
250
- let index = -1;
251
- for (const texture of textures) {
252
- // Find the texture by name and use the pointer string to get the index
253
- if (texture.name === filenameWithoutExt) {
254
- const ptr = texture.pointer;
255
- const indexStr = ptr.substring("/textures/".length);
256
- index = parseInt(indexStr);
257
-
258
- if (isNaN(index) || index < 0) {
259
- console.error("[MaterialX] Invalid texture index in pointer:", ptr);
260
- return;
261
- }
262
- else {
263
- if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
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
+ }
264
259
  }
265
260
  }
266
- }
267
261
 
268
- if (index < 0) {
269
- console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
270
- return;
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
+ });
271
289
  }
272
- return this.parser.getDependency("texture", index).then(tex => {
273
- if (debug) console.log("[MaterialX] Texture loaded:" + tex.name, tex);
274
- return tex;
275
- });
276
- }
277
- return null;
290
+ });
291
+ return defaultTexture;
278
292
  }
279
293
  }
280
294
  });
@@ -73,162 +73,95 @@ function fromMatrix(matrix: MaterialX.Matrix, dimension: MaterialX.Matrix["size"
73
73
 
74
74
 
75
75
  export type Loaders = {
76
- /**
77
- * Cache key for the loaders, used to identify and reuse textures
78
- */
79
- readonly cacheKey: string;
80
- /**
81
- * Get a texture by path
82
- * @param {string} path - The path to the texture
83
- * @return {Promise<THREE.Texture>} - A promise that resolves to the texture
84
- */
85
- readonly getTexture: (path: string) => Promise<THREE.Texture>;
86
- }
87
-
88
- const defaultTexture = new THREE.Texture();
89
- defaultTexture.needsUpdate = true;
90
- defaultTexture.image = new Image();
91
- defaultTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRFr6+vGqg52AAAAAxJREFUeJxjZGBEgQAAWAAJLpjsTQAAAABJRU5ErkJggg=="
92
- // defaultTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAB5QTFRFAAAABAQEw8PD////v7+/vb29Xl5eQEBA+/v7PDw8GPBYkgAAAB1JREFUeJxjZGBgYFQSABIUMlxgDGMGBtaIAnIZAKwQCSDYUEZEAAAAAElFTkSuQmCC";
93
- // defaultTexture.wrapS = THREE.RepeatWrapping;
94
- // defaultTexture.wrapT = THREE.RepeatWrapping;
95
- // defaultTexture.minFilter = THREE.NearestFilter;
96
- // defaultTexture.magFilter = THREE.NearestFilter;
97
- // defaultTexture.repeat = new THREE.Vector2(100, 100);
98
-
99
-
100
- const defaultNormalTexture = new THREE.Texture();
101
- defaultNormalTexture.needsUpdate = true;
102
- defaultNormalTexture.image = new Image();
103
- defaultNormalTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAABJQTFRFgYH4gIH4gYH3gIH3gIH5gID4m94ORAAAADFJREFUeJxjZBBkfMdo9P/BB0aBj/8FGB0ufghgFGT4r8wo+P8rD2Pgo3sMjIz8jAwAMLoN0ZjS5hgAAAAASUVORK5CYII=";
104
-
105
-
106
- function tryGetFromCache(key: string): any {
107
- const wasEnabled = THREE.Cache.enabled;
108
- THREE.Cache.enabled = true;
109
- const value = THREE.Cache.get(key);
110
- THREE.Cache.enabled = wasEnabled;
111
- return value;
112
- }
113
- function addToCache(key: string, value: any): void {
114
- const wasEnabled = THREE.Cache.enabled;
115
- THREE.Cache.enabled = true;
116
- THREE.Cache.add(key, value);
117
- THREE.Cache.enabled = wasEnabled;
118
- if (debug) console.log('[MaterialX] Added to cache:', key, value);
76
+ getTexture: (path: string) => THREE.Texture;
119
77
  }
120
78
 
121
79
  /**
122
80
  * Get Three uniform from MaterialX value
81
+ * @param {mx.Uniform.type} type
82
+ * @param {mx.Uniform.value} value
83
+ * @param {mx.Uniform.name} name
84
+ * @param {mx.Uniforms} uniforms
85
+ * @param {Loaders} loaders
86
+ * @param {string} searchPath
87
+ * @param {boolean} flipY
123
88
  */
124
- function toThreeUniform(uniforms: any, type: string, value: any, name: string, loaders: Loaders, searchPath: string): THREE.Uniform {
125
-
126
- const uniform = new THREE.Uniform<any>(null);
89
+ function toThreeUniform(type: string, value: any, name: string, uniforms: any, loaders: Loaders, searchPath, flipY: boolean) {
127
90
 
128
91
  switch (type) {
129
92
  case 'float':
130
93
  case 'integer':
131
94
  case 'boolean':
132
- uniform.value = value;
95
+ return value;
133
96
  break;
134
97
  case 'vector2':
135
- uniform.value = fromVector(value, 2);
98
+ return fromVector(value, 2);
136
99
  break;
137
100
  case 'vector3':
138
101
  case 'color3':
139
- uniform.value = fromVector(value, 3);
140
- break;
102
+ return fromVector(value, 3);
141
103
  case 'vector4':
142
104
  case 'color4':
143
- uniform.value = fromVector(value, 4);
144
- break;
105
+ return fromVector(value, 4);
145
106
  case 'matrix33':
146
- uniform.value = fromMatrix(value, 9);
147
- break;
107
+ return fromMatrix(value, 9);
148
108
  case 'matrix44':
149
- uniform.value = fromMatrix(value, 16);
150
- break;
109
+ return fromMatrix(value, 16);
151
110
  case 'filename':
152
111
  if (value) {
153
112
  // Cache / reuse texture to avoid reload overhead.
154
113
  // Note: that data blobs and embedded data textures are not cached as they are transient data.
155
- let checkCache = true;
114
+ let checkCache = false;
156
115
  let texturePath = searchPath + IMAGE_PATH_SEPARATOR + value;
157
116
  if (value.startsWith('blob:')) {
158
117
  texturePath = value;
118
+ if (debug) console.log('Load blob URL:', texturePath);
159
119
  checkCache = false;
160
120
  }
161
- else if (value.startsWith('data:')) {
121
+ else if (value.startsWith('http')) {
162
122
  texturePath = value;
163
- checkCache = false;
123
+ if (debug) console.log('Load HTTP URL:', texturePath);
164
124
  }
165
- else if (value.startsWith('http')) {
125
+ else if (value.startsWith('data:')) {
166
126
  texturePath = value;
167
- checkCache = true;
127
+ checkCache = false;
128
+ if (debug) console.log('Load data URL:', texturePath);
168
129
  }
169
-
170
- const cacheKey = `${loaders.cacheKey}-${texturePath}`;
171
- const cacheValue = checkCache ? tryGetFromCache(cacheKey) : null;
172
- if (cacheValue) {
173
- if (debug) console.log('[MaterialX] Use cached texture: ', cacheKey, cacheValue);
174
- if (cacheValue instanceof Promise) {
175
- cacheValue.then(res => {
176
- if (res) uniform.value = res;
177
- else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
178
- });
179
- }
180
- else {
181
- uniform.value = cacheValue;
182
- }
130
+ const cachedTexture = checkCache && THREE.Cache.get(texturePath);
131
+ if (cachedTexture) {
132
+ if (debug) console.log('Use cached texture: ', texturePath, cachedTexture);
133
+ return cachedTexture;
183
134
  }
184
135
  else {
185
- if (name.toLowerCase().includes("normal")) {
186
- uniform.value = defaultNormalTexture;
187
- }
188
- else {
189
- uniform.value = defaultTexture;
190
- }
191
-
192
- if (debug) console.log('[MaterialX] Load texture:', texturePath);
193
- // Save the loading promise in the cache
194
- const promise = loaders.getTexture(texturePath).then(res => {
195
- if (res) {
196
- res.colorSpace = THREE.LinearSRGBColorSpace;
197
- setTextureParameters(res, name, uniforms);
198
- }
199
- return res;
200
- });
201
- if (checkCache) {
202
- addToCache(cacheKey, promise);
203
- }
204
- promise.then(res => {
205
- if (res) uniform.value = res;
206
- else console.warn(`[MaterialX] Failed to load texture ${name} '${texturePath}'`);
207
- });
136
+ const texture = loaders.getTexture(texturePath);
137
+ if (checkCache) THREE.Cache.add(texturePath, texture);
138
+ // Set address & filtering mode
139
+ if (texture) setTextureParameters(texture, name, uniforms, flipY);
140
+ return texture;
208
141
  }
209
142
  }
210
143
  break;
211
144
  case 'samplerCube':
212
145
  case 'string':
213
- break;
146
+ return null;
214
147
  default:
215
148
  const key = type + ':' + name;
216
149
  if (!valueTypeWarningMap.has(key)) {
217
150
  valueTypeWarningMap.set(key, true);
218
151
  console.warn('MaterialX: Unsupported uniform type: ' + type + ' for uniform: ' + name, value);
219
152
  }
220
- break;
153
+ return null;
221
154
  }
222
-
223
- return uniform;
224
155
  }
225
156
 
226
157
  const valueTypeWarningMap = new Map<string, boolean>();
227
158
 
228
159
  /**
229
160
  * Get Three wrapping mode
161
+ * @param {mx.TextureFilter.wrap} mode
162
+ * @returns {THREE.Wrapping}
230
163
  */
231
- function getWrapping(mode: number): THREE.Wrapping {
164
+ function getWrapping(mode) {
232
165
  let wrap;
233
166
  switch (mode) {
234
167
  case 1:
@@ -247,14 +180,36 @@ function getWrapping(mode: number): THREE.Wrapping {
247
180
  return wrap;
248
181
  }
249
182
 
183
+ /**
184
+ * Get Three minification filter
185
+ * @param {mx.TextureFilter.minFilter} type
186
+ * @param {mx.TextureFilter.generateMipmaps} generateMipmaps
187
+ */
188
+ function getMinFilter(type, generateMipmaps) {
189
+ let filterType: THREE.TextureFilter = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
190
+ if (type === 0) {
191
+ filterType = generateMipmaps ? THREE.NearestMipMapNearestFilter : THREE.NearestFilter;
192
+ }
193
+ return filterType;
194
+ }
250
195
 
251
196
  /**
252
197
  * Set Three texture parameters
198
+ * @param {THREE.Texture} texture
199
+ * @param {mx.Uniform.name} name
200
+ * @param {mx.Uniforms} uniforms
201
+ * @param {mx.TextureFilter.generateMipmaps} generateMipmaps
253
202
  */
254
- function setTextureParameters(texture: THREE.Texture, name: string, uniforms: any, generateMipmaps = true) {
203
+ function setTextureParameters(texture, name, uniforms, flipY = true, generateMipmaps = true) {
255
204
  const idx = name.lastIndexOf(IMAGE_PROPERTY_SEPARATOR);
256
205
  const base = name.substring(0, idx) || name;
257
206
 
207
+ // texture.generateMipmaps = generateMipmaps;
208
+ // texture.wrapS = THREE.RepeatWrapping;
209
+ // texture.wrapT = THREE.RepeatWrapping;
210
+ // texture.magFilter = THREE.LinearFilter;
211
+ // texture.flipY = flipY;
212
+
258
213
  if (uniforms.find(base + UADDRESS_MODE_SUFFIX)) {
259
214
  const uaddressmode = uniforms.find(base + UADDRESS_MODE_SUFFIX).getValue().getData();
260
215
  texture.wrapS = getWrapping(uaddressmode);
@@ -265,12 +220,8 @@ function setTextureParameters(texture: THREE.Texture, name: string, uniforms: an
265
220
  texture.wrapT = getWrapping(vaddressmode);
266
221
  }
267
222
 
268
- const mxFilterType = uniforms.find(base + FILTER_TYPE_SUFFIX) ? uniforms.get(base + FILTER_TYPE_SUFFIX).value : -1;
269
- let minFilter: THREE.TextureFilter = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
270
- if (mxFilterType === 0) {
271
- minFilter = generateMipmaps ? THREE.NearestMipMapNearestFilter : THREE.NearestFilter;
272
- }
273
- texture.minFilter = minFilter;
223
+ const filterType = uniforms.find(base + FILTER_TYPE_SUFFIX) ? uniforms.get(base + FILTER_TYPE_SUFFIX).value : -1;
224
+ texture.minFilter = getMinFilter(filterType, generateMipmaps);
274
225
  }
275
226
 
276
227
  /**
@@ -454,7 +405,7 @@ export function getLightData(lights: any, genContext: any): { lightData: LightDa
454
405
  /**
455
406
  * Get uniform values for a shader
456
407
  */
457
- export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Loaders, searchPath: string) {
408
+ export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Loaders, searchPath: string, flipY: boolean) {
458
409
  const threeUniforms = {};
459
410
 
460
411
  const uniformBlocks = shaderStage.getUniformBlocks()
@@ -467,8 +418,9 @@ export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Lo
467
418
  const variable = uniforms.get(i);
468
419
  const value = variable.getValue()?.getData();
469
420
  const name = variable.getVariable();
470
- if (debug) console.log("Adding uniform", { path: variable.getPath(), name: name, value: value, type: variable.getType().getName() });
471
- threeUniforms[name] = toThreeUniform(uniforms, variable.getType().getName(), value, name, loaders, searchPath);;
421
+ if (debug) console.log("Adding uniform", { path: variable.getPath(), name, value, type: variable.getType().getName() });
422
+ threeUniforms[name] = new THREE.Uniform(toThreeUniform(variable.getType().getName(), value, name, uniforms,
423
+ loaders, searchPath, flipY));
472
424
  }
473
425
  }
474
426
  }
@@ -1,6 +1,6 @@
1
- import { Camera, DoubleSide, FrontSide, GLSL3, MaterialParameters, Matrix3, Matrix4, Object3D, ShaderMaterial, Texture, Vector3 } from "three";
1
+ import { Camera, DoubleSide, FrontSide, GLSL3, Matrix3, Matrix4, Mesh, Object3D, ShaderMaterial, Texture, Vector3 } from "three";
2
2
  import { debug } from "./utils.js";
3
- import { MaterialXEnvironment } from "./materialx.js";
3
+ import { MaterialXEnvironment, state } from "./materialx.js";
4
4
  import { getUniformValues, Loaders } from "./materialx.helper.js";
5
5
  import { Context } from "@needle-tools/engine";
6
6
 
@@ -16,7 +16,6 @@ declare type MaterialXMaterialInitParameters = {
16
16
  shader: any,
17
17
  loaders: Loaders,
18
18
  transparent?: boolean,
19
- side?: MaterialParameters['side'],
20
19
  }
21
20
 
22
21
  export class MaterialXMaterial extends ShaderMaterial {
@@ -98,22 +97,26 @@ export class MaterialXMaterial extends ShaderMaterial {
98
97
  #include <colorspace_fragment>`);
99
98
 
100
99
  const searchPath = ""; // Could be derived from the asset path if needed
100
+ const flipV = false; // Set based on your geometry requirements
101
101
  const isTransparent = init.transparent ?? false;
102
102
  super({
103
103
  name: init.name,
104
- uniforms: {},
104
+ uniforms: {
105
+ ...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath, flipV),
106
+ ...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath, flipV),
107
+ },
105
108
  vertexShader: vertexShader,
106
109
  fragmentShader: fragmentShader,
107
110
  glslVersion: GLSL3,
108
111
  transparent: isTransparent,
109
- side: init.side ? init.side : FrontSide,
112
+ side: FrontSide,
110
113
  depthTest: true,
111
114
  depthWrite: !isTransparent,
112
115
  });
113
116
 
117
+
118
+
114
119
  Object.assign(this.uniforms, {
115
- ...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath),
116
- ...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath),
117
120
  u_envMatrix: { value: new Matrix4() },
118
121
  u_envRadiance: { value: null, type: 't' },
119
122
  u_envRadianceMips: { value: 8, type: 'i' },
@@ -142,6 +145,8 @@ export class MaterialXMaterial extends ShaderMaterial {
142
145
 
143
146
  const uniforms = this.uniforms;
144
147
 
148
+ if (!uniforms) return;
149
+
145
150
  // TODO remove. Not sure why this is needed, but without it
146
151
  // we currently get some "swimming" where matrices are not up to date.
147
152
  camera.updateMatrixWorld(true);
@@ -188,15 +193,20 @@ export class MaterialXMaterial extends ShaderMaterial {
188
193
  const lightCount = environment.lightCount || 0;
189
194
  const textures = environment.getTextures(this) || null;
190
195
 
196
+ // Update each generated material's lighting uniforms
197
+ if (!this.uniforms) return;
198
+
191
199
  // Update light count
192
200
  if (this.uniforms.u_numActiveLightSources && lightCount >= 0) {
193
201
  this.uniforms.u_numActiveLightSources.value = lightCount;
194
202
  }
195
203
 
196
- // Update light data
204
+ // Update light data if we have lights
197
205
  if (lightData) {
198
206
  this.uniforms.u_lightData.value = lightData;
207
+ if (debug) console.log("[MaterialX] Updated light data for material", this.name, lightData, this.uniforms,);
199
208
  }
209
+ else if (debug) console.warn("[MaterialX] No light data available to update uniforms for material", this.name);
200
210
 
201
211
  // Update environment uniforms
202
212
  if (this.uniforms.u_envMatrix) {