MaterialXLoader.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. ( function () {
  2. const colorSpaceLib = {
  3. THREE.mx_srgb_texture_to_lin_rec709
  4. };
  5. class MtlXElement {
  6. constructor(name, nodeFunc, params = null) {
  7. this.name = name;
  8. this.nodeFunc = nodeFunc;
  9. this.params = params;
  10. }
  11. }
  12. // Ref: https://github.com/mrdoob/three.js/issues/24674
  13. const MtlXElements = [
  14. // << Math >>
  15. new MtlXElement('add', THREE.add, ['in1', 'in2']), new MtlXElement('subtract', THREE.sub, ['in1', 'in2']), new MtlXElement('multiply', THREE.mul, ['in1', 'in2']), new MtlXElement('divide', THREE.div, ['in1', 'in2']), new MtlXElement('modulo', THREE.mod, ['in1', 'in2']), new MtlXElement('absval', THREE.abs, ['in1', 'in2']), new MtlXElement('sign', THREE.sign, ['in1', 'in2']), new MtlXElement('floor', THREE.floor, ['in1', 'in2']), new MtlXElement('ceil', THREE.ceil, ['in1', 'in2']), new MtlXElement('round', THREE.round, ['in1', 'in2']), new MtlXElement('power', THREE.pow, ['in1', 'in2']), new MtlXElement('sin', THREE.sin, ['in']), new MtlXElement('cos', THREE.cos, ['in']), new MtlXElement('tan', THREE.tan, ['in']), new MtlXElement('asin', THREE.asin, ['in']), new MtlXElement('acos', THREE.acos, ['in']), new MtlXElement('atan2', THREE.atan2, ['in1', 'in2']), new MtlXElement('sqrt', THREE.sqrt, ['in']),
  16. //new MtlXElement( 'ln', ... ),
  17. new MtlXElement('exp', THREE.exp, ['in']), new MtlXElement('clamp', THREE.clamp, ['in', 'low', 'high']), new MtlXElement('min', THREE.min, ['in1', 'in2']), new MtlXElement('max', THREE.max, ['in1', 'in2']), new MtlXElement('normalize', THREE.normalize, ['in']), new MtlXElement('magnitude', THREE.length, ['in1', 'in2']), new MtlXElement('dotproduct', THREE.dot, ['in1', 'in2']), new MtlXElement('crossproduct', THREE.cross, ['in']),
  18. //new MtlXElement( 'transformpoint', ... ),
  19. //new MtlXElement( 'transformvector', ... ),
  20. //new MtlXElement( 'transformnormal', ... ),
  21. //new MtlXElement( 'transformmatrix', ... ),
  22. new MtlXElement('normalmap', THREE.normalMap, ['in', 'scale']),
  23. //new MtlXElement( 'transpose', ... ),
  24. //new MtlXElement( 'determinant', ... ),
  25. //new MtlXElement( 'invertmatrix', ... ),
  26. //new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ),
  27. //new MtlXElement( 'rotate3d', ... ),
  28. //new MtlXElement( 'arrayappend', ... ),
  29. //new MtlXElement( 'dot', ... ),
  30. // << Adjustment >>
  31. new MtlXElement('remap', THREE.remap, ['in', 'inlow', 'inhigh', 'outlow', 'outhigh']), new MtlXElement('smoothstep', THREE.smoothstep, ['in', 'low', 'high']),
  32. //new MtlXElement( 'curveadjust', ... ),
  33. //new MtlXElement( 'curvelookup', ... ),
  34. new MtlXElement('luminance', THREE.luminance, ['in', 'lumacoeffs']), new MtlXElement('rgbtohsv', THREE.mx_rgbtohsv, ['in']), new MtlXElement('hsvtorgb', THREE.mx_hsvtorgb, ['in']),
  35. // << Mix >>
  36. new MtlXElement('mix', THREE.mix, ['bg', 'fg', 'mix']),
  37. // << Channel >>
  38. new MtlXElement('combine2', THREE.vec2, ['in1', 'in2']), new MtlXElement('combine3', THREE.vec3, ['in1', 'in2', 'in3']), new MtlXElement('combine4', THREE.vec4, ['in1', 'in2', 'in3', 'in4']),
  39. // << Procedural >>
  40. new MtlXElement('ramplr', THREE.mx_ramplr, ['valuel', 'valuer', 'texcoord']), new MtlXElement('ramptb', THREE.mx_ramptb, ['valuet', 'valueb', 'texcoord']), new MtlXElement('splitlr', THREE.mx_splitlr, ['valuel', 'valuer', 'texcoord']), new MtlXElement('splittb', THREE.mx_splittb, ['valuet', 'valueb', 'texcoord']), new MtlXElement('noise2d', THREE.mx_noise_float, ['texcoord', 'amplitude', 'pivot']), new MtlXElement('noise3d', THREE.mx_noise_float, ['texcoord', 'amplitude', 'pivot']), new MtlXElement('fractal3d', THREE.mx_fractal_noise_float, ['position', 'octaves', 'lacunarity', 'diminish', 'amplitude']), new MtlXElement('cellnoise2d', THREE.mx_cell_noise_float, ['texcoord']), new MtlXElement('cellnoise3d', THREE.mx_cell_noise_float, ['texcoord']), new MtlXElement('worleynoise2d', THREE.mx_worley_noise_float, ['texcoord', 'jitter']), new MtlXElement('worleynoise3d', THREE.mx_worley_noise_float, ['texcoord', 'jitter']),
  41. // << Supplemental >>
  42. //new MtlXElement( 'tiledimage', ... ),
  43. //new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ),
  44. //new MtlXElement( 'ramp4', ... ),
  45. //new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ),
  46. new MtlXElement('safepower', THREE.mx_safepower, ['in1', 'in2']), new MtlXElement('contrast', THREE.mx_contrast, ['in', 'amount', 'pivot']),
  47. //new MtlXElement( 'hsvadjust', ... ),
  48. new MtlXElement('saturate', THREE.saturation, ['in', 'amount'])
  49. //new MtlXElement( 'extract', ... ),
  50. //new MtlXElement( 'separate2', ... ),
  51. //new MtlXElement( 'separate3', ... ),
  52. //new MtlXElement( 'separate4', ... )
  53. ];
  54. const MtlXLibrary = {};
  55. MtlXElements.forEach(element => MtlXLibrary[element.name] = element);
  56. class MaterialXLoader extends THREE.Loader {
  57. constructor(manager) {
  58. super(manager);
  59. }
  60. load(url, onLoad, onProgress, onError) {
  61. new THREE.FileLoader(this.manager).setPath(this.path).load(url, async text => {
  62. try {
  63. onLoad(this.parse(text));
  64. } catch (e) {
  65. onError(e);
  66. }
  67. }, onProgress, onError);
  68. return this;
  69. }
  70. parse(text) {
  71. return new MaterialX(this.manager, this.path).parse(text);
  72. }
  73. }
  74. class MaterialXNode {
  75. constructor(materialX, nodeXML, nodePath = '') {
  76. this.materialX = materialX;
  77. this.nodeXML = nodeXML;
  78. this.nodePath = nodePath ? nodePath + '/' + this.name : this.name;
  79. this.parent = null;
  80. this.node = null;
  81. this.children = [];
  82. }
  83. get element() {
  84. return this.nodeXML.nodeName;
  85. }
  86. get nodeGraph() {
  87. return this.getAttribute('nodegraph');
  88. }
  89. get nodeName() {
  90. return this.getAttribute('nodename');
  91. }
  92. get interfaceName() {
  93. return this.getAttribute('interfacename');
  94. }
  95. get output() {
  96. return this.getAttribute('output');
  97. }
  98. get name() {
  99. return this.getAttribute('name');
  100. }
  101. get type() {
  102. return this.getAttribute('type');
  103. }
  104. get value() {
  105. return this.getAttribute('value');
  106. }
  107. getNodeGraph() {
  108. let nodeX = this;
  109. while (nodeX !== null) {
  110. if (nodeX.element === 'nodegraph') {
  111. break;
  112. }
  113. nodeX = nodeX.parent;
  114. }
  115. return nodeX;
  116. }
  117. getRoot() {
  118. let nodeX = this;
  119. while (nodeX.parent !== null) {
  120. nodeX = nodeX.parent;
  121. }
  122. return nodeX;
  123. }
  124. get referencePath() {
  125. let referencePath = null;
  126. if (this.nodeGraph !== null && this.output !== null) {
  127. referencePath = this.nodeGraph + '/' + this.output;
  128. } else if (this.nodeName !== null || this.interfaceName !== null) {
  129. referencePath = this.getNodeGraph().nodePath + '/' + (this.nodeName || this.interfaceName);
  130. }
  131. return referencePath;
  132. }
  133. get hasReference() {
  134. return this.referencePath !== null;
  135. }
  136. get isConst() {
  137. return this.element === 'input' && this.value !== null && this.type !== 'filename';
  138. }
  139. getColorSpaceNode() {
  140. const csSource = this.getAttribute('colorspace');
  141. const csTarget = this.getRoot().getAttribute('colorspace');
  142. const nodeName = `mx_${csSource}_to_${csTarget}`;
  143. return colorSpaceLib[nodeName];
  144. }
  145. getTexture() {
  146. const filePrefix = this.getRecursiveAttribute('fileprefix') || '';
  147. const THREE.texture = this.materialX.textureLoader.load(filePrefix + this.value);
  148. THREE.texture.wrapS = THREE.texture.wrapT = THREE.RepeatWrapping;
  149. THREE.texture.flipY = false;
  150. return THREE.texture;
  151. }
  152. getClassFromType(type) {
  153. let nodeClass = null;
  154. if (type === 'integer') nodeClass = THREE.int;else if (type === 'float') nodeClass = THREE.float;else if (type === 'vector2') nodeClass = THREE.vec2;else if (type === 'vector3') nodeClass = THREE.vec3;else if (type === 'vector4' || type === 'color4') nodeClass = THREE.vec4;else if (type === 'color3') nodeClass = THREE.color;else if (type === 'boolean') nodeClass = THREE.bool;
  155. return nodeClass;
  156. }
  157. getNode() {
  158. let node = this.node;
  159. if (node !== null) {
  160. return node;
  161. }
  162. //
  163. const type = this.type;
  164. if (this.isConst) {
  165. const nodeClass = this.getClassFromType(type);
  166. node = nodeClass(...this.getVector());
  167. } else if (this.hasReference) {
  168. node = this.materialX.getMaterialXNode(this.referencePath).getNode();
  169. } else {
  170. const element = this.element;
  171. if (element === 'convert') {
  172. const nodeClass = this.getClassFromType(type);
  173. node = nodeClass(this.getNodeByName('in'));
  174. } else if (element === 'constant') {
  175. node = this.getNodeByName('value');
  176. } else if (element === 'position') {
  177. node = THREE.positionLocal;
  178. } else if (element === 'tiledimage') {
  179. const file = this.getChildByName('file');
  180. const textureFile = file.getTexture();
  181. const uvTiling = THREE.mx_transform_uv(...this.getNodesByNames(['uvtiling', 'uvoffset']));
  182. node = THREE.texture(textureFile, uvTiling);
  183. const colorSpaceNode = file.getColorSpaceNode();
  184. if (colorSpaceNode) {
  185. node = colorSpaceNode(node);
  186. }
  187. } else if (element === 'image') {
  188. const file = this.getChildByName('file');
  189. const uvNode = this.getNodeByName('texcoord');
  190. const textureFile = file.getTexture();
  191. node = THREE.texture(textureFile, uvNode);
  192. const colorSpaceNode = file.getColorSpaceNode();
  193. if (colorSpaceNode) {
  194. node = colorSpaceNode(node);
  195. }
  196. } else if (MtlXLibrary[element] !== undefined) {
  197. const nodeElement = MtlXLibrary[element];
  198. node = nodeElement.nodeFunc(...this.getNodesByNames(...nodeElement.params));
  199. }
  200. }
  201. //
  202. if (node === null) {
  203. console.warn(`THREE.MaterialXLoader: Unexpected node ${new XMLSerializer().serializeToString(this.nodeXML)}.`);
  204. node = THREE.float(0);
  205. }
  206. //
  207. const nodeToTypeClass = this.getClassFromType(type);
  208. if (nodeToTypeClass !== null) {
  209. node = nodeToTypeClass(node);
  210. }
  211. node.name = this.name;
  212. this.node = node;
  213. return node;
  214. }
  215. getChildByName(name) {
  216. for (const input of this.children) {
  217. if (input.name === name) {
  218. return input;
  219. }
  220. }
  221. }
  222. getNodes() {
  223. const nodes = {};
  224. for (const input of this.children) {
  225. const node = input.getNode();
  226. nodes[node.name] = node;
  227. }
  228. return nodes;
  229. }
  230. getNodeByName(name) {
  231. return this.getChildByName(name)?.getNode();
  232. }
  233. getNodesByNames(...names) {
  234. const nodes = [];
  235. for (const name of names) {
  236. const node = this.getNodeByName(name);
  237. if (node) nodes.push(node);
  238. }
  239. return nodes;
  240. }
  241. getValue() {
  242. return this.value.trim();
  243. }
  244. getVector() {
  245. const vector = [];
  246. for (const val of this.getValue().split(/[,|\s]/)) {
  247. if (val !== '') {
  248. vector.push(Number(val.trim()));
  249. }
  250. }
  251. return vector;
  252. }
  253. getAttribute(name) {
  254. return this.nodeXML.getAttribute(name);
  255. }
  256. getRecursiveAttribute(name) {
  257. let attribute = this.nodeXML.getAttribute(name);
  258. if (attribute === null && this.parent !== null) {
  259. attribute = this.parent.getRecursiveAttribute(name);
  260. }
  261. return attribute;
  262. }
  263. setStandardSurfaceToGltfPBR(material) {
  264. const inputs = this.getNodes();
  265. //
  266. let colorNode = null;
  267. if (inputs.base && inputs.base_color) colorNode = THREE.mul(inputs.base, inputs.base_color);else if (inputs.base) colorNode = inputs.base;else if (inputs.base_color) colorNode = inputs.base_color;
  268. //
  269. let roughnessNode = null;
  270. if (inputs.specular_roughness) roughnessNode = inputs.specular_roughness;
  271. //
  272. let metalnessNode = null;
  273. if (inputs.metalness) metalnessNode = inputs.metalness;
  274. //
  275. let clearcoatNode = null;
  276. let clearcoatRoughnessNode = null;
  277. if (inputs.coat) clearcoatNode = inputs.coat;
  278. if (inputs.coat_roughness) clearcoatRoughnessNode = inputs.coat_roughness;
  279. if (inputs.coat_color) {
  280. colorNode = colorNode ? THREE.mul(colorNode, inputs.coat_color) : colorNode;
  281. }
  282. //
  283. material.colorNode = colorNode || THREE.color(0.8, 0.8, 0.8);
  284. material.roughnessNode = roughnessNode || THREE.float(0.2);
  285. material.metalnessNode = metalnessNode || THREE.float(0);
  286. material.clearcoatNode = clearcoatNode || THREE.float(0);
  287. material.clearcoatRoughnessNode = clearcoatRoughnessNode || THREE.float(0);
  288. }
  289. /*setGltfPBR( material ) {
  290. const inputs = this.getNodes();
  291. console.log( inputs );
  292. }*/
  293. setMaterial(material) {
  294. const element = this.element;
  295. if (element === 'gltf_pbr') {
  296. //this.setGltfPBR( material );
  297. } else if (element === 'standard_surface') {
  298. this.setStandardSurfaceToGltfPBR(material);
  299. }
  300. }
  301. toMaterial() {
  302. const material = new THREE.MeshPhysicalNodeMaterial();
  303. material.name = this.name;
  304. for (const nodeX of this.children) {
  305. const shaderProperties = this.materialX.getMaterialXNode(nodeX.nodeName);
  306. shaderProperties.setMaterial(material);
  307. }
  308. return material;
  309. }
  310. toMaterials() {
  311. const materials = {};
  312. for (const nodeX of this.children) {
  313. if (nodeX.element === 'surfacematerial') {
  314. const material = nodeX.toMaterial();
  315. materials[material.name] = material;
  316. }
  317. }
  318. return materials;
  319. }
  320. THREE.add(materialXNode) {
  321. materialXNode.parent = this;
  322. this.children.push(materialXNode);
  323. }
  324. }
  325. class MaterialX {
  326. constructor(manager, path) {
  327. this.manager = manager;
  328. this.path = path;
  329. this.resourcePath = '';
  330. this.nodesXLib = new Map();
  331. //this.nodesXRefLib = new WeakMap();
  332. this.textureLoader = new THREE.TextureLoader(manager);
  333. }
  334. addMaterialXNode(materialXNode) {
  335. this.nodesXLib.set(materialXNode.nodePath, materialXNode);
  336. }
  337. /*getMaterialXNodeFromXML( xmlNode ) {
  338. return this.nodesXRefLib.get( xmlNode );
  339. }*/
  340. getMaterialXNode(...names) {
  341. return this.nodesXLib.get(names.join('/'));
  342. }
  343. parseNode(nodeXML, nodePath = '') {
  344. const materialXNode = new MaterialXNode(this, nodeXML, nodePath);
  345. if (materialXNode.nodePath) this.addMaterialXNode(materialXNode);
  346. for (const childNodeXML of nodeXML.children) {
  347. const childMXNode = this.parseNode(childNodeXML, materialXNode.nodePath);
  348. materialXNode.add(childMXNode);
  349. }
  350. return materialXNode;
  351. }
  352. parse(text) {
  353. const rootXML = new DOMParser().parseFromString(text, 'application/xml').documentElement;
  354. this.textureLoader.setPath(this.path);
  355. //
  356. const materials = this.parseNode(rootXML).toMaterials();
  357. return {
  358. materials
  359. };
  360. }
  361. }
  362. THREE.MaterialXLoader = MaterialXLoader;
  363. } )();