@needle-tools/materialx 1.0.4-next.8713311 → 1.0.4-next.a95b900

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.8713311",
3
+ "version": "1.0.4-next.a95b900",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "exports": {
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';
@@ -1,8 +1,7 @@
1
- import { Camera, DoubleSide, FrontSide, GLSL3, IUniform, 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,21 +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
22
  type Uniforms = Record<string, IUniform & { needsUpdate?: boolean }>;
23
23
 
24
24
  export class MaterialXMaterial extends ShaderMaterial {
25
25
 
26
- // copy(source: MaterialXMaterial): this {
27
- // super.copy(source);
28
- // this.name = source.name;
29
- // this.uniforms = { ...source.uniforms }; // Shallow copy of uniforms
30
- // this.envMapIntensity = source.envMapIntensity;
31
- // this.envMap = source.envMap;
32
- // this.updateUniforms = source.updateUniforms; // Copy the update function
33
- // return this;
34
- // }
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;
35
33
 
36
34
  constructor(init?: MaterialXMaterialInitParameters) {
37
35
 
@@ -133,6 +131,8 @@ export class MaterialXMaterial extends ShaderMaterial {
133
131
  u_lightData: { value: [], needsUpdate: false }, // Array of light data. We need to set needsUpdate to false until we actually update it
134
132
  });
135
133
 
134
+ this._context = init.context;
135
+
136
136
  if (debug) {
137
137
  // Get lighting and environment data from MaterialX environment
138
138
  console.group("[MaterialX]: ", name);
@@ -140,13 +140,22 @@ export class MaterialXMaterial extends ShaderMaterial {
140
140
  console.log("Fragment shader length:", fragmentShader.length, fragmentShader);
141
141
  console.groupEnd();
142
142
  }
143
+ }
143
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
+ }
144
153
  }
145
154
 
146
155
 
147
156
  envMapIntensity: number = 1.0; // Default intensity for environment map
148
157
  envMap: Texture | null = null; // Environment map texture, can be set externally
149
- updateUniforms = (context: Context, environment: MaterialXEnvironment, object: Object3D, camera: Camera) => {
158
+ updateUniforms = (environment: MaterialXEnvironment, object: Object3D, camera: Camera) => {
150
159
 
151
160
  const uniforms = this.uniforms as Uniforms;
152
161
 
@@ -176,11 +185,13 @@ export class MaterialXMaterial extends ShaderMaterial {
176
185
  }
177
186
 
178
187
  // Update time uniforms
179
- if (uniforms.u_time) {
180
- uniforms.u_time.value = context.time.time;
181
- }
182
- if (uniforms.u_frame) {
183
- 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
+ }
184
195
  }
185
196
 
186
197
  // Update light uniforms
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;