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

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.
@@ -2,9 +2,7 @@
2
2
  import { TypeStore } from "@needle-tools/engine"
3
3
 
4
4
  // Import types
5
- import { MaterialXMaterial } from "../src/materialx.material.js";
6
5
  import { MaterialXUniformUpdate } from "../src/loader/loader.needle.js";
7
6
 
8
7
  // Register types
9
- TypeStore.add("MaterialXMaterial", MaterialXMaterial);
10
8
  TypeStore.add("MaterialXUniformUpdate", MaterialXUniformUpdate);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/materialx",
3
- "version": "1.0.1-next.c315a2f",
3
+ "version": "1.0.1-next.df0e959",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "exports": {
package/src/helper.js ADDED
@@ -0,0 +1,490 @@
1
+ //
2
+ // Copyright Contributors to the MaterialX Project
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ //
5
+
6
+ import { getParam, getWorldDirection } from '@needle-tools/engine';
7
+ import * as THREE from 'three';
8
+
9
+ const debug = getParam("debugmaterialx");
10
+
11
+ const IMAGE_PROPERTY_SEPARATOR = "_";
12
+ const UADDRESS_MODE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "uaddressmode";
13
+ const VADDRESS_MODE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "vaddressmode";
14
+ const FILTER_TYPE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "filtertype";
15
+ const IMAGE_PATH_SEPARATOR = "/";
16
+
17
+ /**
18
+ * Initialized the environment texture as MaterialX expects it
19
+ * @param {THREE.Texture} texture
20
+ * @param {Object} capabilities
21
+ * @returns {THREE.Texture}
22
+ */
23
+ export function prepareEnvTexture(texture, capabilities)
24
+ {
25
+ let newTexture = new THREE.DataTexture(texture.image.data, texture.image.width, texture.image.height, texture.format, texture.type);
26
+ newTexture.wrapS = THREE.RepeatWrapping;
27
+ newTexture.anisotropy = capabilities.getMaxAnisotropy();
28
+ newTexture.minFilter = THREE.LinearMipmapLinearFilter;
29
+ newTexture.magFilter = THREE.LinearFilter;
30
+ newTexture.generateMipmaps = true;
31
+ newTexture.needsUpdate = true;
32
+
33
+ return newTexture;
34
+ }
35
+
36
+ /**
37
+ * Get Three uniform from MaterialX vector
38
+ * @param {any} value
39
+ * @param {any} dimension
40
+ * @returns {THREE.Uniform}
41
+ */
42
+ function fromVector(value, dimension)
43
+ {
44
+ let outValue;
45
+ if (value)
46
+ {
47
+ outValue = [...value.data()];
48
+ }
49
+ else
50
+ {
51
+ outValue = [];
52
+ for (let i = 0; i < dimension; ++i)
53
+ outValue.push(0.0);
54
+ }
55
+
56
+ return outValue;
57
+ }
58
+
59
+ /**
60
+ * Get Three uniform from MaterialX matrix
61
+ * @param {mx.matrix} matrix
62
+ * @param {mx.matrix.size} dimension
63
+ */
64
+ function fromMatrix(matrix, dimension)
65
+ {
66
+ let vec = [];
67
+ if (matrix)
68
+ {
69
+ for (let i = 0; i < matrix.numRows(); ++i)
70
+ {
71
+ for (let k = 0; k < matrix.numColumns(); ++k)
72
+ {
73
+ vec.push(matrix.getItem(i, k));
74
+ }
75
+ }
76
+ } else
77
+ {
78
+ for (let i = 0; i < dimension; ++i)
79
+ vec.push(0.0);
80
+ }
81
+
82
+ return vec;
83
+ }
84
+
85
+ /**
86
+ * Get Three uniform from MaterialX value
87
+ * @param {mx.Uniform.type} type
88
+ * @param {mx.Uniform.value} value
89
+ * @param {mx.Uniform.name} name
90
+ * @param {mx.Uniforms} uniforms
91
+ * @param {THREE.TextureLoader} textureLoader
92
+ * @param {string} searchPath
93
+ * @param {boolean} flipY
94
+ */
95
+ function toThreeUniform(type, value, name, uniforms, textureLoader, searchPath, flipY)
96
+ {
97
+ let outValue = null;
98
+ switch (type)
99
+ {
100
+ case 'float':
101
+ case 'integer':
102
+ case 'boolean':
103
+ outValue = value;
104
+ break;
105
+ case 'vector2':
106
+ outValue = fromVector(value, 2);
107
+ break;
108
+ case 'vector3':
109
+ case 'color3':
110
+ outValue = fromVector(value, 3);
111
+ break;
112
+ case 'vector4':
113
+ case 'color4':
114
+ outValue = fromVector(value, 4);
115
+ break;
116
+ case 'matrix33':
117
+ outValue = fromMatrix(value, 9);
118
+ break;
119
+ case 'matrix44':
120
+ outValue = fromMatrix(value, 16);
121
+ break;
122
+ case 'filename':
123
+ if (value)
124
+ {
125
+ // Cache / reuse texture to avoid reload overhead.
126
+ // Note: that data blobs and embedded data textures are not cached as they are transient data.
127
+ let checkCache = false;
128
+ let texturePath = searchPath + IMAGE_PATH_SEPARATOR + value;
129
+ if (value.startsWith('blob:'))
130
+ {
131
+ texturePath = value;
132
+ if (debug) console.log('Load blob URL:', texturePath);
133
+ checkCache = false;
134
+ }
135
+ else if (value.startsWith('http'))
136
+ {
137
+ texturePath = value;
138
+ if (debug) console.log('Load HTTP URL:', texturePath);
139
+ }
140
+ else if (value.startsWith('data:'))
141
+ {
142
+ texturePath = value;
143
+ checkCache = false;
144
+ if (debug) console.log('Load data URL:', texturePath);
145
+ }
146
+ const cachedTexture = checkCache && THREE.Cache.get(texturePath);
147
+ if (cachedTexture)
148
+ {
149
+ // Get texture from cache
150
+ outValue = cachedTexture;
151
+ if (debug) console.log('Use cached texture: ', texturePath, outValue);
152
+ }
153
+ else
154
+ {
155
+ outValue = textureLoader.load(
156
+ texturePath,
157
+ function (texture) {
158
+ if (debug) console.log('Load new texture: ' + texturePath, texture);
159
+ outValue = texture;
160
+
161
+ // Add texture to ThreeJS cache
162
+ if (checkCache)
163
+ THREE.Cache.add(texturePath, texture);
164
+ },
165
+ undefined,
166
+ function (error) {
167
+ console.error('Error loading texture: ', error);
168
+ });
169
+
170
+ // Set address & filtering mode
171
+ if (outValue)
172
+ setTextureParameters(outValue, name, uniforms, flipY);
173
+ }
174
+ }
175
+ break;
176
+ case 'samplerCube':
177
+ case 'string':
178
+ break;
179
+ default:
180
+ const key = type + ':' + name;
181
+ if (!valueTypeWarningMap.has(key))
182
+ {
183
+ valueTypeWarningMap.set(key, true);
184
+ console.warn('MaterialX: Unsupported uniform type: ' + type + ' for uniform: ' + name, value);
185
+ }
186
+ outValue = null;
187
+ }
188
+
189
+ return outValue;
190
+ }
191
+
192
+ const valueTypeWarningMap = new Map();
193
+
194
+ /**
195
+ * Get Three wrapping mode
196
+ * @param {mx.TextureFilter.wrap} mode
197
+ * @returns {THREE.Wrapping}
198
+ */
199
+ function getWrapping(mode)
200
+ {
201
+ let wrap;
202
+ switch (mode)
203
+ {
204
+ case 1:
205
+ wrap = THREE.ClampToEdgeWrapping;
206
+ break;
207
+ case 2:
208
+ wrap = THREE.RepeatWrapping;
209
+ break;
210
+ case 3:
211
+ wrap = THREE.MirroredRepeatWrapping;
212
+ break;
213
+ default:
214
+ wrap = THREE.RepeatWrapping;
215
+ break;
216
+ }
217
+ return wrap;
218
+ }
219
+
220
+ /**
221
+ * Get Three minification filter
222
+ * @param {mx.TextureFilter.minFilter} type
223
+ * @param {mx.TextureFilter.generateMipmaps} generateMipmaps
224
+ */
225
+ function getMinFilter(type, generateMipmaps)
226
+ {
227
+ /** @type {THREE.TextureFilter} */
228
+ let filterType = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
229
+ if (type === 0)
230
+ {
231
+ filterType = generateMipmaps ? THREE.NearestMipMapNearestFilter : THREE.NearestFilter;
232
+ }
233
+ return filterType;
234
+ }
235
+
236
+ /**
237
+ * Set Three texture parameters
238
+ * @param {THREE.Texture} texture
239
+ * @param {mx.Uniform.name} name
240
+ * @param {mx.Uniforms} uniforms
241
+ * @param {mx.TextureFilter.generateMipmaps} generateMipmaps
242
+ */
243
+ function setTextureParameters(texture, name, uniforms, flipY = true, generateMipmaps = true)
244
+ {
245
+ const idx = name.lastIndexOf(IMAGE_PROPERTY_SEPARATOR);
246
+ const base = name.substring(0, idx) || name;
247
+
248
+ texture.generateMipmaps = generateMipmaps;
249
+ texture.wrapS = THREE.RepeatWrapping;
250
+ texture.wrapT = THREE.RepeatWrapping;
251
+ texture.magFilter = THREE.LinearFilter;
252
+ texture.flipY = flipY;
253
+
254
+ if (uniforms.find(base + UADDRESS_MODE_SUFFIX))
255
+ {
256
+ const uaddressmode = uniforms.find(base + UADDRESS_MODE_SUFFIX).getValue().getData();
257
+ texture.wrapS = getWrapping(uaddressmode);
258
+ }
259
+
260
+ if (uniforms.find(base + VADDRESS_MODE_SUFFIX))
261
+ {
262
+ const vaddressmode = uniforms.find(base + VADDRESS_MODE_SUFFIX).getValue().getData();
263
+ texture.wrapT = getWrapping(vaddressmode);
264
+ }
265
+
266
+ const filterType = uniforms.find(base + FILTER_TYPE_SUFFIX) ? uniforms.get(base + FILTER_TYPE_SUFFIX).value : -1;
267
+ texture.minFilter = getMinFilter(filterType, generateMipmaps);
268
+ }
269
+
270
+ /**
271
+ * Return the global light rotation matrix
272
+ */
273
+ export function getLightRotation()
274
+ {
275
+ return new THREE.Matrix4().makeRotationY(Math.PI / 2);
276
+ }
277
+
278
+ /**
279
+ * Returns all lights nodes in a MaterialX document
280
+ * @param {mx.Document} doc
281
+ * @returns {Array.<mx.Node>}
282
+ */
283
+ export function findLights(doc)
284
+ {
285
+ let lights = [];
286
+ for (let node of doc.getNodes())
287
+ {
288
+ if (node.getType() === "lightshader")
289
+ lights.push(node);
290
+ }
291
+ return lights;
292
+ }
293
+
294
+ /**
295
+ * Register lights in shader generation context
296
+ * @param {Object} mx MaterialX Module
297
+ * @param {Array.<mx.Node>} lights Light nodes
298
+ * @param {mx.GenContext} genContext Shader generation context
299
+ * @returns {Array.<mx.Node>}
300
+ */
301
+ export async function registerLights(mx, lights, genContext)
302
+ {
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
+
307
+ const lightTypesBound = {};
308
+ const lightData = [];
309
+ let lightId = 1;
310
+ let lightCount = 0;
311
+ const maxLightCount = genContext.getOptions().hwMaxActiveLightSources;
312
+
313
+ // All light types so that we have NodeDefs for them
314
+ const defaultLightRigXml = `<?xml version="1.0"?>
315
+ <materialx version="1.39">
316
+ <directional_light name="default_directional_light" type="lightshader">
317
+ </directional_light>
318
+ <point_light name="default_point_light" type="lightshader">
319
+ </point_light>
320
+ <spot_light name="default_spot_light" type="lightshader">
321
+ </spot_light>
322
+ <!--
323
+ <area_light name="default_area_light" type="lightshader">
324
+ </area_light>
325
+ -->
326
+ </materialx>`;
327
+
328
+ // Load default light rig XML to ensure we have all light types available
329
+ const lightRigDoc = mx.createDocument();
330
+ await mx.readFromXmlString(lightRigDoc, defaultLightRigXml);
331
+ const document = mx.createDocument();
332
+ const stdlib = mx.loadStandardLibraries(genContext);
333
+ document.setDataLibrary(stdlib);
334
+ document.importLibrary(lightRigDoc);
335
+ const defaultLights = findLights(document);
336
+ if (debug) console.log("Default lights in MaterialX document", defaultLights);
337
+
338
+ // Loading a document seems to reset this option for some reason, so we set it again
339
+ genContext.getOptions().hwMaxActiveLightSources = maxLightCount;
340
+
341
+ // Register types only – we get these from the default light rig XML above
342
+ // This is needed to ensure that the light shaders are bound for each light type
343
+ for (let light of defaultLights)
344
+ {
345
+ const lightDef = light.getNodeDef();
346
+ if (debug) console.log("Default light node definition", lightDef);
347
+ if (!lightDef) continue;
348
+
349
+ const lightName = lightDef.getName();
350
+ if (debug) console.log("Registering default light", { lightName, lightDef });
351
+ if (!lightTypesBound[lightName])
352
+ {
353
+ // TODO check if we need to bind light shader for each three.js light instead of once per type
354
+ if (debug) console.log("Bind light shader for node", { lightName, lightId, lightDef });
355
+ lightTypesBound[lightName] = lightId;
356
+ mx.HwShaderGenerator.bindLightShader(lightDef, lightId++, genContext);
357
+ }
358
+ }
359
+
360
+ if (debug) console.log("Light types bound in MaterialX context", lightTypesBound);
361
+
362
+ // MaterialX light nodes
363
+ for (let light of lights)
364
+ {
365
+ // Skip if light does not have a node definition
366
+ if (!("getNodeDef" in light)) continue;
367
+
368
+ let nodeDef = light.getNodeDef();
369
+ let nodeName = nodeDef.getName();
370
+ if (!lightTypesBound[nodeName])
371
+ {
372
+ if (debug) console.log("bind light shader for node", { nodeName, lightId, nodeDef });
373
+ lightTypesBound[nodeName] = lightId;
374
+ mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
375
+ }
376
+
377
+ const lightDirection = light.getValueElement("direction").getValue().getData().data();
378
+ const lightColor = light.getValueElement("color").getValue().getData().data();
379
+ const lightIntensity = light.getValueElement("intensity").getValue().getData();
380
+
381
+ let rotatedLightDirection = new THREE.Vector3(...lightDirection)
382
+ rotatedLightDirection.transformDirection(getLightRotation())
383
+
384
+ lightData.push({
385
+ type: lightTypesBound[nodeName],
386
+ direction: rotatedLightDirection,
387
+ color: new THREE.Vector3(...lightColor),
388
+ intensity: lightIntensity,
389
+ });
390
+ }
391
+
392
+ const threeLightTypeToMaterialXNodeName = (threeLightType) => {
393
+ switch (threeLightType) {
394
+ case 'PointLight':
395
+ return 'ND_point_light';
396
+ case 'DirectionalLight':
397
+ return 'ND_directional_light';
398
+ case 'SpotLight':
399
+ return 'ND_spot_light';
400
+ default:
401
+ console.warn('MaterialX: Unsupported light type: ' + threeLightType);
402
+ return 'ND_point_light'; // Default to point light
403
+ }
404
+ };
405
+
406
+ if (debug) console.log("Registering lights in MaterialX context", lights, lightData);
407
+
408
+ // Three.js lights
409
+ for (let light of lights) {
410
+ // Skip if light is not a Three.js light
411
+ if (!light.isLight) continue;
412
+
413
+ // Types in MaterialX: point_light, directional_light, spot_light
414
+
415
+ const lightDefinitionName = threeLightTypeToMaterialXNodeName(light.type);
416
+
417
+ if (!lightTypesBound[lightDefinitionName])
418
+ {
419
+ lightTypesBound[lightDefinitionName] = lightId;
420
+ const nodeDef = null;
421
+ mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
422
+ }
423
+
424
+ const wp = light.getWorldPosition(new THREE.Vector3());
425
+ const wd = getWorldDirection(light, new THREE.Vector3(0,0,-1));
426
+ lightData.push({
427
+ type: lightTypesBound[lightDefinitionName],
428
+ position: wp.clone(),
429
+ direction: wd.clone(),
430
+ 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,
435
+ });
436
+ }
437
+
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);
460
+
461
+ return { lightData, lightCount };
462
+ }
463
+
464
+ /**
465
+ * Get uniform values for a shader
466
+ * @param {mx.shaderStage} shaderStage
467
+ * @param {THREE.TextureLoader} textureLoader
468
+ */
469
+ export function getUniformValues(shaderStage, textureLoader, searchPath, flipY)
470
+ {
471
+ let threeUniforms = {};
472
+
473
+ const uniformBlocks = Object.values(shaderStage.getUniformBlocks());
474
+ uniformBlocks.forEach(uniforms =>
475
+ {
476
+ if (!uniforms.empty())
477
+ {
478
+ for (let i = 0; i < uniforms.size(); ++i)
479
+ {
480
+ const variable = uniforms.get(i);
481
+ const value = variable.getValue()?.getData();
482
+ const name = variable.getVariable();
483
+ threeUniforms[name] = new THREE.Uniform(toThreeUniform(variable.getType().getName(), value, name, uniforms,
484
+ textureLoader, searchPath, flipY));
485
+ }
486
+ }
487
+ });
488
+
489
+ return threeUniforms;
490
+ }
@@ -6,21 +6,32 @@ import type { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
6
6
  import type { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
7
7
  import { MaterialXLoader } from "./loader.three.js";
8
8
  import { debug } from "../utils.js";
9
- import { MaterialXEnvironment, state } from "../materialx.js";
10
- import { MaterialXMaterial } from "../materialx.material.js";
9
+ import { state } from "../materialx.js";
11
10
 
12
11
  //@dont-generate-component
13
12
  export class MaterialXUniformUpdate extends Component {
14
13
 
14
+ static updateMaterial(mat: Material | Material[], object: Object3D, camera: Camera) {
15
+ if (Array.isArray(mat)) {
16
+ mat.forEach(m => {
17
+ if (m.userData?.updateUniforms) {
18
+ m.userData.updateUniforms(object, camera);
19
+ }
20
+ });
21
+ } else if (mat.userData?.updateUniforms) {
22
+ mat.userData.updateUniforms(object, camera);
23
+ }
24
+ }
25
+
15
26
  onEnable(): void {
16
- this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
27
+ this.context.addBeforeRenderListener(this.gameObject, this._onBeforeRender);
17
28
  }
18
29
 
19
30
  onDisable(): void {
20
- this.context.removeBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
31
+ this.context.removeBeforeRenderListener(this.gameObject, this._onBeforeRender);
21
32
  }
22
33
 
23
- onBeforeRenderThree = () => {
34
+ _onBeforeRender = () => {
24
35
  // Update uniforms or perform any pre-render logic here
25
36
  const gameObject = this.gameObject as any as Mesh;
26
37
  const material = gameObject?.material;
@@ -28,16 +39,14 @@ export class MaterialXUniformUpdate extends Component {
28
39
  const camera = this.context.mainCamera;
29
40
  if (!camera) return;
30
41
 
31
- const env = state.materialXEnvironment;
42
+ MaterialXUniformUpdate.updateMaterial(material, gameObject, camera);
32
43
 
33
- if (Array.isArray(material)) {
34
- for (const entry of material) {
35
- if (entry && entry instanceof MaterialXMaterial) {
36
- entry.updateUniforms(this.context, env, this.gameObject, camera);
37
- }
38
- }
39
- } else if (material instanceof MaterialXMaterial) {
40
- material.updateUniforms(this.context, env, this.gameObject, camera);
44
+ // If this is a Group, we need to update all direct children
45
+ if ((gameObject as any as Group).isGroup) {
46
+ gameObject.children.forEach((child: Object3D) => {
47
+ if (child instanceof Mesh && child.material)
48
+ MaterialXUniformUpdate.updateMaterial(child.material, child, camera);
49
+ });
41
50
  }
42
51
  }
43
52
  }
@@ -53,18 +62,14 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
53
62
  // Register the MaterialX loader extension
54
63
  // Environment initialization is now handled in the MaterialXLoader constructor
55
64
  loader.register(p => {
56
- this.loader = new MaterialXLoader(p, url, context);
65
+ this.loader = new MaterialXLoader(p, context);
57
66
  return this.loader;
58
67
  });
59
68
  };
60
69
 
61
- onLoaded = (url: string, gltf: GLTF, _context: Context) => {
62
- if (debug) console.log("[MaterialX] MaterialXLoaderPlugin: glTF loaded", { url, scene: gltf.scene, materialX_root_data: this.loader?.materialX_root_data });
70
+ onLoaded = (url: string, gltf: GLTF, context: Context) => {
71
+ if (debug) console.log("[MaterialX] MaterialXLoaderPlugin: glTF loaded", url, gltf.scene);
63
72
 
64
- // If we don't have MaterialX data in the loaded glTF we don't need to do anything else here
65
- if (!this.loader?.materialX_root_data) {
66
- return;
67
- }
68
73
  // Set up onBeforeRender callbacks for objects with MaterialX materials
69
74
  // This ensures uniforms are updated properly during rendering
70
75
  gltf.scene.traverse((child) => {
@@ -72,7 +77,7 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
72
77
  const mesh = child as Mesh;
73
78
  const material = mesh.material as Material;
74
79
 
75
- if (material instanceof MaterialXMaterial) {
80
+ if (material?.userData?.updateUniforms) {
76
81
  if (debug) console.log("[MaterialX] Adding MaterialX uniform update component to:", child.name);
77
82
  child.addComponent(MaterialXUniformUpdate);
78
83
  }
@@ -80,6 +85,12 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
80
85
  });
81
86
 
82
87
  if (debug) console.log("[MaterialX] Loaded: ", this.loader);
88
+
89
+ // Initialize MaterialX lighting system with scene data
90
+ const environment = state.materialXEnvironment;
91
+ environment.initializeFromContext(context).then(() => {
92
+ this.loader?.updateLightingFromEnvironment(environment);
93
+ });
83
94
  };
84
95
 
85
96
  onExport = (_exporter: GLTFExporter, _context: Context) => {