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/makepath.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-upstream/1.2.2.tar.xz inkscape-upstream/1.2.2.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/makepath.cpp | 1554 |
1 files changed, 1554 insertions, 0 deletions
diff --git a/src/3rdparty/adaptagrams/libavoid/makepath.cpp b/src/3rdparty/adaptagrams/libavoid/makepath.cpp new file mode 100644 index 0000000..8e8f4f0 --- /dev/null +++ b/src/3rdparty/adaptagrams/libavoid/makepath.cpp @@ -0,0 +1,1554 @@ +/* + * 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 +*/ + +// For M_PI. +// This should be first include for MSVC. +#define _USE_MATH_DEFINES +#include <cmath> + +#include <algorithm> +#include <vector> +#include <climits> +#include <cfloat> + +#include "libavoid/makepath.h" +#include "libavoid/vertices.h" +#include "libavoid/geometry.h" +#include "libavoid/connector.h" +#include "libavoid/viscluster.h" +#include "libavoid/graph.h" +#include "libavoid/router.h" +#include "libavoid/debug.h" +#include "libavoid/assertions.h" +#include "libavoid/debughandler.h" + +//#define ESTIMATED_COST_DEBUG + +namespace Avoid { + +class ANode +{ + public: + VertInf* inf; + double g; // Gone + double h; // Heuristic + double f; // Formula f = g + h + + ANode *prevNode; // VertInf for the previous ANode. + int timeStamp; // Time-stamp used to determine exploration order of + // seemingly equal paths during orthogonal routing. + + ANode(VertInf *vinf, int time) + : inf(vinf), + g(0), + h(0), + f(0), + prevNode(nullptr), + timeStamp(time) + { + } + ANode() + : inf(nullptr), + g(0), + h(0), + f(0), + prevNode(nullptr), + timeStamp(-1) + { + } +}; + +class AStarPathPrivate +{ + public: + AStarPathPrivate() + : m_available_nodes(), + m_available_array_size(0), + m_available_array_index(0), + m_available_node_index(0) + { + } + ~AStarPathPrivate() + { + // Free memory + for (size_t i = 0; i < m_available_nodes.size(); ++i) + { + delete[] m_available_nodes[i]; + } + } + // Returns a pointer to an ANode for aStar search, but allocates + // these in blocks + ANode *newANode(const ANode& node, const bool addToPending = true) + { + const size_t blockSize = 5000; + if ((m_available_array_index + 1 > m_available_array_size) || + (m_available_node_index >= blockSize)) + { + m_available_nodes.push_back(new ANode[blockSize]); + ++m_available_array_size; + m_available_node_index = 0; + m_available_array_index = m_available_array_size - 1; + } + + ANode *nodes = m_available_nodes[m_available_array_index]; + ANode *newNode = &(nodes[m_available_node_index++]); + *newNode = node; + if (addToPending) + { + node.inf->aStarPendingNodes.push_back(newNode); + } + return newNode; + } + void search(ConnRef *lineRef, VertInf *src, VertInf *tar, + VertInf *start); + + private: + void determineEndPointLocation(double dist, VertInf *start, + VertInf *target, VertInf *other, int level); + double estimatedCost(ConnRef *lineRef, const Point *last, + const Point& curr) const; + + std::vector<ANode *> m_available_nodes; + size_t m_available_array_size; + size_t m_available_array_index; + size_t m_available_node_index; + + // For determining estimated cost target. + std::vector<VertInf *> m_cost_targets; + std::vector<unsigned int> m_cost_targets_directions; + std::vector<double> m_cost_targets_displacements; +}; + + + +// This returns the opposite result (>) so that when used with stl::make_heap, +// the head node of the heap will be the smallest value, rather than the +// largest. This saves us from having to sort the heap (and then reorder +// it back into a heap) when getting the next node to examine. This way we +// get better complexity -- logarithmic pushes and pops to the heap. +// +class ANodeCmp +{ + public: + ANodeCmp() + { + } +bool operator()(const ANode *a, const ANode *b) +{ + // We need to use an epsilon here since otherwise the multiple addition + // of floating point numbers that makes up the 'f' values cause a problem + // with routings occasionally being non-deterministic. + if (fabs(a->f - b->f) > 0.0000001) + { + return a->f > b->f; + } + if (a->timeStamp != b->timeStamp) + { + // Tiebreaker, if two paths have equal cost, then choose the one with + // the highest timeStamp. This corresponds to the furthest point + // explored along the straight-line path. When exploring we give the + // directions the following timeStamps; left:1, right:2 and forward:3, + // then we always try to explore forward first. + return a->timeStamp < b->timeStamp; + } + return false; +} +}; + + +static double Dot(const Point& l, const Point& r) +{ + return (l.x * r.x) + (l.y * r.y); +} + +static double CrossLength(const Point& l, const Point& r) +{ + return (l.x * r.y) - (l.y * r.x); +} + + +// Return the angle between the two line segments made by the +// points p1--p2 and p2--p3. Return value is in radians. +// +static double angleBetween(const Point& p1, const Point& p2, const Point& p3) +{ + if ((p1.x == p2.x && p1.y == p2.y) || (p2.x == p3.x && p2.y == p3.y)) + { + // If two of the points are the same, then we can't say anything + // about the angle between. Treat them as being collinear. + return M_PI; + } + + Point v1(p1.x - p2.x, p1.y - p2.y); + Point v2(p3.x - p2.x, p3.y - p2.y); + + return fabs(atan2(CrossLength(v1, v2), Dot(v1, v2))); +} + + +// Construct a temporary Polygon path given several VertInf's for a connector. +// +static void constructPolygonPath(Polygon& connRoute, VertInf *inf2, + VertInf *inf3, ANode *inf1Node) +{ + // Don't include colinear points. + bool simplified = true; + + int routeSize = 2; + for (ANode *curr = inf1Node; curr != nullptr; curr = curr->prevNode) + { + routeSize += 1; + } + connRoute.ps.resize(routeSize); + int arraySize = routeSize; + connRoute.ps[routeSize - 1] = inf3->point; + connRoute.ps[routeSize - 2] = inf2->point; + routeSize -= 3; + for (ANode *curr = inf1Node; curr != nullptr; curr = curr->prevNode) + { + // For connection pins, we stop and don't include the fake shape + // center as part of this path. + bool isConnectionPin = curr->inf->id.isConnectionPin(); + + if (!simplified) + { + // If this is non-simplified, we don't need to do anything + // clever and can simply add the new point. + connRoute.ps[routeSize] = curr->inf->point; + routeSize -= 1; + + if (isConnectionPin) + { + // Stop at the connection pin. + break; + } + continue; + } + + if ((curr == inf1Node) || + vecDir(curr->inf->point, connRoute.ps[routeSize + 1], + connRoute.ps[routeSize + 2]) != 0) + { + // Add new point if this is the earlier than the last segment + // and it is not colinear with the other points. + // Note, you can't collapse the 'last' segment with previous + // segments, or if this just intersects another line you risk + // penalising it once for each collapsed line segment. + connRoute.ps[routeSize] = curr->inf->point; + routeSize -= 1; + } + else + { + // The last point is inline with this one, so update it. + connRoute.ps[routeSize + 1] = curr->inf->point; + } + + if (isConnectionPin) + { + // Stop at the connection pin. + break; + } + } + + // If the vector is not filled, move entries to the beginning and + // remove the unused end of the vector. + int diff = routeSize + 1; + COLA_ASSERT(simplified || (diff == 0)); + if (diff > 0) + { + for (int i = diff; i < arraySize; ++i) + { + connRoute.ps[i - diff] = connRoute.ps[i]; + } + connRoute.ps.resize(connRoute.size() - diff); + } +} + +// Used to get an indication of if a diffence is positive (1), +// negative (-1) or no different (0). +static inline int dimDirection(double difference) +{ + if (difference > 0) + { + return 1; + } + else if (difference < 0) + { + return -1; + } + return 0; +} + +// Given the two points for a new segment of a path (inf2 & inf3) +// as well as the distance between these points (dist), as well as +// possibly the previous point (inf1) [from inf1--inf2], return a +// cost associated with this route. +// +static double cost(ConnRef *lineRef, const double dist, VertInf *inf2, + VertInf *inf3, ANode *inf1Node) +{ + bool isOrthogonal = (lineRef->routingType() == ConnType_Orthogonal); + VertInf *inf1 = (inf1Node) ? inf1Node->inf : nullptr; + double result = dist; + Polygon connRoute; + + Router *router = inf2->_router; + if (inf1 != nullptr) + { + const double angle_penalty = router->routingParameter(anglePenalty); + const double segmt_penalty = router->routingParameter(segmentPenalty); + + // This is not the first segment, so there is a bend + // between it and the last one in the existing path. + if ((angle_penalty > 0) || (segmt_penalty > 0)) + { + Point p1 = inf1->point; + Point p2 = inf2->point; + Point p3 = inf3->point; + + double rad = M_PI - angleBetween(p1, p2, p3); + + if ((rad > 0) && !isOrthogonal) + { + // Make `xval' between 0--10 then take its log so small + // angles are not penalised as much as large ones. + // + double xval = rad * 10 / M_PI; + double yval = xval * log10(xval + 1) / 10.5; + result += (angle_penalty * yval); + //db_printf("deg from straight: %g\tpenalty: %g\n", + // rad * 180 / M_PI, (angle_penalty * yval)); + } + + if (rad == M_PI) + { + // Needs to double back + result += (2 * segmt_penalty); + } + else if (rad > 0) + { + // Only penalise as an extra segment if the two + // segments are not collinear. + result += segmt_penalty; + } + } + } + + const double cluster_crossing_penalty = + router->routingParameter(clusterCrossingPenalty); + // XXX: Clustered routing doesn't yet work with orthogonal connectors. + if (router->ClusteredRouting && !router->clusterRefs.empty() && + (cluster_crossing_penalty > 0)) + { + if (connRoute.empty()) + { + constructPolygonPath(connRoute, inf2, inf3, inf1Node); + } + // There are clusters so do cluster routing. + for (ClusterRefList::const_iterator cl = router->clusterRefs.begin(); + cl != router->clusterRefs.end(); ++cl) + { + Polygon cBoundary = (isOrthogonal) ? + (*cl)->rectangularPolygon() : (*cl)->polygon(); + if (cBoundary.size() <= 2) + { + continue; + } + COLA_ASSERT(cBoundary.ps[0] != cBoundary.ps[cBoundary.size() - 1]); + for (size_t j = 0; j < cBoundary.size(); ++j) + { + // Non-orthogonal cluster boundary points should correspond to + // shape vertices and hence already be in the list of vertices. + COLA_ASSERT(isOrthogonal || + router->vertices.getVertexByPos(cBoundary.at(j))); + } + + bool isConn = false; + Polygon dynamic_conn_route(connRoute); + const bool finalSegment = (inf3 == lineRef->dst()); + ConnectorCrossings cross(cBoundary, isConn, dynamic_conn_route); + cross.checkForBranchingSegments = true; + cross.countForSegment(connRoute.size() - 1, finalSegment); + + result += (cross.crossingCount * cluster_crossing_penalty); + } + } + + // This penalty penalises route segments that head in a direction opposite + // of the direction(s) toward the target point. + const double reversePenalty = router->routingParameter( + reverseDirectionPenalty); + if (reversePenalty) + { + // X and Y direction of destination from source point. + const Point& srcPoint = lineRef->src()->point; + const Point& dstPoint = lineRef->dst()->point; + int xDir = dimDirection(dstPoint.x - srcPoint.x); + int yDir = dimDirection(dstPoint.y - srcPoint.y); + + bool doesReverse = false; + + if ((xDir != 0) && + (-xDir == dimDirection(inf3->point.x - inf2->point.x))) + { + // Connector has an X component and the segment heads in the + // opposite direction. + doesReverse |= true; + } + + if ((yDir != 0) && + (-yDir == dimDirection(inf3->point.y - inf2->point.y))) + { + // Connector has an Y component and the segment heads in the + // opposite direction. + doesReverse |= true; + } + + if (doesReverse) + { + result += reversePenalty; + } + } + + if (!router->isInCrossingPenaltyReroutingStage()) + { + // Return here if we are not in the post-processing stage + return result; + } + + const double crossing_penalty = router->routingParameter(crossingPenalty); + const double shared_path_penalty = + router->routingParameter(fixedSharedPathPenalty); + if ((shared_path_penalty > 0) || (crossing_penalty > 0)) + { + if (connRoute.empty()) + { + constructPolygonPath(connRoute, inf2, inf3, inf1Node); + } + ConnRefList::const_iterator curr, finish = router->connRefs.end(); + for (curr = router->connRefs.begin(); curr != finish; ++curr) + { + ConnRef *connRef = *curr; + + if (connRef->id() == lineRef->id()) + { + continue; + } + const Avoid::PolyLine& route2 = connRef->displayRoute(); + + bool isConn = true; + Polygon dynamic_route2(route2); + Polygon dynamic_conn_route(connRoute); + const bool finalSegment = (inf3->point == lineRef->dst()->point); + ConnectorCrossings cross(dynamic_route2, isConn, + dynamic_conn_route, connRef, lineRef); + cross.checkForBranchingSegments = true; + cross.countForSegment(connRoute.size() - 1, finalSegment); + + if ((cross.crossingFlags & CROSSING_SHARES_PATH) && + (cross.crossingFlags & CROSSING_SHARES_FIXED_SEGMENT) && + (router->routingOption( + penaliseOrthogonalSharedPathsAtConnEnds) || + !(cross.crossingFlags & CROSSING_SHARES_PATH_AT_END))) + { + // Penalise unnecessary shared paths in the middle of + // connectors. + result += shared_path_penalty; + } + result += (cross.crossingCount * crossing_penalty); + } + } + + return result; +} + +// Directions for estimated orthgonal cost, as bitflags. +static const unsigned int CostDirectionN = 1; +static const unsigned int CostDirectionE = 2; +static const unsigned int CostDirectionS = 4; +static const unsigned int CostDirectionW = 8; + +#ifdef ESTIMATED_COST_DEBUG +static void printDirections(FILE *fp, unsigned int directions) +{ + if (directions & CostDirectionN) + { + fprintf(fp, "N "); + } + if (directions & CostDirectionE) + { + fprintf(fp, "E "); + } + if (directions & CostDirectionS) + { + fprintf(fp, "S "); + } + if (directions & CostDirectionW) + { + fprintf(fp, "W "); + } +} +#endif + +// Returns the number of directions for the argument. +static unsigned int orthogonalDirectionsCount(const unsigned int directions) +{ + unsigned int count = 0; + if (directions & CostDirectionN) + { + ++count; + } + if (directions & CostDirectionE) + { + ++count; + } + if (directions & CostDirectionS) + { + ++count; + } + if (directions & CostDirectionW) + { + ++count; + } + return count; +} + +// Returns the directions of point b from point a. +static unsigned int orthogonalDirection(const Point &a, const Point &b) +{ + unsigned int result = 0; + + if (b.y > a.y) + { + result |= CostDirectionS; + } + else if (b.y < a.y) + { + result |= CostDirectionN; + } + + if (b.x > a.x) + { + result |= CostDirectionE; + } + else if (b.x < a.x) + { + result |= CostDirectionW; + } + + return result; +} + +// Returns the direction to the right of the given direction. +static unsigned int dirRight(unsigned int direction) +{ + if (direction == CostDirectionN) + { + return CostDirectionE; + } + else if (direction == CostDirectionE) + { + return CostDirectionS; + } + else if (direction == CostDirectionS) + { + return CostDirectionW; + } + else if (direction == CostDirectionW) + { + return CostDirectionN; + } + + // Should not be possible to reach here. + COLA_ASSERT(false); + return direction; +} + +// Returns the direction to the left of the given direction. +static unsigned int dirLeft(unsigned int direction) +{ + if (direction == CostDirectionN) + { + return CostDirectionW; + } + else if (direction == CostDirectionE) + { + return CostDirectionN; + } + else if (direction == CostDirectionS) + { + return CostDirectionE; + } + else if (direction == CostDirectionW) + { + return CostDirectionS; + } + + // Should not be possible to reach here. + COLA_ASSERT(false); + return direction; +} + +// Returns the reverse direction to the given direction. +static unsigned int dirReverse(unsigned int direction) +{ + if (direction == CostDirectionN) + { + return CostDirectionS; + } + else if (direction == CostDirectionE) + { + return CostDirectionW; + } + else if (direction == CostDirectionS) + { + return CostDirectionN; + } + else if (direction == CostDirectionW) + { + return CostDirectionE; + } + + // Should not be possible to reach here. + COLA_ASSERT(false); + return direction; +} + +// Given Point curr with a direction of currDir, returns the nimimum number +// of bends to reach Point dest with the entry direction of destDir +// +// This is used for estimating the bend penalty cost to the target point +// from the current point of the search. The geometry was described in the +// "Orthogonal Connector Routing" paper, although the version described +// there is incorrect. +// +int bends(const Point& curr, unsigned int currDir, const Point& dest, + unsigned int destDir) +{ + // Bend counts from 'o' to 'D' should be: + // + // 1 1 3 + // v v v + // 2 > o < 2 2 > o < 2 4 > o < 2 + // ^ ^ ^ + // 3 3 3 + // + // 0 > o < 4 D--> 4 > o < 4 + // ^ ^ + // 1 3 + // + COLA_ASSERT(currDir != 0); + unsigned int currToDestDir = orthogonalDirection(curr, dest); + unsigned int reverseDestDir = dirReverse(destDir); + bool currDirPerpendicularToDestDir = + (currDir == dirLeft(destDir)) || (currDir == dirRight(destDir)); + + if ((currDir == destDir) && + (currToDestDir == currDir)) + { + // + // 0 > o D--> + // + return 0; + } + else if (currDirPerpendicularToDestDir && + (currToDestDir == (destDir | currDir))) + { + // + // 1 + // v + // o + // + // + // D--> + // + return 1; + } + else if (currDirPerpendicularToDestDir && + (currToDestDir == currDir)) + { + // + // 1 + // v + // o + // + // + // D--> + // + return 1; + } + else if (currDirPerpendicularToDestDir && + (currToDestDir == destDir)) + { + // + // o D--> + // ^ + // 1 + // + return 1; + } + else if ((currDir == destDir) && + (currToDestDir != currDir) && + !(currToDestDir & reverseDestDir)) + { + // + // 2 > o 2 > o + // + // + // D--> + // + return 2; + } + else if (currDir == reverseDestDir && + (currToDestDir != destDir) && + (currToDestDir != currDir)) + { + // + // o < 2 o < 2 o < 2 + // + // + // D--> + // + return 2; + } + else if (currDirPerpendicularToDestDir && + (currToDestDir != (destDir | currDir)) && + (currToDestDir != currDir)) + { + // + // 3 + // v + // o o o + // ^ ^ ^ + // 3 3 3 + // + // D--> o + // ^ + // 3 + // + return 3; + } + else if ((currDir == reverseDestDir) && + ((currToDestDir == destDir) || (currToDestDir == currDir))) + { + // + // + // + // o < 4 D--> o < 4 + // + return 4; + } + else if ((currDir == destDir) && + (currToDestDir & reverseDestDir)) + { + // + // 4 > o + // + // + // D--> 4 > o + // + return 4; + } + + // Should not be possible to reach here. + COLA_ASSERT(false); + return 0; +} + + +static double estimatedCostSpecific(ConnRef *lineRef, const Point *last, + const Point& curr, const VertInf *costTar, + const unsigned int costTarDirs) +{ + Point costTarPoint = costTar->point; + + if (lineRef->routingType() == ConnType_PolyLine) + { + return euclideanDist(curr, costTarPoint); + } + else // Orthogonal + { + // Really doesn't make sense to route orthogonal paths without + // a segment penalty. + COLA_ASSERT(lineRef->router()->routingParameter(segmentPenalty) > 0); + + double dist = manhattanDist(curr, costTarPoint); + + int bendCount = 0; + double xmove = costTarPoint.x - curr.x; + double ymove = costTarPoint.y - curr.y; + if (last == nullptr) + { + // This is just the initial point. Penalise it simply if it is + // not inline with the target in either the x- or y-dimension. + if ((xmove != 0) && (ymove != 0)) + { + bendCount += 1; + } + } + else if (dist > 0) + { + // We have two points and a non-zero distance, so we know + // the segment direction. + + unsigned int currDir = orthogonalDirection(*last, curr); + if ((currDir > 0) && (orthogonalDirectionsCount(currDir) == 1)) + { + // Suitably high value, then we find the minimum. + bendCount = 10; + + // Find the minimum bent penalty given all the possible + // directions at the target point. + if (costTarDirs & CostDirectionN) + { + bendCount = std::min(bendCount, + bends(curr, currDir, costTarPoint, CostDirectionN)); + } + if (costTarDirs & CostDirectionE) + { + bendCount = std::min(bendCount, + bends(curr, currDir, costTarPoint, CostDirectionE)); + } + if (costTarDirs & CostDirectionS) + { + bendCount = std::min(bendCount, + bends(curr, currDir, costTarPoint, CostDirectionS)); + } + if (costTarDirs & CostDirectionW) + { + bendCount = std::min(bendCount, + bends(curr, currDir, costTarPoint, CostDirectionW)); + } + } + } + double penalty = bendCount * + lineRef->router()->routingParameter(segmentPenalty); + + return dist + penalty; + } +} + + + +double AStarPathPrivate::estimatedCost(ConnRef *lineRef, const Point *last, + const Point& curr) const +{ + double estimate = DBL_MAX; + COLA_ASSERT(m_cost_targets.size() > 0); + + // Find the minimum cost from the estimates to each of the possible + // target points from this current point. + for (size_t i = 0; i < m_cost_targets.size(); ++i) + { + double iEstimate = estimatedCostSpecific(lineRef, last, + curr, m_cost_targets[i], m_cost_targets_directions[i]); + + // Add on the distance to the real target, otherwise this difference + // might may make the comparisons unfair if they vary between targets. + iEstimate += m_cost_targets_displacements[i]; + + estimate = std::min(estimate, iEstimate); + } + return estimate; +} + + +class CmpVisEdgeRotation +{ + public: + CmpVisEdgeRotation(const VertInf* lastPt) + : _lastPt(lastPt) + { + } + bool operator() (const EdgeInf* u, const EdgeInf* v) const + { + // Dummy ShapeConnectionPin edges are not orthogonal and + // therefore can't be compared in the same way. + if (u->isOrthogonal() && v->isOrthogonal()) + { + return u->rotationLessThan(_lastPt, v); + } + return u < v; + } + private: + const VertInf *_lastPt; +}; + + +static inline bool pointAlignedWithOneOf(const Point& point, + const std::vector<Point>& points, const size_t dim) +{ + for (size_t i = 0; i < points.size(); ++i) + { + if (point[dim] == points[i][dim]) + { + return true; + } + } + return false; +} + +AStarPath::AStarPath(void) + : m_private(new AStarPathPrivate()) +{ +} + +AStarPath::~AStarPath(void) +{ + delete m_private; +} + +void AStarPath::search(ConnRef *lineRef, VertInf *src, VertInf *tar, VertInf *start) +{ + m_private->search(lineRef, src, tar, start); +} + +void AStarPathPrivate::determineEndPointLocation(double dist, VertInf *start, + VertInf *target, VertInf *other, int level) +{ + COLA_UNUSED(dist); + COLA_UNUSED(start); + COLA_UNUSED(level); + + Point otherPoint = other->point; + unsigned int thisDirs = orthogonalDirection(otherPoint, target->point); + COLA_ASSERT(orthogonalDirectionsCount(thisDirs) > 0); + double displacement = manhattanDist(otherPoint, target->point); + + m_cost_targets.push_back(other); + m_cost_targets_directions.push_back(thisDirs); + m_cost_targets_displacements.push_back(displacement); + +#ifdef ESTIMATED_COST_DEBUG + fprintf(stderr," - %g %g ", otherPoint.x, otherPoint.y); + if (manhattanDist(start->point, otherPoint) > dist) + { + fprintf(stderr,"far "); + } + fprintf(stderr, "%s", (level == 1) ? "--" : "- "); + printDirections(stderr, thisDirs); + fprintf(stderr,"\n"); +#endif +} + +// Returns the best path from src to tar using the cost function. +// +// The path is worked out using the aStar algorithm, and is encoded via +// prevNode values for each ANode which point back to the previous ANode. +// At completion, this order is written into the pathNext links in each +// of the VerInfs along the path. +// +// The aStar STL code is originally based on public domain code available +// on the internet. +// +void AStarPathPrivate::search(ConnRef *lineRef, VertInf *src, VertInf *tar, VertInf *start) +{ + ANodeCmp pendingCmp; + + bool isOrthogonal = (lineRef->routingType() == ConnType_Orthogonal); + + if (start == nullptr) + { + start = src; + } + +#ifdef DEBUGHANDLER + if (lineRef->router()->debugHandler()) + { + lineRef->router()->debugHandler()->beginningSearchWithEndpoints(start, tar); + } +#endif + + // Find a target point to use for cost estimate for orthogonal routing. + // + // If the connectivity is only on the far side we need to estimate to the + // point on the far side. Otherwise for orthogonal routing we can explore + // all the space in between before we pay the extra cost to explore this + // area. This is especially true given many orthogonal routes have + // equivalent costs. +#ifdef ESTIMATED_COST_DEBUG + fprintf(stderr,"== aStar %g %g ==\n", tar->point.x, tar->point.y); +#endif + if (isOrthogonal && tar->id.isConnPt() && !tar->id.isConnCheckpoint()) + { + // The target is a connector endpoint and the connector is orthogonal. + double dist = manhattanDist(start->point, tar->point); + for (EdgeInfList::const_iterator it = tar->orthogVisList.begin(); + it != tar->orthogVisList.end(); ++it) + { + // For each edge from the target endpoint, find the other vertex. + EdgeInf *edge = *it; + VertInf *other = edge->otherVert(tar); + if (other->id.isConnectionPin()) + { + // If this is a connection pin we need to do this process + // another time since the current edge will be a dummy + // zero-length edge. + VertInf *replacementTar = other; + for (EdgeInfList::const_iterator it = + replacementTar->orthogVisList.begin(); + it != replacementTar->orthogVisList.end(); ++it) + { + EdgeInf *edge = *it; + VertInf *other = edge->otherVert(replacementTar); + if ((other == tar) || + (other->point == tar->point)) + { + // Ignore edge we came from, or zero-length edges. + continue; + } + + // Determine possible target endpoint directions and + // position. + determineEndPointLocation(dist, start, replacementTar, + other, 2); + } + continue; + } + + // Determine possible target endpoint directions and position. + determineEndPointLocation(dist, start, tar, other, 1); + } + } + + + if (m_cost_targets.empty()) + { + m_cost_targets.push_back(tar); + // For polyline routing, assume target has visibility is all + // directions for the purpose of cost estimations. + m_cost_targets_directions.push_back(CostDirectionN | + CostDirectionE | CostDirectionS | CostDirectionW); + m_cost_targets_displacements.push_back(0.0); + } + +#ifdef ESTIMATED_COST_DEBUG + fprintf(stderr, "------------\n"); + for (size_t i = 0; i < m_cost_targets.size(); ++i) + { + fprintf(stderr,"== %g %g - ", m_cost_targets[i]->point.x, + m_cost_targets[i]->point.y); + printDirections(stderr, m_cost_targets_directions[i]); + fprintf(stderr,"\n"); + } +#endif + + + double (*dist)(const Point& a, const Point& b) = + (isOrthogonal) ? manhattanDist : euclideanDist; + + // We need to know the possible endpoints for doing an orthogonal + // routing optimisation where we only turn when we are heading beside + // a shape or are in line with a possible endpoint. + std::vector<Point> endPoints; + if (isOrthogonal) + { + endPoints = lineRef->possibleDstPinPoints(); + } + endPoints.push_back(tar->point); + + // Heap of PENDING nodes. + std::vector<ANode *> PENDING; + PENDING.reserve(1000); + + size_t exploredCount = 0; + ANode node, ati; + ANode *bestNode = nullptr; // Temporary bestNode + bool bNodeFound = false; // Flag if node is found in container + int timestamp = 1; + + Router *router = lineRef->router(); + if (router->RubberBandRouting && (start != src)) + { + COLA_ASSERT(router->IgnoreRegions == true); + + const PolyLine& currRoute = lineRef->route(); + VertInf *last = nullptr; + int rIndx = 0; + while (last != start) + { + const Point& pnt = currRoute.at(rIndx); + VertIDProps props = (rIndx > 0) ? 0 : VertID::PROP_ConnPoint; + VertID vID(pnt.id, pnt.vn, props); + +#ifdef PATHDEBUG + db_printf("/// %d %d\n", pnt.id, pnt.vn); +#endif + VertInf *curr = router->vertices.getVertexByID(vID); + COLA_ASSERT(curr != nullptr); + + node = ANode(curr, timestamp++); + if (!last) + { + node.inf = src; + node.g = 0; + node.h = estimatedCost(lineRef, nullptr, node.inf->point); + + node.f = node.g + node.h; + } + else + { + double edgeDist = dist(bestNode->inf->point, curr->point); + + node.g = bestNode->g + cost(lineRef, edgeDist, bestNode->inf, + node.inf, bestNode->prevNode); + + // Calculate the Heuristic. + node.h = estimatedCost(lineRef, &(bestNode->inf->point), + node.inf->point); + + // The A* formula + node.f = node.g + node.h; + + // Point parent to last bestNode + node.prevNode = bestNode; + } + + if (curr != start) + { + bool addToPending = false; + bestNode = newANode(node, addToPending); + bestNode->inf->aStarDoneNodes.push_back(bestNode); + ++exploredCount; + } + else + { + ANode * newNode = newANode(node); + PENDING.push_back(newNode); + } + + rIndx++; + last = curr; + } + } + else + { + if (start->pathNext) + { + // If we are doing checkpoint routing and have already done one + // path, then we have an existing segment to consider for the + // cost of the choice from the start node, so we add a dummy + // nodes as if they were already in the Done set. This causes + // us to first search in a collinear direction from the previous + // segment. + bool addToPending = false; + bestNode = newANode(ANode(start->pathNext, timestamp++), + addToPending); + bestNode->inf->aStarDoneNodes.push_back(bestNode); + ++exploredCount; + } + + // Create the start node + node = ANode(src, timestamp++); + node.g = 0; + node.h = estimatedCost(lineRef, nullptr, node.inf->point); + node.f = node.g + node.h; + // Set a nullptr parent, so cost function knows this is the first segment. + node.prevNode = bestNode; + + // Populate the PENDING container with the first location + ANode *newNode = newANode(node); + PENDING.push_back(newNode); + } + + tar->pathNext = nullptr; + + // Create a heap from PENDING for sorting + using std::make_heap; using std::push_heap; using std::pop_heap; + make_heap( PENDING.begin(), PENDING.end(), pendingCmp); + + // Continue until the queue is empty. + while (!PENDING.empty()) + { + TIMER_VAR_ADD(router, 0, 1); + // Set the Node with lowest f value to BESTNODE. + // Since the ANode operator< is reversed, the head of the + // heap is the node with the lowest f value. + bestNode = PENDING.front(); + VertInf *bestNodeInf = bestNode->inf; + +#ifdef DEBUGHANDLER + if (router->debugHandler()) + { + PolyLine currentSearchPath; + + ANode *curr = bestNode; + while (curr) + { + currentSearchPath.ps.push_back(curr->inf->point); + curr = curr->prevNode; + } + router->debugHandler()->updateCurrentSearchPath(currentSearchPath); + } +#endif + + // Remove this node from the aStarPendingList + std::list<ANode *>::iterator finishIt = + bestNodeInf->aStarPendingNodes.end(); + for (std::list<ANode *>::iterator currInd = + bestNodeInf->aStarPendingNodes.begin(); currInd != finishIt; + ++currInd) + { + if (*currInd == bestNode) + { + bestNodeInf->aStarPendingNodes.erase(currInd); + break; + } + } + + // Pop off the heap. Actually this moves the + // far left value to the far right. The node + // is not actually removed since the pop is to + // the heap and not the container. + pop_heap(PENDING.begin(), PENDING.end(), pendingCmp); + // Remove node from right (the value we pop_heap'd) + PENDING.pop_back(); + + // Add the bestNode into the Done set. + bestNodeInf->aStarDoneNodes.push_back(bestNode); + ++exploredCount; + + VertInf *prevInf = (bestNode->prevNode) ? bestNode->prevNode->inf : nullptr; +#ifdef ASTAR_DEBUG + db_printf("Considering... "); + db_printf(" %g %g ", bestNodeInf->point.x, bestNodeInf->point.y); + bestNodeInf->id.db_print(); + db_printf(" - g: %3.1f h: %3.1f back: ", bestNode->g, bestNode->h); + if (prevInf) + { + db_printf(" %g %g", prevInf->point.x, prevInf->point.y); + //prevInf->id.db_print(); + } + db_printf("\n"); +#endif + + if (bestNodeInf == tar) + { + TIMER_VAR_ADD(router, 1, PENDING.size()); + // This node is our goal. +#ifdef ASTAR_DEBUG + db_printf("LINE %10d Steps: %4d Cost: %g\n", lineRef->id(), + (int) exploredCount, bestNode->f); +#endif + + // Correct all the pathNext pointers. + for (ANode *curr = bestNode; curr->prevNode; curr = curr->prevNode) + { +#ifdef ASTAR_DEBUG + db_printf("[%.12f, %.12f]\n", curr->inf->point.x, curr->inf->point.y); +#endif + curr->inf->pathNext = curr->prevNode->inf; + } +#ifdef ASTAR_DEBUG + db_printf("\n"); +#endif + + // Exit from the search + break; + } + + // Check adjacent points in graph and add them to the queue. + EdgeInfList& visList = (!isOrthogonal) ? + bestNodeInf->visList : bestNodeInf->orthogVisList; + if (isOrthogonal) + { + // We would like to explore in a structured way, + // so sort the points in the visList... + CmpVisEdgeRotation compare(prevInf); + visList.sort(compare); + } + EdgeInfList::const_iterator finish = visList.end(); + for (EdgeInfList::const_iterator edge = visList.begin(); + edge != finish; ++edge) + { + if ((*edge)->isDisabled()) + { + // Skip disabled edges. + continue; + } + + node = ANode((*edge)->otherVert(bestNodeInf), timestamp++); + + // Set the index to the previous ANode that we reached + // this ANode via. + node.prevNode = bestNode; + + VertInf *prevInf = (bestNode->prevNode) ? + bestNode->prevNode->inf : nullptr; + + // Don't bother looking at the segment we just arrived along. + if (prevInf && (prevInf == node.inf)) + { + continue; + } + if (node.inf->id.isConnectionPin() && + !node.inf->id.isConnCheckpoint()) + { + if ( !( (bestNodeInf == lineRef->src()) && + lineRef->src()->id.isDummyPinHelper() + ) && + !( node.inf->hasNeighbour(lineRef->dst(), isOrthogonal) && + lineRef->dst()->id.isDummyPinHelper()) + ) + { + // Don't check connection pins if they don't have the + // target vertex as a direct neighbour, or are directly + // leaving the source vertex. + continue; + } + } + else if (node.inf->id.isConnPt()) + { + if ((node.inf != tar)) + { + // Don't check connector endpoints vertices unless they + // are the target endpoint. + continue; + } + } + + if (isOrthogonal && !(*edge)->isDummyConnection()) + { + // Orthogonal routing optimisation. + // Skip the edges that don't lead to shape edges, or the + // connection point we are looking for. Though allow them + // if we haven't yet turned from the source point, since it + // may be a free-floating endpoint with directional visibility. + // Also, don't check if the previous point was a dummy for a + // connection pin and this happens to be placed diagonally + // from here, i.e., when both of notInline{X,Y} are true. + Point& bestPt = bestNodeInf->point; + Point& nextPt = node.inf->point; + + bool notInlineX = prevInf && (prevInf->point.x != bestPt.x); + bool notInlineY = prevInf && (prevInf->point.y != bestPt.y); + if ((bestPt.x == nextPt.x) && notInlineX && !notInlineY && + (bestPt[YDIM] != src->point[YDIM])) + { + if (nextPt.y < bestPt.y) + { + if (!(bestNodeInf->orthogVisPropFlags & YL_EDGE) && + !pointAlignedWithOneOf(bestPt, endPoints, XDIM)) + { + continue; + } + } + else if (nextPt.y > bestPt.y) + { + if (!(bestNodeInf->orthogVisPropFlags & YH_EDGE) && + !pointAlignedWithOneOf(bestPt, endPoints, XDIM)) + { + continue; + } + } + } + if ((bestPt.y == nextPt.y) && notInlineY && !notInlineX && + (bestPt[XDIM] != src->point[XDIM])) + { + if (nextPt.x < bestPt.x) + { + if (!(bestNodeInf->orthogVisPropFlags & XL_EDGE) && + !pointAlignedWithOneOf(bestPt, endPoints, YDIM)) + { + continue; + } + } + else if (nextPt.x > bestPt.x) + { + if (!(bestNodeInf->orthogVisPropFlags & XH_EDGE) && + !pointAlignedWithOneOf(bestPt, endPoints, YDIM)) + { + continue; + } + } + } + } + + double edgeDist = (*edge)->getDist(); + + if (edgeDist == 0) + { + continue; + } + + if (!isOrthogonal && + (!router->RubberBandRouting || (start == src)) && + (validateBendPoint(prevInf, bestNodeInf, node.inf) == false)) + { + // The bendpoint is not valid, i.e., is a zigzag corner, so... + continue; + // For RubberBand routing we want to allow these routes and + // unwind them later, otherwise instead or unwinding, paths + // can go the *really* long way round. + } + + // Figure out if we are at one of the cost targets. + bool atCostTarget = false; + for (size_t i = 0; i < m_cost_targets.size(); ++i) + { + if (bestNode->inf == m_cost_targets[i]) + + { + atCostTarget = true; + break; + } + } + + if (atCostTarget && + (node.inf->id.isConnectionPin() || (node.inf == tar))) + { + // This is a point on the side of an obstacle that connects + // to the target or a connection pin. It should have no + // further cost and the heuristic should be zero. + node.g = bestNode->g; + node.h = 0; + } + else + { + if (node.inf == tar) + { + // We've reached the target. The heuristic should be zero. + node.h = 0; + } + else + { + // Otherwise, calculate the heuristic value. + node.h = estimatedCost(lineRef, &(bestNodeInf->point), + node.inf->point); + } + + if (node.inf->id.isDummyPinHelper()) + { + // This is connecting to a connection pin helper vertex. + // There should be no additional cost for this step. + node.g = bestNode->g; + } + else + { + // Otherwise, calculate the cost of this step. + node.g = bestNode->g + cost(lineRef, edgeDist, bestNodeInf, + node.inf, bestNode->prevNode); + } + } + + // The A* formula + node.f = node.g + node.h; + +#ifdef ASTAR_DEBUG + db_printf("-- Adding: %g %g ", node.inf->point.x, + node.inf->point.y); + node.inf->id.db_print(); + db_printf(" - g: %3.1f h: %3.1f \n", node.g, node.h); +#endif + + bNodeFound = false; + + + // Check to see if already on PENDING + std::list<ANode *>::const_iterator finish = node.inf->aStarPendingNodes.end(); + for (std::list<ANode *>::const_iterator currInd = + node.inf->aStarPendingNodes.begin(); currInd != finish; ++currInd) + { + ati = **currInd; + // The (node.prevNode == ati.prevNode) is redundant, but may + // save checking the mosre costly prevNode->inf test if the + // Nodes are the same. + if ((node.inf == ati.inf) && + ((node.prevNode == ati.prevNode) || + (node.prevNode->inf == ati.prevNode->inf))) + { + // If already on PENDING + if (node.g < ati.g) + { + // Replace the existing node in PENDING + **currInd = node; + make_heap( PENDING.begin(), PENDING.end(), pendingCmp); + } + bNodeFound = true; + break; + } + } + if ( !bNodeFound ) // If Node NOT found on PENDING + { + // Check to see if it is already in the Done set for this + // vertex. + for (std::list<ANode *>::const_iterator currInd = + node.inf->aStarDoneNodes.begin(); + currInd != node.inf->aStarDoneNodes.end(); ++currInd) + { + ati = **currInd; + // The (node.prevNode == ati.prevNode) is redundant, but may + // save checking the mosre costly prevNode->inf test if the + // Nodes are the same. + if ((node.inf == ati.inf) && ati.prevNode && + ((node.prevNode == ati.prevNode) || + (node.prevNode->inf == ati.prevNode->inf))) + { + //COLA_ASSERT(node.g >= (ati.g - 10e-10)); + // This node is already in the Done set and the + // current node also has a higher g-value, so we + // don't need to consider this node. + bNodeFound = true; + break; + } + } + } + + if (!bNodeFound ) // If Node NOT in either Pending or Done. + { + // Push NewNode onto PENDING + ANode *newNode = newANode(node); + PENDING.push_back(newNode); + // Push NewNode onto heap + push_heap( PENDING.begin(), PENDING.end(), pendingCmp); + +#if 0 + using std::cout; using std::endl; + // Display PENDING container (For Debugging) + cout << "PENDING: "; + for (unsigned int i = 0; i < PENDING.size(); i++) + { + cout << PENDING[i]->g << "," << PENDING[i]->h << ","; + cout << PENDING[i]->inf << "," << PENDING[i]->pp << " "; + } + cout << endl << endl; +#endif + } + } + } + + // Cleanup lists used to store Done and Pending sets for each vertex. + VertInf *endVert = router->vertices.end(); + for (VertInf *k = router->vertices.connsBegin(); k != endVert; + k = k->lstNext) + { + k->aStarDoneNodes.clear(); + k->aStarPendingNodes.clear(); + } +} + + +} + + |