Skip to content

Commit

Permalink
Line rendering improvements; Fixed a bug in ProcessConnections relate…
Browse files Browse the repository at this point in the history
…d to improper connection creation.
  • Loading branch information
Azzinoth committed Aug 12, 2023
1 parent 026aaf8 commit 61f50c3
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 60 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ This library provides a powerful framework for creating and managing visual node

- **Smooth Zoom**: A precise and dynamic zoom functionality, allowing for detailed viewing and efficient navigation.

- **Versatile Node Styles**: This library supports various visual node styles, currently including Default and Circle. You can easily expand this with new styles.
- **Reroute Nodes**: Reroute nodes offer enhanced organization for clearer, more readable visual graphs, enabling flexible customization of connection paths. Also they simplify debugging by making connections traceable. To add reroute node just double click on connection.
<div align="center">
<img src="https://github.com/Azzinoth/VisualNodeSystem/blob/media/Reroute%20Node%20example.png" width="60%">
</div>

- **Flexible Socket Management**: The library offers functionalities for managing node sockets (input/output). These sockets serve as points of data connection between different nodes.

Expand All @@ -30,8 +33,6 @@ This library provides a powerful framework for creating and managing visual node

- **Node Event Callbacks**: The library provides functionalities to set callbacks for node events.

- **Area Properties**: You can get and set various area properties like area position, size, and render offset.

## Usage

For a simple example of how to use library, see the [VisualNodeSystem Example](https://github.com/Azzinoth/VisualNodeSystem-Example).
Expand Down
27 changes: 24 additions & 3 deletions SubSystems/VisualNodeArea/VisualNodeArea.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,12 @@ void VisualNodeArea::ProcessConnections(const std::vector<NodeSocket*>& Sockets,
{
std::unordered_map<VisualNodeRerouteNode*, VisualNodeRerouteNode*> OldToNewRerouteNode;
// Get connection info from old node area.
VisualNodeConnection* OldConnection = SourceArea->GetAllConnections(CurrentSocket, ConnectedSocket);
VisualNodeConnection* OldConnection = SourceArea->GetConnection(CurrentSocket, ConnectedSocket);

TargetArea->Connections.push_back(new VisualNodeConnection(OldToNewSocket[CurrentSocket], OldToNewSocket[ConnectedSocket]));
VisualNodeConnection* NewConnection = TargetArea->Connections.back();
if (!TargetArea->TryToConnect(OldToNewSocket[CurrentSocket]->GetParent(), OldToNewSocket[CurrentSocket]->GetID(), OldToNewSocket[ConnectedSocket]->GetParent(), OldToNewSocket[ConnectedSocket]->GetID()))
continue;

VisualNodeConnection* NewConnection = TargetArea->Connections.back();
// First pass to fill OldToNewRerouteNode map and other information that does not depend on OldToNewRerouteNode map.
for (size_t j = 0; j < OldConnection->RerouteConnections.size(); j++)
{
Expand Down Expand Up @@ -611,4 +612,24 @@ ImVec2 VisualNodeArea::LocalToScreen(ImVec2 LocalPosition) const
ImVec2 VisualNodeArea::ScreenToLocal(ImVec2 ScreenPosition) const
{
return (ScreenPosition - ImGui::GetCurrentWindow()->Pos - RenderOffset) / Zoom;
}

std::vector<ImVec2> VisualNodeArea::GetTangentsForLine(const ImVec2 Begin, const ImVec2 End) const
{
std::vector<ImVec2> Result;
Result.resize(2);

float ScaledXTangentMagnitude = LineXTangentMagnitude * Zoom;
float ScaledYTangentMagnitude = LineYTangentMagnitude * Zoom;

Result[0] = ImVec2(ScaledXTangentMagnitude, ScaledYTangentMagnitude);
Result[1] = ImVec2(ScaledXTangentMagnitude, ScaledYTangentMagnitude);

if (Begin.x >= End.x && Begin.y >= End.y)
{
Result[0] = ImVec2(-ScaledXTangentMagnitude, ScaledYTangentMagnitude);
Result[1] = ImVec2(-ScaledXTangentMagnitude, ScaledYTangentMagnitude);
}

return Result;
}
6 changes: 5 additions & 1 deletion SubSystems/VisualNodeArea/VisualNodeArea.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ class VisualNodeArea
ImVec4 GridLinesColor = ImVec4(53.0f / 255.0f, 53.0f / 255.0f, 53.0f / 255.0f, 0.5f);
ImVec4 GridBoldLinesColor = ImVec4(27.0f / 255.0f, 27.0f / 255.0f, 27.0f / 255.0f, 1.0f);
ImVec2 RenderOffset = ImVec2(0.0, 0.0);
int LineSegments = 16;
float LineXTangentMagnitude = 80.0f * 2.0f;
float LineYTangentMagnitude = 0.0f;
void(*MainContextMenuFunc)() = nullptr;
std::vector<void(*)(VisualNode*, VISUAL_NODE_EVENT)> NodeEventsCallbacks;
std::queue<SocketEvent> SocketEventQueue;
Expand All @@ -152,7 +155,7 @@ class VisualNodeArea
void ProcessSocketEventQueue();
ImVec2 SocketToPosition(const NodeSocket* Socket) const;
std::vector<VisualNodeConnection*> GetAllConnections(const NodeSocket* Socket) const;
VisualNodeConnection* GetAllConnections(const NodeSocket* FirstSocket, const NodeSocket* SecondSocket) const;
VisualNodeConnection* GetConnection(const NodeSocket* FirstSocket, const NodeSocket* SecondSocket) const;

void Delete(VisualNodeConnection* Connection);
void Delete(VisualNodeRerouteNode* RerouteNode);
Expand Down Expand Up @@ -206,6 +209,7 @@ class VisualNodeArea
void RenderNode(VisualNode* Node) const;
void RenderNodeSockets(const VisualNode* Node) const;
void RenderNodeSocket(NodeSocket* Socket) const;
std::vector<ImVec2> GetTangentsForLine(const ImVec2 P1, const ImVec2 P2) const;
void DrawHermiteLine(ImVec2 P1, ImVec2 P2, int Steps, ImColor Color, const VisualNodeConnectionStyle* Style) const;
void DrawHermiteLine(const ImVec2 P1, const ImVec2 P2, const int Steps, const ImColor Color, const float Thickness) const;
void RenderConnection(const VisualNodeConnection* Connection) const;
Expand Down
21 changes: 10 additions & 11 deletions SubSystems/VisualNodeArea/VisualNodeAreaInput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -689,12 +689,12 @@ void VisualNodeArea::InputUpdateReroute(VisualNodeRerouteNode* Reroute)
bool VisualNodeArea::IsMouseOverConnection(VisualNodeConnection* Connection, const int Steps, const float MaxDistance, ImVec2& CollisionPoint)
{
if (Connection->RerouteConnections.empty())
return IsMouseOverSegment(SocketToPosition(Connection->Out), SocketToPosition(Connection->In), 12, 10.0f);
return IsMouseOverSegment(SocketToPosition(Connection->Out), SocketToPosition(Connection->In), LineSegments, 10.0f);

std::vector<VisualNodeConnectionSegment> Segments = GetConnectionSegments(Connection);
for (size_t i = 0; i < Segments.size(); i++)
{
if (IsMouseOverSegment(Segments[i].Begin, Segments[i].End, 12, 10.0f))
if (IsMouseOverSegment(Segments[i].Begin, Segments[i].End, LineSegments, 10.0f))
return true;
}

Expand All @@ -703,8 +703,7 @@ bool VisualNodeArea::IsMouseOverConnection(VisualNodeConnection* Connection, con

bool VisualNodeArea::IsMouseOverSegment(ImVec2 Begin, ImVec2 End, const int Steps, const float maxDistance, ImVec2& CollisionPoint)
{
const ImVec2 t1 = ImVec2(80.0f, 0.0f);
const ImVec2 t2 = ImVec2(80.0f, 0.0f);
std::vector<ImVec2> LineTangents = GetTangentsForLine(Begin, End);

for (int Step = 0; Step <= Steps; Step++)
{
Expand All @@ -714,15 +713,15 @@ bool VisualNodeArea::IsMouseOverSegment(ImVec2 Begin, ImVec2 End, const int Step
float h3 = t * t * t - 2 * t * t + t;
float h4 = t * t * t - t * t;

ImVec2 SegmentStart = ImVec2(h1 * Begin.x + h2 * End.x + h3 * t1.x + h4 * t2.x, h1 * Begin.y + h2 * End.y + h3 * t1.y + h4 * t2.y);
ImVec2 SegmentStart = ImVec2(h1 * Begin.x + h2 * End.x + h3 * LineTangents[0].x + h4 * LineTangents[1].x, h1 * Begin.y + h2 * End.y + h3 * LineTangents[0].y + h4 * LineTangents[1].y);

t = static_cast<float>(Step + 1) / static_cast<float>(Steps); // Update t for the end segment.
h1 = +2 * t * t * t - 3 * t * t + 1.0f;
h2 = -2 * t * t * t + 3 * t * t;
h3 = t * t * t - 2 * t * t + t;
h4 = t * t * t - t * t;

ImVec2 SegmentEnd = ImVec2(h1 * Begin.x + h2 * End.x + h3 * t1.x + h4 * t2.x, h1 * Begin.y + h2 * End.y + h3 * t1.y + h4 * t2.y);
ImVec2 SegmentEnd = ImVec2(h1 * Begin.x + h2 * End.x + h3 * LineTangents[0].x + h4 * LineTangents[1].x, h1 * Begin.y + h2 * End.y + h3 * LineTangents[0].y + h4 * LineTangents[1].y);

// Compute the shortest distance from mousePos to the line defined by the segment.
ImVec2 SegmentDirection = SegmentEnd - SegmentStart;
Expand Down Expand Up @@ -790,12 +789,12 @@ bool IsLineSegmentIntersecting(const ImVec2& p1, const ImVec2& q1, const ImVec2&
bool VisualNodeArea::IsConnectionInRegion(VisualNodeConnection* Connection, const int Steps)
{
if (Connection->RerouteConnections.empty())
return IsSegmentInRegion(SocketToPosition(Connection->Out), SocketToPosition(Connection->In), 12);
return IsSegmentInRegion(SocketToPosition(Connection->Out), SocketToPosition(Connection->In), LineSegments);

std::vector<VisualNodeConnectionSegment> Segments = GetConnectionSegments(Connection);
for (size_t i = 0; i < Segments.size(); i++)
{
if (IsSegmentInRegion(Segments[i].Begin, Segments[i].End, 12))
if (IsSegmentInRegion(Segments[i].Begin, Segments[i].End, LineSegments))
return true;
}

Expand Down Expand Up @@ -887,15 +886,15 @@ void VisualNodeArea::MouseInputUpdateConnections()
{
for (size_t i = 0; i < Connections.size(); i++)
{
if (HoveredConnection == nullptr && IsMouseOverConnection(Connections[i], 12, 10.0f))
if (HoveredConnection == nullptr && IsMouseOverConnection(Connections[i], LineSegments, 10.0f))
{
Connections[i]->bHovered = true;
HoveredConnection = Connections[i];
}

if (IsMouseRegionSelectionActive() && SelectedNodes.empty())
{
if (IsConnectionInRegion(Connections[i], 12))
if (IsConnectionInRegion(Connections[i], LineSegments))
{
Connections[i]->bSelected = true;
AddSelected(Connections[i]);
Expand Down Expand Up @@ -945,7 +944,7 @@ void VisualNodeArea::ConnectionsDoubleMouseClick()
std::vector<VisualNodeConnectionSegment> Segments = GetConnectionSegments(HoveredConnection);
for (size_t i = 0; i < Segments.size(); i++)
{
if (IsMouseOverSegment(Segments[i].Begin, Segments[i].End, 12, 10.0f))
if (IsMouseOverSegment(Segments[i].Begin, Segments[i].End, LineSegments, 10.0f))
{
if (i == 0)
{
Expand Down
2 changes: 1 addition & 1 deletion SubSystems/VisualNodeArea/VisualNodeAreaLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ std::vector<VisualNodeConnection*> VisualNodeArea::GetAllConnections(const NodeS
return Result;
}

VisualNodeConnection* VisualNodeArea::GetAllConnections(const NodeSocket* FirstSocket, const NodeSocket* SecondSocket) const
VisualNodeConnection* VisualNodeArea::GetConnection(const NodeSocket* FirstSocket, const NodeSocket* SecondSocket) const
{
for (size_t i = 0; i < Connections.size(); i++)
{
Expand Down
53 changes: 12 additions & 41 deletions SubSystems/VisualNodeArea/VisualNodeAreaRendering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ void VisualNodeArea::RenderNodeSocket(NodeSocket* Socket) const
ConnectionColor = NodeSocket::SocketTypeToColorAssosiations[SocketLookingForConnection->GetType()];

CurrentDrawList->ChannelsSetCurrent(3);
DrawHermiteLine(SocketPosition, ImGui::GetIO().MousePos, 12, ConnectionColor, &DefaultConnectionStyle);
DrawHermiteLine(SocketPosition, ImGui::GetIO().MousePos, LineSegments, ConnectionColor, &DefaultConnectionStyle);
}

// Draw socket icon.
Expand Down Expand Up @@ -264,33 +264,6 @@ void VisualNodeArea::Render()
for (size_t i = 0; i < Connections.size(); i++)
{
RenderConnection(Connections[i]);

/*if (Connections[i]->bHovered)
{
Connections[i]->Style.ForceColor = DEFAULT_NODE_SOCKET_MOUSE_HOVERED_CONNECTION_COLOR;
}
else
{
Connections[i]->Style.ForceColor = DEFAULT_NODE_SOCKET_CONNECTION_COLOR;
}*/

/*if (MouseSelectRegionMin.x != FLT_MAX && MouseSelectRegionMin.y != FLT_MAX &&
MouseSelectRegionMax.x != FLT_MAX && MouseSelectRegionMax.y != FLT_MAX)
{
if (IsConnectionInRegion(Connections[i], 12))
{
ImColor* DefaultColor = new ImColor(200, 0, 0);
Connections[i]->Out->SetForcedConnectionColor(DefaultColor);
}
else
{
if (Connections[i]->Out->ConnectionStyle.ForceColor != nullptr)
{
delete Connections[i]->Out->ConnectionStyle.ForceColor;
Connections[i]->Out->ConnectionStyle.ForceColor = nullptr;
}
}
}*/
}

// ************************* RENDER CONTEXT MENU *************************
Expand Down Expand Up @@ -331,10 +304,9 @@ void VisualNodeArea::Render()
CurrentStyle = OriginalStyle;
}

void VisualNodeArea::DrawHermiteLine(const ImVec2 P1, const ImVec2 P2, const int Steps, const ImColor Color, const VisualNodeConnectionStyle* Style) const
void VisualNodeArea::DrawHermiteLine(const ImVec2 Begin, const ImVec2 End, const int Steps, const ImColor Color, const VisualNodeConnectionStyle* Style) const
{
const ImVec2 t1 = ImVec2(80.0f, 0.0f);
const ImVec2 t2 = ImVec2(80.0f, 0.0f);
std::vector<ImVec2> LineTangents = GetTangentsForLine(Begin, End);

if (Style->bMarchingAntsEffect)
{
Expand All @@ -350,7 +322,7 @@ void VisualNodeArea::DrawHermiteLine(const ImVec2 P1, const ImVec2 P2, const int
const float h3 = t * t * t - 2 * t * t + t;
const float h4 = t * t * t - t * t;

ImVec2 CurrentPoint = ImVec2(h1 * P1.x + h2 * P2.x + h3 * t1.x + h4 * t2.x, h1 * P1.y + h2 * P2.y + h3 * t1.y + h4 * t2.y);
ImVec2 CurrentPoint = ImVec2(h1 * Begin.x + h2 * End.x + h3 * LineTangents[0].x + h4 * LineTangents[1].x, h1 * Begin.y + h2 * End.y + h3 * LineTangents[0].y + h4 * LineTangents[1].y);

if (Step != 0) // Avoid drawing on the first step because lastPoint is not valid.
{
Expand Down Expand Up @@ -386,7 +358,7 @@ void VisualNodeArea::DrawHermiteLine(const ImVec2 P1, const ImVec2 P2, const int
const float h3 = t * t * t - 2 * t * t + t;
const float h4 = t * t * t - t * t;

CurrentDrawList->PathLineTo(ImVec2(h1 * P1.x + h2 * P2.x + h3 * t1.x + h4 * t2.x, h1 * P1.y + h2 * P2.y + h3 * t1.y + h4 * t2.y));
CurrentDrawList->PathLineTo(ImVec2(h1 * Begin.x + h2 * End.x + h3 * LineTangents[0].x + h4 * LineTangents[1].x, h1 * Begin.y + h2 * End.y + h3 * LineTangents[0].y + h4 * LineTangents[1].y));
}

double Time = glfwGetTime();
Expand All @@ -407,7 +379,7 @@ void VisualNodeArea::DrawHermiteLine(const ImVec2 P1, const ImVec2 P2, const int
const float h3 = t * t * t - 2 * t * t + t;
const float h4 = t * t * t - t * t;

ImVec2 CurrentPoint = ImVec2(h1 * P1.x + h2 * P2.x + h3 * t1.x + h4 * t2.x, h1 * P1.y + h2 * P2.y + h3 * t1.y + h4 * t2.y);
ImVec2 CurrentPoint = ImVec2(h1 * Begin.x + h2 * End.x + h3 * LineTangents[0].x + h4 * LineTangents[1].x, h1 * Begin.y + h2 * End.y + h3 * LineTangents[0].y + h4 * LineTangents[1].y);
CurrentDrawList->PathLineTo(CurrentPoint);

LastPoint = CurrentPoint;
Expand All @@ -417,10 +389,9 @@ void VisualNodeArea::DrawHermiteLine(const ImVec2 P1, const ImVec2 P2, const int
}
}

void VisualNodeArea::DrawHermiteLine(const ImVec2 P1, const ImVec2 P2, const int Steps, const ImColor Color, const float Thickness) const
void VisualNodeArea::DrawHermiteLine(const ImVec2 Begin, const ImVec2 End, const int Steps, const ImColor Color, const float Thickness) const
{
const ImVec2 t1 = ImVec2(80.0f, 0.0f);
const ImVec2 t2 = ImVec2(80.0f, 0.0f);
std::vector<ImVec2> LineTangents = GetTangentsForLine(Begin, End);

ImVec2 LastPoint = ImVec2(0, 0);
for (int Step = 0; Step <= Steps; Step++)
Expand All @@ -431,7 +402,7 @@ void VisualNodeArea::DrawHermiteLine(const ImVec2 P1, const ImVec2 P2, const int
const float h3 = t * t * t - 2 * t * t + t;
const float h4 = t * t * t - t * t;

ImVec2 CurrentPoint = ImVec2(h1 * P1.x + h2 * P2.x + h3 * t1.x + h4 * t2.x, h1 * P1.y + h2 * P2.y + h3 * t1.y + h4 * t2.y);
ImVec2 CurrentPoint = ImVec2(h1 * Begin.x + h2 * End.x + h3 * LineTangents[0].x + h4 * LineTangents[1].x, h1 * Begin.y + h2 * End.y + h3 * LineTangents[0].y + h4 * LineTangents[1].y);
CurrentDrawList->PathLineTo(CurrentPoint);

LastPoint = CurrentPoint;
Expand All @@ -457,14 +428,14 @@ void VisualNodeArea::RenderConnection(const VisualNodeConnection* Connection) co

if (Connection->bSelected)
{
DrawHermiteLine(BeginPosition, EndPosition, 12, ImColor(55, 255, 55), GetConnectionThickness() + GetConnectionThickness() * 1.2f);
DrawHermiteLine(BeginPosition, EndPosition, LineSegments, ImColor(55, 255, 55), GetConnectionThickness() + GetConnectionThickness() * 1.2f);
}
else if (Connection->bHovered)
{
DrawHermiteLine(BeginPosition, EndPosition, 12, ImColor(55, 55, 250), GetConnectionThickness() + GetConnectionThickness() * 1.2f);
DrawHermiteLine(BeginPosition, EndPosition, LineSegments, ImColor(55, 55, 250), GetConnectionThickness() + GetConnectionThickness() * 1.2f);
}

DrawHermiteLine(BeginPosition, EndPosition, 12, CurrentConnectionColor, &Connection->Style);
DrawHermiteLine(BeginPosition, EndPosition, LineSegments, CurrentConnectionColor, &Connection->Style);

// If it is reroute than we should render circle.
if (i > 0)
Expand Down

0 comments on commit 61f50c3

Please sign in to comment.