@needle-tools/materialx 1.2.0 → 1.2.1-next.2cdc46a

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/CHANGELOG.md CHANGED
@@ -4,6 +4,9 @@ All notable changes to this package will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.2.1] - 2025-07-23
8
+ - Fix: error caused by scene.environment being null
9
+
7
10
  ## [1.2.0] - 2025-07-23
8
11
  - Add: Support to load raw MaterialX materials (from mtlx as XML)
9
12
  - Fix: Warn if tangents are missing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/materialx",
3
- "version": "1.2.0",
3
+ "version": "1.2.1-next.2cdc46a",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -56,4 +56,4 @@
56
56
  "mtlx",
57
57
  "rendering"
58
58
  ]
59
- }
59
+ }
package/src/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { ready, type MaterialXContext } from "./materialx.js";
1
+ export { ready, type MaterialXContext, preloadWasm } from "./materialx.js";
2
2
  export { MaterialXMaterial } from "./materialx.material.js";
3
3
  export { MaterialXLoader } from "./loader/loader.three.js";
4
4
 
@@ -9,3 +9,4 @@ declare const Experimental_API: {
9
9
  };
10
10
 
11
11
  export { Experimental_API };
12
+
package/src/index.js CHANGED
@@ -1,11 +1,16 @@
1
- export { ready } from "./materialx.js";
1
+ import { createMaterialXMaterial } from "./loader/loader.three.js";
2
+
3
+ export { ready, preloadWasm } from "./materialx.js";
2
4
  export { MaterialXMaterial } from "./materialx.material.js";
3
5
  export { MaterialXLoader } from "./loader/loader.three.js";
4
6
 
5
- import { createMaterialXMaterial } from "./loader/loader.three.js";
6
7
 
8
+ /**
9
+ * Experimental API for creating MaterialX materials.
10
+ */
7
11
  const Experimental_API = {
8
12
  createMaterialXMaterial
9
- }
13
+ };
14
+
15
+ export { Experimental_API };
10
16
 
11
- export { Experimental_API }
@@ -1,6 +1,7 @@
1
1
  import { addCustomExtensionPlugin, Context } from "@needle-tools/engine";
2
2
  import { useNeedleMaterialX as _useNeedleMaterialX } from "./loader.three.js";
3
3
  import { debug } from "../utils.js";
4
+ import { preloadWasm } from "../materialx.js";
4
5
 
5
6
  /**
6
7
  * @typedef {import("@needle-tools/engine").INeedleGLTFExtensionPlugin} INeedleGLTFExtensionPlugin
@@ -58,5 +59,6 @@ export class MaterialXLoaderPlugin {
58
59
  * @returns {Promise<void>}
59
60
  */
60
61
  export async function useNeedleMaterialX() {
62
+ preloadWasm("network_idle");
61
63
  addCustomExtensionPlugin(new MaterialXLoaderPlugin());
62
64
  }
@@ -1,6 +1,8 @@
1
1
  import { Light, Scene, Texture, WebGLRenderer } from "three";
2
2
  import type { MaterialX as MX } from "./materialx.types.js";
3
3
 
4
+ export function preloadWasm(trigger: "immediately" | "network_idle"): Promise<void>;
5
+
4
6
  export type MaterialXContext = {
5
7
  getTime?(): number;
6
8
  getFrame?(): number;
package/src/materialx.js CHANGED
@@ -1,8 +1,26 @@
1
1
  import MaterialX from "../bin/JsMaterialXGenShader.js";
2
- import { debug } from "./utils.js";
2
+ import { debug, waitForNetworkIdle } from "./utils.js";
3
3
  import { renderPMREMToEquirect } from "./utils.texture.js";
4
4
  import { Light, Mesh, MeshBasicMaterial, Object3D, PlaneGeometry, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
5
5
  import { registerLights, getLightData } from "./materialx.helper.js";
6
+ import { whiteTexture } from "./utils.texture.js";
7
+
8
+
9
+ /**
10
+ * Preloads the MaterialX WebAssembly module.
11
+ * @type {import("./materialx.js").preloadWasm}
12
+ */
13
+ export async function preloadWasm(trigger) {
14
+ if (trigger === "immediately") {
15
+ // Load the WASM module immediately
16
+ return ready();
17
+ } else if (trigger === "network_idle") {
18
+ // Wait for network to be idle before loading
19
+ return waitForNetworkIdle().then(ready);
20
+ }
21
+ }
22
+
23
+
6
24
 
7
25
  export const state = new class {
8
26
  /** @type {import("./materialx.types.js").MaterialX.MODULE | null} */
@@ -113,8 +131,9 @@ export async function ready() {
113
131
  */
114
132
 
115
133
  /**
116
- * MaterialXEnvironment manages the environment settings for MaterialX materials.
134
+ * MaterialXEnvironment manages the environment settings for MaterialX materials.
117
135
  */
136
+ // @dont-generate-component
118
137
  export class MaterialXEnvironment {
119
138
 
120
139
  /**
@@ -256,7 +275,14 @@ export class MaterialXEnvironment {
256
275
  // If the material has its own envMap, we don't use the irradiance texture
257
276
  return this._getTextures(material.envMap);
258
277
  }
259
- return this._getTextures(this._scene.environment);
278
+
279
+ // Use the scene background for lighting if no environment is available
280
+ // If we don't do this we don't see the correct lighting for scenes exported with 'Environment Lighting: Color' and 'Environment Reflections: Skybox'
281
+ const skybox = this._scene.environment || this._scene.background;
282
+ if (skybox instanceof Texture) {
283
+ return this._getTextures(skybox);
284
+ }
285
+ return this._getTextures(null);
260
286
  }
261
287
 
262
288
  /** @type {PMREMGenerator | null} */
@@ -284,6 +310,12 @@ export class MaterialXEnvironment {
284
310
  * @returns {{radianceTexture: Texture | null, irradianceTexture: Texture | null}}
285
311
  */
286
312
  _getTextures(texture) {
313
+
314
+ // Fallback to white texture if no texture is provided
315
+ if (!texture) {
316
+ texture = whiteTexture;
317
+ }
318
+
287
319
  /** @type {EnvironmentTextureSet | undefined} */
288
320
  let res = this._texturesCache.get(texture || null);
289
321
  if (res) {
@@ -322,7 +354,7 @@ export class MaterialXEnvironment {
322
354
  const lights = new Array();
323
355
  this._scene.traverse((/** @type {Object3D} */ object) => {
324
356
  if ((/** @type {Light} */ (object)).isLight && object.visible)
325
- lights.push(/** @type {Light} */ (object));
357
+ lights.push(/** @type {Light} */(object));
326
358
  });
327
359
  this._lights = lights;
328
360
  }
@@ -23,6 +23,7 @@ const worldViewPos = new Vector3();
23
23
  * @typedef {"highp" | "mediump" | "lowp"} Precision
24
24
  */
25
25
 
26
+ // @dont-generate-component
26
27
  export class MaterialXMaterial extends ShaderMaterial {
27
28
 
28
29
  /** The original name of the shader
@@ -90,6 +91,7 @@ export class MaterialXMaterial extends ShaderMaterial {
90
91
  // Patch fragmentShader
91
92
  const precision = init.parameters?.precision || "highp";
92
93
  vertexShader = vertexShader.replace(/precision mediump float;/g, `precision ${precision} float;`);
94
+ vertexShader = vertexShader.replace(/#define M_FLOAT_EPS 1e-8/g, precision === "highp" ? `#define M_FLOAT_EPS 1e-8` : `#define M_FLOAT_EPS 1e-3`);
93
95
  fragmentShader = fragmentShader.replace(/precision mediump float;/g, `precision ${precision} float;`);
94
96
  fragmentShader = fragmentShader.replace(/#define M_FLOAT_EPS 1e-8/g, precision === "highp" ? `#define M_FLOAT_EPS 1e-8` : `#define M_FLOAT_EPS 1e-3`);
95
97
 
@@ -258,21 +260,25 @@ export class MaterialXMaterial extends ShaderMaterial {
258
260
  // Update standard transformation matrices
259
261
  if (uniforms.u_worldMatrix) {
260
262
  uniforms.u_worldMatrix.value = object.matrixWorld;
263
+ // @ts-ignore
261
264
  uniforms.u_worldMatrix.needsUpdate = true;
262
265
  }
263
266
 
264
267
  if (uniforms.u_viewProjectionMatrix) {
265
268
  uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
269
+ // @ts-ignore
266
270
  uniforms.u_viewProjectionMatrix.needsUpdate = true;
267
271
  }
268
272
 
269
273
  if (uniforms.u_worldInverseTransposeMatrix) {
270
274
  uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
275
+ // @ts-ignore
271
276
  uniforms.u_worldInverseTransposeMatrix.needsUpdate = true;
272
277
  }
273
278
 
274
279
  if (uniforms.u_viewPosition) {
275
280
  uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
281
+ // @ts-ignore
276
282
  uniforms.u_viewPosition.needsUpdate = true;
277
283
  }
278
284
 
@@ -294,10 +300,10 @@ export class MaterialXMaterial extends ShaderMaterial {
294
300
  uniforms.u_frame.value = frame;
295
301
  }
296
302
 
303
+ this.uniformsNeedUpdate = true;
304
+
297
305
  // Update light uniforms
298
306
  this.updateEnvironmentUniforms(environment);
299
-
300
- this.uniformsNeedUpdate = true;
301
307
  }
302
308
 
303
309
  /**
@@ -331,6 +337,7 @@ export class MaterialXMaterial extends ShaderMaterial {
331
337
  if (uniforms.u_envRadiance) {
332
338
  const prev = uniforms.u_envRadiance.value;
333
339
  uniforms.u_envRadiance.value = textures.radianceTexture;
340
+ // @ts-ignore
334
341
  if (prev != textures.radianceTexture) uniforms.u_envRadiance.needsUpdate = true;
335
342
  }
336
343
  if (uniforms.u_envRadianceMips) {
@@ -339,6 +346,7 @@ export class MaterialXMaterial extends ShaderMaterial {
339
346
  if (uniforms.u_envIrradiance) {
340
347
  const prev = uniforms.u_envIrradiance.value;
341
348
  uniforms.u_envIrradiance.value = textures.irradianceTexture;
349
+ // @ts-ignore
342
350
  if (prev != textures.irradianceTexture) uniforms.u_envIrradiance.needsUpdate = true;
343
351
  }
344
352
 
package/src/utils.d.ts CHANGED
@@ -15,3 +15,6 @@ export function getTime(): number;
15
15
  * Get current frame number
16
16
  */
17
17
  export function getFrame(): number;
18
+
19
+
20
+ export function waitForNetworkIdle(): Promise<void>;
package/src/utils.js CHANGED
@@ -44,3 +44,16 @@ function updateTime() {
44
44
  }
45
45
 
46
46
  window.requestAnimationFrame(updateTime);
47
+
48
+
49
+
50
+
51
+ export async function waitForNetworkIdle() {
52
+ if (typeof requestIdleCallback !== "undefined") {
53
+ return new Promise(res => requestIdleCallback(res));
54
+ }
55
+ else {
56
+ console.debug("[MaterialX] Can not wait for network idle, using fallback");
57
+ return new Promise(res => setTimeout(res, 100)); // Fallback to a short delay
58
+ }
59
+ }
@@ -1,5 +1,7 @@
1
1
  import { WebGLRenderer, WebGLRenderTarget, Texture } from 'three';
2
2
 
3
+ export const whiteTexture: Texture;
4
+
3
5
  /**
4
6
  * Renders a PMREM environment map to an equirectangular texture with specified roughness
5
7
  */
@@ -3,6 +3,13 @@ import { getParam } from './utils.js';
3
3
 
4
4
  const debug = getParam("debugmaterialx");
5
5
 
6
+ export const whiteTexture = new Texture();
7
+ whiteTexture.needsUpdate = true;
8
+ whiteTexture.image = new Image();
9
+ whiteTexture.image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAANQTFRFr6+vGqg52AAAAAxJREFUeJxjZGBEgQAAWAAJLpjsTQAAAABJRU5ErkJggg=="
10
+
11
+
12
+
6
13
  /**
7
14
  * Renders a PMREM environment map to an equirectangular texture with specified roughness
8
15
  * @param {WebGLRenderer} renderer - Three.js WebGL renderer