@needle-tools/materialx 1.1.1 → 1.2.0

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.
@@ -1,30 +1,27 @@
1
- import type { MaterialX as MX } from "./materialx.types.js";
2
1
  import MaterialX from "../bin/JsMaterialXGenShader.js";
3
2
  import { debug } from "./utils.js";
4
- import { renderPMREMToEquirect } from "./textureHelper.js";
3
+ import { renderPMREMToEquirect } from "./utils.texture.js";
5
4
  import { Light, Mesh, MeshBasicMaterial, Object3D, PlaneGeometry, PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
6
- import { registerLights, getLightData, LightData } from "./materialx.helper.js";
7
- import type { MaterialXMaterial } from "./materialx.material.js";
8
-
9
- export type MaterialXContext = {
10
- getTime?(): number,
11
- getFrame?(): number,
12
- }
13
-
5
+ import { registerLights, getLightData } from "./materialx.helper.js";
14
6
 
15
7
  export const state = new class {
16
- materialXModule: MX.MODULE | null = null;
17
- materialXGenerator: any = null;
18
- materialXGenContext: any = null;
19
- materialXStdLib: any = null;
20
- materialXInitPromise: Promise<void> | null = null;
8
+ /** @type {import("./materialx.types.js").MaterialX.MODULE | null} */
9
+ materialXModule = null;
10
+ /** @type {any} */
11
+ materialXGenerator = null;
12
+ /** @type {any} */
13
+ materialXGenContext = null;
14
+ /** @type {any} */
15
+ materialXStdLib = null;
16
+ /** @type {Promise<void> | null} */
17
+ materialXInitPromise = null;
21
18
  }
22
19
 
23
-
24
20
  /**
25
21
  * Wait for the MaterialX WASM module to be ready.
22
+ * @returns {Promise<void>}
26
23
  */
27
- export async function ready(): Promise<void> {
24
+ export async function ready() {
28
25
  if (state.materialXInitPromise) {
29
26
  return state.materialXInitPromise;
30
27
  }
@@ -33,18 +30,19 @@ export async function ready(): Promise<void> {
33
30
  if (debug) console.log("[MaterialX] Initializing WASM module...");
34
31
  try {
35
32
 
36
- const urls: Array<string> = await Promise.all([
33
+ /** @type {Array<string>} */
34
+ const urls = await Promise.all([
37
35
  /** @ts-ignore */
38
- import(`../bin/JsMaterialXCore.wasm?url`).then(m => m.default || m),
36
+ import( /* @vite-ignore */ `../bin/JsMaterialXCore.wasm?url`).then(m => m.default || m),
39
37
  /** @ts-ignore */
40
- import(`../bin/JsMaterialXGenShader.wasm?url`).then(m => m.default || m),
38
+ import( /* @vite-ignore */ `../bin/JsMaterialXGenShader.wasm?url`).then(m => m.default || m),
41
39
  /** @ts-ignore */
42
- import(`../bin/JsMaterialXGenShader.data.txt?url`).then(m => m.default || m),
40
+ import( /* @vite-ignore */ `../bin/JsMaterialXGenShader.data.txt?url`).then(m => m.default || m),
43
41
  ]);
44
42
  const [JsMaterialXCore, JsMaterialXGenShader, JsMaterialXGenShader_data] = urls;
45
43
 
46
44
  const module = await MaterialX({
47
- locateFile: (path: string, scriptDirectory: string) => {
45
+ locateFile: (/** @type {string} */ path, /** @type {string} */ scriptDirectory) => {
48
46
  if (debug) console.debug("[MaterialX] locateFile called:", { path, scriptDirectory });
49
47
 
50
48
  if (path.includes("JsMaterialXCore.wasm")) {
@@ -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 = /** @type {import("./materialx.types.js").MaterialX.MODULE} */ (module);
65
63
 
66
64
  // Initialize shader generator and context
67
65
  state.materialXGenerator = module.EsslShaderGenerator.create();
@@ -95,6 +93,8 @@ export async function ready(): Promise<void> {
95
93
  // Set a reasonable default for max active lights
96
94
  state.materialXGenContext.getOptions().hwMaxActiveLightSources = 4;
97
95
 
96
+ // state.materialXGenContext.getOptions().hwShadowMap = true;
97
+
98
98
  // This prewarms the shader generation context to have all light types
99
99
  await registerLights(state.materialXModule, state.materialXGenContext);
100
100
 
@@ -106,45 +106,69 @@ export async function ready(): Promise<void> {
106
106
  })();
107
107
  }
108
108
 
109
- type EnvironmentTextureSet = {
110
- radianceTexture: Texture | null;
111
- irradianceTexture: Texture | null;
112
- }
113
-
109
+ /**
110
+ * @typedef {Object} EnvironmentTextureSet
111
+ * @property {Texture | null} radianceTexture
112
+ * @property {Texture | null} irradianceTexture
113
+ */
114
114
 
115
115
  /**
116
116
  * MaterialXEnvironment manages the environment settings for MaterialX materials.
117
117
  */
118
118
  export class MaterialXEnvironment {
119
119
 
120
- static get(scene: Scene): MaterialXEnvironment | null {
120
+ /**
121
+ * @param {Scene} scene
122
+ * @returns {MaterialXEnvironment | null}
123
+ */
124
+ static get(scene) {
121
125
  return this.getEnvironment(scene);
122
126
  }
123
- private static _environments: WeakMap<Scene, MaterialXEnvironment> = new Map();
124
- private static getEnvironment(scene: Scene): MaterialXEnvironment {
127
+
128
+ /** @type {WeakMap<Scene, MaterialXEnvironment>} */
129
+ static _environments = new Map();
130
+
131
+ /**
132
+ * @param {Scene} scene
133
+ * @returns {MaterialXEnvironment}
134
+ */
135
+ static getEnvironment(scene) {
125
136
  if (this._environments.has(scene)) {
126
- return this._environments.get(scene)!;
137
+ return /** @type {MaterialXEnvironment} */ (this._environments.get(scene));
127
138
  }
128
139
  const env = new MaterialXEnvironment(scene);
129
140
  this._environments.set(scene, env);
130
141
  return env;
131
142
  }
132
143
 
133
-
134
- private _lights: Array<Light> = [];
135
- private _lightData: null | LightData[] = null;
136
- private _lightCount: number = 0;
137
-
138
- private _initializePromise: Promise<boolean> | null = null;
139
- private _isInitialized: boolean = false;
140
- private _lastUpdateFrame: number = -1;
141
-
142
- constructor(private _scene: Scene) {
144
+ /** @type {Array<Light>} */
145
+ _lights = [];
146
+ /** @type {import("./materialx.helper.js").LightData[] | null} */
147
+ _lightData = null;
148
+ /** @type {number} */
149
+ _lightCount = 0;
150
+
151
+ /** @type {Promise<boolean> | null} */
152
+ _initializePromise = null;
153
+ /** @type {boolean} */
154
+ _isInitialized = false;
155
+ /** @type {number} */
156
+ _lastUpdateFrame = -1;
157
+
158
+ /**
159
+ * @param {Scene} _scene
160
+ */
161
+ constructor(_scene) {
162
+ this._scene = _scene;
143
163
  if (debug) console.log("[MaterialX] Environment created");
144
164
  }
145
165
 
146
- // Initialize with Needle Engine context
147
- async initialize(renderer: WebGLRenderer): Promise<boolean> {
166
+ /**
167
+ * Initialize with Needle Engine context
168
+ * @param {WebGLRenderer} renderer
169
+ * @returns {Promise<boolean>}
170
+ */
171
+ async initialize(renderer) {
148
172
  if (this._initializePromise) {
149
173
  return this._initializePromise;
150
174
  }
@@ -152,7 +176,12 @@ export class MaterialXEnvironment {
152
176
  return this._initializePromise;
153
177
  }
154
178
 
155
- update(frame: number, scene: Scene, renderer: WebGLRenderer): void {
179
+ /**
180
+ * @param {number} frame
181
+ * @param {Scene} scene
182
+ * @param {WebGLRenderer} renderer
183
+ */
184
+ update(frame, scene, renderer) {
156
185
  if (!this._initializePromise) {
157
186
  this.initialize(renderer);
158
187
  return;
@@ -179,13 +208,15 @@ export class MaterialXEnvironment {
179
208
  const radianceCube = new Mesh(planeGeometry, radianceMat);
180
209
  const irradianceMat = unlitMat.clone();
181
210
  irradianceMat.map = textures.irradianceTexture;
182
- const irradianceCube = new Mesh(planeGeometry, radianceMat);
211
+ const irradianceCube = new Mesh(planeGeometry, irradianceMat);
183
212
  scene.add(radianceCube);
184
213
  scene.add(irradianceCube);
185
214
  radianceCube.name = "MaterialXRadianceCube";
186
- radianceCube.position.set(.6, 1, 0);
215
+ radianceCube.position.set(.8, 1, .01);
216
+ radianceCube.scale.set(1.5, 1, 1);
187
217
  irradianceCube.name = "MaterialXIrradianceCube";
188
- irradianceCube.position.set(-.6, 1, 0);
218
+ irradianceCube.position.set(-.8, 1, -.01);
219
+ irradianceCube.scale.set(1.5, 0.98, 1);
189
220
  console.log("[MaterialX] environment initialized from Needle context", { textures, radianceCube, irradianceCube });
190
221
  }
191
222
  }
@@ -209,11 +240,18 @@ export class MaterialXEnvironment {
209
240
  this._texturesCache.clear();
210
241
  }
211
242
 
243
+ get lights() {
244
+ return this._lights;
245
+ }
212
246
  get lightData() {
213
247
  return this._lightData;
214
248
  }
215
249
  get lightCount() { return this._lightCount || 0; }
216
- getTextures(material: MaterialXMaterial) {
250
+
251
+ /**
252
+ * @param {import("./materialx.material.js").MaterialXMaterial} material
253
+ */
254
+ getTextures(material) {
217
255
  if (material.envMap) {
218
256
  // If the material has its own envMap, we don't use the irradiance texture
219
257
  return this._getTextures(material.envMap);
@@ -221,11 +259,18 @@ export class MaterialXEnvironment {
221
259
  return this._getTextures(this._scene.environment);
222
260
  }
223
261
 
224
- private _pmremGenerator: PMREMGenerator | null = null;
225
- private _renderer: WebGLRenderer | null = null;
226
- private readonly _texturesCache: Map<Texture | null, EnvironmentTextureSet> = new Map();
227
-
228
- private async _initialize(renderer: WebGLRenderer): Promise<boolean> {
262
+ /** @type {PMREMGenerator | null} */
263
+ _pmremGenerator = null;
264
+ /** @type {WebGLRenderer | null} */
265
+ _renderer = null;
266
+ /** @type {Map<Texture | null, EnvironmentTextureSet>} */
267
+ _texturesCache = new Map();
268
+
269
+ /**
270
+ * @param {WebGLRenderer} renderer
271
+ * @returns {Promise<boolean>}
272
+ */
273
+ async _initialize(renderer) {
229
274
  this._isInitialized = false;
230
275
  this._pmremGenerator = new PMREMGenerator(renderer);
231
276
  this._renderer = renderer;
@@ -234,11 +279,13 @@ export class MaterialXEnvironment {
234
279
  return true;
235
280
  }
236
281
 
237
- private _getTextures(texture: Texture | null | undefined): {
238
- radianceTexture: Texture | null,
239
- irradianceTexture: Texture | null
240
- } {
241
- let res: EnvironmentTextureSet | undefined = this._texturesCache.get(texture || null);
282
+ /**
283
+ * @param {Texture | null | undefined} texture
284
+ * @returns {{radianceTexture: Texture | null, irradianceTexture: Texture | null}}
285
+ */
286
+ _getTextures(texture) {
287
+ /** @type {EnvironmentTextureSet | undefined} */
288
+ let res = this._texturesCache.get(texture || null);
242
289
  if (res) {
243
290
  return res;
244
291
  }
@@ -264,14 +311,18 @@ export class MaterialXEnvironment {
264
311
  return res;
265
312
  }
266
313
 
267
- private updateLighting = (collectLights: boolean = false) => {
314
+ /**
315
+ * @param {boolean} collectLights
316
+ */
317
+ updateLighting = (collectLights = false) => {
268
318
  if (!this._scene) return;
269
319
  // Find lights in scene
270
320
  if (collectLights) {
271
- const lights = new Array<Light>();
272
- this._scene.traverse((object: Object3D) => {
273
- if ((object as Light).isLight && object.visible)
274
- lights.push(object as Light);
321
+ /** @type {Array<Light>} */
322
+ const lights = new Array();
323
+ this._scene.traverse((/** @type {Object3D} */ object) => {
324
+ if ((/** @type {Light} */ (object)).isLight && object.visible)
325
+ lights.push(/** @type {Light} */ (object));
275
326
  });
276
327
  this._lights = lights;
277
328
  }
@@ -0,0 +1,37 @@
1
+ import { BufferGeometry, Camera, Group, IUniform, MaterialParameters, Object3D, Scene, ShaderMaterial, Texture, WebGLRenderer } from "three";
2
+ import { MaterialXContext, MaterialXEnvironment } from "./materialx.js";
3
+ import { Callbacks } from "./materialx.helper.js";
4
+
5
+ declare type MaterialXMaterialInitParameters = {
6
+ name: string;
7
+ shaderName?: string | null;
8
+ shader: any;
9
+ loaders: Callbacks;
10
+ context: MaterialXContext;
11
+ parameters?: MaterialParameters;
12
+ debug?: boolean;
13
+ }
14
+
15
+ type Uniforms = Record<string, IUniform & { needsUpdate?: boolean }>;
16
+ type Precision = "highp" | "mediump" | "lowp";
17
+
18
+ export declare class MaterialXMaterial extends ShaderMaterial {
19
+ readonly shaderName: string | null;
20
+
21
+ copy(source: MaterialXMaterial): this;
22
+
23
+ private _context: MaterialXContext | null;
24
+ private _shader: any;
25
+ private _needsTangents: boolean;
26
+
27
+ constructor(init?: MaterialXMaterialInitParameters);
28
+
29
+ private _missingTangentsWarned: boolean;
30
+ onBeforeRender(renderer: WebGLRenderer, _scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D, _group: Group): void;
31
+
32
+ envMapIntensity: number;
33
+ envMap: Texture | null;
34
+ updateUniforms(environment: MaterialXEnvironment, _renderer: WebGLRenderer, object: Object3D, camera: Camera, time?: number, frame?: number): void;
35
+
36
+ private updateEnvironmentUniforms(environment: MaterialXEnvironment): void;
37
+ }
@@ -1,33 +1,43 @@
1
- import { BufferGeometry, Camera, FrontSide, GLSL3, Group, IUniform, MaterialParameters, Matrix3, Matrix4, Object3D, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
1
+ import { BufferGeometry, Camera, FrontSide, GLSL3, Group, Matrix3, Matrix4, Object3D, Scene, ShaderMaterial, Texture, Vector3, WebGLRenderer } from "three";
2
2
  import { debug, getFrame, getTime } from "./utils.js";
3
- import { MaterialXContext, MaterialXEnvironment } from "./materialx.js";
4
- import { generateMaterialPropertiesForUniforms, getUniformValues, Loaders } from "./materialx.helper.js";
3
+ import { MaterialXEnvironment } from "./materialx.js";
4
+ import { generateMaterialPropertiesForUniforms, getUniformValues } from "./materialx.helper.js";
5
5
  import { cloneUniforms, cloneUniformsGroups } from "three/src/renderers/shaders/UniformsUtils.js";
6
6
 
7
-
8
7
  // Add helper matrices for uniform updates (similar to MaterialX example)
9
8
  const normalMat = new Matrix3();
10
9
  const worldViewPos = new Vector3();
11
10
 
12
- declare type MaterialXMaterialInitParameters = {
13
- name: string,
14
- shader: any,
15
- loaders: Loaders,
16
- context: MaterialXContext,
17
- // Optional parameters
18
- parameters?: MaterialParameters,
19
- }
20
-
21
- type Uniforms = Record<string, IUniform & { needsUpdate?: boolean }>;
22
- type Precision = "highp" | "mediump" | "lowp";
11
+ /**
12
+ * @typedef {Object} MaterialXMaterialInitParameters
13
+ * @property {string} name
14
+ * @property {string | null} [shaderName] - Optional name of the shader
15
+ * @property {any} shader
16
+ * @property {import('./materialx.helper.js').Callbacks} loaders
17
+ * @property {import('./materialx.js').MaterialXContext} context
18
+ * @property {import('three').MaterialParameters} [parameters] - Optional parameters
19
+ * @property {boolean} [debug] - Debug flag
20
+ */
21
+
22
+ /**
23
+ * @typedef {"highp" | "mediump" | "lowp"} Precision
24
+ */
23
25
 
24
26
  export class MaterialXMaterial extends ShaderMaterial {
25
27
 
26
- copy(source: MaterialXMaterial): this {
28
+ /** The original name of the shader
29
+ * @type {string | null} */
30
+ shaderName = null;
31
+
32
+ /**
33
+ * @param {MaterialXMaterial} source
34
+ * @returns {this}
35
+ */
36
+ copy(source) {
27
37
  super.copy(source);
28
38
  this._context = source._context;
29
39
  this._shader = source._shader;
30
- this.uniforms = cloneUniforms(source.uniforms) as Uniforms;
40
+ this.uniforms = cloneUniforms(source.uniforms);
31
41
  this.uniformsGroups = cloneUniformsGroups(source.uniformsGroups);
32
42
  this.envMapIntensity = source.envMapIntensity;
33
43
  this.envMap = source.envMap;
@@ -37,10 +47,17 @@ export class MaterialXMaterial extends ShaderMaterial {
37
47
  return this;
38
48
  }
39
49
 
40
- private _context: MaterialXContext | null = null;
41
- private _shader: any;
50
+ /** @type {import('./materialx.js').MaterialXContext | null} */
51
+ _context = null;
52
+ /** @type {any} */
53
+ _shader = null;
54
+ /** @type {boolean} */
55
+ _needsTangents = false;
42
56
 
43
- constructor(init?: MaterialXMaterialInitParameters) {
57
+ /**
58
+ * @param {MaterialXMaterialInitParameters} [init]
59
+ */
60
+ constructor(init) {
44
61
 
45
62
  // TODO: we need to properly copy the uniforms and other properties from the source material
46
63
  if (!init) {
@@ -71,7 +88,8 @@ export class MaterialXMaterial extends ShaderMaterial {
71
88
  vertexShader = vertexShader.replace(/\bi_color_0\b/g, 'color');
72
89
 
73
90
  // Patch fragmentShader
74
- const precision = init.parameters?.precision || "highp" as Precision;
91
+ const precision = init.parameters?.precision || "highp";
92
+ vertexShader = vertexShader.replace(/precision mediump float;/g, `precision ${precision} float;`);
75
93
  fragmentShader = fragmentShader.replace(/precision mediump float;/g, `precision ${precision} float;`);
76
94
  fragmentShader = fragmentShader.replace(/#define M_FLOAT_EPS 1e-8/g, precision === "highp" ? `#define M_FLOAT_EPS 1e-8` : `#define M_FLOAT_EPS 1e-3`);
77
95
 
@@ -84,9 +102,16 @@ export class MaterialXMaterial extends ShaderMaterial {
84
102
  fragmentShader = fragmentShader.replace(/\bi_tangent\b/g, 'tangent');
85
103
  fragmentShader = fragmentShader.replace(/\bi_color_0\b/g, 'color');
86
104
 
105
+ // Capture some vertex shader properties
106
+ const uv_is_vec2 = vertexShader.includes('in vec2 uv;'); // check if uv is vec2; e.g. https://matlib.gpuopen.com/main/materials/all?material=da6ec531-f5c1-4790-ac14-8a5c51d0314e
107
+ const uv1_is_vec2 = vertexShader.includes('in vec2 uv1;');
108
+ const uv2_is_vec2 = vertexShader.includes('in vec2 uv2;');
109
+ const uv3_is_vec2 = vertexShader.includes('in vec2 uv3;');
110
+
87
111
  // Remove `in vec3 position;` and so on since they're already declared by ShaderMaterial
88
112
  vertexShader = vertexShader.replace(/in\s+vec3\s+position;/g, '');
89
113
  vertexShader = vertexShader.replace(/in\s+vec3\s+normal;/g, '');
114
+ vertexShader = vertexShader.replace(/in\s+vec2\s+uv;/g, '');
90
115
  vertexShader = vertexShader.replace(/in\s+vec3\s+uv;/g, '');
91
116
  var hasUv1 = vertexShader.includes('in vec3 uv1;');
92
117
  vertexShader = vertexShader.replace(/in\s+vec3\s+uv1;/g, '');
@@ -101,10 +126,10 @@ export class MaterialXMaterial extends ShaderMaterial {
101
126
 
102
127
  // Patch uv 2-component to 3-component (`texcoord_0 = uv;` needs to be replaced with `texcoord_0 = vec3(uv, 0.0);`)
103
128
  // TODO what if we actually have a 3-component UV? Not sure what three.js does then
104
- vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
105
- vertexShader = vertexShader.replace(/texcoord_1 = uv1;/g, 'texcoord_1 = vec3(uv1, 0.0);');
106
- vertexShader = vertexShader.replace(/texcoord_2 = uv2;/g, 'texcoord_2 = vec3(uv2, 0.0);');
107
- vertexShader = vertexShader.replace(/texcoord_3 = uv3;/g, 'texcoord_3 = vec3(uv3, 0.0);');
129
+ if (!uv_is_vec2) vertexShader = vertexShader.replace(/texcoord_0 = uv;/g, 'texcoord_0 = vec3(uv, 0.0);');
130
+ if (!uv1_is_vec2) vertexShader = vertexShader.replace(/texcoord_1 = uv1;/g, 'texcoord_1 = vec3(uv1, 0.0);');
131
+ if (!uv2_is_vec2) vertexShader = vertexShader.replace(/texcoord_2 = uv2;/g, 'texcoord_2 = vec3(uv2, 0.0);');
132
+ if (!uv3_is_vec2) vertexShader = vertexShader.replace(/texcoord_3 = uv3;/g, 'texcoord_3 = vec3(uv3, 0.0);');
108
133
 
109
134
  // Patch units – seems MaterialX uses different units and we end up with wrong light values?
110
135
  // result.direction = light.position - position;
@@ -124,7 +149,7 @@ export class MaterialXMaterial extends ShaderMaterial {
124
149
  #include <tonemapping_fragment>
125
150
  #include <colorspace_fragment>`);
126
151
 
127
- const defines: Record<string, string> = {};
152
+ const defines = {};
128
153
  if (hasUv1) defines['USE_UV1'] = '';
129
154
  if (hasUv2) defines['USE_UV2'] = '';
130
155
  if (hasUv3) defines['USE_UV3'] = '';
@@ -144,8 +169,10 @@ export class MaterialXMaterial extends ShaderMaterial {
144
169
  defines: defines,
145
170
  ...init.parameters, // Spread any additional parameters passed to the material
146
171
  });
172
+ this.shaderName = init.shaderName || null;
147
173
  this._context = init.context;
148
174
  this._shader = init.shader;
175
+ this._needsTangents = vertexShader.includes('in vec4 tangent;') || vertexShader.includes('in vec3 tangent;');
149
176
 
150
177
  Object.assign(this.uniforms, {
151
178
  ...getUniformValues(init.shader.getStage('vertex'), init.loaders, searchPath),
@@ -156,6 +183,9 @@ export class MaterialXMaterial extends ShaderMaterial {
156
183
  u_viewPosition: { value: new Vector3() },
157
184
  u_worldInverseTransposeMatrix: { value: new Matrix4() },
158
185
 
186
+ // u_shadowMatrix: { value: new Matrix4() },
187
+ // u_shadowMap: { value: null, type: 't' }, // Shadow map
188
+
159
189
  u_envMatrix: { value: new Matrix4() },
160
190
  u_envRadiance: { value: null, type: 't' },
161
191
  u_envRadianceMips: { value: 8, type: 'i' },
@@ -171,7 +201,7 @@ export class MaterialXMaterial extends ShaderMaterial {
171
201
  generateMaterialPropertiesForUniforms(this, init.shader.getStage('vertex'));
172
202
 
173
203
 
174
- if (debug) {
204
+ if (debug || init.debug) {
175
205
  // Get lighting and environment data from MaterialX environment
176
206
  console.group("[MaterialX]: ", this.name);
177
207
  console.log(`Vertex shader length: ${vertexShader.length}\n`, vertexShader);
@@ -180,22 +210,50 @@ export class MaterialXMaterial extends ShaderMaterial {
180
210
  }
181
211
  }
182
212
 
183
- onBeforeRender(_renderer: WebGLRenderer, _scene: Scene, camera: Camera, _geometry: BufferGeometry, object: Object3D, _group: Group): void {
213
+ /** @type {boolean} */
214
+ _missingTangentsWarned = false;
215
+
216
+ /**
217
+ * @param {WebGLRenderer} renderer
218
+ * @param {Scene} _scene
219
+ * @param {Camera} camera
220
+ * @param {BufferGeometry} geometry
221
+ * @param {Object3D} object
222
+ * @param {Group} _group
223
+ */
224
+ onBeforeRender(renderer, _scene, camera, geometry, object, _group) {
225
+ if (this._needsTangents && !geometry.attributes.tangent) {
226
+ if (!this._missingTangentsWarned) {
227
+ this._missingTangentsWarned = true;
228
+ console.warn(`[MaterialX] Tangents are required for this material (${this.name}) but not present in the geometry.`);
229
+ // TODO: can we compute tangents here?
230
+ }
231
+ }
184
232
  const time = this._context?.getTime?.() || getTime();
185
233
  const frame = this._context?.getFrame?.() || getFrame();
186
234
  const env = MaterialXEnvironment.get(_scene);
187
235
  if (env) {
188
- env.update(frame, _scene, _renderer);
189
- this.updateUniforms(env, object, camera, time, frame);
236
+ env.update(frame, _scene, renderer);
237
+ this.updateUniforms(env, renderer, object, camera, time, frame);
190
238
  }
191
239
  }
192
240
 
241
+ /** @type {number} */
242
+ envMapIntensity = 1.0; // Default intensity for environment map
243
+ /** @type {Texture | null} */
244
+ envMap = null; // Environment map texture, can be set externally
193
245
 
194
- envMapIntensity: number = 1.0; // Default intensity for environment map
195
- envMap: Texture | null = null; // Environment map texture, can be set externally
196
- updateUniforms = (environment: MaterialXEnvironment, object: Object3D, camera: Camera, time?: number, frame?: number) => {
246
+ /**
247
+ * @param {MaterialXEnvironment} environment
248
+ * @param {WebGLRenderer} _renderer
249
+ * @param {Object3D} object
250
+ * @param {Camera} camera
251
+ * @param {number} [time]
252
+ * @param {number} [frame]
253
+ */
254
+ updateUniforms = (environment, _renderer, object, camera, time, frame) => {
197
255
 
198
- const uniforms = this.uniforms as Uniforms;
256
+ const uniforms = this.uniforms;
199
257
 
200
258
  // Update standard transformation matrices
201
259
  if (uniforms.u_worldMatrix) {
@@ -208,15 +266,23 @@ export class MaterialXMaterial extends ShaderMaterial {
208
266
  uniforms.u_viewProjectionMatrix.needsUpdate = true;
209
267
  }
210
268
 
269
+ if (uniforms.u_worldInverseTransposeMatrix) {
270
+ uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
271
+ uniforms.u_worldInverseTransposeMatrix.needsUpdate = true;
272
+ }
273
+
211
274
  if (uniforms.u_viewPosition) {
212
275
  uniforms.u_viewPosition.value.copy(camera.getWorldPosition(worldViewPos));
213
276
  uniforms.u_viewPosition.needsUpdate = true;
214
277
  }
215
278
 
216
- if (uniforms.u_worldInverseTransposeMatrix) {
217
- uniforms.u_worldInverseTransposeMatrix.value.setFromMatrix3(normalMat.getNormalMatrix(object.matrixWorld));
218
- uniforms.u_worldInverseTransposeMatrix.needsUpdate = true;
219
- }
279
+ // if (uniforms.u_shadowMap) {
280
+ // const light = environment.lights?.[2] || null;
281
+ // uniforms.u_shadowMatrix.value = light?.shadow?.matrix.clone().premultiply(object.matrixWorld.clone()).invert();
282
+ // uniforms.u_shadowMap.value = light.shadow?.map || null;
283
+ // uniforms.u_shadowMap.needsUpdate = true;
284
+ // console.log("[MaterialX] Renderer shadow map updated", light);
285
+ // }
220
286
 
221
287
  // Update time uniforms
222
288
  if (uniforms.u_time) {
@@ -234,9 +300,13 @@ export class MaterialXMaterial extends ShaderMaterial {
234
300
  this.uniformsNeedUpdate = true;
235
301
  }
236
302
 
237
- private updateEnvironmentUniforms = (environment: MaterialXEnvironment) => {
303
+ /**
304
+ * @private
305
+ * @param {MaterialXEnvironment} environment
306
+ */
307
+ updateEnvironmentUniforms = (environment) => {
238
308
 
239
- const uniforms = this.uniforms as Uniforms;
309
+ const uniforms = this.uniforms;
240
310
 
241
311
  // Get lighting data from environment
242
312
  const lightData = environment.lightData || null;
package/src/utils.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Get URL parameter value
3
+ */
4
+ export function getParam(name: string): boolean | string;
5
+
6
+ export const debug: boolean | string;
7
+ export const debugUpdate: boolean;
8
+
9
+ /**
10
+ * Get current time in seconds
11
+ */
12
+ export function getTime(): number;
13
+
14
+ /**
15
+ * Get current frame number
16
+ */
17
+ export function getFrame(): number;
@@ -1,4 +1,12 @@
1
- export function getParam(name: string) {
1
+ // import { BufferGeometry } from 'three';
2
+ // import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
3
+
4
+ /**
5
+ * Get URL parameter value
6
+ * @param {string} name - Parameter name
7
+ * @returns {boolean|string} Parameter value or false if not found
8
+ */
9
+ export function getParam(name) {
2
10
  const urlParams = new URLSearchParams(window.location.search);
3
11
  const param = urlParams.get(name);
4
12
  if (param == null || param === "0" || param === "false") return false;
@@ -9,21 +17,30 @@ export function getParam(name: string) {
9
17
  export const debug = getParam("debugmaterialx");
10
18
  export const debugUpdate = debug === "update";
11
19
 
12
-
13
20
  let time = 0;
21
+ /**
22
+ * Get current time in seconds
23
+ * @returns {number} Current time
24
+ */
14
25
  export function getTime() {
15
26
  return time;
16
27
  }
28
+
17
29
  let frame = 0;
30
+ /**
31
+ * Get current frame number
32
+ * @returns {number} Current frame
33
+ */
18
34
  export function getFrame() {
19
35
  return frame;
20
36
  }
21
37
 
22
- const performance = window.performance || (window as any).webkitPerformance || (window as any).mozPerformance;
38
+ const performance = window.performance || /** @type {any} */ (window).webkitPerformance || /** @type {any} */ (window).mozPerformance;
39
+
23
40
  function updateTime() {
24
41
  time = performance.now() / 1000; // Convert to seconds
25
42
  frame++;
26
43
  window.requestAnimationFrame(updateTime);
27
44
  }
28
- window.requestAnimationFrame(updateTime);
29
45
 
46
+ window.requestAnimationFrame(updateTime);