MaterialXLoader.js 15 KB

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