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

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.64f3b67",
3
+ "version": "1.0.1-next.7bd39cb",
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, context);
56
+ this.loader = new MaterialXLoader(p, url, 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 } from "three";
2
+ import { Material, MeshStandardMaterial, Texture, NearestFilter, CompressedTexture, DoubleSide, FrontSide } 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";
@@ -13,12 +13,22 @@ export type MaterialX_root_extension = {
13
13
  name: string;
14
14
  /** MaterialX xml content */
15
15
  mtlx: string;
16
+ /** MaterialX texture pointers */
17
+ textures: Array<{ name: string, pointer: string }>;
16
18
  }
17
19
 
18
20
  export type MaterialX_material_extension = {
19
21
  name: string; // Material name reference
20
22
  }
21
23
 
24
+ type MaterialDefinition = {
25
+ name?: string; // Optional name for the material
26
+ doubleSided?: boolean; // Whether the material is double-sided
27
+ extensions?: {
28
+ [key: string]: any; // Extensions for the material, including MaterialX
29
+ },
30
+ }
31
+
22
32
  // MaterialX loader extension for js GLTFLoader
23
33
  export class MaterialXLoader implements GLTFLoaderPlugin {
24
34
  readonly name = "NEEDLE_materials_mtlx";
@@ -36,7 +46,13 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
36
46
  return this._generatedMaterials;
37
47
  }
38
48
 
39
- constructor(private parser: GLTFParser, private context: Context) {
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) {
40
56
  if (debug) console.log("MaterialXLoader created for parser");
41
57
  // Start loading of MaterialX environment if the root extension exists
42
58
  if (this.materialX_root_data) {
@@ -92,15 +108,14 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
92
108
 
93
109
  private async _loadMaterialAsync(materialIndex: number): Promise<Material> {
94
110
 
95
- const materialDef = this.parser.json.materials?.[materialIndex];
96
- if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions[this.name]);
111
+ const materialDef = this.parser.json.materials?.[materialIndex] as MaterialDefinition;
112
+ if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions?.[this.name]);
97
113
 
98
114
  // Handle different types of MaterialX data
99
- const dataIndex = materialDef.extensions[this.name] as MaterialX_material_extension;
115
+ const ext = materialDef.extensions?.[this.name] as MaterialX_material_extension;
100
116
 
101
- if (dataIndex) {
102
- // Create a new material and process MaterialX - AWAIT THIS!
103
- return await this._createMaterialXMaterial(dataIndex);
117
+ if (ext) {
118
+ return this._createMaterialXMaterial(materialDef, ext);
104
119
  }
105
120
 
106
121
  // Return fallback material instead of null
@@ -109,7 +124,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
109
124
  return fallbackMaterial;
110
125
  }
111
126
 
112
- private async _createMaterialXMaterial(material_extension: MaterialX_material_extension): Promise<Material> {
127
+ private async _createMaterialXMaterial(material_def: MaterialDefinition, material_extension: MaterialX_material_extension): Promise<Material> {
113
128
  try {
114
129
  if (debug) console.log(`Creating MaterialX material: ${material_extension.name}`);
115
130
 
@@ -221,60 +236,45 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
221
236
  name: material_extension.name,
222
237
  shader,
223
238
  transparent: isTransparent,
239
+ side: material_def.doubleSided ? DoubleSide : FrontSide,
224
240
  loaders: {
225
- getTexture: url => {
226
- // Return a checkerboard texture for now
227
- const defaultTexture = new Texture();
228
- defaultTexture.image = new Image();
229
- defaultTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAG0lEQVR4nGJqWH9q9e8XjA/VrL8UfQIEAAD//zn2CCX5UcsdAAAAAElFTkSuQmCC";
230
- defaultTexture.needsUpdate = true;
231
- // Pixelated filtering
232
- defaultTexture.magFilter = NearestFilter;
233
- defaultTexture.minFilter = NearestFilter;
234
- new Promise(() => {
235
- // Find the index of the texture in the parser
236
- const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
237
-
238
- // Find in the root extension; there's the textures dictionary:
239
- // a mapping from the texture name used in MaterialX to the glTF texture index
240
- const ext = this.parser.json.extensions?.["NEEDLE_materials_mtlx"];
241
- const textures = ext?.textures || [];
242
-
243
- const index = textures.findIndex(tex => {
244
- if (debug) console.log("[MaterialX] Checking texture:", tex.name, "against URL:", filenameWithoutExt);
245
- return tex.name === filenameWithoutExt;
246
- });
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);
264
+ }
265
+ }
266
+ }
247
267
 
248
268
  if (index < 0) {
249
269
  console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
250
270
  return;
251
271
  }
252
- this.parser.getDependency("texture", index).then(tex => {
253
- if (debug) console.log("[MaterialX] Texture loaded:", tex);
254
- console.log(tex, " -> ", defaultTexture)
255
-
256
- // update the checkerboard texture with the loaded texture
257
- defaultTexture.image = tex.image;
258
- defaultTexture.needsUpdate = true;
259
- defaultTexture.userData = tex.userData || {}; // needed for LODs
260
- defaultTexture.format = tex.format;
261
- defaultTexture.source = tex.source || defaultTexture.source;
262
- defaultTexture.type = tex.type;
263
- defaultTexture.format = tex.format;
264
- defaultTexture.magFilter = tex.magFilter;
265
- defaultTexture.minFilter = tex.minFilter;
266
- defaultTexture.wrapS = tex.wrapS;
267
- defaultTexture.wrapT = tex.wrapT;
268
- defaultTexture.generateMipmaps = tex.generateMipmaps;
269
- defaultTexture.mipmaps = tex.mipmaps || defaultTexture.mipmaps;
270
- defaultTexture.unpackAlignment = tex.unpackAlignment;
271
-
272
- if (tex instanceof CompressedTexture) {
273
- defaultTexture.isCompressedTexture = true;
274
- }
272
+ return this.parser.getDependency("texture", index).then(tex => {
273
+ if (debug) console.log("[MaterialX] Texture loaded:" + tex.name, tex);
274
+ return tex;
275
275
  });
276
- });
277
- return defaultTexture;
276
+ }
277
+ return null;
278
278
  }
279
279
  }
280
280
  });
@@ -73,95 +73,162 @@ function fromMatrix(matrix: MaterialX.Matrix, dimension: MaterialX.Matrix["size"
73
73
 
74
74
 
75
75
  export type Loaders = {
76
- getTexture: (path: string) => THREE.Texture;
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);
77
119
  }
78
120
 
79
121
  /**
80
122
  * 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
88
123
  */
89
- function toThreeUniform(type: string, value: any, name: string, uniforms: any, loaders: Loaders, searchPath, flipY: boolean) {
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);
90
127
 
91
128
  switch (type) {
92
129
  case 'float':
93
130
  case 'integer':
94
131
  case 'boolean':
95
- return value;
132
+ uniform.value = value;
96
133
  break;
97
134
  case 'vector2':
98
- return fromVector(value, 2);
135
+ uniform.value = fromVector(value, 2);
99
136
  break;
100
137
  case 'vector3':
101
138
  case 'color3':
102
- return fromVector(value, 3);
139
+ uniform.value = fromVector(value, 3);
140
+ break;
103
141
  case 'vector4':
104
142
  case 'color4':
105
- return fromVector(value, 4);
143
+ uniform.value = fromVector(value, 4);
144
+ break;
106
145
  case 'matrix33':
107
- return fromMatrix(value, 9);
146
+ uniform.value = fromMatrix(value, 9);
147
+ break;
108
148
  case 'matrix44':
109
- return fromMatrix(value, 16);
149
+ uniform.value = fromMatrix(value, 16);
150
+ break;
110
151
  case 'filename':
111
152
  if (value) {
112
153
  // Cache / reuse texture to avoid reload overhead.
113
154
  // Note: that data blobs and embedded data textures are not cached as they are transient data.
114
- let checkCache = false;
155
+ let checkCache = true;
115
156
  let texturePath = searchPath + IMAGE_PATH_SEPARATOR + value;
116
157
  if (value.startsWith('blob:')) {
117
158
  texturePath = value;
118
- if (debug) console.log('Load blob URL:', texturePath);
119
159
  checkCache = false;
120
160
  }
121
- else if (value.startsWith('http')) {
122
- texturePath = value;
123
- if (debug) console.log('Load HTTP URL:', texturePath);
124
- }
125
161
  else if (value.startsWith('data:')) {
126
162
  texturePath = value;
127
163
  checkCache = false;
128
- if (debug) console.log('Load data URL:', texturePath);
129
164
  }
130
- const cachedTexture = checkCache && THREE.Cache.get(texturePath);
131
- if (cachedTexture) {
132
- if (debug) console.log('Use cached texture: ', texturePath, cachedTexture);
133
- return cachedTexture;
165
+ else if (value.startsWith('http')) {
166
+ texturePath = value;
167
+ checkCache = true;
168
+ }
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
+ }
134
183
  }
135
184
  else {
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;
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
+ });
141
208
  }
142
209
  }
143
210
  break;
144
211
  case 'samplerCube':
145
212
  case 'string':
146
- return null;
213
+ break;
147
214
  default:
148
215
  const key = type + ':' + name;
149
216
  if (!valueTypeWarningMap.has(key)) {
150
217
  valueTypeWarningMap.set(key, true);
151
218
  console.warn('MaterialX: Unsupported uniform type: ' + type + ' for uniform: ' + name, value);
152
219
  }
153
- return null;
220
+ break;
154
221
  }
222
+
223
+ return uniform;
155
224
  }
156
225
 
157
226
  const valueTypeWarningMap = new Map<string, boolean>();
158
227
 
159
228
  /**
160
229
  * Get Three wrapping mode
161
- * @param {mx.TextureFilter.wrap} mode
162
- * @returns {THREE.Wrapping}
163
230
  */
164
- function getWrapping(mode) {
231
+ function getWrapping(mode: number): THREE.Wrapping {
165
232
  let wrap;
166
233
  switch (mode) {
167
234
  case 1:
@@ -180,36 +247,14 @@ function getWrapping(mode) {
180
247
  return wrap;
181
248
  }
182
249
 
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
- }
195
250
 
196
251
  /**
197
252
  * 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
202
253
  */
203
- function setTextureParameters(texture, name, uniforms, flipY = true, generateMipmaps = true) {
254
+ function setTextureParameters(texture: THREE.Texture, name: string, uniforms: any, generateMipmaps = true) {
204
255
  const idx = name.lastIndexOf(IMAGE_PROPERTY_SEPARATOR);
205
256
  const base = name.substring(0, idx) || name;
206
257
 
207
- // texture.generateMipmaps = generateMipmaps;
208
- // texture.wrapS = THREE.RepeatWrapping;
209
- // texture.wrapT = THREE.RepeatWrapping;
210
- // texture.magFilter = THREE.LinearFilter;
211
- // texture.flipY = flipY;
212
-
213
258
  if (uniforms.find(base + UADDRESS_MODE_SUFFIX)) {
214
259
  const uaddressmode = uniforms.find(base + UADDRESS_MODE_SUFFIX).getValue().getData();
215
260
  texture.wrapS = getWrapping(uaddressmode);
@@ -220,8 +265,12 @@ function setTextureParameters(texture, name, uniforms, flipY = true, generateMip
220
265
  texture.wrapT = getWrapping(vaddressmode);
221
266
  }
222
267
 
223
- const filterType = uniforms.find(base + FILTER_TYPE_SUFFIX) ? uniforms.get(base + FILTER_TYPE_SUFFIX).value : -1;
224
- texture.minFilter = getMinFilter(filterType, generateMipmaps);
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;
225
274
  }
226
275
 
227
276
  /**
@@ -405,7 +454,7 @@ export function getLightData(lights: any, genContext: any): { lightData: LightDa
405
454
  /**
406
455
  * Get uniform values for a shader
407
456
  */
408
- export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Loaders, searchPath: string, flipY: boolean) {
457
+ export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Loaders, searchPath: string) {
409
458
  const threeUniforms = {};
410
459
 
411
460
  const uniformBlocks = shaderStage.getUniformBlocks()
@@ -418,9 +467,8 @@ export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Lo
418
467
  const variable = uniforms.get(i);
419
468
  const value = variable.getValue()?.getData();
420
469
  const name = variable.getVariable();
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));
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);;
424
472
  }
425
473
  }
426
474
  }
@@ -1,6 +1,6 @@
1
- import { Camera, DoubleSide, FrontSide, GLSL3, Matrix3, Matrix4, Mesh, Object3D, ShaderMaterial, Texture, Vector3 } from "three";
1
+ import { Camera, DoubleSide, FrontSide, GLSL3, MaterialParameters, Matrix3, Matrix4, Object3D, ShaderMaterial, Texture, Vector3 } from "three";
2
2
  import { debug } from "./utils.js";
3
- import { MaterialXEnvironment, state } from "./materialx.js";
3
+ import { MaterialXEnvironment } from "./materialx.js";
4
4
  import { getUniformValues, Loaders } from "./materialx.helper.js";
5
5
  import { Context } from "@needle-tools/engine";
6
6
 
@@ -16,6 +16,7 @@ declare type MaterialXMaterialInitParameters = {
16
16
  shader: any,
17
17
  loaders: Loaders,
18
18
  transparent?: boolean,
19
+ side?: MaterialParameters['side'],
19
20
  }
20
21
 
21
22
  export class MaterialXMaterial extends ShaderMaterial {
@@ -97,26 +98,22 @@ export class MaterialXMaterial extends ShaderMaterial {
97
98
  #include <colorspace_fragment>`);
98
99
 
99
100
  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: {
105
- ...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath, flipV),
106
- ...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath, flipV),
107
- },
104
+ uniforms: {},
108
105
  vertexShader: vertexShader,
109
106
  fragmentShader: fragmentShader,
110
107
  glslVersion: GLSL3,
111
108
  transparent: isTransparent,
112
- side: FrontSide,
109
+ side: init.side ? init.side : FrontSide,
113
110
  depthTest: true,
114
111
  depthWrite: !isTransparent,
115
112
  });
116
113
 
117
-
118
-
119
114
  Object.assign(this.uniforms, {
115
+ ...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath),
116
+ ...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath),
120
117
  u_envMatrix: { value: new Matrix4() },
121
118
  u_envRadiance: { value: null, type: 't' },
122
119
  u_envRadianceMips: { value: 8, type: 'i' },
@@ -145,8 +142,6 @@ export class MaterialXMaterial extends ShaderMaterial {
145
142
 
146
143
  const uniforms = this.uniforms;
147
144
 
148
- if (!uniforms) return;
149
-
150
145
  // TODO remove. Not sure why this is needed, but without it
151
146
  // we currently get some "swimming" where matrices are not up to date.
152
147
  camera.updateMatrixWorld(true);
@@ -193,20 +188,15 @@ export class MaterialXMaterial extends ShaderMaterial {
193
188
  const lightCount = environment.lightCount || 0;
194
189
  const textures = environment.getTextures(this) || null;
195
190
 
196
- // Update each generated material's lighting uniforms
197
- if (!this.uniforms) return;
198
-
199
191
  // Update light count
200
192
  if (this.uniforms.u_numActiveLightSources && lightCount >= 0) {
201
193
  this.uniforms.u_numActiveLightSources.value = lightCount;
202
194
  }
203
195
 
204
- // Update light data if we have lights
196
+ // Update light data
205
197
  if (lightData) {
206
198
  this.uniforms.u_lightData.value = lightData;
207
- if (debug) console.log("[MaterialX] Updated light data for material", this.name, lightData, this.uniforms,);
208
199
  }
209
- else if (debug) console.warn("[MaterialX] No light data available to update uniforms for material", this.name);
210
200
 
211
201
  // Update environment uniforms
212
202
  if (this.uniforms.u_envMatrix) {