diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/3rdparty/adaptagrams/libavoid/connector.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-upstream.tar.xz inkscape-upstream.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/3rdparty/adaptagrams/libavoid/connector.cpp | 2487 |
1 files changed, 2487 insertions, 0 deletions
diff --git a/src/3rdparty/adaptagrams/libavoid/connector.cpp b/src/3rdparty/adaptagrams/libavoid/connector.cpp new file mode 100644 index 0000000..24ab289 --- /dev/null +++ b/src/3rdparty/adaptagrams/libavoid/connector.cpp @@ -0,0 +1,2487 @@ +/* + * vim: ts=4 sw=4 et tw=0 wm=0 + * + * libavoid - Fast, Incremental, Object-avoiding Line Router + * + * Copyright (C) 2004-2014 Monash University + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * See the file LICENSE.LGPL distributed with the library. + * + * Licensees holding a valid commercial license may use this file in + * accordance with the commercial license agreement provided with the + * library. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Author(s): Michael Wybrow +*/ + + +#include <cstring> +#include <cfloat> +#include <cmath> +#include <cstdlib> +#include <algorithm> +#include <queue> +#include <limits> + +#include "libavoid/connector.h" +#include "libavoid/connend.h" +#include "libavoid/router.h" +#include "libavoid/visibility.h" +#include "libavoid/debug.h" +#include "libavoid/assertions.h" +#include "libavoid/junction.h" +#include "libavoid/makepath.h" +#include "libavoid/debughandler.h" + + +namespace Avoid { + + +ConnRef::ConnRef(Router *router, const unsigned int id) + : m_router(router), + m_type(router->validConnType()), + m_reroute_flag_ptr(nullptr), + m_needs_reroute_flag(true), + m_false_path(false), + m_needs_repaint(false), + m_active(false), + m_hate_crossings(false), + m_has_fixed_route(false), + m_route_dist(0), + m_src_vert(nullptr), + m_dst_vert(nullptr), + m_start_vert(nullptr), + m_callback_func(nullptr), + m_connector(nullptr), + m_src_connend(nullptr), + m_dst_connend(nullptr) +{ + COLA_ASSERT(m_router != nullptr); + m_id = m_router->assignId(id); + + // TODO: Store endpoints and details. + m_route.clear(); + + m_reroute_flag_ptr = m_router->m_conn_reroute_flags.addConn(this); +} + + +ConnRef::ConnRef(Router *router, const ConnEnd& src, const ConnEnd& dst, + const unsigned int id) + : m_router(router), + m_type(router->validConnType()), + m_reroute_flag_ptr(nullptr), + m_needs_reroute_flag(true), + m_false_path(false), + m_needs_repaint(false), + m_active(false), + m_hate_crossings(false), + m_has_fixed_route(false), + m_route_dist(0), + m_src_vert(nullptr), + m_dst_vert(nullptr), + m_callback_func(nullptr), + m_connector(nullptr), + m_src_connend(nullptr), + m_dst_connend(nullptr) +{ + COLA_ASSERT(m_router != nullptr); + m_id = m_router->assignId(id); + m_route.clear(); + + // Set endpoint values. + setEndpoints(src, dst); + + m_reroute_flag_ptr = m_router->m_conn_reroute_flags.addConn(this); +} + + +ConnRef::~ConnRef() +{ + COLA_ASSERT(m_router); + + if (m_router->m_currently_calling_destructors == false) + { + err_printf("ERROR: ConnRef::~ConnRef() shouldn't be called directly.\n"); + err_printf(" It is owned by the router. Call Router::deleteConnector() instead.\n"); + abort(); + } + + m_router->m_conn_reroute_flags.removeConn(this); + + m_router->removeObjectFromQueuedActions(this); + + freeRoutes(); + + if (m_src_vert) + { + m_src_vert->removeFromGraph(); + m_router->vertices.removeVertex(m_src_vert); + delete m_src_vert; + m_src_vert = nullptr; + } + if (m_src_connend) + { + m_src_connend->disconnect(); + m_src_connend->freeActivePin(); + delete m_src_connend; + m_src_connend = nullptr; + } + + if (m_dst_vert) + { + m_dst_vert->removeFromGraph(); + m_router->vertices.removeVertex(m_dst_vert); + delete m_dst_vert; + m_dst_vert = nullptr; + } + if (m_dst_connend) + { + m_dst_connend->disconnect(); + m_dst_connend->freeActivePin(); + delete m_dst_connend; + m_dst_connend = nullptr; + } + + // Clear checkpoint vertices. + for (size_t i = 0; i < m_checkpoint_vertices.size(); ++i) + { + m_checkpoint_vertices[i]->removeFromGraph(true); + m_router->vertices.removeVertex(m_checkpoint_vertices[i]); + delete m_checkpoint_vertices[i]; + } + m_checkpoint_vertices.clear(); + + if (m_active) + { + makeInactive(); + } +} + + +ConnType ConnRef::routingType(void) const +{ + return m_type; +} + + +void ConnRef::setRoutingType(ConnType type) +{ + type = m_router->validConnType(type); + if (m_type != type) + { + m_type = type; + + makePathInvalid(); + + m_router->modifyConnector(this); + } +} + + +std::vector<Checkpoint> ConnRef::routingCheckpoints(void) const +{ + return m_checkpoints; +} + + +void ConnRef::setRoutingCheckpoints(const std::vector<Checkpoint>& checkpoints) +{ + m_checkpoints = checkpoints; + + // Clear previous checkpoint vertices. + for (size_t i = 0; i < m_checkpoint_vertices.size(); ++i) + { + m_checkpoint_vertices[i]->removeFromGraph(true); + m_router->vertices.removeVertex(m_checkpoint_vertices[i]); + delete m_checkpoint_vertices[i]; + } + m_checkpoint_vertices.clear(); + + for (size_t i = 0; i < m_checkpoints.size(); ++i) + { + VertID ptID(m_id, 2 + i, + VertID::PROP_ConnPoint | VertID::PROP_ConnCheckpoint); + VertInf *vertex = new VertInf(m_router, ptID, m_checkpoints[i].point); + vertex->visDirections = ConnDirAll; + + m_checkpoint_vertices.push_back(vertex); + } + if (m_router->m_allows_polyline_routing) + { + for (size_t i = 0; i < m_checkpoints.size(); ++i) + { + vertexVisibility(m_checkpoint_vertices[i], nullptr, true, true); + } + } +} + + +void ConnRef::common_updateEndPoint(const unsigned int type, ConnEnd connEnd) +{ + const Point& point = connEnd.position(); + //db_printf("common_updateEndPoint(%d,(pid=%d,vn=%d,(%f,%f)))\n", + // type,point.id,point.vn,point.x,point.y); + COLA_ASSERT((type == (unsigned int) VertID::src) || + (type == (unsigned int) VertID::tar)); + + // The connEnd is a copy of a ConnEnd that will get disconnected, + // so don't leave it looking like it is still connected. + connEnd.m_conn_ref = nullptr; + + if (!m_active) + { + makeActive(); + } + + VertInf *altered = nullptr; + + VertIDProps properties = VertID::PROP_ConnPoint; + if (connEnd.isPinConnection()) + { + properties |= VertID::PROP_DummyPinHelper; + } + VertID ptID(m_id, type, properties); + if (type == (unsigned int) VertID::src) + { + if (m_src_vert) + { + m_src_vert->Reset(ptID, point); + } + else + { + m_src_vert = new VertInf(m_router, ptID, point); + } + m_src_vert->visDirections = connEnd.directions(); + + if (m_src_connend) + { + m_src_connend->disconnect(); + m_src_connend->freeActivePin(); + delete m_src_connend; + m_src_connend = nullptr; + } + if (connEnd.isPinConnection()) + { + m_src_connend = new ConnEnd(connEnd); + m_src_connend->connect(this); + // Don't need this to have visibility since we won't + // be connecting to it. + m_src_vert->visDirections = ConnDirNone; + } + + altered = m_src_vert; + } + else // if (type == (unsigned int) VertID::tar) + { + if (m_dst_vert) + { + m_dst_vert->Reset(ptID, point); + } + else + { + m_dst_vert = new VertInf(m_router, ptID, point); + } + m_dst_vert->visDirections = connEnd.directions(); + + if (m_dst_connend) + { + m_dst_connend->disconnect(); + m_dst_connend->freeActivePin(); + delete m_dst_connend; + m_dst_connend = nullptr; + } + if (connEnd.isPinConnection()) + { + m_dst_connend = new ConnEnd(connEnd); + m_dst_connend->connect(this); + // Don't need this to have visibility since we won't + // be connecting to it. + m_dst_vert->visDirections = ConnDirNone; + } + + altered = m_dst_vert; + } + + // XXX: Seems to be faster to just remove the edges and recreate + bool isConn = true; + altered->removeFromGraph(isConn); + + makePathInvalid(); + m_router->setStaticGraphInvalidated(true); +} + + +void ConnRef::setEndpoints(const ConnEnd& srcPoint, const ConnEnd& dstPoint) +{ + m_router->modifyConnector(this, VertID::src, srcPoint); + m_router->modifyConnector(this, VertID::tar, dstPoint); +} + + +void ConnRef::setEndpoint(const unsigned int type, const ConnEnd& connEnd) +{ + m_router->modifyConnector(this, type, connEnd); +} + + +void ConnRef::setSourceEndpoint(const ConnEnd& srcPoint) +{ + m_router->modifyConnector(this, VertID::src, srcPoint); +} + + +void ConnRef::setDestEndpoint(const ConnEnd& dstPoint) +{ + m_router->modifyConnector(this, VertID::tar, dstPoint); +} + + +// Given the start or end vertex of a connector, returns the ConnEnd that +// can be used to reproduce that endpoint. This is used for hyperedge routing. +// +bool ConnRef::getConnEndForEndpointVertex(VertInf *vertex, + ConnEnd& connEnd) const +{ + if (vertex == nullptr) + { + err_printf("Warning: In ConnRef::getConnEndForEndpointVertex():\n" + " ConnEnd for connector %d is uninitialised. It may have been\n" + " set but Router::processTrancaction has not yet been called.\n", + (int) id()); + return false; + } + + if (vertex == m_src_vert) + { + if (m_src_connend) + { + connEnd = *m_src_connend; + } + else + { + connEnd = ConnEnd(Point(m_src_vert->point.x, m_src_vert->point.y), + m_src_vert->visDirections); + } + return true; + } + else if (vertex == m_dst_vert) + { + if (m_dst_connend) + { + connEnd = *m_dst_connend; + } + else + { + connEnd = ConnEnd(Point(m_dst_vert->point.x, m_dst_vert->point.y), + m_dst_vert->visDirections); + } + return true; + } + return false; +} + + +void ConnRef::updateEndPoint(const unsigned int type, const ConnEnd& connEnd) +{ + common_updateEndPoint(type, connEnd); + + if (m_has_fixed_route) + { + // Don't need to continue and compute visibility if route is fixed. + return; + } + + if (m_router->m_allows_polyline_routing) + { + bool knownNew = true; + bool genContains = true; + if (type == (unsigned int) VertID::src) + { + bool dummySrc = m_src_connend && m_src_connend->isPinConnection(); + if (!dummySrc) + { + // Only generate visibility if not attached to a pin. + vertexVisibility(m_src_vert, m_dst_vert, knownNew, genContains); + } + } + else + { + bool dummyDst = m_dst_connend && m_dst_connend->isPinConnection(); + if (!dummyDst) + { + // Only generate visibility if not attached to a pin. + vertexVisibility(m_dst_vert, m_src_vert, knownNew, genContains); + } + } + } +} + + +void ConnRef::outputCode(FILE *fp) const +{ + fprintf(fp, " // connRef%u\n", id()); + fprintf(fp, " connRef = new ConnRef(router, %u);\n", id()); + if (m_src_connend) + { + m_src_connend->outputCode(fp, "src"); + fprintf(fp, " connRef->setSourceEndpoint(srcPt);\n"); + } + else if (src()) + { + fprintf(fp, " srcPt = ConnEnd(Point(%" PREC "g, %" PREC "g), %u);\n", + src()->point.x, src()->point.y, src()->visDirections); + fprintf(fp, " connRef->setSourceEndpoint(srcPt);\n"); + } + if (m_dst_connend) + { + m_dst_connend->outputCode(fp, "dst"); + fprintf(fp, " connRef->setDestEndpoint(dstPt);\n"); + } + else if (dst()) + { + fprintf(fp, " dstPt = ConnEnd(Point(%" PREC "g, %" PREC "g), %u);\n", + dst()->point.x, dst()->point.y, dst()->visDirections); + fprintf(fp, " connRef->setDestEndpoint(dstPt);\n"); + } + fprintf(fp, " connRef->setRoutingType((ConnType)%u);\n", routingType()); + + if (m_has_fixed_route) + { + PolyLine currRoute = route(); + fprintf(fp, " newRoute._id = %u;\n", id()); + fprintf(fp, " newRoute.ps.resize(%d);\n", (int)currRoute.size()); + for (size_t i = 0; i < currRoute.size(); ++i) + { + fprintf(fp, " newRoute.ps[%d] = Point(%" PREC "g, %" PREC "g);\n", + (int) i, currRoute.ps[i].x, currRoute.ps[i].y); + fprintf(fp, " newRoute.ps[%d].id = %u;\n", + (int) i, currRoute.ps[i].id); + fprintf(fp, " newRoute.ps[%d].vn = %u;\n", + (int) i, currRoute.ps[i].vn); + } + fprintf(fp, " connRef->setFixedRoute(newRoute);\n"); + } + + if (!m_checkpoints.empty()) + { + fprintf(fp, " std::vector<Checkpoint> checkpoints%u(%d);\n", id(), + (int) m_checkpoints.size()); + for (size_t cInd = 0; cInd < m_checkpoints.size(); ++cInd) + { + fprintf(fp, " checkpoints%u[%d] = Checkpoint(Point(" + "%" PREC "g, %" PREC "g), (ConnDirFlags) %d, " + "(ConnDirFlags) %d);\n", id(), (int) cInd, + m_checkpoints[cInd].point.x, m_checkpoints[cInd].point.y, + m_checkpoints[cInd].arrivalDirections, + m_checkpoints[cInd].departureDirections); + } + fprintf(fp, " connRef->setRoutingCheckpoints(checkpoints%u);\n", + id()); + } + fprintf(fp, "\n"); +} + + +std::pair<Obstacle *, Obstacle *> ConnRef::endpointAnchors(void) const +{ + std::pair<Obstacle *, Obstacle *> anchors; + anchors.first = nullptr; + anchors.second = nullptr; + + if (m_src_connend) + { + anchors.first = m_src_connend->m_anchor_obj; + } + if (m_dst_connend) + { + anchors.second = m_dst_connend->m_anchor_obj; + } + return anchors; +} + +std::pair<ConnEnd, ConnEnd> ConnRef::endpointConnEnds(void) const +{ + std::pair<ConnEnd, ConnEnd> endpoints; + getConnEndForEndpointVertex(m_src_vert, endpoints.first); + getConnEndForEndpointVertex(m_dst_vert, endpoints.second); + return endpoints; +} + +bool ConnRef::setEndpoint(const unsigned int type, const VertID& pointID, + Point *pointSuggestion) +{ + VertInf *vInf = m_router->vertices.getVertexByID(pointID); + if (vInf == nullptr) + { + return false; + } + Point& point = vInf->point; + if (pointSuggestion) + { + if (euclideanDist(point, *pointSuggestion) > 0.5) + { + return false; + } + } + + common_updateEndPoint(type, point); + + // Give this visibility just to the point it is over. + EdgeInf *edge = new EdgeInf( + (type == VertID::src) ? m_src_vert : m_dst_vert, vInf); + // XXX: We should be able to set this to zero, but can't due to + // assumptions elsewhere in the code. + edge->setDist(0.001); + + m_router->processTransaction(); + return true; +} + + +void ConnRef::makeActive(void) +{ + COLA_ASSERT(!m_active); + + // Add to connRefs list. + m_connrefs_pos = m_router->connRefs.insert(m_router->connRefs.begin(), this); + m_active = true; +} + + +void ConnRef::freeActivePins(void) +{ + if (m_src_connend) + { + m_src_connend->freeActivePin(); + } + if (m_dst_connend) + { + m_dst_connend->freeActivePin(); + } +} + + +void ConnRef::makeInactive(void) +{ + COLA_ASSERT(m_active); + + // Remove from connRefs list. + m_router->connRefs.erase(m_connrefs_pos); + m_active = false; +} + + +void ConnRef::freeRoutes(void) +{ + m_route.clear(); + m_display_route.clear(); +} + + +const PolyLine& ConnRef::route(void) const +{ + return m_route; +} + + +PolyLine& ConnRef::routeRef(void) +{ + return m_route; +} + + +void ConnRef::set_route(const PolyLine& route) +{ + if (&m_display_route == &route) + { + db_printf("Error:\tTrying to update libavoid route with itself.\n"); + return; + } + m_display_route.ps = route.ps; + + //_display_route.clear(); +} + +void ConnRef::setFixedExistingRoute(void) +{ + COLA_ASSERT(m_route.size() >= 2); + m_has_fixed_route = true; + m_router->registerSettingsChange(); +} + +void ConnRef::setFixedRoute(const PolyLine& route) +{ + if (route.size() >= 2) + { + // Set endpoints based on the fixed route incase the + // fixed route is later cleared. + setEndpoints(route.ps[0], route.ps[route.size() - 1]); + } + m_has_fixed_route = true; + m_route = route; + m_display_route = m_route.simplify(); + m_router->registerSettingsChange(); +} + +bool ConnRef::hasFixedRoute(void) const +{ + return m_has_fixed_route; +} + +void ConnRef::clearFixedRoute(void) +{ + m_has_fixed_route = false; + makePathInvalid(); + m_router->registerSettingsChange(); +} + +Polygon& ConnRef::displayRoute(void) +{ + if (m_display_route.empty()) + { + // No displayRoute is set. Simplify the current route to get it. + m_display_route = m_route.simplify(); + } + return m_display_route; +} + + +void ConnRef::calcRouteDist(void) +{ + double (*dist)(const Point& a, const Point& b) = + (m_type == ConnType_PolyLine) ? euclideanDist : manhattanDist; + + m_route_dist = 0; + for (size_t i = 1; i < m_route.size(); ++i) + { + m_route_dist += dist(m_route.at(i), m_route.at(i - 1)); + } +} + + +bool ConnRef::needsRepaint(void) const +{ + return m_needs_repaint; +} + + +unsigned int ConnRef::id(void) const +{ + return m_id; +} + + +Point midpoint(Point a, Point b) +{ + Point midpoint; + + midpoint.x = (a.x + b.x) / 2.0; + midpoint.y = (a.y + b.y) / 2.0; + + return midpoint; +} + + +std::pair<JunctionRef *, ConnRef *> ConnRef::splitAtSegment( + const size_t segmentN) +{ + ConnRef *newConn = nullptr; + JunctionRef *newJunction = nullptr; + + if (m_display_route.size() > segmentN) + { + // Position the junction at the midpoint of the desired segment. + Point junctionPos = midpoint(m_display_route.at(segmentN - 1), + m_display_route.at(segmentN)); + + // Create the new junction. + newJunction = new JunctionRef(router(), junctionPos); + router()->addJunction(newJunction); + newJunction->preferOrthogonalDimension( + (m_display_route.at(segmentN - 1).x == + m_display_route.at(segmentN).x) ? YDIM : XDIM); + + // Create a new connection routing from the junction to the original + // connector's endpoint. + ConnEnd newConnSrc = ConnEnd(newJunction); + ConnEnd newConnDst = *m_dst_connend; + newConn = new ConnRef(router(), newConnSrc, newConnDst); + + // Reroute the endpoint of the original connector to attach to the + // new junction. + ConnEnd oldConnDst = ConnEnd(newJunction); + this->setDestEndpoint(oldConnDst); + } + + return std::make_pair(newJunction, newConn); +} + + +VertInf *ConnRef::src(void) const +{ + return m_src_vert; +} + + +VertInf *ConnRef::dst(void) const +{ + return m_dst_vert; +} + + +VertInf *ConnRef::start(void) +{ + return m_start_vert; +} + + +bool ConnRef::isInitialised(void) const +{ + return m_active; +} + + +void ConnRef::unInitialise(void) +{ + m_router->vertices.removeVertex(m_src_vert); + m_router->vertices.removeVertex(m_dst_vert); + makeInactive(); +} + + +void ConnRef::removeFromGraph(void) +{ + if (m_src_vert) + { + m_src_vert->removeFromGraph(); + } + + if (m_dst_vert) + { + m_dst_vert->removeFromGraph(); + } +} + + +void ConnRef::setCallback(void (*cb)(void *), void *ptr) +{ + m_callback_func = cb; + m_connector = ptr; +} + + +void ConnRef::performCallback(void) +{ + if (m_callback_func) + { + m_callback_func(m_connector); + } +} + + +void ConnRef::makePathInvalid(void) +{ + m_needs_reroute_flag = true; +} + + +Router *ConnRef::router(void) const +{ + return m_router; +} + + +// Validates a bend point on a path to check it does not form a zigzag corner. +// a, b, c are consecutive points on the path. d and e are b's neighbours, +// forming the shape corner d-b-e. +// +bool validateBendPoint(VertInf *aInf, VertInf *bInf, VertInf *cInf) +{ + if (bInf->id.isConnectionPin() || bInf->id.isConnCheckpoint()) + { + // We shouldn't check connection pins or checkpoints. + return true; + } + bool bendOkay = true; + + if ((aInf == nullptr) || (cInf == nullptr)) + { + // Not a bendpoint, i.e., the end of the connector, so don't test. + return bendOkay; + } + + COLA_ASSERT(bInf != nullptr); + VertInf *dInf = bInf->shPrev; + VertInf *eInf = bInf->shNext; + COLA_ASSERT(dInf != nullptr); + COLA_ASSERT(eInf != nullptr); + + Point& a = aInf->point; + Point& b = bInf->point; + Point& c = cInf->point; + Point& d = dInf->point; + Point& e = eInf->point; + + if ((a == b) || (b == c)) + { + return bendOkay; + } + +#ifdef PATHDEBUG + db_printf("a=(%g, %g)\n", a.x, a.y); + db_printf("b=(%g, %g)\n", b.x, b.y); + db_printf("c=(%g, %g)\n", c.x, c.y); + db_printf("d=(%g, %g)\n", d.x, d.y); + db_printf("e=(%g, %g)\n", e.x, e.y); +#endif + // Check angle: + int abc = vecDir(a, b, c); +#ifdef PATHDEBUG + db_printf("(abc == %d) ", abc); +#endif + + if (abc == 0) + { + // The three consecutive point on the path are in a line. + // There should always be an equally short path that skips this + // bend point, but this check is used during rubber-band routing + // so we allow this case. + bendOkay = true; + } + else // (abc != 0) + { + COLA_ASSERT(vecDir(d, b, e) > 0); + int abe = vecDir(a, b, e); + int abd = vecDir(a, b, d); + int bce = vecDir(b, c, e); + int bcd = vecDir(b, c, d); +#ifdef PATHDEBUG + db_printf("&& (abe == %d) && (abd == %d) &&\n(bce == %d) && (bcd == %d)", + abe, abd, bce, bcd); +#endif + + bendOkay = false; + if (abe > 0) + { + if ((abc > 0) && (abd >= 0) && (bce >= 0)) + { + bendOkay = true; + } + } + else if (abd < 0) + { + if ((abc < 0) && (abe <= 0) && (bcd <= 0)) + { + bendOkay = true; + } + } + } +#ifdef PATHDEBUG + db_printf("\n"); +#endif + return bendOkay; +} + + +std::pair<bool, bool> ConnRef::assignConnectionPinVisibility(const bool connect) +{ + // XXX This is kind of a hack for connection pins. Probably we want to + // not use m_src_vert and m_dst_vert. For the moment we will clear + // their visibility and give them visibility to the pins. + bool dummySrc = m_src_connend && m_src_connend->isPinConnection(); + if (dummySrc) + { + m_src_vert->removeFromGraph(); + if (connect) + { + m_src_connend->assignPinVisibilityTo(m_src_vert, m_dst_vert); + } + } + bool dummyDst = m_dst_connend && m_dst_connend->isPinConnection(); + if (dummyDst) + { + m_dst_vert->removeFromGraph(); + if (connect) + { + m_dst_connend->assignPinVisibilityTo(m_dst_vert, m_src_vert); + } + } + + return std::make_pair(dummySrc, dummyDst); +} + + +bool ConnRef::generatePath(void) +{ + // XXX Currently rubber-band routing only works when dragging the + // destination point of a connector, but not the source. The code + // needs to be reworked to work in both directions. + + if (!m_false_path && !m_needs_reroute_flag) + { + // This connector is up to date. + return false; + } + + if (!m_dst_vert || !m_src_vert) + { + // Connector is not fully initialised. + return false; + } + + //COLA_ASSERT(_srcVert->point != _dstVert->point); + + m_false_path = false; + m_needs_reroute_flag = false; + + m_start_vert = m_src_vert; + + // Some connectors may attach to connection pins, which means they route + // to the closest of multiple pins on a shape. How we handle this is to + // add a dummy vertex as the source or target vertex. This is then given + // visibility to each of the possible pins and tiny distance. Here we + // assign this visibility by adding edges to the visibility graph that we + // later remove. + std::pair<bool, bool> isDummyAtEnd = assignConnectionPinVisibility(true); + + + if (m_router->RubberBandRouting && route().size() > 0) + { + if (isDummyAtEnd.first) + { + //ShapeConnectionPin *activePin = m_src_connend->active + Point firstPoint = m_src_vert->point; + firstPoint.id = m_src_vert->id.objID; + firstPoint.vn = m_src_vert->id.vn; + PolyLine& existingRoute = routeRef(); + existingRoute.ps.insert(existingRoute.ps.begin(), 1, firstPoint); + } + } + + std::vector<Point> path; + std::vector<VertInf *> vertices; + if (m_checkpoints.empty()) + { + generateStandardPath(path, vertices); + } + else + { + generateCheckpointsPath(path, vertices); + } + + COLA_ASSERT(vertices.size() >= 2); + COLA_ASSERT(vertices[0] == src()); + COLA_ASSERT(vertices[vertices.size() - 1] == dst()); + COLA_ASSERT(m_reroute_flag_ptr != nullptr); + + for (size_t i = 1; i < vertices.size(); ++i) + { + if (m_router->InvisibilityGrph && (m_type == ConnType_PolyLine)) + { + // TODO: Again, we could know this edge without searching. + EdgeInf *edge = EdgeInf::existingEdge(vertices[i - 1], vertices[i]); + if (edge) { + edge->addConn(m_reroute_flag_ptr); + } + } + else + { + m_false_path = true; + } + + VertInf *vertex = vertices[i]; + if (vertex->pathNext && + (vertex->pathNext->point == vertex->point)) + { + if (!(vertex->pathNext->id.isConnPt()) && !(vertex->id.isConnPt())) + { + // Check for consecutive points on opposite + // corners of two touching shapes. + COLA_ASSERT(abs(vertex->pathNext->id.vn - vertex->id.vn) != 2); + } + } + } + + // Get rid of dummy ShapeConnectionPin bridging points at beginning + // and end of path. + std::vector<Point> clippedPath; + std::vector<Point>::iterator pathBegin = path.begin(); + std::vector<Point>::iterator pathEnd = path.end(); + if (path.size() > 2 && isDummyAtEnd.first) + { + ++pathBegin; + m_src_connend->usePinVertex(vertices[1]); + } + if (path.size() > 2 && isDummyAtEnd.second) + { + --pathEnd; + m_dst_connend->usePinVertex(vertices[vertices.size() - 2]); + } + clippedPath.insert(clippedPath.end(), pathBegin, pathEnd); + + // Clear visibility edges added for connection pins dummy vertices. + assignConnectionPinVisibility(false); + + freeRoutes(); + PolyLine& output_route = m_route; + output_route.ps = clippedPath; + +#ifdef PATHDEBUG + db_printf("Output route:\n"); + for (size_t i = 0; i < output_route.ps.size(); ++i) + { + db_printf("[%d,%d] %g, %g ", output_route.ps[i].id, + output_route.ps[i].vn, output_route.ps[i].x, + output_route.ps[i].y); + } + db_printf("\n\n"); +#endif + +#ifdef DEBUGHANDLER + if (m_router->debugHandler()) + { + m_router->debugHandler()->updateConnectorRoute(this, -1, -1); + } +#endif + + return true; +} + +void ConnRef::generateCheckpointsPath(std::vector<Point>& path, + std::vector<VertInf *>& vertices) +{ + std::vector<VertInf *> checkpoints = m_checkpoint_vertices; + checkpoints.insert(checkpoints.begin(), src()); + checkpoints.push_back(dst()); + + path.clear(); + vertices.clear(); + path.push_back(src()->point); + vertices.push_back(src()); + + size_t lastSuccessfulIndex = 0; + for (size_t i = 1; i < checkpoints.size(); ++i) + { + VertInf *start = checkpoints[lastSuccessfulIndex]; + VertInf *end = checkpoints[i]; + + // Handle checkpoint directions by disabling some visibility edges. + if (lastSuccessfulIndex > 0) + { + Checkpoint& srcCP = m_checkpoints[lastSuccessfulIndex - 1]; + if (srcCP.departureDirections != ConnDirAll) + { + start->setVisibleDirections(srcCP.departureDirections); + } + } + if ((i + 1) < checkpoints.size()) + { + Checkpoint& dstCP = m_checkpoints[i - 1]; + if (dstCP.arrivalDirections != ConnDirAll) + { + end->setVisibleDirections(dstCP.arrivalDirections); + } + } + + AStarPath aStar; + // Route the connector + aStar.search(this, start, end, nullptr); + + // Restore changes made for checkpoint visibility directions. + if (lastSuccessfulIndex > 0) + { + start->setVisibleDirections(ConnDirAll); + } + if ((i + 1) < checkpoints.size()) + { + end->setVisibleDirections(ConnDirAll); + } + + // Process the path. + int pathlen = end->pathLeadsBackTo(start); + if (pathlen >= 2) + { + size_t prev_path_size = path.size(); + path.resize(prev_path_size + (pathlen - 1)); + vertices.resize(prev_path_size + (pathlen - 1)); + VertInf *vertInf = end; + for (size_t index = path.size() - 1; index >= prev_path_size; + --index) + { + path[index] = vertInf->point; + if (vertInf->id.isConnPt()) + { + path[index].id = m_id; + path[index].vn = kUnassignedVertexNumber; + } + else + { + path[index].id = vertInf->id.objID; + path[index].vn = vertInf->id.vn; + } + vertices[index] = vertInf; + vertInf = vertInf->pathNext; + } + lastSuccessfulIndex = i; + } + else if (i + 1 == checkpoints.size()) + { + // There is no valid path. + db_printf("Warning: Path not found...\n"); + m_needs_reroute_flag = true; + + path.push_back(dst()->point); + vertices.push_back(dst()); + + COLA_ASSERT(path.size() >= 2); + } + else + { + err_printf("Warning: skipping checkpoint for connector " + "%d at (%g, %g).\n", (int) id(), + checkpoints[i]->point.x, checkpoints[i]->point.y); + fflush(stderr); + } + } + // Use topbit to differentiate between start and end point of connector. + // They need unique IDs for nudging. + unsigned int topbit = ((unsigned int) 1) << 31; + path[path.size() - 1].id = m_id | topbit; + path[path.size() - 1].vn = kUnassignedVertexNumber; +} + + +void ConnRef::generateStandardPath(std::vector<Point>& path, + std::vector<VertInf *>& vertices) +{ + VertInf *tar = m_dst_vert; + size_t existingPathStart = 0; + const PolyLine& currRoute = route(); + if (m_router->RubberBandRouting) + { + COLA_ASSERT(m_router->IgnoreRegions == true); + +#ifdef PATHDEBUG + db_printf("\n"); + src()->id.db_print(); + db_printf(": %g, %g\n", src()->point.x, src()->point.y); + tar->id.db_print(); + db_printf(": %g, %g\n", tar->point.x, tar->point.y); + for (size_t i = 0; i < currRoute.ps.size(); ++i) + { + db_printf("%g, %g ", currRoute.ps[i].x, currRoute.ps[i].y); + } + db_printf("\n"); +#endif + if (currRoute.size() > 2) + { + if (m_src_vert->point == currRoute.ps[0]) + { + existingPathStart = currRoute.size() - 2; + COLA_ASSERT(existingPathStart != 0); + const Point& pnt = currRoute.at(existingPathStart); + VertID vID(pnt.id, pnt.vn); + + m_start_vert = m_router->vertices.getVertexByID(vID); + COLA_ASSERT(m_start_vert); + } + } + } + //db_printf("GO\n"); + //db_printf("src: %X strt: %X dst: %X\n", (int) m_src_vert, (int) m_start_vert, (int) m_dst_vert); + unsigned int pathlen = 0; + while (pathlen == 0) + { + AStarPath aStar; + aStar.search(this, src(), dst(), start()); + pathlen = dst()->pathLeadsBackTo(src()); + if (pathlen < 2) + { + if (existingPathStart == 0) + { + break; + } +#ifdef PATHDEBUG + db_printf("BACK\n"); +#endif + existingPathStart--; + const Point& pnt = currRoute.at(existingPathStart); + VertIDProps props = (existingPathStart > 0) ? 0 : + VertID::PROP_ConnPoint; + VertID vID(pnt.id, pnt.vn, props); + + m_start_vert = m_router->vertices.getVertexByID(vID); + COLA_ASSERT(m_start_vert); + } + else if (m_router->RubberBandRouting) + { + // found. + bool unwind = false; + +#ifdef PATHDEBUG + db_printf("\n\n\nSTART:\n\n"); +#endif + VertInf *prior = nullptr; + for (VertInf *curr = tar; curr != m_start_vert->pathNext; + curr = curr->pathNext) + { + if (!validateBendPoint(curr->pathNext, curr, prior)) + { + unwind = true; + break; + } + prior = curr; + } + if (unwind) + { +#ifdef PATHDEBUG + db_printf("BACK II\n"); +#endif + if (existingPathStart == 0) + { + break; + } + existingPathStart--; + const Point& pnt = currRoute.at(existingPathStart); + VertIDProps props = (existingPathStart > 0) ? 0 : + VertID::PROP_ConnPoint; + VertID vID(pnt.id, pnt.vn, props); + + m_start_vert = m_router->vertices.getVertexByID(vID); + COLA_ASSERT(m_start_vert); + + pathlen = 0; + } + } + } + + if (pathlen < 2) + { + // There is no valid path. + db_printf("Warning: Path not found...\n"); + m_needs_reroute_flag = true; + pathlen = 2; + tar->pathNext = m_src_vert; + if ((m_type == ConnType_PolyLine) && m_router->InvisibilityGrph) + { + // TODO: Could we know this edge already? + //EdgeInf *edge = EdgeInf::existingEdge(m_src_vert, tar); + //COLA_ASSERT(edge != nullptr); + //edge->addCycleBlocker(); + } + } + path.resize(pathlen); + vertices.resize(pathlen); + + unsigned int j = pathlen - 1; + for (VertInf *i = tar; i != m_src_vert; i = i->pathNext) + { + path[j] = i->point; + vertices[j] = i; + path[j].id = i->id.objID; + path[j].vn = i->id.vn; + + j--; + } + vertices[0] = m_src_vert; + path[0] = m_src_vert->point; + path[0].id = m_src_vert->id.objID; + path[0].vn =m_src_vert->id.vn; +} + + +void ConnRef::setHateCrossings(bool value) +{ + m_hate_crossings = value; +} + + +bool ConnRef::doesHateCrossings(void) const +{ + return m_hate_crossings; +} + + +std::vector<Point> ConnRef::possibleDstPinPoints(void) const +{ + std::vector<Point> points; + if (m_dst_connend) + { + points = m_dst_connend->possiblePinPoints(); + } + return points; +} + + +PtOrder::PtOrder() +{ + // We have sorted neither list initially. + for (size_t dim = 0; dim < 2; ++dim) + { + sorted[dim] = false; + } +} + + +PtOrder::~PtOrder() +{ +} + + +PointRepVector PtOrder::sortedPoints(const size_t dim) +{ + // Sort if not already sorted. + if (sorted[dim] == false) + { + sort(dim); + } + return sortedConnVector[dim]; +} + + +int PtOrder::positionFor(const size_t dim, const ConnRef *conn) +{ + // Sort if not already sorted. + if (sorted[dim] == false) + { + sort(dim); + } + + // Just return position from the sorted list. + size_t i = 0; + for ( ; i < sortedConnVector[dim].size(); ++i) + { + if (sortedConnVector[dim][i].second == conn) + { + return (int) i; + } + } + return -1; +} + + +size_t PtOrder::insertPoint(const size_t dim, const PtConnPtrPair& pointPair) +{ + // Is this connector bendpoint already inserted? + size_t i = 0; + for ( ; i < nodes[dim].size(); ++i) + { + if (nodes[dim][i].second == pointPair.second) + { + return i; + } + } + // Not found, insert. + nodes[dim].push_back(pointPair); + return nodes[dim].size() - 1; +} + +void PtOrder::addPoints(const size_t dim, const PtConnPtrPair& arg1, + const PtConnPtrPair& arg2) +{ + // Add points, but not ordering information. + insertPoint(dim, arg1); + insertPoint(dim, arg2); +} + + +void PtOrder::addOrderedPoints(const size_t dim, const PtConnPtrPair& innerArg, + const PtConnPtrPair& outerArg, bool swapped) +{ + PtConnPtrPair inner = (swapped) ? outerArg : innerArg; + PtConnPtrPair outer = (swapped) ? innerArg : outerArg; + COLA_ASSERT(inner != outer); + + // Add points. + size_t innerIndex = insertPoint(dim, inner); + size_t outerIndex = insertPoint(dim, outer); + + // And edge for ordering information. + links[dim].push_back(std::make_pair(outerIndex, innerIndex)); +} + + +void PtOrder::sort(const size_t dim) +{ + // This is just a topological sort of the points using the edges info. + + sorted[dim] = true; + + size_t n = nodes[dim].size(); + + // Build an adjacency matrix for easy lookup. + std::vector<std::vector<bool> > adjacencyMatrix(n); + for (size_t i = 0; i < n; ++i) + { + adjacencyMatrix[i].assign(n, false); + } + std::vector<int> incomingDegree(n); + std::queue<size_t> queue; + + // Populate the dependency matrix. + for (NodeIndexPairLinkList::iterator it = links[dim].begin(); + it != links[dim].end(); ++it) + { + adjacencyMatrix[it->first][it->second] = true; + } + + // Build incoming degree lookup structure, and add nodes with no + // incoming edges to queue. + for (size_t i = 0; i < n; ++i) + { + int degree = 0; + + for (size_t j = 0; j < n; ++j) + { + if (adjacencyMatrix[j][i]) + { + degree++; + } + } + incomingDegree[i] = degree; + + if (degree == 0) + { + queue.push(i); + } + } + + while (queue.empty() == false) + { + size_t k = queue.front(); + assert(k < nodes[dim].size()); + queue.pop(); + + // Insert node k into the sorted list + sortedConnVector[dim].push_back(nodes[dim][k]); + + // Remove all edges leaving node k: + for (size_t i = 0; i < n; ++i) + { + if (adjacencyMatrix[k][i]) + { + adjacencyMatrix[k][i] = false; + incomingDegree[i]--; + + if (incomingDegree[i] == 0) + { + queue.push(i); + } + } + } + } +} + + +// Returns a vertex number representing a point on the line between +// two shape corners, represented by p0 and p1. +// +static int midVertexNumber(const Point& p0, const Point& p1, const Point& c) +{ + if (c.vn != kUnassignedVertexNumber) + { + // The split point is a shape corner, so doesn't need its + // vertex number adjusting. + return c.vn; + } + if ((p0.vn >= 4) && (p0.vn < kUnassignedVertexNumber)) + { + // The point next to this has the correct nudging direction, + // so use that. + return p0.vn; + } + if ((p1.vn >= 4) && (p1.vn < kUnassignedVertexNumber)) + { + // The point next to this has the correct nudging direction, + // so use that. + return p1.vn; + } + if ((p0.vn < 4) && (p1.vn < 4)) + { + if (p0.vn != p1.vn) + { + return p0.vn; + } + // Splitting between two ordinary shape corners. + int vn_mid = std::min(p0.vn, p1.vn); + if ((std::max(p0.vn, p1.vn) == 3) && (vn_mid == 0)) + { + vn_mid = 3; // Next vn is effectively 4. + } + return vn_mid + 4; + } + COLA_ASSERT((p0.x == p1.x) || (p0.y == p1.y)); + if (p0.vn != kUnassignedVertexNumber) + { + if (p0.x == p1.x) + { + if ((p0.vn == 2) || (p0.vn == 3)) + { + return 6; + } + return 4; + } + else + { + if ((p0.vn == 0) || (p0.vn == 3)) + { + return 7; + } + return 5; + } + } + else if (p1.vn != kUnassignedVertexNumber) + { + if (p0.x == p1.x) + { + if ((p1.vn == 2) || (p1.vn == 3)) + { + return 6; + } + return 4; + } + else + { + if ((p1.vn == 0) || (p1.vn == 3)) + { + return 7; + } + return 5; + } + } + + // Shouldn't both be new (kUnassignedVertexNumber) points. + db_printf("midVertexNumber(): p0.vn and p1.vn both = " + "kUnassignedVertexNumber\n"); + db_printf("p0.vn %d p1.vn %d\n", p0.vn, p1.vn); + return kUnassignedVertexNumber; +} + + +// Break up overlapping parallel segments that are not the same edge in +// the visibility graph, i.e., where one segment is a subsegment of another. +void splitBranchingSegments(Avoid::Polygon& poly, bool polyIsConn, + Avoid::Polygon& conn, const double tolerance) +{ + for (std::vector<Avoid::Point>::iterator i = conn.ps.begin(); + i != conn.ps.end(); ++i) + { + if (i == conn.ps.begin()) + { + // Skip the first point. + // There are points-1 segments in a connector. + continue; + } + + for (std::vector<Avoid::Point>::iterator j = poly.ps.begin(); + j != poly.ps.end(); ) + { + if (polyIsConn && (j == poly.ps.begin())) + { + // Skip the first point. + // There are points-1 segments in a connector. + ++j; + continue; + } + Point& c0 = *(i - 1); + Point& c1 = *i; + + Point& p0 = (j == poly.ps.begin()) ? poly.ps.back() : *(j - 1); + Point& p1 = *j; + + // Check the first point of the first segment. + if (((i - 1) == conn.ps.begin()) && + pointOnLine(p0, p1, c0, tolerance)) + { + //db_printf("add to poly %g %g\n", c0.x, c0.y); + + c0.vn = midVertexNumber(p0, p1, c0); + j = poly.ps.insert(j, c0); + if (j != poly.ps.begin()) + { + --j; + } + continue; + } + // And the second point of every segment. + if (pointOnLine(p0, p1, c1, tolerance)) + { + //db_printf("add to poly %g %g\n", c1.x, c1.y); + + c1.vn = midVertexNumber(p0, p1, c1); + j = poly.ps.insert(j, c1); + if (j != poly.ps.begin()) + { + --j; + } + continue; + } + + // Check the first point of the first segment. + if (polyIsConn && ((j - 1) == poly.ps.begin()) && + pointOnLine(c0, c1, p0, tolerance)) + { + //db_printf("add to conn %g %g\n", p0.x, p0.y); + + p0.vn = midVertexNumber(c0, c1, p0); + i = conn.ps.insert(i, p0); + continue; + } + // And the second point of every segment. + if (pointOnLine(c0, c1, p1, tolerance)) + { + //db_printf("add to conn %g %g\n", p1.x, p1.y); + + p1.vn = midVertexNumber(c0, c1, p1); + i = conn.ps.insert(i, p1); + } + ++j; + } + } +} + + +static int segDir(const Point& p1, const Point& p2) +{ + int result = 1; + if (p1.x == p2.x) + { + if (p2.y > p1.y) + { + result = -1; + } + } + else if (p1.y == p2.y) + { + if (p2.x < p1.x) + { + result = -1; + } + } + return result; +} + + +static bool posInlineWithConnEndSegs(const double pos, const size_t dim, + const Avoid::Polygon& poly, const Avoid::Polygon& conn) +{ + size_t pLast = poly.size() - 1; + size_t cLast = conn.size() - 1; + if (( + // Is inline with the beginning of the "poly" line + ((pos == poly.ps[0][dim]) && (pos == poly.ps[1][dim])) || + // Is inline with the end of the "poly" line + ((pos == poly.ps[pLast][dim]) && (pos == poly.ps[pLast - 1][dim])) + ) && ( + // Is inline with the beginning of the "conn" line + ((pos == conn.ps[0][dim]) && (pos == conn.ps[1][dim])) || + // Is inline with the end of the "conn" line + ((pos == conn.ps[cLast][dim]) && (pos == conn.ps[cLast - 1][dim])) + )) + { + return true; + } + return false; +} + +ConnectorCrossings::ConnectorCrossings(Avoid::Polygon& poly, bool polyIsConn, + Avoid::Polygon& conn, ConnRef *polyConnRef, ConnRef *connConnRef) + : poly(poly), + polyIsConn(polyIsConn), + conn(conn), + checkForBranchingSegments(false), + polyConnRef(polyConnRef), + connConnRef(connConnRef), + crossingPoints(nullptr), + pointOrders(nullptr), + sharedPaths(nullptr) +{ +} + +void ConnectorCrossings::clear(void) +{ + crossingCount = 0; + crossingFlags = CROSSING_NONE; + firstSharedPathAtEndLength = DBL_MAX; + secondSharedPathAtEndLength = DBL_MAX; +} + + +// Computes the *shared* length of these two shared paths. +// +static double pathLength(Avoid::Point **c_path, Avoid::Point **p_path, + size_t size) +{ + double length = 0; + + for (unsigned int ind = 1; ind < size; ++ind) + { + if ( (*(c_path[ind - 1]) == *(p_path[ind - 1])) && + (*(c_path[ind]) == *(p_path[ind])) ) + { + // This segment is shared by both paths. + // + // This function will only be used for orthogonal paths, so we + // can use Manhattan distance here since it will be faster to + // compute. + length += manhattanDist(*(c_path[ind - 1]), *(c_path[ind])); + } + } + + return length; +} + +// Works out if the segment conn[cIndex-1]--conn[cIndex] really crosses poly. +// This does not not count non-crossing shared paths as crossings. +// poly can be either a connector (polyIsConn = true) or a cluster +// boundary (polyIsConn = false). +// +void ConnectorCrossings::countForSegment(size_t cIndex, const bool finalSegment) +{ + clear(); + + bool polyIsOrthogonal = (polyConnRef && + (polyConnRef->routingType() == ConnType_Orthogonal)); + bool connIsOrthogonal = (connConnRef && + (connConnRef->routingType() == ConnType_Orthogonal)); + + // Fixed routes will not have segment breaks at possible crossings. + bool polyIsFixed = (polyConnRef && polyConnRef->hasFixedRoute()); + bool connIsFixed = (connConnRef && connConnRef->hasFixedRoute()); + + // We need to split apart connectors at potential crossing points if + // either has a fixed route or it is a polyline connector. This is not + // needed for orthogonal connectors where crossings occur at vertices + // in visibility graph and on the raw connector routes. + if (checkForBranchingSegments || polyIsFixed || connIsFixed || + !polyIsOrthogonal || !connIsOrthogonal) + { + double epsilon = std::numeric_limits<double>::epsilon(); + size_t conn_pn = conn.size(); + // XXX When doing the pointOnLine test we allow the points to be + // slightly non-collinear. This addresses a problem with clustered + // routing where connectors could otherwise route cheaply through + // shape corners that were not quite on the cluster boundary, but + // reported to be on there by the line segment intersection code, + // which I suspect is not numerically accurate enough. This occurred + // for points that only differed by about 10^-12 in the y-dimension. + double tolerance = (!polyIsConn) ? epsilon : 0.0; + splitBranchingSegments(poly, polyIsConn, conn, tolerance); + // cIndex is going to be the last, so take into account added points. + cIndex += (conn.size() - conn_pn); + } + COLA_ASSERT(cIndex >= 1); + COLA_ASSERT(cIndex < conn.size()); + + size_t poly_size = poly.size(); + + Avoid::Point& a1 = conn.ps[cIndex - 1]; + Avoid::Point& a2 = conn.ps[cIndex]; + //db_printf("a1: %g %g\n", a1.x, a1.y); + //db_printf("a2: %g %g\n", a2.x, a2.y); + + // Allocate arrays for computing shared paths. + // Don't use dynamic array due to portablity issues. + size_t max_path_size = std::min(poly_size, conn.size()); + Avoid::Point **c_path = new Avoid::Point*[max_path_size]; + Avoid::Point **p_path = new Avoid::Point*[max_path_size]; + size_t size = 0; + + for (size_t j = ((polyIsConn) ? 1 : 0); j < poly_size; ++j) + { + Avoid::Point& b1 = poly.ps[(j - 1 + poly_size) % poly_size]; + Avoid::Point& b2 = poly.ps[j]; + //db_printf("b1: %g %g\n", b1.x, b1.y); + //db_printf("b2: %g %g\n", b2.x, b2.y); + + size = 0; + + bool converging = false; + + const bool a1_eq_b1 = (a1 == b1); + const bool a2_eq_b1 = (a2 == b1); + const bool a2_eq_b2 = (a2 == b2); + const bool a1_eq_b2 = (a1 == b2); + + if ( (a1_eq_b1 && a2_eq_b2) || + (a2_eq_b1 && a1_eq_b2) ) + { + if (finalSegment) + { + converging = true; + } + else + { + // Route along same segment: no penalty. We detect + // crossovers when we see the segments diverge. + continue; + } + } + else if (a2_eq_b1 || a2_eq_b2 || a1_eq_b2) + { + // Each crossing that is at a vertex in the + // visibility graph gets noticed four times. + // We ignore three of these cases. + // This also catches the case of a shared path, + // but this is one that terminates at a common + // endpoint, so we don't care about it. + continue; + } + + if (a1_eq_b1 || converging) + { + if (!converging) + { + if (polyIsConn && (j == 1)) + { + // Can't be the end of a shared path or crossing path + // since the common point is the first point of the + // connector path. This is not a shared path at all. + continue; + } + + Avoid::Point& b0 = poly.ps[(j - 2 + poly_size) % poly_size]; + // The segments share an endpoint -- a1==b1. + if (a2 == b0) + { + // a2 is not a split, continue. + continue; + } + } + + // If here and not converging, then we know that a2 != b2 + // And a2 and its pair in b are a split. + COLA_ASSERT(converging || !a2_eq_b2); + + bool shared_path = false; + + // Initial values here don't matter. They are only used after + // being set to sensible values, but we set them to stop a MSVC + // warning. + bool p_dir_back; + int p_dir = 0; + int trace_c = 0; + int trace_p = 0; + + if (converging) + { + // Determine direction we have to look through + // the points of connector b. + p_dir_back = a2_eq_b2 ? true : false; + p_dir = p_dir_back ? -1 : 1; + trace_c = (int) cIndex; + trace_p = (int) j; + if (!p_dir_back) + { + if (finalSegment) + { + trace_p--; + } + else + { + trace_c--; + } + } + + shared_path = true; + } + else if (cIndex >= 2) + { + Avoid::Point& b0 = poly.ps[(j - 2 + poly_size) % poly_size]; + Avoid::Point& a0 = conn.ps[cIndex - 2]; + + //db_printf("a0: %g %g\n", a0.x, a0.y); + //db_printf("b0: %g %g\n", b0.x, b0.y); + + if ((a0 == b2) || (a0 == b0)) + { + // Determine direction we have to look through + // the points of connector b. + p_dir_back = (a0 == b0) ? true : false; + p_dir = p_dir_back ? -1 : 1; + trace_c = (int) cIndex; + trace_p = (int) (p_dir_back ? j : j - 2); + + shared_path = true; + } + } + + if (shared_path) + { + crossingFlags |= CROSSING_SHARES_PATH; + // Shouldn't be here if p_dir is still equal to zero. + COLA_ASSERT(p_dir != 0); + + // Build the shared path, including the diverging points at + // each end if the connector does not end at a common point. + while ( (trace_c >= 0) && (!polyIsConn || + ((trace_p >= 0) && (trace_p < (int) poly_size))) ) + { + // If poly is a cluster boundary, then it is a closed + // poly-line and so it wraps around. + size_t index_p = (size_t) + ((trace_p + (2 * poly_size)) % poly_size); + size_t index_c = (size_t) trace_c; + c_path[size] = &conn.ps[index_c]; + p_path[size] = &poly.ps[index_p]; + ++size; + if ((size > 1) && (conn.ps[index_c] != poly.ps[index_p])) + { + // Points don't match, so break out of loop. + break; + } + trace_c--; + trace_p += p_dir; + } + + // Are there diverging points at the ends of the shared path. + bool front_same = (*(c_path[0]) == *(p_path[0])); + bool back_same = (*(c_path[size - 1]) == *(p_path[size - 1])); + + // Determine if the shared path originates at a junction. + bool terminatesAtJunction = false; + if (polyConnRef && connConnRef && (front_same || back_same)) + { + // To do this we find the two ConnEnds at the common + // end of the shared path. Then check if they are + // attached to a junction and it is the same one. + std::pair<ConnEnd, ConnEnd> connEnds = + connConnRef->endpointConnEnds(); + JunctionRef *connJunction = nullptr; + + std::pair<ConnEnd, ConnEnd> polyEnds = + polyConnRef->endpointConnEnds(); + JunctionRef *polyJunction = nullptr; + + // The front of the c_path corresponds to destination + // of the connector. + connJunction = (front_same) ? connEnds.second.junction() : + connEnds.first.junction(); + bool use_first = back_same; + if (p_dir_back) + { + // Reversed, so use opposite. + use_first = !use_first; + } + // The front of the p_path corresponds to destination + // of the connector. + polyJunction = (use_first) ? polyEnds.second.junction() : + polyEnds.first.junction(); + + terminatesAtJunction = (connJunction && + (connJunction == polyJunction)); + } + + if (sharedPaths) + { + // Store a copy of the shared path + size_t start = (front_same) ? 0 : 1; + size_t limit = size - ((back_same) ? 0 : 1); + + PointList sPath(limit - start); + for (size_t i = start; i < limit; ++i) + { + sPath[i - start] = *(c_path[i]); + } + sharedPaths->push_back(sPath); + } + + // Check to see if these share a fixed segment. + if (polyIsOrthogonal && connIsOrthogonal) + { + size_t startPt = (front_same) ? 0 : 1; + size_t endPt = size - ((back_same) ? 1 : 2); + for (size_t dim = 0; dim < 2; ++dim) + { + if ((*c_path[startPt])[dim] == (*c_path[endPt])[dim]) + { + double pos = (*c_path[startPt])[dim]; + // See if this is inline with either the start + // or end point of both connectors. We don't + // count them as crossing if they originate at a + // junction and are part of the same hyperedge. + if ( ((pos == poly.ps[0][dim]) || + (pos == poly.ps[poly_size - 1][dim])) && + ((pos == conn.ps[0][dim]) || + (pos == conn.ps[cIndex][dim])) && + (terminatesAtJunction == false)) + { + crossingFlags |= CROSSING_SHARES_FIXED_SEGMENT; + } + } + } + + if (!front_same && !back_same) + { + // Find overlapping segments that are constrained by + // the fact that both the adjoining segments are fixed + // in the other dimension, i.e., + // + // X------++---X + // || + // || + // X---++------X + // + // In the example above, altDim is X, and dim is Y. + // + + // For each dimension... + for (size_t dim = 0; dim < 2; ++dim) + { + size_t end = size - 1; + size_t altDim = (dim + 1) % 2; + // If segment is in this dimension... + if ((*c_path[1])[altDim] == (*c_path[end - 1])[altDim]) + { + double posBeg = (*c_path[1])[dim]; + double posEnd = (*c_path[end - 1])[dim]; + // If both segment ends diverge at right-angles... + if ( (posBeg == (*c_path[0])[dim]) && + (posBeg == (*p_path[0])[dim]) && + (posEnd == (*c_path[end])[dim]) && + (posEnd == (*p_path[end])[dim]) ) + { + // and these segments are inline with the conn and path ends themselves... + if (posInlineWithConnEndSegs(posBeg, dim, + conn, poly) && + posInlineWithConnEndSegs(posEnd, dim, + conn, poly)) + { + // If all endpoints branch at right angles, + // then penalise this since it is a segment + // will will not be able to nudge apart + // without introducing fixed segment + // crossings. + crossingFlags |= + CROSSING_SHARES_FIXED_SEGMENT; + } + } + } + } + } + +#if 0 + // XXX: What is this code for? It is pretty much + // incomprehensible and also causes one of the test + // cases to fail. + // + if (!front_same && !back_same) + { + for (size_t dim = 0; dim < 2; ++dim) + { + size_t altDim = (dim + 1) % 2; + if ((*c_path[1])[altDim] == (*c_path[1])[altDim]) + { + size_t n = c_path.size(); + double yPosB = (*c_path[1])[dim]; + if ( (yPosB == (*c_path[0])[dim]) && + (yPosB == (*p_path[0])[dim]) ) + { + crossingFlags |= + CROSSING_SHARES_FIXED_SEGMENT; + } + + double yPosE = (*c_path[n - 2])[dim]; + if ( (yPosE == (*c_path[n - 1])[dim]) && + (yPosE == (*p_path[n - 1])[dim]) ) + { + crossingFlags |= + CROSSING_SHARES_FIXED_SEGMENT; + } + } + } + } +#endif + } + + // {start,end}CornerSide specifies which side of conn the + // poly path enters and leaves. + int startCornerSide = 1; + int endCornerSide = 1; + + bool reversed = false; + if (!front_same) + { + // If there is a divergence at the beginning, + // then order the shared path based on this. + startCornerSide = Avoid::cornerSide(*c_path[0], *c_path[1], + *c_path[2], *p_path[0]); + } + if (!back_same) + { + // If there is a divergence at the end of the path, + // then order the shared path based on this. + endCornerSide = Avoid::cornerSide(*c_path[size - 3], + *c_path[size - 2], *c_path[size - 1], + *p_path[size - 1]); + } + else + { + endCornerSide = startCornerSide; + } + if (front_same) + { + startCornerSide = endCornerSide; + } + + if (endCornerSide != startCornerSide) + { + // Mark that the shared path crosses. + //db_printf("shared path crosses.\n"); + crossingCount += 1; + if (crossingPoints) + { + crossingPoints->insert(*c_path[1]); + } + } + + if (front_same || back_same) + { + crossingFlags |= CROSSING_SHARES_PATH_AT_END; + + // Reduce the cost of paths that stay straight after + // the split, and make this length available so that the + // paths can be ordered during the improveCrossings() + // step and the straight (usually better) paths will be + // left alone while the router attempts to find better + // paths for the others. + double straightModifier = 200; + firstSharedPathAtEndLength = secondSharedPathAtEndLength = + pathLength(c_path, p_path, size); + if (back_same && (size > 2)) + { + if (vecDir(*p_path[0], *p_path[1], *p_path[2]) == 0) + { + firstSharedPathAtEndLength -= straightModifier; + } + + if (vecDir(*c_path[0], *c_path[1], *c_path[2]) == 0) + { + secondSharedPathAtEndLength -= straightModifier; + } + } + else if (front_same && (size > 2)) + { + if (vecDir(*p_path[size - 3], *p_path[size - 2], + *p_path[size - 1]) == 0) + { + firstSharedPathAtEndLength -= straightModifier; + } + + if (vecDir(*c_path[size - 3], *c_path[size - 2], + *c_path[size - 1]) == 0) + { + secondSharedPathAtEndLength -= straightModifier; + } + } + } + else if (polyIsOrthogonal && connIsOrthogonal) + { + int cStartDir = vecDir(*c_path[0], *c_path[1], *c_path[2]); + int pStartDir = vecDir(*p_path[0], *p_path[1], *p_path[2]); + if ((cStartDir != 0) && (cStartDir == -pStartDir)) + { + // The start segments diverge at 180 degrees to each + // other. So order based on not introducing overlap + // of the diverging segments when these are nudged + // apart. + startCornerSide = -cStartDir; + } + else + { + int cEndDir = vecDir(*c_path[size - 3], + *c_path[size - 2], *c_path[size - 1]); + int pEndDir = vecDir(*p_path[size - 3], + *p_path[size - 2], *p_path[size - 1]); + if ((cEndDir != 0) && (cEndDir == -pEndDir)) + { + // The end segments diverge at 180 degrees to + // each other. So order based on not introducing + // overlap of the diverging segments when these + // are nudged apart. + startCornerSide = -cEndDir; + } + } + } + +#if 0 + int prevTurnDir = 0; + if (pointOrders) + { + // Return the ordering for the shared path. + COLA_ASSERT(c_path.size() > 0 || back_same); + size_t adj_size = (c_path.size() - ((back_same) ? 0 : 1)); + for (size_t i = (front_same) ? 0 : 1; i < adj_size; ++i) + { + Avoid::Point& an = *(c_path[i]); + Avoid::Point& bn = *(p_path[i]); + int currTurnDir = ((i > 0) && (i < (adj_size - 1))) ? + vecDir(*c_path[i - 1], an, + *c_path[i + 1]) : 0; + VertID vID(an.id, true, an.vn); + if ( (currTurnDir == (-1 * prevTurnDir)) && + (currTurnDir != 0) && (prevTurnDir != 0) ) + { + // The connector turns the opposite way around + // this shape as the previous bend on the path, + // so reverse the order so that the inner path + // become the outer path and vice versa. + reversed = !reversed; + } + bool orderSwapped = (*pointOrders)[an].addOrderedPoints( + &bn, &an, reversed); + if (orderSwapped) + { + // Reverse the order for later points. + reversed = !reversed; + } + prevTurnDir = currTurnDir; + } + } +#endif + if (pointOrders) + { + reversed = false; + size_t startPt = (front_same) ? 0 : 1; + + // Orthogonal should always have at least one segment. + COLA_ASSERT(size > (startPt + 1)); + + if (startCornerSide > 0) + { + reversed = !reversed; + } + + int prevDir = 0; + // Return the ordering for the shared path. + COLA_ASSERT(size > 0 || back_same); + size_t adj_size = (size - ((back_same) ? 0 : 1)); + for (size_t i = startPt; i < adj_size; ++i) + { + Avoid::Point& an = *(c_path[i]); + Avoid::Point& bn = *(p_path[i]); + COLA_ASSERT(an == bn); + + if (i > startPt) + { + Avoid::Point& ap = *(c_path[i - 1]); + Avoid::Point& bp = *(p_path[i - 1]); + + int thisDir = segDir(ap, an); + if (prevDir == 0) + { + if (thisDir > 0) + { + reversed = !reversed; + } + } + else if (thisDir != prevDir) + { + reversed = !reversed; + } + + int orientation = (ap.x == an.x) ? 0 : 1; + //printf("prevOri %d\n", prevOrientation); + //printf("1: %X, %X\n", (int) &(bn), (int) &(an)); + (*pointOrders)[an].addOrderedPoints( + orientation, + std::make_pair(&bn, polyConnRef), + std::make_pair(&an, connConnRef), + reversed); + COLA_ASSERT(ap == bp); + //printf("2: %X, %X\n", (int) &bp, (int) &ap); + (*pointOrders)[ap].addOrderedPoints( + orientation, + std::make_pair(&bp, polyConnRef), + std::make_pair(&ap, connConnRef), + reversed); + prevDir = thisDir; + } + } + } +#if 0 + int ymod = -1; + if ((id.vn == 1) || (id.vn == 2)) + { + // bottom. + ymod = +1; + } + + int xmod = -1; + if ((id.vn == 0) || (id.vn == 1)) + { + // right. + xmod = +1; + } + if(id.vn > 3) + { + xmod = ymod = 0; + if (id.vn == 4) + { + // right. + xmod = +1; + } + else if (id.vn == 5) + { + // bottom. + ymod = +1; + } + else if (id.vn == 6) + { + // left. + xmod = -1; + } + else if (id.vn == 7) + { + // top. + ymod = -1; + } + } +#endif + + crossingFlags |= CROSSING_TOUCHES; + } + else if (cIndex >= 2) + { + // The connectors cross or touch at this point. + //db_printf("Cross or touch at point... \n"); + + // Crossing shouldn't be at an endpoint. + COLA_ASSERT(cIndex >= 2); + COLA_ASSERT(!polyIsConn || (j >= 2)); + + Avoid::Point& b0 = poly.ps[(j - 2 + poly_size) % poly_size]; + Avoid::Point& a0 = conn.ps[cIndex - 2]; + + int side1 = Avoid::cornerSide(a0, a1, a2, b0); + int side2 = Avoid::cornerSide(a0, a1, a2, b2); + if (side1 != side2) + { + // The connectors cross at this point. + //db_printf("cross.\n"); + crossingCount += 1; + if (crossingPoints) + { + crossingPoints->insert(a1); + } + } + + crossingFlags |= CROSSING_TOUCHES; + if (pointOrders) + { + if (polyIsOrthogonal && connIsOrthogonal) + { + // Orthogonal case: + // Just order based on which comes from the left and + // top in each dimension because this can only be two + // L-shaped segments touching at the bend. + bool reversedX = ((a0.x < a1.x) || (a2.x < a1.x)); + bool reversedY = ((a0.y < a1.y) || (a2.y < a1.y)); + // XXX: Why do we need to invert the reversed values + // here? Are they wrong for orthogonal points + // in the other places? + (*pointOrders)[b1].addOrderedPoints(0, + std::make_pair(&b1, polyConnRef), + std::make_pair(&a1, connConnRef), + !reversedX); + (*pointOrders)[b1].addOrderedPoints(1, + std::make_pair(&b1, polyConnRef), + std::make_pair(&a1, connConnRef), + !reversedY); + } +#if 0 +// Unused code. + else + { + int turnDirA = vecDir(a0, a1, a2); + int turnDirB = vecDir(b0, b1, b2); + bool reversed = (side1 != -turnDirA); + if (side1 != side2) + { + // Interesting case where a connector routes round + // the edge of a shape and intersects a connector + // which is connected to a port on the edge of the + // shape. + if (turnDirA == 0) + { + // We'll make B the outer by preference, + // because the points of A are collinear. + reversed = false; + } + else if (turnDirB == 0) + { + reversed = true; + } + // TODO COLA_ASSERT((turnDirB != 0) || + // (turnDirA != 0)); + } + VertID vID(b1.id, b1.vn); + //(*pointOrders)[b1].addOrderedPoints(&b1, &a1, reversed); + } +#endif + } + } + } + else + { + if ( polyIsOrthogonal && connIsOrthogonal) + { + // All crossings in orthogonal connectors will be at a + // vertex in the visibility graph, so we need not bother + // doing normal line intersection. + continue; + } + + // No endpoint is shared between these two line segments, + // so just calculate normal segment intersection. + + Point cPt; + int intersectResult = Avoid::segmentIntersectPoint( + a1, a2, b1, b2, &(cPt.x), &(cPt.y)); + + if (intersectResult == Avoid::DO_INTERSECT) + { + if (!polyIsConn && + ((a1 == cPt) || (a2 == cPt) || (b1 == cPt) || (b2 == cPt))) + { + // XXX: This shouldn't actually happen, because these + // points should be added as bends to each line by + // splitBranchingSegments(). Thus, lets ignore them. + COLA_ASSERT(a1 != cPt); + COLA_ASSERT(a2 != cPt); + COLA_ASSERT(b1 != cPt); + COLA_ASSERT(b2 != cPt); + continue; + } + //db_printf("crossing lines:\n"); + //db_printf("cPt: %g %g\n", cPt.x, cPt.y); + crossingCount += 1; + if (crossingPoints) + { + crossingPoints->insert(cPt); + } + } + } + } + //db_printf("crossingcount %d %d\n", crossingCount, crossingFlags); + + // Free shared path memory. + delete[] c_path; + delete[] p_path; +} + + +//============================================================================ + +} + + |