@needle-tools/materialx 1.0.0-alpha
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/.github/workflows/release.yml +39 -0
- package/README.md +1 -0
- package/bin/JsMaterialXCore.js +21 -0
- package/bin/JsMaterialXCore.wasm +0 -0
- package/bin/JsMaterialXGenShader.data +27551 -0
- package/bin/JsMaterialXGenShader.js +23 -0
- package/bin/JsMaterialXGenShader.wasm +0 -0
- package/bin/README.md +5 -0
- package/codegen/register_types.ts +6 -0
- package/index.ts +5 -0
- package/package.json +20 -0
- package/package.needle.json +3 -0
- package/src/helper.js +457 -0
- package/src/index.ts +5 -0
- package/src/loader/loader.needle.ts +106 -0
- package/src/loader/loader.three.ts +555 -0
- package/src/materialx.ts +220 -0
- package/src/textureHelper.ts +170 -0
- package/src/utils.ts +58 -0
- package/tsconfig.json +20 -0
- package/workspace.code-workspace +18 -0
package/src/helper.js
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright Contributors to the MaterialX Project
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import { getParam } from '@needle-tools/engine';
|
|
7
|
+
import * as THREE from 'three';
|
|
8
|
+
|
|
9
|
+
const debug = getParam("debugmaterialx");
|
|
10
|
+
|
|
11
|
+
const IMAGE_PROPERTY_SEPARATOR = "_";
|
|
12
|
+
const UADDRESS_MODE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "uaddressmode";
|
|
13
|
+
const VADDRESS_MODE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "vaddressmode";
|
|
14
|
+
const FILTER_TYPE_SUFFIX = IMAGE_PROPERTY_SEPARATOR + "filtertype";
|
|
15
|
+
const IMAGE_PATH_SEPARATOR = "/";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialized the environment texture as MaterialX expects it
|
|
19
|
+
* @param {THREE.Texture} texture
|
|
20
|
+
* @param {Object} capabilities
|
|
21
|
+
* @returns {THREE.Texture}
|
|
22
|
+
*/
|
|
23
|
+
export function prepareEnvTexture(texture, capabilities)
|
|
24
|
+
{
|
|
25
|
+
let newTexture = new THREE.DataTexture(texture.image.data, texture.image.width, texture.image.height, texture.format, texture.type);
|
|
26
|
+
newTexture.wrapS = THREE.RepeatWrapping;
|
|
27
|
+
newTexture.anisotropy = capabilities.getMaxAnisotropy();
|
|
28
|
+
newTexture.minFilter = THREE.LinearMipmapLinearFilter;
|
|
29
|
+
newTexture.magFilter = THREE.LinearFilter;
|
|
30
|
+
newTexture.generateMipmaps = true;
|
|
31
|
+
newTexture.needsUpdate = true;
|
|
32
|
+
|
|
33
|
+
return newTexture;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get Three uniform from MaterialX vector
|
|
38
|
+
* @param {any} value
|
|
39
|
+
* @param {any} dimension
|
|
40
|
+
* @returns {THREE.Uniform}
|
|
41
|
+
*/
|
|
42
|
+
function fromVector(value, dimension)
|
|
43
|
+
{
|
|
44
|
+
let outValue;
|
|
45
|
+
if (value)
|
|
46
|
+
{
|
|
47
|
+
outValue = [...value.data()];
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
{
|
|
51
|
+
outValue = [];
|
|
52
|
+
for (let i = 0; i < dimension; ++i)
|
|
53
|
+
outValue.push(0.0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return outValue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get Three uniform from MaterialX matrix
|
|
61
|
+
* @param {mx.matrix} matrix
|
|
62
|
+
* @param {mx.matrix.size} dimension
|
|
63
|
+
*/
|
|
64
|
+
function fromMatrix(matrix, dimension)
|
|
65
|
+
{
|
|
66
|
+
let vec = [];
|
|
67
|
+
if (matrix)
|
|
68
|
+
{
|
|
69
|
+
for (let i = 0; i < matrix.numRows(); ++i)
|
|
70
|
+
{
|
|
71
|
+
for (let k = 0; k < matrix.numColumns(); ++k)
|
|
72
|
+
{
|
|
73
|
+
vec.push(matrix.getItem(i, k));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else
|
|
77
|
+
{
|
|
78
|
+
for (let i = 0; i < dimension; ++i)
|
|
79
|
+
vec.push(0.0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return vec;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get Three uniform from MaterialX value
|
|
87
|
+
* @param {mx.Uniform.type} type
|
|
88
|
+
* @param {mx.Uniform.value} value
|
|
89
|
+
* @param {mx.Uniform.name} name
|
|
90
|
+
* @param {mx.Uniforms} uniforms
|
|
91
|
+
* @param {THREE.textureLoader} textureLoader
|
|
92
|
+
* @param {string} searchPath
|
|
93
|
+
* @param {boolean} flipY
|
|
94
|
+
*/
|
|
95
|
+
function toThreeUniform(type, value, name, uniforms, textureLoader, searchPath, flipY)
|
|
96
|
+
{
|
|
97
|
+
let outValue = null;
|
|
98
|
+
switch (type)
|
|
99
|
+
{
|
|
100
|
+
case 'float':
|
|
101
|
+
case 'integer':
|
|
102
|
+
case 'boolean':
|
|
103
|
+
outValue = value;
|
|
104
|
+
break;
|
|
105
|
+
case 'vector2':
|
|
106
|
+
outValue = fromVector(value, 2);
|
|
107
|
+
break;
|
|
108
|
+
case 'vector3':
|
|
109
|
+
case 'color3':
|
|
110
|
+
outValue = fromVector(value, 3);
|
|
111
|
+
break;
|
|
112
|
+
case 'vector4':
|
|
113
|
+
case 'color4':
|
|
114
|
+
outValue = fromVector(value, 4);
|
|
115
|
+
break;
|
|
116
|
+
case 'matrix33':
|
|
117
|
+
outValue = fromMatrix(value, 9);
|
|
118
|
+
break;
|
|
119
|
+
case 'matrix44':
|
|
120
|
+
outValue = fromMatrix(value, 16);
|
|
121
|
+
break;
|
|
122
|
+
case 'filename':
|
|
123
|
+
if (value)
|
|
124
|
+
{
|
|
125
|
+
// Cache / reuse texture to avoid reload overhead.
|
|
126
|
+
// Note: that data blobs and embedded data textures are not cached as they are transient data.
|
|
127
|
+
let checkCache = false;
|
|
128
|
+
let texturePath = searchPath + IMAGE_PATH_SEPARATOR + value;
|
|
129
|
+
if (value.startsWith('blob:'))
|
|
130
|
+
{
|
|
131
|
+
texturePath = value;
|
|
132
|
+
if (debug) console.log('Load blob URL:', texturePath);
|
|
133
|
+
checkCache = false;
|
|
134
|
+
}
|
|
135
|
+
else if (value.startsWith('http'))
|
|
136
|
+
{
|
|
137
|
+
texturePath = value;
|
|
138
|
+
if (debug) console.log('Load HTTP URL:', texturePath);
|
|
139
|
+
}
|
|
140
|
+
else if (value.startsWith('data:'))
|
|
141
|
+
{
|
|
142
|
+
texturePath = value;
|
|
143
|
+
checkCache = false;
|
|
144
|
+
if (debug) console.log('Load data URL:', texturePath);
|
|
145
|
+
}
|
|
146
|
+
const cachedTexture = checkCache && THREE.Cache.get(texturePath);
|
|
147
|
+
if (cachedTexture)
|
|
148
|
+
{
|
|
149
|
+
// Get texture from cache
|
|
150
|
+
outValue = cachedTexture;
|
|
151
|
+
if (debug) console.log('Use cached texture: ', texturePath, outValue);
|
|
152
|
+
}
|
|
153
|
+
else
|
|
154
|
+
{
|
|
155
|
+
outValue = textureLoader.load(
|
|
156
|
+
texturePath,
|
|
157
|
+
function (texture) {
|
|
158
|
+
if (debug) console.log('Load new texture: ' + texturePath, texture);
|
|
159
|
+
outValue = texture;
|
|
160
|
+
|
|
161
|
+
// Add texture to ThreeJS cache
|
|
162
|
+
if (checkCache)
|
|
163
|
+
THREE.Cache.add(texturePath, texture);
|
|
164
|
+
},
|
|
165
|
+
undefined,
|
|
166
|
+
function (error) {
|
|
167
|
+
console.error('Error loading texture: ', error);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Set address & filtering mode
|
|
171
|
+
if (outValue)
|
|
172
|
+
setTextureParameters(outValue, name, uniforms, flipY);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
case 'samplerCube':
|
|
177
|
+
case 'string':
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
const key = type + ':' + name;
|
|
181
|
+
if (!valueTypeWarningMap.has(key))
|
|
182
|
+
{
|
|
183
|
+
valueTypeWarningMap.set(key, true);
|
|
184
|
+
console.warn('MaterialX: Unsupported uniform type: ' + type + ' for uniform: ' + name, value);
|
|
185
|
+
}
|
|
186
|
+
outValue = null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return outValue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const valueTypeWarningMap = new Map();
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get Three wrapping mode
|
|
196
|
+
* @param {mx.TextureFilter.wrap} mode
|
|
197
|
+
* @returns {THREE.Wrapping}
|
|
198
|
+
*/
|
|
199
|
+
function getWrapping(mode)
|
|
200
|
+
{
|
|
201
|
+
let wrap;
|
|
202
|
+
switch (mode)
|
|
203
|
+
{
|
|
204
|
+
case 1:
|
|
205
|
+
wrap = THREE.ClampToEdgeWrapping;
|
|
206
|
+
break;
|
|
207
|
+
case 2:
|
|
208
|
+
wrap = THREE.RepeatWrapping;
|
|
209
|
+
break;
|
|
210
|
+
case 3:
|
|
211
|
+
wrap = THREE.MirroredRepeatWrapping;
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
wrap = THREE.RepeatWrapping;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
return wrap;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get Three minification filter
|
|
222
|
+
* @param {mx.TextureFilter.minFilter} type
|
|
223
|
+
* @param {mx.TextureFilter.generateMipmaps} generateMipmaps
|
|
224
|
+
*/
|
|
225
|
+
function getMinFilter(type, generateMipmaps)
|
|
226
|
+
{
|
|
227
|
+
/** @type {THREE.TextureFilter} */
|
|
228
|
+
let filterType = generateMipmaps ? THREE.LinearMipMapLinearFilter : THREE.LinearFilter;
|
|
229
|
+
if (type === 0)
|
|
230
|
+
{
|
|
231
|
+
filterType = generateMipmaps ? THREE.NearestMipMapNearestFilter : THREE.NearestFilter;
|
|
232
|
+
}
|
|
233
|
+
return filterType;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Set Three texture parameters
|
|
238
|
+
* @param {THREE.Texture} texture
|
|
239
|
+
* @param {mx.Uniform.name} name
|
|
240
|
+
* @param {mx.Uniforms} uniforms
|
|
241
|
+
* @param {mx.TextureFilter.generateMipmaps} generateMipmaps
|
|
242
|
+
*/
|
|
243
|
+
function setTextureParameters(texture, name, uniforms, flipY = true, generateMipmaps = true)
|
|
244
|
+
{
|
|
245
|
+
const idx = name.lastIndexOf(IMAGE_PROPERTY_SEPARATOR);
|
|
246
|
+
const base = name.substring(0, idx) || name;
|
|
247
|
+
|
|
248
|
+
texture.generateMipmaps = generateMipmaps;
|
|
249
|
+
texture.wrapS = THREE.RepeatWrapping;
|
|
250
|
+
texture.wrapT = THREE.RepeatWrapping;
|
|
251
|
+
texture.magFilter = THREE.LinearFilter;
|
|
252
|
+
texture.flipY = flipY;
|
|
253
|
+
|
|
254
|
+
if (uniforms.find(base + UADDRESS_MODE_SUFFIX))
|
|
255
|
+
{
|
|
256
|
+
const uaddressmode = uniforms.find(base + UADDRESS_MODE_SUFFIX).getValue().getData();
|
|
257
|
+
texture.wrapS = getWrapping(uaddressmode);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (uniforms.find(base + VADDRESS_MODE_SUFFIX))
|
|
261
|
+
{
|
|
262
|
+
const vaddressmode = uniforms.find(base + VADDRESS_MODE_SUFFIX).getValue().getData();
|
|
263
|
+
texture.wrapT = getWrapping(vaddressmode);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const filterType = uniforms.find(base + FILTER_TYPE_SUFFIX) ? uniforms.get(base + FILTER_TYPE_SUFFIX).value : -1;
|
|
267
|
+
texture.minFilter = getMinFilter(filterType, generateMipmaps);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Return the global light rotation matrix
|
|
272
|
+
*/
|
|
273
|
+
export function getLightRotation()
|
|
274
|
+
{
|
|
275
|
+
return new THREE.Matrix4().makeRotationY(Math.PI / 2);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Returns all lights nodes in a MaterialX document
|
|
280
|
+
* @param {mx.Document} doc
|
|
281
|
+
* @returns {Array.<mx.Node>}
|
|
282
|
+
*/
|
|
283
|
+
export function findLights(doc)
|
|
284
|
+
{
|
|
285
|
+
let lights = [];
|
|
286
|
+
for (let node of doc.getNodes())
|
|
287
|
+
{
|
|
288
|
+
if (node.getType() === "lightshader")
|
|
289
|
+
lights.push(node);
|
|
290
|
+
}
|
|
291
|
+
return lights;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Register lights in shader generation context
|
|
296
|
+
* @param {Object} mx MaterialX Module
|
|
297
|
+
* @param {Array.<mx.Node>} lights Light nodes
|
|
298
|
+
* @param {mx.GenContext} genContext Shader generation context
|
|
299
|
+
* @returns {Array.<mx.Node>}
|
|
300
|
+
*/
|
|
301
|
+
export async function registerLights(mx, lights, genContext)
|
|
302
|
+
{
|
|
303
|
+
mx.HwShaderGenerator.unbindLightShaders(genContext);
|
|
304
|
+
|
|
305
|
+
const lightTypesBound = {};
|
|
306
|
+
const lightData = [];
|
|
307
|
+
let lightId = 1;
|
|
308
|
+
|
|
309
|
+
// All light types so that we have NodeDefs for them
|
|
310
|
+
const defaultLightRigXml = `<?xml version="1.0"?>
|
|
311
|
+
<materialx version="1.39">
|
|
312
|
+
<directional_light name="default_directional_light" type="lightshader">
|
|
313
|
+
</directional_light>
|
|
314
|
+
<point_light name="default_point_light" type="lightshader">
|
|
315
|
+
</point_light>
|
|
316
|
+
<spot_light name="default_spot_light" type="lightshader">
|
|
317
|
+
</spot_light>
|
|
318
|
+
<!--
|
|
319
|
+
<area_light name="default_area_light" type="lightshader">
|
|
320
|
+
</area_light>
|
|
321
|
+
-->
|
|
322
|
+
</materialx>`;
|
|
323
|
+
|
|
324
|
+
// Load default light rig XML to ensure we have all light types available
|
|
325
|
+
const lightRigDoc = mx.createDocument();
|
|
326
|
+
await mx.readFromXmlString(lightRigDoc, defaultLightRigXml);
|
|
327
|
+
const document = mx.createDocument();
|
|
328
|
+
const stdlib = mx.loadStandardLibraries(genContext);
|
|
329
|
+
document.setDataLibrary(stdlib);
|
|
330
|
+
document.importLibrary(lightRigDoc);
|
|
331
|
+
const defaultLights = findLights(document);
|
|
332
|
+
if (debug) console.log("Default lights in MaterialX document", defaultLights);
|
|
333
|
+
|
|
334
|
+
// Register types only – we get these from the default light rig XML above
|
|
335
|
+
// This is needed to ensure that the light shaders are bound for each light type
|
|
336
|
+
for (let light of defaultLights)
|
|
337
|
+
{
|
|
338
|
+
const lightDef = light.getNodeDef();
|
|
339
|
+
if (debug) console.log("Default light node definition", lightDef);
|
|
340
|
+
if (!lightDef) continue;
|
|
341
|
+
|
|
342
|
+
const lightName = lightDef.getName();
|
|
343
|
+
if (debug) console.log("Registering default light", { lightName, lightDef });
|
|
344
|
+
if (!lightTypesBound[lightName])
|
|
345
|
+
{
|
|
346
|
+
// TODO check if we need to bind light shader for each three.js light instead of once per type
|
|
347
|
+
if (debug) console.log("Bind light shader for node", { lightName, lightId, lightDef });
|
|
348
|
+
lightTypesBound[lightName] = lightId;
|
|
349
|
+
mx.HwShaderGenerator.bindLightShader(lightDef, lightId++, genContext);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (debug) console.log("Light types bound in MaterialX context", lightTypesBound);
|
|
354
|
+
|
|
355
|
+
// MaterialX light nodes
|
|
356
|
+
for (let light of lights)
|
|
357
|
+
{
|
|
358
|
+
// Skip if light does not have a node definition
|
|
359
|
+
if (!("getNodeDef" in light)) continue;
|
|
360
|
+
|
|
361
|
+
let nodeDef = light.getNodeDef();
|
|
362
|
+
let nodeName = nodeDef.getName();
|
|
363
|
+
if (!lightTypesBound[nodeName])
|
|
364
|
+
{
|
|
365
|
+
if (debug) console.log("bind light shader for node", { nodeName, lightId, nodeDef });
|
|
366
|
+
lightTypesBound[nodeName] = lightId;
|
|
367
|
+
mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const lightDirection = light.getValueElement("direction").getValue().getData().data();
|
|
371
|
+
const lightColor = light.getValueElement("color").getValue().getData().data();
|
|
372
|
+
const lightIntensity = light.getValueElement("intensity").getValue().getData();
|
|
373
|
+
|
|
374
|
+
let rotatedLightDirection = new THREE.Vector3(...lightDirection)
|
|
375
|
+
rotatedLightDirection.transformDirection(getLightRotation())
|
|
376
|
+
|
|
377
|
+
lightData.push({
|
|
378
|
+
type: lightTypesBound[nodeName],
|
|
379
|
+
direction: rotatedLightDirection,
|
|
380
|
+
color: new THREE.Vector3(...lightColor),
|
|
381
|
+
intensity: lightIntensity,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const threeLightTypeToMaterialXNodeName = (threeLightType) => {
|
|
386
|
+
switch (threeLightType) {
|
|
387
|
+
case 'PointLight':
|
|
388
|
+
return 'ND_point_light';
|
|
389
|
+
case 'DirectionalLight':
|
|
390
|
+
return 'ND_directional_light';
|
|
391
|
+
case 'SpotLight':
|
|
392
|
+
return 'ND_spot_light';
|
|
393
|
+
default:
|
|
394
|
+
console.warn('MaterialX: Unsupported light type: ' + threeLightType);
|
|
395
|
+
return 'ND_point_light'; // Default to point light
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
if (debug) console.log("Registering lights in MaterialX context", lights, lightData);
|
|
400
|
+
|
|
401
|
+
// Three.js lights
|
|
402
|
+
for (let light of lights) {
|
|
403
|
+
// Skip if light is not a Three.js light
|
|
404
|
+
if (!light.isLight) continue;
|
|
405
|
+
|
|
406
|
+
// Types in MaterialX: point_light, directional_light, spot_light
|
|
407
|
+
|
|
408
|
+
const lightDefinitionName = threeLightTypeToMaterialXNodeName(light.type);
|
|
409
|
+
|
|
410
|
+
if (!lightTypesBound[lightDefinitionName])
|
|
411
|
+
{
|
|
412
|
+
lightTypesBound[lightDefinitionName] = lightId;
|
|
413
|
+
const nodeDef = null;
|
|
414
|
+
mx.HwShaderGenerator.bindLightShader(nodeDef, lightId++, genContext);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
lightData.push({
|
|
418
|
+
type: lightTypesBound[lightDefinitionName],
|
|
419
|
+
direction: light.direction?.clone() || new THREE.Vector3(0, -1, 0),
|
|
420
|
+
color: new THREE.Vector3().fromArray(light.color.toArray()),
|
|
421
|
+
intensity: light.intensity,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Make sure max light count is large enough
|
|
426
|
+
genContext.getOptions().hwMaxActiveLightSources = Math.max(genContext.getOptions().hwMaxActiveLightSources, lightData.length);
|
|
427
|
+
|
|
428
|
+
return lightData;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get uniform values for a shader
|
|
433
|
+
* @param {mx.shaderStage} shaderStage
|
|
434
|
+
* @param {THREE.TextureLoader} textureLoader
|
|
435
|
+
*/
|
|
436
|
+
export function getUniformValues(shaderStage, textureLoader, searchPath, flipY)
|
|
437
|
+
{
|
|
438
|
+
let threeUniforms = {};
|
|
439
|
+
|
|
440
|
+
const uniformBlocks = Object.values(shaderStage.getUniformBlocks());
|
|
441
|
+
uniformBlocks.forEach(uniforms =>
|
|
442
|
+
{
|
|
443
|
+
if (!uniforms.empty())
|
|
444
|
+
{
|
|
445
|
+
for (let i = 0; i < uniforms.size(); ++i)
|
|
446
|
+
{
|
|
447
|
+
const variable = uniforms.get(i);
|
|
448
|
+
const value = variable.getValue()?.getData();
|
|
449
|
+
const name = variable.getVariable();
|
|
450
|
+
threeUniforms[name] = new THREE.Uniform(toThreeUniform(variable.getType().getName(), value, name, uniforms,
|
|
451
|
+
textureLoader, searchPath, flipY));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
return threeUniforms;
|
|
457
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { Group, Camera, Material, Mesh, Object3D } from "three";
|
|
4
|
+
import { Context, GLTF, addCustomExtensionPlugin, Component, INeedleGLTFExtensionPlugin } from "@needle-tools/engine";
|
|
5
|
+
import type { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
6
|
+
import type { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
|
|
7
|
+
import { MaterialXLoader } from "./loader.three.js";
|
|
8
|
+
import { debug } from "../utils.js";
|
|
9
|
+
import { state } from "../materialx.js";
|
|
10
|
+
|
|
11
|
+
//@dont-generate-component
|
|
12
|
+
export class MaterialXUniformUpdate extends Component {
|
|
13
|
+
|
|
14
|
+
static updateMaterial(mat: Material | Material[], object: Object3D, camera: Camera) {
|
|
15
|
+
if (Array.isArray(mat)) {
|
|
16
|
+
mat.forEach(m => {
|
|
17
|
+
if (m.userData?.updateUniforms) {
|
|
18
|
+
m.userData.updateUniforms(object, camera);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
} else if (mat.userData?.updateUniforms) {
|
|
22
|
+
mat.userData.updateUniforms(object, camera);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onEnable(): void {
|
|
27
|
+
this.context.addBeforeRenderListener(this.gameObject, this._onBeforeRender);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onDisable(): void {
|
|
31
|
+
this.context.removeBeforeRenderListener(this.gameObject, this._onBeforeRender);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_onBeforeRender = () => {
|
|
35
|
+
// Update uniforms or perform any pre-render logic here
|
|
36
|
+
const gameObject = this.gameObject as any as Mesh;
|
|
37
|
+
const material = gameObject?.material;
|
|
38
|
+
|
|
39
|
+
const camera = this.context.mainCamera;
|
|
40
|
+
if (!camera) return;
|
|
41
|
+
|
|
42
|
+
MaterialXUniformUpdate.updateMaterial(material, gameObject, camera);
|
|
43
|
+
|
|
44
|
+
// If this is a Group, we need to update all direct children
|
|
45
|
+
if ((gameObject as any as Group).isGroup) {
|
|
46
|
+
gameObject.children.forEach((child: Object3D) => {
|
|
47
|
+
if (child instanceof Mesh && child.material)
|
|
48
|
+
MaterialXUniformUpdate.updateMaterial(child.material, child, camera);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
55
|
+
name = "MaterialXLoaderPlugin";
|
|
56
|
+
|
|
57
|
+
mtlxLoader: MaterialXLoader | null = null;
|
|
58
|
+
|
|
59
|
+
onImport = (loader: GLTFLoader, url: string, context: Context) => {
|
|
60
|
+
if (debug) console.log("MaterialXLoaderPlugin: Registering MaterialX extension for", url);
|
|
61
|
+
|
|
62
|
+
// Register the MaterialX loader extension
|
|
63
|
+
// Environment initialization is now handled in the MaterialXLoader constructor
|
|
64
|
+
loader.register(p => {
|
|
65
|
+
this.mtlxLoader = new MaterialXLoader(p, context);
|
|
66
|
+
return this.mtlxLoader;
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
onLoaded = (url: string, gltf: GLTF, _context: Context) => {
|
|
71
|
+
if (debug) console.log("MaterialXLoaderPlugin: glTF loaded", url, gltf.scene);
|
|
72
|
+
|
|
73
|
+
// Set up onBeforeRender callbacks for objects with MaterialX materials
|
|
74
|
+
// This ensures uniforms are updated properly during rendering
|
|
75
|
+
gltf.scene.traverse((child) => {
|
|
76
|
+
if ((child as any).isMesh) {
|
|
77
|
+
const mesh = child as Mesh;
|
|
78
|
+
const material = mesh.material as Material;
|
|
79
|
+
|
|
80
|
+
if (material?.userData?.updateUniforms) {
|
|
81
|
+
if (debug) console.log("Adding MaterialX uniform update component to:", child.name);
|
|
82
|
+
child.addComponent(MaterialXUniformUpdate);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (debug) console.log("Loaded: ", this.mtlxLoader);
|
|
88
|
+
|
|
89
|
+
// Initialize MaterialX lighting system with scene data
|
|
90
|
+
const environment = state.materialXEnvironment;
|
|
91
|
+
environment.initializeFromContext().then(() => {
|
|
92
|
+
if (this.mtlxLoader) {
|
|
93
|
+
this.mtlxLoader.updateLightingFromEnvironment(environment);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
onExport = (_exporter: GLTFExporter, _context: Context) => {
|
|
99
|
+
console.log("TODO: MaterialXLoaderPlugin: Setting up export extensions");
|
|
100
|
+
// TODO: Add MaterialX export functionality if needed
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function registerNeedleLoader() {
|
|
105
|
+
addCustomExtensionPlugin(new MaterialXLoaderPlugin());
|
|
106
|
+
}
|