Files
UnrealEngine/Engine/Source/ThirdParty/MaterialX/MaterialX-1.39.3/source/MaterialXGraphEditor/Graph.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

4478 lines
159 KiB
C++

//
// Copyright Contributors to the MaterialX Project
// SPDX-License-Identifier: Apache-2.0
//
#include <MaterialXGraphEditor/Graph.h>
#include <MaterialXRenderGlsl/External/Glad/glad.h>
#include <MaterialXFormat/Util.h>
#include <imgui_stdlib.h>
#include <imgui_node_editor_internal.h>
#include <widgets.h>
#include <iostream>
namespace
{
// Based on the dimensions of the dot_color3 node, computed by calling ed::getNodeSize
const ImVec2 DEFAULT_NODE_SIZE = ImVec2(138, 116);
const int DEFAULT_ALPHA = 255;
const int FILTER_ALPHA = 50;
const std::array<std::string, 22> NODE_GROUP_ORDER =
{
"texture2d",
"texture3d",
"procedural",
"procedural2d",
"procedural3d",
"geometric",
"translation",
"convolution2d",
"math",
"adjustment",
"compositing",
"conditional",
"channel",
"organization",
"global",
"application",
"material",
"shader",
"pbr",
"light",
"colortransform",
"none"
};
// Based on ImRect_Expanded function in ImGui Node Editor blueprints-example.cpp
ImRect expandImRect(const ImRect& rect, float x, float y)
{
ImRect result = rect;
result.Min.x -= x;
result.Min.y -= y;
result.Max.x += x;
result.Max.y += y;
return result;
}
// Based on the splitter function in the ImGui Node Editor blueprints-example.cpp
static bool splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f)
{
using namespace ImGui;
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiID id = window->GetID("##Splitter");
ImRect bb;
bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1));
bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f);
return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 0.0f);
}
// Based on showLabel from ImGui Node Editor blueprints-example.cpp
auto showLabel = [](const char* label, ImColor color)
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetTextLineHeight());
auto size = ImGui::CalcTextSize(label);
auto padding = ImGui::GetStyle().FramePadding;
auto spacing = ImGui::GetStyle().ItemSpacing;
ImGui::SetCursorPos(ImGui::GetCursorPos() + ImVec2(spacing.x, -spacing.y));
auto rectMin = ImGui::GetCursorScreenPos() - padding;
auto rectMax = ImGui::GetCursorScreenPos() + size + padding;
auto drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(rectMin, rectMax, color, size.y * 0.15f);
ImGui::TextUnformatted(label);
};
// Create a more user-friendly node definition name
std::string getUserNodeDefName(const std::string& val)
{
const std::string ND_PREFIX = "ND_";
std::string result = val;
if (mx::stringStartsWith(val, ND_PREFIX))
{
result = val.substr(3, val.length());
}
return result;
}
} // anonymous namespace
//
// Link methods
//
Link::Link() :
_startAttr(-1),
_endAttr(-1)
{
static int nextId = 1;
_id = nextId++;
}
//
// Graph methods
//
Graph::Graph(const std::string& materialFilename,
const std::string& meshFilename,
const mx::FileSearchPath& searchPath,
const mx::FilePathVec& libraryFolders,
int viewWidth,
int viewHeight) :
_materialFilename(materialFilename),
_searchPath(searchPath),
_libraryFolders(libraryFolders),
_initial(false),
_delete(false),
_fileDialogSave(FileDialog::EnterNewFilename),
_isNodeGraph(false),
_graphTotalSize(0),
_popup(false),
_shaderPopup(false),
_searchNodeId(-1),
_addNewNode(false),
_ctrlClick(false),
_isCut(false),
_autoLayout(false),
_frameCount(INT_MIN),
_fontScale(1.0f),
_saveNodePositions(true)
{
loadStandardLibraries();
setPinColor();
// Set up filters load and save
_mtlxFilter.push_back(".mtlx");
_geomFilter.push_back(".obj");
_geomFilter.push_back(".glb");
_geomFilter.push_back(".gltf");
_graphDoc = loadDocument(materialFilename);
_initial = true;
createNodeUIList(_stdLib);
if (_graphDoc)
{
buildUiBaseGraph(_graphDoc);
_currGraphElem = _graphDoc;
_prevUiNode = nullptr;
}
// Create a renderer using the initial startup document.
mx::FilePath captureFilename = "resources/Materials/Examples/example.png";
std::string envRadianceFilename = "resources/Lights/san_giuseppe_bridge_split.hdr";
_renderer = std::make_shared<RenderView>(_graphDoc, _stdLib, meshFilename, envRadianceFilename,
_searchPath, viewWidth, viewHeight);
_renderer->initialize();
for (const std::string& ext : _renderer->getImageHandler()->supportedExtensions())
{
_imageFilter.push_back("." + ext);
}
_renderer->updateMaterials(nullptr);
for (const std::string& incl : _renderer->getXincludeFiles())
{
_xincludeFiles.insert(incl);
}
}
mx::ElementPredicate Graph::getElementPredicate() const
{
return [this](mx::ConstElementPtr elem)
{
if (elem->hasSourceUri())
{
return (_xincludeFiles.count(elem->getSourceUri()) == 0);
}
return true;
};
}
void Graph::loadStandardLibraries()
{
// Initialize the standard library.
try
{
_stdLib = mx::createDocument();
_xincludeFiles = mx::loadLibraries(_libraryFolders, _searchPath, _stdLib);
if (_xincludeFiles.empty())
{
std::cerr << "Could not find standard data libraries on the given search path: " << _searchPath.asString() << std::endl;
}
}
catch (std::exception& e)
{
std::cerr << "Failed to load standard data libraries: " << e.what() << std::endl;
return;
}
}
mx::DocumentPtr Graph::loadDocument(const mx::FilePath& filename)
{
mx::FilePathVec libraryFolders = { "libraries" };
_libraryFolders = libraryFolders;
mx::XmlReadOptions readOptions;
readOptions.readXIncludeFunction = [](mx::DocumentPtr doc, const mx::FilePath& filename,
const mx::FileSearchPath& searchPath, const mx::XmlReadOptions* options)
{
mx::FilePath resolvedFilename = searchPath.find(filename);
if (resolvedFilename.exists())
{
try
{
readFromXmlFile(doc, resolvedFilename, searchPath, options);
}
catch (mx::Exception& e)
{
std::cerr << "Failed to read include file: " << filename.asString() << ". " <<
std::string(e.what()) << std::endl;
}
}
else
{
std::cerr << "Include file not found: " << filename.asString() << std::endl;
}
};
mx::DocumentPtr doc = mx::createDocument();
try
{
if (!filename.isEmpty())
{
mx::readFromXmlFile(doc, filename, _searchPath, &readOptions);
doc->setDataLibrary(_stdLib);
std::string message;
if (!doc->validate(&message))
{
std::cerr << "*** Validation warnings for " << filename.asString() << " ***" << std::endl;
std::cerr << message << std::endl;
}
// Cache the currently loaded file
_materialFilename = filename;
}
}
catch (mx::Exception& e)
{
std::cerr << "Failed to read file: " << filename.asString() << ": \"" <<
std::string(e.what()) << "\"" << std::endl;
}
_graphStack = std::stack<std::vector<UiNodePtr>>();
_pinStack = std::stack<std::vector<UiPinPtr>>();
return doc;
}
void Graph::addExtraNodes()
{
if (!_graphDoc)
{
return;
}
// Get all types from the doc
std::vector<std::string> types;
std::vector<mx::TypeDefPtr> typeDefs = _graphDoc->getTypeDefs();
types.reserve(typeDefs.size());
for (auto typeDef : typeDefs)
{
types.push_back(typeDef->getName());
}
// Add input and output nodes for all types
for (const std::string& type : types)
{
std::string nodeName = "ND_input_" + type;
_nodesToAdd.emplace_back(nodeName, type, "input", "Input Nodes");
nodeName = "ND_output_" + type;
_nodesToAdd.emplace_back(nodeName, type, "output", "Output Nodes");
}
// Add group node
_nodesToAdd.emplace_back("ND_group", "", "group", "Group Nodes");
// Add nodegraph node
_nodesToAdd.emplace_back("ND_nodegraph", "", "nodegraph", "Node Graph");
}
ed::PinId Graph::getOutputPin(UiNodePtr node, UiNodePtr upNode, UiPinPtr input)
{
if (upNode->getNodeGraph() != nullptr)
{
// For nodegraph need to get the correct output pin according to the names of the output nodes
mx::OutputPtr output;
if (input->_pinNode->getNode())
{
output = input->_pinNode->getNode()->getConnectedOutput(input->_name);
}
else if (input->_pinNode->getNodeGraph())
{
output = input->_pinNode->getNodeGraph()->getConnectedOutput(input->_name);
}
if (output)
{
std::string outName = output->getName();
for (UiPinPtr outputs : upNode->outputPins)
{
if (outputs->_name == outName)
{
return outputs->_pinId;
}
}
}
return ed::PinId();
}
else
{
// For node need to get the correct output pin based on the output attribute
if (!upNode->outputPins.empty())
{
std::string outputName = mx::EMPTY_STRING;
if (input->_input)
{
outputName = input->_input->getOutputString();
}
else if (input->_output)
{
outputName = input->_output->getOutputString();
}
size_t pinIndex = 0;
if (!outputName.empty())
{
for (size_t i = 0; i < upNode->outputPins.size(); i++)
{
if (upNode->outputPins[i]->_name == outputName)
{
pinIndex = i;
break;
}
}
}
return (upNode->outputPins[pinIndex]->_pinId);
}
return ed::PinId();
}
}
void Graph::linkGraph()
{
_currLinks.clear();
// Start with bottom of graph
for (UiNodePtr node : _graphNodes)
{
std::vector<UiPinPtr> inputs = node->inputPins;
if (node->getInput() == nullptr)
{
for (size_t i = 0; i < inputs.size(); i++)
{
// Get upstream node for all inputs
std::string inputName = inputs[i]->_name;
UiNodePtr inputNode = node->getConnectedNode(inputName);
if (inputNode != nullptr)
{
Link link;
// Get the input connections for the current UiNode
ax::NodeEditor::PinId id = inputs[i]->_pinId;
inputs[i]->setConnected(true);
int end = int(id.Get());
link._endAttr = end;
// Get id number of output of node
ed::PinId outputId = getOutputPin(node, inputNode, inputs[i]);
int start = int(outputId.Get());
if (start >= 0)
{
// Connect the correct output pin to this input
for (UiPinPtr outPin : inputNode->outputPins)
{
if (outPin->_pinId == outputId)
{
outPin->setConnected(true);
outPin->addConnection(inputs[i]);
}
}
link._startAttr = start;
if (!linkExists(link))
{
_currLinks.push_back(link);
}
}
}
else if (inputs[i]->_input)
{
if (inputs[i]->_input->getInterfaceInput())
{
inputs[i]->setConnected(true);
}
}
else
{
inputs[i]->setConnected(false);
}
}
}
}
}
void Graph::connectLinks()
{
for (Link const& link : _currLinks)
{
ed::Link(link._id, link._startAttr, link._endAttr);
}
}
int Graph::findLinkPosition(int id)
{
int count = 0;
for (size_t i = 0; i < _currLinks.size(); i++)
{
if (_currLinks[i]._id == id)
{
return count;
}
count++;
}
return -1;
}
bool Graph::checkPosition(UiNodePtr node)
{
mx::ElementPtr elem = node->getElement();
return elem && !elem->getAttribute(mx::Element::XPOS_ATTRIBUTE).empty();
}
// Calculate the total vertical space the node level takes up
float Graph::totalHeight(int level)
{
float total = 0.f;
for (UiNodePtr node : _levelMap[level])
{
total += ed::GetNodeSize(node->getId()).y;
}
return total;
}
// Set the y-position of node based on the starting position and the nodes above it
void Graph::setYSpacing(int level, float startingPos)
{
// set the y spacing for each node
float currPos = startingPos;
for (UiNodePtr node : _levelMap[level])
{
ImVec2 oldPos = ed::GetNodePosition(node->getId());
ed::SetNodePosition(node->getId(), ImVec2(oldPos.x, currPos));
currPos += ed::GetNodeSize(node->getId()).y + 40;
}
}
// Calculate the average y-position for a specific node level
float Graph::findAvgY(const std::vector<UiNodePtr>& nodes)
{
// find the mid point of node level grou[
float total = 0.f;
int count = 0;
for (UiNodePtr node : nodes)
{
ImVec2 pos = ed::GetNodePosition(node->getId());
ImVec2 size = ed::GetNodeSize(node->getId());
total += ((size.y + pos.y) + pos.y) / 2;
count++;
}
return (total / count);
}
void Graph::findYSpacing(float startY)
{
// Assume level 0 is set
// For each level find the average y position of the previous level to use as a spacing guide
int i = 0;
for (std::pair<int, std::vector<UiNodePtr>> levelChunk : _levelMap)
{
if (_levelMap[i].size() > 0)
{
if (_levelMap[i][0]->_level > 0)
{
int prevLevel = _levelMap[i].front()->_level - 1;
float avgY = findAvgY(_levelMap[prevLevel]);
float height = totalHeight(_levelMap[i].front()->_level);
// calculate the starting position to be above the previous level's center so that it is evenly spaced on either side of the center
float startingPos = avgY - ((height + (_levelMap[i].size() * 20)) / 2) + startY;
setYSpacing(_levelMap[i].front()->_level, startingPos);
}
else
{
setYSpacing(_levelMap[i].front()->_level, startY);
}
}
++i;
}
}
ImVec2 Graph::layoutPosition(UiNodePtr layoutNode, ImVec2 startingPos, bool initialLayout, int level)
{
if (checkPosition(layoutNode) && !_autoLayout)
{
for (UiNodePtr node : _graphNodes)
{
// Since nodegraph nodes do not have MaterialX info they are placed based on their connected node
if (node->getNodeGraph() != nullptr)
{
std::vector<UiNodePtr> outputCon = node->getOutputConnections();
if (outputCon.size() > 0)
{
ImVec2 outputPos = ed::GetNodePosition(outputCon[0]->getId());
ed::SetNodePosition(node->getId(), ImVec2(outputPos.x - 400, outputPos.y));
node->setPos(ImVec2(outputPos.x - 400, outputPos.y));
}
}
else
{
// Don't set position of group nodes
if (node->getMessage().empty())
{
mx::ElementPtr elem = node->getElement();
if (elem && elem->hasAttribute(mx::Element::XPOS_ATTRIBUTE))
{
float x = std::stof(elem->getAttribute(mx::Element::XPOS_ATTRIBUTE));
if (elem->hasAttribute(mx::Element::YPOS_ATTRIBUTE))
{
float y = std::stof(elem->getAttribute(mx::Element::YPOS_ATTRIBUTE));
x *= DEFAULT_NODE_SIZE.x;
y *= DEFAULT_NODE_SIZE.y;
ed::SetNodePosition(node->getId(), ImVec2(x, y));
node->setPos(ImVec2(x, y));
}
}
}
}
}
return ImVec2(0.f, 0.f);
}
else
{
ImVec2 currPos = startingPos;
ImVec2 newPos = currPos;
if (layoutNode->_level != -1)
{
if (layoutNode->_level < level)
{
// Remove the old instance of the node from the map
int levelNum = 0;
int removeNum = -1;
for (UiNodePtr levelNode : _levelMap[layoutNode->_level])
{
if (levelNode->getName() == layoutNode->getName())
{
removeNum = levelNum;
}
levelNum++;
}
if (removeNum > -1)
{
_levelMap[layoutNode->_level].erase(_levelMap[layoutNode->_level].begin() + removeNum);
}
layoutNode->_level = level;
}
}
else
{
layoutNode->_level = level;
}
auto it = _levelMap.find(layoutNode->_level);
if (it != _levelMap.end())
{
// Key already exists so add to it
bool nodeFound = false;
for (UiNodePtr node : it->second)
{
if (node && node->getName() == layoutNode->getName())
{
nodeFound = true;
break;
}
}
if (!nodeFound)
{
_levelMap[layoutNode->_level].push_back(layoutNode);
}
}
else
{
// Insert new vector into key
std::vector<UiNodePtr> newValue = { layoutNode };
_levelMap.insert({ layoutNode->_level, newValue });
}
std::vector<UiPinPtr> pins = layoutNode->inputPins;
if (initialLayout)
{
// Check number of inputs that are connected to node
if (layoutNode->getInputConnect() > 0)
{
// Not top of node graph so stop recursion
if (pins.size() != 0 && layoutNode->getInput() == nullptr)
{
for (size_t i = 0; i < pins.size(); i++)
{
// Get upstream node for all inputs
newPos = startingPos;
UiNodePtr nextNode = layoutNode->getConnectedNode(pins[i]->_name);
if (nextNode)
{
startingPos.x = (1200.f - ((layoutNode->_level) * 250)) * _fontScale;
ed::SetNodePosition(layoutNode->getId(), startingPos);
layoutNode->setPos(ImVec2(startingPos));
// Call layout position on upstream node with newPos to the left of current node
layoutPosition(nextNode, ImVec2(newPos.x, startingPos.y), initialLayout, layoutNode->_level + 1);
}
}
}
}
else
{
startingPos.x = (1200.f - ((layoutNode->_level) * 250)) * _fontScale;
layoutNode->setPos(ImVec2(startingPos));
// Set current node position
ed::SetNodePosition(layoutNode->getId(), ImVec2(startingPos));
}
}
return ImVec2(0.f, 0.f);
}
}
void Graph::layoutInputs()
{
// Layout inputs after other nodes so that they can be all in a line on far left side of node graph
if (_levelMap.begin() != _levelMap.end())
{
int levelCount = -1;
for (std::pair<int, std::vector<UiNodePtr>> nodes : _levelMap)
{
++levelCount;
}
ImVec2 startingPos = ed::GetNodePosition(_levelMap[levelCount].back()->getId());
startingPos.y += ed::GetNodeSize(_levelMap[levelCount].back()->getId()).y + 20;
for (UiNodePtr uiNode : _graphNodes)
{
if (uiNode->getOutputConnections().size() == 0 && (uiNode->getInput() != nullptr))
{
ed::SetNodePosition(uiNode->getId(), ImVec2(startingPos));
startingPos.y += ed::GetNodeSize(uiNode->getId()).y;
startingPos.y += 23;
}
else if (uiNode->getOutputConnections().size() == 0 && (uiNode->getNode() != nullptr))
{
if (uiNode->getNode()->getCategory() != mx::SURFACE_MATERIAL_NODE_STRING)
{
layoutPosition(uiNode, ImVec2(1200, 750), _initial, 0);
}
}
}
}
}
void Graph::setPinColor()
{
_pinColor.emplace("integer", ImColor(255, 255, 28, 255));
_pinColor.emplace("boolean", ImColor(255, 0, 255, 255));
_pinColor.emplace("float", ImColor(50, 100, 255, 255));
_pinColor.emplace("color3", ImColor(178, 34, 34, 255));
_pinColor.emplace("color4", ImColor(50, 10, 255, 255));
_pinColor.emplace("vector2", ImColor(100, 255, 100, 255));
_pinColor.emplace("vector3", ImColor(0, 255, 0, 255));
_pinColor.emplace("vector4", ImColor(100, 0, 100, 255));
_pinColor.emplace("matrix33", ImColor(0, 100, 100, 255));
_pinColor.emplace("matrix44", ImColor(50, 255, 100, 255));
_pinColor.emplace("filename", ImColor(255, 184, 28, 255));
_pinColor.emplace("string", ImColor(100, 100, 50, 255));
_pinColor.emplace("geomname", ImColor(121, 60, 180, 255));
_pinColor.emplace("BSDF", ImColor(10, 181, 150, 255));
_pinColor.emplace("EDF", ImColor(255, 50, 100, 255));
_pinColor.emplace("VDF", ImColor(0, 100, 151, 255));
_pinColor.emplace(mx::SURFACE_SHADER_TYPE_STRING, ImColor(150, 255, 255, 255));
_pinColor.emplace(mx::MATERIAL_TYPE_STRING, ImColor(255, 255, 255, 255));
_pinColor.emplace(mx::DISPLACEMENT_SHADER_TYPE_STRING, ImColor(155, 50, 100, 255));
_pinColor.emplace(mx::VOLUME_SHADER_TYPE_STRING, ImColor(155, 250, 100, 255));
_pinColor.emplace(mx::LIGHT_SHADER_TYPE_STRING, ImColor(100, 150, 100, 255));
_pinColor.emplace("none", ImColor(140, 70, 70, 255));
_pinColor.emplace(mx::MULTI_OUTPUT_TYPE_STRING, ImColor(70, 70, 70, 255));
_pinColor.emplace("integerarray", ImColor(200, 10, 100, 255));
_pinColor.emplace("floatarray", ImColor(25, 250, 100));
_pinColor.emplace("color3array", ImColor(25, 200, 110));
_pinColor.emplace("color4array", ImColor(50, 240, 110));
_pinColor.emplace("vector2array", ImColor(50, 200, 75));
_pinColor.emplace("vector3array", ImColor(20, 200, 100));
_pinColor.emplace("vector4array", ImColor(100, 200, 100));
_pinColor.emplace("geomnamearray", ImColor(150, 200, 100));
_pinColor.emplace("stringarray", ImColor(120, 180, 100));
}
void Graph::setRenderMaterial(UiNodePtr node)
{
// For now only 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)
{
_currRenderNode = node;
_frameCount = ImGui::GetFrameCount();
_renderer->setMaterialCompilation(true);
}
}
// Traverse downstream looking for the first renderable element.
else
{
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<mx::NodeGraph>())
mtlxNodeGraph = parent->asA<mx::NodeGraph>();
else if (parent->isA<mx::Node>())
mtlxNode = parent->asA<mx::Node>();
}
mx::StringSet testPaths;
if (mtlxNode)
{
mx::ElementPtr parent = mtlxNode->getParent();
if (parent->isA<mx::NodeGraph>())
{
// 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<mx::NodeGraph>();
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<mx::Node>();
std::vector<mx::PortElementPtr> downstreamPorts;
if (testNode)
{
downstreamPorts = testNode->getDownstreamPorts();
}
else
{
mx::NodeGraphPtr testGraph = testElem->asA<mx::NodeGraph>();
if (testGraph)
{
downstreamPorts = testGraph->getDownstreamPorts();
}
}
// Test all downstream ports. If the port's node is renderable
// then stop searching.
for (mx::PortElementPtr downstreamPort : downstreamPorts)
{
mx::ElementPtr parent = downstreamPort->getParent();
if (parent)
{
mx::NodePtr downstreamNode = parent->asA<mx::Node>();
if (downstreamNode)
{
mx::NodeDefPtr nodeDef = downstreamNode->getNodeDef();
if (nodeDef)
{
if (RENDERABLE_TYPES.count(nodeDef->getType()))
{
foundNode = downstreamNode;
break;
}
}
}
if (!foundNode)
{
nextPaths.insert(parent->getNamePath());
}
}
}
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 != uiNode)
{
_currRenderNode = uiNode;
_frameCount = ImGui::GetFrameCount();
_renderer->setMaterialCompilation(true);
}
break;
}
}
}
else
{
_currRenderNode = nullptr;
_frameCount = ImGui::GetFrameCount();
_renderer->setMaterialCompilation(true);
}
}
}
void Graph::updateMaterials(mx::InputPtr input /* = nullptr */, mx::ValuePtr value /* = nullptr */)
{
std::string renderablePath;
if (_currRenderNode)
{
if (_currRenderNode->getNode())
{
renderablePath = _currRenderNode->getNode()->getNamePath();
}
else if (_currRenderNode->getOutput())
{
renderablePath = _currRenderNode->getOutput()->getNamePath();
}
}
if (renderablePath.empty())
{
_renderer->updateMaterials(nullptr);
}
else
{
if (!input)
{
mx::ElementPtr elem = nullptr;
{
elem = _graphDoc->getDescendant(renderablePath);
}
mx::TypedElementPtr typedElem = elem ? elem->asA<mx::TypedElement>() : nullptr;
_renderer->updateMaterials(typedElem);
}
else
{
std::string name = input->getNamePath();
// 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()[0]->modifyUniform(name, value);
}
}
}
void Graph::setConstant(UiNodePtr node, mx::InputPtr& input, const mx::UIProperties& uiProperties)
{
ImGui::PushItemWidth(-1);
mx::ValuePtr minVal = uiProperties.uiMin;
mx::ValuePtr maxVal = uiProperties.uiMax;
// If input is a float set the float slider UI to the value
if (input->getType() == "float")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<float>())
{
// Update the value to the default for new nodes
float prev, temp;
prev = temp = val->asA<float>();
float min = minVal ? minVal->asA<float>() : 0.f;
float max = maxVal ? maxVal->asA<float>() : 100.f;
float speed = (max - min) / 1000.0f;
ImGui::DragFloat("##hidelabel", &temp, speed, min, max);
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials(input, input->getValue());
}
}
}
else if (input->getType() == "integer")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<int>())
{
int prev, temp;
prev = temp = val->asA<int>();
int min = minVal ? minVal->asA<int>() : 0;
int max = maxVal ? maxVal->asA<int>() : 100;
float speed = (max - min) / 100.0f;
ImGui::DragInt("##hidelabel", &temp, speed, min, max);
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials(input, input->getValue());
}
}
}
else if (input->getType() == "color3")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<mx::Color3>())
{
mx::Color3 prev, temp;
prev = temp = val->asA<mx::Color3>();
float min = minVal ? minVal->asA<mx::Color3>()[0] : 0.f;
float max = maxVal ? maxVal->asA<mx::Color3>()[0] : 100.f;
float speed = (max - min) / 1000.0f;
ImGui::PushItemWidth(-100);
ImGui::DragFloat3("##hidelabel", &temp[0], speed, min, max);
ImGui::PopItemWidth();
ImGui::SameLine();
ImGui::ColorEdit3("##color", &temp[0], ImGuiColorEditFlags_NoInputs);
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials(input, input->getValue());
}
}
}
else if (input->getType() == "color4")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<mx::Color4>())
{
mx::Color4 prev, temp;
prev = temp = val->asA<mx::Color4>();
float min = minVal ? minVal->asA<mx::Color4>()[0] : 0.f;
float max = maxVal ? maxVal->asA<mx::Color4>()[0] : 100.f;
float speed = (max - min) / 1000.0f;
ImGui::PushItemWidth(-100);
ImGui::DragFloat4("##hidelabel", &temp[0], speed, min, max);
ImGui::PopItemWidth();
ImGui::SameLine();
// Color edit for the color picker to the right of the color floats
ImGui::ColorEdit4("##color", &temp[0], ImGuiColorEditFlags_NoInputs);
// Set input value and update materials if different from previous value
if (temp != prev)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials(input, input->getValue());
}
}
}
else if (input->getType() == "vector2")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<mx::Vector2>())
{
mx::Vector2 prev, temp;
prev = temp = val->asA<mx::Vector2>();
float min = minVal ? minVal->asA<mx::Vector2>()[0] : 0.f;
float max = maxVal ? maxVal->asA<mx::Vector2>()[0] : 100.f;
float speed = (max - min) / 1000.0f;
ImGui::DragFloat2("##hidelabel", &temp[0], speed, min, max);
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials(input, input->getValue());
}
}
}
else if (input->getType() == "vector3")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<mx::Vector3>())
{
mx::Vector3 prev, temp;
prev = temp = val->asA<mx::Vector3>();
float min = minVal ? minVal->asA<mx::Vector3>()[0] : 0.f;
float max = maxVal ? maxVal->asA<mx::Vector3>()[0] : 100.f;
float speed = (max - min) / 1000.0f;
ImGui::DragFloat3("##hidelabel", &temp[0], speed, min, max);
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials(input, input->getValue());
}
}
}
else if (input->getType() == "vector4")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<mx::Vector4>())
{
mx::Vector4 prev, temp;
prev = temp = val->asA<mx::Vector4>();
float min = minVal ? minVal->asA<mx::Vector4>()[0] : 0.f;
float max = maxVal ? maxVal->asA<mx::Vector4>()[0] : 100.f;
float speed = (max - min) / 1000.0f;
ImGui::DragFloat4("##hidelabel", &temp[0], speed, min, max);
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials(input, input->getValue());
}
}
}
else if (input->getType() == "string")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<std::string>())
{
std::string prev, temp;
prev = temp = val->asA<std::string>();
ImGui::InputText("##constant", &temp);
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials();
}
}
}
else if (input->getType() == "filename")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<std::string>())
{
std::string prev, temp;
prev = temp = val->asA<std::string>();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.15f, .15f, .15f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.2f, .4f, .6f, 1.0f));
// Browser button to select new file
ImGui::PushItemWidth(-100);
if (ImGui::Button("Browse"))
{
_fileDialogImageInputName = input->getName();
_fileDialogImage.setTitle("Node Input Dialog");
_fileDialogImage.open();
_fileDialogImage.setTypeFilters(_imageFilter);
}
ImGui::PopItemWidth();
ImGui::SameLine();
ImGui::Text("%s", mx::FilePath(temp).getBaseName().c_str());
ImGui::PopStyleColor();
ImGui::PopStyleColor();
// Create and load document from selected file
if (_fileDialogImage.hasSelected() && _fileDialogImageInputName == input->getName())
{
// Set the new filename to the complete file path
mx::FilePath fileName = _fileDialogImage.getSelected();
temp = fileName;
// Need to clear the file prefix so that it can find the new file
input->setFilePrefix(mx::EMPTY_STRING);
_fileDialogImage.clearSelected();
_fileDialogImage.setTypeFilters(std::vector<std::string>());
_fileDialogImageInputName = "";
}
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValueString(temp);
input->setValue(temp, input->getType());
updateMaterials();
}
}
}
else if (input->getType() == "boolean")
{
mx::ValuePtr val = input->getValue();
if (val && val->isA<bool>())
{
bool prev, temp;
prev = temp = val->asA<bool>();
ImGui::Checkbox("", &temp);
// Set input value and update materials if different from previous value
if (prev != temp)
{
addNodeInput(_currUiNode, input);
input->setValue(temp, input->getType());
updateMaterials(input, input->getValue());
}
}
}
ImGui::PopItemWidth();
}
void Graph::setUiNodeInfo(UiNodePtr node, const std::string& type, const std::string& category)
{
node->setType(type);
node->setCategory(category);
++_graphTotalSize;
// Create pins
if (node->getNodeGraph())
{
std::vector<mx::OutputPtr> outputs = node->getNodeGraph()->getOutputs();
for (mx::OutputPtr out : outputs)
{
UiPinPtr outPin = std::make_shared<UiPin>(_graphTotalSize, &*out->getName().begin(), out->getType(), node, ax::NodeEditor::PinKind::Output, nullptr, nullptr);
++_graphTotalSize;
node->outputPins.push_back(outPin);
_currPins.push_back(outPin);
}
for (mx::InputPtr input : node->getNodeGraph()->getInputs())
{
UiPinPtr inPin = std::make_shared<UiPin>(_graphTotalSize, &*input->getName().begin(), input->getType(), node, ax::NodeEditor::PinKind::Input, input, nullptr);
node->inputPins.push_back(inPin);
_currPins.push_back(inPin);
++_graphTotalSize;
}
}
else
{
if (node->getNode())
{
mx::NodeDefPtr nodeDef = node->getNode()->getNodeDef(node->getNode()->getName());
if (nodeDef)
{
for (mx::InputPtr input : nodeDef->getActiveInputs())
{
if (node->getNode()->getInput(input->getName()))
{
input = node->getNode()->getInput(input->getName());
}
UiPinPtr inPin = std::make_shared<UiPin>(_graphTotalSize, &*input->getName().begin(), input->getType(), node, ax::NodeEditor::PinKind::Input, input, nullptr);
node->inputPins.push_back(inPin);
_currPins.push_back(inPin);
++_graphTotalSize;
}
for (mx::OutputPtr output : nodeDef->getActiveOutputs())
{
if (node->getNode()->getOutput(output->getName()))
{
output = node->getNode()->getOutput(output->getName());
}
UiPinPtr outPin = std::make_shared<UiPin>(_graphTotalSize, &*output->getName().begin(), output->getType(),
node, ax::NodeEditor::PinKind::Output, nullptr, nullptr);
node->outputPins.push_back(outPin);
_currPins.push_back(outPin);
++_graphTotalSize;
}
}
}
else if (node->getInput())
{
UiPinPtr inPin = std::make_shared<UiPin>(_graphTotalSize, &*("Value"), node->getInput()->getType(), node, ax::NodeEditor::PinKind::Input, node->getInput(), nullptr);
node->inputPins.push_back(inPin);
_currPins.push_back(inPin);
++_graphTotalSize;
}
else if (node->getOutput())
{
UiPinPtr inPin = std::make_shared<UiPin>(_graphTotalSize, &*("input"), node->getOutput()->getType(), node, ax::NodeEditor::PinKind::Input, nullptr, node->getOutput());
node->inputPins.push_back(inPin);
_currPins.push_back(inPin);
++_graphTotalSize;
}
if (node->getInput() || node->getOutput())
{
UiPinPtr outPin = std::make_shared<UiPin>(_graphTotalSize, &*("output"), type, node, ax::NodeEditor::PinKind::Output, nullptr, nullptr);
++_graphTotalSize;
node->outputPins.push_back(outPin);
_currPins.push_back(outPin);
}
}
_graphNodes.push_back(std::move(node));
}
void Graph::createNodeUIList(mx::DocumentPtr doc)
{
_nodesToAdd.clear();
auto nodeDefs = doc->getNodeDefs();
std::unordered_map<std::string, std::vector<mx::NodeDefPtr>> groupToNodeDef;
std::vector<std::string> groupList = std::vector(NODE_GROUP_ORDER.begin(), NODE_GROUP_ORDER.end());
for (const auto& nodeDef : nodeDefs)
{
std::string group = nodeDef->getNodeGroup();
if (group.empty())
{
group = NODE_GROUP_ORDER.back();
}
// If the group is not in the groupList already (seeded by NODE_GROUP_ORDER) then add it.
if (std::find(groupList.begin(), groupList.end(), group) == groupList.end())
{
groupList.emplace_back(group);
}
if (groupToNodeDef.find(group) == groupToNodeDef.end())
{
groupToNodeDef[group] = std::vector<mx::NodeDefPtr>();
}
groupToNodeDef[group].push_back(nodeDef);
}
for (const auto& group : groupList)
{
auto it = groupToNodeDef.find(group);
if (it != groupToNodeDef.end())
{
const auto& groupNodeDefs = it->second;
for (const auto& nodeDef : groupNodeDefs)
{
_nodesToAdd.emplace_back(nodeDef->getName(), nodeDef->getType(), nodeDef->getNodeString(), group);
}
}
}
addExtraNodes();
}
void Graph::buildUiBaseGraph(mx::DocumentPtr doc)
{
std::vector<mx::NodeGraphPtr> nodeGraphs = doc->getNodeGraphs();
std::vector<mx::InputPtr> inputNodes = doc->getActiveInputs();
std::vector<mx::OutputPtr> outputNodes = doc->getOutputs();
std::vector<mx::NodePtr> docNodes = doc->getNodes();
mx::ElementPredicate includeElement = getElementPredicate();
_graphNodes.clear();
_currLinks.clear();
_currEdge.clear();
_newLinks.clear();
_currPins.clear();
_graphTotalSize = 1;
// Create UiNodes for nodes that belong to the document so they are not in a nodegraph
for (mx::NodePtr node : docNodes)
{
if (!includeElement(node))
continue;
std::string name = node->getName();
auto currNode = std::make_shared<UiNode>(name, _graphTotalSize);
currNode->setNode(node);
setUiNodeInfo(currNode, node->getType(), node->getCategory());
}
// Create UiNodes for the nodegraph
for (mx::NodeGraphPtr nodeGraph : nodeGraphs)
{
if (!includeElement(nodeGraph))
continue;
std::string name = nodeGraph->getName();
auto currNode = std::make_shared<UiNode>(name, _graphTotalSize);
currNode->setNodeGraph(nodeGraph);
setUiNodeInfo(currNode, "", "nodegraph");
}
for (mx::InputPtr input : inputNodes)
{
if (!includeElement(input))
continue;
auto currNode = std::make_shared<UiNode>(input->getName(), _graphTotalSize);
currNode->setInput(input);
setUiNodeInfo(currNode, input->getType(), input->getCategory());
}
for (mx::OutputPtr output : outputNodes)
{
if (!includeElement(output))
continue;
auto currNode = std::make_shared<UiNode>(output->getName(), _graphTotalSize);
currNode->setOutput(output);
setUiNodeInfo(currNode, output->getType(), output->getCategory());
}
// Create edges for nodegraphs
for (mx::NodeGraphPtr graph : nodeGraphs)
{
for (mx::InputPtr input : graph->getActiveInputs())
{
int downNum = -1;
int upNum = -1;
mx::string nodeGraphName = input->getNodeGraphString();
mx::NodePtr connectedNode = input->getConnectedNode();
if (!nodeGraphName.empty())
{
downNum = findNode(graph->getName(), "nodegraph");
upNum = findNode(nodeGraphName, "nodegraph");
}
else if (connectedNode)
{
downNum = findNode(graph->getName(), "nodegraph");
upNum = findNode(connectedNode->getName(), "node");
}
if (upNum > -1)
{
UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNum], input);
if (!edgeExists(newEdge))
{
_graphNodes[downNum]->edges.push_back(newEdge);
_graphNodes[downNum]->setInputNodeNum(1);
_graphNodes[upNum]->setOutputConnection(_graphNodes[downNum]);
_currEdge.push_back(newEdge);
}
}
}
}
// Create edges for surface and material nodes
for (mx::NodePtr node : docNodes)
{
mx::NodeDefPtr nD = node->getNodeDef(node->getName());
for (mx::InputPtr input : node->getActiveInputs())
{
mx::string nodeGraphName = input->getNodeGraphString();
mx::NodePtr connectedNode = input->getConnectedNode();
mx::OutputPtr connectedOutput = input->getConnectedOutput();
int upNum = -1;
int downNum = -1;
if (!nodeGraphName.empty())
{
upNum = findNode(nodeGraphName, "nodegraph");
downNum = findNode(node->getName(), "node");
}
else if (connectedNode)
{
upNum = findNode(connectedNode->getName(), "node");
downNum = findNode(node->getName(), "node");
}
else if (connectedOutput)
{
upNum = findNode(connectedOutput->getName(), "output");
downNum = findNode(node->getName(), "node");
}
else if (!input->getInterfaceName().empty())
{
upNum = findNode(input->getInterfaceName(), "input");
downNum = findNode(node->getName(), "node");
}
if (upNum != -1)
{
UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNum], input);
if (!edgeExists(newEdge))
{
_graphNodes[downNum]->edges.push_back(newEdge);
_graphNodes[downNum]->setInputNodeNum(1);
_graphNodes[upNum]->setOutputConnection(_graphNodes[downNum]);
_currEdge.push_back(newEdge);
}
}
}
}
}
void Graph::buildUiNodeGraph(const mx::NodeGraphPtr& nodeGraphs)
{
// Clear all values so that ids can start with 0 or 1
_graphNodes.clear();
_currLinks.clear();
_currEdge.clear();
_newLinks.clear();
_currPins.clear();
_graphTotalSize = 1;
if (nodeGraphs)
{
mx::NodeGraphPtr nodeGraph = nodeGraphs;
std::vector<mx::ElementPtr> children = nodeGraph->topologicalSort();
mx::NodeDefPtr nodeDef = nodeGraph->getNodeDef();
mx::NodeDefPtr currNodeDef;
// Create input nodes
if (nodeDef)
{
std::vector<mx::InputPtr> inputs = nodeDef->getActiveInputs();
for (mx::InputPtr input : inputs)
{
auto currNode = std::make_shared<UiNode>(input->getName(), _graphTotalSize);
currNode->setInput(input);
setUiNodeInfo(currNode, input->getType(), input->getCategory());
}
}
// Search node graph children to create uiNodes
for (mx::ElementPtr elem : children)
{
mx::NodePtr node = elem->asA<mx::Node>();
mx::InputPtr input = elem->asA<mx::Input>();
mx::OutputPtr output = elem->asA<mx::Output>();
std::string name = elem->getName();
auto currNode = std::make_shared<UiNode>(name, _graphTotalSize);
if (node)
{
currNode->setNode(node);
setUiNodeInfo(currNode, node->getType(), node->getCategory());
}
else if (input)
{
currNode->setInput(input);
setUiNodeInfo(currNode, input->getType(), input->getCategory());
}
else if (output)
{
currNode->setOutput(output);
setUiNodeInfo(currNode, output->getType(), output->getCategory());
}
}
// Write out all connections.
std::set<mx::Edge> processedEdges;
for (mx::OutputPtr output : nodeGraph->getOutputs())
{
for (mx::Edge edge : output->traverseGraph())
{
if (!processedEdges.count(edge))
{
mx::ElementPtr upstreamElem = edge.getUpstreamElement();
mx::ElementPtr downstreamElem = edge.getDownstreamElement();
mx::ElementPtr connectingElem = edge.getConnectingElement();
mx::NodePtr upstreamNode = upstreamElem->asA<mx::Node>();
mx::NodePtr downstreamNode = downstreamElem->asA<mx::Node>();
mx::InputPtr upstreamInput = upstreamElem->asA<mx::Input>();
mx::InputPtr downstreamInput = downstreamElem->asA<mx::Input>();
mx::OutputPtr upstreamOutput = upstreamElem->asA<mx::Output>();
mx::OutputPtr downstreamOutput = downstreamElem->asA<mx::Output>();
std::string downName = downstreamElem->getName();
std::string upName = upstreamElem->getName();
std::string upstreamType;
std::string downstreamType;
if (upstreamNode)
{
upstreamType = "node";
}
else if (upstreamInput)
{
upstreamType = "input";
}
else if (upstreamOutput)
{
upstreamType = "output";
}
if (downstreamNode)
{
downstreamType = "node";
}
else if (downstreamInput)
{
downstreamType = "input";
}
else if (downstreamOutput)
{
downstreamType = "output";
}
int upNode = findNode(upName, upstreamType);
int downNode = findNode(downName, downstreamType);
if (downNode > 0 && upNode > 0 && _graphNodes[downNode]->getOutput())
{
// Create edges for the output nodes
UiEdge newEdge = UiEdge(_graphNodes[upNode], _graphNodes[downNode], nullptr);
if (!edgeExists(newEdge))
{
_graphNodes[downNode]->edges.push_back(newEdge);
_graphNodes[downNode]->setInputNodeNum(1);
_graphNodes[upNode]->setOutputConnection(_graphNodes[downNode]);
_currEdge.push_back(newEdge);
}
}
else if (connectingElem)
{
mx::InputPtr connectingInput = connectingElem->asA<mx::Input>();
if (connectingInput)
{
if ((upNode >= 0) && (downNode >= 0))
{
UiEdge newEdge = UiEdge(_graphNodes[upNode], _graphNodes[downNode], connectingInput);
if (!edgeExists(newEdge))
{
_graphNodes[downNode]->edges.push_back(newEdge);
_graphNodes[downNode]->setInputNodeNum(1);
_graphNodes[upNode]->setOutputConnection(_graphNodes[downNode]);
_currEdge.push_back(newEdge);
}
}
}
}
if (upstreamNode)
{
std::vector<mx::InputPtr> ins = upstreamNode->getActiveInputs();
for (mx::InputPtr input : ins)
{
// Connect input nodes
if (input->hasInterfaceName())
{
std::string interfaceName = input->getInterfaceName();
int newUp = findNode(interfaceName, "input");
if (newUp >= 0)
{
mx::InputPtr inputP = std::make_shared<mx::Input>(downstreamElem, input->getName());
UiEdge newEdge = UiEdge(_graphNodes[newUp], _graphNodes[upNode], input);
if (!edgeExists(newEdge))
{
_graphNodes[upNode]->edges.push_back(newEdge);
_graphNodes[upNode]->setInputNodeNum(1);
_graphNodes[newUp]->setOutputConnection(_graphNodes[upNode]);
_currEdge.push_back(newEdge);
}
}
}
}
}
processedEdges.insert(edge);
}
}
}
// Second pass to catch all of the connections that arent part of an output
for (mx::ElementPtr elem : children)
{
mx::NodePtr node = elem->asA<mx::Node>();
mx::InputPtr inputElem = elem->asA<mx::Input>();
mx::OutputPtr output = elem->asA<mx::Output>();
if (node)
{
std::vector<mx::InputPtr> inputs = node->getActiveInputs();
for (mx::InputPtr input : inputs)
{
mx::NodePtr upNode = input->getConnectedNode();
if (upNode)
{
int upNum = findNode(upNode->getName(), "node");
int downNode = findNode(node->getName(), "node");
if ((upNum >= 0) && (downNode >= 0))
{
UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNode], input);
if (!edgeExists(newEdge))
{
_graphNodes[downNode]->edges.push_back(newEdge);
_graphNodes[downNode]->setInputNodeNum(1);
_graphNodes[upNum]->setOutputConnection(_graphNodes[downNode]);
_currEdge.push_back(newEdge);
}
}
}
else if (input->getInterfaceInput())
{
int upNum = findNode(input->getInterfaceInput()->getName(), "input");
int downNode = findNode(node->getName(), "node");
if ((upNum >= 0) && (downNode >= 0))
{
UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNode], input);
if (!edgeExists(newEdge))
{
_graphNodes[downNode]->edges.push_back(newEdge);
_graphNodes[downNode]->setInputNodeNum(1);
_graphNodes[upNum]->setOutputConnection(_graphNodes[downNode]);
_currEdge.push_back(newEdge);
}
}
}
}
}
else if (output)
{
mx::NodePtr upNode = output->getConnectedNode();
if (upNode)
{
int upNum = findNode(upNode->getName(), "node");
int downNode = findNode(output->getName(), "output");
UiEdge newEdge = UiEdge(_graphNodes[upNum], _graphNodes[downNode], nullptr);
if (!edgeExists(newEdge))
{
_graphNodes[downNode]->edges.push_back(newEdge);
_graphNodes[downNode]->setInputNodeNum(1);
_graphNodes[upNum]->setOutputConnection(_graphNodes[downNode]);
_currEdge.push_back(newEdge);
}
}
}
}
}
}
int Graph::findNode(const std::string& name, const std::string& type)
{
int count = 0;
for (size_t i = 0; i < _graphNodes.size(); i++)
{
if (_graphNodes[i]->getName() == name)
{
if (type == "node" && _graphNodes[i]->getNode() != nullptr)
{
return count;
}
else if (type == "input" && _graphNodes[i]->getInput() != nullptr)
{
return count;
}
else if (type == "output" && _graphNodes[i]->getOutput() != nullptr)
{
return count;
}
else if (type == "nodegraph" && _graphNodes[i]->getNodeGraph() != nullptr)
{
return count;
}
}
count++;
}
return -1;
}
void Graph::positionPasteBin(ImVec2 pos)
{
ImVec2 totalPos = ImVec2(0, 0);
ImVec2 avgPos = ImVec2(0, 0);
// Get average position of original nodes
for (auto pasteNode : _copiedNodes)
{
ImVec2 origPos = ed::GetNodePosition(pasteNode.first->getId());
totalPos.x += origPos.x;
totalPos.y += origPos.y;
}
avgPos.x = totalPos.x / (int) _copiedNodes.size();
avgPos.y = totalPos.y / (int) _copiedNodes.size();
// Get offset from clicked position
ImVec2 offset = ImVec2(0, 0);
offset.x = pos.x - avgPos.x;
offset.y = pos.y - avgPos.y;
for (auto pasteNode : _copiedNodes)
{
if (!pasteNode.second)
{
continue;
}
ImVec2 newPos = ImVec2(0, 0);
newPos.x = ed::GetNodePosition(pasteNode.first->getId()).x + offset.x;
newPos.y = ed::GetNodePosition(pasteNode.first->getId()).y + offset.y;
ed::SetNodePosition(pasteNode.second->getId(), newPos);
}
}
void Graph::createEdge(UiNodePtr upNode, UiNodePtr downNode, mx::InputPtr connectingInput)
{
if (downNode->getOutput())
{
// Create edges for the output nodes
UiEdge newEdge = UiEdge(upNode, downNode, nullptr);
if (!edgeExists(newEdge))
{
downNode->edges.push_back(newEdge);
downNode->setInputNodeNum(1);
upNode->setOutputConnection(downNode);
_currEdge.push_back(newEdge);
}
}
else if (connectingInput)
{
UiEdge newEdge = UiEdge(upNode, downNode, connectingInput);
downNode->edges.push_back(newEdge);
downNode->setInputNodeNum(1);
upNode->setOutputConnection(downNode);
_currEdge.push_back(newEdge);
}
}
void Graph::copyUiNode(UiNodePtr node)
{
UiNodePtr copyNode = std::make_shared<UiNode>(mx::EMPTY_STRING, int(_graphTotalSize + 1));
++_graphTotalSize;
if (node->getElement())
{
std::string newName = _currGraphElem->createValidChildName(node->getName());
if (node->getNode())
{
mx::NodePtr mxNode;
mxNode = _currGraphElem->addNodeInstance(node->getNode()->getNodeDef());
mxNode->copyContentFrom(node->getNode());
mxNode->setName(newName);
copyNode->setNode(mxNode);
}
else if (node->getInput())
{
mx::InputPtr mxInput;
mxInput = _currGraphElem->addInput(newName);
mxInput->copyContentFrom(node->getInput());
copyNode->setInput(mxInput);
}
else if (node->getOutput())
{
mx::OutputPtr mxOutput;
mxOutput = _currGraphElem->addOutput(newName);
mxOutput->copyContentFrom(node->getOutput());
mxOutput->setName(newName);
copyNode->setOutput(mxOutput);
}
copyNode->getElement()->setName(newName);
copyNode->setName(newName);
}
else if (node->getNodeGraph())
{
_graphDoc->addNodeGraph();
std::string nodeGraphName = _graphDoc->getNodeGraphs().back()->getName();
copyNode->setNodeGraph(_graphDoc->getNodeGraphs().back());
copyNode->setName(nodeGraphName);
copyNodeGraph(node, copyNode);
}
setUiNodeInfo(copyNode, node->getType(), node->getCategory());
_copiedNodes[node] = copyNode;
_graphNodes.push_back(copyNode);
}
void Graph::copyNodeGraph(UiNodePtr origGraph, UiNodePtr copyGraph)
{
copyGraph->getNodeGraph()->copyContentFrom(origGraph->getNodeGraph());
std::vector<mx::InputPtr> inputs = copyGraph->getNodeGraph()->getActiveInputs();
for (mx::InputPtr input : inputs)
{
std::string newName = _graphDoc->createValidChildName(input->getName());
input->setName(newName);
}
}
void Graph::copyInputs()
{
for (std::map<UiNodePtr, UiNodePtr>::iterator iter = _copiedNodes.begin(); iter != _copiedNodes.end(); ++iter)
{
int count = 0;
UiNodePtr origNode = iter->first;
UiNodePtr copyNode = iter->second;
for (UiPinPtr pin : origNode->inputPins)
{
if (origNode->getConnectedNode(pin->_name) && !_ctrlClick)
{
// If original node is connected check if connect node is in copied nodes
if (_copiedNodes.find(origNode->getConnectedNode(pin->_name)) != _copiedNodes.end())
{
// Set copy node connected to the value at this key
createEdge(_copiedNodes[origNode->getConnectedNode(pin->_name)], copyNode, copyNode->inputPins[count]->_input);
UiNodePtr upNode = _copiedNodes[origNode->getConnectedNode(pin->_name)];
if (copyNode->getNode() || copyNode->getNodeGraph())
{
mx::InputPtr connectingInput = nullptr;
copyNode->inputPins[count]->_input->copyContentFrom(pin->_input);
// Update value to be empty
if (copyNode->getNode() && copyNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING)
{
if (upNode->getOutput())
{
copyNode->inputPins[count]->_input->setConnectedOutput(upNode->getOutput());
}
else if (upNode->getInput())
{
copyNode->inputPins[count]->_input->setConnectedInterfaceName(upNode->getName());
}
else
{
if (upNode->getNodeGraph())
{
ed::PinId outputId = getOutputPin(copyNode, upNode, copyNode->inputPins[count]);
for (UiPinPtr outPin : upNode->outputPins)
{
if (outPin->_pinId == outputId)
{
mx::OutputPtr outputs = upNode->getNodeGraph()->getOutput(outPin->_name);
copyNode->inputPins[count]->_input->setConnectedOutput(outputs);
}
}
}
else
{
copyNode->inputPins[count]->_input->setConnectedNode(upNode->getNode());
}
}
}
else
{
if (upNode->getInput())
{
copyNode->inputPins[count]->_input->setConnectedInterfaceName(upNode->getName());
}
else
{
copyNode->inputPins[count]->_input->setConnectedNode(upNode->getNode());
}
}
copyNode->inputPins[count]->setConnected(true);
}
else if (copyNode->getOutput() != nullptr)
{
mx::InputPtr connectingInput = nullptr;
copyNode->getOutput()->setConnectedNode(upNode->getNode());
}
// Update input node num and output connections
copyNode->setInputNodeNum(1);
upNode->setOutputConnection(copyNode);
}
else if (pin->_input)
{
if (pin->_input->getInterfaceInput())
{
copyNode->inputPins[count]->_input->setConnectedInterfaceName(mx::EMPTY_STRING);
}
copyNode->inputPins[count]->setConnected(false);
setDefaults(copyNode->inputPins[count]->_input);
copyNode->inputPins[count]->_input->setConnectedNode(nullptr);
copyNode->inputPins[count]->_input->setConnectedOutput(nullptr);
}
}
count++;
}
}
}
void Graph::addNode(const std::string& category, const std::string& name, const std::string& type)
{
mx::NodePtr node = nullptr;
std::vector<mx::NodeDefPtr> matchingNodeDefs;
// Create document or node graph is there is not already one
if (category == "output")
{
std::string outName = "";
mx::OutputPtr newOut;
// add output as child of correct parent and create valid name
outName = _currGraphElem->createValidChildName(name);
newOut = _currGraphElem->addOutput(outName, type);
auto outputNode = std::make_shared<UiNode>(outName, int(++_graphTotalSize));
outputNode->setOutput(newOut);
setUiNodeInfo(outputNode, type, category);
return;
}
if (category == "input")
{
std::string inName = "";
mx::InputPtr newIn = nullptr;
// Add input as child of correct parent and create valid name
inName = _currGraphElem->createValidChildName(name);
newIn = _currGraphElem->addInput(inName, type);
auto inputNode = std::make_shared<UiNode>(inName, int(++_graphTotalSize));
setDefaults(newIn);
inputNode->setInput(newIn);
setUiNodeInfo(inputNode, type, category);
return;
}
else if (category == "group")
{
auto groupNode = std::make_shared<UiNode>(name, int(++_graphTotalSize));
// Set message of group UiNode in order to identify it as such
groupNode->setMessage("Comment");
setUiNodeInfo(groupNode, type, "group");
// Create ui portions of group node
buildGroupNode(_graphNodes.back());
return;
}
else if (category == "nodegraph")
{
// Create new mx::NodeGraph and set as current node graph
_graphDoc->addNodeGraph();
std::string nodeGraphName = _graphDoc->getNodeGraphs().back()->getName();
auto nodeGraphNode = std::make_shared<UiNode>(nodeGraphName, int(++_graphTotalSize));
// Set mx::Nodegraph as node graph for uiNode
nodeGraphNode->setNodeGraph(_graphDoc->getNodeGraphs().back());
setUiNodeInfo(nodeGraphNode, type, "nodegraph");
return;
}
else
{
matchingNodeDefs = _graphDoc->getMatchingNodeDefs(category);
for (mx::NodeDefPtr nodedef : matchingNodeDefs)
{
std::string userNodeDefName = getUserNodeDefName(nodedef->getName());
if (userNodeDefName == name)
{
node = _currGraphElem->addNodeInstance(nodedef, _currGraphElem->createValidChildName(name));
}
}
}
if (node)
{
int num = 0;
int countDef = 0;
for (size_t i = 0; i < matchingNodeDefs.size(); i++)
{
std::string userNodeDefName = getUserNodeDefName(matchingNodeDefs[i]->getName());
if (userNodeDefName == name)
{
num = countDef;
}
countDef++;
}
std::vector<mx::InputPtr> defInputs = matchingNodeDefs[num]->getActiveInputs();
// Add inputs to UiNode as pins so that we can later add them to the node if necessary
auto newNode = std::make_shared<UiNode>(node->getName(), int(++_graphTotalSize));
newNode->setCategory(category);
newNode->setType(type);
newNode->setNode(node);
newNode->_showAllInputs = true;
node->setType(type);
++_graphTotalSize;
for (mx::InputPtr input : defInputs)
{
UiPinPtr inPin = std::make_shared<UiPin>(_graphTotalSize, &*input->getName().begin(), input->getType(), newNode, ax::NodeEditor::PinKind::Input, input, nullptr);
newNode->inputPins.push_back(inPin);
_currPins.push_back(inPin);
++_graphTotalSize;
}
std::vector<mx::OutputPtr> defOutputs = matchingNodeDefs[num]->getActiveOutputs();
for (mx::OutputPtr output : defOutputs)
{
UiPinPtr outPin = std::make_shared<UiPin>(_graphTotalSize, &*output->getName().begin(), output->getType(), newNode, ax::NodeEditor::PinKind::Output, nullptr, nullptr);
newNode->outputPins.push_back(outPin);
_currPins.push_back(outPin);
++_graphTotalSize;
}
_graphNodes.push_back(std::move(newNode));
updateMaterials();
}
}
int Graph::getNodeId(ed::PinId pinId)
{
for (UiPinPtr pin : _currPins)
{
if (pin->_pinId == pinId)
{
return findNode(pin->_pinNode->getId());
}
}
return -1;
}
UiPinPtr Graph::getPin(ed::PinId pinId)
{
for (UiPinPtr pin : _currPins)
{
if (pin->_pinId == pinId)
{
return pin;
}
}
UiPinPtr nullPin = std::make_shared<UiPin>(-10000, "nullPin", "null", nullptr, ax::NodeEditor::PinKind::Output, nullptr, nullptr);
return nullPin;
}
void Graph::drawPinIcon(const std::string& type, bool connected, int alpha)
{
ax::Drawing::IconType iconType = ax::Drawing::IconType::Flow;
ImColor color = ImColor(0, 0, 0, 255);
if (_pinColor.find(type) != _pinColor.end())
{
color = _pinColor[type];
}
color.Value.w = alpha / 255.0f;
ax::Widgets::Icon(ImVec2(24, 24), iconType, connected, color, ImColor(32, 32, 32, alpha));
}
void Graph::buildGroupNode(UiNodePtr node)
{
const float commentAlpha = 0.75f;
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, commentAlpha);
ed::PushStyleColor(ed::StyleColor_NodeBg, ImColor(255, 255, 255, 64));
ed::PushStyleColor(ed::StyleColor_NodeBorder, ImColor(255, 255, 255, 64));
ed::BeginNode(node->getId());
ImGui::PushID(node->getId());
std::string temp = node->getMessage();
ImVec2 messageSize = ImGui::CalcTextSize(temp.c_str());
ImGui::PushItemWidth(messageSize.x + 15);
ImGui::InputText("##edit", &temp);
node->setMessage(temp);
ImGui::PopItemWidth();
ed::Group(ImVec2(300, 200));
ImGui::PopID();
ed::EndNode();
ed::PopStyleColor(2);
ImGui::PopStyleVar();
if (ed::BeginGroupHint(node->getId()))
{
auto bgAlpha = static_cast<int>(ImGui::GetStyle().Alpha * 255);
auto min = ed::GetGroupMin();
ImGui::SetCursorScreenPos(min - ImVec2(-8, ImGui::GetTextLineHeightWithSpacing() + 4));
ImGui::BeginGroup();
ImGui::PushID(node->getId() + 1000);
std::string tempName = node->getName();
ImVec2 nameSize = ImGui::CalcTextSize(temp.c_str());
ImGui::PushItemWidth(nameSize.x);
ImGui::InputText("##edit", &tempName);
node->setName(tempName);
ImGui::PopID();
ImGui::EndGroup();
auto drawList = ed::GetHintBackgroundDrawList();
ImRect hintBounds = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax());
ImRect hintFrameBounds = expandImRect(hintBounds, 8, 4);
drawList->AddRectFilled(
hintFrameBounds.GetTL(),
hintFrameBounds.GetBR(),
IM_COL32(255, 255, 255, 64 * bgAlpha / 255), 4.0f);
drawList->AddRect(
hintFrameBounds.GetTL(),
hintFrameBounds.GetBR(),
IM_COL32(0, 255, 255, 128 * bgAlpha / 255), 4.0f);
}
ed::EndGroupHint();
}
bool Graph::readOnly()
{
// If the sources are not the same then the current graph cannot be modified
return _currGraphElem->getActiveSourceUri() != _graphDoc->getActiveSourceUri();
}
void Graph::drawOutputPins(UiNodePtr node, const std::string& longestInputLabel)
{
std::string longestLabel = longestInputLabel;
for (UiPinPtr pin : node->outputPins)
{
if (pin->_name.size() > longestLabel.size())
longestLabel = pin->_name;
}
// Create output pins
float nodeWidth = ImGui::CalcTextSize(longestLabel.c_str()).x;
for (UiPinPtr pin : node->outputPins)
{
const float indent = nodeWidth - ImGui::CalcTextSize(pin->_name.c_str()).x;
ImGui::Indent(indent);
ImGui::TextUnformatted(pin->_name.c_str());
ImGui::SameLine();
ed::BeginPin(pin->_pinId, ed::PinKind::Output);
bool connected = pin->getConnected();
if (!_pinFilterType.empty())
{
drawPinIcon(pin->_type, connected, _pinFilterType == pin->_type ? DEFAULT_ALPHA : FILTER_ALPHA);
}
else
{
drawPinIcon(pin->_type, connected, DEFAULT_ALPHA);
}
ed::EndPin();
ImGui::Unindent(indent);
}
}
void Graph::drawInputPin(UiPinPtr pin)
{
ed::BeginPin(pin->_pinId, ed::PinKind::Input);
ImGui::PushID(int(pin->_pinId.Get()));
bool connected = pin->getConnected();
if (!_pinFilterType.empty())
{
if (_pinFilterType == pin->_type)
{
drawPinIcon(pin->_type, connected, DEFAULT_ALPHA);
}
else
{
drawPinIcon(pin->_type, connected, FILTER_ALPHA);
}
}
else
{
drawPinIcon(pin->_type, connected, DEFAULT_ALPHA);
}
ImGui::PopID();
ed::EndPin();
ImGui::SameLine();
ImGui::TextUnformatted(pin->_name.c_str());
}
std::vector<int> Graph::createNodes(bool nodegraph)
{
std::vector<int> outputNum;
for (UiNodePtr node : _graphNodes)
{
if (node->getCategory() == "group")
{
buildGroupNode(node);
}
else
{
// Color for output pin
std::string outputType;
if (node->getNode() != nullptr)
{
ed::BeginNode(node->getId());
ImGui::PushID(node->getId());
ImGui::SetWindowFontScale(1.2f * _fontScale);
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCursorScreenPos() + ImVec2(-7.0, -8.0),
ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f),
ImColor(ImColor(55, 55, 55, 255)), 12.f);
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCursorScreenPos() + ImVec2(-7.0, 3),
ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f),
ImColor(ImColor(55, 55, 55, 255)), 0.f);
ImGui::Text("%s", node->getName().c_str());
ImGui::SetWindowFontScale(_fontScale);
std::string longestInputLabel = node->getName();
for (UiPinPtr pin : node->inputPins)
{
UiNodePtr upUiNode = node->getConnectedNode(pin->_name);
if (upUiNode)
{
size_t pinIndex = 0;
if (upUiNode->outputPins.size() > 0)
{
const std::string outputString = pin->_input->getOutputString();
if (!outputString.empty())
{
for (size_t i = 0; i < upUiNode->outputPins.size(); i++)
{
UiPinPtr outPin = upUiNode->outputPins[i];
if (outPin->_name == outputString)
{
pinIndex = i;
break;
}
}
}
upUiNode->outputPins[pinIndex]->addConnection(pin);
pin->addConnection(upUiNode->outputPins[pinIndex]);
}
pin->setConnected(true);
}
if (node->_showAllInputs || (pin->getConnected() || node->getNode()->getInput(pin->_name)))
{
drawInputPin(pin);
if (pin->_name.size() > longestInputLabel.size())
longestInputLabel = pin->_name;
}
}
drawOutputPins(node, longestInputLabel);
// Set color of output pin
if (node->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING)
{
if (node->getOutputConnections().size() > 0)
{
for (UiNodePtr outputCon : node->getOutputConnections())
{
outputNum.push_back(findNode(outputCon->getId()));
}
}
}
}
else if (node->getInput() != nullptr)
{
std::string longestInputLabel = node->getName();
ed::BeginNode(node->getId());
ImGui::PushID(node->getId());
ImGui::SetWindowFontScale(1.2f * _fontScale);
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCursorScreenPos() + ImVec2(-7.0f, -8.0f),
ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f),
ImColor(ImColor(85, 85, 85, 255)), 12.f);
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCursorScreenPos() + ImVec2(-7.0f, 3.f),
ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f),
ImColor(ImColor(85, 85, 85, 255)), 0.f);
ImGui::Text("%s", node->getName().c_str());
ImGui::SetWindowFontScale(_fontScale);
outputType = node->getInput()->getType();
for (UiPinPtr pin : node->inputPins)
{
UiNodePtr upUiNode = node->getConnectedNode(node->getName());
if (upUiNode)
{
if (upUiNode->outputPins.size())
{
std::string outString = pin->_output ? pin->_output->getOutputString() : mx::EMPTY_STRING;
size_t pinIndex = 0;
if (!outString.empty())
{
for (size_t i = 0; i < upUiNode->outputPins.size(); i++)
{
if (upUiNode->outputPins[i]->_name == outString)
{
pinIndex = i;
break;
}
}
}
upUiNode->outputPins[pinIndex]->addConnection(pin);
pin->addConnection(upUiNode->outputPins[pinIndex]);
}
pin->setConnected(true);
}
ed::BeginPin(pin->_pinId, ed::PinKind::Input);
if (!_pinFilterType.empty())
{
if (_pinFilterType == pin->_type)
{
drawPinIcon(pin->_type, true, DEFAULT_ALPHA);
}
else
{
drawPinIcon(pin->_type, true, FILTER_ALPHA);
}
}
else
{
drawPinIcon(pin->_type, true, DEFAULT_ALPHA);
}
ImGui::SameLine();
ImGui::TextUnformatted("value");
ed::EndPin();
if (pin->_name.size() > longestInputLabel.size())
longestInputLabel = pin->_name;
}
drawOutputPins(node, longestInputLabel);
}
else if (node->getOutput() != nullptr)
{
std::string longestInputLabel = node->getName();
ed::BeginNode(node->getId());
ImGui::PushID(node->getId());
ImGui::SetWindowFontScale(1.2f * _fontScale);
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCursorScreenPos() + ImVec2(-7.0, -8.0),
ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f),
ImColor(ImColor(35, 35, 35, 255)), 12.f);
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCursorScreenPos() + ImVec2(-7.0, 3),
ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f),
ImColor(ImColor(35, 35, 35, 255)), 0);
ImGui::Text("%s", node->getName().c_str());
ImGui::SetWindowFontScale(_fontScale);
outputType = node->getOutput()->getType();
for (UiPinPtr pin : node->inputPins)
{
UiNodePtr upUiNode = node->getConnectedNode("");
if (upUiNode)
{
if (upUiNode->outputPins.size())
{
std::string outString = pin->_output ? pin->_output->getOutputString() : mx::EMPTY_STRING;
size_t pinIndex = 0;
if (!outString.empty())
{
for (size_t i = 0; i < upUiNode->outputPins.size(); i++)
{
if (upUiNode->outputPins[i]->_name == outString)
{
pinIndex = i;
break;
}
}
}
upUiNode->outputPins[pinIndex]->addConnection(pin);
pin->addConnection(upUiNode->outputPins[pinIndex]);
}
}
ed::BeginPin(pin->_pinId, ed::PinKind::Input);
if (!_pinFilterType.empty())
{
if (_pinFilterType == pin->_type)
{
drawPinIcon(pin->_type, true, DEFAULT_ALPHA);
}
else
{
drawPinIcon(pin->_type, true, FILTER_ALPHA);
}
}
else
{
drawPinIcon(pin->_type, true, DEFAULT_ALPHA);
}
ImGui::SameLine();
ImGui::TextUnformatted("input");
ed::EndPin();
if (pin->_name.size() > longestInputLabel.size())
longestInputLabel = pin->_name;
}
drawOutputPins(node, longestInputLabel);
if (nodegraph)
{
outputNum.push_back(findNode(node->getId()));
}
}
else if (node->getNodeGraph() != nullptr)
{
std::string longestInputLabel = node->getName();
ed::BeginNode(node->getId());
ImGui::PushID(node->getId());
ImGui::SetWindowFontScale(1.2f * _fontScale);
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCursorScreenPos() + ImVec2(-7.0, -8.0),
ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f),
ImColor(ImColor(35, 35, 35, 255)), 12.f);
ImGui::GetWindowDrawList()->AddRectFilled(
ImGui::GetCursorScreenPos() + ImVec2(-7.0, 3),
ImGui::GetCursorScreenPos() + ImVec2(ed::GetNodeSize(node->getId()).x - 9.f, ImGui::GetTextLineHeight() + 2.f),
ImColor(ImColor(35, 35, 35, 255)), 0);
ImGui::Text("%s", node->getName().c_str());
ImGui::SetWindowFontScale(_fontScale);
for (UiPinPtr pin : node->inputPins)
{
if (node->getConnectedNode(pin->_name) != nullptr)
{
pin->setConnected(true);
}
if (node->_showAllInputs || (pin->getConnected() || node->getNodeGraph()->getInput(pin->_name)))
{
drawInputPin(pin);
if (pin->_name.size() > longestInputLabel.size())
longestInputLabel = pin->_name;
}
}
drawOutputPins(node, longestInputLabel);
}
ImGui::PopID();
ed::EndNode();
}
}
ImGui::SetWindowFontScale(_fontScale);
return outputNum;
}
void Graph::addNodeInput(UiNodePtr node, mx::InputPtr& input)
{
if (node->getNode())
{
if (!node->getNode()->getInput(input->getName()))
{
input = node->getNode()->addInput(input->getName(), input->getType());
input->setConnectedNode(nullptr);
}
}
}
void Graph::setDefaults(mx::InputPtr input)
{
if (input->getType() == "float")
{
input->setValue(0.f, "float");
}
else if (input->getType() == "integer")
{
input->setValue(0, "integer");
}
else if (input->getType() == "color3")
{
input->setValue(mx::Color3(0.f, 0.f, 0.f), "color3");
}
else if (input->getType() == "color4")
{
input->setValue(mx::Color4(0.f, 0.f, 0.f, 1.f), "color4");
}
else if (input->getType() == "vector2")
{
input->setValue(mx::Vector2(0.f, 0.f), "vector2");
}
else if (input->getType() == "vector3")
{
input->setValue(mx::Vector3(0.f, 0.f, 0.f), "vector3");
}
else if (input->getType() == "vector4")
{
input->setValue(mx::Vector4(0.f, 0.f, 0.f, 0.f), "vector4");
}
else if (input->getType() == "string")
{
input->setValue("", "string");
}
else if (input->getType() == "filename")
{
input->setValue("", "filename");
}
else if (input->getType() == "boolean")
{
input->setValue(false, "boolean");
}
}
void Graph::addLink(ed::PinId startPinId, ed::PinId endPinId)
{
// Prefer to assume left to right - start is an output, end is an input; swap if inaccurate
if (UiPinPtr inputPin = getPin(endPinId); inputPin && inputPin->_kind != ed::PinKind::Input)
{
auto tmp = startPinId;
startPinId = endPinId;
endPinId = tmp;
}
int end_attr = int(endPinId.Get());
int start_attr = int(startPinId.Get());
ed::PinId outputPinId = startPinId;
ed::PinId inputPinId = endPinId;
UiPinPtr outputPin = getPin(outputPinId);
UiPinPtr inputPin = getPin(inputPinId);
if (!inputPin || !outputPin)
{
ed::RejectNewItem();
return;
}
// Perform type check
bool typesMatch = (outputPin->_type == inputPin->_type);
if (!typesMatch)
{
ed::RejectNewItem();
showLabel("Invalid connection due to mismatched types", ImColor(50, 50, 50, 255));
return;
}
// Perform kind check
bool kindsMatch = (outputPin->_kind == inputPin->_kind);
if (kindsMatch)
{
ed::RejectNewItem();
showLabel("Invalid connection due to same input/output kind", ImColor(50, 50, 50, 255));
return;
}
int upNode = getNodeId(outputPinId);
int downNode = getNodeId(inputPinId);
UiNodePtr uiDownNode = _graphNodes[downNode];
UiNodePtr uiUpNode = _graphNodes[upNode];
if (!uiDownNode || !uiUpNode)
{
ed::RejectNewItem();
return;
}
// Make sure there is an implementation for node
const mx::ShaderGenerator& shadergen = _renderer->getGenContext().getShaderGenerator();
// Prevent direct connecting from input to output
if (uiDownNode->getInput() && uiUpNode->getOutput())
{
ed::RejectNewItem();
showLabel("Direct connections between inputs and outputs is invalid", ImColor(50, 50, 50, 255));
return;
}
// Find the implementation for this nodedef if not an input or output uinode
if (uiDownNode->getInput() && _isNodeGraph)
{
ed::RejectNewItem();
showLabel("Cannot connect to inputs inside of graph", ImColor(50, 50, 50, 255));
return;
}
else if (uiUpNode->getNode())
{
mx::ShaderNodeImplPtr impl = shadergen.getImplementation(*_graphNodes[upNode]->getNode()->getNodeDef(), _renderer->getGenContext());
if (!impl)
{
ed::RejectNewItem();
showLabel("Invalid Connection: Node does not have an implementation", ImColor(50, 50, 50, 255));
return;
}
}
if (ed::AcceptNewItem())
{
// If the accepting node already has a link, remove it
if (inputPin->_connected)
{
for (auto iter = _currLinks.begin(); iter != _currLinks.end(); ++iter)
{
if (iter->_endAttr == end_attr)
{
// Found existing link - remove it; adapted from deleteLink
// note: ed::BreakLinks doesn't work as the order ends up inaccurate
deleteLinkInfo(iter->_startAttr, iter->_endAttr);
_currLinks.erase(iter);
break;
}
}
}
// Since we accepted new link, lets add one to our list of links.
Link link;
link._startAttr = start_attr;
link._endAttr = end_attr;
_currLinks.push_back(link);
_frameCount = ImGui::GetFrameCount();
_renderer->setMaterialCompilation(true);
inputPin->addConnection(outputPin);
outputPin->addConnection(inputPin);
outputPin->setConnected(true);
inputPin->setConnected(true);
if (uiDownNode->getNode() || uiDownNode->getNodeGraph())
{
mx::InputPtr connectingInput = nullptr;
for (UiPinPtr pin : uiDownNode->inputPins)
{
if (pin->_pinId == inputPinId)
{
addNodeInput(uiDownNode, pin->_input);
// Update value to be empty
if (uiDownNode->getNode() && uiDownNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING)
{
if (uiUpNode->getOutput() != nullptr)
{
pin->_input->setConnectedOutput(uiUpNode->getOutput());
}
else if (uiUpNode->getInput() != nullptr)
{
pin->_input->setConnectedInterfaceName(uiUpNode->getName());
}
else
{
if (uiUpNode->getNodeGraph() != nullptr)
{
for (UiPinPtr outPin : uiUpNode->outputPins)
{
// Set pin connection to correct output
if (outPin->_pinId == outputPinId)
{
mx::OutputPtr outputs = uiUpNode->getNodeGraph()->getOutput(outPin->_name);
pin->_input->setConnectedOutput(outputs);
}
}
}
else
{
pin->_input->setConnectedNode(uiUpNode->getNode());
}
}
}
else
{
if (uiUpNode->getInput())
{
pin->_input->setConnectedInterfaceName(uiUpNode->getName());
}
else
{
if (uiUpNode->getNode())
{
mx::NodePtr upstreamNode = _graphNodes[upNode]->getNode();
mx::NodeDefPtr upstreamNodeDef = upstreamNode->getNodeDef();
bool isMultiOutput = upstreamNodeDef ? upstreamNodeDef->getOutputs().size() > 1 : false;
if (!isMultiOutput)
{
pin->_input->setConnectedNode(uiUpNode->getNode());
}
else
{
for (UiPinPtr outPin : _graphNodes[upNode]->outputPins)
{
// Set pin connection to correct output
if (outPin->_pinId == outputPinId)
{
mx::OutputPtr outputs = uiUpNode->getNode()->getOutput(outPin->_name);
if (!outputs)
{
outputs = uiUpNode->getNode()->addOutput(outPin->_name, pin->_input->getType());
}
pin->_input->setConnectedOutput(outputs);
}
}
}
}
else if (uiUpNode->getNodeGraph())
{
for (UiPinPtr outPin : uiUpNode->outputPins)
{
// Set pin connection to correct output
if (outPin->_pinId == outputPinId)
{
mx::OutputPtr outputs = uiUpNode->getNodeGraph()->getOutput(outPin->_name);
pin->_input->setConnectedOutput(outputs);
}
}
}
}
}
pin->setConnected(true);
connectingInput = pin->_input;
break;
}
}
// Create new edge and set edge information
createEdge(_graphNodes[upNode], _graphNodes[downNode], connectingInput);
}
else if (_graphNodes[downNode]->getOutput() != nullptr)
{
mx::InputPtr connectingInput = nullptr;
_graphNodes[downNode]->getOutput()->setConnectedNode(_graphNodes[upNode]->getNode());
// Create new edge and set edge information
createEdge(_graphNodes[upNode], _graphNodes[downNode], connectingInput);
}
else
{
// Create new edge and set edge info
UiEdge newEdge = UiEdge(_graphNodes[upNode], _graphNodes[downNode], nullptr);
if (!edgeExists(newEdge))
{
_graphNodes[downNode]->edges.push_back(newEdge);
_currEdge.push_back(newEdge);
// Update input node num and output connections
_graphNodes[downNode]->setInputNodeNum(1);
_graphNodes[upNode]->setOutputConnection(_graphNodes[downNode]);
}
}
}
}
void Graph::removeEdge(int downNode, int upNode, UiPinPtr pin)
{
int num = _graphNodes[downNode]->getEdgeIndex(_graphNodes[upNode]->getId(), pin);
if (num != -1)
{
if (_graphNodes[downNode]->edges.size() == 1)
{
_graphNodes[downNode]->edges.erase(_graphNodes[downNode]->edges.begin() + 0);
}
else if (_graphNodes[downNode]->edges.size() > 1)
{
_graphNodes[downNode]->edges.erase(_graphNodes[downNode]->edges.begin() + num);
}
}
_graphNodes[downNode]->setInputNodeNum(-1);
_graphNodes[upNode]->removeOutputConnection(_graphNodes[downNode]->getName());
}
void Graph::deleteLinkInfo(int startAttr, int endAttr)
{
int upNode = getNodeId(startAttr);
int downNode = getNodeId(endAttr);
if (upNode == -1 || downNode == -1)
{
return;
}
// Change input to default value
if (_graphNodes[downNode]->getNode())
{
mx::NodeDefPtr nodeDef = _graphNodes[downNode]->getNode()->getNodeDef(_graphNodes[downNode]->getNode()->getName());
for (UiPinPtr pin : _graphNodes[downNode]->inputPins)
{
if ((int) pin->_pinId.Get() == endAttr)
{
removeEdge(downNode, upNode, pin);
mx::ValuePtr val = nodeDef->getActiveInput(pin->_input->getName())->getValue();
if (_graphNodes[downNode]->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING && _graphNodes[upNode]->getNodeGraph())
{
pin->_input->setConnectedOutput(nullptr);
}
else
{
pin->_input->setConnectedNode(nullptr);
}
if (_graphNodes[upNode]->getInput())
{
// Remove interface value in order to set the default of the input
pin->_input->setConnectedInterfaceName(mx::EMPTY_STRING);
setDefaults(pin->_input);
setDefaults(_graphNodes[upNode]->getInput());
}
for (UiPinPtr connect : pin->_connections)
{
pin->deleteConnection(connect);
}
// Remove any output reference
pin->_input->removeAttribute(mx::PortElement::OUTPUT_ATTRIBUTE);
pin->setConnected(false);
// If a value exists update the input with it
if (val)
{
pin->_input->setValueString(val->getValueString());
}
}
}
}
else if (_graphNodes[downNode]->getNodeGraph())
{
// Set default values for nodegraph node pins ie nodegraph inputs
mx::NodeDefPtr nodeDef = _graphNodes[downNode]->getNodeGraph()->getNodeDef();
for (UiPinPtr pin : _graphNodes[downNode]->inputPins)
{
if ((int) pin->_pinId.Get() == endAttr)
{
removeEdge(downNode, upNode, pin);
if (_graphNodes[upNode]->getInput())
{
_graphNodes[downNode]->getNodeGraph()->getInput(pin->_name)->setConnectedInterfaceName(mx::EMPTY_STRING);
setDefaults(_graphNodes[upNode]->getInput());
}
for (UiPinPtr connect : pin->_connections)
{
pin->deleteConnection(connect);
}
pin->_input->setConnectedNode(nullptr);
pin->setConnected(false);
setDefaults(pin->_input);
}
}
}
else if (_graphNodes[downNode]->getOutput())
{
for (UiPinPtr pin : _graphNodes[downNode]->inputPins)
{
if ((int) pin->_pinId.Get() == endAttr)
{
removeEdge(downNode, upNode, pin);
_graphNodes[downNode]->getOutput()->removeAttribute("nodename");
for (UiPinPtr connect : pin->_connections)
{
pin->deleteConnection(connect);
}
pin->setConnected(false);
}
}
}
}
void Graph::deleteLink(ed::LinkId deletedLinkId)
{
// If you agree that link can be deleted, accept deletion.
if (ed::AcceptDeletedItem())
{
_renderer->setMaterialCompilation(true);
_frameCount = ImGui::GetFrameCount();
int link_id = int(deletedLinkId.Get());
// Then remove link from your data.
int pos = findLinkPosition(link_id);
// Link start -1 equals node num
Link currLink = _currLinks[pos];
deleteLinkInfo(currLink._startAttr, currLink._endAttr);
_currLinks.erase(_currLinks.begin() + pos);
}
}
void Graph::deleteNode(UiNodePtr node)
{
// Delete link
for (UiPinPtr inputPin : node->inputPins)
{
UiNodePtr upNode = node->getConnectedNode(inputPin->_name);
if (upNode)
{
upNode->removeOutputConnection(node->getName());
int num = node->getEdgeIndex(upNode->getId(), inputPin);
// Erase edge between node and up node
if (num != -1)
{
if (node->edges.size() == 1)
{
node->edges.erase(node->edges.begin() + 0);
}
else if (node->edges.size() > 1)
{
node->edges.erase(node->edges.begin() + num);
}
}
}
}
for (UiPinPtr outputPin : node->outputPins)
{
// Update downNode info
for (UiPinPtr pin : outputPin.get()->getConnections())
{
mx::ValuePtr val;
if (pin->_pinNode->getNode())
{
mx::NodeDefPtr nodeDef = pin->_pinNode->getNode()->getNodeDef(pin->_pinNode->getNode()->getName());
val = nodeDef->getActiveInput(pin->_input->getName())->getValue();
if (pin->_pinNode->getNode()->getType() == mx::SURFACE_SHADER_TYPE_STRING)
{
pin->_input->setConnectedOutput(nullptr);
}
else
{
pin->_input->setConnectedNode(nullptr);
}
if (node->getInput())
{
// Remove interface in order to set the default of the input
pin->_input->setConnectedInterfaceName(mx::EMPTY_STRING);
setDefaults(pin->_input);
setDefaults(node->getInput());
}
}
else if (pin->_pinNode->getNodeGraph())
{
if (node->getInput())
{
pin->_pinNode->getNodeGraph()->getInput(pin->_name)->setConnectedInterfaceName(mx::EMPTY_STRING);
setDefaults(node->getInput());
}
pin->_input->setConnectedNode(nullptr);
pin->setConnected(false);
setDefaults(pin->_input);
}
pin->setConnected(false);
if (val)
{
pin->_input->setValueString(val->getValueString());
}
int num = pin->_pinNode->getEdgeIndex(node->getId(), pin);
if (num != -1)
{
if (pin->_pinNode->edges.size() == 1)
{
pin->_pinNode->edges.erase(pin->_pinNode->edges.begin() + 0);
}
else if (pin->_pinNode->edges.size() > 1)
{
pin->_pinNode->edges.erase(pin->_pinNode->edges.begin() + num);
}
}
pin->_pinNode->setInputNodeNum(-1);
// Not really necessary since it will be deleted
node->removeOutputConnection(pin->_pinNode->getName());
}
}
// Remove from NodeGraph
// All link information is handled in delete link which is called before this
int nodeNum = findNode(node->getId());
_currGraphElem->removeChild(node->getName());
_graphNodes.erase(_graphNodes.begin() + nodeNum);
}
void Graph::addNodeGraphPins()
{
for (UiNodePtr node : _graphNodes)
{
if (node->getNodeGraph())
{
if (node->inputPins.size() != node->getNodeGraph()->getInputs().size())
{
for (mx::InputPtr input : node->getNodeGraph()->getInputs())
{
std::string name = input->getName();
auto result = std::find_if(node->inputPins.begin(), node->inputPins.end(), [name](UiPinPtr x)
{
return x->_name == name;
});
if (result == node->inputPins.end())
{
UiPinPtr inPin = std::make_shared<UiPin>(++_graphTotalSize, &*input->getName().begin(), input->getType(), node, ax::NodeEditor::PinKind::Input, input, nullptr);
node->inputPins.push_back(inPin);
_currPins.push_back(inPin);
++_graphTotalSize;
}
}
}
if (node->outputPins.size() != node->getNodeGraph()->getOutputs().size())
{
for (mx::OutputPtr output : node->getNodeGraph()->getOutputs())
{
std::string name = output->getName();
auto result = std::find_if(node->outputPins.begin(), node->outputPins.end(), [name](UiPinPtr x)
{
return x->_name == name;
});
if (result == node->outputPins.end())
{
UiPinPtr outPin = std::make_shared<UiPin>(++_graphTotalSize, &*output->getName().begin(), output->getType(), node, ax::NodeEditor::PinKind::Output, nullptr, nullptr);
++_graphTotalSize;
node->outputPins.push_back(outPin);
_currPins.push_back(outPin);
}
}
}
}
}
}
void Graph::upNodeGraph()
{
if (!_graphStack.empty())
{
savePosition();
_graphNodes = _graphStack.top();
_currPins = _pinStack.top();
_graphTotalSize = _sizeStack.top();
addNodeGraphPins();
_graphStack.pop();
_pinStack.pop();
_sizeStack.pop();
_currGraphName.pop_back();
_initial = true;
ed::NavigateToContent();
if (_currUiNode)
{
ed::DeselectNode(_currUiNode->getId());
_currUiNode = nullptr;
}
_prevUiNode = nullptr;
_isNodeGraph = false;
_currGraphElem = _graphDoc;
_initial = true;
}
}
void Graph::clearGraph()
{
_graphNodes.clear();
_currLinks.clear();
_currEdge.clear();
_newLinks.clear();
_currPins.clear();
_graphDoc = mx::createDocument();
_graphDoc->setDataLibrary(_stdLib);
_currGraphElem = _graphDoc;
if (_currUiNode != nullptr)
{
ed::DeselectNode(_currUiNode->getId());
_currUiNode = nullptr;
}
_prevUiNode = nullptr;
_currRenderNode = nullptr;
_isNodeGraph = false;
_currGraphName.clear();
_renderer->setDocument(_graphDoc);
_renderer->updateMaterials(nullptr);
}
void Graph::loadGraphFromFile(bool prompt)
{
// Deselect node before loading new file
if (_currUiNode)
{
ed::DeselectNode(_currUiNode->getId());
_currUiNode = nullptr;
}
if (prompt || _materialFilename.isEmpty())
{
_fileDialog.setTitle("Open File");
_fileDialog.setTypeFilters(_mtlxFilter);
_fileDialog.open();
}
else
{
_graphDoc = loadDocument(_materialFilename);
// Rebuild the UI
_initial = true;
buildUiBaseGraph(_graphDoc);
_currGraphElem = _graphDoc;
_prevUiNode = nullptr;
_renderer->setDocument(_graphDoc);
_renderer->updateMaterials(nullptr);
}
}
void Graph::saveGraphToFile()
{
_fileDialogSave.setTypeFilters(_mtlxFilter);
_fileDialogSave.setTitle("Save File As");
_fileDialogSave.open();
}
void Graph::loadGeometry()
{
_fileDialogGeom.setTitle("Load Geometry");
_fileDialogGeom.setTypeFilters(_geomFilter);
_fileDialogGeom.open();
}
void Graph::graphButtons()
{
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.15f, .15f, .15f, 1.0f));
ImGui::SetWindowFontScale(_fontScale);
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("File"))
{
// Buttons for loading and saving a .mtlx
if (ImGui::MenuItem("New", "Ctrl-N"))
{
clearGraph();
}
else if (ImGui::MenuItem("Open", "Ctrl-O"))
{
loadGraphFromFile(true);
}
else if (ImGui::MenuItem("Reload", "Ctrl-R"))
{
loadGraphFromFile(false);
}
else if (ImGui::MenuItem("Save", "Ctrl-S"))
{
saveGraphToFile();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Graph"))
{
if (ImGui::MenuItem("Auto Layout"))
{
_autoLayout = true;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Viewer"))
{
if (ImGui::MenuItem("Load Geometry"))
{
loadGeometry();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Options"))
{
ImGui::Checkbox("Save Node Positions", &_saveNodePositions);
ImGui::EndMenu();
}
if (ImGui::Button("Help"))
{
ImGui::OpenPopup("Help");
}
if (ImGui::BeginPopup("Help"))
{
showHelp();
ImGui::EndPopup();
}
ImGui::EndMenuBar();
}
// Menu keys
ImGuiIO& guiIO = ImGui::GetIO();
if (guiIO.KeyCtrl && !_fileDialogSave.isOpened() && !_fileDialog.isOpened() && !_fileDialogGeom.isOpened())
{
if (ImGui::IsKeyReleased(ImGuiKey_O))
{
loadGraphFromFile(true);
}
else if (ImGui::IsKeyReleased(ImGuiKey_N))
{
clearGraph();
}
else if (ImGui::IsKeyReleased(ImGuiKey_R))
{
loadGraphFromFile(false);
}
else if (ImGui::IsKeyReleased(ImGuiKey_S))
{
saveGraphToFile();
}
}
// Split window into panes for NodeEditor
static float leftPaneWidth = 375.0f;
static float rightPaneWidth = 750.0f;
splitter(true, 4.0f, &leftPaneWidth, &rightPaneWidth, 20.0f, 20.0f);
// Create back button and graph hierarchy name display
ImGui::Indent(leftPaneWidth + 15.f);
if (ImGui::Button("<"))
{
upNodeGraph();
}
ImGui::SameLine();
if (!_currGraphName.empty())
{
for (std::string name : _currGraphName)
{
ImGui::Text("%s", name.c_str());
ImGui::SameLine();
if (name != _currGraphName.back())
{
ImGui::Text(">");
ImGui::SameLine();
}
}
}
ImVec2 windowPos2 = ImGui::GetWindowPos();
ImGui::Unindent(leftPaneWidth + 15.f);
ImGui::PopStyleColor();
ImGui::NewLine();
// Create two windows using splitter
float paneWidth = (leftPaneWidth - 2.0f);
float aspectRatio = _renderer->getPixelRatio();
ImVec2 screenSize = ImVec2(paneWidth, paneWidth / aspectRatio);
ImVec2 mousePos = ImGui::GetMousePos();
ImVec2 tempWindowPos = ImGui::GetCursorPos();
bool cursorInRenderView = mousePos.x > tempWindowPos.x && mousePos.x < (tempWindowPos.x + screenSize.x) &&
mousePos.y > tempWindowPos.y && mousePos.y < (tempWindowPos.y + screenSize.y);
ImGuiWindowFlags windowFlags = 0;
if (cursorInRenderView)
{
windowFlags |= ImGuiWindowFlags_NoScrollWithMouse;
}
ImGui::BeginChild("Selection", ImVec2(paneWidth, 0), false, windowFlags);
ImVec2 windowPos = ImGui::GetWindowPos();
// Update cursorInRenderView to account for other windows overlapping the Render View (e.g. Menu dropdown).
cursorInRenderView &= ImGui::IsWindowHovered(ImGuiHoveredFlags_None);
// Update cursorInRenderView to account for visible scrollbar and scroll amount.
ImGuiContext* context = ImGui::GetCurrentContext();
bool hasScrollbar = context->CurrentWindow->ScrollbarY;
cursorInRenderView &= hasScrollbar ? mousePos.x < (tempWindowPos.x + screenSize.x - ImGui::GetStyle().ScrollbarSize) : true;
cursorInRenderView &= hasScrollbar ? mousePos.y < (tempWindowPos.y + screenSize.y - ImGui::GetScrollY()) : true;
// RenderView window
ImVec2 wsize = ImVec2((float) _renderer->getViewWidth(), (float) _renderer->getViewHeight());
_renderer->setViewWidth((int) screenSize[0]);
_renderer->setViewHeight((int) screenSize[1]);
if (_renderer)
{
glEnable(GL_FRAMEBUFFER_SRGB);
_renderer->getViewCamera()->setViewportSize(mx::Vector2(screenSize[0], screenSize[1]));
GLuint64 my_image_texture = _renderer->_textureID;
mx::Vector2 vec = _renderer->getViewCamera()->getViewportSize();
// current image has correct color space but causes problems for gui
ImGui::Image((ImTextureID) my_image_texture, screenSize, ImVec2(0, 1), ImVec2(1, 0));
}
ImGui::Separator();
// Property editor for current nodes
propertyEditor();
ImGui::EndChild();
ImGui::SameLine(0.0f, 12.0f);
if (cursorInRenderView)
{
handleRenderViewInputs();
}
}
void Graph::propertyEditor()
{
// Get parent dimensions
ImVec2 textPos = ImGui::GetCursorScreenPos(); // Position for the background
float parentWidth = ImGui::GetContentRegionAvail().x; // Available width in the parent
// Draw the title bar
const ImGuiStyle& style = ImGui::GetStyle();
ImVec4 menuBarBgColor = style.Colors[ImGuiCol_MenuBarBg];
ImU32 bgColor = ImGui::ColorConvertFloat4ToU32(menuBarBgColor); // Convert to 32-bit color
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(textPos,
ImVec2(textPos.x + parentWidth, textPos.y + ImGui::GetTextLineHeight()),
bgColor);
ImGui::Text("Node Property Editor");
if (_currUiNode)
{
// Set and edit name
ImGui::Text("Name: ");
ImGui::SameLine();
std::string original = _currUiNode->getName();
std::string temp = original;
float availableWidth = ImGui::GetContentRegionAvail().x;
ImGui::PushItemWidth(availableWidth);
ImGui::InputText("##edit", &temp);
ImGui::PopItemWidth();
std::string docString = "NodeDef Doc String: \n";
if (_currUiNode->getNode())
{
if (temp != original)
{
std::string name = _currUiNode->getNode()->getParent()->createValidChildName(temp);
std::vector<UiNodePtr> downstreamNodes = _currUiNode->getOutputConnections();
for (UiNodePtr uiNode : downstreamNodes)
{
if (!uiNode->getInput() && uiNode->getNode())
{
for (mx::InputPtr input : uiNode->getNode()->getActiveInputs())
{
if (input->getConnectedNode() == _currUiNode->getNode())
{
_currUiNode->getNode()->setName(name);
uiNode->getNode()->setConnectedNode(input->getName(), _currUiNode->getNode());
}
}
}
}
_currUiNode->setName(name);
_currUiNode->getNode()->setName(name);
}
}
else if (_currUiNode->getInput())
{
if (temp != original)
{
std::string name = _currUiNode->getInput()->getParent()->createValidChildName(temp);
std::vector<UiNodePtr> downstreamNodes = _currUiNode->getOutputConnections();
for (UiNodePtr uiNode : downstreamNodes)
{
if (uiNode->getInput() == nullptr)
{
if (uiNode->getNode())
{
for (mx::InputPtr input : uiNode->getNode()->getActiveInputs())
{
if (input->getInterfaceInput() == _currUiNode->getInput())
{
_currUiNode->getInput()->setName(name);
mx::ValuePtr val = _currUiNode->getInput()->getValue();
input->setConnectedInterfaceName(name);
mx::InputPtr pt = input->getInterfaceInput();
}
}
}
else
{
uiNode->getOutput()->setConnectedNode(_currUiNode->getNode());
}
}
}
_currUiNode->getInput()->setName(name);
_currUiNode->setName(name);
}
}
else if (_currUiNode->getOutput())
{
if (temp != original)
{
std::string name = _currUiNode->getOutput()->getParent()->createValidChildName(temp);
_currUiNode->getOutput()->setName(name);
_currUiNode->setName(name);
}
}
else if (_currUiNode->getCategory() == "group")
{
_currUiNode->setName(temp);
}
else if (_currUiNode->getCategory() == "nodegraph")
{
if (temp != original)
{
std::string name = _currUiNode->getNodeGraph()->getParent()->createValidChildName(temp);
_currUiNode->getNodeGraph()->setName(name);
_currUiNode->setName(name);
for (UiNodePtr node : _graphNodes)
{
if (!node->getInput())
{
std::vector<UiPinPtr> inputs = node->inputPins;
for (size_t i = 0; i < inputs.size(); i++)
{
const std::string& inputName = inputs[i]->_name;
UiNodePtr inputNode = node->getConnectedNode(inputName);
if (inputNode && inputNode->getName() == name && node->getNode())
{
node->getNode()->getInput(inputName)->setAttribute("nodegraph", name);
}
}
}
}
}
}
const float TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing() * 1.3f;
const int SCROLL_LINE_COUNT = 20;
ImGuiTableFlags tableFlags = ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings |
ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_NoBordersInBody;
ImGui::Text("Category:");
ImGui::SameLine();
// Change button color to match background
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.096f, .096f, .096f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.1f, .1f, .1f, 1.0f));
if (_currUiNode->getNode())
{
ImGui::NextColumn();
ImGui::Text("%s", _currUiNode->getNode()->getCategory().c_str());
docString += _currUiNode->getNode()->getCategory();
if (_currUiNode->getNode()->getNodeDef())
{
docString += ":";
docString += _currUiNode->getNode()->getNodeDef()->getDocString() + "\n";
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::SetTooltip("%s", _currUiNode->getNode()->getNodeDef()->getDocString().c_str());
}
ImGui::Checkbox("Show all inputs", &_currUiNode->_showAllInputs);
int count = 0;
for (UiPinPtr input : _currUiNode->inputPins)
{
if (_currUiNode->_showAllInputs || (input->getConnected() || _currUiNode->getNode()->getInput(input->_name)))
{
count++;
}
}
if (count)
{
ImVec2 tableSize(0.0f, TEXT_BASE_HEIGHT * std::min(SCROLL_LINE_COUNT, count));
bool haveTable = ImGui::BeginTable("inputs_node_table", 2, tableFlags, tableSize);
if (haveTable)
{
ImGui::SetWindowFontScale(_fontScale);
for (UiPinPtr input : _currUiNode->inputPins)
{
if (_currUiNode->_showAllInputs || (input->getConnected() || _currUiNode->getNode()->getInput(input->_name)))
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
mx::UIProperties uiProperties;
mx::getUIProperties(input->_input, mx::EMPTY_STRING, uiProperties);
std::string inputLabel = !uiProperties.uiName.empty() ? uiProperties.uiName : input->_input->getName();
mx::OutputPtr out = input->_input->getConnectedOutput();
// Set comment help box
ImGui::PushID(int(input->_pinId.Get()));
ImGui::Text("%s", inputLabel.c_str());
mx::InputPtr tempInt = _currUiNode->getNode()->getNodeDef()->getActiveInput(input->_input->getName());
docString += input->_name;
docString += ": ";
if (tempInt)
{
std::string newStr = _currUiNode->getNode()->getNodeDef()->getActiveInput(input->_input->getName())->getDocString();
if (newStr != mx::EMPTY_STRING)
{
docString += newStr;
}
}
docString += "\t \n";
// Set constant sliders for input values
ImGui::TableNextColumn();
if (!input->getConnected())
{
setConstant(_currUiNode, input->_input, uiProperties);
}
else
{
std::string typeText = " [" + input->_input->getType() + "]";
ImGui::Text("%s", typeText.c_str());
}
ImGui::PopID();
}
}
ImGui::EndTable();
ImGui::SetWindowFontScale(1.0f);
}
}
}
else if (_currUiNode->getInput() != nullptr)
{
ImGui::Text("%s", _currUiNode->getCategory().c_str());
std::vector<UiPinPtr> inputs = _currUiNode->inputPins;
int count = static_cast<int>(inputs.size());
if (count)
{
bool haveTable = ImGui::BeginTable("inputs_input_table", 2, tableFlags,
ImVec2(0.0f, TEXT_BASE_HEIGHT * std::min(SCROLL_LINE_COUNT, count)));
if (haveTable)
{
ImGui::SetWindowFontScale(_fontScale);
for (size_t i = 0; i < inputs.size(); i++)
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
mx::InputPtr mxinput = inputs[i]->_input;
mx::UIProperties uiProperties;
mx::getUIProperties(mxinput, mx::EMPTY_STRING, uiProperties);
std::string inputLabel = !uiProperties.uiName.empty() ? uiProperties.uiName : mxinput->getName();
// Set comment help box
ImGui::PushID(int(inputs[i]->_pinId.Get()));
ImGui::Text("%s", inputLabel.c_str());
ImGui::TableNextColumn();
// Set constant sliders for input values
if (!inputs[i]->getConnected())
{
setConstant(_currUiNode, inputs[i]->_input, uiProperties);
}
else
{
std::string typeText = " [" + inputs[i]->_input->getType() + "]";
ImGui::Text("%s", typeText.c_str());
}
ImGui::PopID();
}
ImGui::EndTable();
ImGui::SetWindowFontScale(1.0f);
}
}
}
else if (_currUiNode->getOutput() != nullptr)
{
ImGui::Text("%s", _currUiNode->getOutput()->getCategory().c_str());
}
else if (_currUiNode->getNodeGraph() != nullptr)
{
std::vector<UiPinPtr> inputs = _currUiNode->inputPins;
ImGui::Text("%s", _currUiNode->getCategory().c_str());
int count = 0;
for (UiPinPtr input : inputs)
{
if (_currUiNode->_showAllInputs || (input->getConnected() || _currUiNode->getNodeGraph()->getInput(input->_name)))
{
count++;
}
}
if (count)
{
bool haveTable = ImGui::BeginTable("inputs_nodegraph_table", 2, tableFlags,
ImVec2(0.0f, TEXT_BASE_HEIGHT * std::min(SCROLL_LINE_COUNT, count)));
if (haveTable)
{
ImGui::SetWindowFontScale(_fontScale);
for (UiPinPtr input : inputs)
{
if (_currUiNode->_showAllInputs || (input->getConnected() || _currUiNode->getNodeGraph()->getInput(input->_name)))
{
ImGui::TableNextRow();
ImGui::TableNextColumn();
mx::InputPtr mxinput = input->_input;
mx::UIProperties uiProperties;
mx::getUIProperties(mxinput, mx::EMPTY_STRING, uiProperties);
std::string inputLabel = !uiProperties.uiName.empty() ? uiProperties.uiName : mxinput->getName();
// Set comment help box
ImGui::PushID(int(input->_pinId.Get()));
ImGui::Text("%s", inputLabel.c_str());
docString += _currUiNode->getNodeGraph()->getActiveInput(input->_input->getName())->getDocString();
ImGui::TableNextColumn();
if (!input->_input->getConnectedNode() && _currUiNode->getNodeGraph()->getActiveInput(input->_input->getName()))
{
setConstant(_currUiNode, input->_input, uiProperties);
}
else
{
std::string typeText = " [" + input->_input->getType() + "]";
ImGui::Text("%s", typeText.c_str());
}
ImGui::PopID();
}
}
ImGui::EndTable();
ImGui::SetWindowFontScale(1.0f);
}
}
ImGui::Checkbox("Show all inputs", &_currUiNode->_showAllInputs);
}
ImGui::PopStyleColor();
ImGui::PopStyleColor();
if (ImGui::Button("Node Info"))
{
ImGui::OpenPopup("docstring");
}
if (ImGui::BeginPopup("docstring"))
{
ImGui::SetWindowFontScale(_fontScale);
ImGui::Text("%s", docString.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::EndPopup();
}
}
}
void Graph::showHelp() const
{
ImGui::Text("MATERIALX GRAPH EDITOR HELP");
if (ImGui::CollapsingHeader("Graph"))
{
if (ImGui::TreeNode("Navigation"))
{
ImGui::BulletText("F : Frame selected nodes in graph.");
ImGui::BulletText("RIGHT MOUSE button to pan.");
ImGui::BulletText("SCROLL WHEEL to zoom.");
ImGui::BulletText("\"<\" BUTTON to view parent of current graph");
ImGui::TreePop();
}
if (ImGui::TreeNode("Editing"))
{
ImGui::BulletText("TAB : Show popup menu to add new nodes.");
ImGui::BulletText("CTRL-C : Copy selected nodes to clipboard.");
ImGui::BulletText("CTRL-V : Paste clipboard to graph.");
ImGui::BulletText("CTRL-F : Find a node by name.");
ImGui::BulletText("CTRL-X : Delete selected nodes and add to clipboard.");
ImGui::BulletText("DELETE : Delete selected nodes or connections.");
ImGui::TreePop();
}
}
if (ImGui::CollapsingHeader("Viewer"))
{
ImGui::BulletText("LEFT MOUSE button to tumble.");
ImGui::BulletText("RIGHT MOUSE button to pan.");
ImGui::BulletText("SCROLL WHEEL to zoom.");
ImGui::BulletText("Keypad +/- to zoom in fixed increments");
}
if (ImGui::CollapsingHeader("Property Editor"))
{
ImGui::BulletText("UP/DOWN ARROW to move between inputs.");
ImGui::BulletText("LEFT-MOUSE DRAG to modify values while entry field is in focus.");
ImGui::BulletText("DBL_CLICK or CTRL+CLICK LEFT-MOUSE on entry field to input values.");
ImGui::Separator();
ImGui::BulletText("\"Show all inputs\" Will toggle between showing all inputs and\n only those that have been modified.");
ImGui::BulletText("\"Node Info\" Will toggle showing node information.");
}
}
void Graph::addNodePopup(bool cursor)
{
bool open_AddPopup = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow) && ImGui::IsKeyReleased(ImGuiKey_Tab);
static char input[32]{ "" };
if (open_AddPopup)
{
cursor = true;
ImGui::OpenPopup("add node");
}
if (ImGui::BeginPopup("add node"))
{
ImGui::Text("Add Node");
ImGui::Separator();
if (cursor)
{
ImGui::SetKeyboardFocusHere();
}
ImGui::InputText("##input", input, sizeof(input));
std::string subs(input);
// Input string length
// Filter extra nodes - includes inputs, outputs, groups, and node graphs
const std::string NODEGRAPH_ENTRY = "Node Graph";
// Filter nodedefs and add to menu if matches filter
for (auto node : _nodesToAdd)
{
// Filter out list of nodes
if (subs.size() > 0)
{
ImGui::SetNextWindowSizeConstraints(ImVec2(250.0f, 300.0f), ImVec2(-1.0f, 500.0f));
std::string str(node.getName());
std::string nodeName = node.getName();
// Disallow creating nested nodegraphs
if (_isNodeGraph && node.getGroup() == NODEGRAPH_ENTRY)
{
continue;
}
// Allow spaces to be used to search for node names
std::replace(subs.begin(), subs.end(), ' ', '_');
if (str.find(subs) != std::string::npos)
{
if (ImGui::MenuItem(getUserNodeDefName(nodeName).c_str()) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter)))
{
addNode(node.getCategory(), getUserNodeDefName(nodeName), node.getType());
_addNewNode = true;
memset(input, '\0', sizeof(input));
}
}
}
else
{
ImGui::SetNextWindowSizeConstraints(ImVec2(100, 10), ImVec2(-1, 300));
if (ImGui::BeginMenu(node.getGroup().c_str()))
{
ImGui::SetWindowFontScale(_fontScale);
std::string name = node.getName();
std::string prefix = "ND_";
if (name.compare(0, prefix.size(), prefix) == 0 && name.compare(prefix.size(), std::string::npos, node.getCategory()) == 0)
{
if (ImGui::MenuItem(getUserNodeDefName(name).c_str()) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter)))
{
addNode(node.getCategory(), getUserNodeDefName(name), node.getType());
_addNewNode = true;
}
}
else
{
if (ImGui::BeginMenu(node.getCategory().c_str()))
{
if (ImGui::MenuItem(getUserNodeDefName(name).c_str()) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter)))
{
addNode(node.getCategory(), getUserNodeDefName(name), node.getType());
_addNewNode = true;
}
ImGui::EndMenu();
}
}
ImGui::EndMenu();
}
}
}
ImGui::EndPopup();
open_AddPopup = false;
}
}
void Graph::searchNodePopup(bool cursor)
{
const bool open_search = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && ImGui::IsKeyDown(ImGuiKey_F) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl);
if (open_search)
{
cursor = true;
ImGui::OpenPopup("search");
}
if (ImGui::BeginPopup("search"))
{
ed::NavigateToSelection();
static ImGuiTextFilter filter;
ImGui::Text("Search for Node:");
static char input[16]{ "" };
ImGui::SameLine();
if (cursor)
{
ImGui::SetKeyboardFocusHere();
}
ImGui::InputText("##input", input, sizeof(input));
if (std::string(input).size() > 0)
{
for (UiNodePtr node : _graphNodes)
{
if (node->getName().find(std::string(input)) != std::string::npos)
{
if (ImGui::MenuItem(node->getName().c_str()) || (ImGui::IsItemFocused() && ImGui::IsKeyPressedMap(ImGuiKey_Enter)))
{
_searchNodeId = node->getId();
memset(input, '\0', sizeof(input));
}
}
}
}
ImGui::EndPopup();
}
}
bool Graph::isPinHovered()
{
ed::PinId currentPin = ed::GetHoveredPin();
ed::PinId nullPin = 0;
return currentPin != nullPin;
}
void Graph::addPinPopup()
{
// Add a floating popup to pin when hovered
if (isPinHovered())
{
ed::Suspend();
UiPinPtr pin = getPin(ed::GetHoveredPin());
std::string connected;
std::string value;
if (pin->_connected)
{
mx::StringVec connectedNames;
for (UiPinPtr connectedPin : pin->getConnections())
{
connectedNames.push_back(connectedPin->_name);
}
connected = "\nConnected to " + mx::joinStrings(connectedNames, ", ");
}
else if (pin->_input)
{
value = "\nValue: " + pin->_input->getValueString();
}
const std::string message("Name: " + pin->_name + "\nType: " + pin->_type + value + connected);
ImGui::SetTooltip("%s", message.c_str());
ed::Resume();
}
}
void Graph::readOnlyPopup()
{
if (_popup)
{
ImGui::SetNextWindowSize(ImVec2(200, 100));
ImGui::OpenPopup("Read Only");
_popup = false;
}
if (ImGui::BeginPopup("Read Only"))
{
ImGui::Text("This graph is Read Only");
ImGui::EndPopup();
}
}
void Graph::shaderPopup()
{
if (_renderer->getMaterialCompilation())
{
ImGui::SetNextWindowPos(ImVec2((float) _renderer->getViewWidth() - 135, (float) _renderer->getViewHeight() + 5));
ImGui::SetNextWindowBgAlpha(80.f);
ImGui::OpenPopup("Shaders");
}
if (ImGui::BeginPopup("Shaders"))
{
ImGui::Text("Compiling Shaders");
if (!_renderer->getMaterialCompilation())
{
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void Graph::handleRenderViewInputs()
{
ImVec2 mousePos = ImGui::GetMousePos();
mx::Vector2 mxMousePos = mx::Vector2(mousePos.x, mousePos.y);
float scrollAmt = ImGui::GetIO().MouseWheel;
int button = -1;
bool down = false;
if (ImGui::IsMouseDragging(0) || ImGui::IsMouseDragging(1))
{
_renderer->setMouseMotionEvent(mxMousePos);
}
if (ImGui::IsMouseClicked(0))
{
button = 0;
down = true;
_renderer->setMouseButtonEvent(button, down, mxMousePos);
}
else if (ImGui::IsMouseClicked(1))
{
button = 1;
down = true;
_renderer->setMouseButtonEvent(button, down, mxMousePos);
}
else if (ImGui::IsMouseReleased(0))
{
button = 0;
_renderer->setMouseButtonEvent(button, down, mxMousePos);
}
else if (ImGui::IsMouseReleased(1))
{
button = 1;
_renderer->setMouseButtonEvent(button, down, mxMousePos);
}
else if (ImGui::IsKeyPressed(ImGuiKey_KeypadAdd))
{
_renderer->setKeyEvent(ImGuiKey_KeypadAdd);
}
else if (ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract))
{
_renderer->setKeyEvent(ImGuiKey_KeypadSubtract);
}
// Scrolling not possible if open or save file dialog is open
if (scrollAmt != 0 && !_fileDialogSave.isOpened() && !_fileDialog.isOpened() && !_fileDialogGeom.isOpened())
{
_renderer->setScrollEvent(scrollAmt);
}
}
void Graph::drawGraph(ImVec2 mousePos)
{
if (_searchNodeId > 0)
{
ed::SelectNode(_searchNodeId);
ed::NavigateToSelection();
_searchNodeId = -1;
}
bool TextCursor = false;
// Center imgui window and set size
ImGuiIO& io2 = ImGui::GetIO();
ImGui::SetNextWindowSize(io2.DisplaySize);
ImGui::SetNextWindowPos(ImVec2(io2.DisplaySize.x * 0.5f, io2.DisplaySize.y * 0.5f), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::Begin("MaterialX", nullptr, ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings);
io2.ConfigFlags = ImGuiConfigFlags_IsSRGB | ImGuiConfigFlags_NavEnableKeyboard;
io2.MouseDoubleClickTime = .5;
graphButtons();
ed::Begin("My Editor");
{
ed::Suspend();
// Set up popups for adding a node when tab is pressed
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f));
ImGui::SetNextWindowSizeConstraints(ImVec2(250.0f, 300.0f), ImVec2(-1.0f, 500.0f));
addNodePopup(TextCursor);
searchNodePopup(TextCursor);
addPinPopup();
readOnlyPopup();
ImGui::PopStyleVar();
ed::Resume();
// Gather selected nodes / links - from ImGui Node Editor blueprints-example.cpp
std::vector<ed::NodeId> selectedNodes;
std::vector<ed::LinkId> selectedLinks;
selectedNodes.resize(ed::GetSelectedObjectCount());
selectedLinks.resize(ed::GetSelectedObjectCount());
int nodeCount = ed::GetSelectedNodes(selectedNodes.data(), static_cast<int>(selectedNodes.size()));
int linkCount = ed::GetSelectedLinks(selectedLinks.data(), static_cast<int>(selectedLinks.size()));
selectedNodes.resize(nodeCount);
selectedLinks.resize(linkCount);
if (io2.KeyCtrl && io2.MouseDown[0])
{
_ctrlClick = true;
}
// Set current node based off of selected node
if (selectedNodes.size() > 0)
{
int graphPos = findNode(int(selectedNodes[0].Get()));
if (graphPos > -1)
{
// Only selected if its not the same as previously selected
if (!_prevUiNode || (_prevUiNode->getName() != _graphNodes[graphPos]->getName()))
{
_currUiNode = _graphNodes[graphPos];
// Update render material if needed
if (_currUiNode->getNode())
{
setRenderMaterial(_currUiNode);
}
else if (_currUiNode->getNodeGraph() || _currUiNode->getOutput())
{
setRenderMaterial(_currUiNode);
}
_prevUiNode = _currUiNode;
}
}
}
// Check if keyboard shortcuts for copy/cut/paste have been used
if (ed::BeginShortcut())
{
if (ed::AcceptCopy())
{
_copiedNodes.clear();
for (ed::NodeId selected : selectedNodes)
{
int pos = findNode((int) selected.Get());
if (pos >= 0)
{
_copiedNodes.insert(std::pair<UiNodePtr, UiNodePtr>(_graphNodes[pos], nullptr));
}
}
}
else if (ed::AcceptCut())
{
if (!readOnly())
{
_copiedNodes.clear();
// Same as copy but remove from graphNodes
for (ed::NodeId selected : selectedNodes)
{
int pos = findNode((int) selected.Get());
if (pos >= 0)
{
_copiedNodes.insert(std::pair<UiNodePtr, UiNodePtr>(_graphNodes[pos], nullptr));
}
}
_isCut = true;
}
else
{
_popup = true;
}
}
else if (ed::AcceptPaste())
{
if (!readOnly())
{
for (auto iter = _copiedNodes.begin(); iter != _copiedNodes.end(); ++iter)
{
copyUiNode(iter->first);
_addNewNode = true;
}
}
else
{
_popup = true;
}
}
}
// Set y-position of first node
std::vector<int> outputNum = createNodes(_isNodeGraph);
// Address copy information if applicable and relink graph if a new node has been added
if (_addNewNode)
{
copyInputs();
linkGraph();
ImVec2 canvasPos = ed::ScreenToCanvas(mousePos);
// Place the copied nodes or the individual new nodes
if (!_copiedNodes.empty())
{
positionPasteBin(canvasPos);
}
else if (!_graphNodes.empty())
{
ed::SetNodePosition(_graphNodes.back()->getId(), canvasPos);
}
_copiedNodes.clear();
_addNewNode = false;
}
// Layout and link graph during the initial call of drawGraph
if (_initial || _autoLayout)
{
_currLinks.clear();
float y = 0.f;
_levelMap = std::unordered_map<int, std::vector<UiNodePtr>>();
// Start layout with output or material nodes since layout algorithm works right to left
for (int outN : outputNum)
{
layoutPosition(_graphNodes[outN], ImVec2(1200.f, y), true, 0);
y += 350;
}
// If there are no output or material nodes but the nodes have position layout each individual node
if (_graphNodes.size() > 0)
{
if (outputNum.size() == 0 && _graphNodes[0]->getElement())
{
for (UiNodePtr node : _graphNodes)
{
layoutPosition(node, ImVec2(0, 0), true, 0);
}
}
}
linkGraph();
findYSpacing(0.f);
layoutInputs();
// Automatically frame node graph upon loading
ed::NavigateToContent();
}
if (_delete)
{
linkGraph();
_delete = false;
}
connectLinks();
// Set to false after initial layout so that nodes can be moved
_initial = false;
_autoLayout = false;
// Start the session with content centered
if (ImGui::GetFrameCount() == 2)
{
ed::NavigateToContent(0.0f);
}
// Delete selected nodes and their links if delete key is pressed
// or if the shortcut for cut is used
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootWindow))
{
if (ImGui::IsKeyReleased(ImGuiKey_Delete) || _isCut)
{
if (selectedNodes.size() > 0)
{
_frameCount = ImGui::GetFrameCount();
_renderer->setMaterialCompilation(true);
for (ed::NodeId id : selectedNodes)
{
if (int(id.Get()) > 0)
{
int pos = findNode(int(id.Get()));
if (pos >= 0 && !readOnly())
{
deleteNode(_graphNodes[pos]);
_delete = true;
ed::DeselectNode(id);
ed::DeleteNode(id);
_currUiNode = nullptr;
}
else if (readOnly())
{
_popup = true;
}
}
}
linkGraph();
}
_isCut = false;
}
// Hotkey to frame selected node(s)
if (ImGui::IsKeyReleased(ImGuiKey_F) && !_fileDialogSave.isOpened())
{
ed::NavigateToSelection();
}
// Go back up from inside a subgraph
if (ImGui::IsKeyReleased(ImGuiKey_U) && (!ImGui::IsPopupOpen("add node")) && (!ImGui::IsPopupOpen("search")) && !_fileDialogSave.isOpened())
{
upNodeGraph();
}
}
// Add new link
if (ed::BeginCreate())
{
ed::PinId startPinId, endPinId, filterPinId;
if (ed::QueryNewLink(&startPinId, &endPinId))
{
if (!readOnly())
{
addLink(startPinId, endPinId);
}
else
{
_popup = true;
}
}
if (ed::QueryNewNode(&filterPinId))
{
if (getPin(filterPinId)->_type != "null")
{
_pinFilterType = getPin(filterPinId)->_type;
}
}
}
else
{
_pinFilterType = mx::EMPTY_STRING;
}
ed::EndCreate();
// Delete link
if (ed::BeginDelete())
{
ed::LinkId deletedLinkId;
while (ed::QueryDeletedLink(&deletedLinkId))
{
if (!readOnly())
{
deleteLink(deletedLinkId);
}
else
{
_popup = true;
}
}
}
ed::EndDelete();
}
// Dive into a node that has a subgraph
ed::NodeId clickedNode = ed::GetDoubleClickedNode();
if (clickedNode.Get() > 0)
{
if (_currUiNode != nullptr)
{
if (_currUiNode->getNode() != nullptr)
{
mx::InterfaceElementPtr impl = _currUiNode->getNode()->getImplementation();
// Only dive if current node is a node graph
if (impl && impl->isA<mx::NodeGraph>())
{
savePosition();
_graphStack.push(_graphNodes);
_pinStack.push(_currPins);
_sizeStack.push(_graphTotalSize);
mx::NodeGraphPtr implGraph = impl->asA<mx::NodeGraph>();
_initial = true;
_graphNodes.clear();
ed::DeselectNode(_currUiNode->getId());
_currUiNode = nullptr;
_currGraphElem = implGraph;
if (readOnly())
{
std::string graphName = implGraph->getName() + " (Read Only)";
_currGraphName.push_back(graphName);
_popup = true;
}
else
{
_currGraphName.push_back(implGraph->getName());
}
buildUiNodeGraph(implGraph);
ed::NavigateToContent();
}
}
else if (_currUiNode->getNodeGraph() != nullptr)
{
savePosition();
_graphStack.push(_graphNodes);
_pinStack.push(_currPins);
_sizeStack.push(_graphTotalSize);
mx::NodeGraphPtr implGraph = _currUiNode->getNodeGraph();
_initial = true;
_graphNodes.clear();
_isNodeGraph = true;
setRenderMaterial(_currUiNode);
ed::DeselectNode(_currUiNode->getId());
_currUiNode = nullptr;
_currGraphElem = implGraph;
if (readOnly())
{
std::string graphName = implGraph->getName() + " (Read Only)";
_currGraphName.push_back(graphName);
_popup = true;
}
else
{
_currGraphName.push_back(implGraph->getName());
}
buildUiNodeGraph(implGraph);
ed::NavigateToContent();
}
}
}
shaderPopup();
if (ImGui::GetFrameCount() == (_frameCount + 2))
{
updateMaterials();
_renderer->setMaterialCompilation(false);
}
ed::Suspend();
_fileDialogSave.display();
// Save file
if (_fileDialogSave.hasSelected())
{
std::string message;
if (!_graphDoc->validate(&message))
{
std::cerr << "*** Validation warnings for " << _materialFilename.getBaseName() << " ***" << std::endl;
std::cerr << message;
}
_materialFilename = _fileDialogSave.getSelected();
ed::Resume();
savePosition();
saveDocument(_materialFilename);
_fileDialogSave.clearSelected();
}
else
{
ed::Resume();
}
ed::End();
ImGui::End();
_fileDialog.display();
// Create and load document from selected file
if (_fileDialog.hasSelected())
{
mx::FilePath fileName = _fileDialog.getSelected();
_currGraphName.clear();
std::string graphName = fileName.getBaseName();
_currGraphName.push_back(graphName.substr(0, graphName.length() - 5));
_graphDoc = loadDocument(fileName);
_initial = true;
buildUiBaseGraph(_graphDoc);
_currGraphElem = _graphDoc;
_prevUiNode = nullptr;
_fileDialog.clearSelected();
_renderer->setDocument(_graphDoc);
_renderer->updateMaterials(nullptr);
}
_fileDialogGeom.display();
if (_fileDialogGeom.hasSelected())
{
mx::FilePath fileName = _fileDialogGeom.getSelected();
_fileDialogGeom.clearSelected();
_renderer->loadMesh(fileName);
_renderer->updateMaterials(nullptr);
}
_fileDialogImage.display();
}
int Graph::findNode(int nodeId)
{
int count = 0;
for (size_t i = 0; i < _graphNodes.size(); i++)
{
if (_graphNodes[i]->getId() == nodeId)
{
return count;
}
count++;
}
return -1;
}
bool Graph::edgeExists(UiEdge newEdge)
{
if (_currEdge.size() > 0)
{
for (UiEdge edge : _currEdge)
{
if (edge.getDown()->getId() == newEdge.getDown()->getId())
{
if (edge.getUp()->getId() == newEdge.getUp()->getId())
{
if (edge.getInput() == newEdge.getInput())
{
return true;
}
}
}
else if (edge.getUp()->getId() == newEdge.getDown()->getId())
{
if (edge.getDown()->getId() == newEdge.getUp()->getId())
{
if (edge.getInput() == newEdge.getInput())
{
return true;
}
}
}
}
}
else
{
return false;
}
return false;
}
bool Graph::linkExists(Link newLink)
{
for (const auto& link : _currLinks)
{
if (link._startAttr == newLink._startAttr)
{
if (link._endAttr == newLink._endAttr)
{
return true;
}
}
else if (link._startAttr == newLink._endAttr)
{
if (link._endAttr == newLink._startAttr)
{
return true;
}
}
}
return false;
}
void Graph::savePosition()
{
for (UiNodePtr node : _graphNodes)
{
mx::ElementPtr elem = node->getElement();
if (elem)
{
ImVec2 pos = ed::GetNodePosition(node->getId());
pos.x /= DEFAULT_NODE_SIZE.x;
pos.y /= DEFAULT_NODE_SIZE.y;
elem->setAttribute(mx::Element::XPOS_ATTRIBUTE, std::to_string(pos.x));
elem->setAttribute(mx::Element::YPOS_ATTRIBUTE, std::to_string(pos.y));
if (elem->hasAttribute("nodedef"))
{
elem->removeAttribute("nodedef");
}
}
}
}
void Graph::saveDocument(mx::FilePath filePath)
{
if (filePath.getExtension() != mx::MTLX_EXTENSION)
{
filePath.addExtension(mx::MTLX_EXTENSION);
}
mx::DocumentPtr writeDoc = _graphDoc;
// If requested, create a modified version of the document for saving.
if (!_saveNodePositions)
{
writeDoc = _graphDoc->copy();
for (mx::ElementPtr elem : writeDoc->traverseTree())
{
elem->removeAttribute(mx::Element::XPOS_ATTRIBUTE);
elem->removeAttribute(mx::Element::YPOS_ATTRIBUTE);
}
}
mx::XmlWriteOptions writeOptions;
writeOptions.elementPredicate = getElementPredicate();
mx::writeToXmlFile(writeDoc, filePath, &writeOptions);
}