Manhattan routing snapped to the rectangular grid with port-side-aware exit stubs, right-angle crossing detection between routed polylines, and painter paths bridging crossings with semicircular arcs (configurable radius). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
129 lines
4.3 KiB
C++
129 lines
4.3 KiB
C++
#include "core/Router.h"
|
|
|
|
#include <QTest>
|
|
|
|
using namespace diag;
|
|
|
|
namespace {
|
|
|
|
bool isOrthogonal(const QList<QPointF>& poly)
|
|
{
|
|
for (int i = 0; i + 1 < poly.size(); ++i) {
|
|
const QPointF a = poly.at(i);
|
|
const QPointF b = poly.at(i + 1);
|
|
if (qAbs(a.x() - b.x()) > 1e-6 && qAbs(a.y() - b.y()) > 1e-6)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class TestRouter : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void route_isOrthogonalAndKeepsEndpoints()
|
|
{
|
|
const QList<QPointF> poly =
|
|
Router::route({63, 37}, Side::Right, {305, 143}, Side::Left, 20);
|
|
QVERIFY(poly.size() >= 2);
|
|
QCOMPARE(poly.first(), QPointF(63, 37));
|
|
QCOMPARE(poly.last(), QPointF(305, 143));
|
|
QVERIFY(isOrthogonal(poly));
|
|
}
|
|
|
|
void route_intermediatePointsOnGrid()
|
|
{
|
|
const QList<QPointF> poly =
|
|
Router::route({63, 37}, Side::Right, {305, 143}, Side::Left, 20);
|
|
// The vertical middle segment must run on a grid line.
|
|
bool foundGridX = false;
|
|
for (int i = 1; i + 1 < poly.size(); ++i) {
|
|
const double x = poly.at(i).x();
|
|
if (qAbs(x - Router::snap(x, 20)) < 1e-6)
|
|
foundGridX = true;
|
|
}
|
|
QVERIFY(foundGridX);
|
|
}
|
|
|
|
void route_leavesPortInSideDirection()
|
|
{
|
|
const QList<QPointF> down =
|
|
Router::route({50, 50}, Side::Bottom, {250, 250}, Side::Top, 20);
|
|
QVERIFY(down.at(1).y() > down.at(0).y());
|
|
QCOMPARE(down.at(1).x(), down.at(0).x());
|
|
|
|
const QList<QPointF> left =
|
|
Router::route({300, 50}, Side::Left, {50, 50}, Side::Right, 20);
|
|
QVERIFY(left.at(1).x() < left.at(0).x());
|
|
}
|
|
|
|
void route_mixedAxesSingleCorner()
|
|
{
|
|
const QList<QPointF> poly =
|
|
Router::route({100, 100}, Side::Right, {200, 300}, Side::Top, 20);
|
|
QVERIFY(isOrthogonal(poly));
|
|
QCOMPARE(poly.first(), QPointF(100, 100));
|
|
QCOMPARE(poly.last(), QPointF(200, 300));
|
|
}
|
|
|
|
void crossings_detectsPerpendicularCross()
|
|
{
|
|
const QList<QPointF> horizontal{{0, 100}, {200, 100}};
|
|
const QList<QPointF> vertical{{100, 0}, {100, 200}};
|
|
const auto hits = Router::crossings(horizontal, vertical);
|
|
QCOMPARE(hits.size(), 1);
|
|
QCOMPARE(hits.first(), QPointF(100, 100));
|
|
// Symmetric case reports the same point.
|
|
QCOMPARE(Router::crossings(vertical, horizontal).size(), 1);
|
|
}
|
|
|
|
void crossings_ignoresParallelAndTouching()
|
|
{
|
|
const QList<QPointF> a{{0, 100}, {200, 100}};
|
|
const QList<QPointF> parallel{{0, 120}, {200, 120}};
|
|
QCOMPARE(Router::crossings(a, parallel).size(), 0);
|
|
// Vertical segment ending exactly on the line: a T-joint, not a cross.
|
|
const QList<QPointF> touching{{100, 0}, {100, 100}};
|
|
QCOMPARE(Router::crossings(a, touching).size(), 0);
|
|
}
|
|
|
|
void toPath_arcAddsDetourLength()
|
|
{
|
|
const QList<QPointF> poly{{0, 100}, {200, 100}};
|
|
const QPainterPath plain = Router::toPath(poly, {}, 6);
|
|
const QPainterPath hopped = Router::toPath(poly, {QPointF(100, 100)}, 6);
|
|
QVERIFY(hopped.length() > plain.length() + 1.0);
|
|
// The arc bulges upward: path bounding box extends above the line.
|
|
QVERIFY(hopped.boundingRect().top() < 100.0 - 4.0);
|
|
QCOMPARE(hopped.currentPosition(), QPointF(200, 100));
|
|
}
|
|
|
|
void toPath_hopTooCloseToCornerSkipped()
|
|
{
|
|
const QList<QPointF> poly{{0, 100}, {200, 100}};
|
|
const QPainterPath plain = Router::toPath(poly, {}, 6);
|
|
const QPainterPath nearEnd = Router::toPath(poly, {QPointF(198, 100)}, 6);
|
|
QCOMPARE(nearEnd.length(), plain.length());
|
|
}
|
|
|
|
void polyline_lengthAndPointAt()
|
|
{
|
|
const QList<QPointF> poly{{0, 0}, {100, 0}, {100, 100}};
|
|
QCOMPARE(Router::polylineLength(poly), 200.0);
|
|
QCOMPARE(Router::pointAt(poly, 0.25), QPointF(50, 0));
|
|
QCOMPARE(Router::pointAt(poly, 0.75), QPointF(100, 50));
|
|
QCOMPARE(Router::pointAt(poly, 1.0), QPointF(100, 100));
|
|
}
|
|
|
|
void snap_roundsToGrid()
|
|
{
|
|
QCOMPARE(Router::snap(47, 20), 40.0);
|
|
QCOMPARE(Router::snap(51, 20), 60.0);
|
|
QCOMPARE(Router::snapPoint({47, 51}, 20), QPointF(40, 60));
|
|
}
|
|
};
|
|
|
|
QTEST_GUILESS_MAIN(TestRouter)
|
|
#include "tst_router.moc"
|