diff --git a/javascript/MaterialXView/source/dropHandling.js b/javascript/MaterialXView/source/dropHandling.js new file mode 100644 index 0000000000..adcbef2ca1 --- /dev/null +++ b/javascript/MaterialXView/source/dropHandling.js @@ -0,0 +1,281 @@ +import * as THREE from 'three'; +import * as fflate from 'three/examples/jsm/libs/fflate.module.js'; + +const debugFileHandling = false; +let loadingCallback; + +export function setLoadingCallback(cb) { + loadingCallback = cb; +} + +export function dropHandler(ev) { + if (debugFileHandling) console.log('File(s) dropped', ev.dataTransfer.items, ev.dataTransfer.files); + + // Prevent default behavior (Prevent file from being opened) + ev.preventDefault(); + + if (ev.dataTransfer.items) + { + const allEntries = []; + + let haveGetAsEntry = false; + if (ev.dataTransfer.items.length > 0) + haveGetAsEntry = + ("getAsEntry" in ev.dataTransfer.items[0]) || + ("webkitGetAsEntry" in ev.dataTransfer.items[0]); + + // Useful for debugging file handling on platforms that don't support newer file system APIs + // haveGetAsEntry = false; + + if (haveGetAsEntry) { + for (var i = 0; i < ev.dataTransfer.items.length; i++) + { + let item = ev.dataTransfer.items[i]; + let entry = ("getAsEntry" in item) ? item.getAsEntry() : item.webkitGetAsEntry(); + allEntries.push(entry); + } + handleFilesystemEntries(allEntries); + return; + } + + for (var i = 0; i < ev.dataTransfer.items.length; i++) + { + let item = ev.dataTransfer.items[i]; + + // API when there's no "getAsEntry" support + console.log(item.kind, item); + if (item.kind === 'file') + { + var file = item.getAsFile(); + testAndLoadFile(file); + } + // could also be a directory + else if (item.kind === 'directory') + { + var dirReader = item.createReader(); + dirReader.readEntries(function(entries) { + for (var i = 0; i < entries.length; i++) { + console.log(entries[i].name); + var entry = entries[i]; + if (entry.isFile) { + entry.file(function(file) { + testAndLoadFile(file); + }); + } + } + }); + } + } + } else { + for (var i = 0; i < ev.dataTransfer.files.length; i++) { + let file = ev.dataTransfer.files[i]; + testAndLoadFile(file); + } + } +} + +export function dragOverHandler(ev) { + ev.preventDefault(); +} + +async function getBufferFromFile(fileEntry) { + + if (fileEntry instanceof ArrayBuffer) return fileEntry; + if (fileEntry instanceof String) return fileEntry; + + const name = fileEntry.fullPath || fileEntry.name; + const ext = name.split('.').pop(); + const readAsText = ext === 'mtlx'; + + if (debugFileHandling) console.log("reading ", fileEntry, "as text?", readAsText); + + if (debugFileHandling) console.log("getBufferFromFile", fileEntry); + const buffer = await new Promise((resolve, reject) => { + function readFile(file) { + var reader = new FileReader(); + reader.onloadend = function(e) { + if (debugFileHandling) console.log("loaded", "should be text?", readAsText, this.result); + resolve(this.result); + }; + + if (readAsText) + reader.readAsText(file); + else + reader.readAsArrayBuffer(file); + } + + if ("file" in fileEntry) { + fileEntry.file(function(file) { + readFile(file); + }, (e) => { + console.error("Error reading file ", e); + }); + } + else { + readFile(fileEntry); + } + }); + return buffer; +} + +async function handleFilesystemEntries(entries) { + const allFiles = []; + const fileIgnoreList = [ + '.gitignore', + 'README.md', + '.DS_Store', + ] + const dirIgnoreList = [ + '.git', + 'node_modules', + ] + + for (let entry of entries) { + if (debugFileHandling) console.log("file entry", entry) + if (entry.isFile) { + if (debugFileHandling) console.log("single file", entry); + if (fileIgnoreList.includes(entry.name)) { + continue; + } + allFiles.push(entry); + } + else if (entry.isDirectory) { + if (dirIgnoreList.includes(entry.name)) { + continue; + } + const files = await readDirectory(entry); + if (debugFileHandling) console.log("all files", files); + for (const file of files) { + if (fileIgnoreList.includes(file.name)) { + continue; + } + allFiles.push(file); + } + } + } + + const imageLoader = new THREE.ImageLoader(); + + // unpack zip files first + for (const fileEntry of allFiles) { + // special case: zip archives + if (fileEntry.fullPath.toLowerCase().endsWith('.zip')) { + await new Promise(async (resolve, reject) => { + const arrayBuffer = await getBufferFromFile(fileEntry); + + // use fflate to unpack them and add the files to the cache + fflate.unzip(new Uint8Array(arrayBuffer), (error, unzipped) => { + // push these files into allFiles + for (const [filePath, buffer] of Object.entries(unzipped)) { + + // mock FileEntry for easier usage downstream + const blob = new Blob([buffer]); + const newFileEntry = { + fullPath: "/" + filePath, + name: filePath.split('/').pop(), + file: (callback) => { + callback(blob); + }, + isFile: true, + }; + allFiles.push(newFileEntry); + } + + resolve(); + }); + }); + } + } + + // sort so mtlx files come first + allFiles.sort((a, b) => { + if (a.name.endsWith('.mtlx') && !b.name.endsWith('.mtlx')) { + return -1; + } + if (!a.name.endsWith('.mtlx') && b.name.endsWith('.mtlx')) { + return 1; + } + return 0; + }); + + if (debugFileHandling) console.log("all files", allFiles); + + // put all files in three' Cache + for (const fileEntry of allFiles) { + + const allowedFileTypes = [ + 'png', 'jpg', 'jpeg' + ]; + + const ext = fileEntry.fullPath.split('.').pop(); + if (!allowedFileTypes.includes(ext)) { + // console.log("skipping file", fileEntry.fullPath); + continue; + } + + const buffer = await getBufferFromFile(fileEntry); + const img = await imageLoader.loadAsync(URL.createObjectURL(new Blob([buffer]))); + if (debugFileHandling) console.log("caching file", fileEntry.fullPath, img); + THREE.Cache.add(fileEntry.fullPath, img); + } + + // TODO we could also allow dropping of multiple MaterialX files (or folders with them inside) + // and seed the dropdown from that. + // At that point, actually reading files and textures into memory should be deferred until they are actually used. + const rootFile = allFiles[0]; + THREE.Cache.add(rootFile.fullPath, await getBufferFromFile(rootFile)); + + if (debugFileHandling) console.log("CACHE", THREE.Cache.files); + + loadingCallback(rootFile); +} + +async function readDirectory(directory) { + let entries = []; + let getEntries = async (directory) => { + let dirReader = directory.createReader(); + await new Promise((resolve, reject) => { + dirReader.readEntries( + async (results) => { + if (results.length) { + // entries = entries.concat(results); + for (let entry of results) { + if (entry.isDirectory) { + await getEntries(entry); + } + else { + entries.push(entry); + } + } + } + resolve(); + }, + (error) => { + /* handle error — error is a FileError object */ + }, + )} + )}; + + await getEntries(directory); + return entries; +} + +async function testAndLoadFile(file) { + let ext = file.name.split('.').pop(); + if (debugFileHandling) console.log(file.name + ", " + file.size + ", " + ext); + + const arrayBuffer = await getBufferFromFile(file); + console.log(arrayBuffer) + + // mock a fileEntry and pass through the same loading logic + const newFileEntry = { + fullPath: "/" + file.name, + name: file.name.split('/').pop(), + isFile: true, + file: (callback) => { + callback(file); + } + }; + + handleFilesystemEntries([newFileEntry]); +} \ No newline at end of file diff --git a/javascript/MaterialXView/source/index.js b/javascript/MaterialXView/source/index.js index 424f63ec43..56e2e1f5a2 100644 --- a/javascript/MaterialXView/source/index.js +++ b/javascript/MaterialXView/source/index.js @@ -12,6 +12,7 @@ import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js'; import { Viewer } from './viewer.js' +import { dropHandler, dragOverHandler, setLoadingCallback } from './dropHandling.js'; let renderer, composer, orbitControls; @@ -115,6 +116,20 @@ function init() console.error(Number.isInteger(err) ? this.getMx().getExceptionMessage(err) : err); }) + // allow dropping files and directories + document.addEventListener('drop', dropHandler, false); + document.addEventListener('dragover', dragOverHandler, false); + + setLoadingCallback(file => { + materialFilename = file.fullPath || file.name; + viewer.getEditor().clearFolders(); + viewer.getMaterial().loadMaterials(viewer, materialFilename); + viewer.getEditor().updateProperties(0.9); + viewer.getScene().setUpdateTransforms(); + }); + + // enable three.js Cache so that dropped files can reference each other + THREE.Cache.enabled = true; } function onWindowResize() diff --git a/javascript/MaterialXView/source/viewer.js b/javascript/MaterialXView/source/viewer.js index 0bc537faf0..d1adfd9ef4 100644 --- a/javascript/MaterialXView/source/viewer.js +++ b/javascript/MaterialXView/source/viewer.js @@ -589,6 +589,7 @@ export class Material // Set search path. Assumes images are relative to current file // location. + if (!materialFilename) materialFilename = "/"; const paths = materialFilename.split('/'); paths.pop(); const searchPath = paths.join('/'); diff --git a/source/MaterialXCore/Document.cpp b/source/MaterialXCore/Document.cpp index f1a0c4bd1a..4ba48cc84b 100644 --- a/source/MaterialXCore/Document.cpp +++ b/source/MaterialXCore/Document.cpp @@ -86,6 +86,7 @@ class Document::Cache for (ElementPtr elem : doc.lock()->traverseTree()) { const string& nodeName = elem->getAttribute(PortElement::NODE_NAME_ATTRIBUTE); + const string& nodeGraphName = elem->getAttribute(PortElement::NODE_GRAPH_ATTRIBUTE); const string& nodeString = elem->getAttribute(NodeDef::NODE_ATTRIBUTE); const string& nodeDefString = elem->getAttribute(InterfaceElement::NODE_DEF_ATTRIBUTE); @@ -97,6 +98,17 @@ class Document::Cache portElementMap.emplace(portElem->getQualifiedName(nodeName), portElem); } } + else + { + if (!nodeGraphName.empty()) + { + PortElementPtr portElem = elem->asA(); + if (portElem) + { + portElementMap.emplace(portElem->getQualifiedName(nodeGraphName), portElem); + } + } + } if (!nodeString.empty()) { NodeDefPtr nodeDef = elem->asA(); diff --git a/source/MaterialXCore/Node.cpp b/source/MaterialXCore/Node.cpp index 28be7e432c..9596d905bc 100644 --- a/source/MaterialXCore/Node.cpp +++ b/source/MaterialXCore/Node.cpp @@ -728,6 +728,25 @@ InterfaceElementPtr NodeGraph::getImplementation() const return nodedef ? nodedef->getImplementation() : InterfaceElementPtr(); } +vector NodeGraph::getDownstreamPorts() const +{ + vector downstreamPorts; + for (PortElementPtr port : getDocument()->getMatchingPorts(getQualifiedName(getName()))) + { + ElementPtr node = port->getParent(); + ElementPtr graph = node ? node->getParent() : nullptr; + if (graph && graph->isA() && graph == getParent()) + { + downstreamPorts.push_back(port); + } + } + std::sort(downstreamPorts.begin(), downstreamPorts.end(), [](const ConstElementPtr& a, const ConstElementPtr& b) + { + return a->getName() > b->getName(); + }); + return downstreamPorts; +} + bool NodeGraph::validate(string* message) const { bool res = true; diff --git a/source/MaterialXCore/Node.h b/source/MaterialXCore/Node.h index 1b1115fde7..55846cf2e3 100644 --- a/source/MaterialXCore/Node.h +++ b/source/MaterialXCore/Node.h @@ -357,6 +357,14 @@ class MX_CORE_API NodeGraph : public GraphElement /// none was found. InterfaceElementPtr getImplementation() const; + /// @} + /// @name Traversal + /// @{ + + /// Return a vector of all downstream ports that connect to this graph, ordered by + /// the names of the port elements. + vector getDownstreamPorts() const; + /// @} /// @name Utility /// @{ diff --git a/source/MaterialXGraphEditor/Graph.cpp b/source/MaterialXGraphEditor/Graph.cpp index e4714dd9b0..801a7e09aa 100644 --- a/source/MaterialXGraphEditor/Graph.cpp +++ b/source/MaterialXGraphEditor/Graph.cpp @@ -644,8 +644,8 @@ void Graph::setPinColor() _pinColor.insert(std::make_pair("BSDF", ImColor(10, 181, 150, 255))); _pinColor.insert(std::make_pair("EDF", ImColor(255, 50, 100, 255))); _pinColor.insert(std::make_pair("VDF", ImColor(0, 100, 151, 255))); - _pinColor.insert(std::make_pair("surfaceshader", ImColor(150, 255, 255, 255))); - _pinColor.insert(std::make_pair("material", ImColor(255, 255, 255, 255))); + _pinColor.insert(std::make_pair(mx::SURFACE_SHADER_TYPE_STRING, ImColor(150, 255, 255, 255))); + _pinColor.insert(std::make_pair(mx::MATERIAL_TYPE_STRING, ImColor(255, 255, 255, 255))); _pinColor.insert(std::make_pair(mx::DISPLACEMENT_SHADER_TYPE_STRING, ImColor(155, 50, 100, 255))); _pinColor.insert(std::make_pair(mx::VOLUME_SHADER_TYPE_STRING, ImColor(155, 250, 100, 255))); _pinColor.insert(std::make_pair(mx::LIGHT_SHADER_TYPE_STRING, ImColor(100, 150, 100, 255))); @@ -701,8 +701,13 @@ void Graph::selectMaterial(UiNodePtr uiNode) // set the node to display in render veiw based off the selected node or nodegraph void Graph::setRenderMaterial(UiNodePtr node) { - // set render node right away is node is a material - if (node->getNode() && node->getNode()->getType() == "material") + // For now surface shaders and materials are considered renderable. + // This can be adjusted as desired to include being able to use outputs, + // and / a sub-graph in the nodegraph. + const mx::StringSet RENDERABLE_TYPES = { mx::MATERIAL_TYPE_STRING, mx::SURFACE_SHADER_TYPE_STRING }; + + // set render node right away is node is renderable + if (node->getNode() && RENDERABLE_TYPES.count(node->getNode()->getType())) { // only set new render node if different material has been selected if (_currRenderNode != node) @@ -712,55 +717,123 @@ void Graph::setRenderMaterial(UiNodePtr node) _renderer->setMaterialCompilation(true); } } + + // Traverse downstream looking for the first renderable element. else { - // continue downstream using output connections until a material node is found - std::vector outNodes = node->getOutputConnections(); - if (outNodes.size() > 0) + mx::NodePtr mtlxNode = node->getNode(); + mx::NodeGraphPtr mtlxNodeGraph = node->getNodeGraph(); + mx::OutputPtr mtlxOutput = node->getOutput(); + if (mtlxOutput) + { + mx::ElementPtr parent = mtlxOutput->getParent(); + if (parent->isA()) + mtlxNodeGraph = parent->asA(); + else if (parent->isA()) + mtlxNode = parent->asA(); + } + mx::StringSet testPaths; + if (mtlxNode) { - if (outNodes[0]->getNode()) + mx::ElementPtr parent = mtlxNode->getParent(); + if (parent->isA()) { - if (outNodes[0]->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING) + // There is no logic to support traversing from inside a functional graph + // to it's instance and hence downstream so skip this from consideration. + // The closest approach would be to "flatten" all definitions to compound graphs. + mx::NodeGraphPtr parentGraph = parent->asA(); + if (parentGraph->getNodeDef()) + { + return; + } + } + testPaths.insert(mtlxNode->getNamePath()); + } + else if (mtlxNodeGraph) + { + testPaths.insert(mtlxNodeGraph->getNamePath()); + } + + mx::NodePtr foundNode = nullptr; + while (!testPaths.empty() && !foundNode) + { + mx::StringSet nextPaths; + for (const std::string& testPath : testPaths) + { + mx::ElementPtr testElem = _graphDoc->getDescendant(testPath); + mx::NodePtr testNode = testElem->asA(); + std::vector downstreamPorts; + if (testNode) + { + downstreamPorts = testNode->getDownstreamPorts(); + } + else + { + mx::NodeGraphPtr testGraph = testElem->asA(); + if (testGraph) + { + downstreamPorts = testGraph->getDownstreamPorts(); + } + } + // Test all downstream ports. If the port's node is renderable + // then stop searching. + for (mx::PortElementPtr downstreamPort : downstreamPorts) { - std::vector shaderOut = outNodes[0]->getOutputConnections(); - if (shaderOut.size() > 0) + mx::ElementPtr parent = downstreamPort->getParent(); + if (parent) { - if (shaderOut[0]) + mx::NodePtr downstreamNode = parent->asA(); + if (downstreamNode) { - if (shaderOut[0]->getNode()->getType() == "material") + mx::NodeDefPtr nodeDef = downstreamNode->getNodeDef(); + if (nodeDef) { - if (_currRenderNode != shaderOut[0]) + if (RENDERABLE_TYPES.count(nodeDef->getType())) { - _currRenderNode = shaderOut[0]; - _frameCount = ImGui::GetFrameCount(); - _renderer->setMaterialCompilation(true); + foundNode = downstreamNode; + break; } } } - } - else - { - _currRenderNode = nullptr; + if (!foundNode) + { + nextPaths.insert(parent->getNamePath()); + } } } - else if (outNodes[0]->getNode()->getType() == mx::MATERIAL_TYPE_STRING) + if (foundNode) + { + break; + } + } + + // Set up next set of nodes to search downstream + testPaths = nextPaths; + } + + // Update rendering. If found use that node, otherwise + // use the current fallback of using the first renderable node. + if (foundNode) + { + for (auto uiNode : _graphNodes) + { + if (uiNode->getNode() == foundNode) { - if (_currRenderNode != outNodes[0]) + if (_currRenderNode != uiNode) { - _currRenderNode = outNodes[0]; + _currRenderNode = uiNode; _frameCount = ImGui::GetFrameCount(); _renderer->setMaterialCompilation(true); } + break; } } - else - { - _currRenderNode = nullptr; - } } else { _currRenderNode = nullptr; + _frameCount = ImGui::GetFrameCount(); + _renderer->setMaterialCompilation(true); } } } @@ -768,38 +841,15 @@ void Graph::setRenderMaterial(UiNodePtr node) void Graph::updateMaterials(mx::InputPtr input, mx::ValuePtr value) { std::string renderablePath; - mx::TypedElementPtr renderableElem; - std::vector elems = mx::findRenderableElements(_graphDoc); - - size_t num = 0; - int num2 = 0; - for (mx::TypedElementPtr elem : elems) + if (_currRenderNode) { - renderableElem = elem; - mx::NodePtr node = elem->asA(); - if (node) + if (_currRenderNode->getNode()) { - if (_currRenderNode) - { - if (node->getName() == _currRenderNode->getName()) - { - renderablePath = renderableElem->getNamePath(); - break; - } - } - else - { - renderablePath = renderableElem->getNamePath(); - } + renderablePath = _currRenderNode->getNode()->getNamePath(); } - else + else if (_currRenderNode->getOutput()) { - renderablePath = renderableElem->getNamePath(); - if (num2 == 2) - { - break; - } - num2++; + renderablePath = _currRenderNode->getOutput()->getNamePath(); } } @@ -824,7 +874,7 @@ void Graph::updateMaterials(mx::InputPtr input, mx::ValuePtr value) // Note that if there is a topogical change due to // this value change or a transparency change, then // this is not currently caught here. - _renderer->getMaterials()[num]->modifyUniform(name, value); + _renderer->getMaterials()[0]->modifyUniform(name, value); } } } @@ -2737,7 +2787,7 @@ void Graph::deleteNode(UiNodePtr node) { mx::NodeDefPtr nodeDef = pin->_pinNode->getNode()->getNodeDef(pin->_pinNode->getNode()->getName()); val = nodeDef->getActiveInput(pin->_input->getName())->getValue(); - if (pin->_pinNode->getNode()->getType() == "surfaceshader") + if (pin->_pinNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING) { pin->_input->setConnectedOutput(nullptr); } @@ -3710,17 +3760,10 @@ void Graph::drawGraph(ImVec2 mousePos) _currUiNode = _graphNodes[graphPos]; // update render material if needed if (_currUiNode->getNode()) - { - if (_currUiNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING || _currUiNode->getNode()->getType() == mx::MATERIAL_TYPE_STRING) - { - setRenderMaterial(_currUiNode); - } - } - else if (_currUiNode->getNodeGraph()) { setRenderMaterial(_currUiNode); } - else if (_currUiNode->getOutput()) + else if (_currUiNode->getNodeGraph() || _currUiNode->getOutput()) { setRenderMaterial(_currUiNode); } diff --git a/source/MaterialXGraphEditor/RenderView.cpp b/source/MaterialXGraphEditor/RenderView.cpp index f36a492502..a88ca5cdaa 100644 --- a/source/MaterialXGraphEditor/RenderView.cpp +++ b/source/MaterialXGraphEditor/RenderView.cpp @@ -452,7 +452,7 @@ void RenderView::updateMaterials(mx::TypedElementPtr typedElem) if (typedElem) { mx::NodePtr node = typedElem->asA(); - materialNode = node && node->getType() == mx::MATERIAL_TYPE_STRING ? node : nullptr; + materialNode = node; if (udimSetValue && udimSetValue->isA()) { for (const std::string& udim : udimSetValue->asA()) diff --git a/source/MaterialXRender/External/OpenImageIO/FindOpenImageIO.cmake b/source/MaterialXRender/External/OpenImageIO/FindOpenImageIO.cmake index 4fab4f9814..6c1b1682d6 100644 --- a/source/MaterialXRender/External/OpenImageIO/FindOpenImageIO.cmake +++ b/source/MaterialXRender/External/OpenImageIO/FindOpenImageIO.cmake @@ -45,6 +45,13 @@ find_library ( OPENIMAGEIO_LIBRARY HINTS ${OPENIMAGEIO_ROOT_DIR}/lib PATH_SUFFIXES lib64 lib PATHS "${OPENIMAGEIO_ROOT_DIR}/lib" ) + +find_library ( OPENIMAGEIO_UTIL_LIBRARY + NAMES OpenImageIO_Util${OIIO_LIBNAME_SUFFIX} + HINTS ${OPENIMAGEIO_ROOT_DIR}/lib + PATH_SUFFIXES lib64 lib + PATHS "${OPENIMAGEIO_ROOT_DIR}/lib" ) + find_path ( OPENIMAGEIO_INCLUDE_DIR NAMES OpenImageIO/imageio.h HINTS ${OPENIMAGEIO_ROOT_DIR}/include @@ -66,7 +73,7 @@ if (EXISTS "${OIIO_VERSION_HEADER}") set (OPENIMAGEIO_VERSION "${OPENIMAGEIO_VERSION_MAJOR}.${OPENIMAGEIO_VERSION_MINOR}.${OPENIMAGEIO_VERSION_PATCH}") endif () -set ( OPENIMAGEIO_LIBRARIES ${OPENIMAGEIO_LIBRARY}) +set ( OPENIMAGEIO_LIBRARIES ${OPENIMAGEIO_LIBRARY} ${OPENIMAGEIO_UTIL_LIBRARY}) get_filename_component (OPENIMAGEIO_LIBRARY_DIRS "${OPENIMAGEIO_LIBRARY}" DIRECTORY CACHE) if (NOT OpenImageIO_FIND_QUIETLY) diff --git a/source/MaterialXRender/ShaderRenderer.cpp b/source/MaterialXRender/ShaderRenderer.cpp index a5e81d1f68..b621e5cd88 100644 --- a/source/MaterialXRender/ShaderRenderer.cpp +++ b/source/MaterialXRender/ShaderRenderer.cpp @@ -25,17 +25,26 @@ const float DEFAULT_FAR_PLANE = 100.0f; // ShaderRenderer methods // -ShaderRenderer::ShaderRenderer(unsigned int width, unsigned int height, Image::BaseType baseType) : +ShaderRenderer::ShaderRenderer(unsigned int width, unsigned int height, Image::BaseType baseType, MatrixConvention matrixConvention) : _width(width), _height(height), - _baseType(baseType) + _baseType(baseType), + _matrixConvention(matrixConvention) { // Initialize a default camera. float fH = std::tan(DEFAULT_FIELD_OF_VIEW / 360.0f * PI) * DEFAULT_NEAR_PLANE; float fW = fH * 1.0f; _camera = Camera::create(); _camera->setViewMatrix(Camera::createViewMatrix(DEFAULT_EYE_POSITION, DEFAULT_TARGET_POSITION, DEFAULT_UP_VECTOR)); - _camera->setProjectionMatrix(Camera::createPerspectiveMatrix(-fW, fW, -fH, fH, DEFAULT_NEAR_PLANE, DEFAULT_FAR_PLANE)); + + if (_matrixConvention == ShaderRenderer::MatrixConvention::Metal) + { + _camera->setProjectionMatrix(Camera::createPerspectiveMatrixZP(-fW, fW, -fH, fH, DEFAULT_NEAR_PLANE, DEFAULT_FAR_PLANE)); + } + else // MatrixConvention::OpenGL (default) + { + _camera->setProjectionMatrix(Camera::createPerspectiveMatrix(-fW, fW, -fH, fH, DEFAULT_NEAR_PLANE, DEFAULT_FAR_PLANE)); + } } void ShaderRenderer::createProgram(ShaderPtr) diff --git a/source/MaterialXRender/ShaderRenderer.h b/source/MaterialXRender/ShaderRenderer.h index 2c6f5fbf87..b049f81126 100644 --- a/source/MaterialXRender/ShaderRenderer.h +++ b/source/MaterialXRender/ShaderRenderer.h @@ -30,6 +30,12 @@ using ShaderRendererPtr = std::shared_ptr; class MX_RENDER_API ShaderRenderer { public: + /// Viewing API matrix conventions designation (default to OpenGL). + enum class MatrixConvention + { + OpenGL = 0, + Metal = 1 + }; /// A map with name and source code for each shader stage. using StageMap = StringMap; @@ -123,13 +129,16 @@ class MX_RENDER_API ShaderRenderer /// @} protected: - ShaderRenderer(unsigned int width, unsigned int height, Image::BaseType baseType); + ShaderRenderer(unsigned int width, unsigned int height, Image::BaseType baseType, + MatrixConvention matrixConvention = MatrixConvention::OpenGL); protected: unsigned int _width; unsigned int _height; Image::BaseType _baseType; + MatrixConvention _matrixConvention; + CameraPtr _camera; ImageHandlerPtr _imageHandler; GeometryHandlerPtr _geometryHandler; diff --git a/source/MaterialXRenderGlsl/GlslRenderer.cpp b/source/MaterialXRenderGlsl/GlslRenderer.cpp index e81ded996e..bf2c8cd849 100644 --- a/source/MaterialXRenderGlsl/GlslRenderer.cpp +++ b/source/MaterialXRenderGlsl/GlslRenderer.cpp @@ -25,7 +25,7 @@ GlslRendererPtr GlslRenderer::create(unsigned int width, unsigned int height, Im } GlslRenderer::GlslRenderer(unsigned int width, unsigned int height, Image::BaseType baseType) : - ShaderRenderer(width, height, baseType), + ShaderRenderer(width, height, baseType, MatrixConvention::OpenGL), _initialized(false), _screenColor(DEFAULT_SCREEN_COLOR_LIN_REC709) { diff --git a/source/MaterialXRenderMsl/MslRenderer.h b/source/MaterialXRenderMsl/MslRenderer.h index bf968078fe..1c54bdbc6a 100644 --- a/source/MaterialXRenderMsl/MslRenderer.h +++ b/source/MaterialXRenderMsl/MslRenderer.h @@ -126,9 +126,6 @@ class MX_RENDERMSL_API MslRenderer : public ShaderRenderer protected: MslRenderer(unsigned int width, unsigned int height, Image::BaseType baseType); - - virtual void updateViewInformation(); - virtual void updateWorldInformation(); void triggerProgrammaticCapture(); void stopProgrammaticCapture(); @@ -146,11 +143,6 @@ class MX_RENDERMSL_API MslRenderer : public ShaderRenderer bool _initialized; - const Vector3 _eye; - const Vector3 _center; - const Vector3 _up; - float _objectScale; - SimpleWindowPtr _window; Color3 _screenColor; }; diff --git a/source/MaterialXRenderMsl/MslRenderer.mm b/source/MaterialXRenderMsl/MslRenderer.mm index f6de188b2f..072f6f1a3a 100644 --- a/source/MaterialXRenderMsl/MslRenderer.mm +++ b/source/MaterialXRenderMsl/MslRenderer.mm @@ -14,13 +14,6 @@ MATERIALX_NAMESPACE_BEGIN -const float PI = std::acos(-1.0f); - -// View information -const float FOV_PERSP = 45.0f; // degrees -const float NEAR_PLANE_PERSP = 0.05f; -const float FAR_PLANE_PERSP = 100.0f; - // // MslRenderer methods // @@ -36,20 +29,14 @@ } MslRenderer::MslRenderer(unsigned int width, unsigned int height, Image::BaseType baseType) : - ShaderRenderer(width, height, baseType), + ShaderRenderer(width, height, baseType, MatrixConvention::Metal), _initialized(false), - _eye(0.0f, 0.0f, 3.0f), - _center(0.0f, 0.0f, 0.0f), - _up(0.0f, 1.0f, 0.0f), - _objectScale(1.0f), _screenColor(DEFAULT_SCREEN_COLOR_LIN_REC709) { _program = MslProgram::create(); _geometryHandler = GeometryHandler::create(); _geometryHandler->addLoader(TinyObjLoader::create()); - - _camera = Camera::create(); } void MslRenderer::initialize(RenderContextHandle) @@ -160,20 +147,6 @@ } -void MslRenderer::updateViewInformation() -{ - float fH = std::tan(FOV_PERSP / 360.0f * PI) * NEAR_PLANE_PERSP; - float fW = fH * 1.0f; - - _camera->setViewMatrix(Camera::createViewMatrix(_eye, _center, _up)); - _camera->setProjectionMatrix(Camera::createPerspectiveMatrixZP(-fW, fW, -fH, fH, NEAR_PLANE_PERSP, FAR_PLANE_PERSP)); -} - -void MslRenderer::updateWorldInformation() -{ - _camera->setWorldMatrix(Matrix44::createScale(Vector3(_objectScale))); -} - void MslRenderer::triggerProgrammaticCapture() { MTLCaptureManager* captureManager = [MTLCaptureManager sharedCaptureManager]; @@ -217,9 +190,6 @@ [renderCmdEncoder setCullMode:MTLCullModeBack]; - - updateViewInformation(); - updateWorldInformation(); try { diff --git a/source/PyMaterialX/PyMaterialXCore/PyNode.cpp b/source/PyMaterialX/PyMaterialXCore/PyNode.cpp index 704cda7589..55448dee80 100644 --- a/source/PyMaterialX/PyMaterialXCore/PyNode.cpp +++ b/source/PyMaterialX/PyMaterialXCore/PyNode.cpp @@ -59,6 +59,7 @@ void bindPyNode(py::module& mod) .def("addInterfaceName", &mx::NodeGraph::addInterfaceName) .def("removeInterfaceName", &mx::NodeGraph::removeInterfaceName) .def("modifyInterfaceName", &mx::NodeGraph::modifyInterfaceName) + .def("getDownstreamPorts", &mx::NodeGraph::getDownstreamPorts) .def_readonly_static("CATEGORY", &mx::NodeGraph::CATEGORY); py::class_(mod, "Backdrop")