@needle-tools/materialx 1.0.4-next.6620f9d → 1.0.4-next.90b66fb

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/index.ts CHANGED
@@ -2,4 +2,4 @@ import { registerNeedleLoader } from "./src/loader/loader.needle.js";
2
2
 
3
3
  registerNeedleLoader();
4
4
 
5
- export { ready, getMaterialXEnvironment } from "./src/index.js";
5
+ export * from "./src/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/materialx",
3
- "version": "1.0.4-next.6620f9d",
3
+ "version": "1.0.4-next.90b66fb",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "exports": {
@@ -17,8 +17,7 @@
17
17
  "devDependencies": {
18
18
  "@needle-tools/engine": "4.x",
19
19
  "@types/three": "0.169.0",
20
- "three": "npm:@needle-tools/three@^0.169.5",
21
- "vite": "^7.0.3"
20
+ "three": "npm:@needle-tools/three@^0.169.5"
22
21
  },
23
22
  "publishConfig": {
24
23
  "access": "public",
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { ready, state } from "./materialx.js";
2
-
3
- const getMaterialXEnvironment = () => state.materialXEnvironment;
4
-
5
- export { ready, getMaterialXEnvironment };
1
+ export { ready, type MaterialXContext } from "./materialx.js";
2
+ export { MaterialXMaterial } from "./materialx.material.js";
3
+ export { MaterialXLoader } from "./loader/loader.three.js";
4
+ export { registerNeedleLoader } from "./loader/loader.needle.js";
@@ -1,46 +1,10 @@
1
1
 
2
2
 
3
- import { Group, Camera, Material, Mesh, Object3D } from "three";
4
- import { Context, GLTF, addCustomExtensionPlugin, Component, INeedleGLTFExtensionPlugin } from "@needle-tools/engine";
3
+ import { Context, GLTF, addCustomExtensionPlugin, INeedleGLTFExtensionPlugin } from "@needle-tools/engine";
5
4
  import type { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
6
5
  import type { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
7
6
  import { MaterialXLoader } from "./loader.three.js";
8
7
  import { debug } from "../utils.js";
9
- import { MaterialXEnvironment, state } from "../materialx.js";
10
- import { MaterialXMaterial } from "../materialx.material.js";
11
-
12
- //@dont-generate-component
13
- export class MaterialXUniformUpdate extends Component {
14
-
15
- onEnable(): void {
16
- this.context.addBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
17
- }
18
-
19
- onDisable(): void {
20
- this.context.removeBeforeRenderListener(this.gameObject, this.onBeforeRenderThree);
21
- }
22
-
23
- onBeforeRenderThree = () => {
24
- // Update uniforms or perform any pre-render logic here
25
- const gameObject = this.gameObject as any as Mesh;
26
- const material = gameObject?.material;
27
-
28
- const camera = this.context.mainCamera;
29
- if (!camera) return;
30
-
31
- const env = state.materialXEnvironment;
32
-
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);
41
- }
42
- }
43
- }
44
8
 
45
9
  export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
46
10
  readonly name = "MaterialXLoaderPlugin";
@@ -53,7 +17,12 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
53
17
  // Register the MaterialX loader extension
54
18
  // Environment initialization is now handled in the MaterialXLoader constructor
55
19
  loader.register(p => {
56
- this.loader = new MaterialXLoader(p, url, context);
20
+ this.loader = new MaterialXLoader(p, url, {
21
+ getTime: () => context.time.time,
22
+ getFrame: ()=> context.time.frame,
23
+ getScene: () => context.scene,
24
+ getRenderer: () => context.renderer,
25
+ });
57
26
  return this.loader;
58
27
  });
59
28
  };
@@ -65,25 +34,14 @@ export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
65
34
  if (!this.loader?.materialX_root_data) {
66
35
  return;
67
36
  }
68
- // Set up onBeforeRender callbacks for objects with MaterialX materials
69
- // This ensures uniforms are updated properly during rendering
70
- gltf.scene.traverse((child) => {
71
- if ((child as any).isMesh) {
72
- const mesh = child as Mesh;
73
- const material = mesh.material as Material;
74
-
75
- if (material instanceof MaterialXMaterial) {
76
- if (debug) console.log("[MaterialX] Adding MaterialX uniform update component to:", child.name);
77
- child.addComponent(MaterialXUniformUpdate);
78
- }
79
- }
80
- });
81
37
 
82
38
  if (debug) console.log("[MaterialX] Loaded: ", this.loader);
39
+
40
+
83
41
  };
84
42
 
85
43
  onExport = (_exporter: GLTFExporter, _context: Context) => {
86
- console.log("[MaterialX] TODO: MaterialXLoaderPlugin: Setting up export extensions");
44
+ console.warn("[MaterialX] Export is not supported");
87
45
  // TODO: Add MaterialX export functionality if needed
88
46
  };
89
47
  }
@@ -1,7 +1,6 @@
1
- import { Context } from "@needle-tools/engine";
2
- import { Material, MeshStandardMaterial, Texture, NearestFilter, CompressedTexture, DoubleSide, FrontSide } from "three";
3
- import { GLTF, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
4
- import { ready, MaterialXEnvironment, state } from "../materialx.js";
1
+ import { Material, MeshStandardMaterial, DoubleSide, FrontSide } from "three";
2
+ import { GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
3
+ import { ready, state, MaterialXContext } from "../materialx.js";
5
4
  import { debug } from "../utils.js";
6
5
  import { MaterialXMaterial } from "../materialx.material.js";
7
6
 
@@ -52,7 +51,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
52
51
  * @param url The URL of the GLTF file
53
52
  * @param context The context for the GLTF loading process
54
53
  */
55
- constructor(private parser: GLTFParser, private url: string, private context: Context) {
54
+ constructor(private parser: GLTFParser, private url: string, private context: MaterialXContext) {
56
55
  if (debug) console.log("MaterialXLoader created for parser");
57
56
  // Start loading of MaterialX environment if the root extension exists
58
57
  if (this.materialX_root_data) {
@@ -69,12 +68,6 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
69
68
  return this._loadMaterialAsync(materialIndex);
70
69
  }
71
70
 
72
- afterRoot = async (_gltf: GLTF) => {
73
- // Initialize MaterialX lighting system with scene data
74
- const environment = state.materialXEnvironment;
75
- environment.initialize(this.context);
76
- }
77
-
78
71
  // Parse the MaterialX document once and cache it
79
72
  private async _materialXDocumentReady(): Promise<any> {
80
73
  if (this._documentReadyPromise) {
@@ -238,6 +231,7 @@ export class MaterialXLoader implements GLTFLoaderPlugin {
238
231
  shader,
239
232
  transparent: isTransparent,
240
233
  side: material_def.doubleSided ? DoubleSide : FrontSide,
234
+ context: this.context,
241
235
  loaders: {
242
236
  cacheKey: this.url,
243
237
  getTexture: async url => {
@@ -3,7 +3,7 @@
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  //
5
5
 
6
- import { getParam, getWorldDirection } from '@needle-tools/engine';
6
+ import { getWorldDirection } from '@needle-tools/engine';
7
7
  import * as THREE from 'three';
8
8
  import { debug, debugUpdate } from './utils';
9
9
  import { MaterialX } from './materialx.types';
@@ -216,7 +216,7 @@ function toThreeUniform(uniforms: any, type: string, value: any, name: string, l
216
216
  const key = type + ':' + name;
217
217
  if (!valueTypeWarningMap.has(key)) {
218
218
  valueTypeWarningMap.set(key, true);
219
- console.warn('MaterialX: Unsupported uniform type: ' + type + ' for uniform: ' + name, value);
219
+ console.warn(`MaterialX: Unsupported uniform type: ${type} for uniform: ${name}`);
220
220
  }
221
221
  break;
222
222
  }
@@ -469,7 +469,7 @@ export function getUniformValues(shaderStage: MaterialX.ShaderStage, loaders: Lo
469
469
  const value = variable.getValue()?.getData();
470
470
  const name = variable.getVariable();
471
471
  if (debug) console.log("Adding uniform", { path: variable.getPath(), name: name, value: value, type: variable.getType().getName() });
472
- threeUniforms[name] = toThreeUniform(uniforms, variable.getType().getName(), value, name, loaders, searchPath);;
472
+ threeUniforms[name] = toThreeUniform(uniforms, variable.getType().getName(), value, name, loaders, searchPath);
473
473
  }
474
474
  }
475
475
  }
@@ -1,8 +1,7 @@
1
- import { Camera, DoubleSide, FrontSide, GLSL3, MaterialParameters, Matrix3, Matrix4, Object3D, ShaderMaterial, Texture, Vector3 } from "three";
1
+ import { BufferGeometry, Camera, FrontSide, GLSL3, Group, IUniform, MaterialParameters, Matrix3, Matrix4, Object3D, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
2
2
  import { debug } from "./utils.js";
3
- import { MaterialXEnvironment } from "./materialx.js";
4
- import { getLightData, getUniformValues, Loaders } from "./materialx.helper.js";
5
- import { Context } from "@needle-tools/engine";
3
+ import { MaterialXContext, MaterialXEnvironment } from "./materialx.js";
4
+ import { getUniformValues, Loaders } from "./materialx.helper.js";
6
5
 
7
6
 
8
7
  // Add helper matrices for uniform updates (similar to MaterialX example)
@@ -17,19 +16,20 @@ declare type MaterialXMaterialInitParameters = {
17
16
  loaders: Loaders,
18
17
  transparent?: boolean,
19
18
  side?: MaterialParameters['side'],
19
+ context: MaterialXContext,
20
20
  }
21
21
 
22
+ type Uniforms = Record<string, IUniform & { needsUpdate?: boolean }>;
23
+
22
24
  export class MaterialXMaterial extends ShaderMaterial {
23
25
 
24
- // copy(source: MaterialXMaterial): this {
25
- // super.copy(source);
26
- // this.name = source.name;
27
- // this.uniforms = { ...source.uniforms }; // Shallow copy of uniforms
28
- // this.envMapIntensity = source.envMapIntensity;
29
- // this.envMap = source.envMap;
30
- // this.updateUniforms = source.updateUniforms; // Copy the update function
31
- // return this;
32
- // }
26
+ copy(source: MaterialXMaterial): this {
27
+ super.copy(source);
28
+ this._context = source._context;
29
+ return this;
30
+ }
31
+
32
+ private _context: MaterialXContext | null = null;
33
33
 
34
34
  constructor(init?: MaterialXMaterialInitParameters) {
35
35
 
@@ -114,6 +114,12 @@ export class MaterialXMaterial extends ShaderMaterial {
114
114
  Object.assign(this.uniforms, {
115
115
  ...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath),
116
116
  ...getUniformValues(init.shader.getStage('pixel'), init.loaders, searchPath),
117
+
118
+ u_worldMatrix: { value: new Matrix4() },
119
+ u_viewProjectionMatrix: { value: new Matrix4() },
120
+ u_viewPosition: { value: new Vector3() },
121
+ u_worldInverseTransposeMatrix: { value: new Matrix4() },
122
+
117
123
  u_envMatrix: { value: new Matrix4() },
118
124
  u_envRadiance: { value: null, type: 't' },
119
125
  u_envRadianceMips: { value: 8, type: 'i' },
@@ -122,9 +128,11 @@ export class MaterialXMaterial extends ShaderMaterial {
122
128
  u_envIrradiance: { value: null, type: 't' },
123
129
  u_refractionEnv: { value: true },
124
130
  u_numActiveLightSources: { value: 0 },
125
- u_lightData: { value: [], needsUpdate: false }, // Array of light data
131
+ u_lightData: { value: [], needsUpdate: false }, // Array of light data. We need to set needsUpdate to false until we actually update it
126
132
  });
127
133
 
134
+ this._context = init.context;
135
+
128
136
  if (debug) {
129
137
  // Get lighting and environment data from MaterialX environment
130
138
  console.group("[MaterialX]: ", name);
@@ -132,15 +140,24 @@ export class MaterialXMaterial extends ShaderMaterial {
132
140
  console.log("Fragment shader length:", fragmentShader.length, fragmentShader);
133
141
  console.groupEnd();
134
142
  }
143
+ }
135
144
 
145
+ onBeforeRender(_renderer: WebGLRenderer, _scene: Scene, camera: Camera, _geometry: BufferGeometry, object: Object3D, _group: Group): void {
146
+ if (this._context) {
147
+ const env = MaterialXEnvironment.get(this._context);
148
+ if (env) {
149
+ env.update(this._context.getFrame(), this._context.getScene());
150
+ this.updateUniforms(env, object, camera);
151
+ }
152
+ }
136
153
  }
137
154
 
138
155
 
139
156
  envMapIntensity: number = 1.0; // Default intensity for environment map
140
157
  envMap: Texture | null = null; // Environment map texture, can be set externally
141
- updateUniforms = (context: Context, environment: MaterialXEnvironment, object: Object3D, camera: Camera) => {
158
+ updateUniforms = (environment: MaterialXEnvironment, object: Object3D, camera: Camera) => {
142
159
 
143
- const uniforms = this.uniforms;
160
+ const uniforms = this.uniforms as Uniforms;
144
161
 
145
162
  // TODO remove. Not sure why this is needed, but without it
146
163
  // we currently get some "swimming" where matrices are not up to date.
@@ -148,31 +165,33 @@ export class MaterialXMaterial extends ShaderMaterial {
148
165
 
149
166
  // Update standard transformation matrices
150
167
  if (uniforms.u_worldMatrix) {
151
- if (!uniforms.u_worldMatrix.value?.isMatrix4) uniforms.u_worldMatrix.value = new Matrix4();
152
168
  uniforms.u_worldMatrix.value = object.matrixWorld;
169
+ uniforms.u_worldMatrix.needsUpdate = true;
153
170
  }
154
171
 
155
172
  if (uniforms.u_viewProjectionMatrix) {
156
- if (!uniforms.u_viewProjectionMatrix.value?.isMatrix4) uniforms.u_viewProjectionMatrix.value = new Matrix4();
157
173
  uniforms.u_viewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
174
+ uniforms.u_viewProjectionMatrix.needsUpdate = true;
158
175
  }
159
176
 
160
177
  if (uniforms.u_viewPosition) {
161
- if (!uniforms.u_viewPosition.value?.isVector3) uniforms.u_viewPosition.value = new Vector3();
162
178
  uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
179
+ uniforms.u_viewPosition.needsUpdate = true;
163
180
  }
164
181
 
165
182
  if (uniforms.u_worldInverseTransposeMatrix) {
166
- if (!uniforms.u_worldInverseTransposeMatrix.value?.isMatrix4) uniforms.u_worldInverseTransposeMatrix.value = new Matrix4();
167
183
  uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
184
+ uniforms.u_worldInverseTransposeMatrix.needsUpdate = true;
168
185
  }
169
186
 
170
187
  // Update time uniforms
171
- if (uniforms.u_time) {
172
- uniforms.u_time.value = context.time.time;
173
- }
174
- if (uniforms.u_frame) {
175
- uniforms.u_frame.value = context.time.frame;
188
+ if (this._context) {
189
+ if (uniforms.u_time) {
190
+ uniforms.u_time.value = this._context.getTime();
191
+ }
192
+ if (uniforms.u_frame) {
193
+ uniforms.u_frame.value = this._context.getFrame();
194
+ }
176
195
  }
177
196
 
178
197
  // Update light uniforms
@@ -183,37 +202,40 @@ export class MaterialXMaterial extends ShaderMaterial {
183
202
 
184
203
  private updateEnvironmentUniforms = (environment: MaterialXEnvironment) => {
185
204
 
205
+ const uniforms = this.uniforms as Uniforms;
206
+
186
207
  // Get lighting data from environment
187
208
  const lightData = environment.lightData || null;
188
209
  const lightCount = environment.lightCount || 0;
189
210
  const textures = environment.getTextures(this) || null;
190
211
 
191
212
  // Update light count
192
- if (this.uniforms.u_numActiveLightSources && lightCount >= 0) {
193
- this.uniforms.u_numActiveLightSources.value = lightCount;
213
+ if (uniforms.u_numActiveLightSources && lightCount >= 0) {
214
+ uniforms.u_numActiveLightSources.value = lightCount;
194
215
  }
195
216
 
196
217
  // Update light data
197
218
  if (lightData?.length) {
198
- this.uniforms.u_lightData.value = lightData;
199
- if ("needsUpdate" in this.uniforms.u_lightData && this.uniforms.u_lightData.needsUpdate === false) {
219
+ uniforms.u_lightData.value = lightData;
220
+ if ("needsUpdate" in uniforms.u_lightData && uniforms.u_lightData.needsUpdate === false) {
200
221
  if (debug) console.debug(`[MaterialX] LightData assigned (${this.name}, ${this.uuid})`, lightData);
201
- this.uniforms.u_lightData.needsUpdate = undefined;
222
+ uniforms.u_lightData.needsUpdate = undefined;
202
223
  }
203
224
  }
204
225
 
205
226
  // Update environment uniforms
206
- if (this.uniforms.u_envMatrix) {
207
- this.uniforms.u_envMatrix.value = identityMatrix;
208
- }
209
- if (this.uniforms.u_envRadiance) {
210
- this.uniforms.u_envRadiance.value = textures.radianceTexture;
227
+ if (uniforms.u_envRadiance) {
228
+ const prev = uniforms.u_envRadiance.value;
229
+ uniforms.u_envRadiance.value = textures.radianceTexture;
230
+ if (prev != textures.radianceTexture) uniforms.u_envRadiance.needsUpdate = true;
211
231
  }
212
- if (this.uniforms.u_envRadianceMips) {
213
- this.uniforms.u_envRadianceMips.value = Math.trunc(Math.log2(Math.max(textures.radianceTexture?.source.data.width ?? 0, textures.radianceTexture?.source.data.height ?? 0))) + 1;
232
+ if (uniforms.u_envRadianceMips) {
233
+ uniforms.u_envRadianceMips.value = Math.trunc(Math.log2(Math.max(textures.radianceTexture?.source.data.width ?? 0, textures.radianceTexture?.source.data.height ?? 0))) + 1;
214
234
  }
215
- if (this.uniforms.u_envIrradiance) {
216
- this.uniforms.u_envIrradiance.value = textures.irradianceTexture;
235
+ if (uniforms.u_envIrradiance) {
236
+ const prev = uniforms.u_envIrradiance.value;
237
+ uniforms.u_envIrradiance.value = textures.irradianceTexture;
238
+ if (prev != textures.irradianceTexture) uniforms.u_envIrradiance.needsUpdate = true;
217
239
  }
218
240
 
219
241
  this.uniformsNeedUpdate = true;
package/src/materialx.ts CHANGED
@@ -1,12 +1,18 @@
1
- import { Context, delay, isDevEnvironment, ObjectUtils, GameObject, onBeforeRender } from "@needle-tools/engine";
2
1
  import type { MaterialX as MX } from "./materialx.types.js";
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";
5
+ import { Light, Object3D, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
7
6
  import { registerLights, getLightData, LightData } from "./materialx.helper.js";
8
7
  import type { MaterialXMaterial } from "./materialx.material.js";
9
8
 
9
+ export type MaterialXContext = {
10
+ getTime(): number,
11
+ getFrame(): number,
12
+ getScene(): Scene,
13
+ getRenderer(): WebGLRenderer,
14
+ }
15
+
10
16
 
11
17
  export const state = new class {
12
18
  materialXModule: MX.MODULE | null = null;
@@ -14,16 +20,12 @@ export const state = new class {
14
20
  materialXGenContext: any = null;
15
21
  materialXStdLib: any = null;
16
22
  materialXInitPromise: Promise<void> | null = null;
17
-
18
- // Global MaterialX environment instance
19
- private _materialXEnvironment: MaterialXEnvironment | null = null;
20
- get materialXEnvironment() {
21
- this._materialXEnvironment ??= new MaterialXEnvironment();
22
- return this._materialXEnvironment;
23
- }
24
23
  }
25
24
 
26
- /** Initialize the MaterialX module. Must be awaited before trying to create materials */
25
+
26
+ /**
27
+ * Wait for the MaterialX WASM module to be ready.
28
+ */
27
29
  export async function ready(): Promise<void> {
28
30
  if (state.materialXInitPromise) {
29
31
  return state.materialXInitPromise;
@@ -111,93 +113,72 @@ type EnvironmentTextureSet = {
111
113
  irradianceTexture: Texture | null;
112
114
  }
113
115
 
114
- // MaterialX Environment Manager - handles lighting and environment setup
116
+
117
+
118
+ type EnvironmentContext = Pick<MaterialXContext, "getRenderer" | "getScene">;
119
+ /**
120
+ * MaterialXEnvironment manages the environment settings for MaterialX materials.
121
+ */
115
122
  export class MaterialXEnvironment {
116
- private _context: Context | null = null;
123
+
124
+ static get(context: EnvironmentContext): MaterialXEnvironment | null {
125
+ return this.getEnvironment(context);
126
+ }
127
+ private static _environments: WeakMap<EnvironmentContext, MaterialXEnvironment> = new Map();
128
+ private static getEnvironment(context: EnvironmentContext): MaterialXEnvironment {
129
+ if (this._environments.has(context)) {
130
+ return this._environments.get(context)!;
131
+ }
132
+ const env = new MaterialXEnvironment(context);
133
+ this._environments.set(context, env);
134
+ return env;
135
+ }
136
+
137
+
117
138
  private _lights: Array<Light> = [];
118
139
  private _lightData: null | LightData[] = null;
119
140
  private _lightCount: number = 0;
120
- private _initializePromise: Promise<boolean> | null = null;
121
141
 
122
- private _unsubscribehook: (() => void) | null = null;
142
+ private _initializePromise: Promise<boolean> | null = null;
143
+ private _isInitialized: boolean = false;
144
+ private _lastUpdateFrame: number = -1;
123
145
 
124
- constructor() {
146
+ constructor(private _context: EnvironmentContext) {
125
147
  if (debug) console.log("[MaterialX] Environment created");
126
148
  }
127
149
 
128
150
  // Initialize with Needle Engine context
129
- async initialize(context: Context): Promise<boolean> {
151
+ async initialize(): Promise<boolean> {
130
152
  if (this._initializePromise) {
131
153
  return this._initializePromise;
132
154
  }
133
- return this._initializePromise = this._initialize(context);
155
+ this._initializePromise = this._initialize();
156
+ return this._initializePromise;
134
157
  }
135
158
 
136
- get lightData() {
137
- // if (this._lightData === null) {
138
- // if(debug) console.warn("[MaterialX] Light data is not initialized, updating lighting");
139
- // this.updateLighting(true);
140
- // }
141
- return this._lightData;
142
- }
143
- get lightCount() { return this._lightCount || 0; }
144
- getTextures(material: MaterialXMaterial) {
145
- if (material.envMap) {
146
- // If the material has its own envMap, we don't use the irradiance texture
147
- return this._getTextures(material.envMap);
159
+ update(frame: number, scene: Scene) {
160
+ if (!this._initializePromise) {
161
+ this.initialize();
162
+ return;
148
163
  }
149
- return this._getTextures(this._context?.scene.environment);
150
- }
151
-
152
- private _pmremGenerator: PMREMGenerator | null = null;
153
- private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
154
-
155
- private _initialize: (context: Context) => Promise<boolean> = async (context: Context) => {
156
-
157
- this._context = context;
158
- this._pmremGenerator = new PMREMGenerator(context.renderer);
159
-
160
- this._unsubscribehook?.();
161
- this._unsubscribehook = onBeforeRender(() => {
162
- this.updateLighting(false);
163
- this._getTextures(context.scene.environment);
164
- })
165
-
166
- // TODO remove this delay; we should wait for the scene lighting to be ready
167
- // and then update the uniforms
168
- while (!context.scene.environment) {
169
- await delay(5);
164
+ if (!this._isInitialized) {
165
+ return;
170
166
  }
171
- this._getTextures(context.scene.environment);
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
- // unlitMat.side = 2;
178
- // const radianceMat = unlitMat.clone();
179
- // radianceMat.map = this._radianceTexture;
180
- // const radianceCube = ObjectUtils.createPrimitive("Quad", { material: radianceMat });
181
- // const irradianceMat = unlitMat.clone();
182
- // irradianceMat.map = this._irradianceTexture;
183
- // const irradianceCube = ObjectUtils.createPrimitive("Quad", { material: irradianceMat });
184
- // context.scene.add(radianceCube);
185
- // context.scene.add(irradianceCube);
186
- // radianceCube.position.set(2, 0, 0);
187
- // irradianceCube.position.set(-2, 0, 0);
188
- // console.log("[MaterialX] environment initialized from Needle context", this, this._context.scene);
189
- // }
190
-
191
- this.updateLighting(true);
192
-
193
- // Mark as initialized
194
- return true;
167
+ if (this._lastUpdateFrame === frame) {
168
+ // Already updated this frame
169
+ return;
170
+ }
171
+ this._lastUpdateFrame = frame;
172
+ this.updateLighting(false);
173
+ this._getTextures(scene.environment);
195
174
  }
196
175
 
197
176
  // Reset the environment to allow re-initialization
198
177
  reset() {
199
178
  if (debug) console.log("[MaterialX] Resetting environment");
200
179
  this._initializePromise = null;
180
+ this._isInitialized = false;
181
+ this._lastUpdateFrame = -1;
201
182
  this._lights = [];
202
183
  this._lightData = null;
203
184
  this._lightCount = 0;
@@ -208,16 +189,35 @@ export class MaterialXEnvironment {
208
189
  textureSet.irradianceTexture?.dispose();
209
190
  }
210
191
  this._texturesCache.clear();
192
+ }
193
+
194
+ get lightData() {
195
+ return this._lightData;
196
+ }
197
+ get lightCount() { return this._lightCount || 0; }
198
+ getTextures(material: MaterialXMaterial) {
199
+ if (material.envMap) {
200
+ // If the material has its own envMap, we don't use the irradiance texture
201
+ return this._getTextures(material.envMap);
202
+ }
203
+ return this._getTextures(this._context?.getScene().environment);
204
+ }
205
+
206
+ private _pmremGenerator: PMREMGenerator | null = null;
207
+ private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
211
208
 
212
- this._unsubscribehook?.();
213
- this._unsubscribehook = null;
209
+ private async _initialize(): Promise<boolean> {
210
+ this._isInitialized = false;
211
+ this._pmremGenerator = new PMREMGenerator(this._context.getRenderer());
212
+ this.updateLighting(true);
213
+ this._isInitialized = true;
214
+ return true;
214
215
  }
215
216
 
216
217
  private _getTextures(texture: Texture | null | undefined): {
217
218
  radianceTexture: Texture | null,
218
219
  irradianceTexture: Texture | null
219
220
  } {
220
-
221
221
  let res: EnvironmentTextureSet | undefined = this._texturesCache.get(texture || null);
222
222
  if (res) {
223
223
  return res;
@@ -225,10 +225,9 @@ export class MaterialXEnvironment {
225
225
 
226
226
  if (this._context && this._pmremGenerator && texture) {
227
227
  if (debug) console.log("[MaterialX] Generating environment textures", texture.name);
228
-
229
228
  const target = this._pmremGenerator.fromEquirectangular(texture);
230
- const radianceRenderTarget = renderPMREMToEquirect(this._context.renderer, target.texture, 0.0, 1024, 512, target.height);
231
- const irradianceRenderTarget = renderPMREMToEquirect(this._context.renderer, target.texture, 1.0, 32, 16, target.height);
229
+ const radianceRenderTarget = renderPMREMToEquirect(this._context.getRenderer(), target.texture, 0.0, 1024, 512, target.height);
230
+ const irradianceRenderTarget = renderPMREMToEquirect(this._context.getRenderer(), target.texture, 1.0, 32, 16, target.height);
232
231
  target.dispose();
233
232
  res = {
234
233
  radianceTexture: radianceRenderTarget.texture,
@@ -247,12 +246,11 @@ export class MaterialXEnvironment {
247
246
 
248
247
  private updateLighting = (collectLights: boolean = false) => {
249
248
  if (!this._context) return;
250
-
251
249
  // Find lights in scene
252
250
  if (collectLights) {
253
251
  const lights = new Array<Light>();
254
- this._context.scene.traverse((object: Object3D) => {
255
- if ((object as Light).isLight && GameObject.isActiveInHierarchy(object))
252
+ this._context.getScene().traverse((object: Object3D) => {
253
+ if ((object as Light).isLight && object.visible)
256
254
  lights.push(object as Light);
257
255
  });
258
256
  this._lights = lights;