From 07e2fb4c5a8e2186de0f5d217884479882963054 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Fri, 6 Feb 2026 13:24:18 -0700 Subject: [PATCH] Better routing --- gui/SquareConnectionPainter.cpp | 47 +++++++++++++--------- gui/WarpGraphModel.cpp | 70 ++++++++++++++++++++++++++++----- gui/WarpGraphModel.h | 9 +++++ 3 files changed, 99 insertions(+), 27 deletions(-) diff --git a/gui/SquareConnectionPainter.cpp b/gui/SquareConnectionPainter.cpp index 57739e1..5139e3e 100644 --- a/gui/SquareConnectionPainter.cpp +++ b/gui/SquareConnectionPainter.cpp @@ -66,7 +66,17 @@ QPainterPath SquareConnectionPainter::orthogonalPath( constexpr double kNodePad = 15.0; auto const cId = cgo.connectionId(); - double const spread = static_cast(cId.outPortIndex) * kSpacing; + + double spread = static_cast(cId.outPortIndex) * kSpacing; + auto *sceneForChannel = cgo.nodeScene(); + if (sceneForChannel) { + auto *mdl = dynamic_cast(&sceneForChannel->graphModel()); + if (mdl) { + auto ch = mdl->connectionChannel(cId); + spread = (static_cast(ch.index) - (ch.count - 1) / 2.0) + * kSpacing; + } + } double const dy = in.y() - out.y(); @@ -114,51 +124,52 @@ QPainterPath SquareConnectionPainter::orthogonalPath( // out -------+ seg 1 + corner 1 // - double rightX = out.x() + kMinStub + spread; - double leftX = in.x() - kMinStub - spread; - double midY = (out.y() + in.y()) / 2.0; + double railOffset = 0.0; + if (sceneForChannel) { + auto *mdl2 = dynamic_cast(&sceneForChannel->graphModel()); + if (mdl2) { + auto ch = mdl2->connectionChannel(cId); + railOffset = static_cast(ch.index) * kSpacing; + } + } + + double rightX = out.x() + kMinStub + railOffset; + double leftX = in.x() - kMinStub - railOffset; + double midY = (out.y() + in.y()) / 2.0 + spread; - // Use actual node geometry when available so the path routes cleanly - // around both nodes instead of cutting through them. auto *scene = cgo.nodeScene(); if (scene) { auto *outNGO = scene->nodeGraphicsObject(cId.outNodeId); auto *inNGO = scene->nodeGraphicsObject(cId.inNodeId); if (outNGO && inNGO) { - // Map node scene bounds into the CGO's local coordinate space - // (endPoint() values live there too). QRectF const outRect = cgo.mapRectFromScene(outNGO->sceneBoundingRect()); QRectF const inRect = cgo.mapRectFromScene(inNGO->sceneBoundingRect()); - // Push vertical rails outside both nodes. double const rightEdge = std::max(outRect.right(), inRect.right()); - rightX = std::max(rightX, rightEdge + kNodePad + spread); + rightX = std::max(rightX, rightEdge + kNodePad + railOffset); double const leftEdge = std::min(outRect.left(), inRect.left()); - leftX = std::min(leftX, leftEdge - kNodePad - spread); + leftX = std::min(leftX, leftEdge - kNodePad - railOffset); - // Place the horizontal crossover in the gap between nodes when - // they don't overlap vertically; otherwise route above or below. double const topInner = std::min(outRect.bottom(), inRect.bottom()); double const botInner = std::max(outRect.top(), inRect.top()); if (botInner > topInner + 2.0 * kNodePad) { - // Vertical gap exists — clamp midY into the gap. midY = std::clamp(midY, topInner + kNodePad, botInner - kNodePad); } else { - // Nodes overlap vertically — pick the shorter detour. double const above = std::min(outRect.top(), inRect.top()) - kNodePad; double const below = std::max(outRect.bottom(), inRect.bottom()) + kNodePad; - midY = (std::abs(midY - above) < std::abs(midY - below)) - ? above - : below; + double baseMidY = (out.y() + in.y()) / 2.0; + midY = (std::abs(baseMidY - above) < std::abs(baseMidY - below)) + ? above + spread + : below + spread; } } } diff --git a/gui/WarpGraphModel.cpp b/gui/WarpGraphModel.cpp index 682ff9f..f066cbd 100644 --- a/gui/WarpGraphModel.cpp +++ b/gui/WarpGraphModel.cpp @@ -565,22 +565,38 @@ void WarpGraphModel::refreshFromClient() { QtNodes::PortIndex inPortIdx = 0; bool found = false; + bool outPortFound = false; + bool inPortFound = false; + for (const auto &[qtId, nodeData] : m_nodes) { - for (size_t i = 0; i < nodeData.outputPorts.size(); ++i) { - if (nodeData.outputPorts[i].id.value == link.output_port.value) { - outNodeIt = m_pwToQt.find(nodeData.info.id.value); - outPortIdx = static_cast(i); + if (!outPortFound) { + for (size_t i = 0; i < nodeData.outputPorts.size(); ++i) { + if (nodeData.outputPorts[i].id.value == + link.output_port.value) { + outNodeIt = m_pwToQt.find(nodeData.info.id.value); + outPortIdx = static_cast(i); + outPortFound = true; + break; + } } } - for (size_t i = 0; i < nodeData.inputPorts.size(); ++i) { - if (nodeData.inputPorts[i].id.value == link.input_port.value) { - inNodeIt = m_pwToQt.find(nodeData.info.id.value); - inPortIdx = static_cast(i); + if (!inPortFound) { + for (size_t i = 0; i < nodeData.inputPorts.size(); ++i) { + if (nodeData.inputPorts[i].id.value == + link.input_port.value) { + inNodeIt = m_pwToQt.find(nodeData.info.id.value); + inPortIdx = static_cast(i); + inPortFound = true; + break; + } } } + if (outPortFound && inPortFound) + break; } - if (outNodeIt != m_pwToQt.end() && inNodeIt != m_pwToQt.end()) { + if (outPortFound && inPortFound && outNodeIt != m_pwToQt.end() && + inNodeIt != m_pwToQt.end()) { found = true; } @@ -710,6 +726,8 @@ void WarpGraphModel::refreshFromClient() { Q_EMIT nodeVolumeChanged(qtId, previous, cached); } + recomputeConnectionChannels(); + m_refreshing = false; if (sceneChanged) { Q_EMIT endBatchUpdate(); @@ -976,6 +994,40 @@ float WarpGraphModel::nodePeakLevel(QtNodes::NodeId nodeId) const { return it != m_peakLevels.end() ? it->second : 0.0f; } +void WarpGraphModel::recomputeConnectionChannels() { + m_connectionChannels.clear(); + + std::unordered_map> byTarget; + for (const auto &cId : m_connections) + byTarget[cId.inNodeId].push_back(cId); + + for (auto &[targetId, conns] : byTarget) { + std::sort(conns.begin(), conns.end(), + [this](const auto &a, const auto &b) { + auto posA = m_positions.count(a.outNodeId) + ? m_positions.at(a.outNodeId).y() + : 0.0; + auto posB = m_positions.count(b.outNodeId) + ? m_positions.at(b.outNodeId).y() + : 0.0; + if (posA != posB) + return posA < posB; + return a.outPortIndex < b.outPortIndex; + }); + + int count = static_cast(conns.size()); + for (int i = 0; i < count; ++i) + m_connectionChannels[conns[i]] = {i, count}; + } +} + +WarpGraphModel::ConnectionChannel +WarpGraphModel::connectionChannel(QtNodes::ConnectionId cId) const { + auto it = m_connectionChannels.find(cId); + return it != m_connectionChannels.end() ? it->second + : ConnectionChannel{0, 1}; +} + void WarpGraphModel::saveLayout(const QString &path) const { ViewState vs{}; saveLayout(path, vs); diff --git a/gui/WarpGraphModel.h b/gui/WarpGraphModel.h index aa76c3a..37a1265 100644 --- a/gui/WarpGraphModel.h +++ b/gui/WarpGraphModel.h @@ -98,6 +98,12 @@ public: void setNodePeakLevel(QtNodes::NodeId nodeId, float level); float nodePeakLevel(QtNodes::NodeId nodeId) const; + struct ConnectionChannel { + int index = 0; + int count = 1; + }; + ConnectionChannel connectionChannel(QtNodes::ConnectionId cId) const; + Q_SIGNALS: void beginBatchUpdate(); void endBatchUpdate(); @@ -173,4 +179,7 @@ private: std::unordered_map> m_volumeWidgets; mutable std::unordered_map m_styleCache; std::unordered_map m_peakLevels; + std::unordered_map m_connectionChannels; + + void recomputeConnectionChannels(); };