KiCad PCB EDA Suite
Loading...
Searching...
No Matches
ratsnest_data.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KICAD, a free EDA CAD application.
3 *
4 * Copyright (C) 2013-2017 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Maciej Suminski <[email protected]>
8 * @author Tomasz Wlostowski <[email protected]>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, you may find one here:
22 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
23 * or you may search the http://www.gnu.org website for the version 2 license,
24 * or you may write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 */
27
32
33#ifdef PROFILE
34#include <core/profile.h>
35#endif
36
38#include <functional>
39using namespace std::placeholders;
40
41#include <algorithm>
42#include <cassert>
43#include <limits>
44#include <list>
45
46#include <delaunator.hpp>
47
49{
50
51public:
52 disjoint_set( size_t size )
53 {
54 m_data.resize( size );
55 m_depth.resize( size, 0 );
56
57 for( size_t i = 0; i < size; i++ )
58 m_data[i] = i;
59 }
60
61 int find( int aVal )
62 {
63 int root = aVal;
64
65 while( m_data[root] != root )
66 root = m_data[root];
67
68 // Compress the path
69 while( m_data[aVal] != aVal )
70 {
71 auto& tmp = m_data[aVal];
72 aVal = tmp;
73 tmp = root;
74 }
75
76 return root;
77 }
78
79
80 bool unite( int aVal1, int aVal2 )
81 {
82 aVal1 = find( aVal1 );
83 aVal2 = find( aVal2 );
84
85 if( aVal1 != aVal2 )
86 {
87 if( m_depth[aVal1] < m_depth[aVal2] )
88 {
89 m_data[aVal1] = aVal2;
90 }
91 else
92 {
93 m_data[aVal2] = aVal1;
94
95 if( m_depth[aVal1] == m_depth[aVal2] )
96 m_depth[aVal1]++;
97 }
98
99 return true;
100 }
101
102 return false;
103 }
104
105private:
106 std::vector<int> m_data;
107 std::vector<int> m_depth;
108};
109
110
111void RN_NET::kruskalMST( const std::vector<CN_EDGE> &aEdges )
112{
113 disjoint_set dset( m_nodes.size() );
114
115 m_rnEdges.clear();
116
117 int i = 0;
118
119 for( const std::shared_ptr<CN_ANCHOR>& node : m_nodes )
120 node->SetTag( i++ );
121
122 for( const CN_EDGE& tmp : aEdges )
123 {
124 const std::shared_ptr<const CN_ANCHOR>& source = tmp.GetSourceNode();
125 const std::shared_ptr<const CN_ANCHOR>& target = tmp.GetTargetNode();
126
127 wxCHECK2( source && !source->Dirty() && target && !target->Dirty(), continue );
128
129 if( dset.unite( source->GetTag(), target->GetTag() ) )
130 {
131 if( tmp.GetWeight() > 0 )
132 m_rnEdges.push_back( tmp );
133 }
134 }
135}
136
137
139{
140private:
141 std::multiset<std::shared_ptr<CN_ANCHOR>, CN_PTR_CMP> m_allNodes;
142
143
144 // Checks if all nodes in aNodes lie on a single line. Requires the nodes to
145 // have unique coordinates!
146 bool areNodesColinear( const std::vector<std::shared_ptr<CN_ANCHOR>>& aNodes ) const
147 {
148 if ( aNodes.size() <= 2 )
149 return true;
150
151 const VECTOR2I p0( aNodes[0]->Pos() );
152 const VECTOR2I v0( aNodes[1]->Pos() - p0 );
153
154 for( unsigned i = 2; i < aNodes.size(); i++ )
155 {
156 const VECTOR2I v1 = aNodes[i]->Pos() - p0;
157
158 if( v0.Cross( v1 ) != 0 )
159 return false;
160 }
161
162 return true;
163 }
164
165public:
166
167 void Clear()
168 {
169 m_allNodes.clear();
170 }
171
172 void AddNode( const std::shared_ptr<CN_ANCHOR>& aNode )
173 {
174 m_allNodes.insert( aNode );
175 }
176
177 void Triangulate( std::vector<CN_EDGE>& mstEdges )
178 {
179 std::vector<double> node_pts;
180 std::vector<std::shared_ptr<CN_ANCHOR>> anchors;
181 std::vector< std::vector<std::shared_ptr<CN_ANCHOR>> > anchorChains( m_allNodes.size() );
182
183 node_pts.reserve( 2 * m_allNodes.size() );
184 anchors.reserve( m_allNodes.size() );
185
186 auto addEdge =
187 [&]( const std::shared_ptr<CN_ANCHOR>& src, const std::shared_ptr<CN_ANCHOR>& dst )
188 {
189 mstEdges.emplace_back( src, dst, src->Dist( *dst ) );
190 };
191
192 std::shared_ptr<CN_ANCHOR> prev = nullptr;
193
194 for( const std::shared_ptr<CN_ANCHOR>& n : m_allNodes )
195 {
196 if( !prev || prev->Pos() != n->Pos() )
197 {
198 node_pts.push_back( n->Pos().x );
199 node_pts.push_back( n->Pos().y );
200 anchors.push_back( n );
201 prev = n;
202 }
203
204 anchorChains[anchors.size() - 1].push_back( n );
205 }
206
207 if( anchors.empty() )
208 {
209 return;
210 }
211 else if( anchors.size() == 1 )
212 {
213 // The anchors all have the same position, but may not have overlapping layers.
214 prev = nullptr;
215
216 for( const std::shared_ptr<CN_ANCHOR>& n : m_allNodes )
217 {
218 if( prev && !( prev->Parent()->GetLayerSet() & n->Parent()->GetLayerSet() ).any() )
219 {
220 // Use a minimal but non-zero distance or the edge will be ignored
221 mstEdges.emplace_back( prev, n, 1 );
222 }
223
224 prev = n;
225 }
226
227 return;
228 }
229 else if( areNodesColinear( anchors ) )
230 {
231 // special case: all nodes are on the same line - there's no
232 // triangulation for such set. In this case, we sort along any coordinate
233 // and chain the nodes together.
234 for( size_t i = 0; i < anchors.size() - 1; i++ )
235 addEdge( anchors[i], anchors[i + 1] );
236 }
237 else
238 {
239 delaunator::Delaunator delaunator( node_pts );
240 auto& triangles = delaunator.triangles;
241
242 for( size_t i = 0; i < triangles.size(); i += 3 )
243 {
244 addEdge( anchors[triangles[i]], anchors[triangles[i + 1]] );
245 addEdge( anchors[triangles[i + 1]], anchors[triangles[i + 2]] );
246 addEdge( anchors[triangles[i + 2]], anchors[triangles[i]] );
247 }
248
249 for( size_t i = 0; i < delaunator.halfedges.size(); i++ )
250 {
251 if( delaunator.halfedges[i] == delaunator::INVALID_INDEX )
252 continue;
253
254 addEdge( anchors[triangles[i]], anchors[triangles[delaunator.halfedges[i]]] );
255 }
256 }
257
258 for( size_t i = 0; i < anchorChains.size(); i++ )
259 {
260 std::vector<std::shared_ptr<CN_ANCHOR>>& chain = anchorChains[i];
261
262 if( chain.size() < 2 )
263 continue;
264
265 std::sort( chain.begin(), chain.end(),
266 [] ( const std::shared_ptr<CN_ANCHOR>& a, const std::shared_ptr<CN_ANCHOR>& b )
267 {
268 return a->GetCluster().get() < b->GetCluster().get();
269 } );
270
271 for( unsigned int j = 1; j < chain.size(); j++ )
272 {
273 const std::shared_ptr<CN_ANCHOR>& prevNode = chain[j - 1];
274 const std::shared_ptr<CN_ANCHOR>& curNode = chain[j];
275 int weight = prevNode->GetCluster() != curNode->GetCluster() ? 1 : 0;
276 mstEdges.emplace_back( prevNode, curNode, weight );
277 }
278 }
279 }
280};
281
282
284{
286}
287
288
290{
291 // Special cases do not need complicated algorithms (actually, it does not work well with
292 // the Delaunay triangulator)
293 if( m_nodes.size() <= 2 )
294 {
295 m_rnEdges.clear();
296
297 // Check if the only possible connection exists
298 if( m_boardEdges.size() == 0 && m_nodes.size() == 2 )
299 {
300 // There can be only one possible connection, but it is missing
301 auto it = m_nodes.begin();
302 const std::shared_ptr<CN_ANCHOR>& source = *it++;
303 const std::shared_ptr<CN_ANCHOR>& target = *it;
304
305 source->SetTag( 0 );
306 target->SetTag( 1 );
307 m_rnEdges.emplace_back( source, target );
308 }
309 else
310 {
311 // Set tags to m_nodes as connected
312 for( const std::shared_ptr<CN_ANCHOR>& node : m_nodes )
313 node->SetTag( 0 );
314 }
315
316 return;
317 }
318
319
320 m_triangulator->Clear();
321
322 for( const std::shared_ptr<CN_ANCHOR>& n : m_nodes )
323 m_triangulator->AddNode( n );
324
325 std::vector<CN_EDGE> triangEdges;
326 triangEdges.reserve( m_nodes.size() + m_boardEdges.size() );
327
328#ifdef PROFILE
329 PROF_TIMER cnt( "triangulate" );
330#endif
331 m_triangulator->Triangulate( triangEdges );
332#ifdef PROFILE
333 cnt.Show();
334#endif
335
336 for( const CN_EDGE& e : m_boardEdges )
337 triangEdges.emplace_back( e );
338
339 std::sort( triangEdges.begin(), triangEdges.end() );
340
341// Get the minimal spanning tree
342#ifdef PROFILE
343 PROF_TIMER cnt2( "mst" );
344#endif
345 kruskalMST( triangEdges );
346#ifdef PROFILE
347 cnt2.Show();
348#endif
349}
350
351
353{
354 auto optimizeZoneAnchor =
355 [&]( const VECTOR2I& aPos, const LSET& aLayerSet,
356 const std::shared_ptr<const CN_ANCHOR>& aAnchor,
357 const std::function<void( std::shared_ptr<const CN_ANCHOR> )>& setOptimizedTo )
358 {
359 SEG::ecoord closest_dist_sq = ( aAnchor->Pos() - aPos ).SquaredEuclideanNorm();
360 VECTOR2I closest_pt;
361 CN_ITEM* closest_item = nullptr;
362
363 for( CN_ITEM* item : aAnchor->Item()->ConnectedItems() )
364 {
365 // Don't consider shorted items
366 if( aAnchor->Item()->Net() != item->Net() )
367 continue;
368
369 CN_ZONE_LAYER* zoneLayer = dynamic_cast<CN_ZONE_LAYER*>( item );
370
371 if( zoneLayer && aLayerSet.test( zoneLayer->GetBoardLayer() ) )
372 {
373 const std::vector<VECTOR2I>& pts = zoneLayer->GetOutline().CPoints();
374
375 for( const VECTOR2I& pt : pts )
376 {
377 SEG::ecoord dist_sq = ( pt - aPos ).SquaredEuclideanNorm();
378
379 if( dist_sq < closest_dist_sq )
380 {
381 closest_pt = pt;
382 closest_item = zoneLayer;
383 closest_dist_sq = dist_sq;
384 }
385 }
386 }
387 }
388
389 if( closest_item )
390 setOptimizedTo( std::make_shared<CN_ANCHOR>( closest_pt, closest_item ) );
391 };
392
393 auto optimizeZoneToZoneAnchors =
394 [&]( const std::shared_ptr<const CN_ANCHOR>& a,
395 const std::shared_ptr<const CN_ANCHOR>& b,
396 const std::function<void( const std::shared_ptr<const CN_ANCHOR>& )>&
397 setOptimizedATo,
398 const std::function<void( const std::shared_ptr<const CN_ANCHOR>& )>&
399 setOptimizedBTo )
400 {
401 struct CENTER
402 {
403 VECTOR2I pt;
404 bool valid = false;
405 };
406
407 struct DIST_PAIR
408 {
409 DIST_PAIR( int64_t aDistSq, size_t aIdA, size_t aIdB )
410 : dist_sq( aDistSq ), idA( aIdA ), idB( aIdB )
411 {}
412
413 int64_t dist_sq;
414 size_t idA;
415 size_t idB;
416 };
417
418 const std::vector<CN_ITEM*>& connectedItemsA = a->Item()->ConnectedItems();
419 const std::vector<CN_ITEM*>& connectedItemsB = b->Item()->ConnectedItems();
420
421 std::vector<CENTER> centersA( connectedItemsA.size() );
422 std::vector<CENTER> centersB( connectedItemsB.size() );
423
424 for( size_t i = 0; i < connectedItemsA.size(); i++ )
425 {
426 CN_ITEM* itemA = connectedItemsA[i];
427 CN_ZONE_LAYER* zoneLayerA = dynamic_cast<CN_ZONE_LAYER*>( itemA );
428
429 if( !zoneLayerA )
430 continue;
431
432 const SHAPE_LINE_CHAIN& shapeA = zoneLayerA->GetOutline();
433 centersA[i].pt = shapeA.BBox().GetCenter();
434 centersA[i].valid = true;
435 }
436
437 for( size_t i = 0; i < connectedItemsB.size(); i++ )
438 {
439 CN_ITEM* itemB = connectedItemsB[i];
440 CN_ZONE_LAYER* zoneLayerB = dynamic_cast<CN_ZONE_LAYER*>( itemB );
441
442 if( !zoneLayerB )
443 continue;
444
445 const SHAPE_LINE_CHAIN& shapeB = zoneLayerB->GetOutline();
446 centersB[i].pt = shapeB.BBox().GetCenter();
447 centersB[i].valid = true;
448 }
449
450 std::vector<DIST_PAIR> pairsToTest;
451
452 for( size_t ia = 0; ia < centersA.size(); ia++ )
453 {
454 for( size_t ib = 0; ib < centersB.size(); ib++ )
455 {
456 const CENTER& ca = centersA[ia];
457 const CENTER& cb = centersB[ib];
458
459 if( !ca.valid || !cb.valid )
460 continue;
461
462 VECTOR2L pA( ca.pt );
463 VECTOR2L pB( cb.pt );
464
465 int64_t dist_sq = ( pB - pA ).SquaredEuclideanNorm();
466 pairsToTest.emplace_back( dist_sq, ia, ib );
467 }
468 }
469
470 std::sort( pairsToTest.begin(), pairsToTest.end(),
471 []( const DIST_PAIR& dp_a, const DIST_PAIR& dp_b )
472 {
473 return dp_a.dist_sq < dp_b.dist_sq;
474 } );
475
476 const int c_polyPairsLimit = 3;
477
478 for( size_t i = 0; i < pairsToTest.size() && i < c_polyPairsLimit; i++ )
479 {
480 const DIST_PAIR& pair = pairsToTest[i];
481
482 CN_ZONE_LAYER* zoneLayerA = static_cast<CN_ZONE_LAYER*>( connectedItemsA[pair.idA] );
483 CN_ZONE_LAYER* zoneLayerB = static_cast<CN_ZONE_LAYER*>( connectedItemsB[pair.idB] );
484
485 if( zoneLayerA == zoneLayerB )
486 continue;
487
488 const SHAPE_LINE_CHAIN& shapeA = zoneLayerA->GetOutline();
489 const SHAPE_LINE_CHAIN& shapeB = zoneLayerB->GetOutline();
490
491 VECTOR2I ptA;
492 VECTOR2I ptB;
493
494 if( shapeA.ClosestSegmentsFast( shapeB, ptA, ptB ) )
495 {
496 setOptimizedATo( std::make_shared<CN_ANCHOR>( ptA, zoneLayerA ) );
497 setOptimizedBTo( std::make_shared<CN_ANCHOR>( ptB, zoneLayerB ) );
498 }
499 }
500 };
501
502 for( CN_EDGE& edge : m_rnEdges )
503 {
504 const std::shared_ptr<const CN_ANCHOR>& source = edge.GetSourceNode();
505 const std::shared_ptr<const CN_ANCHOR>& target = edge.GetTargetNode();
506
507 wxCHECK2( source && !source->Dirty() && target && !target->Dirty(), continue );
508
509 if( source->ConnectedItemsCount() == 0 )
510 {
511 optimizeZoneAnchor( source->Pos(), source->Parent()->GetLayerSet(), target,
512 [&]( const std::shared_ptr<const CN_ANCHOR>& optimized )
513 {
514 edge.SetTargetNode( optimized );
515 } );
516 }
517 else if( target->ConnectedItemsCount() == 0 )
518 {
519 optimizeZoneAnchor( target->Pos(), target->Parent()->GetLayerSet(), source,
520 [&]( const std::shared_ptr<const CN_ANCHOR>& optimized )
521 {
522 edge.SetSourceNode( optimized );
523 } );
524 }
525 else
526 {
527 optimizeZoneToZoneAnchors( source, target,
528 [&]( const std::shared_ptr<const CN_ANCHOR>& optimized )
529 {
530 edge.SetSourceNode( optimized );
531 },
532 [&]( const std::shared_ptr<const CN_ANCHOR>& optimized )
533 {
534 edge.SetTargetNode( optimized );
535 } );
536 }
537 }
538}
539
540
542{
543 compute();
544
545 m_dirty = false;
546}
547
548
550{
551 for( CN_EDGE& edge : m_rnEdges )
552 edge.RemoveInvalidRefs();
553
554 for( CN_EDGE& edge : m_boardEdges )
555 edge.RemoveInvalidRefs();
556
557 auto is_invalid = []( const CN_EDGE& edge )
558 {
559 return !edge.GetSourceNode() || !edge.GetTargetNode();
560 };
561
562 m_rnEdges.erase( std::remove_if( m_rnEdges.begin(), m_rnEdges.end(), is_invalid ), m_rnEdges.end() );
563 m_boardEdges.erase( std::remove_if( m_boardEdges.begin(), m_boardEdges.end(), is_invalid ),
564 m_boardEdges.end() );
565}
566
567
569{
570 m_rnEdges.clear();
571 m_boardEdges.clear();
572 m_nodes.clear();
573
574 m_dirty = true;
575}
576
577
578void RN_NET::AddCluster( std::shared_ptr<CN_CLUSTER> aCluster )
579{
580 std::shared_ptr<CN_ANCHOR> firstAnchor;
581
582 for( CN_ITEM* item : *aCluster )
583 {
584 std::vector<std::shared_ptr<CN_ANCHOR>>& anchors = item->Anchors();
585 unsigned int nAnchors = dynamic_cast<CN_ZONE_LAYER*>( item ) ? 1 : anchors.size();
586
587 if( nAnchors > anchors.size() )
588 nAnchors = anchors.size();
589
590 for( unsigned int i = 0; i < nAnchors; i++ )
591 {
592 anchors[i]->SetCluster( aCluster );
593 m_nodes.insert( anchors[i] );
594
595 if( firstAnchor )
596 {
597 if( firstAnchor != anchors[i] )
598 m_boardEdges.emplace_back( firstAnchor, anchors[i], 0 );
599 }
600 else
601 {
602 firstAnchor = anchors[i];
603 }
604 }
605 }
606}
607
608
609bool RN_NET::NearestBicoloredPair( RN_NET* aOtherNet, VECTOR2I& aPos1, VECTOR2I& aPos2 ) const
610{
611 bool rv = false;
612
614
615 auto verify =
616 [&]( const std::shared_ptr<CN_ANCHOR>& aTestNode1,
617 const std::shared_ptr<CN_ANCHOR>& aTestNode2 )
618 {
619 VECTOR2I diff = aTestNode1->Pos() - aTestNode2->Pos();
620 SEG::ecoord dist_sq = diff.SquaredEuclideanNorm();
621
622 if( dist_sq < distMax_sq )
623 {
624 rv = true;
625 distMax_sq = dist_sq;
626 aPos1 = aTestNode1->Pos();
627 aPos2 = aTestNode2->Pos();
628 }
629 };
630
634 for( const std::shared_ptr<CN_ANCHOR>& nodeA : aOtherNet->m_nodes )
635 {
636
637 if( nodeA->GetNoLine() )
638 continue;
639
643 auto fwd_it = m_nodes.lower_bound( nodeA );
644 auto rev_it = std::make_reverse_iterator( fwd_it );
645
646 for( ; fwd_it != m_nodes.end(); ++fwd_it )
647 {
648 const std::shared_ptr<CN_ANCHOR>& nodeB = *fwd_it;
649
650 if( nodeB->GetNoLine() )
651 continue;
652
653 SEG::ecoord distX_sq = SEG::Square( nodeA->Pos().x - nodeB->Pos().x );
654
657 if( distX_sq > distMax_sq )
658 break;
659
660 verify( nodeA, nodeB );
661 }
662
664 for( ; rev_it != m_nodes.rend(); ++rev_it )
665 {
666 const std::shared_ptr<CN_ANCHOR>& nodeB = *rev_it;
667
668 if( nodeB->GetNoLine() )
669 continue;
670
671 SEG::ecoord distX_sq = SEG::Square( nodeA->Pos().x - nodeB->Pos().x );
672
673 if( distX_sq > distMax_sq )
674 break;
675
676 verify( nodeA, nodeB );
677 }
678 }
679
680 return rv;
681}
constexpr const Vec GetCenter() const
Definition box2.h:230
CN_EDGE represents a point-to-point connection, whether realized or unrealized (ie: tracks etc.
CN_ITEM represents a BOARD_CONNETED_ITEM in the connectivity system (ie: a pad, track/arc/via,...
const std::vector< CN_ITEM * > & ConnectedItems() const
PCB_LAYER_ID GetBoardLayer() const
When using CN_ITEM layers to compare against board items, use this function which correctly remaps th...
const SHAPE_LINE_CHAIN & GetOutline() const
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
A small class to help profiling.
Definition profile.h:49
void Show(std::ostream &aStream=std::cerr)
Print the elapsed time (in a suitable unit) to a stream.
Definition profile.h:105
void AddNode(const std::shared_ptr< CN_ANCHOR > &aNode)
std::multiset< std::shared_ptr< CN_ANCHOR >, CN_PTR_CMP > m_allNodes
bool areNodesColinear(const std::vector< std::shared_ptr< CN_ANCHOR > > &aNodes) const
void Triangulate(std::vector< CN_EDGE > &mstEdges)
std::shared_ptr< TRIANGULATOR_STATE > m_triangulator
std::multiset< std::shared_ptr< CN_ANCHOR >, CN_PTR_CMP > m_nodes
< Vector of nodes
void RemoveInvalidRefs()
void kruskalMST(const std::vector< CN_EDGE > &aEdges)
void UpdateNet()
Recompute ratsnest for a net.
std::vector< CN_EDGE > m_rnEdges
Flag indicating necessity of recalculation of ratsnest for a net.
void OptimizeRNEdges()
Find optimal ends of RNEdges.
bool NearestBicoloredPair(RN_NET *aOtherNet, VECTOR2I &aPos1, VECTOR2I &aPos2) const
bool m_dirty
std::vector< CN_EDGE > m_boardEdges
Vector of edges that makes ratsnest for a given net.
void compute()
< Recompute ratsnest from scratch.
void Clear()
void AddCluster(std::shared_ptr< CN_CLUSTER > aCluster)
VECTOR2I::extended_type ecoord
Definition seg.h:44
static SEG::ecoord Square(int a)
Definition seg.h:123
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool ClosestSegmentsFast(const SHAPE_LINE_CHAIN &aOther, VECTOR2I &aPt0, VECTOR2I &aPt1) const
Finds closest points between segments of this and the other line chain.
const std::vector< VECTOR2I > & CPoints() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition vector2d.h:546
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:307
static constexpr extended_type ECOORD_MAX
Definition vector2d.h:76
disjoint_set(size_t size)
std::vector< int > m_data
std::vector< int > m_depth
int find(int aVal)
bool unite(int aVal1, int aVal2)
Class that computes missing connections on a PCB.
VECTOR3I v1(5, 5, 5)
const SHAPE_LINE_CHAIN chain
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< int64_t > VECTOR2L
Definition vector2d.h:696