@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.
package/src/materialx.ts CHANGED
@@ -1,15 +1,13 @@
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, GameObject } 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, Texture } from "three";
6
+ import { registerLights } from "./helper.js";
9
7
 
10
8
 
11
9
  export const state = new class {
12
- materialXModule: MX.MODULE | null = null;
10
+ materialXModule: typeof MaterialX | null = null;
13
11
  materialXGenerator: any = null;
14
12
  materialXGenContext: any = null;
15
13
  materialXStdLib: any = null;
@@ -61,7 +59,7 @@ export async function ready(): Promise<void> {
61
59
  },
62
60
  });
63
61
  if (debug) console.log("[MaterialX] module loaded", module);
64
- state.materialXModule = module as MX.MODULE
62
+ state.materialXModule = module;
65
63
 
66
64
  // Initialize shader generator and context
67
65
  state.materialXGenerator = module.EsslShaderGenerator.create();
@@ -73,6 +71,7 @@ export async function ready(): Promise<void> {
73
71
  tempDoc.setDataLibrary(state.materialXStdLib);
74
72
 
75
73
  // TODO ShaderInterfaceType.SHADER_INTERFACE_REDUCED would be better, but doesn't actually seem to be supported in the MaterialX javascript bindings
74
+ const options = state.materialXGenContext.getOptions();
76
75
  state.materialXGenContext.getOptions().shaderInterfaceType = state.materialXModule.ShaderInterfaceType.SHADER_INTERFACE_COMPLETE;
77
76
 
78
77
  // SPECULAR_ENVIRONMENT_NONE: Do not use specular environment maps.
@@ -96,7 +95,7 @@ export async function ready(): Promise<void> {
96
95
  state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
97
96
 
98
97
  // This prewarms the shader generation context to have all light types
99
- await registerLights(state.materialXModule, state.materialXGenContext);
98
+ await registerLights(state.materialXModule, [], state.materialXGenContext);
100
99
 
101
100
  if (debug) console.log("[MaterialX] generator initialized successfully");
102
101
  } catch (error) {
@@ -106,156 +105,124 @@ export async function ready(): Promise<void> {
106
105
  })();
107
106
  }
108
107
 
109
- type EnvironmentTextureSet = {
110
- radianceTexture: Texture | null;
111
- irradianceTexture: Texture | null;
112
- }
113
-
114
108
  // MaterialX Environment Manager - handles lighting and environment setup
115
109
  export class MaterialXEnvironment {
116
110
  private _context: Context | null = null;
117
- private _lights: Array<Light> = [];
118
111
  private _lightData: any = null;
119
112
  private _lightCount: number = 0;
113
+ private _radianceTexture: Texture | null = null;
114
+ private _irradianceTexture: Texture | null = null;
120
115
  private _initializePromise: Promise<boolean> | null = null;
121
116
 
122
- private _unsubscribehook: (() => void) | null = null;
123
-
124
117
  constructor() {
125
118
  if (debug) console.log("[MaterialX] Environment created");
126
119
  }
127
120
 
128
121
  // Initialize with Needle Engine context
129
- async initialize(context: Context): Promise<boolean> {
122
+ async initializeFromContext(context: Context): Promise<boolean> {
123
+
124
+ // Prevent multiple initializations
130
125
  if (this._initializePromise) {
126
+ if (debug) console.log("[MaterialX] environment already initialized, skipping");
131
127
  return this._initializePromise;
132
128
  }
133
- return this._initializePromise = this._initialize(context);
134
- }
135
129
 
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);
142
- }
143
- return this._getTextures(this._context?.scene.environment);
130
+ return this._initializePromise = this._initialize(context);
144
131
  }
145
132
 
146
- private _pmremGenerator: PMREMGenerator | null = null;
147
- private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
133
+ getLightData() { return this._lightData; }
134
+ getLightCount() { return this._lightCount; }
135
+
136
+ setRadianceTexture(texture: Texture) { this._radianceTexture = texture; }
137
+ getRadianceTexture() { return this._radianceTexture; }
148
138
 
149
- private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
150
-
151
- this._context = context;
152
- this._pmremGenerator = new PMREMGenerator(context.renderer);
153
-
154
- this._unsubscribehook?.();
155
- this._unsubscribehook = onBeforeRender(() => {
156
- this.updateLighting(false);
157
- this._getTextures(context.scene.environment);
158
- })
159
-
160
- // TODO remove this delay; we should wait for the scene lighting to be ready
161
- // and then update the uniforms
162
- while (!context.scene.environment) {
163
- await delay(5);
164
- }
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
- }
139
+ getIrradianceTexture() { return this._irradianceTexture; }
140
+ setIrradianceTexture(texture: Texture) { this._irradianceTexture = texture; }
190
141
 
191
142
  // Reset the environment to allow re-initialization
192
143
  reset() {
193
144
  if (debug) console.log("[MaterialX] Resetting environment");
145
+ if (this._radianceTexture) {
146
+ this._radianceTexture.dispose();
147
+ this._radianceTexture = null;
148
+ }
149
+ if (this._irradianceTexture) {
150
+ this._irradianceTexture.dispose();
151
+ this._irradianceTexture = null;
152
+ }
194
153
  this._initializePromise = null;
195
- this._lights = [];
154
+ // this.lights = [];
196
155
  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();
203
- }
204
- this._texturesCache.clear();
205
-
206
- this._unsubscribehook?.();
207
- this._unsubscribehook = null;
208
156
  }
209
157
 
210
- private _getTextures(texture: Texture | null | undefined): {
211
- radianceTexture: Texture | null,
212
- irradianceTexture: Texture | null
213
- } {
158
+ private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
214
159
 
215
- let res: EnvironmentTextureSet | undefined = this._texturesCache.get(texture || null);
216
- if (res) {
217
- return res;
218
- }
160
+ this._context = context;
219
161
 
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
- }
162
+ // Clean up previous textures if they exist
163
+ if (this._radianceTexture) {
164
+ if (debug) console.log("[MaterialX] Disposing previous radiance texture");
165
+ this._radianceTexture.dispose();
166
+ this._radianceTexture = null;
231
167
  }
232
- else {
233
- res = {
234
- radianceTexture: null,
235
- irradianceTexture: null
236
- }
168
+ if (this._irradianceTexture) {
169
+ if (debug) console.log("[MaterialX] Disposing previous irradiance texture");
170
+ this._irradianceTexture.dispose();
171
+ this._irradianceTexture = null;
237
172
  }
238
- this._texturesCache.set(texture || null, res);
239
- return res;
240
- }
241
173
 
242
- private updateLighting = (collectLights: boolean = false) => {
243
- if (!this._context) return;
174
+ // Get renderer from context
175
+ const renderer = this._context.renderer;
244
176
 
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;
177
+ // TODO remove this delay; we should wait for the scene lighting to be ready
178
+ // and then update the uniforms
179
+ let envMap = this._context.scene.environment;
180
+ while (!envMap) {
181
+ await delay(200);
182
+ envMap = this._context.scene.environment;
253
183
  }
254
-
255
- if (state.materialXGenContext) {
256
- const { lightData, lightCount } = getLightData(this._lights, state.materialXGenContext);
257
- this._lightData = lightData;
258
- this._lightCount = lightCount;
184
+ var pmrem = new PMREMGenerator(renderer);
185
+ const target = pmrem.fromEquirectangular(envMap);
186
+
187
+ const radianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 0.0, 1024, 512, target.height);
188
+ const irradianceRenderTarget = renderPMREMToEquirect(renderer, target.texture, 1.0, 32, 16, target.height);
189
+
190
+ this._radianceTexture = radianceRenderTarget.texture;
191
+ this._irradianceTexture = irradianceRenderTarget.texture;
192
+
193
+ // Clean up PMREM generator and its render target
194
+ target.dispose();
195
+ pmrem.dispose();
196
+
197
+ if (debug) {
198
+ console.log({ radiance: this._radianceTexture, irradiance: this._irradianceTexture });
199
+ // Show both of them on cubes in the scene
200
+ const unlitMat = new MeshBasicMaterial();
201
+ unlitMat.side = 2;
202
+ const radianceMat = unlitMat.clone();
203
+ radianceMat.map = this._radianceTexture;
204
+ const radianceCube = ObjectUtils.createPrimitive("Quad", { material: radianceMat });
205
+ const irradianceMat = unlitMat.clone();
206
+ irradianceMat.map = this._irradianceTexture;
207
+ const irradianceCube = ObjectUtils.createPrimitive("Quad", { material: irradianceMat });
208
+ this._context.scene.add(radianceCube);
209
+ this._context.scene.add(irradianceCube);
210
+ radianceCube.position.set(2, 0, 0);
211
+ irradianceCube.position.set(-2, 0, 0);
212
+ // await this.initializeLighting(defaultLightRigXml, renderer);
213
+ console.log("[MaterialX] environment initialized from Needle context", this, this._context.scene);
259
214
  }
215
+
216
+ // Find lights in scene
217
+ let lights = new Array<Light>();
218
+ this._context.scene.traverse((object: Object3D) => {
219
+ if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
220
+ lights.push(object as Light);
221
+ });
222
+
223
+ const { lightData, lightCount } = await registerLights(state.materialXModule, lights, state.materialXGenContext);
224
+ this._lightData = lightData;
225
+ this._lightCount = lightCount;
226
+ return true;
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