1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*******************************************************************************
21  *  Includes
22  ******************************************************************************/
23 #include "airwiresbuilder.h"
24 
25 #include <unordered_map>
26 
27 #include <QtCore>
28 
29 /*******************************************************************************
30  *  Namespace
31  ******************************************************************************/
32 namespace librepcb {
33 
34 /*******************************************************************************
35  *  Constructors / Destructor
36  ******************************************************************************/
37 
AirWiresBuilder()38 AirWiresBuilder::AirWiresBuilder() noexcept {
39 }
40 
~AirWiresBuilder()41 AirWiresBuilder::~AirWiresBuilder() noexcept {
42 }
43 
44 /*******************************************************************************
45  *  Public Methods
46  ******************************************************************************/
47 
addPoint(const Point & p)48 int AirWiresBuilder::addPoint(const Point& p) noexcept {
49   int id = mPoints.size();
50   mPoints.emplace_back(p.getX().toNm(), p.getY().toNm(), id);
51   return id;
52 }
53 
addEdge(int p1,int p2)54 void AirWiresBuilder::addEdge(int p1, int p2) noexcept {
55   mEdges.emplace_back(mPoints[p1], mPoints[p2], -1);
56 }
57 
buildAirWires()58 AirWiresBuilder::AirWires AirWiresBuilder::buildAirWires() noexcept {
59   // remember how many edges are already known as connected
60   uint connectedEdges = mEdges.size();
61 
62   // determine additional edges between found points (candidates for airwires)
63   if (mPoints.size() == 2) {
64     mEdges.emplace_back(mPoints[0], mPoints[1], -1);
65   } else if (mPoints.size() == 3) {
66     // manually triangulate since it is easy and more stable than the
67     // delaunay-triangulation library
68     mEdges.emplace_back(mPoints[0], mPoints[1], -1);
69     mEdges.emplace_back(mPoints[1], mPoints[2], -1);
70     mEdges.emplace_back(mPoints[2], mPoints[0], -1);
71   } else if (mPoints.size() >= 3) {
72     // since delaunay-triangulation sometimes doesn't work well, add fallback
73     // edges to make sure at least all points are connected somehow
74     for (std::size_t i = 1; i < mPoints.size(); ++i) {
75       mEdges.emplace_back(mPoints[i - 1], mPoints[i], -1);
76     }
77 
78     // now run delaunay triangulation to add additional edges
79     delaunay::Delaunay<qreal> del;
80     del.triangulate(mPoints);
81     mEdges.insert(mEdges.end(), del.getEdges().begin(), del.getEdges().end());
82   }
83 
84   // determine weights of these new edges
85   for (uint i = connectedEdges; i < mEdges.size(); ++i) {
86     mEdges[i].weight = mEdges[i].p1.dist2(mEdges[i].p2);
87   }
88 
89   // find airwires in list of edges
90   return AirWiresBuilder::kruskalMst();
91 }
92 
93 /*******************************************************************************
94  *  Private Methods
95  ******************************************************************************/
96 
97 // adapted from horizon/kicad
kruskalMst()98 AirWiresBuilder::AirWires AirWiresBuilder::kruskalMst() noexcept {
99   unsigned int nodeNumber = mPoints.size();
100   unsigned int mstExpectedSize = nodeNumber - 1;
101   unsigned int mstSize = 0;
102   bool ratsnestLines = false;
103 
104   // printf("mst nodes : %d edges : %d\n", mPoints.size(), mEdges.size () );
105   // The output
106   AirWires mst;
107 
108   // Set tags for marking cycles
109   std::unordered_map<int, int> tags;
110   unsigned int tag = 0;
111 
112   for (auto& node : mPoints) {
113     node.tag = tag;
114     tags[node.id] = tag++;
115   }
116 
117   // Lists of nodes connected together (subtrees) to detect cycles in the
118   // graph
119   std::vector<std::list<int>> cycles(nodeNumber);
120 
121   for (unsigned int i = 0; i < nodeNumber; ++i) cycles[i].push_back(i);
122 
123   // Kruskal algorithm requires edges to be sorted by their weight
124   std::sort(mEdges.begin(), mEdges.end(),
125             [](const delaunay::Edge<qreal>& a, const delaunay::Edge<qreal>& b) {
126               return a.weight > b.weight;
127             });
128 
129   while (mstSize < mstExpectedSize && !mEdges.empty()) {
130     auto& dt = mEdges.back();
131 
132     int srcTag = tags[dt.p1.id];
133     int trgTag = tags[dt.p2.id];
134     // printf("mstSize %d %d %f %d<>%d\n", mstSize, mstExpectedSize,
135     // dt.weight, srcTag, trgTag);
136 
137     // Check if by adding this edge we are going to join two different
138     // forests
139     if (srcTag != trgTag) {
140       // Because edges are sorted by their weight, first we always process
141       // connected
142       // items (weight == 0). Once we stumble upon an edge with non-zero
143       // weight,
144       // it means that the rest of the lines are ratsnest.
145       if (!ratsnestLines && dt.weight >= 0) ratsnestLines = true;
146 
147       // Update tags
148       if (ratsnestLines) {
149         for (auto it = cycles[trgTag].begin(); it != cycles[trgTag].end();
150              ++it) {
151           tags[mPoints[*it].id] = srcTag;
152         }
153 
154         // Do a copy of edge, but make it RN_EDGE_MST. In contrary to
155         // RN_EDGE,
156         // RN_EDGE_MST saves both source and target node and does not
157         // require any other
158         // edges to exist for getting source/target nodes
159         // CN_EDGE newEdge ( dt.GetSourceNode(), dt.GetTargetNode(),
160         // dt.GetWeight() );
161 
162         // assert( newEdge.GetSourceNode()->GetTag() !=
163         // newEdge.GetTargetNode()->GetTag() );
164         // assert(dt.p1.tag != dt.p2.tag);
165         mst.append(qMakePair(Point(dt.p1.x, dt.p1.y), Point(dt.p2.x, dt.p2.y)));
166         ++mstSize;
167       } else {
168         // for( it = cycles[trgTag].begin(), itEnd =
169         // cycles[trgTag].end(); it != itEnd; ++it )
170         // for( auto it : cycles[trgTag] )
171         for (auto it = cycles[trgTag].begin(); it != cycles[trgTag].end();
172              ++it) {
173           tags[mPoints[*it].id] = srcTag;
174           mPoints[*it].tag = srcTag;
175         }
176 
177         // Processing a connection, decrease the expected size of the
178         // ratsnest MST
179         --mstExpectedSize;
180       }
181 
182       // Move nodes that were marked with old tag to the list marked with
183       // the new tag
184       cycles[srcTag].splice(cycles[srcTag].end(), cycles[trgTag]);
185     }
186 
187     // Remove the edge that was just processed
188     mEdges.pop_back();
189   }
190 
191   return mst;
192 }
193 
194 /*******************************************************************************
195  *  End of File
196  ******************************************************************************/
197 
198 }  // namespace librepcb
199