1
0

MaterialXDocument.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. import {
  2. Texture,
  3. RepeatWrapping,
  4. ClampToEdgeWrapping,
  5. MirroredRepeatWrapping,
  6. ImageLoader,
  7. ImageBitmapLoader,
  8. Matrix3,
  9. Matrix4,
  10. MeshBasicNodeMaterial,
  11. MeshPhysicalNodeMaterial,
  12. } from 'three/webgpu';
  13. import {
  14. float,
  15. int,
  16. bool,
  17. sub,
  18. vec2,
  19. vec3,
  20. vec4,
  21. color,
  22. uv,
  23. mat3,
  24. mat4,
  25. element,
  26. mx_transform_uv,
  27. mx_srgb_texture_to_lin_rec709,
  28. } from 'three/tsl';
  29. import { MaterialXLogCodes } from './MaterialXLog.js';
  30. import { createMaterialXCompileRegistry, compileNodeFromRegistry } from './compile/MaterialXCompileRegistry.js';
  31. import { parseMaterialXNodeTree, parseMaterialXText } from './parse/MaterialXParser.js';
  32. import { getSurfaceMapper } from './MaterialXSurfaceMappings.js';
  33. import { MtlXLibrary } from './MaterialXNodeLibrary.js';
  34. import { mxHextileCoord, mxHextileComputeBlendWeights } from './MaterialXHextile.js';
  35. import { toBooleanNode } from './MaterialXUtils.js';
  36. const colorSpaceLib = {
  37. mx_srgb_texture_to_lin_rec709,
  38. };
  39. const DEFAULT_DOCUMENT_COLOR_SPACE = 'lin_rec709';
  40. const IDENTITY_MAT3_VALUES = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];
  41. const IDENTITY_MAT4_VALUES = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ];
  42. const MATRIX_INVERSE_EPSILON = 1e-8;
  43. const COMPILE_REGISTRY = createMaterialXCompileRegistry();
  44. const TEXTURE_ADDRESS_MODE_WRAPPING = {
  45. constant: ClampToEdgeWrapping,
  46. clamp: ClampToEdgeWrapping,
  47. periodic: RepeatWrapping,
  48. mirror: MirroredRepeatWrapping,
  49. };
  50. const NODE_CLASS_BY_TYPE = {
  51. integer: int,
  52. float,
  53. vector2: vec2,
  54. vector3: vec3,
  55. vector4: vec4,
  56. color4: vec4,
  57. color3: color,
  58. boolean: null,
  59. matrix33: mat3,
  60. matrix44: mat4,
  61. };
  62. const OUTPUT_CHANNELS = {
  63. outx: 0,
  64. outr: 0,
  65. outy: 1,
  66. outg: 1,
  67. outz: 2,
  68. outb: 2,
  69. outw: 3,
  70. outa: 3,
  71. };
  72. function mxFlipUvY( uvNode ) {
  73. return vec2( element( uvNode, 0 ), sub( 1, element( uvNode, 1 ) ) );
  74. }
  75. function mxIdentityUv( uvNode ) {
  76. return uvNode;
  77. }
  78. function getBottomLeftUvSpaceHelpers( uvSpace ) {
  79. const helper = uvSpace === 'top-left' ? mxFlipUvY : mxIdentityUv;
  80. return {
  81. mxToBottomLeftUvSpace: helper,
  82. mxFromBottomLeftUvSpace: helper,
  83. };
  84. }
  85. function normalizeUvSpace( uvSpace ) {
  86. if ( uvSpace === undefined || uvSpace === null ) return 'bottom-left';
  87. if ( uvSpace === 'bottom-left' || uvSpace === 'top-left' ) return uvSpace;
  88. throw new Error( `Unsupported MaterialX uvSpace "${uvSpace}". Expected "bottom-left" or "top-left".` );
  89. }
  90. function isSvgUri( uri ) {
  91. if ( typeof uri !== 'string' ) return false;
  92. return /\.svg(?:$|[?#])/i.test( uri );
  93. }
  94. function normalizeTextureAddressMode( value ) {
  95. if ( value === null || value === undefined || value === '' ) return 'periodic';
  96. const mode = value.trim().toLowerCase();
  97. return mode in TEXTURE_ADDRESS_MODE_WRAPPING ? mode : null;
  98. }
  99. function invertConstantMatrixValues( values, size ) {
  100. if ( ! Array.isArray( values ) || values.length !== size * size ) return null;
  101. if ( size === 3 ) {
  102. const matrix = new Matrix3().setFromArray( values );
  103. if ( Math.abs( matrix.determinant() ) < MATRIX_INVERSE_EPSILON ) return null;
  104. matrix.invert();
  105. // Convert Three.js internal column-major storage back to row-major literal order.
  106. return matrix.transpose().elements;
  107. }
  108. if ( size === 4 ) {
  109. const matrix = new Matrix4().setFromArray( values );
  110. if ( Math.abs( matrix.determinant() ) < MATRIX_INVERSE_EPSILON ) return null;
  111. matrix.invert();
  112. // Convert Three.js internal column-major storage back to row-major literal order.
  113. return matrix.transpose().elements;
  114. }
  115. return null;
  116. }
  117. function getOutputChannel( outputName ) {
  118. return OUTPUT_CHANNELS[ outputName ] || 0;
  119. }
  120. function isChannelOutput( outputName ) {
  121. return outputName in OUTPUT_CHANNELS;
  122. }
  123. class MaterialXNode {
  124. constructor( materialX, nodeXML, nodePath = '' ) {
  125. this.materialX = materialX;
  126. this.nodeXML = nodeXML;
  127. this.nodePath = nodePath ? nodePath + '/' + this.name : this.name;
  128. this.parent = null;
  129. this.node = null;
  130. this.children = [];
  131. }
  132. get element() {
  133. return this.nodeXML.nodeName;
  134. }
  135. get nodeGraph() {
  136. return this.getAttribute( 'nodegraph' );
  137. }
  138. get nodeName() {
  139. return this.getAttribute( 'nodename' );
  140. }
  141. get interfaceName() {
  142. return this.getAttribute( 'interfacename' );
  143. }
  144. get output() {
  145. return this.getAttribute( 'output' );
  146. }
  147. get name() {
  148. return this.getAttribute( 'name' );
  149. }
  150. get type() {
  151. return this.getAttribute( 'type' );
  152. }
  153. get value() {
  154. return this.getAttribute( 'value' );
  155. }
  156. getNodeGraph() {
  157. let nodeX = this;
  158. while ( nodeX !== null ) {
  159. if ( nodeX.element === 'nodegraph' ) break;
  160. nodeX = nodeX.parent;
  161. }
  162. return nodeX;
  163. }
  164. getRoot() {
  165. let nodeX = this;
  166. while ( nodeX.parent !== null ) {
  167. nodeX = nodeX.parent;
  168. }
  169. return nodeX;
  170. }
  171. get referencePath() {
  172. let referencePath = null;
  173. if ( this.nodeGraph !== null && this.output !== null ) {
  174. referencePath = this.nodeGraph + '/' + this.output;
  175. } else if ( this.nodeName !== null || this.interfaceName !== null ) {
  176. const graphNode = this.getNodeGraph();
  177. const scopedReference = this.nodeName || this.interfaceName;
  178. if ( graphNode && scopedReference ) {
  179. referencePath = graphNode.nodePath + '/' + scopedReference;
  180. } else if ( this.nodeName !== null ) {
  181. // Surface-level nodename links can legitimately target top-level siblings.
  182. referencePath = this.nodeName;
  183. }
  184. }
  185. return referencePath;
  186. }
  187. get hasReference() {
  188. return this.referencePath !== null;
  189. }
  190. get isConst() {
  191. return this.element === 'input' && this.value !== null && this.type !== 'filename';
  192. }
  193. getColorSpaceNode() {
  194. const csSource = this.getAttribute( 'colorspace' );
  195. const csTarget = this.getRoot().getAttribute( 'colorspace' );
  196. if ( ! csSource || ! csTarget ) return null;
  197. const nodeName = `mx_${csSource}_to_${csTarget}`;
  198. return colorSpaceLib[ nodeName ] || null;
  199. }
  200. getTextureAddressMode( inputName ) {
  201. const rawMode = this.getInputValueByName( inputName );
  202. const mode = normalizeTextureAddressMode( rawMode );
  203. if ( mode ) return mode;
  204. this.materialX.log.add(
  205. MaterialXLogCodes.INVALID_VALUE,
  206. `Unsupported texture address mode "${rawMode}" on input "${inputName}". Expected constant, clamp, periodic, or mirror.`,
  207. this.name,
  208. );
  209. return 'periodic';
  210. }
  211. getTextureAddressModes() {
  212. return {
  213. u: this.getTextureAddressMode( 'uaddressmode' ),
  214. v: this.getTextureAddressMode( 'vaddressmode' ),
  215. };
  216. }
  217. getTexture() {
  218. const filePrefix = this.getRecursiveAttribute( 'fileprefix' ) || '';
  219. const sourceURI = filePrefix + this.value;
  220. const resolvedURI = this.materialX.resolveTextureURI( sourceURI );
  221. const svgTexture = isSvgUri( resolvedURI );
  222. const textureSourceNode = this.parent && typeof this.parent.getTextureAddressModes === 'function' ? this.parent : this;
  223. const addressModes = textureSourceNode.getTextureAddressModes();
  224. const textureCacheKey = `${resolvedURI}|${addressModes.u}|${addressModes.v}`;
  225. if ( this.materialX.textureCache.has( textureCacheKey ) ) {
  226. return this.materialX.textureCache.get( textureCacheKey );
  227. }
  228. let loader = svgTexture ? this.materialX.imageLoader : this.materialX.textureLoader;
  229. if ( resolvedURI && ! svgTexture ) {
  230. const handler = this.materialX.manager.getHandler( resolvedURI );
  231. if ( handler !== null ) loader = handler;
  232. }
  233. const textureNode = new Texture();
  234. textureNode.wrapS = TEXTURE_ADDRESS_MODE_WRAPPING[ addressModes.u ];
  235. textureNode.wrapT = TEXTURE_ADDRESS_MODE_WRAPPING[ addressModes.v ];
  236. textureNode.flipY = false;
  237. this.materialX.textureCache.set( textureCacheKey, textureNode );
  238. loader.load( resolvedURI, ( imageData ) => {
  239. textureNode.image = imageData;
  240. textureNode.needsUpdate = true;
  241. }, undefined, () => {
  242. throw new Error( `Failed to load texture "${resolvedURI}".` );
  243. } );
  244. return textureNode;
  245. }
  246. getClassFromType( type ) {
  247. return NODE_CLASS_BY_TYPE[ type ] || null;
  248. }
  249. toBooleanNode( node ) {
  250. return toBooleanNode( node );
  251. }
  252. getNode( out = null ) {
  253. if ( this.node !== null && out === null ) return this.node;
  254. let node;
  255. if ( ( this.element === 'separate2' || this.element === 'separate3' || this.element === 'separate4' ) && out ) {
  256. const inNode = this.getNodeByName( 'in' );
  257. return element( inNode, getOutputChannel( out ) );
  258. }
  259. const type = this.type;
  260. const channelRequested = this.element !== 'input' && this.element !== 'gltf_colorimage' && isChannelOutput( out );
  261. if ( this.isConst ) {
  262. if ( type === 'boolean' ) {
  263. const normalized = this.getValue().trim().toLowerCase();
  264. node = bool( normalized === 'true' || normalized === '1' );
  265. } else if ( type === 'matrix33' ) {
  266. node = this.getMatrix( 3 ) || mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 );
  267. } else if ( type === 'matrix44' ) {
  268. node = this.getMatrix( 4 ) || mat4( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 );
  269. } else if ( type === 'string' ) {
  270. node = this.getValue();
  271. } else {
  272. const nodeClass = this.getClassFromType( type );
  273. node = nodeClass ? nodeClass( ...this.getVector() ) : float( 0 );
  274. }
  275. } else if ( this.hasReference ) {
  276. if ( this.element === 'output' && this.output && out === null ) out = this.output;
  277. let requestedOutput = out;
  278. // For nodegraph references, this input's `output` attribute selects the graph output
  279. // itself and should not be forwarded as an output selector on the resolved node.
  280. if ( this.element === 'input' && this.nodeGraph !== null && this.output !== null ) {
  281. requestedOutput = null;
  282. }
  283. const referenceNode = this.materialX.getMaterialXNode( this.referencePath );
  284. if ( referenceNode ) {
  285. node = referenceNode.getNode( requestedOutput );
  286. } else {
  287. this.materialX.log.add(
  288. MaterialXLogCodes.MISSING_REFERENCE,
  289. `Missing MaterialX reference "${this.referencePath}" from "${this.name}".`,
  290. this.name,
  291. );
  292. node = float( 0 );
  293. }
  294. } else if ( this.element === 'input' && this.name === 'texcoord' && this.type === 'vector2' ) {
  295. let index = 0;
  296. const defaultGeomProp = this.getAttribute( 'defaultgeomprop' );
  297. if ( defaultGeomProp && /^UV(\d+)$/.test( defaultGeomProp ) ) {
  298. index = parseInt( defaultGeomProp.match( /^UV(\d+)$/ )[ 1 ], 10 );
  299. }
  300. node = this.materialX.compileContext.mxToBottomLeftUvSpace( uv( index ) );
  301. } else {
  302. node = compileNodeFromRegistry( this, out, this.materialX.compileContext );
  303. }
  304. if ( node === null || node === undefined ) {
  305. this.materialX.log.add(
  306. MaterialXLogCodes.UNSUPPORTED_NODE,
  307. `Unsupported MaterialX node category "${this.element}" on "${this.name}".`,
  308. this.name,
  309. );
  310. node = float( 0 );
  311. }
  312. if ( channelRequested ) {
  313. node = element( node, getOutputChannel( out ) );
  314. }
  315. const resolvedType = channelRequested ? 'float' : type;
  316. if ( resolvedType === 'boolean' ) {
  317. node = this.toBooleanNode( node );
  318. } else if ( resolvedType === 'string' ) {
  319. // String-typed inputs (for example transform* fromspace/tospace) are
  320. // valid scalar parameters and should pass through without numeric casting.
  321. node = typeof node === 'string' ? node : this.getValue();
  322. } else {
  323. const nodeToTypeClass = this.getClassFromType( resolvedType );
  324. if ( nodeToTypeClass !== null ) {
  325. node = nodeToTypeClass( node );
  326. } else if ( resolvedType !== null && resolvedType !== undefined && resolvedType !== 'multioutput' ) {
  327. this.materialX.log.add(
  328. MaterialXLogCodes.INVALID_VALUE,
  329. `Unexpected type "${resolvedType}" on node "${this.name}".`,
  330. this.name,
  331. );
  332. node = float( 0 );
  333. }
  334. }
  335. if ( node && typeof node === 'object' ) {
  336. node.name = this.name;
  337. }
  338. this.node = node;
  339. return node;
  340. }
  341. getChildByName( name ) {
  342. for ( const input of this.children ) {
  343. if ( input.name === name ) return input;
  344. }
  345. }
  346. getNodes() {
  347. const nodes = {};
  348. for ( const input of this.children ) {
  349. const value = input.getNode( input.output );
  350. nodes[ input.name ] = value;
  351. }
  352. return nodes;
  353. }
  354. getNodeByName( name ) {
  355. const child = this.getChildByName( name );
  356. return child ? child.getNode( child.output ) : undefined;
  357. }
  358. getInputValueByName( name ) {
  359. const child = this.getChildByName( name );
  360. return child ? child.value : null;
  361. }
  362. getNodesByNames( ...names ) {
  363. const nodes = [];
  364. for ( const name of names ) {
  365. const nodeValue = this.getNodeByName( name );
  366. nodes.push( nodeValue );
  367. }
  368. return nodes;
  369. }
  370. getValue() {
  371. return this.value ? this.value.trim() : '';
  372. }
  373. getVector() {
  374. const vector = [];
  375. for ( const val of this.getValue().split( /[,|\s]/ ) ) {
  376. if ( val !== '' ) vector.push( Number( val.trim() ) );
  377. }
  378. return vector;
  379. }
  380. getMatrix( size ) {
  381. const vector = this.getVector();
  382. const expectedLength = size * size;
  383. if ( vector.length !== expectedLength ) return null;
  384. // MaterialX matrix values are serialized in column-major order.
  385. // Reorder to row-major before constructing TSL matrix nodes so
  386. // transformmatrix semantics match MaterialXJS and MaterialXView.
  387. const reordered = [];
  388. for ( let row = 0; row < size; row += 1 ) {
  389. for ( let column = 0; column < size; column += 1 ) {
  390. reordered.push( vector[ column * size + row ] );
  391. }
  392. }
  393. return size === 3 ? mat3( ...reordered ) : mat4( ...reordered );
  394. }
  395. getAttribute( name ) {
  396. const value = this.nodeXML.getAttribute( name );
  397. if ( value === null && this.element === 'materialx' && name === 'colorspace' ) {
  398. return DEFAULT_DOCUMENT_COLOR_SPACE;
  399. }
  400. return value;
  401. }
  402. getRecursiveAttribute( name ) {
  403. let attribute = this.nodeXML.getAttribute( name );
  404. if ( attribute === null && this.parent !== null ) {
  405. attribute = this.parent.getRecursiveAttribute( name );
  406. }
  407. return attribute;
  408. }
  409. setMaterial( material ) {
  410. const mapper = getSurfaceMapper( this.element );
  411. if ( mapper ) {
  412. mapper.apply( material, this.getNodes(), this.materialX.log, this.name );
  413. } else {
  414. this.materialX.log.add(
  415. MaterialXLogCodes.UNSUPPORTED_NODE,
  416. `Unsupported MaterialX node category "${this.element}" on "${this.name}".`,
  417. this.name,
  418. );
  419. }
  420. }
  421. toBasicMaterial() {
  422. const material = new MeshBasicNodeMaterial();
  423. material.name = this.name;
  424. for ( const nodeX of this.children.toReversed() ) {
  425. if ( nodeX.name === 'out' ) {
  426. material.colorNode = nodeX.getNode();
  427. break;
  428. }
  429. }
  430. return material;
  431. }
  432. resolveSurfaceShaderNode( nodeX ) {
  433. if ( nodeX.hasReference ) {
  434. return this.materialX.getMaterialXNode( nodeX.referencePath ) || null;
  435. }
  436. if ( nodeX.nodeName ) {
  437. return this.materialX.getMaterialXNode( nodeX.nodeName ) || null;
  438. }
  439. return null;
  440. }
  441. toPhysicalMaterial() {
  442. const material = new MeshPhysicalNodeMaterial();
  443. material.name = this.name;
  444. for ( const nodeX of this.children ) {
  445. const shaderProperties = this.resolveSurfaceShaderNode( nodeX );
  446. if ( shaderProperties === null ) {
  447. this.materialX.log.add(
  448. MaterialXLogCodes.MISSING_REFERENCE,
  449. `Missing MaterialX reference "${nodeX.referencePath || nodeX.nodeName || '(unknown)'}" from "${nodeX.name}".`,
  450. nodeX.name,
  451. );
  452. continue;
  453. }
  454. shaderProperties.setMaterial( material );
  455. }
  456. return material;
  457. }
  458. toMaterials( materialName = null ) {
  459. const materials = {};
  460. const surfaceMaterials = this.children.filter( ( nodeX ) => nodeX.element === 'surfacematerial' );
  461. let selectedSurfaceMaterials = surfaceMaterials;
  462. if ( materialName ) {
  463. selectedSurfaceMaterials = surfaceMaterials.filter( ( nodeX ) => nodeX.name === materialName );
  464. if ( selectedSurfaceMaterials.length === 0 ) {
  465. this.materialX.log.add(
  466. MaterialXLogCodes.MISSING_MATERIAL,
  467. `Could not find surfacematerial named "${materialName}".`,
  468. );
  469. }
  470. }
  471. for ( const nodeX of selectedSurfaceMaterials ) {
  472. const material = nodeX.toPhysicalMaterial();
  473. materials[ material.name ] = material;
  474. }
  475. if ( Object.keys( materials ).length === 0 ) {
  476. for ( const nodeX of this.children ) {
  477. if ( nodeX.element === 'nodegraph' ) {
  478. const material = nodeX.toBasicMaterial();
  479. materials[ material.name ] = material;
  480. }
  481. }
  482. }
  483. return materials;
  484. }
  485. add( materialXNode ) {
  486. materialXNode.parent = this;
  487. this.children.push( materialXNode );
  488. }
  489. }
  490. class MaterialXDocument {
  491. constructor( manager, path, log, archiveResolver = null, uvSpace = 'bottom-left' ) {
  492. this.manager = manager;
  493. this.path = path;
  494. this.log = log;
  495. this.archiveResolver = archiveResolver;
  496. this.uvSpace = normalizeUvSpace( uvSpace );
  497. this.nodesXLib = new Map();
  498. this.imageLoader = new ImageLoader( manager );
  499. this.imageLoader.setPath( path );
  500. this.textureLoader = new ImageBitmapLoader( manager );
  501. this.textureLoader.setOptions( { imageOrientation: 'none' } );
  502. this.textureLoader.setPath( path );
  503. this.textureCache = new Map();
  504. const bottomLeftUvSpaceHelpers = getBottomLeftUvSpaceHelpers( this.uvSpace );
  505. this.compileContext = {
  506. compileRegistry: COMPILE_REGISTRY,
  507. nodeLibrary: MtlXLibrary,
  508. ...bottomLeftUvSpaceHelpers,
  509. mxTransformUv: mx_transform_uv,
  510. mxHextileCoord,
  511. mxHextileComputeBlendWeights,
  512. invertConstantMatrixValues,
  513. IDENTITY_MAT3_VALUES,
  514. IDENTITY_MAT4_VALUES,
  515. };
  516. }
  517. resolveTextureURI( uri ) {
  518. if ( this.archiveResolver ) {
  519. const archiveURI = this.archiveResolver( uri );
  520. if ( archiveURI ) return archiveURI;
  521. }
  522. return uri;
  523. }
  524. addMaterialXNode( materialXNode ) {
  525. this.nodesXLib.set( materialXNode.nodePath, materialXNode );
  526. }
  527. getMaterialXNode( ...names ) {
  528. return this.nodesXLib.get( names.join( '/' ) );
  529. }
  530. parseNode( nodeXML, nodePath = '' ) {
  531. return parseMaterialXNodeTree(
  532. nodeXML,
  533. ( childNodeXML, childNodePath ) => new MaterialXNode( this, childNodeXML, childNodePath ),
  534. ( materialXNode ) => this.addMaterialXNode( materialXNode ),
  535. nodePath,
  536. );
  537. }
  538. parse( text, materialName = null, options = {} ) {
  539. const rootNode = parseMaterialXText(
  540. text,
  541. ( childNodeXML, childNodePath ) => new MaterialXNode( this, childNodeXML, childNodePath ),
  542. ( materialXNode ) => this.addMaterialXNode( materialXNode ),
  543. );
  544. if ( options.interfaceValidator ) {
  545. options.interfaceValidator( rootNode, this.log );
  546. }
  547. const materials = rootNode.toMaterials( materialName );
  548. return {
  549. materials,
  550. log: this.log.entries,
  551. errors: this.log.errors,
  552. warnings: this.log.warnings,
  553. };
  554. }
  555. }
  556. export { MaterialXDocument };
粤ICP备19079148号