@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.
- package/CHANGELOG.md +6 -0
- package/bin/.gitattributes +4 -0
- package/bin/README.md +6 -0
- package/{index.ts → index.d.ts} +1 -1
- package/index.js +2 -0
- package/{needle.ts → needle.d.ts} +1 -1
- package/needle.js +2 -0
- package/package.json +24 -10
- package/src/index.d.ts +11 -0
- package/src/index.js +11 -0
- package/src/loader/loader.needle.d.ts +15 -0
- package/src/loader/loader.needle.js +62 -0
- package/src/loader/loader.three.d.ts +71 -0
- package/src/loader/loader.three.js +334 -0
- package/src/materialx.d.ts +60 -0
- package/src/materialx.helper.d.ts +31 -0
- package/src/{materialx.helper.ts → materialx.helper.js} +149 -101
- package/src/{materialx.ts → materialx.js} +115 -64
- package/src/materialx.material.d.ts +37 -0
- package/src/{materialx.material.ts → materialx.material.js} +110 -40
- package/src/utils.d.ts +17 -0
- package/src/{utils.ts → utils.js} +21 -4
- package/src/utils.texture.d.ts +13 -0
- package/src/{textureHelper.ts → utils.texture.js} +18 -17
- package/src/index.ts +0 -3
- package/src/loader/loader.needle.ts +0 -43
- package/src/loader/loader.three.ts +0 -322
- package/tsconfig.json +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.2.0] - 2025-07-23
|
|
8
|
+
- Add: Support to load raw MaterialX materials (from mtlx as XML)
|
|
9
|
+
- Fix: Warn if tangents are missing
|
|
10
|
+
- Fix: Improve unsupported light handling
|
|
11
|
+
- Change: Refactor library to js + jsdoc
|
|
12
|
+
|
|
7
13
|
## [1.1.0] - 2025-07-15
|
|
8
14
|
- Add: `useNeedleMaterialX` hooks for vanilla three.js and Needle Engine
|
|
9
15
|
|
package/bin/README.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Source: https://github.com/AcademySoftwareFoundation/MaterialX/tree/gh-pages
|
|
2
|
+
|
|
3
|
+
Edits:
|
|
4
|
+
|
|
5
|
+
- In `JsMaterialXGenShader.js` added `export default MaterialX;` at bottom
|
|
6
|
+
- Renamed `JsMaterialXGenShader.data` to `JsMaterialXGenShader.data.txt` so it can be loaded by vite etc
|
package/{index.ts → index.d.ts}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from "./src/index.js";
|
|
2
|
-
export { useNeedleMaterialX } from "./src/loader/loader.three.js";
|
|
2
|
+
export { useNeedleMaterialX } from "./src/loader/loader.three.js";
|
package/index.js
ADDED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * from "./src/index.js";
|
|
2
|
-
export { useNeedleMaterialX } from "./src/loader/loader.needle.js";
|
|
2
|
+
export { useNeedleMaterialX } from "./src/loader/loader.needle.js";
|
package/needle.js
ADDED
package/package.json
CHANGED
|
@@ -1,32 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/materialx",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "index.
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": {
|
|
8
|
-
"import": "./index.
|
|
9
|
-
"require": "./index.js"
|
|
9
|
+
"import": "./index.js",
|
|
10
|
+
"require": "./index.js",
|
|
11
|
+
"types": "./index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./needle": {
|
|
14
|
+
"import": "./needle.js",
|
|
15
|
+
"require": "./needle.js",
|
|
16
|
+
"types": "./needle.d.ts"
|
|
10
17
|
},
|
|
11
18
|
"./package.json": "./package.json",
|
|
12
19
|
"./codegen/register_types.ts": {
|
|
13
20
|
"import": "./codegen/register_types.ts",
|
|
14
21
|
"require": "./codegen/register_types.js"
|
|
15
|
-
},
|
|
16
|
-
"./needle": {
|
|
17
|
-
"import": "./needle.ts",
|
|
18
|
-
"require": "./needle.js"
|
|
19
22
|
}
|
|
20
23
|
},
|
|
21
24
|
"peerDependencies": {
|
|
22
|
-
"
|
|
23
|
-
"three": ">=0.169.0"
|
|
25
|
+
"three": ">=0.160.0"
|
|
24
26
|
},
|
|
25
27
|
"devDependencies": {
|
|
26
28
|
"@needle-tools/engine": "4.x",
|
|
27
29
|
"@types/three": "0.169.0",
|
|
28
30
|
"three": "npm:@needle-tools/three@^0.169.5"
|
|
29
31
|
},
|
|
32
|
+
"files": [
|
|
33
|
+
"index.js",
|
|
34
|
+
"index.d.ts",
|
|
35
|
+
"needle.js",
|
|
36
|
+
"needle.d.ts",
|
|
37
|
+
"src/",
|
|
38
|
+
"bin/",
|
|
39
|
+
"codegen/",
|
|
40
|
+
"README.md",
|
|
41
|
+
"CHANGELOG.md",
|
|
42
|
+
"package.needle.json"
|
|
43
|
+
],
|
|
30
44
|
"publishConfig": {
|
|
31
45
|
"access": "public",
|
|
32
46
|
"registry": "https://registry.npmjs.org/"
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { ready, type MaterialXContext } from "./materialx.js";
|
|
2
|
+
export { MaterialXMaterial } from "./materialx.material.js";
|
|
3
|
+
export { MaterialXLoader } from "./loader/loader.three.js";
|
|
4
|
+
|
|
5
|
+
import { createMaterialXMaterial } from "./loader/loader.three.js";
|
|
6
|
+
|
|
7
|
+
declare const Experimental_API: {
|
|
8
|
+
createMaterialXMaterial: typeof createMaterialXMaterial;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { Experimental_API };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { ready } from "./materialx.js";
|
|
2
|
+
export { MaterialXMaterial } from "./materialx.material.js";
|
|
3
|
+
export { MaterialXLoader } from "./loader/loader.three.js";
|
|
4
|
+
|
|
5
|
+
import { createMaterialXMaterial } from "./loader/loader.three.js";
|
|
6
|
+
|
|
7
|
+
const Experimental_API = {
|
|
8
|
+
createMaterialXMaterial
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export { Experimental_API }
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Context, GLTF, INeedleGLTFExtensionPlugin } from "@needle-tools/engine";
|
|
2
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
|
+
import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
|
|
4
|
+
import { MaterialXLoader } from "./loader.three.js";
|
|
5
|
+
|
|
6
|
+
export declare class MaterialXLoaderPlugin implements INeedleGLTFExtensionPlugin {
|
|
7
|
+
readonly name: "MaterialXLoaderPlugin";
|
|
8
|
+
private loader: MaterialXLoader | null;
|
|
9
|
+
|
|
10
|
+
onImport(loader: GLTFLoader, url: string, context: Context): void;
|
|
11
|
+
onLoaded(url: string, gltf: GLTF, _context: Context): void;
|
|
12
|
+
onExport(_exporter: GLTFExporter, _context: Context): void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare function useNeedleMaterialX(): Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { addCustomExtensionPlugin, Context } from "@needle-tools/engine";
|
|
2
|
+
import { useNeedleMaterialX as _useNeedleMaterialX } from "./loader.three.js";
|
|
3
|
+
import { debug } from "../utils.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import("@needle-tools/engine").INeedleGLTFExtensionPlugin} INeedleGLTFExtensionPlugin
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* MaterialX Loader Plugin for Needle Engine
|
|
11
|
+
* @implements {INeedleGLTFExtensionPlugin}
|
|
12
|
+
*/
|
|
13
|
+
export class MaterialXLoaderPlugin {
|
|
14
|
+
/** @readonly */
|
|
15
|
+
name = "MaterialXLoaderPlugin";
|
|
16
|
+
|
|
17
|
+
/** @type {import("./loader.three.d.ts").MaterialXLoader | null} */
|
|
18
|
+
loader = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {import('three/examples/jsm/loaders/GLTFLoader.js').GLTFLoader} loader
|
|
22
|
+
* @param {string} url
|
|
23
|
+
* @param {Context} context
|
|
24
|
+
*/
|
|
25
|
+
onImport = (loader, url, context) => {
|
|
26
|
+
if (debug) console.log("MaterialXLoaderPlugin: Registering MaterialX extension for", url);
|
|
27
|
+
_useNeedleMaterialX(loader, {
|
|
28
|
+
cacheKey: url,
|
|
29
|
+
parameters: {
|
|
30
|
+
precision: /** @type {import('three').MaterialParameters["precision"]} */ (context.renderer.capabilities.getMaxPrecision("highp")),
|
|
31
|
+
}
|
|
32
|
+
}, {
|
|
33
|
+
getTime: () => context.time.time,
|
|
34
|
+
getFrame: () => context.time.frame,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} url
|
|
40
|
+
* @param {import("@needle-tools/engine").GLTF} gltf
|
|
41
|
+
* @param {Context} _context
|
|
42
|
+
*/
|
|
43
|
+
onLoaded = (url, gltf, _context) => {
|
|
44
|
+
if (debug) console.log("[MaterialX] MaterialXLoaderPlugin: glTF loaded", { url, scene: gltf.scene, materialX_root_data: this.loader?.materialX_root_data });
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {import('three/examples/jsm/exporters/GLTFExporter.js').GLTFExporter} _exporter
|
|
49
|
+
* @param {Context} _context
|
|
50
|
+
*/
|
|
51
|
+
onExport = (_exporter, _context) => {
|
|
52
|
+
console.warn("[MaterialX] Export is not supported");
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Add the MaterialXLoaderPlugin to the Needle Engine.
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
export async function useNeedleMaterialX() {
|
|
61
|
+
addCustomExtensionPlugin(new MaterialXLoaderPlugin());
|
|
62
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Material, MaterialParameters } from "three";
|
|
2
|
+
import { GLTFLoader, GLTFLoaderPlugin, GLTFParser } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
|
+
import { MaterialXContext } from "../materialx.js";
|
|
4
|
+
import { MaterialXMaterial } from "../materialx.material.js";
|
|
5
|
+
import { Callbacks } from "../materialx.helper.js";
|
|
6
|
+
|
|
7
|
+
export interface MaterialX_root_extension {
|
|
8
|
+
/** e.g. 1.39 */
|
|
9
|
+
version: string;
|
|
10
|
+
/** e.g. "Material" */
|
|
11
|
+
name: string;
|
|
12
|
+
/** MaterialX xml content */
|
|
13
|
+
mtlx: string;
|
|
14
|
+
/** MaterialX texture pointers */
|
|
15
|
+
textures: Array<{ name: string, pointer: string }>;
|
|
16
|
+
shaders?: Array<{
|
|
17
|
+
/** The materialx node name */
|
|
18
|
+
name: string;
|
|
19
|
+
/** The original name of the shader */
|
|
20
|
+
originalName: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MaterialX_material_extension {
|
|
25
|
+
/** The MaterialX material name */
|
|
26
|
+
name: string;
|
|
27
|
+
/** The index of the shader in the shaders array of the root extension. */
|
|
28
|
+
shader?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MaterialXLoaderOptions {
|
|
32
|
+
/** The URL of the GLTF file being loaded */
|
|
33
|
+
cacheKey?: string;
|
|
34
|
+
/** Parameters for the MaterialX loader */
|
|
35
|
+
parameters?: Pick<MaterialParameters, "precision">;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export declare class MaterialXLoader implements GLTFLoaderPlugin {
|
|
39
|
+
readonly name: "NEEDLE_materials_mtlx";
|
|
40
|
+
private readonly _generatedMaterials: MaterialXMaterial[];
|
|
41
|
+
private _documentReadyPromise: Promise<any> | null;
|
|
42
|
+
private parser: GLTFParser;
|
|
43
|
+
private options: MaterialXLoaderOptions;
|
|
44
|
+
private context: MaterialXContext;
|
|
45
|
+
|
|
46
|
+
get materialX_root_data(): MaterialX_root_extension | null;
|
|
47
|
+
get materials(): MaterialXMaterial[];
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
parser: GLTFParser,
|
|
51
|
+
options: MaterialXLoaderOptions,
|
|
52
|
+
context: MaterialXContext
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
loadMaterial(materialIndex: number): Promise<Material> | null;
|
|
56
|
+
private _loadMaterialAsync(materialIndex: number): Promise<Material>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export declare function useNeedleMaterialX(
|
|
60
|
+
loader: GLTFLoader,
|
|
61
|
+
options?: MaterialXLoaderOptions,
|
|
62
|
+
context?: MaterialXContext
|
|
63
|
+
): void;
|
|
64
|
+
|
|
65
|
+
export declare function createMaterialXMaterial(
|
|
66
|
+
mtlx: string,
|
|
67
|
+
materialNodeName: string,
|
|
68
|
+
loaders: Callbacks,
|
|
69
|
+
options?: MaterialXLoaderOptions,
|
|
70
|
+
context?: MaterialXContext
|
|
71
|
+
): Promise<Material>;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { Material, MeshStandardMaterial, DoubleSide, FrontSide } from "three";
|
|
2
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
3
|
+
import { ready, state } from "../materialx.js";
|
|
4
|
+
import { debug } from "../utils.js";
|
|
5
|
+
import { MaterialXMaterial } from "../materialx.material.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @import { MaterialX_root_extension, MaterialX_material_extension, MaterialXLoaderOptions } from "./loader.three.d.ts"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} MaterialDefinition
|
|
13
|
+
* @property {string} [name] - Optional name for the material
|
|
14
|
+
* @property {boolean} [doubleSided] - Whether the material is double-sided
|
|
15
|
+
* @property {Object<string, any>} [extensions] - Extensions for the material, including MaterialX
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} MaterialXMaterialOptions
|
|
20
|
+
* @property {import('three').MaterialParameters} [parameters]
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
// MaterialX loader extension for js GLTFLoader
|
|
24
|
+
export class MaterialXLoader {
|
|
25
|
+
/** @readonly */
|
|
26
|
+
name = "NEEDLE_materials_mtlx";
|
|
27
|
+
|
|
28
|
+
/** @type {MaterialXMaterial[]} */
|
|
29
|
+
_generatedMaterials = [];
|
|
30
|
+
|
|
31
|
+
/** @type {Promise<any> | null} */
|
|
32
|
+
_documentReadyPromise = null;
|
|
33
|
+
|
|
34
|
+
get materialX_root_data() {
|
|
35
|
+
return /** @type {MaterialX_root_extension | null} */ (this.parser.json.extensions?.[this.name]) || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Generated materialX materials */
|
|
39
|
+
get materials() {
|
|
40
|
+
return this._generatedMaterials;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* MaterialXLoader constructor
|
|
45
|
+
* @param {import('three/examples/jsm/loaders/GLTFLoader.js').GLTFParser} parser - The GLTFParser instance
|
|
46
|
+
* @param {MaterialXLoaderOptions} options - The loader options
|
|
47
|
+
* @param {import('../materialx.js').MaterialXContext} context - The context for the GLTF loading process
|
|
48
|
+
*/
|
|
49
|
+
constructor(parser, options, context) {
|
|
50
|
+
this.parser = parser;
|
|
51
|
+
this.options = options;
|
|
52
|
+
this.context = context;
|
|
53
|
+
|
|
54
|
+
if (debug) console.log("MaterialXLoader created for parser");
|
|
55
|
+
// Start loading of MaterialX environment if the root extension exists
|
|
56
|
+
if (this.materialX_root_data) {
|
|
57
|
+
ready();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {number} materialIndex
|
|
63
|
+
* @returns {Promise<Material> | null}
|
|
64
|
+
*/
|
|
65
|
+
loadMaterial(materialIndex) {
|
|
66
|
+
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
67
|
+
if (!materialDef?.extensions?.[this.name]) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// Wrap the async implementation
|
|
71
|
+
return this._loadMaterialAsync(materialIndex);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @private
|
|
76
|
+
* @param {number} materialIndex
|
|
77
|
+
* @returns {Promise<Material>}
|
|
78
|
+
*/
|
|
79
|
+
async _loadMaterialAsync(materialIndex) {
|
|
80
|
+
|
|
81
|
+
/** @type {MaterialDefinition} */
|
|
82
|
+
const materialDef = this.parser.json.materials?.[materialIndex];
|
|
83
|
+
if (debug) console.log("[MaterialX] extension found in material:", materialDef.extensions?.[this.name]);
|
|
84
|
+
|
|
85
|
+
// Handle different types of MaterialX data
|
|
86
|
+
/** @type {MaterialX_material_extension} */
|
|
87
|
+
const ext = materialDef.extensions?.[this.name];
|
|
88
|
+
|
|
89
|
+
const mtlx = this.materialX_root_data?.mtlx;
|
|
90
|
+
|
|
91
|
+
if (ext && mtlx) {
|
|
92
|
+
|
|
93
|
+
/** @type {MaterialXMaterialOptions} */
|
|
94
|
+
const materialOptions = {
|
|
95
|
+
...this.options,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!materialOptions.parameters) materialOptions.parameters = {};
|
|
99
|
+
|
|
100
|
+
if (materialOptions.parameters?.side === undefined && materialDef.doubleSided !== undefined) {
|
|
101
|
+
materialOptions.parameters.side = materialDef.doubleSided ? DoubleSide : FrontSide;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return createMaterialXMaterial(mtlx, ext.name, {
|
|
105
|
+
cacheKey: this.options.cacheKey || "",
|
|
106
|
+
getTexture: async url => {
|
|
107
|
+
// Find the index of the texture in the parser
|
|
108
|
+
const filenameWithoutExt = url.split('/').pop()?.split('.').shift() || '';
|
|
109
|
+
|
|
110
|
+
// Resolve the texture from the MaterialX root extension
|
|
111
|
+
if (this.materialX_root_data) {
|
|
112
|
+
const textures = this.materialX_root_data.textures || [];
|
|
113
|
+
let index = -1;
|
|
114
|
+
for (const texture of textures) {
|
|
115
|
+
// Find the texture by name and use the pointer string to get the index
|
|
116
|
+
if (texture.name === filenameWithoutExt) {
|
|
117
|
+
const ptr = texture.pointer;
|
|
118
|
+
const indexStr = ptr.substring("/textures/".length);
|
|
119
|
+
index = parseInt(indexStr);
|
|
120
|
+
|
|
121
|
+
if (isNaN(index) || index < 0) {
|
|
122
|
+
console.error("[MaterialX] Invalid texture index in pointer:", ptr);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
if (debug) console.log("[MaterialX] Texture index found:", index, "for", filenameWithoutExt);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (index < 0) {
|
|
132
|
+
console.error("[MaterialX] Texture not found in parser:", filenameWithoutExt, this.parser.json);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
return this.parser.getDependency("texture", index);
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}, materialOptions, this.context)
|
|
140
|
+
// Cache and return the generated material
|
|
141
|
+
.then(mat => {
|
|
142
|
+
if (mat instanceof MaterialXMaterial) this._generatedMaterials.push(mat);
|
|
143
|
+
return mat;
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Return fallback material instead of null
|
|
148
|
+
const fallbackMaterial = new MeshStandardMaterial();
|
|
149
|
+
fallbackMaterial.name = "MaterialX_Fallback";
|
|
150
|
+
return fallbackMaterial;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Add the MaterialXLoader to the GLTFLoader instance.
|
|
156
|
+
* @param {GLTFLoader} loader
|
|
157
|
+
* @param {MaterialXLoaderOptions} [options]
|
|
158
|
+
* @param {import('../materialx.js').MaterialXContext} [context]
|
|
159
|
+
*/
|
|
160
|
+
export function useNeedleMaterialX(loader, options, context) {
|
|
161
|
+
loader.register(p => {
|
|
162
|
+
const loader = new MaterialXLoader(p, options || {}, context || {});
|
|
163
|
+
return loader;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parse the MaterialX document once and cache it
|
|
169
|
+
* @param {string} mtlx
|
|
170
|
+
* @returns {Promise<any>}
|
|
171
|
+
*/
|
|
172
|
+
async function load(mtlx) {
|
|
173
|
+
// Ensure MaterialX is initialized
|
|
174
|
+
await ready();
|
|
175
|
+
if (!state.materialXModule) {
|
|
176
|
+
throw new Error("[MaterialX] module failed to initialize");
|
|
177
|
+
}
|
|
178
|
+
// Create MaterialX document and parse ALL the XML data from root
|
|
179
|
+
const doc = state.materialXModule.createDocument();
|
|
180
|
+
doc.setDataLibrary(state.materialXStdLib);
|
|
181
|
+
// Parse all MaterialX XML strings from the root data
|
|
182
|
+
await state.materialXModule.readFromXmlString(doc, mtlx, "");
|
|
183
|
+
if (debug) console.log("[MaterialX] root document parsed successfully");
|
|
184
|
+
return doc;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {string} mtlx
|
|
189
|
+
* @param {string} materialNodeName
|
|
190
|
+
* @param {import('../materialx.helper.js').Callbacks} loaders
|
|
191
|
+
* @param {MaterialXLoaderOptions} [options]
|
|
192
|
+
* @param {import('../materialx.js').MaterialXContext} [context]
|
|
193
|
+
* @returns {Promise<Material>}
|
|
194
|
+
*/
|
|
195
|
+
export async function createMaterialXMaterial(mtlx, materialNodeName, loaders, options, context) {
|
|
196
|
+
try {
|
|
197
|
+
if (debug) console.log(`Creating MaterialX material: ${materialNodeName}`);
|
|
198
|
+
|
|
199
|
+
const doc = await load(mtlx);
|
|
200
|
+
|
|
201
|
+
if (!state.materialXModule || !state.materialXGenerator || !state.materialXGenContext) {
|
|
202
|
+
console.warn("[MaterialX] WASM module not ready, returning fallback material");
|
|
203
|
+
const fallbackMaterial = new MeshStandardMaterial();
|
|
204
|
+
fallbackMaterial.name = `MaterialX_Fallback_${materialNodeName}`;
|
|
205
|
+
return fallbackMaterial;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Find the renderable element following MaterialX example pattern exactly
|
|
209
|
+
let renderableElement = null;
|
|
210
|
+
let foundRenderable = false;
|
|
211
|
+
|
|
212
|
+
if (debug) console.log("[MaterialX] document", doc);
|
|
213
|
+
|
|
214
|
+
// Search for material nodes first (following the reference pattern)
|
|
215
|
+
const materialNodes = doc.getMaterialNodes();
|
|
216
|
+
if (debug) console.log(`[MaterialX] Found ${materialNodes.length} material nodes in document`, materialNodes);
|
|
217
|
+
|
|
218
|
+
// Handle both array and vector-like APIs
|
|
219
|
+
for (let i = 0; i < materialNodes.length; ++i) {
|
|
220
|
+
const materialNode = materialNodes[i];
|
|
221
|
+
if (materialNode) {
|
|
222
|
+
const name = materialNode.getNamePath();
|
|
223
|
+
if (debug) console.log(`[MaterialX] Scan material[${i}]: ${name}`);
|
|
224
|
+
|
|
225
|
+
// Find the matching material
|
|
226
|
+
if (materialNodes.length === 1 || name == materialNodeName) {
|
|
227
|
+
materialNodeName = name;
|
|
228
|
+
renderableElement = materialNode;
|
|
229
|
+
foundRenderable = true;
|
|
230
|
+
if (debug) console.log(`[MaterialX] Use material node: '${name}'`);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/*
|
|
237
|
+
// If no material nodes found, search nodeGraphs
|
|
238
|
+
if (!foundRenderable) {
|
|
239
|
+
const nodeGraphs = doc.getNodeGraphs();
|
|
240
|
+
console.log(`Found ${nodeGraphs.length} node graphs in document`);
|
|
241
|
+
const nodeGraphsLength = nodeGraphs.length;
|
|
242
|
+
for (let i = 0; i < nodeGraphsLength; ++i) {
|
|
243
|
+
const nodeGraph = nodeGraphs[i];
|
|
244
|
+
if (nodeGraph) {
|
|
245
|
+
// Skip any nodegraph that has nodedef or sourceUri
|
|
246
|
+
if ((nodeGraph as any).hasAttribute('nodedef') || (nodeGraph as any).hasSourceUri()) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
// Skip any nodegraph that is connected to something downstream
|
|
250
|
+
if ((nodeGraph as any).getDownstreamPorts().length > 0) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const outputs = (nodeGraph as any).getOutputs();
|
|
254
|
+
for (let j = 0; j < outputs.length; ++j) {
|
|
255
|
+
const output = outputs[j];
|
|
256
|
+
if (output && !foundRenderable) {
|
|
257
|
+
renderableElement = output;
|
|
258
|
+
foundRenderable = true;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (foundRenderable) break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// If still no element found, search document outputs
|
|
268
|
+
if (!foundRenderable) {
|
|
269
|
+
const outputs = doc.getOutputs();
|
|
270
|
+
console.log(`Found ${outputs.length} output nodes in document`);
|
|
271
|
+
const outputsLength = outputs.length;
|
|
272
|
+
for (let i = 0; i < outputsLength; ++i) {
|
|
273
|
+
const output = outputs[i];
|
|
274
|
+
if (output && !foundRenderable) {
|
|
275
|
+
renderableElement = output;
|
|
276
|
+
foundRenderable = true;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
*/
|
|
282
|
+
|
|
283
|
+
if (!renderableElement) {
|
|
284
|
+
console.warn(`[MaterialX] No renderable element found in MaterialX document (${materialNodeName})`);
|
|
285
|
+
const fallbackMaterial = new MeshStandardMaterial();
|
|
286
|
+
fallbackMaterial.color.set(0xff00ff);
|
|
287
|
+
fallbackMaterial.name = `MaterialX_NoRenderable_${materialNodeName}`;
|
|
288
|
+
return fallbackMaterial;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (debug) console.log("[MaterialX] Using renderable element for shader generation");
|
|
292
|
+
|
|
293
|
+
// Check transparency and set context options like the reference
|
|
294
|
+
const isTransparent = state.materialXModule.isTransparentSurface(renderableElement, state.materialXGenerator.getTarget());
|
|
295
|
+
state.materialXGenContext.getOptions().hwTransparency = isTransparent;
|
|
296
|
+
|
|
297
|
+
// Generate shaders using the element's name path
|
|
298
|
+
if (debug) console.log("[MaterialX] Generating MaterialX shaders...");
|
|
299
|
+
const elementName = renderableElement.getNamePath ? renderableElement.getNamePath() : renderableElement.getName();
|
|
300
|
+
|
|
301
|
+
const shader = state.materialXGenerator.generate(elementName, renderableElement, state.materialXGenContext);
|
|
302
|
+
|
|
303
|
+
// const rootExtension = this.materialX_root_data;
|
|
304
|
+
|
|
305
|
+
// const shaderInfo = rootExtension && material_extension.shader !== undefined && material_extension.shader >= 0
|
|
306
|
+
// ? rootExtension.shaders?.[material_extension.shader]
|
|
307
|
+
// : null;
|
|
308
|
+
|
|
309
|
+
const shaderMaterial = new MaterialXMaterial({
|
|
310
|
+
name: materialNodeName,
|
|
311
|
+
shaderName: null, //shaderInfo?.originalName || shaderInfo?.name || null,
|
|
312
|
+
shader,
|
|
313
|
+
context: context || {},
|
|
314
|
+
parameters: {
|
|
315
|
+
transparent: isTransparent,
|
|
316
|
+
...options?.parameters,
|
|
317
|
+
},
|
|
318
|
+
loaders: loaders,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Add debugging to see if the material compiles correctly
|
|
322
|
+
if (debug) console.log("[MaterialX] material created:", shaderMaterial.name);
|
|
323
|
+
return shaderMaterial;
|
|
324
|
+
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// This is a wasm error (an int) that we need to resolve
|
|
327
|
+
console.error(`[MaterialX] Error creating MaterialX material (${materialNodeName}):`, error);
|
|
328
|
+
// Return a fallback material with stored MaterialX data
|
|
329
|
+
const fallbackMaterial = new MeshStandardMaterial();
|
|
330
|
+
fallbackMaterial.color.set(0xff00ff);
|
|
331
|
+
fallbackMaterial.name = `MaterialX_Error_${materialNodeName}`;
|
|
332
|
+
return fallbackMaterial;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Light, Scene, Texture, WebGLRenderer } from "three";
|
|
2
|
+
import type { MaterialX as MX } from "./materialx.types.js";
|
|
3
|
+
|
|
4
|
+
export type MaterialXContext = {
|
|
5
|
+
getTime?(): number;
|
|
6
|
+
getFrame?(): number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type EnvironmentTextureSet = {
|
|
10
|
+
radianceTexture: Texture | null;
|
|
11
|
+
irradianceTexture: Texture | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export declare const state: {
|
|
15
|
+
materialXModule: MX.MODULE | null;
|
|
16
|
+
materialXGenerator: any | null;
|
|
17
|
+
materialXGenContext: any | null;
|
|
18
|
+
materialXStdLib: any | null;
|
|
19
|
+
materialXInitPromise: Promise<void> | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Wait for the MaterialX WASM module to be ready.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ready(): Promise<void>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* MaterialXEnvironment manages the environment settings for MaterialX materials.
|
|
29
|
+
*/
|
|
30
|
+
export declare class MaterialXEnvironment {
|
|
31
|
+
static get(scene: Scene): MaterialXEnvironment | null;
|
|
32
|
+
private static _environments: WeakMap<Scene, MaterialXEnvironment>;
|
|
33
|
+
private static getEnvironment(scene: Scene): MaterialXEnvironment;
|
|
34
|
+
|
|
35
|
+
private _lights: Array<Light>;
|
|
36
|
+
private _lightData: any[] | null;
|
|
37
|
+
private _lightCount: number;
|
|
38
|
+
private _initializePromise: Promise<boolean> | null;
|
|
39
|
+
private _isInitialized: boolean;
|
|
40
|
+
private _lastUpdateFrame: number;
|
|
41
|
+
private _scene: Scene;
|
|
42
|
+
|
|
43
|
+
constructor(_scene: Scene);
|
|
44
|
+
|
|
45
|
+
initialize(renderer: WebGLRenderer): Promise<boolean>;
|
|
46
|
+
update(frame: number, scene: Scene, renderer: WebGLRenderer): void;
|
|
47
|
+
reset(): void;
|
|
48
|
+
|
|
49
|
+
get lights(): Array<Light>;
|
|
50
|
+
get lightData(): any[] | null;
|
|
51
|
+
get lightCount(): number;
|
|
52
|
+
getTextures(material: any): EnvironmentTextureSet;
|
|
53
|
+
|
|
54
|
+
private _pmremGenerator: any | null;
|
|
55
|
+
private _renderer: WebGLRenderer | null;
|
|
56
|
+
private _texturesCache: Map<Texture | null, EnvironmentTextureSet>;
|
|
57
|
+
private _initialize(renderer: WebGLRenderer): Promise<boolean>;
|
|
58
|
+
private _getTextures(texture: Texture | null | undefined): EnvironmentTextureSet;
|
|
59
|
+
private updateLighting(collectLights?: boolean): void;
|
|
60
|
+
}
|