MaterialXLoader.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. import { FileLoader, Loader, TextureLoader, RepeatWrapping, MeshBasicNodeMaterial, MeshPhysicalNodeMaterial } from 'three/webgpu';
  2. import {
  3. float, bool, int, vec2, vec3, vec4, color, texture,
  4. positionLocal, positionWorld, uv, vertexColor,
  5. normalLocal, normalWorld, tangentLocal, tangentWorld,
  6. add, sub, mul, div, mod, abs, sign, floor, ceil, round, pow, sin, cos, tan,
  7. asin, acos, atan2, sqrt, exp, clamp, min, max, normalize, length, dot, cross, normalMap,
  8. remap, smoothstep, luminance, mx_rgbtohsv, mx_hsvtorgb,
  9. mix, split,
  10. mx_ramplr, mx_ramptb, mx_splitlr, mx_splittb,
  11. mx_fractal_noise_float, mx_noise_float, mx_cell_noise_float, mx_worley_noise_float,
  12. mx_transform_uv,
  13. mx_safepower, mx_contrast,
  14. mx_srgb_texture_to_lin_rec709,
  15. saturation,
  16. timerLocal, frameId
  17. } from 'three/tsl';
  18. const colorSpaceLib = {
  19. mx_srgb_texture_to_lin_rec709
  20. };
  21. class MXElement {
  22. constructor( name, nodeFunc, params = [] ) {
  23. this.name = name;
  24. this.nodeFunc = nodeFunc;
  25. this.params = params;
  26. }
  27. }
  28. // Ref: https://github.com/mrdoob/three.js/issues/24674
  29. const mx_add = ( in1, in2 = float( 0 ) ) => add( in1, in2 );
  30. const mx_subtract = ( in1, in2 = float( 0 ) ) => sub( in1, in2 );
  31. const mx_multiply = ( in1, in2 = float( 1 ) ) => mul( in1, in2 );
  32. const mx_divide = ( in1, in2 = float( 1 ) ) => div( in1, in2 );
  33. const mx_modulo = ( in1, in2 = float( 1 ) ) => mod( in1, in2 );
  34. const mx_power = ( in1, in2 = float( 1 ) ) => pow( in1, in2 );
  35. const mx_atan2 = ( in1 = float( 0 ), in2 = float( 1 ) ) => atan2( in1, in2 );
  36. const mx_timer = () => timerLocal();
  37. const mx_frame = () => frameId;
  38. const mx_invert = ( in1, amount = float( 1 ) ) => sub( amount, in1 );
  39. const separate = ( in1, channel ) => split( in1, channel.at( - 1 ) );
  40. const extract = ( in1, index ) => in1.element( index );
  41. const MXElements = [
  42. // << Math >>
  43. new MXElement( 'add', mx_add, [ 'in1', 'in2' ] ),
  44. new MXElement( 'subtract', mx_subtract, [ 'in1', 'in2' ] ),
  45. new MXElement( 'multiply', mx_multiply, [ 'in1', 'in2' ] ),
  46. new MXElement( 'divide', mx_divide, [ 'in1', 'in2' ] ),
  47. new MXElement( 'modulo', mx_modulo, [ 'in1', 'in2' ] ),
  48. new MXElement( 'absval', abs, [ 'in1', 'in2' ] ),
  49. new MXElement( 'sign', sign, [ 'in1', 'in2' ] ),
  50. new MXElement( 'floor', floor, [ 'in1', 'in2' ] ),
  51. new MXElement( 'ceil', ceil, [ 'in1', 'in2' ] ),
  52. new MXElement( 'round', round, [ 'in1', 'in2' ] ),
  53. new MXElement( 'power', mx_power, [ 'in1', 'in2' ] ),
  54. new MXElement( 'sin', sin, [ 'in' ] ),
  55. new MXElement( 'cos', cos, [ 'in' ] ),
  56. new MXElement( 'tan', tan, [ 'in' ] ),
  57. new MXElement( 'asin', asin, [ 'in' ] ),
  58. new MXElement( 'acos', acos, [ 'in' ] ),
  59. new MXElement( 'atan2', mx_atan2, [ 'in1', 'in2' ] ),
  60. new MXElement( 'sqrt', sqrt, [ 'in' ] ),
  61. //new MtlXElement( 'ln', ... ),
  62. new MXElement( 'exp', exp, [ 'in' ] ),
  63. new MXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ),
  64. new MXElement( 'min', min, [ 'in1', 'in2' ] ),
  65. new MXElement( 'max', max, [ 'in1', 'in2' ] ),
  66. new MXElement( 'normalize', normalize, [ 'in' ] ),
  67. new MXElement( 'magnitude', length, [ 'in1', 'in2' ] ),
  68. new MXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ),
  69. new MXElement( 'crossproduct', cross, [ 'in' ] ),
  70. new MXElement( 'invert', mx_invert, [ 'in', 'amount' ] ),
  71. //new MtlXElement( 'transformpoint', ... ),
  72. //new MtlXElement( 'transformvector', ... ),
  73. //new MtlXElement( 'transformnormal', ... ),
  74. //new MtlXElement( 'transformmatrix', ... ),
  75. new MXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ),
  76. //new MtlXElement( 'transpose', ... ),
  77. //new MtlXElement( 'determinant', ... ),
  78. //new MtlXElement( 'invertmatrix', ... ),
  79. //new MtlXElement( 'rotate2d', rotateUV, [ 'in', radians( 'amount' )** ] ),
  80. //new MtlXElement( 'rotate3d', ... ),
  81. //new MtlXElement( 'arrayappend', ... ),
  82. //new MtlXElement( 'dot', ... ),
  83. // << Adjustment >>
  84. new MXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ),
  85. new MXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ),
  86. //new MtlXElement( 'curveadjust', ... ),
  87. //new MtlXElement( 'curvelookup', ... ),
  88. new MXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ),
  89. new MXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ),
  90. new MXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ),
  91. // << Mix >>
  92. new MXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ),
  93. // << Channel >>
  94. new MXElement( 'combine2', vec2, [ 'in1', 'in2' ] ),
  95. new MXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ),
  96. new MXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ),
  97. // << Procedural >>
  98. new MXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ),
  99. new MXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ),
  100. new MXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ),
  101. new MXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ),
  102. new MXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ),
  103. new MXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ),
  104. new MXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ),
  105. new MXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ),
  106. new MXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ),
  107. new MXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ),
  108. new MXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ),
  109. // << Supplemental >>
  110. //new MtlXElement( 'tiledimage', ... ),
  111. //new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ),
  112. //new MtlXElement( 'ramp4', ... ),
  113. //new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ),
  114. new MXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ),
  115. new MXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ),
  116. //new MtlXElement( 'hsvadjust', ... ),
  117. new MXElement( 'saturate', saturation, [ 'in', 'amount' ] ),
  118. new MXElement( 'extract', extract, [ 'in', 'index' ] ),
  119. new MXElement( 'separate2', separate, [ 'in' ] ),
  120. new MXElement( 'separate3', separate, [ 'in' ] ),
  121. new MXElement( 'separate4', separate, [ 'in' ] ),
  122. new MXElement( 'time', mx_timer ),
  123. new MXElement( 'frame', mx_frame )
  124. ];
  125. const MtlXLibrary = {};
  126. MXElements.forEach( element => MtlXLibrary[ element.name ] = element );
  127. /**
  128. * A loader for the MaterialX format.
  129. *
  130. * The node materials loaded with this loader can only be used with {@link WebGPURenderer}.
  131. *
  132. * ```js
  133. * const loader = new MaterialXLoader().setPath( SAMPLE_PATH );
  134. * const materials = await loader.loadAsync( 'standard_surface_brass_tiled.mtlx' );
  135. * ```
  136. *
  137. * @augments Loader
  138. * @three_import import { MaterialXLoader } from 'three/addons/loaders/MaterialXLoader.js';
  139. */
  140. class MaterialXLoader extends Loader {
  141. /**
  142. * Constructs a new MaterialX loader.
  143. *
  144. * @param {LoadingManager} [manager] - The loading manager.
  145. */
  146. constructor( manager ) {
  147. super( manager );
  148. }
  149. /**
  150. * Starts loading from the given URL and passes the loaded MaterialX asset
  151. * to the `onLoad()` callback.
  152. *
  153. * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
  154. * @param {function(Object<string,NodeMaterial>)} onLoad - Executed when the loading process has been finished.
  155. * @param {onProgressCallback} onProgress - Executed while the loading is in progress.
  156. * @param {onErrorCallback} onError - Executed when errors occur.
  157. * @return {MaterialXLoader} A reference to this loader.
  158. */
  159. load( url, onLoad, onProgress, onError ) {
  160. const _onError = function ( e ) {
  161. if ( onError ) {
  162. onError( e );
  163. } else {
  164. console.error( e );
  165. }
  166. };
  167. new FileLoader( this.manager )
  168. .setPath( this.path )
  169. .load( url, async ( text ) => {
  170. try {
  171. onLoad( this.parse( text ) );
  172. } catch ( e ) {
  173. _onError( e );
  174. }
  175. }, onProgress, _onError );
  176. return this;
  177. }
  178. /**
  179. * Parses the given MaterialX data and returns the resulting materials.
  180. *
  181. * @param {string} text - The raw MaterialX data as a string.
  182. * @return {Object<string,NodeMaterial>} A dictionary holding the parse node materials.
  183. */
  184. parse( text ) {
  185. return new MaterialX( this.manager, this.path ).parse( text );
  186. }
  187. }
  188. class MaterialXNode {
  189. constructor( materialX, nodeXML, nodePath = '' ) {
  190. this.materialX = materialX;
  191. this.nodeXML = nodeXML;
  192. this.nodePath = nodePath ? nodePath + '/' + this.name : this.name;
  193. this.parent = null;
  194. this.node = null;
  195. this.children = [];
  196. }
  197. get element() {
  198. return this.nodeXML.nodeName;
  199. }
  200. get nodeGraph() {
  201. return this.getAttribute( 'nodegraph' );
  202. }
  203. get nodeName() {
  204. return this.getAttribute( 'nodename' );
  205. }
  206. get interfaceName() {
  207. return this.getAttribute( 'interfacename' );
  208. }
  209. get output() {
  210. return this.getAttribute( 'output' );
  211. }
  212. get name() {
  213. return this.getAttribute( 'name' );
  214. }
  215. get type() {
  216. return this.getAttribute( 'type' );
  217. }
  218. get value() {
  219. return this.getAttribute( 'value' );
  220. }
  221. getNodeGraph() {
  222. let nodeX = this;
  223. while ( nodeX !== null ) {
  224. if ( nodeX.element === 'nodegraph' ) {
  225. break;
  226. }
  227. nodeX = nodeX.parent;
  228. }
  229. return nodeX;
  230. }
  231. getRoot() {
  232. let nodeX = this;
  233. while ( nodeX.parent !== null ) {
  234. nodeX = nodeX.parent;
  235. }
  236. return nodeX;
  237. }
  238. get referencePath() {
  239. let referencePath = null;
  240. if ( this.nodeGraph !== null && this.output !== null ) {
  241. referencePath = this.nodeGraph + '/' + this.output;
  242. } else if ( this.nodeName !== null || this.interfaceName !== null ) {
  243. referencePath = this.getNodeGraph().nodePath + '/' + ( this.nodeName || this.interfaceName );
  244. }
  245. return referencePath;
  246. }
  247. get hasReference() {
  248. return this.referencePath !== null;
  249. }
  250. get isConst() {
  251. return this.element === 'input' && this.value !== null && this.type !== 'filename';
  252. }
  253. getColorSpaceNode() {
  254. const csSource = this.getAttribute( 'colorspace' );
  255. const csTarget = this.getRoot().getAttribute( 'colorspace' );
  256. const nodeName = `mx_${ csSource }_to_${ csTarget }`;
  257. return colorSpaceLib[ nodeName ];
  258. }
  259. getTexture() {
  260. const filePrefix = this.getRecursiveAttribute( 'fileprefix' ) || '';
  261. let loader = this.materialX.textureLoader;
  262. const uri = filePrefix + this.value;
  263. if ( uri ) {
  264. const handler = this.materialX.manager.getHandler( uri );
  265. if ( handler !== null ) loader = handler;
  266. }
  267. const texture = loader.load( uri );
  268. texture.wrapS = texture.wrapT = RepeatWrapping;
  269. texture.flipY = false;
  270. return texture;
  271. }
  272. getClassFromType( type ) {
  273. let nodeClass = null;
  274. if ( type === 'integer' ) nodeClass = int;
  275. else if ( type === 'float' ) nodeClass = float;
  276. else if ( type === 'vector2' ) nodeClass = vec2;
  277. else if ( type === 'vector3' ) nodeClass = vec3;
  278. else if ( type === 'vector4' || type === 'color4' ) nodeClass = vec4;
  279. else if ( type === 'color3' ) nodeClass = color;
  280. else if ( type === 'boolean' ) nodeClass = bool;
  281. return nodeClass;
  282. }
  283. getNode( out = null ) {
  284. let node = this.node;
  285. if ( node !== null && out === null ) {
  286. return node;
  287. }
  288. //
  289. const type = this.type;
  290. if ( this.isConst ) {
  291. const nodeClass = this.getClassFromType( type );
  292. node = nodeClass( ...this.getVector() );
  293. } else if ( this.hasReference ) {
  294. if ( this.element === 'output' && this.output && out === null ) {
  295. out = this.output;
  296. }
  297. node = this.materialX.getMaterialXNode( this.referencePath ).getNode( out );
  298. } else {
  299. const element = this.element;
  300. if ( element === 'convert' ) {
  301. const nodeClass = this.getClassFromType( type );
  302. node = nodeClass( this.getNodeByName( 'in' ) );
  303. } else if ( element === 'constant' ) {
  304. node = this.getNodeByName( 'value' );
  305. } else if ( element === 'position' ) {
  306. const space = this.getAttribute( 'space' );
  307. node = space === 'world' ? positionWorld : positionLocal;
  308. } else if ( element === 'normal' ) {
  309. const space = this.getAttribute( 'space' );
  310. node = space === 'world' ? normalWorld : normalLocal;
  311. } else if ( element === 'tangent' ) {
  312. const space = this.getAttribute( 'space' );
  313. node = space === 'world' ? tangentWorld : tangentLocal;
  314. } else if ( element === 'texcoord' ) {
  315. const indexNode = this.getChildByName( 'index' );
  316. const index = indexNode ? parseInt( indexNode.value ) : 0;
  317. node = uv( index );
  318. } else if ( element === 'geomcolor' ) {
  319. const indexNode = this.getChildByName( 'index' );
  320. const index = indexNode ? parseInt( indexNode.value ) : 0;
  321. node = vertexColor( index );
  322. } else if ( element === 'tiledimage' ) {
  323. const file = this.getChildByName( 'file' );
  324. const textureFile = file.getTexture();
  325. const uvTiling = mx_transform_uv( ...this.getNodesByNames( [ 'uvtiling', 'uvoffset' ] ) );
  326. node = texture( textureFile, uvTiling );
  327. const colorSpaceNode = file.getColorSpaceNode();
  328. if ( colorSpaceNode ) {
  329. node = colorSpaceNode( node );
  330. }
  331. } else if ( element === 'image' ) {
  332. const file = this.getChildByName( 'file' );
  333. const uvNode = this.getNodeByName( 'texcoord' );
  334. const textureFile = file.getTexture();
  335. node = texture( textureFile, uvNode );
  336. const colorSpaceNode = file.getColorSpaceNode();
  337. if ( colorSpaceNode ) {
  338. node = colorSpaceNode( node );
  339. }
  340. } else if ( MtlXLibrary[ element ] !== undefined ) {
  341. const nodeElement = MtlXLibrary[ element ];
  342. if ( out !== null ) {
  343. node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ), out );
  344. } else {
  345. node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ) );
  346. }
  347. }
  348. }
  349. //
  350. if ( node === null ) {
  351. console.warn( `THREE.MaterialXLoader: Unexpected node ${ new XMLSerializer().serializeToString( this.nodeXML ) }.` );
  352. node = float( 0 );
  353. }
  354. //
  355. const nodeToTypeClass = this.getClassFromType( type );
  356. if ( nodeToTypeClass !== null ) {
  357. node = nodeToTypeClass( node );
  358. }
  359. node.name = this.name;
  360. this.node = node;
  361. return node;
  362. }
  363. getChildByName( name ) {
  364. for ( const input of this.children ) {
  365. if ( input.name === name ) {
  366. return input;
  367. }
  368. }
  369. }
  370. getNodes() {
  371. const nodes = {};
  372. for ( const input of this.children ) {
  373. const node = input.getNode();
  374. nodes[ node.name ] = node;
  375. }
  376. return nodes;
  377. }
  378. getNodeByName( name ) {
  379. const child = this.getChildByName( name );
  380. return child ? child.getNode( child.output ) : undefined;
  381. }
  382. getNodesByNames( ...names ) {
  383. const nodes = [];
  384. for ( const name of names ) {
  385. const node = this.getNodeByName( name );
  386. if ( node ) nodes.push( node );
  387. }
  388. return nodes;
  389. }
  390. getValue() {
  391. return this.value.trim();
  392. }
  393. getVector() {
  394. const vector = [];
  395. for ( const val of this.getValue().split( /[,|\s]/ ) ) {
  396. if ( val !== '' ) {
  397. vector.push( Number( val.trim() ) );
  398. }
  399. }
  400. return vector;
  401. }
  402. getAttribute( name ) {
  403. return this.nodeXML.getAttribute( name );
  404. }
  405. getRecursiveAttribute( name ) {
  406. let attribute = this.nodeXML.getAttribute( name );
  407. if ( attribute === null && this.parent !== null ) {
  408. attribute = this.parent.getRecursiveAttribute( name );
  409. }
  410. return attribute;
  411. }
  412. setStandardSurfaceToGltfPBR( material ) {
  413. const inputs = this.getNodes();
  414. //
  415. let colorNode = null;
  416. if ( inputs.base && inputs.base_color ) colorNode = mul( inputs.base, inputs.base_color );
  417. else if ( inputs.base ) colorNode = inputs.base;
  418. else if ( inputs.base_color ) colorNode = inputs.base_color;
  419. //
  420. let roughnessNode = null;
  421. if ( inputs.specular_roughness ) roughnessNode = inputs.specular_roughness;
  422. //
  423. let metalnessNode = null;
  424. if ( inputs.metalness ) metalnessNode = inputs.metalness;
  425. //
  426. let clearcoatNode = null;
  427. let clearcoatRoughnessNode = null;
  428. if ( inputs.coat ) clearcoatNode = inputs.coat;
  429. if ( inputs.coat_roughness ) clearcoatRoughnessNode = inputs.coat_roughness;
  430. if ( inputs.coat_color ) {
  431. colorNode = colorNode ? mul( colorNode, inputs.coat_color ) : colorNode;
  432. }
  433. //
  434. let normalNode = null;
  435. if ( inputs.normal ) normalNode = inputs.normal;
  436. //
  437. let emissiveNode = null;
  438. if ( inputs.emission ) emissiveNode = inputs.emission;
  439. if ( inputs.emissionColor ) {
  440. emissiveNode = emissiveNode ? mul( emissiveNode, inputs.emissionColor ) : emissiveNode;
  441. }
  442. //
  443. material.colorNode = colorNode || color( 0.8, 0.8, 0.8 );
  444. material.roughnessNode = roughnessNode || float( 0.2 );
  445. material.metalnessNode = metalnessNode || float( 0 );
  446. material.clearcoatNode = clearcoatNode || float( 0 );
  447. material.clearcoatRoughnessNode = clearcoatRoughnessNode || float( 0 );
  448. if ( normalNode ) material.normalNode = normalNode;
  449. if ( emissiveNode ) material.emissiveNode = emissiveNode;
  450. }
  451. /*setGltfPBR( material ) {
  452. const inputs = this.getNodes();
  453. console.log( inputs );
  454. }*/
  455. setMaterial( material ) {
  456. const element = this.element;
  457. if ( element === 'gltf_pbr' ) {
  458. //this.setGltfPBR( material );
  459. } else if ( element === 'standard_surface' ) {
  460. this.setStandardSurfaceToGltfPBR( material );
  461. }
  462. }
  463. toBasicMaterial() {
  464. const material = new MeshBasicNodeMaterial();
  465. material.name = this.name;
  466. for ( const nodeX of this.children.toReversed() ) {
  467. if ( nodeX.name === 'out' ) {
  468. material.colorNode = nodeX.getNode();
  469. break;
  470. }
  471. }
  472. return material;
  473. }
  474. toPhysicalMaterial() {
  475. const material = new MeshPhysicalNodeMaterial();
  476. material.name = this.name;
  477. for ( const nodeX of this.children ) {
  478. const shaderProperties = this.materialX.getMaterialXNode( nodeX.nodeName );
  479. shaderProperties.setMaterial( material );
  480. }
  481. return material;
  482. }
  483. toMaterials() {
  484. const materials = {};
  485. let isUnlit = true;
  486. for ( const nodeX of this.children ) {
  487. if ( nodeX.element === 'surfacematerial' ) {
  488. const material = nodeX.toPhysicalMaterial();
  489. materials[ material.name ] = material;
  490. isUnlit = false;
  491. }
  492. }
  493. if ( isUnlit ) {
  494. for ( const nodeX of this.children ) {
  495. if ( nodeX.element === 'nodegraph' ) {
  496. const material = nodeX.toBasicMaterial();
  497. materials[ material.name ] = material;
  498. }
  499. }
  500. }
  501. return materials;
  502. }
  503. add( materialXNode ) {
  504. materialXNode.parent = this;
  505. this.children.push( materialXNode );
  506. }
  507. }
  508. class MaterialX {
  509. constructor( manager, path ) {
  510. this.manager = manager;
  511. this.path = path;
  512. this.resourcePath = '';
  513. this.nodesXLib = new Map();
  514. //this.nodesXRefLib = new WeakMap();
  515. this.textureLoader = new TextureLoader( manager );
  516. }
  517. addMaterialXNode( materialXNode ) {
  518. this.nodesXLib.set( materialXNode.nodePath, materialXNode );
  519. }
  520. /*getMaterialXNodeFromXML( xmlNode ) {
  521. return this.nodesXRefLib.get( xmlNode );
  522. }*/
  523. getMaterialXNode( ...names ) {
  524. return this.nodesXLib.get( names.join( '/' ) );
  525. }
  526. parseNode( nodeXML, nodePath = '' ) {
  527. const materialXNode = new MaterialXNode( this, nodeXML, nodePath );
  528. if ( materialXNode.nodePath ) this.addMaterialXNode( materialXNode );
  529. for ( const childNodeXML of nodeXML.children ) {
  530. const childMXNode = this.parseNode( childNodeXML, materialXNode.nodePath );
  531. materialXNode.add( childMXNode );
  532. }
  533. return materialXNode;
  534. }
  535. parse( text ) {
  536. const rootXML = new DOMParser().parseFromString( text, 'application/xml' ).documentElement;
  537. this.textureLoader.setPath( this.path );
  538. //
  539. const materials = this.parseNode( rootXML ).toMaterials();
  540. return { materials };
  541. }
  542. }
  543. export { MaterialXLoader };