Documentation and Updated LICENSE

This commit is contained in:
Maddie Slockbower 2024-07-24 19:50:40 -04:00
parent 80b428ad14
commit d6a7d43d24
10 changed files with 409 additions and 124 deletions

3
.gitignore vendored
View File

@ -9,4 +9,5 @@ install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
/Build/
Build
.idea

View File

@ -33,6 +33,15 @@ endif()
include_directories(Include)
include_directories(External)
# Configure ImGui
set(IMGUI_BACKEND_SDL2 ON)
set(IMGUI_BACKEND_OPENGL ON)
set(IMGUI_STDLIB ON)
set(IMGUI_FREETYPE ON)
# Add ImGui and any extensions
add_subdirectory(External/imgui-docking)
add_executable(OpenShaderDesigner
Source/Entry.cpp
@ -54,24 +63,42 @@ add_executable(OpenShaderDesigner
# Nodes
Source/Graph/Nodes/Math.cpp
# ImGui
External/imgui-docking/imgui_demo.cpp
External/imgui-docking/imgui_draw.cpp
External/imgui-docking/imgui_tables.cpp
External/imgui-docking/imgui.cpp
External/imgui-docking/imgui_widgets.cpp
External/imgui-docking/backends/imgui_impl_sdl2.cpp
External/imgui-docking/backends/imgui_impl_opengl3.cpp
External/imgui-docking/misc/cpp/imgui_stdlib.cpp
External/imgui-docking/misc/freetype/imgui_freetype.cpp
Include/OpenGL/BufferObject.h
)
target_link_libraries(OpenShaderDesigner PRIVATE
Freetype::Freetype
GLEW::GLEW
OpenGL::GL
${SDL2_LIBRARIES}
)
)
# DOXYGEN ==============================================================================================================
# https://vicrucann.github.io/tutorials/quick-cmake-doxygen/
find_package(Doxygen)
if(DOXYGEN_FOUND)
set(DOXYGEN_CONFIG_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
set(DOXYGEN_CONFIG_OUT ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile)
configure_file(${DOXYGEN_CONFIG_IN} ${DOXYGEN_CONFIG_OUT} @ONLY)
message("Doxygen Build Started.")
if(WIN32)
add_custom_target(doxygen ALL
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_CONFIG_OUT}
COMMAND start firefox "${CMAKE_CURRENT_SOURCE_DIR}/Documentation/html/index.html"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Generating Doxygen Documentation"
VERBATIM)
else()
add_custom_target(doxygen ALL
COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_CONFIG_OUT}
COMMAND firefox "${CMAKE_CURRENT_SOURCE_DIR}/Documentation/html/index.html"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Generating Doxygen Documentation"
VERBATIM)
endif()
else()
message("Doxygen not found.")
endif()

View File

@ -95,8 +95,8 @@
// SDL
// (the multi-viewports feature requires SDL features supported from SDL 2.0.4+. SDL 2.0.5+ is highly recommended)
#include <SDL.h>
#include <SDL_syswm.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif

View File

@ -16,15 +16,36 @@
#ifndef MATH_H
#define MATH_H
#include <glm/vec4.hpp>
#include <Graph/ShaderGraph.h>
#include <Utility/Any.h>
namespace OpenShaderDesigner
namespace OpenShaderDesigner::Nodes::Math
{
inline static constexpr ImColor HeaderColor = ImColor(0x92, 0x16, 0x16);
struct Constant : public Node
{
using ValueType = Any<int, unsigned int, float, glm::vec4>;
Constant(ShaderGraph& graph, ImVec2 pos);
virtual ~Constant() = default;
[[nodiscard]] Node* Copy(ShaderGraph& graph) const override;
void Inspect() override;
ValueType Value;
};
RegisterNode("Math/Constant", Constant);
struct Add : public Node
{
Add(ShaderGraph& graph, ImVec2 pos);
virtual ~Add() = default;
[[nodiscard]] Node* Copy(ShaderGraph& graph) const override;
void Inspect() override;
};
RegisterNode("Math/Add", Add);

View File

@ -23,6 +23,7 @@
#include <unordered_map>
#include <filesystem>
#include <unordered_set>
#include <stack>
#include <Utility/DirectedGraph.h>
#include <Utility/Optional.h>
@ -37,6 +38,26 @@ namespace OpenShaderDesigner
using PinId = uint16_t;
using NodeId = uint32_t;
struct PinPtr
{
struct Hash
{
size_t operator()(const PinPtr& p) const
{
return p.hash();
}
};
NodeId Node;
PinId Pin;
bool Input;
size_t hash() const { return (Input ? 0 : 0x8000000) | static_cast<size_t>(Node) << 32 | static_cast<size_t>(Pin & 0x7FFFFFFF); }
bool operator<(const PinPtr& o) const { return hash() < o.hash(); }
bool operator==(const PinPtr& o) const { return hash() == o.hash(); }
};
struct Pin
{
enum PinType
@ -45,9 +66,8 @@ namespace OpenShaderDesigner
, UINT
, FLOAT
, VECTOR
, TEXTURE
, ANY
, ANY
, COUNT
};
@ -62,10 +82,17 @@ namespace OpenShaderDesigner
, ImColor(0x8C, 0xC0, 0x8C)
, ImColor(0x37, 0x95, 0x85)
, ImColor(0xE3, 0x7D, 0xDC)
, ImColor(0xD2, 0x6E, 0x46)
// , ImColor(0xD2, 0x6E, 0x46)
, ImColor(0xD2, 0xD5, 0xD3)
};
inline const static std::string TypeNames[COUNT] = {
"Int"
, "Unsigned Int"
, "Float"
, "Vector"
};
std::string Name;
PinType Type;
PinDirection Direction;
@ -73,6 +100,7 @@ namespace OpenShaderDesigner
struct Node
{
public:
ImVec2 Position = { 0, 0 };
struct
@ -91,7 +119,7 @@ namespace OpenShaderDesigner
struct
{
ImVec2 Size;
bool Const;
bool Const;
} Info;
Node(
@ -100,8 +128,10 @@ namespace OpenShaderDesigner
, const std::vector<Pin>& inputs, bool dyn_inputs
, const std::vector<Pin>& outputs
, bool constant = false);
~Node() = default;
virtual Node* Copy(ShaderGraph& graph) const = 0;
virtual void Inspect() = 0;
};
class ShaderGraph
@ -109,25 +139,6 @@ namespace OpenShaderDesigner
{
private:
friend Node;
struct PinPtr
{
struct Hash
{
size_t operator()(const PinPtr& p) const
{
return p.hash();
}
};
NodeId Node;
PinId Pin;
bool Input;
size_t hash() const { return (Input ? 0 : 0x8000000) | static_cast<size_t>(Node) << 32 | static_cast<size_t>(Pin & 0x7FFFFFFF); }
bool operator<(const PinPtr& o) const { return hash() < o.hash(); }
bool operator==(const PinPtr& o) const { return hash() == o.hash(); }
};
using Connection = std::pair<const PinPtr, PinPtr>;
using ConnectionMap = std::unordered_multimap<PinPtr, PinPtr, PinPtr::Hash>;
@ -145,6 +156,20 @@ namespace OpenShaderDesigner
ConstructorPtr Constructor;
};
struct GraphState
{
ShaderGraph& Parent;
std::vector<Node*> Nodes;
std::unordered_set<PinId> Erased;
ConnectionMap Connections;
GraphState(ShaderGraph& parent);
GraphState(const GraphState& other);
~GraphState();
GraphState& operator=(const GraphState& other);
};
using ContextMenuHierarchy = DirectedGraph<ContextMenuItem>;
using ContextID = ContextMenuHierarchy::Node;
inline static ContextMenuHierarchy ContextMenu;
@ -178,12 +203,19 @@ namespace OpenShaderDesigner
void Paste(const ImVec2& location);
void EraseSelection();
// History Functionality
void PushState();
void PopState();
// Helper functions
float BezierOffset(const ImVec2& out, const ImVec2& in);
bool AABB(const ImVec2& a0, const ImVec2& a1, const ImVec2& b0, const ImVec2& b1);
ImVec2 GridToScreen(const ImVec2& position);
ImVec2 ScreenToGrid(const ImVec2& position);
ImVec2 SnapToGrid(const ImVec2& position);
Pin& GetPin(const PinPtr& ptr);
public:
ShaderGraph();
@ -194,12 +226,9 @@ namespace OpenShaderDesigner
static void Register(const std::filesystem::path& path, ConstructorPtr constructor);
private:
std::vector<Node*> Nodes;
std::unordered_set<PinId> Erased;
ConnectionMap Connections;
GraphState State;
std::stack<GraphState> History;
struct
{
@ -279,6 +308,22 @@ namespace OpenShaderDesigner
bool Focused;
ImVec2 ContextMenuPosition;
friend class Inspector;
};
class Inspector
: public EditorWindow
{
public:
Inspector();
void DrawWindow() override;
private:
ShaderGraph* Graph;
friend class ShaderGraph;
};
}

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2024 Medusa Slockbower
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -56,8 +56,12 @@ Window::Window(const Configuration& config)
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 16);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 16);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 16);
SDL_GL_SetAttribute(SDL_GL_FLOATBUFFERS, 1);
}
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
Context = SDL_GL_CreateContext(Handle);
if(Context == nullptr)

View File

@ -16,6 +16,7 @@
#include <Core/Console.h>
#include <Editor/EditorSystem.h>
#include <Core/Engine.h>
#include <imgui-docking/imgui_internal.h>
#include <imgui-docking/backends/imgui_impl_sdl2.h>
#include <imgui-docking/backends/imgui_impl_opengl3.h>
@ -117,12 +118,12 @@ void EditorSystem::Initialize()
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("./Assets/Fonts/FiraMono-Regular.ttf", 20.0f);
static ImWchar ranges[] = { 0x1, static_cast<ImWchar>(0x1FFFF), 0 };
static ImFontConfig cfg;
cfg.OversampleH = cfg.OversampleV = 1;
cfg.OversampleH = cfg.OversampleV = 2;
cfg.MergeMode = true;
cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_LoadColor;
io.Fonts->AddFontFromFileTTF("./Assets/Fonts/FiraMono-Regular.ttf", 20.0f);
io.Fonts->AddFontFromFileTTF("./Assets/Fonts/remixicon.ttf", 18.0f, &cfg, ranges);
@ -139,8 +140,8 @@ void EditorSystem::Initialize()
void EditorSystem::Draw()
{
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);

View File

@ -14,13 +14,74 @@
// =====================================================================================================================
#include <Graph/Nodes/Math.h>
#include <imgui-extras/imgui_extras.h>
using namespace OpenShaderDesigner;
using namespace OpenShaderDesigner::Nodes::Math;
Constant::Constant(ShaderGraph& graph, ImVec2 pos)
: Node(
graph, pos
, "Constant", HeaderColor
, { }, false
, { { "Out", Pin::FLOAT, Pin::OUTPUT } }
)
{
}
Node* Constant::Copy(ShaderGraph& graph) const
{
return new Constant(graph, Position);
}
void Constant::Inspect()
{
Pin::PinType& Type = IO.Outputs[0].Type;
if(ImGui::BeginCombo("Type", Pin::TypeNames[Type].c_str()))
{
for(int i = 0; i < Pin::ANY; ++i)
{
Pin::PinType t = static_cast<Pin::PinType>(i);
if(ImGui::Selectable(Pin::TypeNames[t].c_str(), t == Type))
{
Type = t;
}
}
ImGui::EndCombo();
}
glm::vec4& v = Value;
switch(Type)
{
case Pin::INT:
ImGui::InputInt("Value", Value);
break;
case Pin::UINT:
ImGui::InputUInt("Value", Value);
break;
case Pin::FLOAT:
ImGui::InputFloat("Value", Value);
break;
case Pin::VECTOR:
ImGui::ColorEdit4("Value", &v.x);
break;
default:
break;
}
}
Add::Add(ShaderGraph& graph, ImVec2 pos)
: Node(
graph, pos
, "Add", ImColor(0x92, 0x16, 0x16)
, "Add", HeaderColor
, { { "A", Pin::ANY, Pin::INPUT }, { "B", Pin::ANY, Pin::INPUT } }, true
, { { "Out", Pin::ANY, Pin::OUTPUT } }
)
@ -30,3 +91,8 @@ Node* Add::Copy(ShaderGraph& graph) const
{
return new Add(graph, Position);
}
void Add::Inspect()
{
}

View File

@ -19,6 +19,7 @@
#include <Core/Engine.h>
#include <Core/Console.h>
#include <Editor/EditorSystem.h>
#include <glm/common.hpp>
#include <Graph/ShaderGraph.h>
@ -31,6 +32,56 @@ ImColor operator*(const ImColor& c, float f)
return ImVec4(c.Value.x * f, c.Value.y * f, c.Value.z * f, c.Value.w);
}
ShaderGraph::GraphState::GraphState(ShaderGraph& parent)
: Parent(parent)
{
}
ShaderGraph::GraphState::GraphState(const GraphState& other)
: Parent(other.Parent)
, Nodes(other.Nodes.size(), nullptr)
, Connections(other.Connections)
, Erased(other.Erased)
{
NodeId id = 0;
for(const Node* node : other.Nodes)
{
if(node) Nodes[id] = node->Copy(Parent);
++id;
}
}
ShaderGraph::GraphState::~GraphState()
{
for(Node* node : Nodes)
{
if(node) delete node;
}
}
ShaderGraph::GraphState& ShaderGraph::GraphState::operator=(const GraphState& other)
{
for(Node* node : Nodes)
{
if(node) delete node;
}
Nodes.clear();
Nodes.resize(other.Nodes.size(), nullptr);
NodeId id = 0;
for(const Node* node : other.Nodes)
{
if(node) Nodes[id] = node->Copy(Parent);
++id;
}
Connections = other.Connections;
Erased = other.Erased;
return *this;
}
float ShaderGraph::CalculateWidth(Node& node)
{
const float GridSize = Style.FontSize + Style.Grid.Lines.Padding;
@ -50,7 +101,7 @@ float ShaderGraph::CalculateWidth(Node& node)
OutputWidth = glm::max(OutputWidth, HeaderHeight + ImGui::CalcTextSize(pin.Name.c_str()).x);
}
float Width = glm::max(InputWidth, HeaderWidth) + OutputWidth + 1 * HeaderHeight;
float Width = glm::max(InputWidth + OutputWidth, HeaderWidth) + HeaderHeight;
Width += GridSize - std::fmod(1.0f + Style.Grid.Lines.Padding + Width, GridSize);
return Width;
@ -90,6 +141,7 @@ Node::Node(
ShaderGraph::ShaderGraph()
: EditorWindow("\uED46 Shader Graph", 0)
, State(*this)
, Style
{
.Grid
@ -166,6 +218,8 @@ void ShaderGraph::OnOpen()
{
Mouse.Location = ImGui::GetMousePos();
Camera.Scroll = Camera.Zoom = 1.0f;
EditorSystem::Open<Inspector>()->Graph = this;
}
void ShaderGraph::DrawWindow()
@ -175,8 +229,9 @@ void ShaderGraph::DrawWindow()
DrawGrid();
NodeId uid = 0;
for(Node* node : Nodes)
for(Node* node : State.Nodes)
{
if(node == nullptr) { ++uid; continue; }
DrawNode(*node, uid++);
@ -344,26 +399,23 @@ void ShaderGraph::DrawNode(Node& node, NodeId id)
ImDrawList& DrawList = *ImGui::GetWindowDrawList();
// Draw Vars
const float HeaderHeight = Style.FontSize;
const ImVec2 Padding = { Style.Grid.Lines.Padding, Style.Grid.Lines.Padding };
const ImVec2 NodePos = node.Position + Padding;
const ImVec2 NodeRoot = GridToScreen(NodePos);
const ImVec2 NodeEdge = GridToScreen(NodePos + node.Info.Size);
const ImVec2 HeaderEdge = GridToScreen(NodePos + ImVec2(node.Info.Size.x, HeaderHeight));
const ImVec2 HeaderText = GridToScreen(NodePos + ImVec2(Style.Nodes.Rounding, 0) + Padding * 0.5f);
const ImVec2 InputRoot = GridToScreen(NodePos + ImVec2(Style.Nodes.Pins.Padding, HeaderHeight));
const ImVec2 OutputRoot = GridToScreen(NodePos + ImVec2(node.Info.Size.x - HeaderHeight - Style.Nodes.Pins.Padding, HeaderHeight));
const bool HasLock = Mouse.FocusedNode();
const bool NodeHovered = ImGui::IsMouseHoveringRect(NodeRoot, NodeEdge);
const bool HeaderHovered = ImGui::IsMouseHoveringRect(NodeRoot, HeaderEdge);
const ImColor HeaderColor = node.Header.Color * (HeaderHovered || HasLock ? 1.2f : 1.0f) * (HasLock ? 0.8f : 1.0f);
const float Rounding = Style.Nodes.Rounding / Camera.Zoom;
const float PinSpacing = HeaderHeight / Camera.Zoom;
const float HeaderHeight = Style.FontSize;
const ImVec2 Padding = { Style.Grid.Lines.Padding, Style.Grid.Lines.Padding };
const ImVec2 NodePos = node.Position + Padding;
const ImVec2 NodeRoot = GridToScreen(NodePos);
const ImVec2 NodeEdge = GridToScreen(NodePos + node.Info.Size);
const ImVec2 HeaderEdge = GridToScreen(NodePos + ImVec2(node.Info.Size.x, HeaderHeight));
const ImVec2 HeaderText = GridToScreen(NodePos + ImVec2(Style.Nodes.Rounding, 0) + Padding * 0.5f);
const ImVec2 InputRoot = GridToScreen(NodePos + ImVec2(Style.Nodes.Pins.Padding, HeaderHeight));
const ImVec2 OutputRoot = GridToScreen(NodePos + ImVec2(node.Info.Size.x - HeaderHeight - Style.Nodes.Pins.Padding, HeaderHeight));
const bool HasLock = Mouse.FocusedNode();
const bool NodeHovered = ImGui::IsMouseHoveringRect(NodeRoot, NodeEdge);
const bool HeaderHovered = ImGui::IsMouseHoveringRect(NodeRoot, HeaderEdge);
const ImColor HeaderColor = node.Header.Color * (HeaderHovered || HasLock ? 1.2f : 1.0f) * (HasLock ? 0.8f : 1.0f);
const float Rounding = Style.Nodes.Rounding / Camera.Zoom;
const float PinSpacing = HeaderHeight / Camera.Zoom;
const float BorderThickness = Style.Nodes.Border.Thickness / Camera.Zoom;
const float GridSize = (Style.FontSize + Style.Grid.Lines.Padding);
const bool Ctrl = ImGui::IsKeyDown(ImGuiKey_ModCtrl);
const bool Shift = ImGui::IsKeyDown(ImGuiKey_ModShift);
@ -387,7 +439,7 @@ void ShaderGraph::DrawNode(Node& node, NodeId id)
for(NodeId selected : Mouse.Selected)
{
Mouse.Locks.emplace(selected, Mouse.Location - Nodes[selected]->Position);
Mouse.Locks.emplace(selected, Mouse.Location - State.Nodes[selected]->Position);
}
}
}
@ -407,14 +459,16 @@ void ShaderGraph::DrawNode(Node& node, NodeId id)
Mouse.Selected.insert(id);
}
// Begin selection
// Begin Dragging
if(Mouse.FocusedNode() && Mouse.Selected.contains(id) && !(Ctrl || Shift))
{
if(Mouse.LocksDragged == false) PushState();
Mouse.LocksDragged = true;
for(NodeId selected : Mouse.Selected)
{
Mouse.Locks.emplace(selected, Mouse.Location - Nodes[selected]->Position);
Mouse.Locks.emplace(selected, Mouse.Location - State.Nodes[selected]->Position);
}
}
@ -495,7 +549,7 @@ void ShaderGraph::DrawNode(Node& node, NodeId id)
if(Mouse.Locks.contains(id))
{
node.Position = Mouse.Location - Mouse.Locks[id];
node.Position = ImFloor(node.Position / GridSize + ImVec2(0.5f, 0.5f)) * GridSize;
node.Position = SnapToGrid(node.Position);
}
// Content =========================================================================================================
@ -511,7 +565,7 @@ void ShaderGraph::DrawNode(Node& node, NodeId id)
DrawList.AddText(NULL, Style.FontSize / Camera.Zoom, HeaderText, Style.Nodes.Title, node.Header.Title.c_str());
DrawList.PopClipRect();
DrawList.AddLine(InputRoot, HeaderEdge, Style.Nodes.Border.Color, BorderThickness);
DrawList.AddLine(ImVec2(NodeRoot.x, HeaderEdge.y), HeaderEdge, Style.Nodes.Border.Color, BorderThickness);
}
// Border ==========================================================================================================
@ -551,26 +605,45 @@ void ShaderGraph::DrawPin(NodeId node_id, Pin& pin, PinId pin_id, ImVec2 locatio
const ImVec2 PinCenter = PinRoot + Offset;
const float PinPadding = Style.Nodes.Pins.Padding / Camera.Zoom;
const float BorderThickness = Style.Nodes.Pins.BorderThickness / Camera.Zoom;
const float PinRadius = (HeaderHeight - PinPadding - 2.0f * Style.Nodes.Pins.BorderThickness) * 0.5f;
const float PinRadius = (HeaderHeight - PinPadding - 2.0f * BorderThickness) * 0.5f;
const bool Hovered = ImGui::IsMouseHoveringRect(PinRoot, PinEdge);
const bool MouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
const bool MouseClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
// Pin =============================================================================================================
// Input
if(ImGui::IsMouseClicked(ImGuiMouseButton_Left) && Hovered && !Mouse.NewConnection() && !ImGui::IsKeyDown(ImGuiKey_ModAlt))
if(MouseClicked && Hovered && !Mouse.NewConnection() && !ImGui::IsKeyDown(ImGuiKey_ModAlt))
{
StartConnection({ node_id, pin_id, input });
}
// Circle
PinPtr ptr = { node_id, pin_id, input };
auto it = Connections.find(ptr);
if(Mouse.NewConnection() && *Mouse.NewConnection == ptr) DrawList.AddCircleFilled(PinCenter, PinRadius, Pin::Colors[pin.Type]);
else if(!input && it != Connections.end()) DrawList.AddCircleFilled(PinCenter, PinRadius, Pin::Colors[pin.Type] * (Hovered ? 0.8f : 1.0f));
else if(it != Connections.end()) DrawList.AddCircleFilled(PinCenter, PinRadius, Pin::Colors[Nodes[it->second.Node]->IO.Outputs[it->second.Pin].Type] * (Hovered ? 0.8f : 1.0f));
else if(Hovered) DrawList.AddCircleFilled(PinCenter, PinRadius, Pin::Colors[pin.Type]);
else DrawList.AddCircleFilled(PinCenter, PinRadius, Style.Nodes.Pins.Background);
DrawList.AddCircle(PinCenter, PinRadius, Pin::Colors[pin.Type], 0, BorderThickness);
auto it = State.Connections.find(ptr);
const bool Connected = (it != State.Connections.end());
const bool NewConnectionRoot = Mouse.NewConnection() && *Mouse.NewConnection == ptr;
const bool NewConnectionNext = Mouse.NewConnection() && *Mouse.NewConnection != ptr && Hovered;
const bool Pressed = Hovered && MouseDown;
const bool Filled = Hovered || Connected || NewConnectionRoot || NewConnectionNext;
ImColor pinColor = Pin::Colors[pin.Type];
ImColor fillColor = Style.Nodes.Pins.Background;
if(input)
{
if(Connected) pinColor = Pin::Colors[GetPin(it->second).Type];
else if(NewConnectionNext)
{
Pin& Next = GetPin(*Mouse.NewConnection);
if(pin.Type == Next.Type || pin.Type == Pin::ANY) pinColor = Pin::Colors[Next.Type];
}
}
if(Pressed && !NewConnectionNext) pinColor = pinColor * 0.8f;
if(Filled) fillColor = pinColor;
DrawList.AddCircleFilled(PinCenter, PinRadius, fillColor);
DrawList.AddCircle(PinCenter, PinRadius, pinColor, 0, BorderThickness);
// Text
const ImVec2 TextOffset = location + ImVec2((input ? HeaderHeight : -ImGui::CalcTextSize(pin.Name.c_str()).x / Camera.Zoom), 0);
@ -615,7 +688,7 @@ void ShaderGraph::DrawContextMenu()
// Create Nodes
ImVec2 position = ContextMenuPosition;
position = ImFloor(position / GridSize) * GridSize;
position = SnapToGrid(position);
std::stack<ContextID> context; context.push(0);
@ -694,32 +767,39 @@ void ShaderGraph::DrawConnections()
// Connections =============================================================================================================
for(const Connection& connection : Connections)
for(const Connection& connection : State.Connections)
{
DrawConnection(connection.first, connection.second);
}
if(Mouse.NewConnection())
{
const Node& Node = *Nodes[Mouse.NewConnection->Node];
const Node& Node = *State.Nodes[Mouse.NewConnection->Node];
const Pin& Pin = Mouse.NewConnection->Input ? Node.IO.Inputs[Mouse.NewConnection->Pin] : Node.IO.Outputs[Mouse.NewConnection->Pin];
const auto Connection = State.Connections.find(*Mouse.NewConnection);
const bool Connected = Connection != State.Connections.end();
const ImVec2 NodePos = Node.Position + Padding;
const ImVec2 InputRoot = GridToScreen(NodePos + ImVec2(Style.Nodes.Pins.Padding, HeaderHeight));
const ImVec2 OutputRoot = GridToScreen(NodePos + ImVec2(Node.Info.Size.x - HeaderHeight - Style.Nodes.Pins.Padding, HeaderHeight));
const ImVec2 Root = (Mouse.NewConnection->Input ? InputRoot : OutputRoot) + ImVec2(0, HeaderHeight) * (Mouse.NewConnection->Pin + 0.5f);
const ImVec2 A = Root + Offset + ImVec2(Mouse.NewConnection->Input ? -PinRadius : PinRadius, 0);
const ImVec2 D = ImGui::GetMousePos();
const ImVec2 PinLoc = Root + Offset + ImVec2(Mouse.NewConnection->Input ? -PinRadius : PinRadius, 0);
const ImVec2 MouseLoc = ImGui::GetMousePos();
const ImVec2 A = Mouse.NewConnection->Input ? MouseLoc : PinLoc;
const ImVec2 D = Mouse.NewConnection->Input ? PinLoc : MouseLoc;
const float Off = BezierOffset(A, D);
const ImVec2 B = ImVec2(A.x + (Mouse.NewConnection->Input ? -Off : Off), A.y);
const ImVec2 C = ImVec2(D.x + (Mouse.NewConnection->Input ? Off : -Off), D.y);
const ImVec2 B = ImVec2(A.x + Off, A.y);
const ImVec2 C = ImVec2(D.x - Off, D.y);
const ImColor Color = Mouse.NewConnection->Input && Connected
? Pin::Colors[GetPin(Connection->second).Type] : Pin::Colors[Pin.Type];
DrawList.AddBezierCubic(
A, B, C, D
, Pin::Colors[Pin.Type], Style.Nodes.Pins.Connections.Thickness / Camera.Zoom
, Color, Style.Nodes.Pins.Connections.Thickness / Camera.Zoom
);
}
@ -751,12 +831,12 @@ void ShaderGraph::DrawConnection(const PinPtr& a, const PinPtr& b)
const PinPtr& In = a.Input ? a : b;
const PinPtr& Out = a.Input ? b : a;
const Node& OutNode = *Nodes[Out.Node];
const Node& OutNode = *State.Nodes[Out.Node];
const ImVec2 OutSize = OutNode.Info.Size / Camera.Zoom;
const ImVec2 OutPosition = CanvasCenter - Camera.Location + OutNode.Position / Camera.Zoom + Padding;
const ImVec2 OutputLoc = OutPosition + ImVec2(OutSize.x - HeaderHeight - Style.Nodes.Pins.Padding / Camera.Zoom, HeaderHeight);
const Node& InNode = *Nodes[In.Node];
const Node& InNode = *State.Nodes[In.Node];
const ImVec2 InPosition = CanvasCenter - Camera.Location + InNode.Position / Camera.Zoom + Padding;
const ImVec2 InputLoc = InPosition + ImVec2(Style.Nodes.Pins.Padding / Camera.Zoom, HeaderHeight);
@ -794,11 +874,11 @@ void ShaderGraph::CreateConnection(const PinPtr& a, const PinPtr& b)
if(a.Node == b.Node) return;
const PinPtr& In = a.Input ? a : b;
const Node& InNode = *Nodes[In.Node];
const Node& InNode = *State.Nodes[In.Node];
const Pin& InPin = InNode.IO.Inputs[In.Pin];
const PinPtr& Out = a.Input ? b : a;
const Node& OutNode = *Nodes[Out.Node];
const Node& OutNode = *State.Nodes[Out.Node];
const Pin& OutPin = OutNode.IO.Outputs[Out.Pin];
// Make sure valid typing
@ -809,28 +889,28 @@ void ShaderGraph::CreateConnection(const PinPtr& a, const PinPtr& b)
if(b.Input) EraseConnections(b);
// Add New Connections
Connections.emplace(a, b);
Connections.emplace(b, a);
State.Connections.emplace(a, b);
State.Connections.emplace(b, a);
}
void ShaderGraph::EraseConnection(const PinPtr& a, const PinPtr& b)
{
auto range = Connections.equal_range(a);
auto range = State.Connections.equal_range(a);
for(auto it = range.first; it != range.second; ++it)
{
if(it->second == b)
{
Connections.erase(it);
State.Connections.erase(it);
break;
}
}
range = Connections.equal_range(b);
range = State.Connections.equal_range(b);
for(auto it = range.first; it != range.second; ++it)
{
if(it->second == a)
{
Connections.erase(it);
State.Connections.erase(it);
break;
}
}
@ -838,41 +918,41 @@ void ShaderGraph::EraseConnection(const PinPtr& a, const PinPtr& b)
void ShaderGraph::EraseConnections(const PinPtr& a)
{
auto it = Connections.find(a);
while(it != Connections.end())
auto it = State.Connections.find(a);
while(it != State.Connections.end())
{
auto range = Connections.equal_range(it->second);
auto range = State.Connections.equal_range(it->second);
for(auto match = range.first; match != range.second; ++match)
{
if(match->second == a)
{
Connections.erase(match);
State.Connections.erase(match);
break;
}
}
Connections.erase(it);
it = Connections.find(a);
State.Connections.erase(it);
it = State.Connections.find(a);
}
}
NodeId ShaderGraph::AddNode(Node* node)
{
if(Erased.empty())
if(State.Erased.empty())
{
Nodes.push_back(node);
return static_cast<NodeId>(Nodes.size() - 1);
State.Nodes.push_back(node);
return static_cast<NodeId>(State.Nodes.size() - 1);
}
NodeId id = *Erased.begin();
Nodes[id] = node;
Erased.erase(id);
NodeId id = *State.Erased.begin();
State.Nodes[id] = node;
State.Erased.erase(id);
return id;
}
void ShaderGraph::RemoveNode(NodeId id)
{
Node* node = Nodes[id];
Node* node = State.Nodes[id];
if(node->Info.Const) return;
PinId i = 0;
@ -881,9 +961,9 @@ void ShaderGraph::RemoveNode(NodeId id)
i = 0;
for(const auto& pin : node->IO.Outputs) EraseConnections({ id, i++, false });
Erased.insert(id);
State.Erased.insert(id);
delete node;
Nodes[id] = nullptr;
State.Nodes[id] = nullptr;
}
void ShaderGraph::ClearClipboard()
@ -899,7 +979,7 @@ void ShaderGraph::Copy()
// Helper for connections
std::unordered_map<NodeId, NodeId> clipboardTransform;
ImVec2 min = Nodes[*Mouse.Selected.begin()]->Position;
ImVec2 min = State.Nodes[*Mouse.Selected.begin()]->Position;
// Reset Clipboard
ClearClipboard();
@ -908,7 +988,7 @@ void ShaderGraph::Copy()
// Copy nodes
for(auto id : Mouse.Selected)
{
Node* node = Nodes[id];
Node* node = State.Nodes[id];
clipboardTransform[id] = static_cast<NodeId>(Clipboard.Nodes.size());
Clipboard.Nodes.push_back(node->Copy(*this));
min = ImMin(node->Position, min);
@ -921,7 +1001,7 @@ void ShaderGraph::Copy()
}
// Copy connections
for(const Connection& connection : Connections)
for(const Connection& connection : State.Connections)
{
if(!(Mouse.Selected.contains(connection.first.Node) && Mouse.Selected.contains(connection.second.Node))) continue;
@ -939,7 +1019,7 @@ void ShaderGraph::Paste(const ImVec2& location)
// Helper for connections
const float GridSize = (Style.FontSize + Style.Grid.Lines.Padding);
std::unordered_map<NodeId, NodeId> clipboardTransform;
ImVec2 root = ImFloor(location / GridSize + ImVec2(0.5f, 0.5f)) * GridSize;
ImVec2 root = SnapToGrid(location);
Mouse.Selected.clear();
// Paste the nodes
@ -947,7 +1027,7 @@ void ShaderGraph::Paste(const ImVec2& location)
for(Node* node : Clipboard.Nodes)
{
NodeId index = clipboardTransform[id++] = AddNode(node->Copy(*this));
Nodes[index]->Position += root;
State.Nodes[index]->Position += root;
Mouse.Selected.insert(index);
}
@ -970,6 +1050,17 @@ void ShaderGraph::EraseSelection()
Mouse.Selected.clear();
}
void ShaderGraph::PushState()
{
History.push(State);
}
void ShaderGraph::PopState()
{
State = History.top();
History.pop();
}
float ShaderGraph::BezierOffset(const ImVec2& out, const ImVec2& in)
{
const float HeaderHeight = Style.FontSize / Camera.Zoom;
@ -1004,6 +1095,18 @@ ImVec2 ShaderGraph::ScreenToGrid(const ImVec2& position)
return (position - CanvasCenter + Camera.Location) * Camera.Zoom;
}
ImVec2 ShaderGraph::SnapToGrid(const ImVec2& position)
{
const float GridSize = (Style.FontSize + Style.Grid.Lines.Padding);
return ImFloor(position / GridSize) * GridSize;
}
Pin& ShaderGraph::GetPin(const PinPtr &ptr)
{
Node* node = State.Nodes[ptr.Node];
return (ptr.Input ? node->IO.Inputs : node->IO.Outputs)[ptr.Pin];
}
void ShaderGraph::Register(const std::filesystem::path& path, ConstructorPtr constructor)
{
const std::string name = path.filename().string();
@ -1042,3 +1145,20 @@ void ShaderGraph::Register(const std::filesystem::path& path, ConstructorPtr con
ContextMenu.Insert({ name, constructor }, node);
}
Inspector::Inspector()
: EditorWindow("Inspector", 0)
, Graph(nullptr)
{
}
void Inspector::DrawWindow()
{
if(Graph->Mouse.Selected.size() != 1)
{
ImGui::Text("Selected %d nodes.", Graph->Mouse.Selected.size());
return;
}
Graph->State.Nodes[*Graph->Mouse.Selected.begin()]->Inspect();
}