KiCad PCB EDA Suite
Loading...
Searching...
No Matches
shape_poly_set.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) 2015-2019 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 * @author Alejandro GarcĂ­a Montoro <[email protected]>
9 *
10 * Point in polygon algorithm adapted from Clipper Library (C) Angus Johnson,
11 * subject to Clipper library license.
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25 */
26
27#include <algorithm>
28#include <assert.h> // for assert
29#include <cmath> // for sqrt, cos, hypot, isinf
30#include <cstdio>
31#include <istream> // for operator<<, operator>>
32#include <limits> // for numeric_limits
33#include <map>
34#include <memory>
35#include <set>
36#include <string> // for char_traits, operator!=
37#include <unordered_map>
38#include <unordered_set>
39#include <thread>
40#include <utility> // for swap, move
41#include <vector>
42#include <array>
43
44#include <clipper2/clipper.h>
48#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
49#include <geometry/shape.h>
55#include <math/box2.h> // for BOX2I
56#include <math/util.h> // for KiROUND, rescale
57#include <math/vector2d.h> // for VECTOR2I, VECTOR2D, VECTOR2
58#include <hash.h>
59#include <mmh3_hash.h>
63
64#include <wx/log.h>
65
66// ADVANCED_CFG::GetCfg() cannot be used on msys2/mingw builds (link failure)
67// So we use the ADVANCED_CFG default values
68#if defined( __MINGW32__ )
69 #define TRIANGULATESIMPLIFICATIONLEVEL 50
70 #define ENABLECACHEFRIENDLYFRACTURE true
71 #define ENABLEFRACTUREEDGEINDEX true
72#else
73 #define TRIANGULATESIMPLIFICATIONLEVEL ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel
74 #define ENABLECACHEFRIENDLYFRACTURE ADVANCED_CFG::GetCfg().m_EnableCacheFriendlyFracture
75 #define ENABLEFRACTUREEDGEINDEX ADVANCED_CFG::GetCfg().m_EnableFractureEdgeIndex
76#endif
77
78// Below this many elements a direct scan beats building and querying a spatial index.
79static constexpr int RTREE_THRESHOLD = 32;
80
81
86
87
90{
91 NewOutline();
92 Append( VECTOR2I( aRect.GetLeft(), aRect.GetTop() ) );
93 Append( VECTOR2I( aRect.GetRight(), aRect.GetTop() ) );
94 Append( VECTOR2I( aRect.GetRight(), aRect.GetBottom() ) );
95 Append( VECTOR2I( aRect.GetLeft(), aRect.GetBottom() ) );
96 Outline( 0 ).SetClosed( true );
97}
98
99
102{
103 AddOutline( aOutline );
104}
105
106
109{
110 AddPolygon( aPolygon );
111}
112
113
115 SHAPE( aOther ),
116 m_polys( aOther.m_polys )
117{
118 if( aOther.IsTriangulationUpToDate() )
119 {
120 m_triangulatedPolys.reserve( aOther.TriangulatedPolyCount() );
121
122 for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
123 {
124 const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
125 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
126 }
127
128 m_hash = aOther.GetHash();
129 m_hashValid = true;
131 }
132 else
133 {
134 m_hash.Clear();
135 m_hashValid = false;
136 m_triangulationValid = false;
137 }
138
139 m_failedHash = aOther.m_failedHash;
140 m_failedHashValid.store( aOther.m_failedHashValid.load() );
141}
142
143
145 SHAPE( aOther ),
146 m_polys( aOther.m_polys )
147{
148 m_hash.Clear();
149 m_hashValid = false;
150 m_triangulationValid = false;
151}
152
153
157
158
160{
161 return new SHAPE_POLY_SET( *this );
162}
163
164
169
170
172 SHAPE_POLY_SET::VERTEX_INDEX* aRelativeIndices ) const
173{
174 int polygonIdx = 0;
175 unsigned int contourIdx = 0;
176 int vertexIdx = 0;
177
178 int currentGlobalIdx = 0;
179
180 for( polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
181 {
182 const POLYGON& currentPolygon = CPolygon( polygonIdx );
183
184 for( contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
185 {
186 const SHAPE_LINE_CHAIN& currentContour = currentPolygon[contourIdx];
187 int totalPoints = currentContour.PointCount();
188
189 for( vertexIdx = 0; vertexIdx < totalPoints; vertexIdx++ )
190 {
191 // Check if the current vertex is the globally indexed as aGlobalIdx
192 if( currentGlobalIdx == aGlobalIdx )
193 {
194 aRelativeIndices->m_polygon = polygonIdx;
195 aRelativeIndices->m_contour = contourIdx;
196 aRelativeIndices->m_vertex = vertexIdx;
197
198 return true;
199 }
200
201 // Advance
202 currentGlobalIdx++;
203 }
204 }
205 }
206
207 return false;
208}
209
210
212 int& aGlobalIdx ) const
213{
214 int selectedVertex = aRelativeIndices.m_vertex;
215 unsigned int selectedContour = aRelativeIndices.m_contour;
216 unsigned int selectedPolygon = aRelativeIndices.m_polygon;
217
218 // Check whether the vertex indices make sense in this poly set
219 if( selectedPolygon < m_polys.size() && selectedContour < m_polys[selectedPolygon].size()
220 && selectedVertex < m_polys[selectedPolygon][selectedContour].PointCount() )
221 {
222 POLYGON currentPolygon;
223
224 aGlobalIdx = 0;
225
226 for( unsigned int polygonIdx = 0; polygonIdx < selectedPolygon; polygonIdx++ )
227 {
228 currentPolygon = Polygon( polygonIdx );
229
230 for( unsigned int contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
231 aGlobalIdx += currentPolygon[contourIdx].PointCount();
232 }
233
234 currentPolygon = Polygon( selectedPolygon );
235
236 for( unsigned int contourIdx = 0; contourIdx < selectedContour; contourIdx++ )
237 aGlobalIdx += currentPolygon[contourIdx].PointCount();
238
239 aGlobalIdx += selectedVertex;
240
241 return true;
242 }
243 else
244 {
245 return false;
246 }
247}
248
249
251{
252 SHAPE_LINE_CHAIN empty_path;
253 POLYGON poly;
254
255 empty_path.SetClosed( true );
256 poly.push_back( empty_path );
257 m_polys.push_back( std::move( poly ) );
258 return m_polys.size() - 1;
259}
260
261
262int SHAPE_POLY_SET::NewHole( int aOutline )
263{
264 SHAPE_LINE_CHAIN empty_path;
265
266 empty_path.SetClosed( true );
267
268 // Default outline is the last one
269 if( aOutline < 0 )
270 aOutline += m_polys.size();
271
272 // Add hole to the selected outline
273 m_polys[aOutline].push_back( empty_path );
274
275 return m_polys.back().size() - 2;
276}
277
278
279int SHAPE_POLY_SET::Append( int x, int y, int aOutline, int aHole, bool aAllowDuplication )
280{
281 assert( m_polys.size() );
282
283 if( aOutline < 0 )
284 aOutline += m_polys.size();
285
286 int idx;
287
288 if( aHole < 0 )
289 idx = 0;
290 else
291 idx = aHole + 1;
292
293 assert( aOutline < (int) m_polys.size() );
294 assert( idx < (int) m_polys[aOutline].size() );
295
296 m_polys[aOutline][idx].Append( x, y, aAllowDuplication );
297
298 return m_polys[aOutline][idx].PointCount();
299}
300
301
302int SHAPE_POLY_SET::Append( const SHAPE_ARC& aArc, int aOutline, int aHole,
303 std::optional<int> aMaxError )
304{
305 assert( m_polys.size() );
306
307 if( aOutline < 0 )
308 aOutline += m_polys.size();
309
310 int idx;
311
312 if( aHole < 0 )
313 idx = 0;
314 else
315 idx = aHole + 1;
316
317 assert( aOutline < (int) m_polys.size() );
318 assert( idx < (int) m_polys[aOutline].size() );
319
320 if( aMaxError.has_value() )
321 m_polys[aOutline][idx].Append( aArc, aMaxError.value() );
322 else
323 m_polys[aOutline][idx].Append( aArc );
324
325 return m_polys[aOutline][idx].PointCount();
326}
327
328
329void SHAPE_POLY_SET::InsertVertex( int aGlobalIndex, const VECTOR2I& aNewVertex )
330{
332
333 if( aGlobalIndex < 0 )
334 aGlobalIndex = 0;
335
336 if( aGlobalIndex >= TotalVertices() )
337 {
338 Append( aNewVertex );
339 }
340 else
341 {
342 // Assure the position to be inserted exists; throw an exception otherwise
343 if( GetRelativeIndices( aGlobalIndex, &index ) )
344 m_polys[index.m_polygon][index.m_contour].Insert( index.m_vertex, aNewVertex );
345 else
346 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
347 }
348}
349
350
351int SHAPE_POLY_SET::VertexCount( int aOutline, int aHole ) const
352{
353 if( m_polys.size() == 0 ) // Empty poly set
354 return 0;
355
356 if( aOutline < 0 ) // Use last outline
357 aOutline += m_polys.size();
358
359 int idx;
360
361 if( aHole < 0 )
362 idx = 0;
363 else
364 idx = aHole + 1;
365
366 if( aOutline >= (int) m_polys.size() ) // not existing outline
367 return 0;
368
369 if( idx >= (int) m_polys[aOutline].size() ) // not existing hole
370 return 0;
371
372 return m_polys[aOutline][idx].PointCount();
373}
374
375
377{
378 int full_count = 0;
379
380 if( m_polys.size() == 0 ) // Empty poly set
381 return full_count;
382
383 for( int ii = 0; ii < OutlineCount(); ii++ )
384 {
385 // the first polygon in m_polys[ii] is the main contour,
386 // only others are holes:
387 for( int idx = 0; idx <= HoleCount( ii ); idx++ )
388 {
389 full_count += m_polys[ii][idx].PointCount();
390 }
391 }
392
393 return full_count;
394}
395
396
397SHAPE_POLY_SET SHAPE_POLY_SET::Subset( int aFirstPolygon, int aLastPolygon )
398{
399 assert( aFirstPolygon >= 0 && aLastPolygon <= OutlineCount() );
400
401 SHAPE_POLY_SET newPolySet;
402
403 for( int index = aFirstPolygon; index < aLastPolygon; index++ )
404 newPolySet.m_polys.push_back( Polygon( index ) );
405
406 return newPolySet;
407}
408
409
410const VECTOR2I& SHAPE_POLY_SET::CVertex( int aIndex, int aOutline, int aHole ) const
411{
412 if( aOutline < 0 )
413 aOutline += m_polys.size();
414
415 int idx;
416
417 if( aHole < 0 )
418 idx = 0;
419 else
420 idx = aHole + 1;
421
422 assert( aOutline < (int) m_polys.size() );
423 assert( idx < (int) m_polys[aOutline].size() );
424
425 return m_polys[aOutline][idx].CPoint( aIndex );
426}
427
428
429const VECTOR2I& SHAPE_POLY_SET::CVertex( int aGlobalIndex ) const
430{
432
433 // Assure the passed index references a legal position; abort otherwise
434 if( !GetRelativeIndices( aGlobalIndex, &index ) )
435 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
436
437 return m_polys[index.m_polygon][index.m_contour].CPoint( index.m_vertex );
438}
439
440
442{
443 return CVertex( index.m_vertex, index.m_polygon, index.m_contour - 1 );
444}
445
446
447bool SHAPE_POLY_SET::GetNeighbourIndexes( int aGlobalIndex, int* aPrevious, int* aNext ) const
448{
450
451 // If the edge does not exist, throw an exception, it is an illegal access memory error
452 if( !GetRelativeIndices( aGlobalIndex, &index ) )
453 return false;
454
455 // Calculate the previous and next index of aGlobalIndex, corresponding to
456 // the same contour;
457 VERTEX_INDEX inext = index;
458 int lastpoint = m_polys[index.m_polygon][index.m_contour].SegmentCount();
459
460 if( index.m_vertex == 0 )
461 {
462 index.m_vertex = lastpoint - 1;
463 inext.m_vertex = 1;
464 }
465 else if( index.m_vertex == lastpoint )
466 {
467 index.m_vertex--;
468 inext.m_vertex = 0;
469 }
470 else
471 {
472 inext.m_vertex++;
473 index.m_vertex--;
474
475 if( inext.m_vertex == lastpoint )
476 inext.m_vertex = 0;
477 }
478
479 if( aPrevious )
480 {
481 int previous;
482 GetGlobalIndex( index, previous );
483 *aPrevious = previous;
484 }
485
486 if( aNext )
487 {
488 int next;
489 GetGlobalIndex( inext, next );
490 *aNext = next;
491 }
492
493 return true;
494}
495
496
497bool SHAPE_POLY_SET::IsPolygonSelfIntersecting( int aPolygonIndex ) const
498{
499 std::vector<SEG> segments;
500 segments.reserve( FullPointCount() );
501
502 for( CONST_SEGMENT_ITERATOR it = CIterateSegmentsWithHoles( aPolygonIndex ); it; it++ )
503 segments.emplace_back( *it );
504
505 std::sort( segments.begin(), segments.end(), []( const SEG& a, const SEG& b )
506 {
507 int min_a_x = std::min( a.A.x, a.B.x );
508 int min_b_x = std::min( b.A.x, b.B.x );
509
510 return min_a_x < min_b_x || ( min_a_x == min_b_x && std::min( a.A.y, a.B.y ) < std::min( b.A.y, b.B.y ) );
511 } );
512
513 for( auto it = segments.begin(); it != segments.end(); ++it )
514 {
515 SEG& firstSegment = *it;
516
517 // Iterate through all remaining segments.
518 auto innerIterator = it;
519 int max_x = std::max( firstSegment.A.x, firstSegment.B.x );
520 int max_y = std::max( firstSegment.A.y, firstSegment.B.y );
521
522 // Start in the next segment, we don't want to check collision between a segment and itself
523 for( innerIterator++; innerIterator != segments.end(); innerIterator++ )
524 {
525 SEG& secondSegment = *innerIterator;
526 int min_x = std::min( secondSegment.A.x, secondSegment.B.x );
527 int min_y = std::min( secondSegment.A.y, secondSegment.B.y );
528
529 // We are ordered in minimum point order, so checking the static max (first segment) against
530 // the ordered min will tell us if any of the following segments are withing the BBox
531 if( max_x < min_x || ( max_x == min_x && max_y < min_y ) )
532 break;
533
534 int index_diff = std::abs( firstSegment.Index() - secondSegment.Index() );
535 bool adjacent = ( index_diff == 1) || (index_diff == ((int)segments.size() - 1) );
536
537 // Check whether the two segments built collide, only when they are not adjacent.
538 if( !adjacent && firstSegment.Collide( secondSegment, 0 ) )
539 return true;
540 }
541 }
542
543 return false;
544}
545
546
548{
549 for( unsigned int polygon = 0; polygon < m_polys.size(); polygon++ )
550 {
551 if( IsPolygonSelfIntersecting( polygon ) )
552 return true;
553 }
554
555 return false;
556}
557
558
560{
561 POLYGON poly;
562
563 poly.push_back( aOutline );
564
565 // This is an assertion because if it's generated by KiCad code, it probably
566 // indicates a bug elsewhere that should be fixed, but we also auto-fix it here
567 // for SWIG plugins that might mess it up
568 wxCHECK2_MSG( aOutline.IsClosed(), poly.back().SetClosed( true ),
569 "Warning: non-closed outline added to SHAPE_POLY_SET" );
570
571 m_polys.push_back( std::move( poly ) );
572
573 return (int) m_polys.size() - 1;
574}
575
576
577int SHAPE_POLY_SET::AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline )
578{
579 assert( m_polys.size() );
580
581 if( aOutline < 0 )
582 aOutline += (int) m_polys.size();
583
584 assert( aOutline < (int)m_polys.size() );
585
586 POLYGON& poly = m_polys[aOutline];
587
588 assert( poly.size() );
589
590 poly.push_back( aHole );
591
592 return (int) poly.size() - 2;
593}
594
595
597{
598 m_polys.push_back( apolygon );
599
600 return m_polys.size() - 1;
601}
602
603
605{
606 double area = 0.0;
607
608 for( int i = 0; i < OutlineCount(); i++ )
609 {
610 area += Outline( i ).Area( true );
611
612 for( int j = 0; j < HoleCount( i ); j++ )
613 area -= Hole( i, j ).Area( true );
614 }
615
616 return area;
617}
618
619
621{
622 int retval = 0;
623
624 for( const POLYGON& poly : m_polys )
625 {
626 for( size_t i = 0; i < poly.size(); i++ )
627 retval += poly[i].ArcCount();
628 }
629
630 return retval;
631}
632
633
634void SHAPE_POLY_SET::GetArcs( std::vector<SHAPE_ARC>& aArcBuffer ) const
635{
636 for( const POLYGON& poly : m_polys )
637 {
638 for( size_t i = 0; i < poly.size(); i++ )
639 {
640 for( SHAPE_ARC arc : poly[i].m_arcs )
641 aArcBuffer.push_back( arc );
642 }
643 }
644}
645
646
648{
649 for( POLYGON& poly : m_polys )
650 {
651 for( size_t i = 0; i < poly.size(); i++ )
652 poly[i].ClearArcs();
653 }
654}
655
656
658{
659 std::vector<SHAPE_LINE_CHAIN> contours;
660
661 for( const POLYGON& poly : m_polys )
662 contours.insert( contours.end(), poly.begin(), poly.end() );
663
664 std::map<int, std::set<int>> parentToChildren;
665 std::map<int, std::set<int>> childToParents;
666
667 for( SHAPE_LINE_CHAIN& contour : contours )
668 contour.GenerateBBoxCache();
669
670 for( size_t i = 0; i < contours.size(); i++ )
671 {
672 const SHAPE_LINE_CHAIN& outline = contours[i];
673
674 for( size_t j = 0; j < contours.size(); j++ )
675 {
676 if( i == j )
677 continue;
678
679 const SHAPE_LINE_CHAIN& candidate = contours[j];
680 const VECTOR2I& pt0 = candidate.CPoint( 0 );
681
682 if( outline.PointInside( pt0, 0, true ) )
683 {
684 parentToChildren[i].emplace( j );
685 childToParents[j].emplace( i );
686 }
687 }
688 }
689
690 std::set<int> topLevelParents;
691
692 for( size_t i = 0; i < contours.size(); i++ )
693 {
694 if( childToParents[i].size() == 0 )
695 {
696 topLevelParents.emplace( i );
697 }
698 }
699
701
702 std::function<void( int, int, std::vector<int> )> process;
703
704 process =
705 [&]( int myId, int parentOutlineId, const std::vector<int>& path )
706 {
707 std::set<int> relParents = childToParents[myId];
708
709 for( int pathId : path )
710 {
711 int erased = relParents.erase( pathId );
712 wxASSERT( erased > 0 );
713 }
714
715 wxASSERT( relParents.size() == 0 );
716
717 int myOutline = -1;
718
719 bool isOutline = path.size() % 2 == 0;
720
721 if( isOutline )
722 {
723 int outlineId = result.AddOutline( contours[myId] );
724 myOutline = outlineId;
725 }
726 else
727 {
728 wxASSERT( parentOutlineId != -1 );
729 result.AddHole( contours[myId], parentOutlineId );
730 }
731
732 auto it = parentToChildren.find( myId );
733 if( it != parentToChildren.end() )
734 {
735 std::vector<int> thisPath = path;
736 thisPath.emplace_back( myId );
737
738 std::set<int> thisPathSet;
739 thisPathSet.insert( thisPath.begin(), thisPath.end() );
740
741 for( int childId : it->second )
742 {
743 const std::set<int>& childPathSet = childToParents[childId];
744
745 if( thisPathSet != childPathSet )
746 continue; // Only interested in immediate children
747
748 process( childId, myOutline, thisPath );
749 }
750 }
751 };
752
753 for( int topParentId : topLevelParents )
754 {
755 std::vector<int> path;
756 process( topParentId, -1, std::move( path ) );
757 }
758
759 *this = std::move( result );
760}
761
762
763void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aOtherShape )
764{
765 booleanOp( aType, *this, aOtherShape );
766}
767
768
775static bool splitAtBridges( const SHAPE_LINE_CHAIN& aChain, std::vector<SHAPE_LINE_CHAIN>& aRings )
776{
777 struct DIRECTED_EDGE
778 {
779 VECTOR2I from;
780 VECTOR2I to;
781
782 bool operator==( const DIRECTED_EDGE& aOther ) const
783 {
784 return from == aOther.from && to == aOther.to;
785 }
786 };
787
788 struct DIRECTED_EDGE_HASH
789 {
790 std::size_t operator()( const DIRECTED_EDGE& aEdge ) const
791 {
792 std::size_t seed = 0x51ed27a3;
793 hash_combine( seed, aEdge.from.x, aEdge.from.y, aEdge.to.x, aEdge.to.y );
794 return seed;
795 }
796 };
797
798 const std::vector<VECTOR2I>& pts = aChain.CPoints();
799 const int count = static_cast<int>( pts.size() );
800
801 // A bucket per directed edge so repeated bridges along one line all find a partner
802 std::unordered_map<DIRECTED_EDGE, std::vector<int>, DIRECTED_EDGE_HASH> unpaired;
803 std::vector<int> partner( count, -1 );
804 bool hasBridge = false;
805
806 unpaired.reserve( count );
807
808 for( int ii = 0; ii < count; ++ii )
809 {
810 const VECTOR2I& a = pts[ii];
811 const VECTOR2I& b = pts[( ii + 1 ) % count];
812
813 if( a == b )
814 continue;
815
816 auto twin = unpaired.find( { b, a } );
817
818 if( twin != unpaired.end() )
819 {
820 const int jj = twin->second.back();
821
822 twin->second.pop_back();
823
824 if( twin->second.empty() )
825 unpaired.erase( twin );
826
827 partner[ii] = jj;
828 partner[jj] = ii;
829 hasBridge = true;
830 }
831 else
832 {
833 unpaired[{ a, b }].push_back( ii );
834 }
835 }
836
837 if( !hasBridge )
838 return false;
839
840 // The edge that continues from the end of aEdge once bridge pairs are skipped
841 auto nextEdge =
842 [&]( int aEdge ) -> int
843 {
844 int next = ( aEdge + 1 ) % count;
845
846 for( int guard = 0; partner[next] >= 0; ++guard )
847 {
848 if( guard > count )
849 return -1;
850
851 next = ( partner[next] + 1 ) % count;
852 }
853
854 return next;
855 };
856
857 std::vector<bool> visited( count, false );
858
859 for( int start = 0; start < count; ++start )
860 {
861 if( visited[start] || partner[start] >= 0 )
862 continue;
863
864 SHAPE_LINE_CHAIN ring;
865 int edge = start;
866
867 do
868 {
869 if( edge < 0 || visited[edge] )
870 {
871 aRings.clear();
872 return false;
873 }
874
875 visited[edge] = true;
876 ring.Append( pts[edge], true );
877 edge = nextEdge( edge );
878 } while( edge != start );
879
880 if( ring.PointCount() >= 3 )
881 {
882 ring.SetClosed( true );
883 aRings.push_back( std::move( ring ) );
884 }
885 }
886
887 return true;
888}
889
890
891bool SHAPE_POLY_SET::appendBridgeFreePaths( const SHAPE_LINE_CHAIN& aChain, Clipper2Lib::Paths64& aPaths,
892 std::vector<CLIPPER_Z_VALUE>& aZValues,
893 std::vector<SHAPE_ARC>& aArcBuffer )
894{
895 // Small rings are cheap for Clipper and dominate the boolean call count
896 constexpr int kMinPoints = 1024;
897
898 if( aChain.PointCount() < kMinPoints || aChain.ArcCount() > 0 )
899 return false;
900
901 std::vector<SHAPE_LINE_CHAIN> rings;
902
903 if( !splitAtBridges( aChain, rings ) )
904 return false;
905
906 // convertToClipper2() orients an outline by reversing the whole ring, so every piece of the
907 // ring follows the same flip to keep its winding relative to the others
908 const bool flip = aChain.Area( false ) < 0;
909
910 for( const SHAPE_LINE_CHAIN& ring : rings )
911 aPaths.push_back( ring.convertToClipper2( ( ring.Area( false ) >= 0 ) != flip, aZValues, aArcBuffer ) );
912
913 return true;
914}
915
916
917void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aShape,
918 const SHAPE_POLY_SET& aOtherShape )
919{
920 if( ( aShape.OutlineCount() > 1 || aOtherShape.OutlineCount() > 0 )
921 && ( aShape.ArcCount() > 0 || aOtherShape.ArcCount() > 0 ) )
922 {
923 wxFAIL_MSG( wxT( "Boolean ops on curved polygons are not supported. You should call "
924 "ClearArcs() before carrying out the boolean operation." ) );
925 }
926
927 Clipper2Lib::Clipper64 c;
928
929 std::vector<CLIPPER_Z_VALUE> zValues;
930 std::vector<SHAPE_ARC> arcBuffer;
931 std::map<VECTOR2I, CLIPPER_Z_VALUE> newIntersectPoints;
932
933 Clipper2Lib::Paths64 paths;
934 Clipper2Lib::Paths64 clips;
935
936 for( const POLYGON& poly : aShape.m_polys )
937 {
938 if( poly.size() == 1 && appendBridgeFreePaths( poly[0], paths, zValues, arcBuffer ) )
939 continue;
940
941 for( size_t i = 0; i < poly.size(); i++ )
942 {
943 paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
944 }
945 }
946
947 for( const POLYGON& poly : aOtherShape.m_polys )
948 {
949 if( poly.size() == 1 && appendBridgeFreePaths( poly[0], clips, zValues, arcBuffer ) )
950 continue;
951
952 for( size_t i = 0; i < poly.size(); i++ )
953 {
954 clips.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
955 }
956 }
957
958 c.AddSubject( paths );
959 c.AddClip( clips );
960
961 Clipper2Lib::PolyTree64 solution;
962
963 Clipper2Lib::ZCallback64 callback =
964 [&]( const Clipper2Lib::Point64 & e1bot, const Clipper2Lib::Point64 & e1top,
965 const Clipper2Lib::Point64 & e2bot, const Clipper2Lib::Point64 & e2top,
966 Clipper2Lib::Point64 & pt )
967 {
968 auto arcIndex =
969 [&]( const ssize_t& aZvalue, const ssize_t& aCompareVal = -1 ) -> ssize_t
970 {
971 ssize_t retval;
972
973 retval = zValues.at( aZvalue ).m_SecondArcIdx;
974
975 if( retval == -1 || ( aCompareVal > 0 && retval != aCompareVal ) )
976 retval = zValues.at( aZvalue ).m_FirstArcIdx;
977
978 return retval;
979 };
980
981 auto arcSegment =
982 [&]( const ssize_t& aBottomZ, const ssize_t aTopZ ) -> ssize_t
983 {
984 ssize_t retval = arcIndex( aBottomZ );
985
986 if( retval != -1 )
987 {
988 if( retval != arcIndex( aTopZ, retval ) )
989 retval = -1; // Not an arc segment as the two indices do not match
990 }
991
992 return retval;
993 };
994
995 ssize_t e1ArcSegmentIndex = arcSegment( e1bot.z, e1top.z );
996 ssize_t e2ArcSegmentIndex = arcSegment( e2bot.z, e2top.z );
997
998 CLIPPER_Z_VALUE newZval;
999
1000 if( e1ArcSegmentIndex != -1 )
1001 {
1002 newZval.m_FirstArcIdx = e1ArcSegmentIndex;
1003 newZval.m_SecondArcIdx = e2ArcSegmentIndex;
1004 }
1005 else
1006 {
1007 newZval.m_FirstArcIdx = e2ArcSegmentIndex;
1008 newZval.m_SecondArcIdx = -1;
1009 }
1010
1011 size_t z_value_ptr = zValues.size();
1012 zValues.push_back( newZval );
1013
1014 // Only worry about arc segments for later processing
1015 if( newZval.m_FirstArcIdx != -1 )
1016 newIntersectPoints.insert( { VECTOR2I( pt.x, pt.y ), newZval } );
1017
1018 pt.z = z_value_ptr;
1019 //@todo amend X,Y values to true intersection between arcs or arc and segment
1020 };
1021
1022 c.SetZCallback( std::move( callback ) ); // register callback
1023
1024 c.Execute( aType, Clipper2Lib::FillRule::NonZero, solution );
1025
1026 importTree( solution, zValues, arcBuffer );
1027 solution.Clear(); // Free used memory (not done in dtor)
1028}
1029
1030
1032{
1033 booleanOp( Clipper2Lib::ClipType::Union, b );
1034}
1035
1036
1038{
1039 booleanOp( Clipper2Lib::ClipType::Difference, b );
1040}
1041
1042
1044{
1045 booleanOp( Clipper2Lib::ClipType::Intersection, b );
1046}
1047
1048
1050{
1051 booleanOp( Clipper2Lib::ClipType::Xor, b );
1052}
1053
1054
1056{
1057 booleanOp( Clipper2Lib::ClipType::Union, a, b );
1058}
1059
1060
1062{
1063 booleanOp( Clipper2Lib::ClipType::Difference, a, b );
1064}
1065
1066
1068{
1069 booleanOp( Clipper2Lib::ClipType::Intersection, a, b );
1070}
1071
1072
1074{
1075 booleanOp( Clipper2Lib::ClipType::Xor, a, b );
1076}
1077
1078
1080 int aMaxError )
1081{
1082 Unfracture();
1083 Inflate( aFactor, aCornerStrategy, aMaxError );
1084 Fracture();
1085}
1086
1087
1088void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy,
1089 bool aSimplify )
1090{
1091 using namespace Clipper2Lib;
1092 // A thread-local table to avoid repetitive calculations of the coefficient
1093 // 1.0 - cos( M_PI / aCircleSegCount )
1094 // aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
1095 #define SEG_CNT_MAX 64
1096 static thread_local double arc_tolerance_factor[SEG_CNT_MAX + 1];
1097
1098 ClipperOffset c;
1099
1100 // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
1101 // and are not what you'd think they are.
1102 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
1103 JoinType joinType = JoinType::Round; // The way corners are offsetted
1104 double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
1105
1106 switch( aCornerStrategy )
1107 {
1109 joinType = JoinType::Miter;
1110 miterLimit = 10; // Allows large spikes
1111 break;
1112
1113 case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
1114 joinType = JoinType::Miter;
1115 break;
1116
1117 case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
1118 joinType = JoinType::Miter;
1119 break;
1120
1121 case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
1122 joinType = JoinType::Square;
1123 break;
1124
1125 case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
1126 joinType = JoinType::Round;
1127 break;
1128 }
1129
1130 std::vector<CLIPPER_Z_VALUE> zValues;
1131 std::vector<SHAPE_ARC> arcBuffer;
1132
1133 for( const POLYGON& poly : m_polys )
1134 {
1135 Paths64 paths;
1136
1137 for( size_t i = 0; i < poly.size(); i++ )
1138 paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
1139
1140 c.AddPaths( paths, joinType, EndType::Polygon );
1141 }
1142
1143 // Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
1144 // nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
1145 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
1146
1147 if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
1148 aCircleSegCount = 6;
1149
1150 double coeff;
1151
1152 if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
1153 {
1154 coeff = 1.0 - cos( M_PI / aCircleSegCount );
1155
1156 if( aCircleSegCount <= SEG_CNT_MAX )
1157 arc_tolerance_factor[aCircleSegCount] = coeff;
1158 }
1159 else
1160 {
1161 coeff = arc_tolerance_factor[aCircleSegCount];
1162 }
1163
1164 c.ArcTolerance( std::abs( aAmount ) * coeff );
1165 c.MiterLimit( miterLimit );
1166
1167 PolyTree64 tree;
1168
1169 if( aSimplify )
1170 {
1171 Paths64 paths;
1172 c.Execute( aAmount, paths );
1173
1174 Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, true );
1175
1176 Clipper64 c2;
1177 c2.PreserveCollinear( false );
1178 c2.ReverseSolution( false );
1179 c2.AddSubject( paths );
1180 c2.Execute(ClipType::Union, FillRule::Positive, tree);
1181 }
1182 else
1183 {
1184 c.Execute( aAmount, tree );
1185 }
1186
1187 importTree( tree, zValues, arcBuffer );
1188 tree.Clear();
1189}
1190
1191
1192void SHAPE_POLY_SET::inflateLine2( const SHAPE_LINE_CHAIN& aLine, int aAmount, int aCircleSegCount,
1193 CORNER_STRATEGY aCornerStrategy, bool aSimplify )
1194{
1195 using namespace Clipper2Lib;
1196 // A thread-local table to avoid repetitive calculations of the coefficient
1197 // 1.0 - cos( M_PI / aCircleSegCount )
1198 // aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
1199 #define SEG_CNT_MAX 64
1200 static thread_local double arc_tolerance_factor[SEG_CNT_MAX + 1];
1201
1202 ClipperOffset c;
1203
1204 // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
1205 // and are not what you'd think they are.
1206 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
1207 JoinType joinType = JoinType::Round; // The way corners are offsetted
1208 double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
1209
1210 switch( aCornerStrategy )
1211 {
1213 joinType = JoinType::Miter;
1214 miterLimit = 10; // Allows large spikes
1215 break;
1216
1217 case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
1218 joinType = JoinType::Miter;
1219 break;
1220
1221 case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
1222 joinType = JoinType::Miter;
1223 break;
1224
1225 case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
1226 joinType = JoinType::Square;
1227 break;
1228
1229 case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
1230 joinType = JoinType::Round;
1231 break;
1232 }
1233
1234 std::vector<CLIPPER_Z_VALUE> zValues;
1235 std::vector<SHAPE_ARC> arcBuffer;
1236
1237 Path64 path = aLine.convertToClipper2( true, zValues, arcBuffer );
1238 c.AddPath( path, joinType, EndType::Butt );
1239
1240 // Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
1241 // nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
1242 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
1243
1244 if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
1245 aCircleSegCount = 6;
1246
1247 double coeff;
1248
1249 if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
1250 {
1251 coeff = 1.0 - cos( M_PI / aCircleSegCount );
1252
1253 if( aCircleSegCount <= SEG_CNT_MAX )
1254 arc_tolerance_factor[aCircleSegCount] = coeff;
1255 }
1256 else
1257 {
1258 coeff = arc_tolerance_factor[aCircleSegCount];
1259 }
1260
1261 c.ArcTolerance( std::abs( aAmount ) * coeff );
1262 c.MiterLimit( miterLimit );
1263
1264 PolyTree64 tree;
1265
1266 if( aSimplify )
1267 {
1268 Paths64 paths2;
1269 c.Execute( aAmount, paths2 );
1270
1271 Clipper2Lib::SimplifyPaths( paths2, std::abs( aAmount ) * coeff, false );
1272
1273 Clipper64 c2;
1274 c2.PreserveCollinear( false );
1275 c2.ReverseSolution( false );
1276 c2.AddSubject( paths2 );
1277 c2.Execute( ClipType::Union, FillRule::Positive, tree );
1278 }
1279 else
1280 {
1281 c.Execute( aAmount, tree );
1282 }
1283
1284 importTree( tree, zValues, arcBuffer );
1285 tree.Clear();
1286}
1287
1288
1289void SHAPE_POLY_SET::Inflate( int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError,
1290 bool aSimplify )
1291{
1292 int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
1293
1294 inflate2( aAmount, segCount, aCornerStrategy, aSimplify );
1295}
1296
1297
1299 CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify )
1300{
1301 int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
1302
1303 inflateLine2( aLine, aAmount, segCount, aCornerStrategy, aSimplify );
1304}
1305
1306
1307void SHAPE_POLY_SET::importPolyPath( const std::unique_ptr<Clipper2Lib::PolyPath64>& aPolyPath,
1308 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1309 const std::vector<SHAPE_ARC>& aArcBuffer )
1310{
1311 if( !aPolyPath->IsHole() )
1312 {
1313 POLYGON paths;
1314 paths.reserve( aPolyPath->Count() + 1 );
1315 paths.emplace_back( aPolyPath->Polygon(), aZValueBuffer, aArcBuffer );
1316
1317 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& child : *aPolyPath )
1318 {
1319 paths.emplace_back( child->Polygon(), aZValueBuffer, aArcBuffer );
1320
1321 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& grandchild : *child )
1322 importPolyPath( grandchild, aZValueBuffer, aArcBuffer );
1323 }
1324
1325 m_polys.emplace_back( std::move( paths ) );
1326 }
1327}
1328
1329
1330void SHAPE_POLY_SET::importTree( Clipper2Lib::PolyTree64& tree,
1331 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1332 const std::vector<SHAPE_ARC>& aArcBuffer )
1333{
1334 m_polys.clear();
1335
1336 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& n : tree )
1337 importPolyPath( n, aZValueBuffer, aArcBuffer );
1338}
1339
1340
1341void SHAPE_POLY_SET::importPaths( Clipper2Lib::Paths64& aPath,
1342 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1343 const std::vector<SHAPE_ARC>& aArcBuffer )
1344{
1345 m_polys.clear();
1346 POLYGON path;
1347
1348 for( const Clipper2Lib::Path64& n : aPath )
1349 {
1350 if( Clipper2Lib::Area( n ) > 0 )
1351 {
1352 if( !path.empty() )
1353 m_polys.emplace_back( path );
1354
1355 path.clear();
1356 }
1357 else
1358 {
1359 wxCHECK2_MSG( !path.empty(), continue, wxT( "Cannot add a hole before an outline" ) );
1360 }
1361
1362 path.emplace_back( n, aZValueBuffer, aArcBuffer );
1363 }
1364
1365 if( !path.empty() )
1366 m_polys.emplace_back( std::move( path ) );
1367}
1368
1369
1371{
1372 using Index = int;
1373
1374 FractureEdge() = default;
1375
1376 FractureEdge( const VECTOR2I& p1, const VECTOR2I& p2, Index next ) :
1377 m_p1( p1 ),
1378 m_p2( p2 ),
1379 m_next( next )
1380 {
1381 }
1382
1383 bool matches( int y ) const
1384 {
1385 return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
1386 }
1387
1391};
1392
1393
1394typedef std::vector<FractureEdge> FractureEdgeSet;
1395
1396
1398{
1399public:
1401
1402 struct NODE
1403 {
1404 uint32_t edge;
1405 uint32_t next;
1406 };
1407
1408 template <typename Originals>
1409 FRACTURE_EDGE_INDEX( FractureEdgeSet& aEdges, Originals&& aOriginals, int aMinY, int aMaxY,
1410 uint32_t aStripeCount, size_t aHoleCount ) :
1411 m_edges( aEdges ),
1412 m_minY( aMinY ),
1413 m_maxY( aMaxY ),
1414 m_stripeCount( aStripeCount ),
1415 m_maxNodes( ( KIGEOM::FRACTURE_INDEX::MAX_BUCKET_SPAN + 2 ) * aHoleCount ),
1416 m_budget( KIGEOM::FRACTURE_INDEX::CapacityBudget( aEdges.size(), sizeof( FractureEdge ) ) )
1417 {
1418 std::vector<uint32_t> counts( m_stripeCount );
1419 size_t bucketIds = 0;
1420 size_t longIds = 0;
1421
1422 aOriginals(
1423 [&]( uint32_t, uint32_t aFirst, uint32_t aLast )
1424 {
1425 if( aLast - aFirst + 1 > KIGEOM::FRACTURE_INDEX::MAX_BUCKET_SPAN )
1426 {
1427 ++longIds;
1428 }
1429 else
1430 {
1431 bucketIds += aLast - aFirst + 1;
1432
1433 for( uint32_t stripe = aFirst; stripe <= aLast; ++stripe )
1434 ++counts[stripe];
1435 }
1436 } );
1437
1438 if( !KIGEOM::FRACTURE_INDEX::CapacityFits( bucketIds, longIds, m_stripeCount, aHoleCount, sizeof( NODE ),
1439 m_budget ) )
1440 {
1441 return;
1442 }
1443
1444 m_offsets.resize( m_stripeCount + 1 );
1445
1446 for( uint32_t stripe = 0; stripe < m_stripeCount; ++stripe )
1447 m_offsets[stripe + 1] = m_offsets[stripe] + counts[stripe];
1448
1449 m_bucketIds.resize( bucketIds );
1450 m_longIds.reserve( longIds );
1451
1452 std::copy( m_offsets.begin(), m_offsets.end() - 1, counts.begin() );
1453
1454 aOriginals(
1455 [&]( uint32_t aEdge, uint32_t aFirst, uint32_t aLast )
1456 {
1457 if( aLast - aFirst + 1 > KIGEOM::FRACTURE_INDEX::MAX_BUCKET_SPAN )
1458 {
1459 m_longIds.push_back( aEdge );
1460 }
1461 else
1462 {
1463 for( uint32_t stripe = aFirst; stripe <= aLast; ++stripe )
1464 m_bucketIds[counts[stripe]++] = aEdge;
1465 }
1466 } );
1467
1468 m_heads.assign( m_stripeCount + 1, INVALID );
1469 m_nodes.reserve( m_maxNodes );
1470
1471 // The ascending order the Query breaks rely on, asserted once rather than per visit
1472#ifndef NDEBUG
1473 for( uint32_t stripe = 0; stripe < m_stripeCount; ++stripe )
1474 {
1475 for( uint32_t pos = m_offsets[stripe] + 1; pos < m_offsets[stripe + 1]; ++pos )
1476 assert( m_bucketIds[pos] >= m_bucketIds[pos - 1] );
1477 }
1478
1479 for( size_t pos = 1; pos < m_longIds.size(); ++pos )
1480 assert( m_longIds[pos] >= m_longIds[pos - 1] );
1481#endif
1482
1483 // The estimate prevents an over-budget allocation; this check catches allocator over-capacity.
1484 size_t allocated_bytes = 0;
1485
1487 m_offsets.capacity(), counts.capacity(), m_heads.capacity(),
1488 m_nodes.capacity(), sizeof( NODE ), m_budget,
1489 &allocated_bytes ) )
1490 {
1491 return;
1492 }
1493
1494 m_valid = true;
1495 logAccepted( aEdges.size(), aHoleCount, m_stripeCount, allocated_bytes );
1496 }
1497
1498 bool IsValid() const { return m_valid; }
1499
1500 template <typename Visitor>
1501 void Query( int aY, Index aProvokingIndex, Visitor&& aVisitor ) const
1502 {
1503 const uint32_t stripe = map( aY );
1504
1505 // Buckets and the long list are ascending, so the first out-of-range id ends the scan
1506 for( uint32_t pos = m_offsets[stripe]; pos < m_offsets[stripe + 1]; ++pos )
1507 {
1508 const uint32_t edge = m_bucketIds[pos];
1509
1510 if( edge >= static_cast<uint32_t>( aProvokingIndex ) )
1511 break;
1512
1513 aVisitor( static_cast<Index>( edge ) );
1514 }
1515
1516 for( uint32_t edge : m_longIds )
1517 {
1518 if( edge >= static_cast<uint32_t>( aProvokingIndex ) )
1519 break;
1520
1521 aVisitor( static_cast<Index>( edge ) );
1522 }
1523
1524 visitOverflow( m_heads[stripe], aProvokingIndex, aVisitor );
1525 visitOverflow( m_heads[m_stripeCount], aProvokingIndex, aVisitor );
1526 }
1527
1528 void InsertBridges( Index aFirst )
1529 {
1530 for( Index edge = aFirst; edge < aFirst + 3; ++edge )
1531 {
1532 const auto [first, last] = span( m_edges[edge] );
1533
1534 if( last - first + 1 > KIGEOM::FRACTURE_INDEX::MAX_BUCKET_SPAN )
1535 {
1536 insert( static_cast<uint32_t>( edge ), m_stripeCount );
1537 }
1538 else
1539 {
1540 for( uint32_t stripe = first; stripe <= last; ++stripe )
1541 insert( static_cast<uint32_t>( edge ), stripe );
1542 }
1543 }
1544 }
1545
1546private:
1547 static constexpr uint32_t INVALID = std::numeric_limits<uint32_t>::max();
1548
1549 uint32_t map( int aY ) const { return KIGEOM::FRACTURE_INDEX::MapYToStripe( aY, m_minY, m_maxY, m_stripeCount ); }
1550
1551 std::pair<uint32_t, uint32_t> span( const FractureEdge& aEdge ) const
1552 {
1554 }
1555
1556 template <typename Visitor>
1557 void visitOverflow( uint32_t aNode, Index aProvokingIndex, Visitor&& aVisitor ) const
1558 {
1559 // Bridges are only ever inserted for already processed holes, so the assert should hold,
1560 // but the filter keeps the release build honest if that ordering ever changes
1561 while( aNode != INVALID )
1562 {
1563 const NODE& node = m_nodes[aNode];
1564 assert( node.edge < static_cast<uint32_t>( aProvokingIndex ) );
1565
1566 if( node.edge < static_cast<uint32_t>( aProvokingIndex ) )
1567 aVisitor( static_cast<Index>( node.edge ) );
1568
1569 aNode = node.next;
1570 }
1571 }
1572
1573 void insert( uint32_t aEdge, uint32_t aHead )
1574 {
1575 assert( m_nodes.size() < m_maxNodes );
1576 m_nodes.push_back( { aEdge, m_heads[aHead] } );
1577 m_heads[aHead] = static_cast<uint32_t>( m_nodes.size() - 1 );
1578 }
1579
1580 static void logAccepted( size_t aEdges, size_t aHoles, uint32_t aStripes, size_t aBytes )
1581 {
1582 wxLogTrace( wxS( "KICAD_FRACTURE_INDEX" ),
1583 wxS( "Using fracture edge index: edges=%zu, holes=%zu, stripes=%u, allocated=%zu bytes" ),
1584 aEdges, aHoles, aStripes, aBytes );
1585 }
1586
1592 size_t m_budget;
1593 bool m_valid = false;
1594 std::vector<uint32_t> m_offsets;
1595 std::vector<uint32_t> m_bucketIds;
1596 std::vector<uint32_t> m_longIds;
1597 std::vector<uint32_t> m_heads;
1598 std::vector<NODE> m_nodes;
1599};
1600
1601
1602// Where a horizontal ray at aY crosses aEdge, valid only when aEdge.matches( aY )
1603static inline int fractureIntersectX( const FractureEdge& aEdge, int aY )
1604{
1605 if( aEdge.m_p1.y == aEdge.m_p2.y )
1606 return std::max( aEdge.m_p1.x, aEdge.m_p2.x );
1607
1608 return aEdge.m_p1.x
1609 + rescale( aEdge.m_p2.x - aEdge.m_p1.x, aY - aEdge.m_p1.y, aEdge.m_p2.y - aEdge.m_p1.y );
1610}
1611
1612
1614 FractureEdge::Index edgeIndex, FractureEdge::Index bridgeIndex,
1615 FRACTURE_EDGE_INDEX* aIndex )
1616{
1617 FractureEdge& edge = edges[edgeIndex];
1618 int x = edge.m_p1.x;
1619 int y = edge.m_p1.y;
1620 int min_dist = std::numeric_limits<int>::max();
1621 int x_nearest = 0;
1622
1623 FractureEdge* e_nearest = nullptr;
1624
1625 // Since this function is run for all holes left to right, no need to
1626 // check for any edge beyond the provoking one because they will always be
1627 // further to the right, and unconnected to the outline anyway.
1628 if( aIndex )
1629 {
1630 FractureEdge::Index nearest_index = -1;
1631
1632 auto consider = [&]( FractureEdge::Index aCandidateIndex )
1633 {
1634 FractureEdge& candidate = edges[aCandidateIndex];
1635
1636 if( !candidate.matches( y ) )
1637 return;
1638
1639 int x_intersect = fractureIntersectX( candidate, y );
1640 int dist = x - x_intersect;
1641
1642 if( dist >= 0
1643 && ( dist < min_dist
1644 || ( nearest_index >= 0 && dist == min_dist && aCandidateIndex < nearest_index ) ) )
1645 {
1646 min_dist = dist;
1647 x_nearest = x_intersect;
1648 nearest_index = aCandidateIndex;
1649 }
1650 };
1651
1652 aIndex->Query( y, provokingIndex, consider );
1653
1654 if( nearest_index >= 0 )
1655 e_nearest = &edges[nearest_index];
1656 }
1657 else
1658 {
1659 for( FractureEdge::Index i = 0; i < provokingIndex; ++i )
1660 {
1661 FractureEdge& candidate = edges[i];
1662
1663 if( !candidate.matches( y ) )
1664 continue;
1665
1666 int x_intersect = fractureIntersectX( candidate, y );
1667 int dist = x - x_intersect;
1668
1669 if( dist >= 0 && dist < min_dist )
1670 {
1671 min_dist = dist;
1672 x_nearest = x_intersect;
1673 e_nearest = &candidate;
1674 }
1675 }
1676 }
1677
1678 if( e_nearest )
1679 {
1680 const FractureEdge::Index outline2hole_index = bridgeIndex;
1681 const FractureEdge::Index hole2outline_index = bridgeIndex + 1;
1682 const FractureEdge::Index split_index = bridgeIndex + 2;
1683 // Make an edge between the split outline edge and the hole...
1684 edges[outline2hole_index] = FractureEdge( VECTOR2I( x_nearest, y ), edge.m_p1, edgeIndex );
1685 // ...between the hole and the edge...
1686 edges[hole2outline_index] =
1687 FractureEdge( edge.m_p1, VECTOR2I( x_nearest, y ), split_index );
1688 // ...and between the split outline edge and the rest.
1689 edges[split_index] =
1690 FractureEdge( VECTOR2I( x_nearest, y ), e_nearest->m_p2, e_nearest->m_next );
1691
1692 // Perform the actual outline edge split
1693 // Existing stripe membership remains conservative because the selected y is inside the
1694 // edge's current interval, while newly initialized bridge edges are inserted below.
1695 e_nearest->m_p2 = VECTOR2I( x_nearest, y );
1696 e_nearest->m_next = outline2hole_index;
1697
1698 FractureEdge* last = &edge;
1699 for( ; last->m_next != edgeIndex; last = &edges[last->m_next] )
1700 ;
1701 last->m_next = hole2outline_index;
1702
1703 if( aIndex )
1704 aIndex->InsertBridges( bridgeIndex );
1705 }
1706
1707 return e_nearest;
1708}
1709
1710
1712{
1713 FractureEdgeSet edges;
1714 bool outline = true;
1715
1716 if( paths.size() == 1 )
1717 return;
1718
1719 size_t total_point_count = 0;
1720
1721 for( const SHAPE_LINE_CHAIN& path : paths )
1722 {
1723 total_point_count += path.PointCount();
1724 }
1725
1726 if( total_point_count > (size_t) std::numeric_limits<FractureEdge::Index>::max() )
1727 {
1728 wxLogWarning( wxT( "Polygon has more points than int limit" ) );
1729 return;
1730 }
1731
1732 // Reserve space in the edge set so pointers don't get invalidated during
1733 // the whole fracture process; one for each original edge, plus 3 per
1734 // path to join it to the outline.
1735 edges.reserve( total_point_count + paths.size() * 3 );
1736
1737 // Sort the paths by their lowest X bound before processing them.
1738 // This ensures the processing order for processEdge() is correct.
1739 struct PathInfo
1740 {
1741 int path_or_provoking_index;
1742 FractureEdge::Index leftmost;
1743 int x;
1744 int y_or_bridge;
1745 };
1746 std::vector<PathInfo> sorted_paths;
1747 const int paths_count = static_cast<int>( paths.size() );
1748 sorted_paths.reserve( paths_count );
1749
1750 for( int path_index = 0; path_index < paths_count; path_index++ )
1751 {
1752 const SHAPE_LINE_CHAIN& path = paths[path_index];
1753 const std::vector<VECTOR2I>& points = path.CPoints();
1754 const int point_count = static_cast<int>( points.size() );
1755 int x_min = std::numeric_limits<int>::max();
1756 int y_min = std::numeric_limits<int>::max();
1757 int leftmost = -1;
1758
1759 for( int point_index = 0; point_index < point_count; point_index++ )
1760 {
1761 const VECTOR2I& point = points[point_index];
1762 if( point.x < x_min )
1763 {
1764 x_min = point.x;
1765 leftmost = point_index;
1766 }
1767 if( point.y < y_min )
1768 y_min = point.y;
1769 }
1770
1771 sorted_paths.emplace_back( PathInfo{ path_index, leftmost, x_min, y_min } );
1772 }
1773
1774 std::sort( sorted_paths.begin() + 1, sorted_paths.end(),
1775 []( const PathInfo& a, const PathInfo& b )
1776 {
1777 if( a.x == b.x )
1778 return a.y_or_bridge < b.y_or_bridge;
1779 return a.x < b.x;
1780 } );
1781
1782 FractureEdge::Index edge_index = 0;
1783
1784 for( PathInfo& path_info : sorted_paths )
1785 {
1786 const SHAPE_LINE_CHAIN& path = paths[path_info.path_or_provoking_index];
1787 const std::vector<VECTOR2I>& points = path.CPoints();
1788 const size_t point_count = points.size();
1789
1790 // Index of the provoking (first) edge for this path
1791 const FractureEdge::Index provoking_edge = edge_index;
1792
1793 for( size_t i = 0; i < point_count - 1; i++ )
1794 {
1795 edges.emplace_back( points[i], points[i + 1], edge_index + 1 );
1796 edge_index++;
1797 }
1798
1799 // Create last edge looping back to the provoking one.
1800 edges.emplace_back( points[point_count - 1], points[0], provoking_edge );
1801 edge_index++;
1802
1803 if( !outline )
1804 {
1805 // Repurpose the path sorting data structure to schedule the leftmost edge
1806 // for merging to the outline, which will in turn merge the rest of the path.
1807 path_info.path_or_provoking_index = provoking_edge;
1808 path_info.y_or_bridge = edge_index;
1809
1810 // Reserve 3 additional edges to bridge with the outline.
1811 edge_index += 3;
1812 edges.resize( edge_index );
1813 }
1814
1815 outline = false; // first path is always the outline
1816 }
1817
1818 assert( edges.size() == total_point_count + 3 * ( paths.size() - 1 ) );
1819#ifndef NDEBUG
1820 FractureEdge::Index expected_edge = paths[0].PointCount();
1821
1822 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); ++it )
1823 {
1824 assert( it->path_or_provoking_index == expected_edge );
1825 expected_edge = it->y_or_bridge + 3;
1826 }
1827
1828 assert( expected_edge == static_cast<FractureEdge::Index>( edges.size() ) );
1829#endif
1830
1831 uint64_t estimated_visits = 0;
1832
1833 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); ++it )
1834 estimated_visits += static_cast<uint64_t>( it->path_or_provoking_index );
1835
1836 std::unique_ptr<FRACTURE_EDGE_INDEX> index;
1837
1838 if( ENABLEFRACTUREEDGEINDEX && KIGEOM::FRACTURE_INDEX::ShouldIndex( estimated_visits, paths.size() - 1 ) )
1839 {
1840 int min_y = std::numeric_limits<int>::max();
1841 int max_y = std::numeric_limits<int>::min();
1842 const uint32_t stripe_count = KIGEOM::FRACTURE_INDEX::StripeCountFor( edges.size() );
1843
1844 for( const SHAPE_LINE_CHAIN& path : paths )
1845 {
1846 for( const VECTOR2I& point : path.CPoints() )
1847 {
1848 min_y = std::min( min_y, point.y );
1849 max_y = std::max( max_y, point.y );
1850 }
1851 }
1852
1853 auto visitOriginals = [&]( auto&& aVisitor )
1854 {
1855 auto visitRange = [&]( FractureEdge::Index aBegin, FractureEdge::Index aEnd )
1856 {
1857 for( FractureEdge::Index edge = aBegin; edge < aEnd; ++edge )
1858 {
1859 const FractureEdge& original = edges[edge];
1860 const auto [first, last] = KIGEOM::FRACTURE_INDEX::StripeSpan( original.m_p1.y, original.m_p2.y,
1861 min_y, max_y, stripe_count );
1862 aVisitor( static_cast<uint32_t>( edge ), first, last );
1863 }
1864 };
1865
1866 visitRange( 0, paths[0].PointCount() );
1867
1868 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); ++it )
1869 visitRange( it->path_or_provoking_index, it->y_or_bridge );
1870 };
1871
1872 index = std::make_unique<FRACTURE_EDGE_INDEX>( edges, visitOriginals, min_y, max_y, stripe_count,
1873 paths.size() - 1 );
1874
1875 if( !index->IsValid() )
1876 index.reset();
1877 }
1878
1879 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); it++ )
1880 {
1881 auto edge = processHole( edges, it->path_or_provoking_index, it->path_or_provoking_index + it->leftmost,
1882 it->y_or_bridge, index.get() );
1883
1884 // If we can't handle the hole, the zone is broken (maybe)
1885 if( !edge )
1886 {
1887 wxLogWarning( wxT( "Broken polygon, dropping path" ) );
1888
1889 return;
1890 }
1891 }
1892
1893 paths.resize( 1 );
1894 SHAPE_LINE_CHAIN& newPath = paths[0];
1895
1896 newPath.Clear();
1897 newPath.SetClosed( true );
1898
1899 // Root edge is always at index 0
1900 FractureEdge* e = &edges[0];
1901
1902 for( ; e->m_next != 0; e = &edges[e->m_next] )
1903 newPath.Append( e->m_p1 );
1904
1905 newPath.Append( e->m_p1 );
1906}
1907
1908
1910{
1911 FractureEdgeSlow( int y = 0 ) : m_connected( false ), m_next( nullptr ) { m_p1.x = m_p2.y = y; }
1912
1913 FractureEdgeSlow( bool connected, const VECTOR2I& p1, const VECTOR2I& p2 ) :
1914 m_connected( connected ), m_p1( p1 ), m_p2( p2 ), m_next( nullptr )
1915 {
1916 }
1917
1918 bool matches( int y ) const
1919 {
1920 return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
1921 }
1922
1927};
1928
1929
1930typedef std::vector<FractureEdgeSlow*> FractureEdgeSetSlow;
1931
1932
1934{
1935 int x = edge->m_p1.x;
1936 int y = edge->m_p1.y;
1937 int min_dist = std::numeric_limits<int>::max();
1938 int x_nearest = 0;
1939
1940 FractureEdgeSlow* e_nearest = nullptr;
1941
1942 for( FractureEdgeSlow* e : edges )
1943 {
1944 if( !e->matches( y ) )
1945 continue;
1946
1947 int x_intersect;
1948
1949 if( e->m_p1.y == e->m_p2.y ) // horizontal edge
1950 {
1951 x_intersect = std::max( e->m_p1.x, e->m_p2.x );
1952 }
1953 else
1954 {
1955 x_intersect = e->m_p1.x
1956 + rescale( e->m_p2.x - e->m_p1.x, y - e->m_p1.y, e->m_p2.y - e->m_p1.y );
1957 }
1958
1959 int dist = ( x - x_intersect );
1960
1961 if( dist >= 0 && dist < min_dist && e->m_connected )
1962 {
1963 min_dist = dist;
1964 x_nearest = x_intersect;
1965 e_nearest = e;
1966 }
1967 }
1968
1969 if( e_nearest && e_nearest->m_connected )
1970 {
1971 int count = 0;
1972
1973 FractureEdgeSlow* lead1 =
1974 new FractureEdgeSlow( true, VECTOR2I( x_nearest, y ), VECTOR2I( x, y ) );
1975 FractureEdgeSlow* lead2 =
1976 new FractureEdgeSlow( true, VECTOR2I( x, y ), VECTOR2I( x_nearest, y ) );
1977 FractureEdgeSlow* split_2 =
1978 new FractureEdgeSlow( true, VECTOR2I( x_nearest, y ), e_nearest->m_p2 );
1979
1980 edges.push_back( split_2 );
1981 edges.push_back( lead1 );
1982 edges.push_back( lead2 );
1983
1984 FractureEdgeSlow* link = e_nearest->m_next;
1985
1986 e_nearest->m_p2 = VECTOR2I( x_nearest, y );
1987 e_nearest->m_next = lead1;
1988 lead1->m_next = edge;
1989
1990 FractureEdgeSlow* last;
1991
1992 for( last = edge; last->m_next != edge; last = last->m_next )
1993 {
1994 last->m_connected = true;
1995 count++;
1996 }
1997
1998 last->m_connected = true;
1999 last->m_next = lead2;
2000 lead2->m_next = split_2;
2001 split_2->m_next = link;
2002
2003 return count + 1;
2004 }
2005
2006 return 0;
2007}
2008
2009
2011{
2012 FractureEdgeSetSlow edges;
2013 FractureEdgeSetSlow border_edges;
2014 FractureEdgeSlow* root = nullptr;
2015
2016 bool first = true;
2017
2018 if( paths.size() == 1 )
2019 return;
2020
2021 int num_unconnected = 0;
2022
2023 for( const SHAPE_LINE_CHAIN& path : paths )
2024 {
2025 const std::vector<VECTOR2I>& points = path.CPoints();
2026 int pointCount = points.size();
2027
2028 FractureEdgeSlow *prev = nullptr, *first_edge = nullptr;
2029
2030 int x_min = std::numeric_limits<int>::max();
2031
2032 for( int i = 0; i < pointCount; i++ )
2033 {
2034 if( points[i].x < x_min )
2035 x_min = points[i].x;
2036
2037 // Do not use path.CPoint() here; open-coding it using the local variables "points"
2038 // and "pointCount" gives a non-trivial performance boost to zone fill times.
2039 FractureEdgeSlow* fe = new FractureEdgeSlow( first, points[i],
2040 points[i + 1 == pointCount ? 0 : i + 1] );
2041
2042 if( !root )
2043 root = fe;
2044
2045 if( !first_edge )
2046 first_edge = fe;
2047
2048 if( prev )
2049 prev->m_next = fe;
2050
2051 if( i == pointCount - 1 )
2052 fe->m_next = first_edge;
2053
2054 prev = fe;
2055 edges.push_back( fe );
2056
2057 if( !first )
2058 {
2059 if( fe->m_p1.x == x_min )
2060 border_edges.push_back( fe );
2061 }
2062
2063 if( !fe->m_connected )
2064 num_unconnected++;
2065 }
2066
2067 first = false; // first path is always the outline
2068 }
2069
2070 // keep connecting holes to the main outline, until there's no holes left...
2071 while( num_unconnected > 0 )
2072 {
2073 int x_min = std::numeric_limits<int>::max();
2074 auto it = border_edges.begin();
2075
2076 FractureEdgeSlow* smallestX = nullptr;
2077
2078 // find the left-most hole edge and merge with the outline
2079 for( ; it != border_edges.end(); ++it )
2080 {
2081 FractureEdgeSlow* border_edge = *it;
2082 int xt = border_edge->m_p1.x;
2083
2084 if( ( xt <= x_min ) && !border_edge->m_connected )
2085 {
2086 x_min = xt;
2087 smallestX = border_edge;
2088 }
2089 }
2090
2091 int num_processed = processEdge( edges, smallestX );
2092
2093 // If we can't handle the edge, the zone is broken (maybe)
2094 if( !num_processed )
2095 {
2096 wxLogWarning( wxT( "Broken polygon, dropping path" ) );
2097
2098 for( FractureEdgeSlow* edge : edges )
2099 delete edge;
2100
2101 return;
2102 }
2103
2104 num_unconnected -= num_processed;
2105 }
2106
2107 paths.clear();
2108 SHAPE_LINE_CHAIN newPath;
2109
2110 newPath.SetClosed( true );
2111
2113
2114 for( e = root; e->m_next != root; e = e->m_next )
2115 newPath.Append( e->m_p1 );
2116
2117 newPath.Append( e->m_p1 );
2118
2119 for( FractureEdgeSlow* edge : edges )
2120 delete edge;
2121
2122 paths.push_back( std::move( newPath ) );
2123}
2124
2125
2127{
2129 return fractureSingleCacheFriendly( paths );
2130 fractureSingleSlow( paths );
2131}
2132
2133
2134void SHAPE_POLY_SET::Fracture( bool aSimplify )
2135{
2136 if( aSimplify )
2137 Simplify(); // remove overlapping holes/degeneracy
2138
2139 for( POLYGON& paths : m_polys )
2140 fractureSingle( paths );
2141}
2142
2143
2145{
2146 assert( aPoly.size() == 1 );
2147
2148 SHAPE_LINE_CHAIN lc = aPoly[0];
2149 lc.Simplify();
2150
2151 // Rings are rebuilt from bare vertices, so arcs never survive unfracturing
2152 lc = SHAPE_LINE_CHAIN( lc.CPoints(), true );
2153
2154 std::vector<SHAPE_LINE_CHAIN> rings;
2155
2156 if( !splitAtBridges( lc, rings ) || rings.empty() )
2157 {
2158 aPoly[0] = std::move( lc );
2159 return;
2160 }
2161
2162 auto outline = std::max_element( rings.begin(), rings.end(),
2163 []( const SHAPE_LINE_CHAIN& aA, const SHAPE_LINE_CHAIN& aB )
2164 {
2165 return std::fabs( aA.Area() ) < std::fabs( aB.Area() );
2166 } );
2167
2168 std::iter_swap( rings.begin(), outline );
2169 aPoly = std::move( rings );
2170}
2171
2172
2174{
2175 // Iterate through all the polygons on the set
2176 for( const POLYGON& paths : m_polys )
2177 {
2178 // If any of them has more than one contour, it is a hole.
2179 if( paths.size() > 1 )
2180 return true;
2181 }
2182
2183 // Return false if and only if every polygon has just one outline, without holes.
2184 return false;
2185}
2186
2187
2189{
2190 for( POLYGON& path : m_polys )
2192
2193 Simplify(); // remove overlapping holes/degeneracy
2194}
2195
2196
2197bool SHAPE_POLY_SET::isExteriorWaist( const SEG& aSegA, const SEG& aSegB ) const
2198{
2199 const VECTOR2I da = aSegA.B - aSegA.A;
2200
2201 int axis = std::abs( da.x ) >= std::abs( da.y ) ? 0 : 1;
2202
2203 std::array<VECTOR2I,4> pts = { aSegA.A, aSegA.B, aSegB.A, aSegB.B };
2204
2205 std::sort( pts.begin(), pts.end(), [axis]( const VECTOR2I& p, const VECTOR2I& q )
2206 {
2207 if( axis == 0 )
2208 return p.x < q.x || ( p.x == q.x && p.y < q.y );
2209 else
2210 return p.y < q.y || ( p.y == q.y && p.x < q.x );
2211 } );
2212
2213 VECTOR2I s = pts[1];
2214 VECTOR2I e = pts[2];
2215
2216 // Check if there is polygon material on either side of the overlapping segments
2217 // Get the midpoint between s and e for testing
2218 VECTOR2I midpoint = ( s + e ) / 2;
2219
2220 // Create perpendicular offset vector to check both sides
2221 VECTOR2I segDir = e - s;
2222
2223 if( segDir.EuclideanNorm() > 25 )
2224 {
2225 VECTOR2I perp = segDir.Perpendicular().Resize( 10 );
2226
2227 // Both sides must be outside for this to be an exterior waist. Short-circuit if
2228 // either side is inside the polygon to avoid the second O(N) point-in-polygon test.
2229 if( !PointInside( midpoint + perp ) && !PointInside( midpoint - perp ) )
2230 {
2231 wxLogTrace( wxT( "collinear" ), wxT( "Found exterior waist between (%d,%d)-(%d,%d) and (%d,%d)-(%d,%d)" ),
2232 aSegA.A.x, aSegA.A.y, aSegA.B.x, aSegA.B.y,
2233 aSegB.A.x, aSegB.A.y, aSegB.B.x, aSegB.B.y );
2234 return true;
2235 }
2236 }
2237
2238 return false;
2239}
2240
2241
2243{
2244 for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
2245 {
2246 bool changed = true;
2247
2248 while( changed )
2249 {
2250 changed = false;
2251
2252 SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
2253 intptr_t count = outline.PointCount();
2254
2255 std::vector<SEG> segs;
2256 segs.reserve( count );
2257
2258 for( intptr_t i = 0; i < count; ++i )
2259 segs.emplace_back( outline.CPoint( i ), outline.CPoint( ( i + 1 ) % count ) );
2260
2261 SEGMENT_INDEX index( std::move( segs ) );
2262
2263 bool found = false;
2264 int segA = -1;
2265 int segB = -1;
2266
2267 for( intptr_t i = 0; i < count && !found; ++i )
2268 {
2269 const VECTOR2I& a = outline.CPoint( i );
2270 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
2271 SEG seg( a, b );
2272
2273 auto visitor =
2274 [&]( int j ) -> bool
2275 {
2276 if( j == i || j == ( ( i + 1 ) % count ) || j == ( ( i + count - 1 ) % count ) )
2277 return true;
2278
2279 VECTOR2I oa = outline.CPoint( j );
2280 VECTOR2I ob = outline.CPoint( ( j + 1 ) % count );
2281 SEG other( oa, ob );
2282
2283 // Skip segments that share start/end points. This is the case for
2284 // fractured segments
2285 if( oa == a && ob == b )
2286 return true;
2287
2288 if( oa == b && ob == a )
2289 return true;
2290
2291 if( seg.ApproxCollinear( other, 10 ) && isExteriorWaist( seg, other ) )
2292 {
2293 segA = i;
2294 segB = j;
2295 found = true;
2296 return false;
2297 }
2298
2299 return true;
2300 };
2301
2302 index.VisitCandidates( seg, 0, visitor );
2303 }
2304
2305 if( !found )
2306 break;
2307
2308 int a0 = segA;
2309 int a1 = ( segA + 1 ) % outline.PointCount();
2310 int b0 = segB;
2311 int b1 = ( segB + 1 ) % outline.PointCount();
2312
2313 SHAPE_LINE_CHAIN lc1;
2314 int idx = a1;
2315 lc1.Append( outline.CPoint( idx ) );
2316
2317 while( idx != b0 )
2318 {
2319 idx = ( idx + 1 ) % outline.PointCount();
2320 lc1.Append( outline.CPoint( idx ) );
2321 }
2322
2323 lc1.SetClosed( true );
2324
2325 SHAPE_LINE_CHAIN lc2;
2326 idx = b1;
2327 lc2.Append( outline.CPoint( idx ) );
2328
2329 while( idx != a0 )
2330 {
2331 idx = ( idx + 1 ) % outline.PointCount();
2332 lc2.Append( outline.CPoint( idx ) );
2333 }
2334
2335 lc2.SetClosed( true );
2336
2337 m_polys[polyIdx][0] = std::move( lc1 );
2338
2339 POLYGON np;
2340 np.push_back( std::move( lc2 ) );
2341 m_polys.push_back( std::move( np ) );
2342
2343 changed = true;
2344 }
2345 }
2346}
2347
2348
2350{
2351 for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
2352 {
2353 bool changed = true;
2354
2355 while( changed )
2356 {
2357 changed = false;
2358
2359 SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
2360 const int count = outline.PointCount();
2361
2362 if( count < 4 )
2363 break;
2364
2365 // Prefix trapezoid terms (SHAPE_LINE_CHAIN::Area()) to sign a candidate's halves
2366 // in O(1). Built on demand; most outlines never produce a candidate.
2367 std::vector<double> prefix;
2368
2369 auto ringTerm =
2370 [&]( int aFrom, int aTo ) -> double
2371 {
2372 if( prefix.empty() )
2373 {
2374 prefix.resize( count + 1, 0.0 );
2375
2376 for( int ii = 0; ii < count; ++ii )
2377 {
2378 const VECTOR2I& a = outline.CPoint( ii );
2379 const VECTOR2I& b = outline.CPoint( ( ii + 1 ) % count );
2380
2381 prefix[ii + 1] = prefix[ii]
2382 + ( (double) a.x + b.x ) * ( (double) a.y - b.y );
2383 }
2384 }
2385
2386 double term = aFrom <= aTo ? prefix[aTo] - prefix[aFrom]
2387 : prefix[count] - prefix[aFrom] + prefix[aTo];
2388
2389 const VECTOR2I& last = outline.CPoint( aTo );
2390 const VECTOR2I& first = outline.CPoint( aFrom );
2391
2392 return term + ( (double) last.x + first.x ) * ( (double) last.y - first.y );
2393 };
2394
2395 auto ringSize =
2396 [&]( int aFrom, int aTo )
2397 {
2398 return aFrom <= aTo ? aTo - aFrom + 1 : count - aFrom + aTo + 1;
2399 };
2400
2401 // Both halves of a real self-touch keep the parent winding. An inverted half is a
2402 // Fracture() corridor; splitting there returns the hole ring as an outline.
2403 auto splittable =
2404 [&]( int aVertIdx, int aSegIdx )
2405 {
2406 const int start = ( aSegIdx + 1 ) % count;
2407
2408 if( ringSize( start, aVertIdx ) < 3 || ringSize( aVertIdx, aSegIdx ) < 3 )
2409 return false;
2410
2411 const bool sign = ringTerm( 0, count - 1 ) > 0;
2412 const double term1 = ringTerm( start, aVertIdx );
2413 const double term2 = ringTerm( aVertIdx, aSegIdx );
2414
2415 return term1 != 0.0 && term2 != 0.0 && ( term1 > 0 ) == sign
2416 && ( term2 > 0 ) == sign;
2417 };
2418
2419 int insertSegIdx = -1;
2420 int insertVertIdx = -1;
2421
2422 if( count < RTREE_THRESHOLD )
2423 {
2424 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2425 {
2426 const VECTOR2I& pt = outline.CPoint( vertIdx );
2427 const int prevSeg = ( vertIdx + count - 1 ) % count;
2428
2429 for( int segIdx = 0; segIdx < count; ++segIdx )
2430 {
2431 // Skip adjacent segments
2432 if( segIdx == prevSeg || segIdx == vertIdx )
2433 continue;
2434
2435 const VECTOR2I& a = outline.CPoint( segIdx );
2436 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2437
2438 // SquaredDistance returns 0 only when pt lies exactly on the
2439 // segment. Clipper2 rounds corridor-cut vertices to integer
2440 // coordinates; they can land within 1nm of an endpoint but are
2441 // not true pinch points.
2442 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0
2443 && splittable( vertIdx, segIdx ) )
2444 {
2445 insertSegIdx = segIdx;
2446 insertVertIdx = vertIdx;
2447 break;
2448 }
2449 }
2450 }
2451 }
2452 else
2453 {
2454 std::vector<SEG> segs;
2455 segs.reserve( count );
2456
2457 for( int i = 0; i < count; ++i )
2458 segs.emplace_back( outline.CPoint( i ), outline.CPoint( ( i + 1 ) % count ) );
2459
2460 SEGMENT_INDEX index( std::move( segs ) );
2461
2462 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2463 {
2464 const VECTOR2I& pt = outline.CPoint( vertIdx );
2465 const int prevSeg = ( vertIdx + count - 1 ) % count;
2466
2467 auto pinchVisitor =
2468 [&]( int segIdx ) -> bool
2469 {
2470 if( segIdx == prevSeg || segIdx == vertIdx )
2471 return true;
2472
2473 const VECTOR2I& a = outline.CPoint( segIdx );
2474 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2475
2476 // SquaredDistance returns 0 only when pt lies exactly on the
2477 // segment. Clipper2 rounds corridor-cut vertices to integer
2478 // coordinates; they can land within 1nm of an endpoint but
2479 // are not true pinch points.
2480 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0
2481 && splittable( vertIdx, segIdx ) )
2482 {
2483 insertSegIdx = segIdx;
2484 insertVertIdx = vertIdx;
2485 return false;
2486 }
2487
2488 return true;
2489 };
2490
2491 index.VisitCandidates( SEG( pt, pt ), 0, pinchVisitor );
2492 }
2493 }
2494
2495 if( insertSegIdx < 0 )
2496 break;
2497
2498 // Split the polygon at the pinch point into two separate polygons.
2499 // Polygon 1: vertices from (insertSegIdx+1) to insertVertIdx
2500 // Polygon 2: vertices from insertVertIdx to insertSegIdx
2501
2502 const int splitStart1 = ( insertSegIdx + 1 ) % count;
2503 const int size1 = ringSize( splitStart1, insertVertIdx );
2504 const int size2 = ringSize( insertVertIdx, insertSegIdx );
2505
2506 SHAPE_LINE_CHAIN poly1;
2507 SHAPE_LINE_CHAIN poly2;
2508 poly1.ReservePoints( size1 );
2509 poly2.ReservePoints( size2 );
2510
2511 int idx = splitStart1;
2512
2513 for( int i = 0; i < size1; ++i )
2514 {
2515 poly1.Append( outline.CPoint( idx ) );
2516 idx = ( idx + 1 ) % count;
2517 }
2518
2519 poly1.SetClosed( true );
2520
2521 idx = insertVertIdx;
2522
2523 for( int i = 0; i < size2; ++i )
2524 {
2525 poly2.Append( outline.CPoint( idx ) );
2526 idx = ( idx + 1 ) % count;
2527 }
2528
2529 poly2.SetClosed( true );
2530
2531 m_polys[polyIdx][0] = std::move( poly1 );
2532
2533 POLYGON np;
2534 np.push_back( std::move( poly2 ) );
2535 m_polys.push_back( std::move( np ) );
2536
2537 changed = true;
2538 }
2539 }
2540}
2541
2542
2544{
2546
2548
2549 booleanOp( Clipper2Lib::ClipType::Union, empty );
2550}
2551
2552
2554{
2555 for( POLYGON& paths : m_polys )
2556 {
2557 for( SHAPE_LINE_CHAIN& path : paths )
2558 {
2559 path.Simplify( aTolerance );
2560 }
2561 }
2562}
2563
2564
2566{
2567 // We are expecting only one main outline, but this main outline can have holes
2568 // if holes: combine holes and remove them from the main outline.
2569 // Note also we are usingin polygon
2570 // calculations, but it is not mandatory. It is used mainly
2571 // because there is usually only very few vertices in area outlines
2572 SHAPE_POLY_SET::POLYGON& outline = Polygon( 0 );
2573 SHAPE_POLY_SET holesBuffer;
2574
2575 // Move holes stored in outline to holesBuffer:
2576 // The first SHAPE_LINE_CHAIN is the main outline, others are holes
2577 while( outline.size() > 1 )
2578 {
2579 holesBuffer.AddOutline( outline.back() );
2580 outline.pop_back();
2581 }
2582
2583 Simplify();
2584
2585 // If any hole, subtract it to main outline
2586 if( holesBuffer.OutlineCount() )
2587 {
2588 holesBuffer.Simplify();
2589 BooleanSubtract( holesBuffer );
2590 }
2591
2592 // In degenerate cases, simplify might return no outlines
2593 if( OutlineCount() > 0 )
2595
2596 return OutlineCount();
2597}
2598
2599
2600const std::string SHAPE_POLY_SET::Format( bool aCplusPlus ) const
2601{
2602 std::stringstream ss;
2603
2604 ss << "SHAPE_LINE_CHAIN poly; \n";
2605
2606 for( unsigned i = 0; i < m_polys.size(); i++ )
2607 {
2608 for( unsigned j = 0; j < m_polys[i].size(); j++ )
2609 {
2610
2611 ss << "{ auto tmp = " << m_polys[i][j].Format() << ";\n";
2612
2613 SHAPE_POLY_SET poly;
2614
2615 if( j == 0 )
2616 {
2617 ss << " poly.AddOutline(tmp); } \n";
2618 }
2619 else
2620 {
2621 ss << " poly.AddHole(tmp); } \n";
2622 }
2623
2624 }
2625 }
2626
2627 return ss.str();
2628}
2629
2630
2631bool SHAPE_POLY_SET::Parse( std::stringstream& aStream )
2632{
2633 std::string tmp;
2634
2635 aStream >> tmp;
2636
2637 if( tmp != "polyset" )
2638 return false;
2639
2640 aStream >> tmp;
2641
2642 int n_polys = atoi( tmp.c_str() );
2643
2644 if( n_polys < 0 )
2645 return false;
2646
2647 for( int i = 0; i < n_polys; i++ )
2648 {
2649 POLYGON paths;
2650
2651 aStream >> tmp;
2652
2653 if( tmp != "poly" )
2654 return false;
2655
2656 aStream >> tmp;
2657 int n_outlines = atoi( tmp.c_str() );
2658
2659 if( n_outlines < 0 )
2660 return false;
2661
2662 for( int j = 0; j < n_outlines; j++ )
2663 {
2664 SHAPE_LINE_CHAIN outline;
2665
2666 outline.SetClosed( true );
2667
2668 aStream >> tmp;
2669 int n_vertices = atoi( tmp.c_str() );
2670
2671 for( int v = 0; v < n_vertices; v++ )
2672 {
2673 VECTOR2I p;
2674
2675 aStream >> tmp; p.x = atoi( tmp.c_str() );
2676 aStream >> tmp; p.y = atoi( tmp.c_str() );
2677 outline.Append( p );
2678 }
2679
2680 paths.push_back( std::move( outline ) );
2681 }
2682
2683 m_polys.push_back( std::move( paths ) );
2684 }
2685
2686 return true;
2687}
2688
2689
2690const BOX2I SHAPE_POLY_SET::BBox( int aClearance ) const
2691{
2692 BOX2I bb;
2693
2694 for( unsigned i = 0; i < m_polys.size(); i++ )
2695 {
2696 if( i == 0 )
2697 bb = m_polys[i][0].BBox();
2698 else
2699 bb.Merge( m_polys[i][0].BBox() );
2700 }
2701
2702 bb.Inflate( aClearance );
2703 return bb;
2704}
2705
2706
2708{
2709 BOX2I bb;
2710
2711 for( unsigned i = 0; i < m_polys.size(); i++ )
2712 {
2713 if( i == 0 )
2714 bb = *m_polys[i][0].GetCachedBBox();
2715 else
2716 bb.Merge( *m_polys[i][0].GetCachedBBox() );
2717 }
2718
2719 return bb;
2720}
2721
2722
2723bool SHAPE_POLY_SET::PointOnEdge( const VECTOR2I& aP, int aAccuracy ) const
2724{
2725 // Iterate through all the polygons in the set
2726 for( const POLYGON& polygon : m_polys )
2727 {
2728 // Iterate through all the line chains in the polygon
2729 for( const SHAPE_LINE_CHAIN& lineChain : polygon )
2730 {
2731 if( lineChain.PointOnEdge( aP, aAccuracy ) )
2732 return true;
2733 }
2734 }
2735
2736 return false;
2737}
2738
2739
2740bool SHAPE_POLY_SET::Collide( const SEG& aSeg, int aClearance, int* aActual,
2741 VECTOR2I* aLocation ) const
2742{
2743 VECTOR2I nearest;
2744 ecoord dist_sq = SquaredDistanceToSeg( aSeg, aLocation ? &nearest : nullptr );
2745
2746 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2747 {
2748 if( aLocation )
2749 *aLocation = nearest;
2750
2751 if( aActual )
2752 *aActual = sqrt( dist_sq );
2753
2754 return true;
2755 }
2756
2757 return false;
2758}
2759
2760
2761bool SHAPE_POLY_SET::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
2762 VECTOR2I* aLocation ) const
2763{
2764 if( IsEmpty() || VertexCount() == 0 )
2765 return false;
2766
2767 VECTOR2I nearest;
2768 ecoord dist_sq = SquaredDistance( aP, false, aLocation ? &nearest : nullptr );
2769
2770 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2771 {
2772 if( aLocation )
2773 *aLocation = nearest;
2774
2775 if( aActual )
2776 *aActual = sqrt( dist_sq );
2777
2778 return true;
2779 }
2780
2781 return false;
2782}
2783
2784
2785bool SHAPE_POLY_SET::Collide( const SHAPE* aShape, int aClearance, int* aActual,
2786 VECTOR2I* aLocation ) const
2787{
2788 // A couple of simple cases are worth trying before we fall back on triangulation.
2789
2790 if( aShape->Type() == SH_SEGMENT )
2791 {
2792 const SHAPE_SEGMENT* segment = static_cast<const SHAPE_SEGMENT*>( aShape );
2793 int extra = segment->GetWidth() / 2;
2794
2795 if( Collide( segment->GetSeg(), aClearance + extra, aActual, aLocation ) )
2796 {
2797 if( aActual )
2798 *aActual = std::max( 0, *aActual - extra );
2799
2800 return true;
2801 }
2802
2803 return false;
2804 }
2805
2806 if( aShape->Type() == SH_CIRCLE )
2807 {
2808 const SHAPE_CIRCLE* circle = static_cast<const SHAPE_CIRCLE*>( aShape );
2809 int extra = circle->GetRadius();
2810
2811 if( Collide( circle->GetCenter(), aClearance + extra, aActual, aLocation ) )
2812 {
2813 if( aActual )
2814 *aActual = std::max( 0, *aActual - extra );
2815
2816 return true;
2817 }
2818
2819 return false;
2820 }
2821
2822 const_cast<SHAPE_POLY_SET*>( this )->CacheTriangulation( false, {}, true );
2823
2824 if( !aActual && !aLocation )
2825 return AnyTriangleHit( m_triIndex.get(), m_triangulatedPolys, aShape, aClearance );
2826
2827 int actual;
2829
2830 if( !AnyTriangleHit( m_triIndex.get(), m_triangulatedPolys, aShape, aClearance, &actual, &location ) )
2831 return false;
2832
2833 if( aActual )
2834 *aActual = std::max( 0, actual );
2835
2836 if( aLocation )
2837 *aLocation = location;
2838
2839 return true;
2840}
2841
2842
2844{
2845 m_triIndex.reset();
2846
2847 m_polys.clear();
2848 m_triangulatedPolys.clear();
2849 m_triangulationValid = false;
2850}
2851
2852
2853void SHAPE_POLY_SET::RemoveContour( int aContourIdx, int aPolygonIdx )
2854{
2855 // Default polygon is the last one
2856 if( aPolygonIdx < 0 )
2857 aPolygonIdx += m_polys.size();
2858
2859 m_polys[aPolygonIdx].erase( m_polys[aPolygonIdx].begin() + aContourIdx );
2860}
2861
2862
2863void SHAPE_POLY_SET::RemoveOutline( int aOutlineIdx )
2864{
2865 m_polys.erase( m_polys.begin() + aOutlineIdx );
2866}
2867
2868
2870{
2871 int removed = 0;
2872
2873 ITERATOR iterator = IterateWithHoles();
2874
2875 VECTOR2I contourStart = *iterator;
2876 VECTOR2I segmentStart, segmentEnd;
2877
2878 VERTEX_INDEX indexStart;
2879 std::vector<VERTEX_INDEX> indices_to_remove;
2880
2881 while( iterator )
2882 {
2883 // Obtain first point and its index
2884 segmentStart = *iterator;
2885 indexStart = iterator.GetIndex();
2886
2887 // Obtain last point
2888 if( iterator.IsEndContour() )
2889 {
2890 segmentEnd = contourStart;
2891
2892 // Advance
2893 iterator++;
2894
2895 // If we have rolled into the next contour, remember its position
2896 // segmentStart and segmentEnd remain valid for comparison here
2897 if( iterator )
2898 contourStart = *iterator;
2899 }
2900 else
2901 {
2902 // Advance
2903 iterator++;
2904
2905 // If we have reached the end of the SHAPE_POLY_SET, something is broken here
2906 wxCHECK_MSG( iterator, removed, wxT( "Invalid polygon. Reached end without noticing. Please report this error" ) );
2907
2908 segmentEnd = *iterator;
2909 }
2910
2911 // Remove segment start if both points are equal
2912 if( segmentStart == segmentEnd )
2913 {
2914 indices_to_remove.push_back( indexStart );
2915 removed++;
2916 }
2917 }
2918
2919 // Proceed in reverse direction to remove the vertices because they are stored as absolute indices in a vector
2920 // Removing in reverse order preserves the remaining index values
2921 for( auto it = indices_to_remove.rbegin(); it != indices_to_remove.rend(); ++it )
2922 RemoveVertex( *it );
2923
2924 return removed;
2925}
2926
2927
2929{
2930 m_polys.erase( m_polys.begin() + aIdx );
2931}
2932
2933
2935{
2936 m_triIndex.reset();
2937
2938 m_polys.erase( m_polys.begin() + aIdx );
2939
2941 {
2942 for( int ii = m_triangulatedPolys.size() - 1; ii >= 0; --ii )
2943 {
2944 std::unique_ptr<TRIANGULATED_POLYGON>& triangleSet = m_triangulatedPolys[ii];
2945
2946 if( triangleSet->GetSourceOutlineIndex() == aIdx )
2947 m_triangulatedPolys.erase( m_triangulatedPolys.begin() + ii );
2948 else if( triangleSet->GetSourceOutlineIndex() > aIdx )
2949 triangleSet->SetSourceOutlineIndex( triangleSet->GetSourceOutlineIndex() - 1 );
2950 }
2951
2952 if( aUpdateHash )
2953 {
2954 m_hash = checksum();
2955 m_hashValid = true;
2956 }
2957 }
2958}
2959
2960
2966
2967
2969{
2970 m_polys.insert( m_polys.end(), aSet.m_polys.begin(), aSet.m_polys.end() );
2971}
2972
2973
2974void SHAPE_POLY_SET::Append( const VECTOR2I& aP, int aOutline, int aHole )
2975{
2976 Append( aP.x, aP.y, aOutline, aHole );
2977}
2978
2979
2981 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
2982 int aClearance ) const
2983{
2984 // Shows whether there was a collision
2985 bool collision = false;
2986
2987 // Difference vector between each vertex and aPoint.
2989 ecoord distance_squared;
2990 ecoord clearance_squared = SEG::Square( aClearance );
2991
2992 for( CONST_ITERATOR iterator = CIterateWithHoles(); iterator; iterator++ )
2993 {
2994 // Get the difference vector between current vertex and aPoint
2995 delta = *iterator - aPoint;
2996
2997 // Compute distance
2998 distance_squared = delta.SquaredEuclideanNorm();
2999
3000 // Check for collisions
3001 if( distance_squared <= clearance_squared )
3002 {
3003 if( !aClosestVertex )
3004 return true;
3005
3006 collision = true;
3007
3008 // Update clearance to look for closer vertices
3009 clearance_squared = distance_squared;
3010
3011 // Store the indices that identify the vertex
3012 *aClosestVertex = iterator.GetIndex();
3013 }
3014 }
3015
3016 return collision;
3017}
3018
3019
3021 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
3022 int aClearance ) const
3023{
3024 // Shows whether there was a collision
3025 bool collision = false;
3026 ecoord clearance_squared = SEG::Square( aClearance );
3027
3028 for( CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
3029 {
3030 const SEG currentSegment = *iterator;
3031 ecoord distance_squared = currentSegment.SquaredDistance( aPoint );
3032
3033 // Check for collisions
3034 if( distance_squared <= clearance_squared )
3035 {
3036 if( !aClosestVertex )
3037 return true;
3038
3039 collision = true;
3040
3041 // Update clearance to look for closer edges
3042 clearance_squared = distance_squared;
3043
3044 // Store the indices that identify the vertex
3045 *aClosestVertex = iterator.GetIndex();
3046 }
3047 }
3048
3049 return collision;
3050}
3051
3052
3054{
3055 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
3056 {
3057 COutline( polygonIdx ).GenerateBBoxCache();
3058
3059 for( int holeIdx = 0; holeIdx < HoleCount( polygonIdx ); holeIdx++ )
3060 CHole( polygonIdx, holeIdx ).GenerateBBoxCache();
3061 }
3062}
3063
3064
3065bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
3066 bool aUseBBoxCaches ) const
3067{
3068 if( m_polys.empty() )
3069 return false;
3070
3071 // If there is a polygon specified, check the condition against that polygon
3072 if( aSubpolyIndex >= 0 )
3073 return containsSingle( aP, aSubpolyIndex, aAccuracy, aUseBBoxCaches );
3074
3075 // In any other case, check it against all polygons in the set
3076 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
3077 {
3078 if( containsSingle( aP, polygonIdx, aAccuracy, aUseBBoxCaches ) )
3079 return true;
3080 }
3081
3082 return false;
3083}
3084
3085
3086void SHAPE_POLY_SET::RemoveVertex( int aGlobalIndex )
3087{
3089
3090 // Assure the to be removed vertex exists, abort otherwise
3091 if( GetRelativeIndices( aGlobalIndex, &index ) )
3093 else
3094 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
3095}
3096
3097
3099{
3100 m_polys[aIndex.m_polygon][aIndex.m_contour].Remove( aIndex.m_vertex );
3101}
3102
3103
3104void SHAPE_POLY_SET::SetVertex( int aGlobalIndex, const VECTOR2I& aPos )
3105{
3107
3108 if( GetRelativeIndices( aGlobalIndex, &index ) )
3109 SetVertex( index, aPos );
3110 else
3111 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
3112}
3113
3114
3115void SHAPE_POLY_SET::SetVertex( const VERTEX_INDEX& aIndex, const VECTOR2I& aPos )
3116{
3117 m_polys[aIndex.m_polygon][aIndex.m_contour].SetPoint( aIndex.m_vertex, aPos );
3118}
3119
3120
3121bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
3122 bool aUseBBoxCaches ) const
3123{
3124 // Check that the point is inside the outline
3125 if( m_polys[aSubpolyIndex][0].PointInside( aP, aAccuracy ) )
3126 {
3127 // Check that the point is not in any of the holes
3128 for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
3129 {
3130 const SHAPE_LINE_CHAIN& hole = CHole( aSubpolyIndex, holeIdx );
3131
3132 // If the point is inside a hole it is outside of the polygon. Do not use aAccuracy
3133 // here as it's meaning would be inverted.
3134 if( hole.PointInside( aP, 1, aUseBBoxCaches ) )
3135 return false;
3136 }
3137
3138 return true;
3139 }
3140
3141 return false;
3142}
3143
3144
3145void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
3146{
3147 m_triIndex.reset();
3148
3149 for( POLYGON& poly : m_polys )
3150 {
3151 for( SHAPE_LINE_CHAIN& path : poly )
3152 path.Move( aVector );
3153 }
3154
3155 for( std::unique_ptr<TRIANGULATED_POLYGON>& tri : m_triangulatedPolys )
3156 tri->Move( aVector );
3157
3158 m_hash = checksum();
3159 m_hashValid = true;
3160}
3161
3162
3163void SHAPE_POLY_SET::Mirror( const VECTOR2I& aRef, FLIP_DIRECTION aFlipDirection )
3164{
3165 for( POLYGON& poly : m_polys )
3166 {
3167 for( SHAPE_LINE_CHAIN& path : poly )
3168 path.Mirror( aRef, aFlipDirection );
3169 }
3170
3173}
3174
3175
3176void SHAPE_POLY_SET::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
3177{
3178 for( POLYGON& poly : m_polys )
3179 {
3180 for( SHAPE_LINE_CHAIN& path : poly )
3181 path.Rotate( aAngle, aCenter );
3182 }
3183
3184 // Don't re-cache if the triangulation is already invalid
3187}
3188
3189
3191{
3192 int c = 0;
3193
3194 for( const POLYGON& poly : m_polys )
3195 {
3196 for( const SHAPE_LINE_CHAIN& path : poly )
3197 c += path.PointCount();
3198 }
3199
3200 return c;
3201}
3202
3203
3204SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex )
3205{
3206 return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0 );
3207}
3208
3209
3210SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax,
3211 int aIndex )
3212{
3213 return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax );
3214}
3215
3216
3218 VECTOR2I* aNearest ) const
3219{
3220 // We calculate the min dist between the segment and each outline segment. However, if the
3221 // segment to test is inside the outline, and does not cross any edge, it can be seen outside
3222 // the polygon. Therefore test if a segment end is inside (testing only one end is enough).
3223 // Use an accuracy of "1" to say that we don't care if it's exactly on the edge or not.
3224 if( containsSingle( aPoint, aPolygonIndex, 1 ) )
3225 {
3226 if( aNearest )
3227 *aNearest = aPoint;
3228
3229 return 0;
3230 }
3231
3232 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
3233
3234 SEG::ecoord minDistance = (*iterator).SquaredDistance( aPoint );
3235
3236 if( aNearest )
3237 *aNearest = ( *iterator ).NearestPoint( aPoint );
3238
3239 for( iterator++; iterator && minDistance > 0; iterator++ )
3240 {
3241 SEG::ecoord currentDistance = (*iterator).SquaredDistance( aPoint );
3242
3243 if( currentDistance < minDistance )
3244 {
3245 if( aNearest )
3246 *aNearest = (*iterator).NearestPoint( aPoint );
3247
3248 minDistance = currentDistance;
3249 }
3250 }
3251
3252 return minDistance;
3253}
3254
3255
3257 VECTOR2I* aNearest ) const
3258{
3259 // Check if the segment is fully-contained. If so, its midpoint is a good-enough nearest point.
3260 if( containsSingle( aSegment.A, aPolygonIndex, 1 ) &&
3261 containsSingle( aSegment.B, aPolygonIndex, 1 ) )
3262 {
3263 if( aNearest )
3264 *aNearest = ( aSegment.A + aSegment.B ) / 2;
3265
3266 return 0;
3267 }
3268
3269 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
3270 SEG::ecoord minDistance = (*iterator).SquaredDistance( aSegment );
3271
3272 if( aNearest )
3273 *aNearest = ( *iterator ).NearestPoint( aSegment );
3274
3275 for( iterator++; iterator && minDistance > 0; iterator++ )
3276 {
3277 SEG::ecoord currentDistance = (*iterator).SquaredDistance( aSegment );
3278
3279 if( currentDistance < minDistance )
3280 {
3281 if( aNearest )
3282 *aNearest = (*iterator).NearestPoint( aSegment );
3283
3284 minDistance = currentDistance;
3285 }
3286 }
3287
3288 // Return the maximum of minDistance and zero
3289 return minDistance < 0 ? 0 : minDistance;
3290}
3291
3292
3293SEG::ecoord SHAPE_POLY_SET::SquaredDistance( const VECTOR2I& aPoint, bool aOutlineOnly,
3294 VECTOR2I* aNearest ) const
3295{
3296 wxASSERT_MSG( !aOutlineOnly, wxT( "Warning: SHAPE_POLY_SET::SquaredDistance does not yet "
3297 "support aOutlineOnly==true" ) );
3298
3299 SEG::ecoord currentDistance_sq;
3300 SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
3301 VECTOR2I nearest;
3302
3303 // Iterate through all the polygons and get the minimum distance.
3304 for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
3305 {
3306 currentDistance_sq = SquaredDistanceToPolygon( aPoint, polygonIdx,
3307 aNearest ? &nearest : nullptr );
3308
3309 if( currentDistance_sq < minDistance_sq )
3310 {
3311 if( aNearest )
3312 *aNearest = nearest;
3313
3314 minDistance_sq = currentDistance_sq;
3315 }
3316 }
3317
3318 return minDistance_sq;
3319}
3320
3321
3323{
3324 SEG::ecoord currentDistance_sq;
3325 SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
3326 VECTOR2I nearest;
3327
3328 // Iterate through all the polygons and get the minimum distance.
3329 for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
3330 {
3331 currentDistance_sq = SquaredDistanceToPolygon( aSegment, polygonIdx,
3332 aNearest ? &nearest : nullptr );
3333
3334 if( currentDistance_sq < minDistance_sq )
3335 {
3336 if( aNearest )
3337 *aNearest = nearest;
3338
3339 minDistance_sq = currentDistance_sq;
3340 }
3341 }
3342
3343 return minDistance_sq;
3344}
3345
3346
3348{
3350
3351 // Get the polygon and contour where the vertex is. If the vertex does not exist, return false
3352 if( !GetRelativeIndices( aGlobalIdx, &index ) )
3353 return false;
3354
3355 // The contour is a hole if its index is greater than zero
3356 return index.m_contour > 0;
3357}
3358
3359
3361{
3362 SHAPE_POLY_SET chamfered;
3363
3364 for( unsigned int idx = 0; idx < m_polys.size(); idx++ )
3365 chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx ) );
3366
3367 return chamfered;
3368}
3369
3370
3371SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax )
3372{
3373 SHAPE_POLY_SET filleted;
3374
3375 for( size_t idx = 0; idx < m_polys.size(); idx++ )
3376 filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx ) );
3377
3378 return filleted;
3379}
3380
3381
3383{
3384 if( this != &aOther )
3385 {
3386 SHAPE::operator=( aOther );
3387
3388 m_polys = std::move( aOther.m_polys );
3389 m_triangulatedPolys = std::move( aOther.m_triangulatedPolys );
3390
3391 m_triIndex = std::move( aOther.m_triIndex );
3392
3393 m_hash = aOther.m_hash;
3394 m_hashValid.store( aOther.m_hashValid.load() );
3395 m_triangulationValid.store( aOther.m_triangulationValid );
3396 }
3397
3398 return *this;
3399}
3400
3401
3403{
3404 m_triIndex.reset();
3405
3406 SHAPE::operator=( aOther );
3407 m_polys = aOther.m_polys;
3408
3409 m_triangulatedPolys.clear();
3410
3411 if( aOther.IsTriangulationUpToDate() )
3412 {
3413 m_triangulatedPolys.reserve( aOther.TriangulatedPolyCount() );
3414
3415 for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
3416 {
3417 const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
3418 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
3419 }
3420
3421 m_hash = aOther.m_hash;
3422 m_hashValid.store( aOther.m_hashValid.load() );
3424 }
3425 else
3426 {
3427 m_hash.Clear();
3428 m_hashValid = false;
3429 m_triangulationValid = false;
3430 }
3431
3432 m_failedHash = aOther.m_failedHash;
3433 m_failedHashValid.store( aOther.m_failedHashValid.load() );
3434
3435 return *this;
3436}
3437
3438
3440{
3441 if( !m_hashValid )
3442 return checksum();
3443
3444 return m_hash;
3445}
3446
3447
3449{
3451 return false;
3452
3453 if( !m_hashValid )
3454 return false;
3455
3456 HASH_128 hash = checksum();
3457
3458 return hash == m_hash;
3459}
3460
3461
3462void SHAPE_POLY_SET::cacheTriangulation( bool aSimplify, std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* aHintData,
3463 const TASK_SUBMITTER& aSubmitter, bool aBuildIndexes )
3464{
3465 auto buildIndexes = [this]()
3466 {
3467 if( m_triIndex )
3468 return;
3469
3470 int triCount = 0;
3471
3472 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
3473 triCount += static_cast<int>( tpoly->GetTriangleCount() );
3474
3475 if( triCount < RTREE_THRESHOLD )
3476 return;
3477
3478 m_triIndex = std::make_unique<TRIANGULATION_INDEX>( m_triangulatedPolys );
3479 };
3480
3481 // if( m_triangulationValid && m_hashValid && m_hash == checksum() )
3482 // return;
3483 // if( m_failedHashValid && m_failedHash == checksum() )
3484 // return;
3485
3486 std::unique_lock<std::mutex> lock( m_triangulationMutex );
3487
3489 {
3490 if( aBuildIndexes )
3491 buildIndexes();
3492
3493 return;
3494 }
3496 return;
3497
3498 // Invalidate, in case anything goes wrong below
3499 m_triIndex.reset();
3500 m_triangulationValid = false;
3501 m_hashValid = false;
3502 m_failedHashValid = false;
3503
3504 auto triangulate =
3505 []( SHAPE_POLY_SET& polySet, int forOutline,
3506 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>& dest,
3507 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* hintData,
3508 const TASK_SUBMITTER& taskSubmitter )
3509 {
3510 bool triangulationValid = false;
3511 int pass = 0;
3512 int index = 0;
3513
3514 if( hintData && hintData->size() != (unsigned) polySet.OutlineCount() )
3515 hintData = nullptr;
3516
3517 while( polySet.OutlineCount() > 0 )
3518 {
3519 if( !dest.empty() && dest.back()->GetTriangleCount() == 0 )
3520 dest.erase( dest.end() - 1 );
3521
3522 {
3523 const SHAPE_LINE_CHAIN& outline = polySet.Polygon( 0 ).front();
3524 TRIANGULATED_POLYGON partitionScratch( forOutline );
3525 POLYGON_TRIANGULATION partitioner( partitionScratch );
3526 size_t partitionLeaves = partitioner.suggestedPartitionLeafCount( outline );
3527
3528 if( partitionLeaves > 1 )
3529 {
3530 std::vector<SHAPE_LINE_CHAIN> partitions =
3531 partitioner.partitionPolygonBalanced( outline, partitionLeaves );
3532
3533 if( partitions.size() > 1 )
3534 {
3535 polySet.DeletePolygon( 0 );
3536
3537 if( taskSubmitter && partitions.size() > 2 )
3538 {
3539 size_t leafCount = partitions.size();
3540
3541 struct WorkStealState
3542 {
3543 std::unique_ptr<std::atomic<bool>[]> claimed;
3544 std::unique_ptr<std::atomic<bool>[]> done;
3545 std::unique_ptr<std::atomic<bool>[]> ok;
3546
3547 explicit WorkStealState( size_t n ) :
3548 claimed( new std::atomic<bool>[n] ),
3549 done( new std::atomic<bool>[n] ),
3550 ok( new std::atomic<bool>[n] )
3551 {
3552 for( size_t i = 0; i < n; ++i )
3553 {
3554 claimed[i].store( false );
3555 done[i].store( false );
3556 ok[i].store( false );
3557 }
3558 }
3559 };
3560
3561 auto state = std::make_shared<WorkStealState>( leafCount );
3562
3563 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>> results( leafCount );
3564
3565 for( size_t i = 0; i < leafCount; ++i )
3566 {
3567 results[i] = std::make_unique<TRIANGULATED_POLYGON>( forOutline );
3568 }
3569
3570 for( size_t i = 0; i < leafCount; ++i )
3571 {
3572 auto* triPoly = results[i].get();
3573 auto* leaf = &partitions[i];
3574
3575 taskSubmitter( [state, i, triPoly, leaf]()
3576 {
3577 if( state->claimed[i].exchange( true ) )
3578 return;
3579
3580 POLYGON_TRIANGULATION tess( *triPoly );
3581 state->ok[i].store( tess.TesselatePolygon( *leaf, nullptr ) );
3582 state->done[i].store( true, std::memory_order_release );
3583 } );
3584 }
3585
3586 for( size_t i = 0; i < leafCount; ++i )
3587 {
3588 if( state->claimed[i].exchange( true ) )
3589 continue;
3590
3591 POLYGON_TRIANGULATION tess( *results[i] );
3592 state->ok[i].store( tess.TesselatePolygon( partitions[i], nullptr ) );
3593 state->done[i].store( true, std::memory_order_release );
3594 }
3595
3596 for( size_t i = 0; i < leafCount; ++i )
3597 {
3598 while( !state->done[i].load( std::memory_order_acquire ) )
3599 {
3600 std::this_thread::yield();
3601 }
3602 }
3603
3604 bool allOk = true;
3605
3606 for( size_t i = 0; i < leafCount; ++i )
3607 allOk = allOk && state->ok[i].load();
3608
3609 if( allOk )
3610 {
3611 for( auto& r : results )
3612 {
3613 if( r->GetTriangleCount() > 0 )
3614 dest.push_back( std::move( r ) );
3615 }
3616
3617 triangulationValid = true;
3618 hintData = nullptr;
3619 continue;
3620 }
3621
3622 }
3623
3624 for( auto it = partitions.rbegin(); it != partitions.rend(); ++it )
3625 polySet.AddOutline( *it );
3626
3627 hintData = nullptr;
3628 continue;
3629 }
3630 }
3631 }
3632
3633 dest.push_back( std::make_unique<TRIANGULATED_POLYGON>( forOutline ) );
3634 POLYGON_TRIANGULATION tess( *dest.back() );
3635
3636 // If the tessellation fails, we re-fracture the polygon, which will
3637 // first simplify the system before fracturing and removing the holes
3638 // This may result in multiple, disjoint polygons.
3639 if( !tess.TesselatePolygon( polySet.Polygon( 0 ).front(),
3640 hintData ? hintData->at( index ).get() : nullptr ) )
3641 {
3642 ++pass;
3643
3644 if( pass == 1 )
3645 {
3647 }
3648 // In Clipper2, there is only one type of simplification
3649 else
3650 {
3651 break;
3652 }
3653
3654 triangulationValid = false;
3655 hintData = nullptr;
3656 continue;
3657 }
3658
3659 polySet.DeletePolygon( 0 );
3660 index++;
3661 triangulationValid = true;
3662 }
3663
3664 return triangulationValid;
3665 };
3666
3667 m_triangulatedPolys.clear();
3668
3669 const SHAPE_POLY_SET* srcSet = this;
3670 SHAPE_POLY_SET tmpSet;
3671
3672 if( ArcCount() > 0 || aSimplify )
3673 {
3674 tmpSet = SHAPE_POLY_SET( *this );
3675 tmpSet.ClearArcs();
3676
3677 if( aSimplify )
3678 tmpSet.Simplify();
3679
3680 srcSet = &tmpSet;
3681 }
3682
3683 bool directOk = true;
3684
3685 for( int ii = 0; ii < srcSet->OutlineCount() && directOk; ++ii )
3686 {
3687 const POLYGON& poly = srcSet->CPolygon( ii );
3688 size_t prevCount = m_triangulatedPolys.size();
3689
3690 if( poly.size() > 1 )
3691 {
3692 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3694
3695 if( tess.TesselatePolygon( poly, nullptr ) )
3696 continue;
3697
3698 // Hole bridging failed; fracture to merge holes into the outline
3699 m_triangulatedPolys.resize( prevCount );
3700
3701 SHAPE_POLY_SET flatSet( poly.front() );
3702
3703 for( size_t jj = 1; jj < poly.size(); ++jj )
3704 flatSet.AddHole( poly[jj] );
3705
3706 flatSet.Fracture();
3707 flatSet.splitSelfTouchingOutlines();
3708
3709 if( triangulate( flatSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3710 continue;
3711
3712 m_triangulatedPolys.resize( prevCount );
3713 directOk = false;
3714 continue;
3715 }
3716
3717 {
3718 TRIANGULATED_POLYGON partScratch( -1 );
3719 POLYGON_TRIANGULATION partChecker( partScratch );
3720
3721 if( partChecker.suggestedPartitionLeafCount( poly.front() ) > 1 )
3722 {
3723 SHAPE_POLY_SET partSet;
3724 partSet.AddOutline( poly.front() );
3725
3726 if( triangulate( partSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3727 {
3728 continue;
3729 }
3730
3731 m_triangulatedPolys.resize( prevCount );
3732 }
3733 }
3734
3735 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3737
3738 bool ok = tess.TesselatePolygon( poly.front(), nullptr );
3739
3740 // Self-touching outlines produce overlapping triangles; detect via area coverage
3741 if( ok )
3742 {
3743 double originalArea = std::abs( poly.front().Area() );
3744
3745 if( originalArea > 0.0 )
3746 {
3747 double triArea = 0.0;
3748
3749 for( const auto& tri : m_triangulatedPolys.back()->Triangles() )
3750 triArea += std::abs( tri.Area() );
3751
3752 double coverage = triArea / originalArea;
3753
3754 if( coverage > 1.01 || coverage < 0.99 )
3755 ok = false;
3756 }
3757 }
3758
3759 if( !ok )
3760 {
3761 m_triangulatedPolys.resize( prevCount );
3762
3763 SHAPE_POLY_SET splitSet;
3764 splitSet.AddOutline( poly[0] );
3765 splitSet.splitSelfTouchingOutlines();
3766
3767 bool splitOk = true;
3768
3769 for( int jj = 0; jj < splitSet.OutlineCount() && splitOk; ++jj )
3770 {
3771 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3772 POLYGON_TRIANGULATION splitTess( *m_triangulatedPolys.back() );
3773 splitOk = splitTess.TesselatePolygon( splitSet.CPolygon( jj ).front(), nullptr );
3774 }
3775
3776 if( !splitOk )
3777 directOk = false;
3778 }
3779 }
3780
3781 if( !m_triangulatedPolys.empty() && m_triangulatedPolys.back()->GetTriangleCount() == 0 )
3782 m_triangulatedPolys.pop_back();
3783
3784 if( directOk && !m_triangulatedPolys.empty() )
3785 {
3786 m_hash = checksum();
3787 m_hashValid = true;
3788 m_triangulationValid = true;
3789 }
3790 else
3791 {
3792 // Fracture each outline individually to preserve source outline indices
3793 m_triangulatedPolys.clear();
3794 bool fallbackOk = true;
3795
3796 for( int ii = 0; ii < srcSet->OutlineCount() && fallbackOk; ++ii )
3797 {
3798 const POLYGON& poly = srcSet->CPolygon( ii );
3799 SHAPE_POLY_SET flatSet( poly.front() );
3800
3801 for( size_t jj = 1; jj < poly.size(); ++jj )
3802 flatSet.AddHole( poly[jj] );
3803
3804 flatSet.ClearArcs();
3805 flatSet.Fracture();
3806 flatSet.splitSelfTouchingOutlines();
3807
3808 if( !triangulate( flatSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3809 fallbackOk = false;
3810 }
3811
3812 if( !fallbackOk )
3813 {
3814 // Last resort: flatten everything together (loses outline indices)
3815 m_triangulatedPolys.clear();
3816 SHAPE_POLY_SET fallbackSet( *this );
3817 fallbackSet.ClearArcs();
3818 fallbackSet.Fracture();
3819 fallbackSet.splitSelfTouchingOutlines();
3820
3821 if( !triangulate( fallbackSet, -1, m_triangulatedPolys, aHintData, aSubmitter ) )
3822 {
3823 wxLogTrace( TRIANGULATE_TRACE, "Failed to triangulate polygon" );
3824 }
3825 else
3826 {
3827 m_triangulationValid = true;
3828 }
3829 }
3830 else
3831 {
3832 m_triangulationValid = true;
3833 }
3834
3836 {
3837 m_hash = checksum();
3838 m_hashValid = true;
3839 }
3840 else
3841 {
3843 m_failedHashValid = true;
3844 }
3845 }
3846
3847 if( aBuildIndexes && m_triangulationValid )
3848 buildIndexes();
3849}
3850
3851
3852
3854{
3855 MMH3_HASH hash( 0x68AF835D ); // Arbitrary seed
3856
3857 hash.add( m_polys.size() );
3858
3859 for( const POLYGON& outline : m_polys )
3860 {
3861 hash.add( outline.size() );
3862
3863 for( const SHAPE_LINE_CHAIN& lc : outline )
3864 {
3865 hash.add( lc.PointCount() );
3866
3867 for( int i = 0; i < lc.PointCount(); i++ )
3868 {
3869 VECTOR2I pt = lc.CPoint( i );
3870
3871 hash.add( pt.x );
3872 hash.add( pt.y );
3873 }
3874 }
3875 }
3876
3877 return hash.digest();
3878}
3879
3880
3882{
3883 for( int i = 0; i < OutlineCount(); i++ )
3884 {
3885 if( hasTouchingHoles( CPolygon( i ) ) )
3886 return true;
3887 }
3888
3889 return false;
3890}
3891
3892
3894{
3895 std::set<long long> ptHashes;
3896
3897 for( const SHAPE_LINE_CHAIN& lc : aPoly )
3898 {
3899 for( const VECTOR2I& pt : lc.CPoints() )
3900 {
3901 const long long ptHash = (long long) pt.x << 32 | pt.y;
3902
3903 if( ptHashes.count( ptHash ) > 0 )
3904 return true;
3905
3906 ptHashes.insert( ptHash );
3907 }
3908 }
3909
3910 return false;
3911}
3912
3913
3918
3919
3921{
3922 size_t n = 0;
3923
3924 for( const std::unique_ptr<TRIANGULATED_POLYGON>& t : m_triangulatedPolys )
3925 n += t->GetTriangleCount();
3926
3927 return n;
3928}
3929
3930
3931void SHAPE_POLY_SET::GetIndexableSubshapes( std::vector<const SHAPE*>& aSubshapes ) const
3932{
3933 aSubshapes.reserve( GetIndexableSubshapeCount() );
3934
3935 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
3936 {
3937 for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
3938 aSubshapes.push_back( &tri );
3939 }
3940}
3941
3942
3944{
3945 BOX2I bbox( parent->m_vertices[a] );
3946 bbox.Merge( parent->m_vertices[b] );
3947 bbox.Merge( parent->m_vertices[c] );
3948
3949 if( aClearance != 0 )
3950 bbox.Inflate( aClearance );
3951
3952 return bbox;
3953}
3954
3955
3957{
3958 m_triangles.emplace_back( a, b, c, this );
3959}
3960
3961
3963{
3965 m_vertices = aOther.m_vertices;
3966 m_triangles = aOther.m_triangles;
3967
3968 for( TRI& tri : m_triangles )
3969 tri.parent = this;
3970}
3971
3972
3974{
3976 m_vertices = aOther.m_vertices;
3977 m_triangles = aOther.m_triangles;
3978
3979 for( TRI& tri : m_triangles )
3980 tri.parent = this;
3981
3982 return *this;
3983}
3984
3985
3987{
3988 const size_t triCount = m_triangles.size();
3989
3990 // Indices are int; skip an implausibly huge mesh rather than overflow.
3991 if( triCount < 2 || triCount > static_cast<size_t>( std::numeric_limits<int>::max() ) / 6 )
3992 return;
3993
3994 const int halfEdgeCount = static_cast<int>( 3 * triCount );
3995
3996 // heVertex[3k+j] is vertex j of triangle k.
3997 std::vector<int> heVertex( halfEdgeCount );
3998
3999 for( size_t k = 0; k < triCount; k++ )
4000 {
4001 heVertex[3 * k + 0] = m_triangles[k].a;
4002 heVertex[3 * k + 1] = m_triangles[k].b;
4003 heVertex[3 * k + 2] = m_triangles[k].c;
4004 }
4005
4006 auto nextInTriangle = []( int aEdge ) { return aEdge - aEdge % 3 + ( aEdge + 1 ) % 3; };
4007
4008 // An untwinned edge is a polygon boundary and must never flip. Slots are never tombstoned so
4009 // probe chains stay intact.
4010 std::vector<int> twin( halfEdgeCount, -1 );
4011
4012 int tableSize = 1;
4013
4014 while( tableSize < halfEdgeCount * 2 )
4015 tableSize <<= 1;
4016
4017 const uint32_t tableMask = static_cast<uint32_t>( tableSize - 1 );
4018 std::vector<int> slotEdge( tableSize, -1 );
4019
4020 // A non-manifold edge (three or more triangles, e.g. a hole-bridge pinch) is retired so no
4021 // triangle straddling it can flip.
4022 std::vector<char> retired( tableSize, 0 );
4023 std::vector<int> toVisit;
4024 std::vector<char> queued( halfEdgeCount, 0 );
4025
4026 for( int edge = 0; edge < halfEdgeCount; edge++ )
4027 {
4028 int from = heVertex[edge];
4029 int to = heVertex[nextInTriangle( edge )];
4030 uint32_t keyLow = static_cast<uint32_t>( from < to ? from : to );
4031 uint32_t keyHigh = static_cast<uint32_t>( from < to ? to : from );
4032 uint32_t slot = ( ( keyLow * 0x9e3779b1u ) ^ ( keyHigh * 0x85ebca6bu ) ) & tableMask;
4033 bool handled = false;
4034
4035 while( slotEdge[slot] != -1 )
4036 {
4037 int other = slotEdge[slot];
4038 int otherFrom = heVertex[other];
4039 int otherTo = heVertex[nextInTriangle( other )];
4040 uint32_t otherLow = static_cast<uint32_t>( otherFrom < otherTo ? otherFrom : otherTo );
4041 uint32_t otherHigh = static_cast<uint32_t>( otherFrom < otherTo ? otherTo : otherFrom );
4042
4043 if( otherLow == keyLow && otherHigh == keyHigh )
4044 {
4045 if( retired[slot] )
4046 {
4047 // Non-manifold; stays a boundary.
4048 }
4049 else if( twin[other] == -1 )
4050 {
4051 twin[edge] = other;
4052 twin[other] = edge;
4053 queued[other] = 1;
4054 toVisit.push_back( other );
4055 }
4056 else
4057 {
4058 // Third half-edge here: retire the edge and unlink the pair already formed.
4059 int mate = twin[other];
4060 twin[other] = -1;
4061 twin[mate] = -1;
4062 retired[slot] = 1;
4063 }
4064
4065 handled = true;
4066 break;
4067 }
4068
4069 slot = ( slot + 1 ) & tableMask;
4070 }
4071
4072 if( !handled )
4073 slotEdge[slot] = edge;
4074 }
4075
4076 while( !toVisit.empty() )
4077 {
4078 int edge = toVisit.back();
4079 toVisit.pop_back();
4080 queued[edge] = 0;
4081
4082 int twinEdge = twin[edge];
4083
4084 if( twinEdge == -1 )
4085 continue;
4086
4087 int tri = edge - edge % 3;
4088 int twinTri = twinEdge - twinEdge % 3;
4089
4090 int apexAEdge = tri + ( edge + 2 ) % 3;
4091 int sharedEndEdge = tri + ( edge + 1 ) % 3;
4092 int apexBEdge = twinTri + ( twinEdge + 2 ) % 3;
4093 int twinSharedEdge = twinTri + ( twinEdge + 1 ) % 3;
4094
4095 int apexA = heVertex[apexAEdge];
4096 int sharedFrom = heVertex[edge];
4097 int sharedTo = heVertex[sharedEndEdge];
4098 int apexB = heVertex[apexBEdge];
4099
4100 const VECTOR2I& apexAPt = m_vertices[apexA];
4101 const VECTOR2I& sharedFromPt = m_vertices[sharedFrom];
4102 const VECTOR2I& sharedToPt = m_vertices[sharedTo];
4103 const VECTOR2I& apexBPt = m_vertices[apexB];
4104
4105 // A non-convex quad would flip a triangle outside the polygon.
4106 if( KIGEOM::OrientationSign( apexAPt, sharedFromPt, apexBPt ) <= 0 )
4107 continue;
4108
4109 if( KIGEOM::OrientationSign( apexAPt, apexBPt, sharedToPt ) <= 0 )
4110 continue;
4111
4112 // Prefer the diagonal with fewer slivers, Delaunay breaks ties. Each flip lowers
4113 // (slivers, then -min-angle) lexicographically, so the loop terminates.
4114 bool legal = KIGEOM::InCircleDelaunayLegal( apexAPt, sharedFromPt, sharedToPt, apexBPt );
4115
4116 int curSlivers = KIGEOM::IsSliverTriangle( apexAPt, sharedFromPt, sharedToPt )
4117 + KIGEOM::IsSliverTriangle( apexBPt, sharedToPt, sharedFromPt );
4118
4119 // Nothing to gain when already Delaunay and neither triangle is a sliver.
4120 if( legal && curSlivers == 0 )
4121 continue;
4122
4123 int flipSlivers = KIGEOM::IsSliverTriangle( apexAPt, sharedFromPt, apexBPt )
4124 + KIGEOM::IsSliverTriangle( apexAPt, apexBPt, sharedToPt );
4125
4126 bool doFlip = ( flipSlivers != curSlivers ) ? ( flipSlivers < curSlivers ) : !legal;
4127
4128 if( !doFlip )
4129 continue;
4130
4131 heVertex[edge] = apexB;
4132 heVertex[twinEdge] = apexA;
4133
4134 int apexBOuterTwin = twin[apexBEdge];
4135 int apexAOuterTwin = twin[apexAEdge];
4136
4137 twin[edge] = apexBOuterTwin;
4138
4139 if( apexBOuterTwin != -1 )
4140 twin[apexBOuterTwin] = edge;
4141
4142 twin[twinEdge] = apexAOuterTwin;
4143
4144 if( apexAOuterTwin != -1 )
4145 twin[apexAOuterTwin] = twinEdge;
4146
4147 twin[apexAEdge] = apexBEdge;
4148 twin[apexBEdge] = apexAEdge;
4149
4150 // The flip may have made the quad's four outer edges illegal.
4151 for( int outer : { edge, twinEdge, sharedEndEdge, twinSharedEdge } )
4152 {
4153 if( twin[outer] != -1 && !queued[outer] )
4154 {
4155 queued[outer] = 1;
4156 toVisit.push_back( outer );
4157 }
4158 }
4159 }
4160
4161 std::deque<TRI> refined;
4162
4163 for( size_t i = 0; i < triCount; i++ )
4164 refined.emplace_back( heVertex[3 * i + 0], heVertex[3 * i + 1], heVertex[3 * i + 2], this );
4165
4166 m_triangles = std::move( refined );
4167}
4168
4169
4171 m_sourceOutline( aSourceOutline )
4172{
4173}
4174
4175
4179
4180
4181void SHAPE_POLY_SET::Scale( double aScaleFactorX, double aScaleFactorY, const VECTOR2I& aCenter )
4182{
4183 for( POLYGON& poly : m_polys )
4184 {
4185 for( SHAPE_LINE_CHAIN& path : poly )
4186 {
4187 for( int i = 0; i < path.PointCount(); i++ )
4188 {
4189 VECTOR2I pt = path.CPoint( i );
4190 VECTOR2D vec;
4191 vec.x = ( pt.x - aCenter.x ) * aScaleFactorX;
4192 vec.y = ( pt.y - aCenter.y ) * aScaleFactorY;
4193 pt.x = KiROUND<double, int>( aCenter.x + vec.x );
4194 pt.y = KiROUND<double, int>( aCenter.y + vec.y );
4195 path.SetPoint( i, pt );
4196 }
4197 }
4198 }
4199
4202}
4203
4204
4205void
4206SHAPE_POLY_SET::BuildPolysetFromOrientedPaths( const std::vector<SHAPE_LINE_CHAIN>& aPaths,
4207 bool aEvenOdd )
4208{
4209 Clipper2Lib::Clipper64 clipper;
4210 Clipper2Lib::PolyTree64 tree;
4211 Clipper2Lib::Paths64 paths;
4212
4213 for( const SHAPE_LINE_CHAIN& path : aPaths )
4214 {
4215 Clipper2Lib::Path64 lc;
4216 lc.reserve( path.PointCount() );
4217
4218 for( int i = 0; i < path.PointCount(); i++ )
4219 lc.emplace_back( path.CPoint( i ).x, path.CPoint( i ).y );
4220
4221 paths.push_back( std::move( lc ) );
4222 }
4223
4224 clipper.AddSubject( paths );
4225 clipper.Execute( Clipper2Lib::ClipType::Union, aEvenOdd ? Clipper2Lib::FillRule::EvenOdd
4226 : Clipper2Lib::FillRule::NonZero, tree );
4227
4228 std::vector<CLIPPER_Z_VALUE> zValues;
4229 std::vector<SHAPE_ARC> arcBuffer;
4230
4231 importTree( tree, zValues, arcBuffer );
4232 tree.Clear(); // Free used memory (not done in dtor)
4233}
4234
4235
4236bool SHAPE_POLY_SET::PointInside( const VECTOR2I& aPt, int aAccuracy, bool aUseBBoxCache ) const
4237{
4238 for( int idx = 0; idx < OutlineCount(); idx++ )
4239 {
4240 if( COutline( idx ).PointInside( aPt, aAccuracy, aUseBBoxCache ) )
4241 return true;
4242 }
4243
4244 return false;
4245}
4246
4247
4248const std::vector<SEG> SHAPE_POLY_SET::GenerateHatchLines( const std::vector<double>& aSlopes,
4249 int aSpacing, int aLineLength ) const
4250{
4251 std::vector<SEG> hatchLines;
4252
4253 if( OutlineCount() == 0 || TotalVertices() == 0 )
4254 return hatchLines;
4255
4256 // define range for hatch lines
4257 int min_x = CVertex( 0 ).x;
4258 int max_x = CVertex( 0 ).x;
4259 int min_y = CVertex( 0 ).y;
4260 int max_y = CVertex( 0 ).y;
4261
4262 for( auto iterator = CIterateWithHoles(); iterator; iterator++ )
4263 {
4264 if( iterator->x < min_x )
4265 min_x = iterator->x;
4266
4267 if( iterator->x > max_x )
4268 max_x = iterator->x;
4269
4270 if( iterator->y < min_y )
4271 min_y = iterator->y;
4272
4273 if( iterator->y > max_y )
4274 max_y = iterator->y;
4275 }
4276
4277 auto sortEndsByDescendingX =
4278 []( const VECTOR2I& ref, const VECTOR2I& tst )
4279 {
4280 return tst.x < ref.x;
4281 };
4282
4283 for( double slope : aSlopes )
4284 {
4285 int64_t max_a, min_a;
4286
4287 if( slope > 0 )
4288 {
4289 max_a = KiROUND<double, int64_t>( max_y - slope * min_x );
4290 min_a = KiROUND<double, int64_t>( min_y - slope * max_x );
4291 }
4292 else
4293 {
4294 max_a = KiROUND<double, int64_t>( max_y - slope * max_x );
4295 min_a = KiROUND<double, int64_t>( min_y - slope * min_x );
4296 }
4297
4298 min_a = ( min_a / aSpacing ) * aSpacing;
4299
4300 // loop through hatch lines
4301 std::vector<VECTOR2I> pointbuffer;
4302 pointbuffer.reserve( 256 );
4303
4304 for( int64_t a = min_a; a < max_a; a += aSpacing )
4305 {
4306 pointbuffer.clear();
4307
4308 // Iterate through all vertices
4309 for( auto iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
4310 {
4311 const SEG seg = *iterator;
4312 VECTOR2I pt;
4313
4314 if( seg.IntersectsLine( slope, a, pt ) )
4315 {
4316 // If the intersection point is outside the polygon, skip it
4317 if( pt.x < min_x || pt.x > max_x || pt.y < min_y || pt.y > max_y )
4318 continue;
4319
4320 // Add the intersection point to the buffer
4321 pointbuffer.emplace_back( KiROUND( pt.x ), KiROUND( pt.y ) );
4322 }
4323 }
4324
4325 // sort points in order of descending x (if more than 2) to
4326 // ensure the starting point and the ending point of the same segment
4327 // are stored one just after the other.
4328 if( pointbuffer.size() > 2 )
4329 sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX );
4330
4331 // creates lines or short segments inside the complex polygon
4332 for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip++ )
4333 {
4334 const VECTOR2I& p1 = pointbuffer[ip];
4335 const VECTOR2I& p2 = pointbuffer[ip + 1];
4336
4337 // Avoid duplicated intersections or segments
4338 if( p1 == p2 )
4339 continue;
4340
4341 SEG candidate( p1, p2 );
4342
4343 VECTOR2I mid( ( candidate.A.x + candidate.B.x ) / 2, ( candidate.A.y + candidate.B.y ) / 2 );
4344
4345 // Check if segment is inside the polygon by checking its middle point
4346 if( Contains( mid, -1, 1, true ) )
4347 {
4348 int dx = p2.x - p1.x;
4349
4350 // Push only one line for diagonal hatch or for small lines < twice
4351 // the line length; else push 2 small lines
4352 if( aLineLength == -1 || std::abs( dx ) < 2 * aLineLength )
4353 {
4354 hatchLines.emplace_back( candidate );
4355 }
4356 else
4357 {
4358 double dy = p2.y - p1.y;
4359 slope = dy / dx;
4360
4361 if( dx > 0 )
4362 dx = aLineLength;
4363 else
4364 dx = -aLineLength;
4365
4366 int x1 = KiROUND( p1.x + dx );
4367 int x2 = KiROUND( p2.x - dx );
4368 int y1 = KiROUND( p1.y + dx * slope );
4369 int y2 = KiROUND( p2.y - dx * slope );
4370
4371 hatchLines.emplace_back( SEG( p1.x, p1.y, x1, y1 ) );
4372
4373 hatchLines.emplace_back( SEG( p2.x, p2.y, x2, y2 ) );
4374 }
4375 }
4376 }
4377 }
4378 }
4379
4380 return hatchLines;
4381}
int index
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
BOX2< VECTOR2D > BOX2D
Definition box2.h:915
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
void InsertBridges(Index aFirst)
void insert(uint32_t aEdge, uint32_t aHead)
std::vector< NODE > m_nodes
std::vector< uint32_t > m_bucketIds
FractureEdge::Index Index
void Query(int aY, Index aProvokingIndex, Visitor &&aVisitor) const
std::vector< uint32_t > m_longIds
std::vector< uint32_t > m_offsets
static void logAccepted(size_t aEdges, size_t aHoles, uint32_t aStripes, size_t aBytes)
std::pair< uint32_t, uint32_t > span(const FractureEdge &aEdge) const
FRACTURE_EDGE_INDEX(FractureEdgeSet &aEdges, Originals &&aOriginals, int aMinY, int aMaxY, uint32_t aStripeCount, size_t aHoleCount)
uint32_t map(int aY) const
static constexpr uint32_t INVALID
void visitOverflow(uint32_t aNode, Index aProvokingIndex, Visitor &&aVisitor) const
FractureEdgeSet & m_edges
std::vector< uint32_t > m_heads
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:61
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:138
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:157
bool TesselatePolygon(const SHAPE_POLY_SET::POLYGON &aPolygon, SHAPE_POLY_SET::TRIANGULATED_POLYGON *aHintData)
Triangulate a polygon with holes by bridging holes directly into the outer ring's VERTEX linked list,...
std::vector< SHAPE_LINE_CHAIN > partitionPolygonBalanced(const SHAPE_LINE_CHAIN &aPoly, size_t aTargetLeaves) const
size_t suggestedPartitionLeafCount(const SHAPE_LINE_CHAIN &aPoly) const
Immutable owning spatial snapshot of straight segments.
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
ecoord SquaredDistance(const SEG &aSeg) const
Definition seg.cpp:35
bool IntersectsLine(double aSlope, double aOffset, VECTOR2I &aIntersection) const
Check if this segment intersects a line defined by slope aSlope and offset aOffset.
Definition seg.cpp:412
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
int Index() const
Return the index of this segment in its parent shape (applicable only to non-local segments).
Definition seg.h:358
static SEG::ecoord Square(int a)
Definition seg.h:119
bool Collide(const SEG &aSeg, int aClearance, int *aActual=nullptr) const
Definition seg.cpp:497
bool ApproxCollinear(const SEG &aSeg, int aDistanceThreshold=1) const
Definition seg.cpp:759
SHAPE_TYPE Type() const
Return the type of the shape.
Definition shape.h:96
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
void GenerateBBoxCache() const
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void ReservePoints(size_t aSize)
Allocate a number of points all at once (for performance).
void Clear()
Remove all points from the line chain.
void Simplify(int aTolerance=0)
Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
double Area(bool aAbsolute=true) const
Return the area of this chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Clipper2Lib::Path64 convertToClipper2(bool aRequiredOrientation, std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, std::vector< SHAPE_ARC > &aArcBuffer) const
Create a new Clipper2 path from the SHAPE_LINE_CHAIN in a given orientation.
size_t ArcCount() const
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
const std::vector< VECTOR2I > & CPoints() const
void Refine()
Refine this triangulation in place with boundary-preserving Lawson edge flips, minimizing the number ...
void AddTriangle(int a, int b, int c)
TRIANGULATED_POLYGON & operator=(const TRIANGULATED_POLYGON &aOther)
std::mutex m_triangulationMutex
virtual bool HasIndexableSubshapes() const override
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
SHAPE_POLY_SET Chamfer(int aDistance)
Return a chamfered version of the polygon set.
void RemoveOutline(int aOutlineIdx)
Delete the aOutlineIdx-th outline of the set including its contours and holes.
void Scale(double aScaleFactorX, double aScaleFactorY, const VECTOR2I &aCenter)
bool CollideEdge(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any edge of any of the contours of the polygon.
HASH_128 GetHash() const
virtual void GetIndexableSubshapes(std::vector< const SHAPE * > &aSubshapes) const override
void BooleanXor(const SHAPE_POLY_SET &b)
Perform boolean polyset exclusive or.
ITERATOR_TEMPLATE< VECTOR2I > ITERATOR
void fractureSingle(POLYGON &paths)
bool HasHoles() const
Return true if the polygon set has any holes.
void cacheTriangulation(bool aSimplify, std::vector< std::unique_ptr< TRIANGULATED_POLYGON > > *aHintData, const TASK_SUBMITTER &aSubmitter={}, bool aBuildIndexes=false)
CONST_ITERATOR CIterateWithHoles() const
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
ITERATOR IterateWithHoles()
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
bool IsTriangulationUpToDate() const
void importPaths(Clipper2Lib::Paths64 &paths, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffe)
void InsertVertex(int aGlobalIndex, const VECTOR2I &aNewVertex)
Adds a vertex in the globally indexed position aGlobalIndex.
std::atomic< bool > m_failedHashValid
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of vertices in a given outline/hole.
void DeletePolygon(int aIdx)
Delete aIdx-th polygon from the set.
double Area()
Return the area of this poly set.
void SetVertex(const VERTEX_INDEX &aIndex, const VECTOR2I &aPos)
Accessor function to set the position of a specific point.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={}, bool aBuildIndexes=false)
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
void BuildPolysetFromOrientedPaths(const std::vector< SHAPE_LINE_CHAIN > &aPaths, bool aEvenOdd=false)
Build a SHAPE_POLY_SET from a bunch of outlines in provided in random order.
bool Parse(std::stringstream &aStream) override
int TotalVertices() const
Return total number of vertices stored in the set.
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
int FullPointCount() const
Return the number of points in the shape poly set.
void GetArcs(std::vector< SHAPE_ARC > &aArcBuffer) const
Appends all the arcs in this polyset to aArcBuffer.
bool IsVertexInHole(int aGlobalIdx)
Check whether the aGlobalIndex-th vertex belongs to a hole.
int NormalizeAreaOutlines()
Convert a self-intersecting polygon to one (or more) non self-intersecting polygon(s).
static bool appendBridgeFreePaths(const SHAPE_LINE_CHAIN &aChain, Clipper2Lib::Paths64 &aPaths, std::vector< CLIPPER_Z_VALUE > &aZValues, std::vector< SHAPE_ARC > &aArcBuffer)
Append the rings left after cutting the fracture bridges out of aChain.
void RemoveVertex(int aGlobalIndex)
Delete the aGlobalIndex-th vertex.
void unfractureSingle(POLYGON &path)
void inflateLine2(const SHAPE_LINE_CHAIN &aLine, int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy, bool aSimplify=false)
bool GetRelativeIndices(int aGlobalIdx, VERTEX_INDEX *aRelativeIndices) const
Convert a global vertex index —i.e., a number that globally identifies a vertex in a concatenated lis...
bool IsPolygonSelfIntersecting(int aPolygonIndex) const
Check whether the aPolygonIndex-th polygon in the set is self intersecting.
SHAPE_POLY_SET Subset(int aFirstPolygon, int aLastPolygon)
Return a subset of the polygons in this set, the ones between aFirstPolygon and aLastPolygon.
int RemoveNullSegments()
Look for null segments; ie, segments whose ends are exactly the same and deletes them.
HASH_128 checksum() const
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
int AddPolygon(const POLYGON &apolygon)
Adds a polygon to the set.
const std::vector< SEG > GenerateHatchLines(const std::vector< double > &aSlopes, int aSpacing, int aLineLength) const
const std::string Format(bool aCplusPlus=true) const override
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
std::vector< std::unique_ptr< TRIANGULATED_POLYGON > > m_triangulatedPolys
ITERATOR_TEMPLATE< const VECTOR2I > CONST_ITERATOR
void inflate2(int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy, bool aSimplify=false)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SEG::ecoord SquaredDistance(const VECTOR2I &aPoint, bool aOutlineOnly, VECTOR2I *aNearest) const
Compute the minimum distance squared between aPoint and all the polygons in the set.
void RemoveContour(int aContourIdx, int aPolygonIdx=-1)
Delete the aContourIdx-th contour of the aPolygonIdx-th polygon in the set.
void Unfracture()
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
int ArcCount() const
Count the number of arc shapes present.
bool GetGlobalIndex(VERTEX_INDEX aRelativeIndices, int &aGlobalIdx) const
Compute the global index of a vertex from the relative indices of polygon, contour and vertex.
bool GetNeighbourIndexes(int aGlobalIndex, int *aPrevious, int *aNext) const
Return the global indexes of the previous and the next corner of the aGlobalIndex-th corner of a cont...
std::unique_ptr< TRIANGULATION_INDEX > m_triIndex
One index over every triangle of every polygon above.
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void SimplifyOutlines(int aMaxError=0)
Simplifies the lines in the polyset.
void booleanOp(Clipper2Lib::ClipType aType, const SHAPE_POLY_SET &aOtherShape)
This is the engine to execute all polygon boolean transforms (AND, OR, ... and polygon simplification...
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
bool hasTouchingHoles(const POLYGON &aPoly) const
Return true if the polygon set has any holes that touch share a vertex.
bool PointOnEdge(const VECTOR2I &aP, int aAccuracy=0) const
Check if point aP lies on an edge or vertex of some of the outlines or holes.
std::atomic< bool > m_hashValid
bool CollideVertex(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any vertex of any of the contours of the polygon.
void DeletePolygonAndTriangulationData(int aIdx, bool aUpdateHash=true)
Delete aIdx-th polygon and its triangulation data from the set.
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
std::atomic< bool > m_triangulationValid
void UpdateTriangulationDataHash()
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int NewHole(int aOutline=-1)
Creates a new hole in a given outline.
SEG::ecoord SquaredDistanceToPolygon(VECTOR2I aPoint, int aIndex, VECTOR2I *aNearest) const
Compute the minimum distance between the aIndex-th polygon and aPoint.
CONST_SEGMENT_ITERATOR CIterateSegmentsWithHoles() const
Return an iterator object, for the aOutline-th outline in the set (with holes).
virtual size_t GetIndexableSubshapeCount() const override
SEG::ecoord SquaredDistanceToSeg(const SEG &aSegment, VECTOR2I *aNearest=nullptr) const
Compute the minimum distance squared between aSegment and all the polygons in the set.
void importPolyPath(const std::unique_ptr< Clipper2Lib::PolyPath64 > &aPolyPath, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffer)
void RebuildHolesFromContours()
Extract all contours from this polygon set, then recreate polygons with holes.
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the line points about y or x (or both)
void OffsetLineChain(const SHAPE_LINE_CHAIN &aLine, int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify)
Perform offsetting of a line chain.
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
std::vector< POLYGON > m_polys
void splitSelfTouchingOutlines()
Split outline segments at vertices that lie on them (self-touching polygons).
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
POLYGON FilletPolygon(unsigned int aRadius, int aErrorMax, int aIndex)
Return a filleted version of the aIndex-th polygon.
bool containsSingle(const VECTOR2I &aP, int aSubpolyIndex, int aAccuracy, bool aUseBBoxCaches=false) const
Check whether the point aP is inside the aSubpolyIndex-th polygon of the polyset.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
int OutlineCount() const
Return the number of outlines in the set.
void InflateWithLinkedHoles(int aFactor, CORNER_STRATEGY aCornerStrategy, int aMaxError)
Perform outline inflation/deflation, using round corners.
POLYGON chamferFilletPolygon(CORNER_MODE aMode, unsigned int aDistance, int aIndex, int aErrorMax)
Return the chamfered or filleted version of the aIndex-th polygon in the set, depending on the aMode ...
SHAPE_POLY_SET Fillet(int aRadius, int aErrorMax)
Return a filleted version of the polygon set.
void Move(const VECTOR2I &aVector) override
bool HasTouchingHoles() const
Return true if the polygon set has any holes that share a vertex.
SHAPE * Clone() const override
Return a dynamically allocated copy of the shape.
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
SHAPE_POLY_SET & operator=(const SHAPE_POLY_SET &aOther)
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
SHAPE_POLY_SET CloneDropTriangulation() const
bool isExteriorWaist(const SEG &aSegA, const SEG &aSegB) const
Check if two line segments are collinear and overlap.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const POLYGON & CPolygon(int aIndex) const
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
POLYGON ChamferPolygon(unsigned int aDistance, int aIndex)
Return a chamfered version of the aIndex-th polygon.
std::function< void(std::function< void()>)> TASK_SUBMITTER
Callback that submits a unit of work for asynchronous execution.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
const BOX2I BBoxFromCaches() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
void importTree(Clipper2Lib::PolyTree64 &tree, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffe)
SEGMENT_ITERATOR_TEMPLATE< const SEG > CONST_SEGMENT_ITERATOR
bool IsSelfIntersecting() const
Check whether any of the polygons in the set is self intersecting.
const SEG & GetSeg() const
int GetWidth() const override
VECTOR2I::extended_type ecoord
Definition shape.h:311
SHAPE(SHAPE_TYPE aType)
Create an empty shape of type aType.
Definition shape.h:134
static constexpr extended_type ECOORD_MAX
Definition vector2d.h:72
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:335
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:406
CORNER_STRATEGY
define how inflate transform build inflated polygon
@ ROUND_ACUTE_CORNERS
Acute angles are rounded.
@ CHAMFER_ACUTE_CORNERS
Acute angles are chamfered.
@ CHAMFER_ALL_CORNERS
All angles are chamfered.
@ ROUND_ALL_CORNERS
All angles are rounded.
@ ALLOW_ACUTE_CORNERS
just inflate the polygon. Acute angles create spikes
static bool empty(const wxTextEntryBase *aCtrl)
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:446
Exact orientation and in-circle predicates over integer coordinates.
a few functions useful in geometry calculations.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
static constexpr void hash_combine(std::size_t &seed)
This is a dummy function to take the final case of hash_combine below.
Definition hash.h:28
FLIP_DIRECTION
Definition mirror.h:23
std::pair< uint32_t, uint32_t > StripeSpan(int aY1, int aY2, int aMinY, int aMaxY, uint32_t aStripeCount)
bool ActualCapacityFits(size_t aBucketIds, size_t aLongIds, size_t aOffsets, size_t aScratch, size_t aHeads, size_t aNodes, size_t aNodeSize, size_t aBudget, size_t *aBytes=nullptr)
bool ShouldIndex(uint64_t aEstimatedVisits, size_t aHoleCount)
bool CapacityFits(size_t aBucketIds, size_t aLongIds, size_t aStripeCount, size_t aHoleCount, size_t aNodeSize, size_t aBudget)
uint32_t MapYToStripe(int aY, int aMinY, int aMaxY, uint32_t aStripeCount)
uint32_t StripeCountFor(size_t aEdgeCount)
Construction helpers for the interactive arc drawing modes.
int OrientationSign(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
Orientation of triangle (a, b, c): +1 counter-clockwise, -1 clockwise, 0 collinear.
bool InCircleDelaunayLegal(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c, const VECTOR2I &p)
True when p is outside the circumcircle of CCW triangle (a, b, c): the shared edge is already Delauna...
bool IsSliverTriangle(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
A triangle is a sliver when its longest edge exceeds ten times its shortest.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
static PGM_BASE * process
#define TRIANGULATE_TRACE
#define TRIANGULATESIMPLIFICATIONLEVEL
CITER next(CITER it)
Definition ptree.cpp:120
@ SH_POLY_SET
set of polygons (with holes, etc.)
Definition shape.h:48
@ SH_CIRCLE
circle
Definition shape.h:46
@ SH_SEGMENT
line segment
Definition shape.h:44
static void fractureSingleCacheFriendly(SHAPE_POLY_SET::POLYGON &paths)
static constexpr int RTREE_THRESHOLD
static void fractureSingleSlow(SHAPE_POLY_SET::POLYGON &paths)
static int fractureIntersectX(const FractureEdge &aEdge, int aY)
std::vector< FractureEdge > FractureEdgeSet
std::vector< FractureEdgeSlow * > FractureEdgeSetSlow
#define ENABLEFRACTUREEDGEINDEX
static bool splitAtBridges(const SHAPE_LINE_CHAIN &aChain, std::vector< SHAPE_LINE_CHAIN > &aRings)
Split a closed ring into the rings left once pairs of coincident opposite edges are removed.
#define SEG_CNT_MAX
#define ENABLECACHEFRIENDLYFRACTURE
static FractureEdge * processHole(FractureEdgeSet &edges, FractureEdge::Index provokingIndex, FractureEdge::Index edgeIndex, FractureEdge::Index bridgeIndex, FRACTURE_EDGE_INDEX *aIndex)
static int processEdge(FractureEdgeSetSlow &edges, FractureEdgeSlow *edge)
bool AnyTriangleHit(const TRIANGULATION_INDEX *aIndex, const std::vector< std::unique_ptr< SHAPE_POLY_SET::TRIANGULATED_POLYGON > > &aPolys, const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr)
Return true if any candidate triangle collides with aShape within aClearance.
Holds information on each point of a SHAPE_LINE_CHAIN that is retrievable after an operation with Cli...
FractureEdgeSlow * m_next
bool matches(int y) const
FractureEdgeSlow(bool connected, const VECTOR2I &p1, const VECTOR2I &p2)
FractureEdge(const VECTOR2I &p1, const VECTOR2I &p2, Index next)
FractureEdge()=default
bool matches(int y) const
A storage class for 128-bit hash value.
Definition hash_128.h:33
virtual const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Structure to hold the necessary information in order to index a vertex on a SHAPE_POLY_SET object: th...
std::string path
KIBIS_PIN * partner
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
VECTOR2I location
int actual
wxString result
Test unit parsing edge cases and error handling.
int delta
#define M_PI
constexpr int sign(T val)
Definition util.h:167
T rescale(T aNumerator, T aValue, T aDenominator)
Scale a number (value) by rational (numerator/denominator).
Definition util.h:161
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707