17let MTLX_MATERIAL_PREFIX =
'MAT_'
23let MTLX_DEFAULT_SHADER_NAME =
'SHD_0'
29let MTLX_SHADER_PREFIX =
'SHD_'
36let MTLX_INTERFACEINPUT_NAME_ATTRIBUTE =
'interfacename';
54let MTLX_GLTF_PBR_CATEGORY =
'gltf_pbr'
60let MTLX_UNLIT_CATEGORY_STRING =
'surface_unlit'
66let MULTI_OUTPUT_TYPE_STRING =
'multioutput'
79 return (() => {
throw new Error(
"Method not implemented. This is a required base class method."); })();
88 return (() => {
throw new Error(
"Method not implemented. This is a required base class method."); })();
100 return (() => {
throw new Error(
"Method not implemented. This is a required base class method."); })();
109 return (() => {
throw new Error(
"Method not implemented. This is a required base class method."); })();
118class glTFMaterialX
extends MxConverter {
150 scalarToString(value, type) {
151 let returnvalue =
null;
153 const supportedTypes = [
'string',
'integer',
'matrix33',
'matrix44',
'vector2',
'vector3',
'vector4',
'float',
'color3',
'color4'];
154 const arrayTypes = [
'matrix33',
'matrix44',
'vector2',
'vector3',
'vector4',
'color3',
'color4'];
156 if (supportedTypes.includes(type)) {
157 if (arrayTypes.includes(type)) {
158 returnvalue = value.map(x => String(x)).join(
', ');
160 returnvalue = String(value);
174 stringToScalar(value, type) {
175 let returnvalue = value;
177 const scalarTypes = [
'integer',
'matrix33',
'matrix44',
'vector2',
'vector3',
'vector4',
'float',
'color3',
'color4'];
179 if (scalarTypes.includes(type)) {
180 const splitvalue = value.split(
',');
182 if (splitvalue.length > 1) {
183 returnvalue = splitvalue.map(x => parseFloat(x));
185 if (type ===
'integer') {
186 returnvalue = parseInt(value, 10);
188 returnvalue = parseFloat(value);
206 initialize_gtlf_texture(texture, name, uri, images) {
218 texture.source = images.length - 1;
228 getGLTFTextureUri(texture, images) {
230 if (texture &&
'source' in texture) {
231 const source = texture.source;
232 if (source < images.length) {
233 const image = images[source];
234 if (
'uri' in image) {
249 haveExtensions(glTFDoc) {
251 let extensionsUsed = glTFDoc.extensionsUsed ||
null;
252 if (extensionsUsed ===
null) {
253 return [
null,
'No extension used'];
255 let found = [
false,
false];
256 for (let ext of extensionsUsed) {
257 if (ext ===
'KHR_texture_procedurals') {
260 if (ext ===
'EXT_texture_procedurals_mx_1_39') {
265 return [
null,
'Missing KHR_texture_procedurals extension'];
267 return [
null,
'Missing EXT_texture_procedurals_mx_1_39 extension'];
279 addInputsFromNodeDef(node, nodeDef) {
281 for (let nodeDefInput of nodeDef.getActiveInputs()) {
282 let inputName = nodeDefInput.getName();
283 let nodeInput = node.getInput(inputName);
286 nodeInput = node.addInput(inputName, nodeDefInput.getType());
287 if (nodeDefInput.hasValueString()) {
288 nodeInput.setValueString(nodeDefInput.getValueString(), nodeDefInput.getType());
304 return [
null,
'MaterialX runtime not passed in'];
306 let glTFDoc = JSON.parse(glTFDocString);
308 let extensionCheck = this.haveExtensions(glTFDoc);
309 if (extensionCheck[0] ===
null) {
310 return extensionCheck;
316 this.importGraphs(
doc, glTFDoc);
318 let global_extensions = glTFDoc.extensions ||
null;
319 let procedurals =
null;
320 if (global_extensions && global_extensions.KHR_texture_procedurals) {
321 procedurals = global_extensions.KHR_texture_procedurals.procedurals ||
null;
322 console.log(
'Imported all procedurals:', procedurals);
326 let shaderName = MTLX_DEFAULT_SHADER_NAME;
328 let materialIndex = 1;
329 let glTFmaterials = glTFDoc.materials ||
null;
334 inputMaps[MTLX_GLTF_PBR_CATEGORY] = [
335 [
'base_color',
'baseColorTexture',
'pbrMetallicRoughness'],
336 [
'metallic',
'metallicRoughnessTexture',
'pbrMetallicRoughness'],
337 [
'roughness',
'metallicRoughnessTexture',
'pbrMetallicRoughness'],
338 [
'occlusion',
'occlusionTexture',
''],
339 [
'normal',
'normalTexture',
''],
340 [
'emissive',
'emissiveTexture',
'']
342 inputMaps[MTLX_UNLIT_CATEGORY_STRING] = [[
'emission_color',
'baseColorTexture',
'pbrMetallicRoughness']]
344 for (let glTFmaterial of glTFmaterials) {
347 let mtlxShaderName = glTFmaterial.name;
348 let mtlxMaterialName =
'';
349 if (mtlxShaderName.length == 0) {
350 mtlxShaderName = shaderName + String(materialIndex);
352 mtlxMaterialName = materialName + String(materialIndex);
355 mtlxMaterialName =
'MAT_' + mtlxShaderName;
357 mtlxShaderName =
doc.createValidChildName(mtlxShaderName);
358 mtlxMaterialName =
doc.createValidChildName(mtlxMaterialName);
362 let use_unlit =
false;
363 let extensions = glTFmaterial.extensions ||
null;
365 let KHR_materials_unlit = extensions.KHR_materials_unlit ||
null;
366 if (KHR_materials_unlit) {
371 let shaderCategory = MTLX_GLTF_PBR_CATEGORY;
372 let nodedefString =
'ND_gltf_pbr_surfaceshader';
374 shaderCategory = MTLX_UNLIT_CATEGORY_STRING;
375 nodedefString =
'ND_surface_unlit';
377 let comment =
doc.addChildOfCategory(
'comment')
378 comment.setDocString(
' Generated shader: ' + mtlxShaderName +
' ')
379 let shaderNode =
doc.addNode(shaderCategory, mtlxShaderName,
ne_mx.SURFACE_SHADER_TYPE_STRING);
381 shaderNode.setAttribute(
ne_mx.InterfaceElement.NODE_DEF_ATTRIBUTE, nodedefString)
383 let nodedef =
stdlib.getNodeDef(nodedefString);
385 this.addInputsFromNodeDef(shaderNode, nodedef);
389 let defaultGraphName =
'nodegraph';
391 let currentMap = inputMaps[shaderCategory];
392 for (let map of currentMap) {
393 let destInput = map[0];
394 let sourceTexture = map[1];
395 let sourceParent = map[2];
396 if (sourceParent.length > 0) {
397 if (sourceParent ==
'pbrMetallicRoughness') {
398 let newSource = glTFmaterial.pbrMetallicRoughness[sourceTexture];
400 sourceTexture = newSource;
404 sourceTexture = glTFmaterial[sourceTexture];
407 let baseColorTexture = sourceTexture;
409 if (baseColorTexture) {
411 if (baseColorTexture) {
412 let extensions = baseColorTexture.extensions ||
null;
415 let KHR_texture_procedurals = extensions.KHR_texture_procedurals ||
null;
416 if (KHR_texture_procedurals) {
417 let pindex = KHR_texture_procedurals.index;
418 let output = KHR_texture_procedurals.output;
420 if (pindex !=
null) {
421 if (pindex < procedurals.length) {
423 let proc = procedurals[pindex];
425 let nodegraphName = defaultGraphName;
427 nodegraphName = proc.name;
429 let graphOutputs = proc.outputs ||
null;
430 if (graphOutputs && graphOutputs.length > 0) {
431 let proc_output = graphOutputs[0];
432 if (output !=
null) {
433 proc_output = graphOutputs[output];
436 let input = shaderNode.getInput(destInput);
438 input = shaderNode.addInput(destInput, proc_output.type);
441 input.setNodeGraphString(nodegraphName);
442 input.setAttribute(
'output', proc_output.name);
456 comment =
doc.addChildOfCategory(
'comment')
457 comment.setDocString(
' Generated material: ' + mtlxMaterialName +
' ')
458 let materialNode =
doc.addNode(
ne_mx.SURFACE_MATERIAL_NODE_STRING, mtlxMaterialName,
ne_mx.MATERIAL_TYPE_STRING)
459 let shaderInput = materialNode.addInput(
ne_mx.SURFACE_SHADER_TYPE_STRING,
ne_mx.SURFACE_SHADER_TYPE_STRING)
463 console.log(
'>> Import material:', materialNode.getName(),
'Shader:', shaderNode.getName());
468 let asset = glTFDoc.asset ||
null;
472 let version = asset.version ||
null;
474 let comment =
doc.addChildOfCategory(
'comment');
475 docDoc +=
'glTF version: ' + version +
'. ';
476 comment.setDocString(
'glTF version: ' + version);
478 let generator = asset.generator ||
null;
480 let comment =
doc.addChildOfCategory(
'comment');
481 docDoc +=
'glTF generator: ' + generator +
'. ';
482 comment.setDocString(
'glTF generator: ' + generator);
484 let copyRight = asset.copyright ||
null;
486 let comment =
doc.addChildOfCategory(
'comment');
487 docDoc +=
'Copyright: ' + copyRight +
'. ';
488 comment.setDocString(
'Copyright: ' + copyRight);
491 if (docDoc.length > 0) {
492 doc.setAttribute(
'doc', docDoc);
499 var valid =
doc.validate(errors);
502 errorString = errors.message
505 let docString =
ne_mx.writeToXmlString(
doc);
507 return [docString, errorString];
517 importGraphs(
doc, gltfDoc) {
518 let root_mtlx =
null;
521 let extensions = gltfDoc.extensions ||
null;
522 let procedurals =
null;
524 procedurals = extensions.KHR_texture_procedurals.procedurals ||
null;
527 if (procedurals ===
null) {
528 console.log(
'> Error - no procedurals array found');
536 for (let proc of procedurals) {
537 console.log(`> Scan procedural ${procindex} of ${procedurals.length} :`);
538 if (!proc.nodetype) {
539 console.log(
'>> Warning: No nodetype found in procedural. SKipping');
543 if (proc.nodetype !==
'nodegraph') {
544 console.log(
'>> Skip unsupported rocedural nodetype:', proc.nodetype);
548 let graphname =
'graph_' + graph_index;
550 graphname = proc.name;
554 let mtlxgraph =
doc.addNodeGraph(graphname);
555 root_mtlx = mtlxgraph;
558 let output_index = 0;
561 let inputs = proc.inputs || [];
562 let outputs = proc.outputs || [];
563 let nodes = proc.nodes || [];
566 for (let input of inputs) {
567 let inputname = input.name ||
'input_' + input_index;
572 let inputtype = input.type ||
null;
574 console.log(
'>> Error - Input type not found for graph input:', inputname);
578 let mtlxinput = mtlxgraph.addInput(inputname, inputtype);
580 if (input.colorspace) {
581 mtlxinput.setAttribute(
'colorspace', input.colorspace);
584 if (inputtype ===
'filename') {
585 let textureIndex = input.texture ||
null;
586 if (textureIndex !==
null) {
587 let gltftextures = gltfDoc.textures ||
null;
588 let gltfimages = gltfDoc.images ||
null;
589 if (gltftextures && gltfimages) {
590 let gltftexture = gltftextures[textureIndex] ||
null;
592 let uri = this.getGLTFTextureUri(gltftexture, gltfimages);
593 mtlxinput.setValueString(uri, inputtype);
599 let inputvalue = input.value ||
null;
600 if (inputvalue !==
null) {
601 let mtlxvalue = this.scalarToString(inputvalue, inputtype);
602 if (mtlxvalue !==
null) {
603 mtlxinput.setValueString(mtlxvalue, inputtype);
605 console.log(
'>> Error - Unsupported handle input type:', inputtype,
'. Performing straight assignment.');
606 mtlxinput.setValueString(String(inputvalue, inputtype));
609 console.log(
'>> Invalid usage of top level graph connections:', inputname);
610 if (input.procedural) {
617 for (let output of outputs) {
618 let outputname = output.name ||
'output_' + output_index;
619 let outputtype = output.type ||
null;
620 let mtlxgraph_output = mtlxgraph.addOutput(outputname, outputtype);
622 let connectable =
null;
623 if (output.input !== undefined) {
624 connectable = inputs[output.input];
626 mtlxgraph_output.setAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE, connectable.name);
628 console.log(
'>> Error - Input not found:', output.input, inputs);
637 else if (output.node !== undefined) {
638 connectable = nodes[output.node];
642 if (output.output !== undefined) {
643 mtlxgraph_output.setAttribute(
'output', output.output);
647 console.log(
'>> Error - Output node not found:', output.node, nodes);
652 for (let node of nodes) {
653 let nodename = node.name ||
'node_' + node_index;
654 let nodetype = node.nodetype ||
null;
655 let outputType = node.type ||
null;
656 let node_outputs = node.outputs || [];
657 if (node_outputs.length > 1) {
658 outputType = MULTI_OUTPUT_TYPE_STRING;
660 let mtlxnode = mtlxgraph.addChildOfCategory(nodetype);
661 mtlxnode.setName(nodename);
663 mtlxnode.setType(outputType);
665 console.log(
'>> Error - no output type for node:', nodename);
669 let node_inputs = node.inputs || [];
670 for (let input of node_inputs) {
671 let inputname = input.name ||
'input_' + input_index;
672 let inputtype = input.type ||
null;
673 let mtlxinput = mtlxnode.addInput(inputname, inputtype);
675 if (input.colorspace) {
676 mtlxinput.setAttribute(
'colorspace', input.colorspace);
679 if (inputtype ===
'filename') {
680 let textureIndex = input.texture ||
null;
681 if (textureIndex !==
null) {
682 let gltftextures = gltfDoc.textures ||
null;
683 let gltfimages = gltfDoc.images ||
null;
684 if (gltftextures && gltfimages) {
685 let gltftexture = gltftextures[textureIndex] ||
null;
687 let uri = this.getGLTFTextureUri(gltftexture, gltfimages);
688 mtlxinput.setValueString(uri, inputtype);
694 let inputvalue = input.value ||
null;
695 if (inputvalue !==
null) {
696 let mtlxvalue = this.scalarToString(inputvalue, inputtype);
697 if (mtlxvalue !==
null) {
698 mtlxinput.setValueString(mtlxvalue, inputtype);
700 console.log(
'>> Error - Unsupported input type:', inputtype,
'. Performing straight assignment.');
701 mtlxinput.setValueString(String(inputvalue), inputtype);
704 let connectable =
null;
705 if (input.input !== undefined) {
706 connectable = inputs[input.input];
707 mtlxinput.setAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE, connectable.name);
708 }
else if (input.output !== undefined) {
709 if (input.node !== undefined) {
710 mtlxinput.setAttribute(
'output', input.output);
712 connectable = outputs[input.output];
713 mtlxinput.setAttribute(
'output', connectable.name);
716 if (input.node !== undefined) {
717 connectable = nodes[input.node];
724 for (let output of node_outputs) {
725 let outputname = output.name ||
'output_' + output_index;
726 let outputtype = output.type ||
null;
727 let mtlxoutput = mtlxnode.addOutput(outputname, outputtype);
729 let connectable =
null;
730 if (output.input !== undefined) {
731 connectable = inputs[output.input];
732 mtlxoutput.setAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE, connectable.name);
733 }
else if (output.output !== undefined) {
734 connectable = outputs[output.output];
735 mtlxoutput.setAttribute(
'output', connectable.name);
737 if (output.node !== undefined) {
738 connectable = nodes[output.node];
758 let no_result = [
null,
null,
null]
761 console.log(
'MaterialX runtime not passed in')
765 let graphOutputs =
graph.getOutputs();
766 if (graphOutputs.length == 0) {
767 console.log(
'No graph outputs found on graph: ',
graph.getNamePath())
772 const usePaths =
false;
775 const fallback =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z/AfAAQAAf/zKSWvAAAAAElFTkSuQmCC';
776 let fallbackIndex = -1;
778 let imageArray = json[
'images'] || [];
779 if (!json[
'images']) json[
'images'] = imageArray;
781 for (let i = 0; i < imageArray.length; i++) {
782 const image = imageArray[i];
783 if (image[
'uri'] === fallback) {
789 let fallbackTextureIndex = -1;
790 if (fallbackIndex === -1) {
793 'name':
'KHR_texture_procedural_fallback'
795 imageArray.push(image);
796 fallbackIndex = imageArray.length - 1;
799 let textureArray = json[
'textures'] || [];
800 if (!json[
'textures']) json[
'textures'] = textureArray;
802 for (let i = 0; i < textureArray.length; i++) {
803 const texture = textureArray[i];
804 if (texture[
'source'] === fallbackIndex) {
805 fallbackTextureIndex = i;
810 if (fallbackTextureIndex === -1) {
811 textureArray.push({
'source': fallbackIndex });
812 fallbackTextureIndex = textureArray.length - 1;
815 const skipAttr = [
'uiname',
'xpos',
'ypos'];
816 const procDictNodes = {};
817 const procDictInputs = {};
818 const procDictOutputs = {};
820 let extensions = json[
'extensions'] || {};
821 if (!json[
'extensions']) json[
'extensions'] = extensions;
823 let KHR_texture_procedurals = extensions[
'KHR_texture_procedurals'] || {};
824 if (!extensions[
'KHR_texture_procedurals']) extensions[
'KHR_texture_procedurals'] = KHR_texture_procedurals;
826 if (!KHR_texture_procedurals[
'procedurals']) {
827 KHR_texture_procedurals[
'procedurals'] = [];
830 const procs = KHR_texture_procedurals[
'procedurals'];
832 'name': usePaths ?
graph.getNamePath() :
graph.getName(),
833 'nodetype':
graph.getCategory()
836 nodegraph[
'type'] = graphOutputs.length > 1 ?
'multioutput' : graphOutputs[0].getType();
837 const nodegraphInputs = nodegraph[
'inputs'] = [];
838 const nodegraphOutputs = nodegraph[
'outputs'] = [];
839 const nodegraphNodes = nodegraph[
'nodes'] = [];
840 procs.push(nodegraph);
842 const metadata = [
'colorspace',
'unit',
'unittype',
'uiname',
'uimin',
'uimax',
'uifolder',
'doc'];
844 graph.getNodes().forEach((node) => {
846 'name': usePaths ? node.getNamePath() : node.getName()
848 nodegraphNodes.push(jsonNode);
849 procDictNodes[node.getNamePath()] = nodegraphNodes.length - 1;
852 graph.getInputs().forEach((input) => {
854 'name': usePaths ? input.getNamePath() : input.getName(),
855 'nodetype': input.getCategory()
858 metadata.forEach((meta) => {
859 if (input.getAttribute(meta)) {
860 jsonNode[meta] = input.getAttribute(meta);
864 if (input.getValue() !==
null) {
865 const inputType = input.getAttribute(
ne_mx.TypedElement.TYPE_ATTRIBUTE);
866 jsonNode[
'type'] = inputType;
867 if (inputType ===
ne_mx.FILENAME_TYPE_STRING) {
869 const filename = input.getResolvedValueString();
871 this.initialize_gtlf_texture(texture, input.getNamePath(), filename, imageArray);
872 textureArray.push(texture);
873 jsonNode[
'texture'] = textureArray.length - 1;
875 let value = input.getValueString();
876 value = this.stringToScalar(value, inputType);
877 jsonNode[
'value'] = value;
879 nodegraphInputs.push(jsonNode);
880 procDictInputs[input.getNamePath()] = nodegraphInputs.length - 1;
889 console.error(
'Error: no value or invalid connection specified for input. Input skipped:', input.getNamePath());
894 graphOutputs.forEach((output) => {
896 'name': usePaths ? output.getNamePath() : output.getName()
898 nodegraphOutputs.push(jsonNode);
899 procDictOutputs[output.getNamePath()] = nodegraphOutputs.length - 1;
901 jsonNode[
'nodetype'] = output.getCategory();
902 jsonNode[
'type'] = output.getType();
904 let connection = output.getAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE);
905 if (connection.length == 0)
908 const connectionNode =
graph.getChild(connection);
909 if (connectionNode) {
910 let connectionPath = connectionNode.getNamePath()
912 jsonNode[
'debug_connection_path'] = connectionPath;
914 if (procDictInputs[connectionPath] !=
null) {
915 jsonNode[
'input'] = procDictInputs[connectionPath];
916 }
else if (procDictNodes[connectionPath] !=
null) {
917 jsonNode[
'node'] = procDictNodes[connectionPath];
919 console.error(
'Invalid output connection to:', connectionPath);
922 const outputString = output.getAttribute(
'output');
923 if (outputString.length > 0) {
924 jsonNode[
'output'] = outputString;
929 graph.getNodes().forEach((node) => {
931 const index = procDictNodes[node.getNamePath()];
932 jsonNode = nodegraphNodes[index];
933 jsonNode[
'nodetype'] = node.getCategory();
934 const nodedef = node.getNodeDef();
936 if (debug && nodedef && nodedef.getNodeGroup().length) {
937 jsonNode[
'nodegroup'] = nodedef.getNodeGroup();
940 node.getAttributeNames().forEach((attrName) => {
941 if (!skipAttr.includes(attrName)) {
942 jsonNode[attrName] = node.getAttribute(attrName);
947 node.getInputs().forEach((input) => {
949 'name': input.getName(),
953 metadata.forEach((meta) => {
954 if (input.getAttribute(meta)) {
955 inputItem[meta] = input.getAttribute(meta);
959 const inputType = input.getAttribute(
ne_mx.TypedElement.TYPE_ATTRIBUTE);
960 inputItem[
'type'] = inputType;
962 if (input.getValue() !==
null) {
963 if (inputType ===
ne_mx.FILENAME_TYPE_STRING) {
965 const filename = input.getResolvedValueString();
966 this.initialize_gtlf_texture(texture, input.getNamePath(), filename, imageArray);
967 textureArray.push(texture);
968 inputItem[
'texture'] = textureArray.length - 1;
970 let value = input.getValueString();
971 value = this.stringToScalar(value, inputType);
972 inputItem[
'value'] = value;
975 let isInterface =
true;
976 let connection = input.getAttribute(MTLX_INTERFACEINPUT_NAME_ATTRIBUTE);
977 if (!connection.length) {
982 if (connection.length > 0) {
983 const connectionNode =
graph.getChild(connection);
984 if (connectionNode) {
985 const inputType = input.getAttribute(
ne_mx.TypedElement.TYPE_ATTRIBUTE);
986 inputItem[
'type'] = inputType;
987 let connectionPath = connectionNode.getNamePath();
989 inputItem[
'debug_connection_path'] = connectionPath;
992 if (isInterface && procDictInputs[connectionPath] !=
null) {
993 inputItem[
'input'] = procDictInputs[connectionPath];
994 }
else if (procDictNodes[connectionPath] !=
null) {
995 inputItem[
'node'] = procDictNodes[connectionPath];
998 const outputString = input.getAttribute(
'output');
999 if (outputString.length > 0) {
1000 const connectedNodeOutputs = connectionNode.getOutputs();
1001 for (let i = 0; i < connectedNodeOutputs.length; i++) {
1002 if (connectedNodeOutputs[i].getName() === outputString) {
1003 inputItem[
'output'] = i;
1009 console.error(
'Error: Invalid input connection to:', connection,
' from: input:', input.getNamePath(),
' node:', node.getNamePath());
1014 console.error(
'Error: Invalid input connection to:', connection,
' from: input:', input.getNamePath(),
' node:', node.getNamePath());
1018 inputs.push(inputItem);
1021 if (inputs.length > 0) {
1022 jsonNode[
'inputs'] = inputs;
1026 node.getOutputs().forEach((output) => {
1027 const outputItem = {
1028 'nodetype':
'output',
1029 'name': output.getName(),
1030 'type': output.getType()
1032 outputs.push(outputItem);
1036 nodedef.getOutputs().forEach((output) => {
1038 outputs.forEach((outputItem) => {
1039 if (outputItem[
'name'] === output.getName()) {
1045 const outputItem = {
1046 'nodetype':
'output',
1047 'name': output.getName(),
1048 'type': output.getType()
1050 outputs.push(outputItem);
1054 console.error(
'Missing nodedef for node:', node.getNamePath());
1057 if (outputs.length > 0) {
1058 jsonNode[
'outputs'] = outputs;
1062 return [procs, procDictOutputs, procDictNodes, fallbackTextureIndex];
1071 export(
ne_mx, glTFDoc) {
1074 status =
'MaterialX runtime not passed in'
1075 return [
null, status];
1080 status =
'Invalid document to convert';
1081 return [
null, status];
1085 let mxMaterials = glTFDoc.getMaterialNodes();
1086 if (mxMaterials.length == 0) {
1094 "generator":
"MaterialX 1.39 to glTF 2.0 procedural textures converter",
1095 "copyright":
"Copyright (c) 2024, Bernard Kwok"
1099 inputMaps[MTLX_GLTF_PBR_CATEGORY] = [
1100 [
'base_color',
'baseColorTexture',
'pbrMetallicRoughness'],
1101 [
'metallic',
'metallicRoughnessTexture',
'pbrMetallicRoughness'],
1102 [
'roughness',
'metallicRoughnessTexture',
'pbrMetallicRoughness'],
1103 [
'occlusion',
'occlusionTexture',
''],
1104 [
'normal',
'normalTexture',
''],
1105 [
'emissive',
'emissiveTexture',
'']
1107 inputMaps[MTLX_UNLIT_CATEGORY_STRING] = [[
'emission_color',
'baseColorTexture',
'pbrMetallicRoughness']]
1110 let fallbackTextureIndex = -1;
1112 let exportGraphNames = [];
1114 for (let mxMaterial of mxMaterials) {
1115 let mxshaders =
ne_mx.getShaderNodes(mxMaterial);
1116 for (let shaderNode of mxshaders) {
1117 let category = shaderNode.getCategory();
1118 let path = shaderNode.getNamePath()
1119 let isUnlit = (category == MTLX_UNLIT_CATEGORY_STRING);
1120 let isPBR = (category == MTLX_GLTF_PBR_CATEGORY);
1121 if ((isPBR || isUnlit) && pbrNodes[path] ==
null) {
1122 console.log(
'> Convert shader to glTF:', shaderNode.getNamePath(),
'Category:', category);
1124 pbrNodes[path] = shaderNode;
1128 let base_color_input =
null;
1129 let base_color_output =
'';
1130 let inputPairs = inputMaps[category];
1131 for (let inputPair of inputPairs) {
1133 base_color_input = shaderNode.getInput(inputPair[0]);
1134 base_color_output = inputPair[1];
1136 if (!base_color_input) {
1140 let nodeGraphName = base_color_input.getNodeGraphString();
1141 if (nodeGraphName.length == 0) {
1145 let nodeGraphOutput = base_color_input.getOutputString();
1146 material[
'name'] = path;
1148 let parent = material;
1149 if (inputPair[2].length > 0) {
1150 if (!material[inputPair[2]]) {
1151 material[inputPair[2]] = {};
1153 parent = material[inputPair[2]]
1157 let graphIndex = -1;
1158 let outputIndex = -1;
1161 for (let proc of procs) {
1162 if (proc[
'name'] == nodeGraphName) {
1164 if (nodeGraphOutput.length > 0) {
1165 let outputs = proc[
'outputs'];
1167 for (let output of outputs) {
1168 if (output[
'name'] == nodeGraphOutput) {
1181 if (graphIndex >= 0) {
1182 let baseColorTexture = parent[base_color_output] = {}
1183 baseColorTexture[
'index'] = fallbackTextureIndex;
1184 let ext = baseColorTexture[
'extensions'] = {}
1187 ext[
'KHR_materials_unlit'] = {};
1189 let lookup = ext[
'KHR_texture_procedurals'] = {}
1190 lookup[
'index'] = graphIndex;
1191 if (outputIndex >= 0) {
1192 lookup[
'output'] = outputIndex;
1197 let
graph = glTFDoc.getNodeGraph(nodeGraphName);
1198 exportGraphNames.push(nodeGraphName);
1200 let gltfInfo = this.exportGraph(
ne_mx,
graph, json, materials)
1203 let outputNodes = gltfInfo[1];
1204 let proceduralNodes = gltfInfo[2];
1205 fallbackTextureIndex = gltfInfo[3];
1211 let baseColorTexture = parent[base_color_output] = {}
1212 baseColorTexture[
'index'] = fallbackTextureIndex;
1213 let ext = baseColorTexture[
'extensions'] = {}
1216 ext[
'KHR_materials_unlit'] = {};
1218 let lookup = ext[
'KHR_texture_procedurals'] = {}
1219 lookup[
'index'] = procs.length - 1;
1220 let outputIndex = -1;
1222 if (nodeGraphOutput.length > 0) {
1223 for (let outputNodeName in outputNodes) {
1224 let nodeGraphOutputPath = (nodeGraphName +
'/' + nodeGraphOutput);
1225 if (outputNodeName == nodeGraphOutputPath) {
1226 outputIndex = outputNodes[outputNodeName];
1230 if (outputIndex == -1) {
1231 console.error(
'Failed to find output:', nodeGraphOutput,
' in:', outputNodes)
1233 lookup[
'output'] = outputIndex;
1237 lookup[
'output'] = 0;
1244 if (material[
'name']) {
1245 materials.push(material);
1251 let unconnectedGraphs = [];
1252 for (let ng of glTFDoc.getNodeGraphs()) {
1253 let ng_name = ng.getName();
1254 if (ng.getAttribute(
'nodedef') || ng.hasSourceUri()) {
1258 if (!exportGraphNames.includes(ng_name)) {
1259 unconnectedGraphs.push(ng_name);
1261 let gltfInfo = this.exportGraph(
ne_mx, ng, json, materials)
1263 let outputNodes = gltfInfo[1];
1264 let proceduralNodes = gltfInfo[2];
1265 fallbackTextureIndex = gltfInfo[3];
1269 if (materials.length > 0) {
1270 json[
'materials'] = materials;
1271 if (unconnectedGraphs.length > 0) {
1272 status =
'Exported unconnected graphs: ' + unconnectedGraphs.join(
', ');
1276 if (unconnectedGraphs.length > 0) {
1277 status =
'Exported unconnected graphs: ' + unconnectedGraphs.join(
', ');
1280 status =
'No appropriate glTF shader graphs found';
1286 if (procs.length > 0) {
1287 json[
'asset'] = json_asset;
1288 json[
'extensionsUsed'] = [];
1289 json[
'extensionsUsed'].push(
'KHR_texture_procedurals');
1290 json[
'extensionsUsed'].push(
'EXT_texture_procedurals_mx_1_39');
1294 let jsonString = json ? JSON.stringify(json,
null, 2) :
'';
1295 if (jsonString ==
'{}') {
1299 return [jsonString, status];
let MTLX_NODEGRAPH_NAME_ATTRIBUTE
MTLX_NODEGRAPH_NAME_ATTRIBUTE.
let MTLX_NODE_NAME_ATTRIBUTE
MTLX_NODE_NAME_ATTRIBUTE.
let MTLX_DEFAULT_MATERIAL_NAME
jsMaterialXglTF