@needle-tools/materialx 1.0.1-next.b9467c8 → 1.0.1-next.b9638d9

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/src/materialx.ts CHANGED
@@ -1,15 +1,14 @@
1
- import { Context, delay, isDevEnvironment, ObjectUtils, GameObject, onBeforeRender } from "@needle-tools/engine";
2
- import type { MaterialX as MX } from "./materialx.types.js";
1
+ import { Context, delay, isDevEnvironment, ObjectUtils } from "@needle-tools/engine";
3
2
  import MaterialX from "../bin/JsMaterialXGenShader.js";
4
3
  import { debug } from "./utils.js";
5
4
  import { renderPMREMToEquirect } from "./textureHelper.js";
6
- import { Light, Material, MeshBasicMaterial, Object3D, PMREMGenerator, Texture } from "three";
7
- import { registerLights, getLightData } from "./materialx.helper.js";
8
- import type { MaterialXMaterial } from "./materialx.material.js";
5
+ import { Light, MeshBasicMaterial, Object3D, PMREMGenerator } from "three";
6
+ import { registerLights } from "./helper.js";
9
7
 
10
8
 
9
+ // Global MaterialX module instance - initialized lazily
11
10
  export const state = new class {
12
- materialXModule: MX.MODULE | null = null;
11
+ materialXModule: any = null;
13
12
  materialXGenerator: any = null;
14
13
  materialXGenContext: any = null;
15
14
  materialXStdLib: any = null;
@@ -23,14 +22,14 @@ export const state = new class {
23
22
  }
24
23
  }
25
24
 
26
- /** Initialize the MaterialX module. Must be awaited before trying to create materials */
27
- export async function ready(): Promise<void> {
25
+ // Initialize MaterialX WASM module lazily
26
+ export async function initializeMaterialX(): Promise<void> {
28
27
  if (state.materialXInitPromise) {
29
28
  return state.materialXInitPromise;
30
29
  }
31
30
  return state.materialXInitPromise = (async () => {
32
31
  if (state.materialXModule) return; // Already initialized
33
- if (debug) console.log("[MaterialX] Initializing WASM module...");
32
+ if (debug) console.log("Initializing MaterialX WASM module...");
34
33
  try {
35
34
 
36
35
  const urls: Array<string> = await Promise.all([
@@ -45,7 +44,7 @@ export async function ready(): Promise<void> {
45
44
 
46
45
  const module = await MaterialX({
47
46
  locateFile: (path: string, scriptDirectory: string) => {
48
- if (debug) console.debug("[MaterialX] locateFile called:", { path, scriptDirectory });
47
+ if (debug) console.debug("MaterialX locateFile called:", { path, scriptDirectory });
49
48
 
50
49
  if (path.includes("JsMaterialXCore.wasm")) {
51
50
  return JsMaterialXCore; // Use the URL for the core WASM file
@@ -60,8 +59,8 @@ export async function ready(): Promise<void> {
60
59
  return scriptDirectory + path;
61
60
  },
62
61
  });
63
- if (debug) console.log("[MaterialX] module loaded", module);
64
- state.materialXModule = module as MX.MODULE
62
+ if (debug) console.log("MaterialXLoader module loaded", module);
63
+ state.materialXModule = module;
65
64
 
66
65
  // Initialize shader generator and context
67
66
  state.materialXGenerator = module.EsslShaderGenerator.create();
@@ -72,190 +71,158 @@ export async function ready(): Promise<void> {
72
71
  state.materialXStdLib = module.loadStandardLibraries(state.materialXGenContext);
73
72
  tempDoc.setDataLibrary(state.materialXStdLib);
74
73
 
75
- // TODO ShaderInterfaceType.SHADER_INTERFACE_REDUCED would be better, but doesn't actually seem to be supported in the MaterialX javascript bindings
76
- state.materialXGenContext.getOptions().shaderInterfaceType = state.materialXModule.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE;
77
-
78
- // SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
79
- // SPECULAR_ENVIRONMENT_FIS: Use Filtered Importance Sampling for specular environment/indirect lighting.
80
- // SPECULAR_ENVIRONMENT_PREFILTER: Use pre-filtered environment maps for specular environment/indirect lighting.
81
- state.materialXGenContext.getOptions().hwSpecularEnvironmentMethod = state.materialXModule.HwSpecularEnvironmentMethod.SPECULAR_ENVIRONMENT_FIS;
82
-
83
- // TRANSMISSION_REFRACTION: Use a refraction approximation for transmission rendering.
84
- // TRANSMISSION_OPACITY: Use opacity for transmission rendering.
85
- // state.materialXGenContext.getOptions().hwTransmissionRenderMethod = state.materialXModule.HwTransmissionRenderMethod.TRANSMISSION_REFRACTION;
86
-
87
- // Turned off because we're doing color space conversion the three.js way
88
- state.materialXGenContext.getOptions().hwSrgbEncodeOutput = false;
89
-
90
- // Enables the generation of a prefiltered environment map.
91
- // TODO Would be great to use but requires setting more uniforms (like u_envPrefilterMip).
92
- // When set to true, the u_envRadiance map is expected to be a prefiltered environment map.
93
- // state.materialXGenContext.getOptions().hwWriteEnvPrefilter = true;
94
-
95
- // Set a reasonable default for max active lights
96
- 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>`;
97
84
 
98
85
  // This prewarms the shader generation context to have all light types
99
- await registerLights(state.materialXModule, state.materialXGenContext);
86
+ await registerLights(state.materialXModule, [], state.materialXGenContext);
100
87
 
101
- if (debug) console.log("[MaterialX] generator initialized successfully");
88
+ if (debug) console.log("MaterialX generator initialized successfully");
102
89
  } catch (error) {
103
- console.error("[MaterialX] Failed to load MaterialX module:", error);
90
+ console.error("Failed to load MaterialX module:", error);
104
91
  throw error;
105
92
  }
106
93
  })();
107
94
  }
108
95
 
109
- type EnvironmentTextureSet = {
110
- radianceTexture: Texture | null;
111
- irradianceTexture: Texture | null;
112
- }
113
-
114
96
  // MaterialX Environment Manager - handles lighting and environment setup
115
97
  export class MaterialXEnvironment {
116
- private _context: Context | null = null;
117
- private _lights: Array<Light> = [];
118
- private _lightData: any = null;
119
- private _lightCount: number = 0;
120
- private _initializePromise: Promise<boolean> | null = null;
121
-
122
- private _unsubscribehook: (() => void) | 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;
123
104
 
124
105
  constructor() {
125
- if (debug) console.log("[MaterialX] Environment created");
106
+ if (debug) console.log("MaterialX Environment created");
126
107
  }
127
108
 
128
- // Initialize with Needle Engine context
129
- async initialize(context: Context): Promise<boolean> {
130
- if (this._initializePromise) {
131
- return this._initializePromise;
132
- }
133
- return this._initializePromise = this._initialize(context);
109
+ setContext(context: Context) {
110
+ this.context = context;
134
111
  }
135
112
 
136
- get lightData() { return this._lightData; }
137
- get lightCount() { return this._lightCount || 0; }
138
- getTextures(material: MaterialXMaterial) {
139
- if (material.envMap) {
140
- // If the material has its own envMap, we don't use the irradiance texture
141
- return this._getTextures(material.envMap);
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;
142
119
  }
143
- return this._getTextures(this._context?.scene.environment);
120
+
121
+ registerLights(materialXModule, this.lights, materialXGenContext);
144
122
  }
123
+ */
145
124
 
146
- private _pmremGenerator: PMREMGenerator | null = null;
147
- private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
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;
130
+ }
148
131
 
149
- private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
132
+ // Prevent multiple initializations
133
+ if (this.initialized) {
134
+ if (debug) console.log("MaterialX environment already initialized, skipping");
135
+ return;
136
+ }
150
137
 
151
- this._context = context;
152
- this._pmremGenerator = new PMREMGenerator(context.renderer);
138
+ // Clean up previous textures if they exist
139
+ if (this.radianceTexture) {
140
+ if (debug) console.log("Disposing previous radiance texture");
141
+ this.radianceTexture.dispose();
142
+ this.radianceTexture = null;
143
+ }
144
+ if (this.irradianceTexture) {
145
+ if (debug) console.log("Disposing previous irradiance texture");
146
+ this.irradianceTexture.dispose();
147
+ this.irradianceTexture = null;
148
+ }
153
149
 
154
- this._unsubscribehook?.();
155
- this._unsubscribehook = onBeforeRender(() => {
156
- this.updateLighting(false);
157
- this._getTextures(context.scene.environment);
158
- })
150
+ // Get renderer from context
151
+ const renderer = this.context.renderer;
159
152
 
160
153
  // TODO remove this delay; we should wait for the scene lighting to be ready
161
154
  // and then update the uniforms
162
- while (!context.scene.environment) {
163
- await delay(5);
155
+ let envMap = this.context.scene.environment;
156
+ while (!envMap) {
157
+ await delay(200);
158
+ envMap = this.context.scene.environment;
164
159
  }
165
- this._getTextures(context.scene.environment);
166
-
167
- // if (debug) {
168
- // console.log({ radiance: this._radianceTexture, irradiance: this._irradianceTexture });
169
- // // Show both of them on cubes in the scene
170
- // const unlitMat = new MeshBasicMaterial();
171
- // unlitMat.side = 2;
172
- // const radianceMat = unlitMat.clone();
173
- // radianceMat.map = this._radianceTexture;
174
- // const radianceCube = ObjectUtils.createPrimitive("Quad", { material: radianceMat });
175
- // const irradianceMat = unlitMat.clone();
176
- // irradianceMat.map = this._irradianceTexture;
177
- // const irradianceCube = ObjectUtils.createPrimitive("Quad", { material: irradianceMat });
178
- // context.scene.add(radianceCube);
179
- // context.scene.add(irradianceCube);
180
- // radianceCube.position.set(2, 0, 0);
181
- // irradianceCube.position.set(-2, 0, 0);
182
- // console.log("[MaterialX] environment initialized from Needle context", this, this._context.scene);
183
- // }
184
-
185
- this.updateLighting(true);
186
-
187
- // Mark as initialized
188
- return true;
189
- }
190
-
191
- // Reset the environment to allow re-initialization
192
- reset() {
193
- if (debug) console.log("[MaterialX] Resetting environment");
194
- this._initializePromise = null;
195
- this._lights = [];
196
- this._lightData = null;
197
- this._lightCount = 0;
198
- this._pmremGenerator?.dispose();
199
- this._pmremGenerator = null;
200
- for(const textureSet of this._texturesCache.values()) {
201
- textureSet.radianceTexture?.dispose();
202
- textureSet.irradianceTexture?.dispose();
160
+ var pmrem = new PMREMGenerator(renderer);
161
+ const target = pmrem.fromEquirectangular(envMap);
162
+
163
+ const radianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 0.0, 1024, 512, target.height);
164
+ const irradianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 1.0, 32, 16, target.height);
165
+
166
+ this.radianceTexture = radianceRenderTarget.texture;
167
+ this.irradianceTexture = irradianceRenderTarget.texture;
168
+
169
+ // Clean up PMREM generator and its render target
170
+ target.dispose();
171
+ pmrem.dispose();
172
+
173
+ if (debug) {
174
+ console.log({ radiance: this.radianceTexture, irradiance: this.irradianceTexture });
175
+ // Show both of them on cubes in the scene
176
+ const unlitMat = new MeshBasicMaterial();
177
+ const radianceMat = unlitMat.clone();
178
+ radianceMat.map = this.radianceTexture;
179
+ const radianceCube = ObjectUtils.createPrimitive("Cube", { material: radianceMat });
180
+ const irradianceMat = unlitMat.clone();
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);
185
+ radianceCube.position.set(2, 0, 0);
186
+ radianceCube.scale.y = 0.00001;
187
+ irradianceCube.position.set(-2, 0, 0);
188
+ irradianceCube.scale.y = 0.00001;
189
+ // await this.initializeLighting(defaultLightRigXml, renderer);
190
+ console.log("MaterialX environment initialized from Needle context", this, this.context.scene);
203
191
  }
204
- this._texturesCache.clear();
205
192
 
206
- this._unsubscribehook?.();
207
- this._unsubscribehook = null;
208
- }
209
-
210
- private _getTextures(texture: Texture | null | undefined): {
211
- radianceTexture: Texture | null,
212
- irradianceTexture: Texture | null
213
- } {
193
+ // Find lights in scene
194
+ let lights = new Array<Light>();
195
+ this.context.scene.traverse((object: Object3D) => {
196
+ if ((object as Light).isLight) lights.push(object as Light);
197
+ });
214
198
 
215
- let res: EnvironmentTextureSet | undefined = this._texturesCache.get(texture || null);
216
- if (res) {
217
- return res;
218
- }
199
+ this.lightData = await registerLights(state.materialXModule, lights, state.materialXGenContext);
219
200
 
220
- if (this._context && this._pmremGenerator && texture) {
221
- if (debug) console.log("[MaterialX] Generating environment textures", texture.name);
222
-
223
- const target = this._pmremGenerator.fromEquirectangular(texture);
224
- const radianceRenderTarget = renderPMREMToEquirect(this._context.renderer, target.texture, 0.0, 1024, 512, target.height);
225
- const irradianceRenderTarget = renderPMREMToEquirect(this._context.renderer, target.texture, 1.0, 32, 16, target.height);
226
- target.dispose();
227
- res = {
228
- radianceTexture: radianceRenderTarget.texture,
229
- irradianceTexture: irradianceRenderTarget.texture
230
- }
231
- }
232
- else {
233
- res = {
234
- radianceTexture: null,
235
- irradianceTexture: null
236
- }
237
- }
238
- this._texturesCache.set(texture || null, res);
239
- return res;
201
+ // Mark as initialized
202
+ this.initialized = true;
240
203
  }
241
204
 
242
- private updateLighting = (collectLights: boolean = false) => {
243
- if (!this._context) return;
205
+ // getLights() { return this.lights; }
206
+ getLightData() { return this.lightData; }
207
+ getRadianceTexture() { return this.radianceTexture; }
208
+ getIrradianceTexture() { return this.irradianceTexture; }
244
209
 
245
- // Find lights in scene
246
- if (collectLights) {
247
- const lights = new Array<Light>();
248
- this._context.scene.traverse((object: Object3D) => {
249
- if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
250
- lights.push(object as Light);
251
- });
252
- this._lights = lights;
253
- }
210
+ setRadianceTexture(texture: any) { this.radianceTexture = texture; }
211
+ setIrradianceTexture(texture: any) { this.irradianceTexture = texture; }
254
212
 
255
- if (state.materialXGenContext) {
256
- const { lightData, lightCount } = getLightData(this._lights, state.materialXGenContext);
257
- this._lightData = lightData;
258
- this._lightCount = lightCount;
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;
259
223
  }
224
+ this.initialized = false;
225
+ // this.lights = [];
226
+ this.lightData = null;
260
227
  }
261
228
  }
@@ -40,7 +40,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
40
40
  } else {
41
41
  imageHeight = 256; // Final fallback
42
42
  }
43
-
43
+
44
44
  const maxMip = Math.log2(imageHeight) - 2;
45
45
  const cubeUVHeight = imageHeight;
46
46
  const cubeUVWidth = 3 * Math.max(Math.pow(2, maxMip), 7 * 16);
@@ -129,14 +129,14 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
129
129
  const currentAutoClear = renderer.autoClear;
130
130
  const currentXrEnabled = renderer.xr.enabled;
131
131
  const currentShadowMapEnabled = renderer.shadowMap.enabled;
132
-
132
+
133
133
  renderTarget.texture.generateMipmaps = true;
134
-
134
+
135
135
  try {
136
136
  // Disable XR and shadow mapping during our render to avoid interference
137
137
  renderer.xr.enabled = false;
138
138
  renderer.shadowMap.enabled = false;
139
-
139
+
140
140
  // Render to our target
141
141
  renderer.autoClear = true;
142
142
  renderer.setRenderTarget(renderTarget);
@@ -148,7 +148,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
148
148
  renderer.autoClear = currentAutoClear;
149
149
  renderer.xr.enabled = currentXrEnabled;
150
150
  renderer.shadowMap.enabled = currentShadowMapEnabled;
151
-
151
+
152
152
  // Clean up temporary objects
153
153
  geometry.dispose();
154
154
  material.dispose();
@@ -159,7 +159,7 @@ export function renderPMREMToEquirect(renderer: WebGLRenderer, pmremTexture: Tex
159
159
  renderTarget.texture.mapping = EquirectangularReflectionMapping;
160
160
 
161
161
  // Log mipmap infos
162
- if (debug) console.log('[MaterialX] PMREM to Equirect Render Target:', {
162
+ if (debug) console.log('PMREM to Equirect Render Target:', {
163
163
  width: renderTarget.width,
164
164
  height: renderTarget.height,
165
165
  mipmaps: renderTarget.texture.mipmaps?.length,
package/src/utils.ts CHANGED
@@ -2,7 +2,10 @@ import { Context, getParam } from "@needle-tools/engine";
2
2
  import { Mesh } from "three";
3
3
 
4
4
  export const debug = getParam("debugmaterialx");
5
- export const debugUpdate = getParam("debugmaterialxupdate");
5
+
6
+
7
+
8
+
6
9
 
7
10
  /**
8
11
  * =====================================
@@ -30,44 +33,6 @@ const patchWebGL2 = () => {
30
33
  }
31
34
  return uniform4fv.call(this, location, v);
32
35
  };
33
-
34
- const uniform3fv = WebGL2RenderingContext.prototype.uniform3fv;
35
- WebGL2RenderingContext.prototype.uniform3fv = function (location: WebGLUniformLocation | null, v: Float32Array | number[]) {
36
- if (location) {
37
- const uniformName = programAndNameToUniformLocation.get(location);
38
- if (true) console.log("Calling uniform3fv", { location, v, name: uniformName?.name });
39
- }
40
- return uniform3fv.call(this, location, v);
41
- };
42
-
43
- const uniform3iv = WebGL2RenderingContext.prototype.uniform3iv;
44
- WebGL2RenderingContext.prototype.uniform3iv = function (location: WebGLUniformLocation | null, v: Int32Array | number[]) {
45
- if (location) {
46
- const uniformName = programAndNameToUniformLocation.get(location);
47
- if (true) console.log("Calling uniform3iv", { location, v, name: uniformName?.name });
48
- }
49
- return uniform3iv.call(this, location, v);
50
- };
51
-
52
- const uniform3uiv = WebGL2RenderingContext.prototype.uniform3uiv;
53
- WebGL2RenderingContext.prototype.uniform3uiv = function (location: WebGLUniformLocation | null, v: Uint32Array | number[]) {
54
- if (location) {
55
- const uniformName = programAndNameToUniformLocation.get(location);
56
- if (true) console.log("Calling uniform3uiv", { location, v, name: uniformName?.name });
57
- }
58
- return uniform3uiv.call(this, location, v);
59
- };
60
-
61
- const uniform3f = WebGL2RenderingContext.prototype.uniform3f;
62
- WebGL2RenderingContext.prototype.uniform3f = function (location: WebGLUniformLocation
63
- | null, x: number, y: number, z: number) {
64
- if (location) {
65
- const uniformName = programAndNameToUniformLocation.get(location);
66
- if (uniformName?.name !== "diffuse")
67
- if (true) console.log("Calling uniform3f", { location, x, y, z, name: uniformName?.name });
68
- }
69
- return uniform3f.call(this, location, x, y, z);
70
- };
71
36
  };
72
37
  // patchWebGL2();
73
38