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_set>
38#include <thread>
39#include <utility> // for swap, move
40#include <vector>
41#include <array>
42
43#include <clipper2/clipper.h>
46#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
47#include <geometry/shape.h>
51#include <math/box2.h> // for BOX2I
52#include <math/util.h> // for KiROUND, rescale
53#include <math/vector2d.h> // for VECTOR2I, VECTOR2D, VECTOR2
54#include <hash.h>
55#include <mmh3_hash.h>
58
59#include <wx/log.h>
60
61// ADVANCED_CFG::GetCfg() cannot be used on msys2/mingw builds (link failure)
62// So we use the ADVANCED_CFG default values
63#if defined( __MINGW32__ )
64 #define TRIANGULATESIMPLIFICATIONLEVEL 50
65 #define ENABLECACHEFRIENDLYFRACTURE true
66#else
67 #define TRIANGULATESIMPLIFICATIONLEVEL ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel
68 #define ENABLECACHEFRIENDLYFRACTURE ADVANCED_CFG::GetCfg().m_EnableCacheFriendlyFracture
69#endif
70
75
76
79{
80 NewOutline();
81 Append( VECTOR2I( aRect.GetLeft(), aRect.GetTop() ) );
82 Append( VECTOR2I( aRect.GetRight(), aRect.GetTop() ) );
83 Append( VECTOR2I( aRect.GetRight(), aRect.GetBottom() ) );
84 Append( VECTOR2I( aRect.GetLeft(), aRect.GetBottom() ) );
85 Outline( 0 ).SetClosed( true );
86}
87
88
91{
92 AddOutline( aOutline );
93}
94
95
98{
99 AddPolygon( aPolygon );
100}
101
102
104 SHAPE( aOther ),
105 m_polys( aOther.m_polys )
106{
107 if( aOther.IsTriangulationUpToDate() )
108 {
109 m_triangulatedPolys.reserve( aOther.TriangulatedPolyCount() );
110
111 for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
112 {
113 const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
114 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
115 }
116
117 m_hash = aOther.GetHash();
118 m_hashValid = true;
120 }
121 else
122 {
123 m_hash.Clear();
124 m_hashValid = false;
125 m_triangulationValid = false;
126 }
127
128 m_failedHash = aOther.m_failedHash;
129 m_failedHashValid.store( aOther.m_failedHashValid.load() );
130}
131
132
134 SHAPE( aOther ),
135 m_polys( aOther.m_polys )
136{
137 m_hash.Clear();
138 m_hashValid = false;
139 m_triangulationValid = false;
140}
141
142
146
147
149{
150 return new SHAPE_POLY_SET( *this );
151}
152
153
158
159
161 SHAPE_POLY_SET::VERTEX_INDEX* aRelativeIndices ) const
162{
163 int polygonIdx = 0;
164 unsigned int contourIdx = 0;
165 int vertexIdx = 0;
166
167 int currentGlobalIdx = 0;
168
169 for( polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
170 {
171 const POLYGON& currentPolygon = CPolygon( polygonIdx );
172
173 for( contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
174 {
175 const SHAPE_LINE_CHAIN& currentContour = currentPolygon[contourIdx];
176 int totalPoints = currentContour.PointCount();
177
178 for( vertexIdx = 0; vertexIdx < totalPoints; vertexIdx++ )
179 {
180 // Check if the current vertex is the globally indexed as aGlobalIdx
181 if( currentGlobalIdx == aGlobalIdx )
182 {
183 aRelativeIndices->m_polygon = polygonIdx;
184 aRelativeIndices->m_contour = contourIdx;
185 aRelativeIndices->m_vertex = vertexIdx;
186
187 return true;
188 }
189
190 // Advance
191 currentGlobalIdx++;
192 }
193 }
194 }
195
196 return false;
197}
198
199
201 int& aGlobalIdx ) const
202{
203 int selectedVertex = aRelativeIndices.m_vertex;
204 unsigned int selectedContour = aRelativeIndices.m_contour;
205 unsigned int selectedPolygon = aRelativeIndices.m_polygon;
206
207 // Check whether the vertex indices make sense in this poly set
208 if( selectedPolygon < m_polys.size() && selectedContour < m_polys[selectedPolygon].size()
209 && selectedVertex < m_polys[selectedPolygon][selectedContour].PointCount() )
210 {
211 POLYGON currentPolygon;
212
213 aGlobalIdx = 0;
214
215 for( unsigned int polygonIdx = 0; polygonIdx < selectedPolygon; polygonIdx++ )
216 {
217 currentPolygon = Polygon( polygonIdx );
218
219 for( unsigned int contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
220 aGlobalIdx += currentPolygon[contourIdx].PointCount();
221 }
222
223 currentPolygon = Polygon( selectedPolygon );
224
225 for( unsigned int contourIdx = 0; contourIdx < selectedContour; contourIdx++ )
226 aGlobalIdx += currentPolygon[contourIdx].PointCount();
227
228 aGlobalIdx += selectedVertex;
229
230 return true;
231 }
232 else
233 {
234 return false;
235 }
236}
237
238
240{
241 SHAPE_LINE_CHAIN empty_path;
242 POLYGON poly;
243
244 empty_path.SetClosed( true );
245 poly.push_back( empty_path );
246 m_polys.push_back( std::move( poly ) );
247 return m_polys.size() - 1;
248}
249
250
251int SHAPE_POLY_SET::NewHole( int aOutline )
252{
253 SHAPE_LINE_CHAIN empty_path;
254
255 empty_path.SetClosed( true );
256
257 // Default outline is the last one
258 if( aOutline < 0 )
259 aOutline += m_polys.size();
260
261 // Add hole to the selected outline
262 m_polys[aOutline].push_back( empty_path );
263
264 return m_polys.back().size() - 2;
265}
266
267
268int SHAPE_POLY_SET::Append( int x, int y, int aOutline, int aHole, bool aAllowDuplication )
269{
270 assert( m_polys.size() );
271
272 if( aOutline < 0 )
273 aOutline += m_polys.size();
274
275 int idx;
276
277 if( aHole < 0 )
278 idx = 0;
279 else
280 idx = aHole + 1;
281
282 assert( aOutline < (int) m_polys.size() );
283 assert( idx < (int) m_polys[aOutline].size() );
284
285 m_polys[aOutline][idx].Append( x, y, aAllowDuplication );
286
287 return m_polys[aOutline][idx].PointCount();
288}
289
290
291int SHAPE_POLY_SET::Append( const SHAPE_ARC& aArc, int aOutline, int aHole,
292 std::optional<int> aMaxError )
293{
294 assert( m_polys.size() );
295
296 if( aOutline < 0 )
297 aOutline += m_polys.size();
298
299 int idx;
300
301 if( aHole < 0 )
302 idx = 0;
303 else
304 idx = aHole + 1;
305
306 assert( aOutline < (int) m_polys.size() );
307 assert( idx < (int) m_polys[aOutline].size() );
308
309 if( aMaxError.has_value() )
310 m_polys[aOutline][idx].Append( aArc, aMaxError.value() );
311 else
312 m_polys[aOutline][idx].Append( aArc );
313
314 return m_polys[aOutline][idx].PointCount();
315}
316
317
318void SHAPE_POLY_SET::InsertVertex( int aGlobalIndex, const VECTOR2I& aNewVertex )
319{
321
322 if( aGlobalIndex < 0 )
323 aGlobalIndex = 0;
324
325 if( aGlobalIndex >= TotalVertices() )
326 {
327 Append( aNewVertex );
328 }
329 else
330 {
331 // Assure the position to be inserted exists; throw an exception otherwise
332 if( GetRelativeIndices( aGlobalIndex, &index ) )
333 m_polys[index.m_polygon][index.m_contour].Insert( index.m_vertex, aNewVertex );
334 else
335 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
336 }
337}
338
339
340int SHAPE_POLY_SET::VertexCount( int aOutline, int aHole ) const
341{
342 if( m_polys.size() == 0 ) // Empty poly set
343 return 0;
344
345 if( aOutline < 0 ) // Use last outline
346 aOutline += m_polys.size();
347
348 int idx;
349
350 if( aHole < 0 )
351 idx = 0;
352 else
353 idx = aHole + 1;
354
355 if( aOutline >= (int) m_polys.size() ) // not existing outline
356 return 0;
357
358 if( idx >= (int) m_polys[aOutline].size() ) // not existing hole
359 return 0;
360
361 return m_polys[aOutline][idx].PointCount();
362}
363
364
366{
367 int full_count = 0;
368
369 if( m_polys.size() == 0 ) // Empty poly set
370 return full_count;
371
372 for( int ii = 0; ii < OutlineCount(); ii++ )
373 {
374 // the first polygon in m_polys[ii] is the main contour,
375 // only others are holes:
376 for( int idx = 0; idx <= HoleCount( ii ); idx++ )
377 {
378 full_count += m_polys[ii][idx].PointCount();
379 }
380 }
381
382 return full_count;
383}
384
385
386SHAPE_POLY_SET SHAPE_POLY_SET::Subset( int aFirstPolygon, int aLastPolygon )
387{
388 assert( aFirstPolygon >= 0 && aLastPolygon <= OutlineCount() );
389
390 SHAPE_POLY_SET newPolySet;
391
392 for( int index = aFirstPolygon; index < aLastPolygon; index++ )
393 newPolySet.m_polys.push_back( Polygon( index ) );
394
395 return newPolySet;
396}
397
398
399const VECTOR2I& SHAPE_POLY_SET::CVertex( int aIndex, int aOutline, int aHole ) const
400{
401 if( aOutline < 0 )
402 aOutline += m_polys.size();
403
404 int idx;
405
406 if( aHole < 0 )
407 idx = 0;
408 else
409 idx = aHole + 1;
410
411 assert( aOutline < (int) m_polys.size() );
412 assert( idx < (int) m_polys[aOutline].size() );
413
414 return m_polys[aOutline][idx].CPoint( aIndex );
415}
416
417
418const VECTOR2I& SHAPE_POLY_SET::CVertex( int aGlobalIndex ) const
419{
421
422 // Assure the passed index references a legal position; abort otherwise
423 if( !GetRelativeIndices( aGlobalIndex, &index ) )
424 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
425
426 return m_polys[index.m_polygon][index.m_contour].CPoint( index.m_vertex );
427}
428
429
431{
432 return CVertex( index.m_vertex, index.m_polygon, index.m_contour - 1 );
433}
434
435
436bool SHAPE_POLY_SET::GetNeighbourIndexes( int aGlobalIndex, int* aPrevious, int* aNext ) const
437{
439
440 // If the edge does not exist, throw an exception, it is an illegal access memory error
441 if( !GetRelativeIndices( aGlobalIndex, &index ) )
442 return false;
443
444 // Calculate the previous and next index of aGlobalIndex, corresponding to
445 // the same contour;
446 VERTEX_INDEX inext = index;
447 int lastpoint = m_polys[index.m_polygon][index.m_contour].SegmentCount();
448
449 if( index.m_vertex == 0 )
450 {
451 index.m_vertex = lastpoint - 1;
452 inext.m_vertex = 1;
453 }
454 else if( index.m_vertex == lastpoint )
455 {
456 index.m_vertex--;
457 inext.m_vertex = 0;
458 }
459 else
460 {
461 inext.m_vertex++;
462 index.m_vertex--;
463
464 if( inext.m_vertex == lastpoint )
465 inext.m_vertex = 0;
466 }
467
468 if( aPrevious )
469 {
470 int previous;
471 GetGlobalIndex( index, previous );
472 *aPrevious = previous;
473 }
474
475 if( aNext )
476 {
477 int next;
478 GetGlobalIndex( inext, next );
479 *aNext = next;
480 }
481
482 return true;
483}
484
485
486bool SHAPE_POLY_SET::IsPolygonSelfIntersecting( int aPolygonIndex ) const
487{
488 std::vector<SEG> segments;
489 segments.reserve( FullPointCount() );
490
491 for( CONST_SEGMENT_ITERATOR it = CIterateSegmentsWithHoles( aPolygonIndex ); it; it++ )
492 segments.emplace_back( *it );
493
494 std::sort( segments.begin(), segments.end(), []( const SEG& a, const SEG& b )
495 {
496 int min_a_x = std::min( a.A.x, a.B.x );
497 int min_b_x = std::min( b.A.x, b.B.x );
498
499 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 ) );
500 } );
501
502 for( auto it = segments.begin(); it != segments.end(); ++it )
503 {
504 SEG& firstSegment = *it;
505
506 // Iterate through all remaining segments.
507 auto innerIterator = it;
508 int max_x = std::max( firstSegment.A.x, firstSegment.B.x );
509 int max_y = std::max( firstSegment.A.y, firstSegment.B.y );
510
511 // Start in the next segment, we don't want to check collision between a segment and itself
512 for( innerIterator++; innerIterator != segments.end(); innerIterator++ )
513 {
514 SEG& secondSegment = *innerIterator;
515 int min_x = std::min( secondSegment.A.x, secondSegment.B.x );
516 int min_y = std::min( secondSegment.A.y, secondSegment.B.y );
517
518 // We are ordered in minimum point order, so checking the static max (first segment) against
519 // the ordered min will tell us if any of the following segments are withing the BBox
520 if( max_x < min_x || ( max_x == min_x && max_y < min_y ) )
521 break;
522
523 int index_diff = std::abs( firstSegment.Index() - secondSegment.Index() );
524 bool adjacent = ( index_diff == 1) || (index_diff == ((int)segments.size() - 1) );
525
526 // Check whether the two segments built collide, only when they are not adjacent.
527 if( !adjacent && firstSegment.Collide( secondSegment, 0 ) )
528 return true;
529 }
530 }
531
532 return false;
533}
534
535
537{
538 for( unsigned int polygon = 0; polygon < m_polys.size(); polygon++ )
539 {
540 if( IsPolygonSelfIntersecting( polygon ) )
541 return true;
542 }
543
544 return false;
545}
546
547
549{
550 POLYGON poly;
551
552 poly.push_back( aOutline );
553
554 // This is an assertion because if it's generated by KiCad code, it probably
555 // indicates a bug elsewhere that should be fixed, but we also auto-fix it here
556 // for SWIG plugins that might mess it up
557 wxCHECK2_MSG( aOutline.IsClosed(), poly.back().SetClosed( true ),
558 "Warning: non-closed outline added to SHAPE_POLY_SET" );
559
560 m_polys.push_back( std::move( poly ) );
561
562 return (int) m_polys.size() - 1;
563}
564
565
566int SHAPE_POLY_SET::AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline )
567{
568 assert( m_polys.size() );
569
570 if( aOutline < 0 )
571 aOutline += (int) m_polys.size();
572
573 assert( aOutline < (int)m_polys.size() );
574
575 POLYGON& poly = m_polys[aOutline];
576
577 assert( poly.size() );
578
579 poly.push_back( aHole );
580
581 return (int) poly.size() - 2;
582}
583
584
586{
587 m_polys.push_back( apolygon );
588
589 return m_polys.size() - 1;
590}
591
592
594{
595 double area = 0.0;
596
597 for( int i = 0; i < OutlineCount(); i++ )
598 {
599 area += Outline( i ).Area( true );
600
601 for( int j = 0; j < HoleCount( i ); j++ )
602 area -= Hole( i, j ).Area( true );
603 }
604
605 return area;
606}
607
608
610{
611 int retval = 0;
612
613 for( const POLYGON& poly : m_polys )
614 {
615 for( size_t i = 0; i < poly.size(); i++ )
616 retval += poly[i].ArcCount();
617 }
618
619 return retval;
620}
621
622
623void SHAPE_POLY_SET::GetArcs( std::vector<SHAPE_ARC>& aArcBuffer ) const
624{
625 for( const POLYGON& poly : m_polys )
626 {
627 for( size_t i = 0; i < poly.size(); i++ )
628 {
629 for( SHAPE_ARC arc : poly[i].m_arcs )
630 aArcBuffer.push_back( arc );
631 }
632 }
633}
634
635
637{
638 for( POLYGON& poly : m_polys )
639 {
640 for( size_t i = 0; i < poly.size(); i++ )
641 poly[i].ClearArcs();
642 }
643}
644
645
647{
648 std::vector<SHAPE_LINE_CHAIN> contours;
649
650 for( const POLYGON& poly : m_polys )
651 contours.insert( contours.end(), poly.begin(), poly.end() );
652
653 std::map<int, std::set<int>> parentToChildren;
654 std::map<int, std::set<int>> childToParents;
655
656 for( SHAPE_LINE_CHAIN& contour : contours )
657 contour.GenerateBBoxCache();
658
659 for( size_t i = 0; i < contours.size(); i++ )
660 {
661 const SHAPE_LINE_CHAIN& outline = contours[i];
662
663 for( size_t j = 0; j < contours.size(); j++ )
664 {
665 if( i == j )
666 continue;
667
668 const SHAPE_LINE_CHAIN& candidate = contours[j];
669 const VECTOR2I& pt0 = candidate.CPoint( 0 );
670
671 if( outline.PointInside( pt0, 0, true ) )
672 {
673 parentToChildren[i].emplace( j );
674 childToParents[j].emplace( i );
675 }
676 }
677 }
678
679 std::set<int> topLevelParents;
680
681 for( size_t i = 0; i < contours.size(); i++ )
682 {
683 if( childToParents[i].size() == 0 )
684 {
685 topLevelParents.emplace( i );
686 }
687 }
688
690
691 std::function<void( int, int, std::vector<int> )> process;
692
693 process =
694 [&]( int myId, int parentOutlineId, const std::vector<int>& path )
695 {
696 std::set<int> relParents = childToParents[myId];
697
698 for( int pathId : path )
699 {
700 int erased = relParents.erase( pathId );
701 wxASSERT( erased > 0 );
702 }
703
704 wxASSERT( relParents.size() == 0 );
705
706 int myOutline = -1;
707
708 bool isOutline = path.size() % 2 == 0;
709
710 if( isOutline )
711 {
712 int outlineId = result.AddOutline( contours[myId] );
713 myOutline = outlineId;
714 }
715 else
716 {
717 wxASSERT( parentOutlineId != -1 );
718 result.AddHole( contours[myId], parentOutlineId );
719 }
720
721 auto it = parentToChildren.find( myId );
722 if( it != parentToChildren.end() )
723 {
724 std::vector<int> thisPath = path;
725 thisPath.emplace_back( myId );
726
727 std::set<int> thisPathSet;
728 thisPathSet.insert( thisPath.begin(), thisPath.end() );
729
730 for( int childId : it->second )
731 {
732 const std::set<int>& childPathSet = childToParents[childId];
733
734 if( thisPathSet != childPathSet )
735 continue; // Only interested in immediate children
736
737 process( childId, myOutline, thisPath );
738 }
739 }
740 };
741
742 for( int topParentId : topLevelParents )
743 {
744 std::vector<int> path;
745 process( topParentId, -1, std::move( path ) );
746 }
747
748 *this = std::move( result );
749}
750
751
752void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aOtherShape )
753{
754 booleanOp( aType, *this, aOtherShape );
755}
756
757
758void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aShape,
759 const SHAPE_POLY_SET& aOtherShape )
760{
761 if( ( aShape.OutlineCount() > 1 || aOtherShape.OutlineCount() > 0 )
762 && ( aShape.ArcCount() > 0 || aOtherShape.ArcCount() > 0 ) )
763 {
764 wxFAIL_MSG( wxT( "Boolean ops on curved polygons are not supported. You should call "
765 "ClearArcs() before carrying out the boolean operation." ) );
766 }
767
768 Clipper2Lib::Clipper64 c;
769
770 std::vector<CLIPPER_Z_VALUE> zValues;
771 std::vector<SHAPE_ARC> arcBuffer;
772 std::map<VECTOR2I, CLIPPER_Z_VALUE> newIntersectPoints;
773
774 Clipper2Lib::Paths64 paths;
775 Clipper2Lib::Paths64 clips;
776
777 for( const POLYGON& poly : aShape.m_polys )
778 {
779 for( size_t i = 0; i < poly.size(); i++ )
780 {
781 paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
782 }
783 }
784
785 for( const POLYGON& poly : aOtherShape.m_polys )
786 {
787 for( size_t i = 0; i < poly.size(); i++ )
788 {
789 clips.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
790 }
791 }
792
793 c.AddSubject( paths );
794 c.AddClip( clips );
795
796 Clipper2Lib::PolyTree64 solution;
797
798 Clipper2Lib::ZCallback64 callback =
799 [&]( const Clipper2Lib::Point64 & e1bot, const Clipper2Lib::Point64 & e1top,
800 const Clipper2Lib::Point64 & e2bot, const Clipper2Lib::Point64 & e2top,
801 Clipper2Lib::Point64 & pt )
802 {
803 auto arcIndex =
804 [&]( const ssize_t& aZvalue, const ssize_t& aCompareVal = -1 ) -> ssize_t
805 {
806 ssize_t retval;
807
808 retval = zValues.at( aZvalue ).m_SecondArcIdx;
809
810 if( retval == -1 || ( aCompareVal > 0 && retval != aCompareVal ) )
811 retval = zValues.at( aZvalue ).m_FirstArcIdx;
812
813 return retval;
814 };
815
816 auto arcSegment =
817 [&]( const ssize_t& aBottomZ, const ssize_t aTopZ ) -> ssize_t
818 {
819 ssize_t retval = arcIndex( aBottomZ );
820
821 if( retval != -1 )
822 {
823 if( retval != arcIndex( aTopZ, retval ) )
824 retval = -1; // Not an arc segment as the two indices do not match
825 }
826
827 return retval;
828 };
829
830 ssize_t e1ArcSegmentIndex = arcSegment( e1bot.z, e1top.z );
831 ssize_t e2ArcSegmentIndex = arcSegment( e2bot.z, e2top.z );
832
833 CLIPPER_Z_VALUE newZval;
834
835 if( e1ArcSegmentIndex != -1 )
836 {
837 newZval.m_FirstArcIdx = e1ArcSegmentIndex;
838 newZval.m_SecondArcIdx = e2ArcSegmentIndex;
839 }
840 else
841 {
842 newZval.m_FirstArcIdx = e2ArcSegmentIndex;
843 newZval.m_SecondArcIdx = -1;
844 }
845
846 size_t z_value_ptr = zValues.size();
847 zValues.push_back( newZval );
848
849 // Only worry about arc segments for later processing
850 if( newZval.m_FirstArcIdx != -1 )
851 newIntersectPoints.insert( { VECTOR2I( pt.x, pt.y ), newZval } );
852
853 pt.z = z_value_ptr;
854 //@todo amend X,Y values to true intersection between arcs or arc and segment
855 };
856
857 c.SetZCallback( std::move( callback ) ); // register callback
858
859 c.Execute( aType, Clipper2Lib::FillRule::NonZero, solution );
860
861 importTree( solution, zValues, arcBuffer );
862 solution.Clear(); // Free used memory (not done in dtor)
863}
864
865
867{
868 booleanOp( Clipper2Lib::ClipType::Union, b );
869}
870
871
873{
874 booleanOp( Clipper2Lib::ClipType::Difference, b );
875}
876
877
879{
880 booleanOp( Clipper2Lib::ClipType::Intersection, b );
881}
882
883
885{
886 booleanOp( Clipper2Lib::ClipType::Xor, b );
887}
888
889
891{
892 booleanOp( Clipper2Lib::ClipType::Union, a, b );
893}
894
895
897{
898 booleanOp( Clipper2Lib::ClipType::Difference, a, b );
899}
900
901
903{
904 booleanOp( Clipper2Lib::ClipType::Intersection, a, b );
905}
906
907
909{
910 booleanOp( Clipper2Lib::ClipType::Xor, a, b );
911}
912
913
915 int aMaxError )
916{
917 Unfracture();
918 Inflate( aFactor, aCornerStrategy, aMaxError );
919 Fracture();
920}
921
922
923void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy,
924 bool aSimplify )
925{
926 using namespace Clipper2Lib;
927 // A thread-local table to avoid repetitive calculations of the coefficient
928 // 1.0 - cos( M_PI / aCircleSegCount )
929 // aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
930 #define SEG_CNT_MAX 64
931 static thread_local double arc_tolerance_factor[SEG_CNT_MAX + 1];
932
933 ClipperOffset c;
934
935 // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
936 // and are not what you'd think they are.
937 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
938 JoinType joinType = JoinType::Round; // The way corners are offsetted
939 double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
940
941 switch( aCornerStrategy )
942 {
944 joinType = JoinType::Miter;
945 miterLimit = 10; // Allows large spikes
946 break;
947
948 case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
949 joinType = JoinType::Miter;
950 break;
951
952 case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
953 joinType = JoinType::Miter;
954 break;
955
956 case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
957 joinType = JoinType::Square;
958 break;
959
960 case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
961 joinType = JoinType::Round;
962 break;
963 }
964
965 std::vector<CLIPPER_Z_VALUE> zValues;
966 std::vector<SHAPE_ARC> arcBuffer;
967
968 for( const POLYGON& poly : m_polys )
969 {
970 Paths64 paths;
971
972 for( size_t i = 0; i < poly.size(); i++ )
973 paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
974
975 c.AddPaths( paths, joinType, EndType::Polygon );
976 }
977
978 // Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
979 // nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
980 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
981
982 if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
983 aCircleSegCount = 6;
984
985 double coeff;
986
987 if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
988 {
989 coeff = 1.0 - cos( M_PI / aCircleSegCount );
990
991 if( aCircleSegCount <= SEG_CNT_MAX )
992 arc_tolerance_factor[aCircleSegCount] = coeff;
993 }
994 else
995 {
996 coeff = arc_tolerance_factor[aCircleSegCount];
997 }
998
999 c.ArcTolerance( std::abs( aAmount ) * coeff );
1000 c.MiterLimit( miterLimit );
1001
1002 PolyTree64 tree;
1003
1004 if( aSimplify )
1005 {
1006 Paths64 paths;
1007 c.Execute( aAmount, paths );
1008
1009 Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, true );
1010
1011 Clipper64 c2;
1012 c2.PreserveCollinear( false );
1013 c2.ReverseSolution( false );
1014 c2.AddSubject( paths );
1015 c2.Execute(ClipType::Union, FillRule::Positive, tree);
1016 }
1017 else
1018 {
1019 c.Execute( aAmount, tree );
1020 }
1021
1022 importTree( tree, zValues, arcBuffer );
1023 tree.Clear();
1024}
1025
1026
1027void SHAPE_POLY_SET::inflateLine2( const SHAPE_LINE_CHAIN& aLine, int aAmount, int aCircleSegCount,
1028 CORNER_STRATEGY aCornerStrategy, bool aSimplify )
1029{
1030 using namespace Clipper2Lib;
1031 // A thread-local table to avoid repetitive calculations of the coefficient
1032 // 1.0 - cos( M_PI / aCircleSegCount )
1033 // aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
1034 #define SEG_CNT_MAX 64
1035 static thread_local double arc_tolerance_factor[SEG_CNT_MAX + 1];
1036
1037 ClipperOffset c;
1038
1039 // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
1040 // and are not what you'd think they are.
1041 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
1042 JoinType joinType = JoinType::Round; // The way corners are offsetted
1043 double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
1044
1045 switch( aCornerStrategy )
1046 {
1048 joinType = JoinType::Miter;
1049 miterLimit = 10; // Allows large spikes
1050 break;
1051
1052 case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
1053 joinType = JoinType::Miter;
1054 break;
1055
1056 case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
1057 joinType = JoinType::Miter;
1058 break;
1059
1060 case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
1061 joinType = JoinType::Square;
1062 break;
1063
1064 case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
1065 joinType = JoinType::Round;
1066 break;
1067 }
1068
1069 std::vector<CLIPPER_Z_VALUE> zValues;
1070 std::vector<SHAPE_ARC> arcBuffer;
1071
1072 Path64 path = aLine.convertToClipper2( true, zValues, arcBuffer );
1073 c.AddPath( path, joinType, EndType::Butt );
1074
1075 // Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
1076 // nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
1077 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
1078
1079 if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
1080 aCircleSegCount = 6;
1081
1082 double coeff;
1083
1084 if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
1085 {
1086 coeff = 1.0 - cos( M_PI / aCircleSegCount );
1087
1088 if( aCircleSegCount <= SEG_CNT_MAX )
1089 arc_tolerance_factor[aCircleSegCount] = coeff;
1090 }
1091 else
1092 {
1093 coeff = arc_tolerance_factor[aCircleSegCount];
1094 }
1095
1096 c.ArcTolerance( std::abs( aAmount ) * coeff );
1097 c.MiterLimit( miterLimit );
1098
1099 PolyTree64 tree;
1100
1101 if( aSimplify )
1102 {
1103 Paths64 paths2;
1104 c.Execute( aAmount, paths2 );
1105
1106 Clipper2Lib::SimplifyPaths( paths2, std::abs( aAmount ) * coeff, false );
1107
1108 Clipper64 c2;
1109 c2.PreserveCollinear( false );
1110 c2.ReverseSolution( false );
1111 c2.AddSubject( paths2 );
1112 c2.Execute( ClipType::Union, FillRule::Positive, tree );
1113 }
1114 else
1115 {
1116 c.Execute( aAmount, tree );
1117 }
1118
1119 importTree( tree, zValues, arcBuffer );
1120 tree.Clear();
1121}
1122
1123
1124void SHAPE_POLY_SET::Inflate( int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError,
1125 bool aSimplify )
1126{
1127 int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
1128
1129 inflate2( aAmount, segCount, aCornerStrategy, aSimplify );
1130}
1131
1132
1134 CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify )
1135{
1136 int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
1137
1138 inflateLine2( aLine, aAmount, segCount, aCornerStrategy, aSimplify );
1139}
1140
1141
1142void SHAPE_POLY_SET::importPolyPath( const std::unique_ptr<Clipper2Lib::PolyPath64>& aPolyPath,
1143 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1144 const std::vector<SHAPE_ARC>& aArcBuffer )
1145{
1146 if( !aPolyPath->IsHole() )
1147 {
1148 POLYGON paths;
1149 paths.reserve( aPolyPath->Count() + 1 );
1150 paths.emplace_back( aPolyPath->Polygon(), aZValueBuffer, aArcBuffer );
1151
1152 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& child : *aPolyPath )
1153 {
1154 paths.emplace_back( child->Polygon(), aZValueBuffer, aArcBuffer );
1155
1156 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& grandchild : *child )
1157 importPolyPath( grandchild, aZValueBuffer, aArcBuffer );
1158 }
1159
1160 m_polys.emplace_back( std::move( paths ) );
1161 }
1162}
1163
1164
1165void SHAPE_POLY_SET::importTree( Clipper2Lib::PolyTree64& tree,
1166 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1167 const std::vector<SHAPE_ARC>& aArcBuffer )
1168{
1169 m_polys.clear();
1170
1171 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& n : tree )
1172 importPolyPath( n, aZValueBuffer, aArcBuffer );
1173}
1174
1175
1176void SHAPE_POLY_SET::importPaths( Clipper2Lib::Paths64& aPath,
1177 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1178 const std::vector<SHAPE_ARC>& aArcBuffer )
1179{
1180 m_polys.clear();
1181 POLYGON path;
1182
1183 for( const Clipper2Lib::Path64& n : aPath )
1184 {
1185 if( Clipper2Lib::Area( n ) > 0 )
1186 {
1187 if( !path.empty() )
1188 m_polys.emplace_back( path );
1189
1190 path.clear();
1191 }
1192 else
1193 {
1194 wxCHECK2_MSG( !path.empty(), continue, wxT( "Cannot add a hole before an outline" ) );
1195 }
1196
1197 path.emplace_back( n, aZValueBuffer, aArcBuffer );
1198 }
1199
1200 if( !path.empty() )
1201 m_polys.emplace_back( std::move( path ) );
1202}
1203
1204
1206{
1207 using Index = int;
1208
1209 FractureEdge() = default;
1210
1211 FractureEdge( const VECTOR2I& p1, const VECTOR2I& p2, Index next ) :
1212 m_p1( p1 ),
1213 m_p2( p2 ),
1214 m_next( next )
1215 {
1216 }
1217
1218 bool matches( int y ) const
1219 {
1220 return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
1221 }
1222
1226};
1227
1228
1229typedef std::vector<FractureEdge> FractureEdgeSet;
1230
1231
1233 FractureEdge::Index edgeIndex, FractureEdge::Index bridgeIndex )
1234{
1235 FractureEdge& edge = edges[edgeIndex];
1236 int x = edge.m_p1.x;
1237 int y = edge.m_p1.y;
1238 int min_dist = std::numeric_limits<int>::max();
1239 int x_nearest = 0;
1240
1241 FractureEdge* e_nearest = nullptr;
1242
1243 // Since this function is run for all holes left to right, no need to
1244 // check for any edge beyond the provoking one because they will always be
1245 // further to the right, and unconnected to the outline anyway.
1246 for( FractureEdge::Index i = 0; i < provokingIndex; i++ )
1247 {
1248 FractureEdge& e = edges[i];
1249 // Don't consider this edge if it can't be bridged to, or faces left.
1250 if( !e.matches( y ) )
1251 continue;
1252
1253 int x_intersect;
1254
1255 if( e.m_p1.y == e.m_p2.y ) // horizontal edge
1256 {
1257 x_intersect = std::max( e.m_p1.x, e.m_p2.x );
1258 }
1259 else
1260 {
1261 x_intersect =
1262 e.m_p1.x + rescale( e.m_p2.x - e.m_p1.x, y - e.m_p1.y, e.m_p2.y - e.m_p1.y );
1263 }
1264
1265 int dist = ( x - x_intersect );
1266
1267 if( dist >= 0 && dist < min_dist )
1268 {
1269 min_dist = dist;
1270 x_nearest = x_intersect;
1271 e_nearest = &e;
1272 }
1273 }
1274
1275 if( e_nearest )
1276 {
1277 const FractureEdge::Index outline2hole_index = bridgeIndex;
1278 const FractureEdge::Index hole2outline_index = bridgeIndex + 1;
1279 const FractureEdge::Index split_index = bridgeIndex + 2;
1280 // Make an edge between the split outline edge and the hole...
1281 edges[outline2hole_index] = FractureEdge( VECTOR2I( x_nearest, y ), edge.m_p1, edgeIndex );
1282 // ...between the hole and the edge...
1283 edges[hole2outline_index] =
1284 FractureEdge( edge.m_p1, VECTOR2I( x_nearest, y ), split_index );
1285 // ...and between the split outline edge and the rest.
1286 edges[split_index] =
1287 FractureEdge( VECTOR2I( x_nearest, y ), e_nearest->m_p2, e_nearest->m_next );
1288
1289 // Perform the actual outline edge split
1290 e_nearest->m_p2 = VECTOR2I( x_nearest, y );
1291 e_nearest->m_next = outline2hole_index;
1292
1293 FractureEdge* last = &edge;
1294 for( ; last->m_next != edgeIndex; last = &edges[last->m_next] )
1295 ;
1296 last->m_next = hole2outline_index;
1297 }
1298
1299 return e_nearest;
1300}
1301
1302
1304{
1305 FractureEdgeSet edges;
1306 bool outline = true;
1307
1308 if( paths.size() == 1 )
1309 return;
1310
1311 size_t total_point_count = 0;
1312
1313 for( const SHAPE_LINE_CHAIN& path : paths )
1314 {
1315 total_point_count += path.PointCount();
1316 }
1317
1318 if( total_point_count > (size_t) std::numeric_limits<FractureEdge::Index>::max() )
1319 {
1320 wxLogWarning( wxT( "Polygon has more points than int limit" ) );
1321 return;
1322 }
1323
1324 // Reserve space in the edge set so pointers don't get invalidated during
1325 // the whole fracture process; one for each original edge, plus 3 per
1326 // path to join it to the outline.
1327 edges.reserve( total_point_count + paths.size() * 3 );
1328
1329 // Sort the paths by their lowest X bound before processing them.
1330 // This ensures the processing order for processEdge() is correct.
1331 struct PathInfo
1332 {
1333 int path_or_provoking_index;
1334 FractureEdge::Index leftmost;
1335 int x;
1336 int y_or_bridge;
1337 };
1338 std::vector<PathInfo> sorted_paths;
1339 const int paths_count = static_cast<int>( paths.size() );
1340 sorted_paths.reserve( paths_count );
1341
1342 for( int path_index = 0; path_index < paths_count; path_index++ )
1343 {
1344 const SHAPE_LINE_CHAIN& path = paths[path_index];
1345 const std::vector<VECTOR2I>& points = path.CPoints();
1346 const int point_count = static_cast<int>( points.size() );
1347 int x_min = std::numeric_limits<int>::max();
1348 int y_min = std::numeric_limits<int>::max();
1349 int leftmost = -1;
1350
1351 for( int point_index = 0; point_index < point_count; point_index++ )
1352 {
1353 const VECTOR2I& point = points[point_index];
1354 if( point.x < x_min )
1355 {
1356 x_min = point.x;
1357 leftmost = point_index;
1358 }
1359 if( point.y < y_min )
1360 y_min = point.y;
1361 }
1362
1363 sorted_paths.emplace_back( PathInfo{ path_index, leftmost, x_min, y_min } );
1364 }
1365
1366 std::sort( sorted_paths.begin() + 1, sorted_paths.end(),
1367 []( const PathInfo& a, const PathInfo& b )
1368 {
1369 if( a.x == b.x )
1370 return a.y_or_bridge < b.y_or_bridge;
1371 return a.x < b.x;
1372 } );
1373
1374 FractureEdge::Index edge_index = 0;
1375
1376 for( PathInfo& path_info : sorted_paths )
1377 {
1378 const SHAPE_LINE_CHAIN& path = paths[path_info.path_or_provoking_index];
1379 const std::vector<VECTOR2I>& points = path.CPoints();
1380 const size_t point_count = points.size();
1381
1382 // Index of the provoking (first) edge for this path
1383 const FractureEdge::Index provoking_edge = edge_index;
1384
1385 for( size_t i = 0; i < point_count - 1; i++ )
1386 {
1387 edges.emplace_back( points[i], points[i + 1], edge_index + 1 );
1388 edge_index++;
1389 }
1390
1391 // Create last edge looping back to the provoking one.
1392 edges.emplace_back( points[point_count - 1], points[0], provoking_edge );
1393 edge_index++;
1394
1395 if( !outline )
1396 {
1397 // Repurpose the path sorting data structure to schedule the leftmost edge
1398 // for merging to the outline, which will in turn merge the rest of the path.
1399 path_info.path_or_provoking_index = provoking_edge;
1400 path_info.y_or_bridge = edge_index;
1401
1402 // Reserve 3 additional edges to bridge with the outline.
1403 edge_index += 3;
1404 edges.resize( edge_index );
1405 }
1406
1407 outline = false; // first path is always the outline
1408 }
1409
1410 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); it++ )
1411 {
1412 auto edge = processHole( edges, it->path_or_provoking_index,
1413 it->path_or_provoking_index + it->leftmost, it->y_or_bridge );
1414
1415 // If we can't handle the hole, the zone is broken (maybe)
1416 if( !edge )
1417 {
1418 wxLogWarning( wxT( "Broken polygon, dropping path" ) );
1419
1420 return;
1421 }
1422 }
1423
1424 paths.resize( 1 );
1425 SHAPE_LINE_CHAIN& newPath = paths[0];
1426
1427 newPath.Clear();
1428 newPath.SetClosed( true );
1429
1430 // Root edge is always at index 0
1431 FractureEdge* e = &edges[0];
1432
1433 for( ; e->m_next != 0; e = &edges[e->m_next] )
1434 newPath.Append( e->m_p1 );
1435
1436 newPath.Append( e->m_p1 );
1437}
1438
1439
1441{
1442 FractureEdgeSlow( int y = 0 ) : m_connected( false ), m_next( nullptr ) { m_p1.x = m_p2.y = y; }
1443
1444 FractureEdgeSlow( bool connected, const VECTOR2I& p1, const VECTOR2I& p2 ) :
1445 m_connected( connected ), m_p1( p1 ), m_p2( p2 ), m_next( nullptr )
1446 {
1447 }
1448
1449 bool matches( int y ) const
1450 {
1451 return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
1452 }
1453
1458};
1459
1460
1461typedef std::vector<FractureEdgeSlow*> FractureEdgeSetSlow;
1462
1463
1465{
1466 int x = edge->m_p1.x;
1467 int y = edge->m_p1.y;
1468 int min_dist = std::numeric_limits<int>::max();
1469 int x_nearest = 0;
1470
1471 FractureEdgeSlow* e_nearest = nullptr;
1472
1473 for( FractureEdgeSlow* e : edges )
1474 {
1475 if( !e->matches( y ) )
1476 continue;
1477
1478 int x_intersect;
1479
1480 if( e->m_p1.y == e->m_p2.y ) // horizontal edge
1481 {
1482 x_intersect = std::max( e->m_p1.x, e->m_p2.x );
1483 }
1484 else
1485 {
1486 x_intersect = e->m_p1.x
1487 + rescale( e->m_p2.x - e->m_p1.x, y - e->m_p1.y, e->m_p2.y - e->m_p1.y );
1488 }
1489
1490 int dist = ( x - x_intersect );
1491
1492 if( dist >= 0 && dist < min_dist && e->m_connected )
1493 {
1494 min_dist = dist;
1495 x_nearest = x_intersect;
1496 e_nearest = e;
1497 }
1498 }
1499
1500 if( e_nearest && e_nearest->m_connected )
1501 {
1502 int count = 0;
1503
1504 FractureEdgeSlow* lead1 =
1505 new FractureEdgeSlow( true, VECTOR2I( x_nearest, y ), VECTOR2I( x, y ) );
1506 FractureEdgeSlow* lead2 =
1507 new FractureEdgeSlow( true, VECTOR2I( x, y ), VECTOR2I( x_nearest, y ) );
1508 FractureEdgeSlow* split_2 =
1509 new FractureEdgeSlow( true, VECTOR2I( x_nearest, y ), e_nearest->m_p2 );
1510
1511 edges.push_back( split_2 );
1512 edges.push_back( lead1 );
1513 edges.push_back( lead2 );
1514
1515 FractureEdgeSlow* link = e_nearest->m_next;
1516
1517 e_nearest->m_p2 = VECTOR2I( x_nearest, y );
1518 e_nearest->m_next = lead1;
1519 lead1->m_next = edge;
1520
1521 FractureEdgeSlow* last;
1522
1523 for( last = edge; last->m_next != edge; last = last->m_next )
1524 {
1525 last->m_connected = true;
1526 count++;
1527 }
1528
1529 last->m_connected = true;
1530 last->m_next = lead2;
1531 lead2->m_next = split_2;
1532 split_2->m_next = link;
1533
1534 return count + 1;
1535 }
1536
1537 return 0;
1538}
1539
1540
1542{
1543 FractureEdgeSetSlow edges;
1544 FractureEdgeSetSlow border_edges;
1545 FractureEdgeSlow* root = nullptr;
1546
1547 bool first = true;
1548
1549 if( paths.size() == 1 )
1550 return;
1551
1552 int num_unconnected = 0;
1553
1554 for( const SHAPE_LINE_CHAIN& path : paths )
1555 {
1556 const std::vector<VECTOR2I>& points = path.CPoints();
1557 int pointCount = points.size();
1558
1559 FractureEdgeSlow *prev = nullptr, *first_edge = nullptr;
1560
1561 int x_min = std::numeric_limits<int>::max();
1562
1563 for( int i = 0; i < pointCount; i++ )
1564 {
1565 if( points[i].x < x_min )
1566 x_min = points[i].x;
1567
1568 // Do not use path.CPoint() here; open-coding it using the local variables "points"
1569 // and "pointCount" gives a non-trivial performance boost to zone fill times.
1570 FractureEdgeSlow* fe = new FractureEdgeSlow( first, points[i],
1571 points[i + 1 == pointCount ? 0 : i + 1] );
1572
1573 if( !root )
1574 root = fe;
1575
1576 if( !first_edge )
1577 first_edge = fe;
1578
1579 if( prev )
1580 prev->m_next = fe;
1581
1582 if( i == pointCount - 1 )
1583 fe->m_next = first_edge;
1584
1585 prev = fe;
1586 edges.push_back( fe );
1587
1588 if( !first )
1589 {
1590 if( fe->m_p1.x == x_min )
1591 border_edges.push_back( fe );
1592 }
1593
1594 if( !fe->m_connected )
1595 num_unconnected++;
1596 }
1597
1598 first = false; // first path is always the outline
1599 }
1600
1601 // keep connecting holes to the main outline, until there's no holes left...
1602 while( num_unconnected > 0 )
1603 {
1604 int x_min = std::numeric_limits<int>::max();
1605 auto it = border_edges.begin();
1606
1607 FractureEdgeSlow* smallestX = nullptr;
1608
1609 // find the left-most hole edge and merge with the outline
1610 for( ; it != border_edges.end(); ++it )
1611 {
1612 FractureEdgeSlow* border_edge = *it;
1613 int xt = border_edge->m_p1.x;
1614
1615 if( ( xt <= x_min ) && !border_edge->m_connected )
1616 {
1617 x_min = xt;
1618 smallestX = border_edge;
1619 }
1620 }
1621
1622 int num_processed = processEdge( edges, smallestX );
1623
1624 // If we can't handle the edge, the zone is broken (maybe)
1625 if( !num_processed )
1626 {
1627 wxLogWarning( wxT( "Broken polygon, dropping path" ) );
1628
1629 for( FractureEdgeSlow* edge : edges )
1630 delete edge;
1631
1632 return;
1633 }
1634
1635 num_unconnected -= num_processed;
1636 }
1637
1638 paths.clear();
1639 SHAPE_LINE_CHAIN newPath;
1640
1641 newPath.SetClosed( true );
1642
1644
1645 for( e = root; e->m_next != root; e = e->m_next )
1646 newPath.Append( e->m_p1 );
1647
1648 newPath.Append( e->m_p1 );
1649
1650 for( FractureEdgeSlow* edge : edges )
1651 delete edge;
1652
1653 paths.push_back( std::move( newPath ) );
1654}
1655
1656
1658{
1660 return fractureSingleCacheFriendly( paths );
1661 fractureSingleSlow( paths );
1662}
1663
1664
1665void SHAPE_POLY_SET::Fracture( bool aSimplify )
1666{
1667 if( aSimplify )
1668 Simplify(); // remove overlapping holes/degeneracy
1669
1670 for( POLYGON& paths : m_polys )
1671 fractureSingle( paths );
1672}
1673
1674
1676{
1677 assert( aPoly.size() == 1 );
1678
1679 struct EDGE
1680 {
1681 int m_index = 0;
1682 SHAPE_LINE_CHAIN* m_poly = nullptr;
1683 bool m_duplicate = false;
1684
1685 EDGE( SHAPE_LINE_CHAIN* aPolygon, int aIndex ) :
1686 m_index( aIndex ),
1687 m_poly( aPolygon )
1688 {}
1689
1690 bool compareSegs( const SEG& s1, const SEG& s2 ) const
1691 {
1692 return (s1.A == s2.B && s1.B == s2.A);
1693 }
1694
1695 bool operator==( const EDGE& aOther ) const
1696 {
1697 return compareSegs( m_poly->CSegment( m_index ),
1698 aOther.m_poly->CSegment( aOther.m_index ) );
1699 }
1700
1701 bool operator!=( const EDGE& aOther ) const
1702 {
1703 return !compareSegs( m_poly->CSegment( m_index ),
1704 aOther.m_poly->CSegment( aOther.m_index ) );
1705 }
1706
1707 struct HASH
1708 {
1709 std::size_t operator()( const EDGE& aEdge ) const
1710 {
1711 const SEG& a = aEdge.m_poly->CSegment( aEdge.m_index );
1712 std::size_t seed = 0xa82de1c0;
1713 hash_combine( seed, a.A.x, a.B.x, a.A.y, a.B.y );
1714 return seed;
1715 }
1716 };
1717 };
1718
1719 struct EDGE_LIST_ENTRY
1720 {
1721 int index;
1722 EDGE_LIST_ENTRY* next;
1723 };
1724
1725 std::unordered_set<EDGE, EDGE::HASH> uniqueEdges;
1726
1727 SHAPE_LINE_CHAIN lc = aPoly[0];
1728 lc.Simplify();
1729
1730 auto edgeList = std::make_unique<EDGE_LIST_ENTRY[]>( lc.SegmentCount() );
1731
1732 for( int i = 0; i < lc.SegmentCount(); i++ )
1733 {
1734 edgeList[i].index = i;
1735 edgeList[i].next = &edgeList[ (i != lc.SegmentCount() - 1) ? i + 1 : 0 ];
1736 }
1737
1738 std::unordered_set<EDGE_LIST_ENTRY*> queue;
1739
1740 for( int i = 0; i < lc.SegmentCount(); i++ )
1741 {
1742 EDGE e( &lc, i );
1743 uniqueEdges.insert( e );
1744 }
1745
1746 for( int i = 0; i < lc.SegmentCount(); i++ )
1747 {
1748 EDGE e( &lc, i );
1749 auto it = uniqueEdges.find( e );
1750
1751 if( it != uniqueEdges.end() && it->m_index != i )
1752 {
1753 int e1 = it->m_index;
1754 int e2 = i;
1755
1756 if( e1 > e2 )
1757 std::swap( e1, e2 );
1758
1759 int e1_prev = e1 - 1;
1760
1761 if( e1_prev < 0 )
1762 e1_prev = lc.SegmentCount() - 1;
1763
1764 int e2_prev = e2 - 1;
1765
1766 if( e2_prev < 0 )
1767 e2_prev = lc.SegmentCount() - 1;
1768
1769 int e1_next = e1 + 1;
1770
1771 if( e1_next == lc.SegmentCount() )
1772 e1_next = 0;
1773
1774 int e2_next = e2 + 1;
1775
1776 if( e2_next == lc.SegmentCount() )
1777 e2_next = 0;
1778
1779 edgeList[e1_prev].next = &edgeList[ e2_next ];
1780 edgeList[e2_prev].next = &edgeList[ e1_next ];
1781 edgeList[i].next = nullptr;
1782 edgeList[it->m_index].next = nullptr;
1783 }
1784 }
1785
1786 for( int i = 0; i < lc.SegmentCount(); i++ )
1787 {
1788 if( edgeList[i].next )
1789 queue.insert( &edgeList[i] );
1790 }
1791
1792 auto edgeBuf = std::make_unique<EDGE_LIST_ENTRY* []>( lc.SegmentCount() );
1793
1794 int n = 0;
1795 int outline = -1;
1796
1798 double max_poly = 0.0;
1799
1800 while( queue.size() )
1801 {
1802 EDGE_LIST_ENTRY* e_first = *queue.begin();
1803 EDGE_LIST_ENTRY* e = e_first;
1804 int cnt = 0;
1805
1806 do
1807 {
1808 edgeBuf[cnt++] = e;
1809 e = e->next;
1810 } while( e && e != e_first );
1811
1812 SHAPE_LINE_CHAIN outl;
1813
1814 for( int i = 0; i < cnt; i++ )
1815 {
1816 VECTOR2I p = lc.CPoint( edgeBuf[i]->index );
1817 outl.Append( p );
1818 queue.erase( edgeBuf[i] );
1819 }
1820
1821 outl.SetClosed( true );
1822
1823 double area = std::fabs( outl.Area() );
1824
1825 if( area > max_poly )
1826 {
1827 outline = n;
1828 max_poly = area;
1829 }
1830
1831 result.push_back( outl );
1832 n++;
1833 }
1834
1835 if( outline > 0 )
1836 std::swap( result[0], result[outline] );
1837
1838 aPoly = std::move( result );
1839}
1840
1841
1843{
1844 // Iterate through all the polygons on the set
1845 for( const POLYGON& paths : m_polys )
1846 {
1847 // If any of them has more than one contour, it is a hole.
1848 if( paths.size() > 1 )
1849 return true;
1850 }
1851
1852 // Return false if and only if every polygon has just one outline, without holes.
1853 return false;
1854}
1855
1856
1858{
1859 for( POLYGON& path : m_polys )
1861
1862 Simplify(); // remove overlapping holes/degeneracy
1863}
1864
1865
1866bool SHAPE_POLY_SET::isExteriorWaist( const SEG& aSegA, const SEG& aSegB ) const
1867{
1868 const VECTOR2I da = aSegA.B - aSegA.A;
1869
1870 int axis = std::abs( da.x ) >= std::abs( da.y ) ? 0 : 1;
1871
1872 std::array<VECTOR2I,4> pts = { aSegA.A, aSegA.B, aSegB.A, aSegB.B };
1873
1874 std::sort( pts.begin(), pts.end(), [axis]( const VECTOR2I& p, const VECTOR2I& q )
1875 {
1876 if( axis == 0 )
1877 return p.x < q.x || ( p.x == q.x && p.y < q.y );
1878 else
1879 return p.y < q.y || ( p.y == q.y && p.x < q.x );
1880 } );
1881
1882 VECTOR2I s = pts[1];
1883 VECTOR2I e = pts[2];
1884
1885 // Check if there is polygon material on either side of the overlapping segments
1886 // Get the midpoint between s and e for testing
1887 VECTOR2I midpoint = ( s + e ) / 2;
1888
1889 // Create perpendicular offset vector to check both sides
1890 VECTOR2I segDir = e - s;
1891
1892 if( segDir.EuclideanNorm() > 25 )
1893 {
1894 VECTOR2I perp = segDir.Perpendicular().Resize( 10 );
1895
1896 // Both sides must be outside for this to be an exterior waist. Short-circuit if
1897 // either side is inside the polygon to avoid the second O(N) point-in-polygon test.
1898 if( !PointInside( midpoint + perp ) && !PointInside( midpoint - perp ) )
1899 {
1900 wxLogTrace( wxT( "collinear" ), wxT( "Found exterior waist between (%d,%d)-(%d,%d) and (%d,%d)-(%d,%d)" ),
1901 aSegA.A.x, aSegA.A.y, aSegA.B.x, aSegA.B.y,
1902 aSegB.A.x, aSegB.A.y, aSegB.B.x, aSegB.B.y );
1903 return true;
1904 }
1905 }
1906
1907 return false;
1908}
1909
1910
1912{
1913 for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
1914 {
1915 bool changed = true;
1916
1917 while( changed )
1918 {
1919 changed = false;
1920
1921 SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
1922 intptr_t count = outline.PointCount();
1923
1925
1926 for( intptr_t i = 0; i < count; ++i )
1927 {
1928 const VECTOR2I& a = outline.CPoint( i );
1929 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
1930 intptr_t min[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
1931 intptr_t max[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
1932 rtree.Insert( min, max, i );
1933 }
1934
1935 bool found = false;
1936 int segA = -1;
1937 int segB = -1;
1938
1939 for( intptr_t i = 0; i < count && !found; ++i )
1940 {
1941 const VECTOR2I& a = outline.CPoint( i );
1942 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
1943 SEG seg( a, b );
1944 intptr_t min[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
1945 intptr_t max[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
1946
1947 auto visitor =
1948 [&]( const intptr_t& j ) -> bool
1949 {
1950 if( j == i || j == ( ( i + 1 ) % count ) || j == ( ( i + count - 1 ) % count ) )
1951 return true;
1952
1953 VECTOR2I oa = outline.CPoint( j );
1954 VECTOR2I ob = outline.CPoint( ( j + 1 ) % count );
1955 SEG other( oa, ob );
1956
1957 // Skip segments that share start/end points. This is the case for
1958 // fractured segments
1959 if( oa == a && ob == b )
1960 return true;
1961
1962 if( oa == b && ob == a )
1963 return true;
1964
1965 if( seg.ApproxCollinear( other, 10 ) && isExteriorWaist( seg, other ) )
1966 {
1967 segA = i;
1968 segB = j;
1969 found = true;
1970 return false;
1971 }
1972
1973 return true;
1974 };
1975
1976 rtree.Search( min, max, visitor );
1977 }
1978
1979 if( !found )
1980 break;
1981
1982 int a0 = segA;
1983 int a1 = ( segA + 1 ) % outline.PointCount();
1984 int b0 = segB;
1985 int b1 = ( segB + 1 ) % outline.PointCount();
1986
1987 SHAPE_LINE_CHAIN lc1;
1988 int idx = a1;
1989 lc1.Append( outline.CPoint( idx ) );
1990
1991 while( idx != b0 )
1992 {
1993 idx = ( idx + 1 ) % outline.PointCount();
1994 lc1.Append( outline.CPoint( idx ) );
1995 }
1996
1997 lc1.SetClosed( true );
1998
1999 SHAPE_LINE_CHAIN lc2;
2000 idx = b1;
2001 lc2.Append( outline.CPoint( idx ) );
2002
2003 while( idx != a0 )
2004 {
2005 idx = ( idx + 1 ) % outline.PointCount();
2006 lc2.Append( outline.CPoint( idx ) );
2007 }
2008
2009 lc2.SetClosed( true );
2010
2011 m_polys[polyIdx][0] = std::move( lc1 );
2012
2013 POLYGON np;
2014 np.push_back( std::move( lc2 ) );
2015 m_polys.push_back( std::move( np ) );
2016
2017 changed = true;
2018 }
2019 }
2020}
2021
2022
2024{
2025 for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
2026 {
2027 bool changed = true;
2028
2029 while( changed )
2030 {
2031 changed = false;
2032
2033 SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
2034 const int count = outline.PointCount();
2035
2036 if( count < 4 )
2037 break;
2038
2039 int insertSegIdx = -1;
2040 int insertVertIdx = -1;
2041
2042 // For small polygons, direct O(n²) search is faster than R-tree overhead
2043 constexpr int RTREE_THRESHOLD = 32;
2044
2045 if( count < RTREE_THRESHOLD )
2046 {
2047 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2048 {
2049 const VECTOR2I& pt = outline.CPoint( vertIdx );
2050 const int prevSeg = ( vertIdx + count - 1 ) % count;
2051
2052 for( int segIdx = 0; segIdx < count; ++segIdx )
2053 {
2054 // Skip adjacent segments
2055 if( segIdx == prevSeg || segIdx == vertIdx )
2056 continue;
2057
2058 const VECTOR2I& a = outline.CPoint( segIdx );
2059 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2060
2061 // SquaredDistance returns 0 only when pt lies exactly on the
2062 // segment. Clipper2 rounds corridor-cut vertices to integer
2063 // coordinates; they can land within 1nm of an endpoint but are
2064 // not true pinch points.
2065 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0 )
2066 {
2067 insertSegIdx = segIdx;
2068 insertVertIdx = vertIdx;
2069 break;
2070 }
2071 }
2072 }
2073 }
2074 else
2075 {
2077
2078 for( int i = 0; i < count; ++i )
2079 {
2080 const VECTOR2I& a = outline.CPoint( i );
2081 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
2082 int bmin[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
2083 int bmax[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
2084 rtree.Insert( bmin, bmax, i );
2085 }
2086
2087 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2088 {
2089 const VECTOR2I& pt = outline.CPoint( vertIdx );
2090 const int prevSeg = ( vertIdx + count - 1 ) % count;
2091 int bmin[2] = { pt.x, pt.y };
2092 int bmax[2] = { pt.x, pt.y };
2093
2094 auto pinchVisitor =
2095 [&]( const intptr_t& segIdx ) -> bool
2096 {
2097 if( segIdx == prevSeg || segIdx == vertIdx )
2098 return true;
2099
2100 const VECTOR2I& a = outline.CPoint( segIdx );
2101 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2102
2103 // SquaredDistance returns 0 only when pt lies exactly on the
2104 // segment. Clipper2 rounds corridor-cut vertices to integer
2105 // coordinates; they can land within 1nm of an endpoint but
2106 // are not true pinch points.
2107 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0 )
2108 {
2109 insertSegIdx = segIdx;
2110 insertVertIdx = vertIdx;
2111 return false;
2112 }
2113
2114 return true;
2115 };
2116
2117 rtree.Search( bmin, bmax, pinchVisitor );
2118 }
2119 }
2120
2121 if( insertSegIdx < 0 )
2122 break;
2123
2124 // Split the polygon at the pinch point into two separate polygons.
2125 // Polygon 1: vertices from (insertSegIdx+1) to insertVertIdx
2126 // Polygon 2: vertices from insertVertIdx to insertSegIdx
2127
2128 const int splitStart1 = ( insertSegIdx + 1 ) % count;
2129
2130 // Calculate sizes for each polygon
2131 int size1, size2;
2132
2133 if( insertVertIdx >= splitStart1 )
2134 size1 = insertVertIdx - splitStart1 + 1;
2135 else
2136 size1 = count - splitStart1 + insertVertIdx + 1;
2137
2138 if( insertSegIdx >= insertVertIdx )
2139 size2 = insertSegIdx - insertVertIdx + 1;
2140 else
2141 size2 = count - insertVertIdx + insertSegIdx + 1;
2142
2143 if( size1 < 3 || size2 < 3 )
2144 break;
2145
2146 SHAPE_LINE_CHAIN poly1;
2147 SHAPE_LINE_CHAIN poly2;
2148 poly1.ReservePoints( size1 );
2149 poly2.ReservePoints( size2 );
2150
2151 int idx = splitStart1;
2152
2153 for( int i = 0; i < size1; ++i )
2154 {
2155 poly1.Append( outline.CPoint( idx ) );
2156 idx = ( idx + 1 ) % count;
2157 }
2158
2159 poly1.SetClosed( true );
2160
2161 idx = insertVertIdx;
2162
2163 for( int i = 0; i < size2; ++i )
2164 {
2165 poly2.Append( outline.CPoint( idx ) );
2166 idx = ( idx + 1 ) % count;
2167 }
2168
2169 poly2.SetClosed( true );
2170
2171 m_polys[polyIdx][0] = std::move( poly1 );
2172
2173 POLYGON np;
2174 np.push_back( std::move( poly2 ) );
2175 m_polys.push_back( std::move( np ) );
2176
2177 changed = true;
2178 }
2179 }
2180}
2181
2182
2184{
2186
2188
2189 booleanOp( Clipper2Lib::ClipType::Union, empty );
2190}
2191
2192
2194{
2195 for( POLYGON& paths : m_polys )
2196 {
2197 for( SHAPE_LINE_CHAIN& path : paths )
2198 {
2199 path.Simplify( aTolerance );
2200 }
2201 }
2202}
2203
2204
2206{
2207 // We are expecting only one main outline, but this main outline can have holes
2208 // if holes: combine holes and remove them from the main outline.
2209 // Note also we are usingin polygon
2210 // calculations, but it is not mandatory. It is used mainly
2211 // because there is usually only very few vertices in area outlines
2212 SHAPE_POLY_SET::POLYGON& outline = Polygon( 0 );
2213 SHAPE_POLY_SET holesBuffer;
2214
2215 // Move holes stored in outline to holesBuffer:
2216 // The first SHAPE_LINE_CHAIN is the main outline, others are holes
2217 while( outline.size() > 1 )
2218 {
2219 holesBuffer.AddOutline( outline.back() );
2220 outline.pop_back();
2221 }
2222
2223 Simplify();
2224
2225 // If any hole, subtract it to main outline
2226 if( holesBuffer.OutlineCount() )
2227 {
2228 holesBuffer.Simplify();
2229 BooleanSubtract( holesBuffer );
2230 }
2231
2232 // In degenerate cases, simplify might return no outlines
2233 if( OutlineCount() > 0 )
2235
2236 return OutlineCount();
2237}
2238
2239
2240const std::string SHAPE_POLY_SET::Format( bool aCplusPlus ) const
2241{
2242 std::stringstream ss;
2243
2244 ss << "SHAPE_LINE_CHAIN poly; \n";
2245
2246 for( unsigned i = 0; i < m_polys.size(); i++ )
2247 {
2248 for( unsigned j = 0; j < m_polys[i].size(); j++ )
2249 {
2250
2251 ss << "{ auto tmp = " << m_polys[i][j].Format() << ";\n";
2252
2253 SHAPE_POLY_SET poly;
2254
2255 if( j == 0 )
2256 {
2257 ss << " poly.AddOutline(tmp); } \n";
2258 }
2259 else
2260 {
2261 ss << " poly.AddHole(tmp); } \n";
2262 }
2263
2264 }
2265 }
2266
2267 return ss.str();
2268}
2269
2270
2271bool SHAPE_POLY_SET::Parse( std::stringstream& aStream )
2272{
2273 std::string tmp;
2274
2275 aStream >> tmp;
2276
2277 if( tmp != "polyset" )
2278 return false;
2279
2280 aStream >> tmp;
2281
2282 int n_polys = atoi( tmp.c_str() );
2283
2284 if( n_polys < 0 )
2285 return false;
2286
2287 for( int i = 0; i < n_polys; i++ )
2288 {
2289 POLYGON paths;
2290
2291 aStream >> tmp;
2292
2293 if( tmp != "poly" )
2294 return false;
2295
2296 aStream >> tmp;
2297 int n_outlines = atoi( tmp.c_str() );
2298
2299 if( n_outlines < 0 )
2300 return false;
2301
2302 for( int j = 0; j < n_outlines; j++ )
2303 {
2304 SHAPE_LINE_CHAIN outline;
2305
2306 outline.SetClosed( true );
2307
2308 aStream >> tmp;
2309 int n_vertices = atoi( tmp.c_str() );
2310
2311 for( int v = 0; v < n_vertices; v++ )
2312 {
2313 VECTOR2I p;
2314
2315 aStream >> tmp; p.x = atoi( tmp.c_str() );
2316 aStream >> tmp; p.y = atoi( tmp.c_str() );
2317 outline.Append( p );
2318 }
2319
2320 paths.push_back( std::move( outline ) );
2321 }
2322
2323 m_polys.push_back( std::move( paths ) );
2324 }
2325
2326 return true;
2327}
2328
2329
2330const BOX2I SHAPE_POLY_SET::BBox( int aClearance ) const
2331{
2332 BOX2I bb;
2333
2334 for( unsigned i = 0; i < m_polys.size(); i++ )
2335 {
2336 if( i == 0 )
2337 bb = m_polys[i][0].BBox();
2338 else
2339 bb.Merge( m_polys[i][0].BBox() );
2340 }
2341
2342 bb.Inflate( aClearance );
2343 return bb;
2344}
2345
2346
2348{
2349 BOX2I bb;
2350
2351 for( unsigned i = 0; i < m_polys.size(); i++ )
2352 {
2353 if( i == 0 )
2354 bb = *m_polys[i][0].GetCachedBBox();
2355 else
2356 bb.Merge( *m_polys[i][0].GetCachedBBox() );
2357 }
2358
2359 return bb;
2360}
2361
2362
2363bool SHAPE_POLY_SET::PointOnEdge( const VECTOR2I& aP, int aAccuracy ) const
2364{
2365 // Iterate through all the polygons in the set
2366 for( const POLYGON& polygon : m_polys )
2367 {
2368 // Iterate through all the line chains in the polygon
2369 for( const SHAPE_LINE_CHAIN& lineChain : polygon )
2370 {
2371 if( lineChain.PointOnEdge( aP, aAccuracy ) )
2372 return true;
2373 }
2374 }
2375
2376 return false;
2377}
2378
2379
2380bool SHAPE_POLY_SET::Collide( const SEG& aSeg, int aClearance, int* aActual,
2381 VECTOR2I* aLocation ) const
2382{
2383 VECTOR2I nearest;
2384 ecoord dist_sq = SquaredDistanceToSeg( aSeg, aLocation ? &nearest : nullptr );
2385
2386 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2387 {
2388 if( aLocation )
2389 *aLocation = nearest;
2390
2391 if( aActual )
2392 *aActual = sqrt( dist_sq );
2393
2394 return true;
2395 }
2396
2397 return false;
2398}
2399
2400
2401bool SHAPE_POLY_SET::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
2402 VECTOR2I* aLocation ) const
2403{
2404 if( IsEmpty() || VertexCount() == 0 )
2405 return false;
2406
2407 VECTOR2I nearest;
2408 ecoord dist_sq = SquaredDistance( aP, false, aLocation ? &nearest : nullptr );
2409
2410 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2411 {
2412 if( aLocation )
2413 *aLocation = nearest;
2414
2415 if( aActual )
2416 *aActual = sqrt( dist_sq );
2417
2418 return true;
2419 }
2420
2421 return false;
2422}
2423
2424
2425bool SHAPE_POLY_SET::Collide( const SHAPE* aShape, int aClearance, int* aActual,
2426 VECTOR2I* aLocation ) const
2427{
2428 // A couple of simple cases are worth trying before we fall back on triangulation.
2429
2430 if( aShape->Type() == SH_SEGMENT )
2431 {
2432 const SHAPE_SEGMENT* segment = static_cast<const SHAPE_SEGMENT*>( aShape );
2433 int extra = segment->GetWidth() / 2;
2434
2435 if( Collide( segment->GetSeg(), aClearance + extra, aActual, aLocation ) )
2436 {
2437 if( aActual )
2438 *aActual = std::max( 0, *aActual - extra );
2439
2440 return true;
2441 }
2442
2443 return false;
2444 }
2445
2446 if( aShape->Type() == SH_CIRCLE )
2447 {
2448 const SHAPE_CIRCLE* circle = static_cast<const SHAPE_CIRCLE*>( aShape );
2449 int extra = circle->GetRadius();
2450
2451 if( Collide( circle->GetCenter(), aClearance + extra, aActual, aLocation ) )
2452 {
2453 if( aActual )
2454 *aActual = std::max( 0, *aActual - extra );
2455
2456 return true;
2457 }
2458
2459 return false;
2460 }
2461
2462 const_cast<SHAPE_POLY_SET*>( this )->CacheTriangulation();
2463
2464 int actual = INT_MAX;
2466
2467 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
2468 {
2469 for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
2470 {
2471 if( aActual || aLocation )
2472 {
2473 int triActual;
2474 VECTOR2I triLocation;
2475
2476 if( aShape->Collide( &tri, aClearance, &triActual, &triLocation ) )
2477 {
2478 if( triActual < actual )
2479 {
2480 actual = triActual;
2481 location = triLocation;
2482 }
2483 }
2484 }
2485 else // A much faster version of above
2486 {
2487 if( aShape->Collide( &tri, aClearance ) )
2488 return true;
2489 }
2490 }
2491 }
2492
2493 if( actual < INT_MAX )
2494 {
2495 if( aActual )
2496 *aActual = std::max( 0, actual );
2497
2498 if( aLocation )
2499 *aLocation = location;
2500
2501 return true;
2502 }
2503
2504 return false;
2505}
2506
2507
2509{
2510 m_polys.clear();
2511 m_triangulatedPolys.clear();
2512 m_triangulationValid = false;
2513}
2514
2515
2516void SHAPE_POLY_SET::RemoveContour( int aContourIdx, int aPolygonIdx )
2517{
2518 // Default polygon is the last one
2519 if( aPolygonIdx < 0 )
2520 aPolygonIdx += m_polys.size();
2521
2522 m_polys[aPolygonIdx].erase( m_polys[aPolygonIdx].begin() + aContourIdx );
2523}
2524
2525
2526void SHAPE_POLY_SET::RemoveOutline( int aOutlineIdx )
2527{
2528 m_polys.erase( m_polys.begin() + aOutlineIdx );
2529}
2530
2531
2533{
2534 int removed = 0;
2535
2536 ITERATOR iterator = IterateWithHoles();
2537
2538 VECTOR2I contourStart = *iterator;
2539 VECTOR2I segmentStart, segmentEnd;
2540
2541 VERTEX_INDEX indexStart;
2542 std::vector<VERTEX_INDEX> indices_to_remove;
2543
2544 while( iterator )
2545 {
2546 // Obtain first point and its index
2547 segmentStart = *iterator;
2548 indexStart = iterator.GetIndex();
2549
2550 // Obtain last point
2551 if( iterator.IsEndContour() )
2552 {
2553 segmentEnd = contourStart;
2554
2555 // Advance
2556 iterator++;
2557
2558 // If we have rolled into the next contour, remember its position
2559 // segmentStart and segmentEnd remain valid for comparison here
2560 if( iterator )
2561 contourStart = *iterator;
2562 }
2563 else
2564 {
2565 // Advance
2566 iterator++;
2567
2568 // If we have reached the end of the SHAPE_POLY_SET, something is broken here
2569 wxCHECK_MSG( iterator, removed, wxT( "Invalid polygon. Reached end without noticing. Please report this error" ) );
2570
2571 segmentEnd = *iterator;
2572 }
2573
2574 // Remove segment start if both points are equal
2575 if( segmentStart == segmentEnd )
2576 {
2577 indices_to_remove.push_back( indexStart );
2578 removed++;
2579 }
2580 }
2581
2582 // Proceed in reverse direction to remove the vertices because they are stored as absolute indices in a vector
2583 // Removing in reverse order preserves the remaining index values
2584 for( auto it = indices_to_remove.rbegin(); it != indices_to_remove.rend(); ++it )
2585 RemoveVertex( *it );
2586
2587 return removed;
2588}
2589
2590
2592{
2593 m_polys.erase( m_polys.begin() + aIdx );
2594}
2595
2596
2598{
2599 m_polys.erase( m_polys.begin() + aIdx );
2600
2602 {
2603 for( int ii = m_triangulatedPolys.size() - 1; ii >= 0; --ii )
2604 {
2605 std::unique_ptr<TRIANGULATED_POLYGON>& triangleSet = m_triangulatedPolys[ii];
2606
2607 if( triangleSet->GetSourceOutlineIndex() == aIdx )
2608 m_triangulatedPolys.erase( m_triangulatedPolys.begin() + ii );
2609 else if( triangleSet->GetSourceOutlineIndex() > aIdx )
2610 triangleSet->SetSourceOutlineIndex( triangleSet->GetSourceOutlineIndex() - 1 );
2611 }
2612
2613 if( aUpdateHash )
2614 {
2615 m_hash = checksum();
2616 m_hashValid = true;
2617 }
2618 }
2619}
2620
2621
2627
2628
2630{
2631 m_polys.insert( m_polys.end(), aSet.m_polys.begin(), aSet.m_polys.end() );
2632}
2633
2634
2635void SHAPE_POLY_SET::Append( const VECTOR2I& aP, int aOutline, int aHole )
2636{
2637 Append( aP.x, aP.y, aOutline, aHole );
2638}
2639
2640
2642 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
2643 int aClearance ) const
2644{
2645 // Shows whether there was a collision
2646 bool collision = false;
2647
2648 // Difference vector between each vertex and aPoint.
2650 ecoord distance_squared;
2651 ecoord clearance_squared = SEG::Square( aClearance );
2652
2653 for( CONST_ITERATOR iterator = CIterateWithHoles(); iterator; iterator++ )
2654 {
2655 // Get the difference vector between current vertex and aPoint
2656 delta = *iterator - aPoint;
2657
2658 // Compute distance
2659 distance_squared = delta.SquaredEuclideanNorm();
2660
2661 // Check for collisions
2662 if( distance_squared <= clearance_squared )
2663 {
2664 if( !aClosestVertex )
2665 return true;
2666
2667 collision = true;
2668
2669 // Update clearance to look for closer vertices
2670 clearance_squared = distance_squared;
2671
2672 // Store the indices that identify the vertex
2673 *aClosestVertex = iterator.GetIndex();
2674 }
2675 }
2676
2677 return collision;
2678}
2679
2680
2682 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
2683 int aClearance ) const
2684{
2685 // Shows whether there was a collision
2686 bool collision = false;
2687 ecoord clearance_squared = SEG::Square( aClearance );
2688
2689 for( CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
2690 {
2691 const SEG currentSegment = *iterator;
2692 ecoord distance_squared = currentSegment.SquaredDistance( aPoint );
2693
2694 // Check for collisions
2695 if( distance_squared <= clearance_squared )
2696 {
2697 if( !aClosestVertex )
2698 return true;
2699
2700 collision = true;
2701
2702 // Update clearance to look for closer edges
2703 clearance_squared = distance_squared;
2704
2705 // Store the indices that identify the vertex
2706 *aClosestVertex = iterator.GetIndex();
2707 }
2708 }
2709
2710 return collision;
2711}
2712
2713
2715{
2716 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
2717 {
2718 COutline( polygonIdx ).GenerateBBoxCache();
2719
2720 for( int holeIdx = 0; holeIdx < HoleCount( polygonIdx ); holeIdx++ )
2721 CHole( polygonIdx, holeIdx ).GenerateBBoxCache();
2722 }
2723}
2724
2725
2726bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
2727 bool aUseBBoxCaches ) const
2728{
2729 if( m_polys.empty() )
2730 return false;
2731
2732 // If there is a polygon specified, check the condition against that polygon
2733 if( aSubpolyIndex >= 0 )
2734 return containsSingle( aP, aSubpolyIndex, aAccuracy, aUseBBoxCaches );
2735
2736 // In any other case, check it against all polygons in the set
2737 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
2738 {
2739 if( containsSingle( aP, polygonIdx, aAccuracy, aUseBBoxCaches ) )
2740 return true;
2741 }
2742
2743 return false;
2744}
2745
2746
2747void SHAPE_POLY_SET::RemoveVertex( int aGlobalIndex )
2748{
2750
2751 // Assure the to be removed vertex exists, abort otherwise
2752 if( GetRelativeIndices( aGlobalIndex, &index ) )
2754 else
2755 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
2756}
2757
2758
2760{
2761 m_polys[aIndex.m_polygon][aIndex.m_contour].Remove( aIndex.m_vertex );
2762}
2763
2764
2765void SHAPE_POLY_SET::SetVertex( int aGlobalIndex, const VECTOR2I& aPos )
2766{
2768
2769 if( GetRelativeIndices( aGlobalIndex, &index ) )
2770 SetVertex( index, aPos );
2771 else
2772 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
2773}
2774
2775
2776void SHAPE_POLY_SET::SetVertex( const VERTEX_INDEX& aIndex, const VECTOR2I& aPos )
2777{
2778 m_polys[aIndex.m_polygon][aIndex.m_contour].SetPoint( aIndex.m_vertex, aPos );
2779}
2780
2781
2782bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
2783 bool aUseBBoxCaches ) const
2784{
2785 // Check that the point is inside the outline
2786 if( m_polys[aSubpolyIndex][0].PointInside( aP, aAccuracy ) )
2787 {
2788 // Check that the point is not in any of the holes
2789 for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
2790 {
2791 const SHAPE_LINE_CHAIN& hole = CHole( aSubpolyIndex, holeIdx );
2792
2793 // If the point is inside a hole it is outside of the polygon. Do not use aAccuracy
2794 // here as it's meaning would be inverted.
2795 if( hole.PointInside( aP, 1, aUseBBoxCaches ) )
2796 return false;
2797 }
2798
2799 return true;
2800 }
2801
2802 return false;
2803}
2804
2805
2806void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
2807{
2808 for( POLYGON& poly : m_polys )
2809 {
2810 for( SHAPE_LINE_CHAIN& path : poly )
2811 path.Move( aVector );
2812 }
2813
2814 for( std::unique_ptr<TRIANGULATED_POLYGON>& tri : m_triangulatedPolys )
2815 tri->Move( aVector );
2816
2817 m_hash = checksum();
2818 m_hashValid = true;
2819}
2820
2821
2822void SHAPE_POLY_SET::Mirror( const VECTOR2I& aRef, FLIP_DIRECTION aFlipDirection )
2823{
2824 for( POLYGON& poly : m_polys )
2825 {
2826 for( SHAPE_LINE_CHAIN& path : poly )
2827 path.Mirror( aRef, aFlipDirection );
2828 }
2829
2832}
2833
2834
2835void SHAPE_POLY_SET::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
2836{
2837 for( POLYGON& poly : m_polys )
2838 {
2839 for( SHAPE_LINE_CHAIN& path : poly )
2840 path.Rotate( aAngle, aCenter );
2841 }
2842
2843 // Don't re-cache if the triangulation is already invalid
2846}
2847
2848
2850{
2851 int c = 0;
2852
2853 for( const POLYGON& poly : m_polys )
2854 {
2855 for( const SHAPE_LINE_CHAIN& path : poly )
2856 c += path.PointCount();
2857 }
2858
2859 return c;
2860}
2861
2862
2863SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex )
2864{
2865 return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0 );
2866}
2867
2868
2869SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax,
2870 int aIndex )
2871{
2872 return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax );
2873}
2874
2875
2877 VECTOR2I* aNearest ) const
2878{
2879 // We calculate the min dist between the segment and each outline segment. However, if the
2880 // segment to test is inside the outline, and does not cross any edge, it can be seen outside
2881 // the polygon. Therefore test if a segment end is inside (testing only one end is enough).
2882 // Use an accuracy of "1" to say that we don't care if it's exactly on the edge or not.
2883 if( containsSingle( aPoint, aPolygonIndex, 1 ) )
2884 {
2885 if( aNearest )
2886 *aNearest = aPoint;
2887
2888 return 0;
2889 }
2890
2891 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
2892
2893 SEG::ecoord minDistance = (*iterator).SquaredDistance( aPoint );
2894
2895 if( aNearest )
2896 *aNearest = ( *iterator ).NearestPoint( aPoint );
2897
2898 for( iterator++; iterator && minDistance > 0; iterator++ )
2899 {
2900 SEG::ecoord currentDistance = (*iterator).SquaredDistance( aPoint );
2901
2902 if( currentDistance < minDistance )
2903 {
2904 if( aNearest )
2905 *aNearest = (*iterator).NearestPoint( aPoint );
2906
2907 minDistance = currentDistance;
2908 }
2909 }
2910
2911 return minDistance;
2912}
2913
2914
2916 VECTOR2I* aNearest ) const
2917{
2918 // Check if the segment is fully-contained. If so, its midpoint is a good-enough nearest point.
2919 if( containsSingle( aSegment.A, aPolygonIndex, 1 ) &&
2920 containsSingle( aSegment.B, aPolygonIndex, 1 ) )
2921 {
2922 if( aNearest )
2923 *aNearest = ( aSegment.A + aSegment.B ) / 2;
2924
2925 return 0;
2926 }
2927
2928 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
2929 SEG::ecoord minDistance = (*iterator).SquaredDistance( aSegment );
2930
2931 if( aNearest )
2932 *aNearest = ( *iterator ).NearestPoint( aSegment );
2933
2934 for( iterator++; iterator && minDistance > 0; iterator++ )
2935 {
2936 SEG::ecoord currentDistance = (*iterator).SquaredDistance( aSegment );
2937
2938 if( currentDistance < minDistance )
2939 {
2940 if( aNearest )
2941 *aNearest = (*iterator).NearestPoint( aSegment );
2942
2943 minDistance = currentDistance;
2944 }
2945 }
2946
2947 // Return the maximum of minDistance and zero
2948 return minDistance < 0 ? 0 : minDistance;
2949}
2950
2951
2952SEG::ecoord SHAPE_POLY_SET::SquaredDistance( const VECTOR2I& aPoint, bool aOutlineOnly,
2953 VECTOR2I* aNearest ) const
2954{
2955 wxASSERT_MSG( !aOutlineOnly, wxT( "Warning: SHAPE_POLY_SET::SquaredDistance does not yet "
2956 "support aOutlineOnly==true" ) );
2957
2958 SEG::ecoord currentDistance_sq;
2959 SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
2960 VECTOR2I nearest;
2961
2962 // Iterate through all the polygons and get the minimum distance.
2963 for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
2964 {
2965 currentDistance_sq = SquaredDistanceToPolygon( aPoint, polygonIdx,
2966 aNearest ? &nearest : nullptr );
2967
2968 if( currentDistance_sq < minDistance_sq )
2969 {
2970 if( aNearest )
2971 *aNearest = nearest;
2972
2973 minDistance_sq = currentDistance_sq;
2974 }
2975 }
2976
2977 return minDistance_sq;
2978}
2979
2980
2982{
2983 SEG::ecoord currentDistance_sq;
2984 SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
2985 VECTOR2I nearest;
2986
2987 // Iterate through all the polygons and get the minimum distance.
2988 for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
2989 {
2990 currentDistance_sq = SquaredDistanceToPolygon( aSegment, polygonIdx,
2991 aNearest ? &nearest : nullptr );
2992
2993 if( currentDistance_sq < minDistance_sq )
2994 {
2995 if( aNearest )
2996 *aNearest = nearest;
2997
2998 minDistance_sq = currentDistance_sq;
2999 }
3000 }
3001
3002 return minDistance_sq;
3003}
3004
3005
3007{
3009
3010 // Get the polygon and contour where the vertex is. If the vertex does not exist, return false
3011 if( !GetRelativeIndices( aGlobalIdx, &index ) )
3012 return false;
3013
3014 // The contour is a hole if its index is greater than zero
3015 return index.m_contour > 0;
3016}
3017
3018
3020{
3021 SHAPE_POLY_SET chamfered;
3022
3023 for( unsigned int idx = 0; idx < m_polys.size(); idx++ )
3024 chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx ) );
3025
3026 return chamfered;
3027}
3028
3029
3030SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax )
3031{
3032 SHAPE_POLY_SET filleted;
3033
3034 for( size_t idx = 0; idx < m_polys.size(); idx++ )
3035 filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx ) );
3036
3037 return filleted;
3038}
3039
3040
3042{
3043 SHAPE::operator=( aOther );
3044 m_polys = aOther.m_polys;
3045
3046 m_triangulatedPolys.clear();
3047
3048 if( aOther.IsTriangulationUpToDate() )
3049 {
3050 m_triangulatedPolys.reserve( aOther.TriangulatedPolyCount() );
3051
3052 for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
3053 {
3054 const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
3055 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
3056 }
3057
3058 m_hash = aOther.m_hash;
3059 m_hashValid.store( aOther.m_hashValid.load() );
3061 }
3062 else
3063 {
3064 m_hash.Clear();
3065 m_hashValid = false;
3066 m_triangulationValid = false;
3067 }
3068
3069 m_failedHash = aOther.m_failedHash;
3070 m_failedHashValid.store( aOther.m_failedHashValid.load() );
3071
3072 return *this;
3073}
3074
3075
3077{
3078 if( !m_hashValid )
3079 return checksum();
3080
3081 return m_hash;
3082}
3083
3084
3086{
3088 return false;
3089
3090 if( !m_hashValid )
3091 return false;
3092
3093 HASH_128 hash = checksum();
3094
3095 return hash == m_hash;
3096}
3097
3098
3100 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* aHintData,
3101 const TASK_SUBMITTER& aSubmitter )
3102{
3103 // if( m_triangulationValid && m_hashValid && m_hash == checksum() )
3104 // return;
3105 // if( m_failedHashValid && m_failedHash == checksum() )
3106 // return;
3107
3108 std::unique_lock<std::mutex> lock( m_triangulationMutex );
3109
3111 return;
3113 return;
3114
3115 // Invalidate, in case anything goes wrong below
3116 m_triangulationValid = false;
3117 m_hashValid = false;
3118 m_failedHashValid = false;
3119
3120 auto triangulate =
3121 []( SHAPE_POLY_SET& polySet, int forOutline,
3122 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>& dest,
3123 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* hintData,
3124 const TASK_SUBMITTER& taskSubmitter )
3125 {
3126 bool triangulationValid = false;
3127 int pass = 0;
3128 int index = 0;
3129
3130 if( hintData && hintData->size() != (unsigned) polySet.OutlineCount() )
3131 hintData = nullptr;
3132
3133 while( polySet.OutlineCount() > 0 )
3134 {
3135 if( !dest.empty() && dest.back()->GetTriangleCount() == 0 )
3136 dest.erase( dest.end() - 1 );
3137
3138 {
3139 const SHAPE_LINE_CHAIN& outline = polySet.Polygon( 0 ).front();
3140 TRIANGULATED_POLYGON partitionScratch( forOutline );
3141 POLYGON_TRIANGULATION partitioner( partitionScratch );
3142 size_t partitionLeaves = partitioner.suggestedPartitionLeafCount( outline );
3143
3144 if( partitionLeaves > 1 )
3145 {
3146 std::vector<SHAPE_LINE_CHAIN> partitions =
3147 partitioner.partitionPolygonBalanced( outline, partitionLeaves );
3148
3149 if( partitions.size() > 1 )
3150 {
3151 polySet.DeletePolygon( 0 );
3152
3153 if( taskSubmitter && partitions.size() > 2 )
3154 {
3155 size_t leafCount = partitions.size();
3156
3157 struct WorkStealState
3158 {
3159 std::unique_ptr<std::atomic<bool>[]> claimed;
3160 std::unique_ptr<std::atomic<bool>[]> done;
3161 std::unique_ptr<std::atomic<bool>[]> ok;
3162
3163 explicit WorkStealState( size_t n ) :
3164 claimed( new std::atomic<bool>[n] ),
3165 done( new std::atomic<bool>[n] ),
3166 ok( new std::atomic<bool>[n] )
3167 {
3168 for( size_t i = 0; i < n; ++i )
3169 {
3170 claimed[i].store( false );
3171 done[i].store( false );
3172 ok[i].store( false );
3173 }
3174 }
3175 };
3176
3177 auto state = std::make_shared<WorkStealState>( leafCount );
3178
3179 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>> results( leafCount );
3180
3181 for( size_t i = 0; i < leafCount; ++i )
3182 {
3183 results[i] = std::make_unique<TRIANGULATED_POLYGON>( forOutline );
3184 }
3185
3186 for( size_t i = 0; i < leafCount; ++i )
3187 {
3188 auto* triPoly = results[i].get();
3189 auto* leaf = &partitions[i];
3190
3191 taskSubmitter( [state, i, triPoly, leaf]()
3192 {
3193 if( state->claimed[i].exchange( true ) )
3194 return;
3195
3196 POLYGON_TRIANGULATION tess( *triPoly );
3197 state->ok[i].store( tess.TesselatePolygon( *leaf, nullptr ) );
3198 state->done[i].store( true, std::memory_order_release );
3199 } );
3200 }
3201
3202 for( size_t i = 0; i < leafCount; ++i )
3203 {
3204 if( state->claimed[i].exchange( true ) )
3205 continue;
3206
3207 POLYGON_TRIANGULATION tess( *results[i] );
3208 state->ok[i].store( tess.TesselatePolygon( partitions[i], nullptr ) );
3209 state->done[i].store( true, std::memory_order_release );
3210 }
3211
3212 for( size_t i = 0; i < leafCount; ++i )
3213 {
3214 while( !state->done[i].load( std::memory_order_acquire ) )
3215 {
3216 std::this_thread::yield();
3217 }
3218 }
3219
3220 bool allOk = true;
3221
3222 for( size_t i = 0; i < leafCount; ++i )
3223 allOk = allOk && state->ok[i].load();
3224
3225 if( allOk )
3226 {
3227 for( auto& r : results )
3228 {
3229 if( r->GetTriangleCount() > 0 )
3230 dest.push_back( std::move( r ) );
3231 }
3232
3233 triangulationValid = true;
3234 hintData = nullptr;
3235 continue;
3236 }
3237
3238 }
3239
3240 for( auto it = partitions.rbegin(); it != partitions.rend(); ++it )
3241 polySet.AddOutline( *it );
3242
3243 hintData = nullptr;
3244 continue;
3245 }
3246 }
3247 }
3248
3249 dest.push_back( std::make_unique<TRIANGULATED_POLYGON>( forOutline ) );
3250 POLYGON_TRIANGULATION tess( *dest.back() );
3251
3252 // If the tessellation fails, we re-fracture the polygon, which will
3253 // first simplify the system before fracturing and removing the holes
3254 // This may result in multiple, disjoint polygons.
3255 if( !tess.TesselatePolygon( polySet.Polygon( 0 ).front(),
3256 hintData ? hintData->at( index ).get() : nullptr ) )
3257 {
3258 ++pass;
3259
3260 if( pass == 1 )
3261 {
3263 }
3264 // In Clipper2, there is only one type of simplification
3265 else
3266 {
3267 break;
3268 }
3269
3270 triangulationValid = false;
3271 hintData = nullptr;
3272 continue;
3273 }
3274
3275 polySet.DeletePolygon( 0 );
3276 index++;
3277 triangulationValid = true;
3278 }
3279
3280 return triangulationValid;
3281 };
3282
3283 m_triangulatedPolys.clear();
3284
3285 const SHAPE_POLY_SET* srcSet = this;
3286 SHAPE_POLY_SET tmpSet;
3287
3288 if( ArcCount() > 0 || aSimplify )
3289 {
3290 tmpSet = SHAPE_POLY_SET( *this );
3291 tmpSet.ClearArcs();
3292
3293 if( aSimplify )
3294 tmpSet.Simplify();
3295
3296 srcSet = &tmpSet;
3297 }
3298
3299 bool directOk = true;
3300
3301 for( int ii = 0; ii < srcSet->OutlineCount() && directOk; ++ii )
3302 {
3303 const POLYGON& poly = srcSet->CPolygon( ii );
3304 size_t prevCount = m_triangulatedPolys.size();
3305
3306 if( poly.size() > 1 )
3307 {
3308 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3310
3311 if( tess.TesselatePolygon( poly, nullptr ) )
3312 continue;
3313
3314 // Hole bridging failed; fracture to merge holes into the outline
3315 m_triangulatedPolys.resize( prevCount );
3316
3317 SHAPE_POLY_SET flatSet( poly.front() );
3318
3319 for( size_t jj = 1; jj < poly.size(); ++jj )
3320 flatSet.AddHole( poly[jj] );
3321
3322 flatSet.Fracture();
3323 flatSet.splitSelfTouchingOutlines();
3324
3325 if( triangulate( flatSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3326 continue;
3327
3328 m_triangulatedPolys.resize( prevCount );
3329 directOk = false;
3330 continue;
3331 }
3332
3333 {
3334 TRIANGULATED_POLYGON partScratch( -1 );
3335 POLYGON_TRIANGULATION partChecker( partScratch );
3336
3337 if( partChecker.suggestedPartitionLeafCount( poly.front() ) > 1 )
3338 {
3339 SHAPE_POLY_SET partSet;
3340 partSet.AddOutline( poly.front() );
3341
3342 if( triangulate( partSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3343 {
3344 continue;
3345 }
3346
3347 m_triangulatedPolys.resize( prevCount );
3348 }
3349 }
3350
3351 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3353
3354 bool ok = tess.TesselatePolygon( poly.front(), nullptr );
3355
3356 // Self-touching outlines produce overlapping triangles; detect via area coverage
3357 if( ok )
3358 {
3359 double originalArea = std::abs( poly.front().Area() );
3360
3361 if( originalArea > 0.0 )
3362 {
3363 double triArea = 0.0;
3364
3365 for( const auto& tri : m_triangulatedPolys.back()->Triangles() )
3366 triArea += std::abs( tri.Area() );
3367
3368 double coverage = triArea / originalArea;
3369
3370 if( coverage > 1.01 || coverage < 0.99 )
3371 ok = false;
3372 }
3373 }
3374
3375 if( !ok )
3376 {
3377 m_triangulatedPolys.resize( prevCount );
3378
3379 SHAPE_POLY_SET splitSet;
3380 splitSet.AddOutline( poly[0] );
3381 splitSet.splitSelfTouchingOutlines();
3382
3383 bool splitOk = true;
3384
3385 for( int jj = 0; jj < splitSet.OutlineCount() && splitOk; ++jj )
3386 {
3387 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3388 POLYGON_TRIANGULATION splitTess( *m_triangulatedPolys.back() );
3389 splitOk = splitTess.TesselatePolygon( splitSet.CPolygon( jj ).front(), nullptr );
3390 }
3391
3392 if( !splitOk )
3393 directOk = false;
3394 }
3395 }
3396
3397 if( !m_triangulatedPolys.empty() && m_triangulatedPolys.back()->GetTriangleCount() == 0 )
3398 m_triangulatedPolys.pop_back();
3399
3400 if( directOk && !m_triangulatedPolys.empty() )
3401 {
3402 m_hash = checksum();
3403 m_hashValid = true;
3404 m_triangulationValid = true;
3405 }
3406 else
3407 {
3408 // Fracture each outline individually to preserve source outline indices
3409 m_triangulatedPolys.clear();
3410 bool fallbackOk = true;
3411
3412 for( int ii = 0; ii < srcSet->OutlineCount() && fallbackOk; ++ii )
3413 {
3414 const POLYGON& poly = srcSet->CPolygon( ii );
3415 SHAPE_POLY_SET flatSet( poly.front() );
3416
3417 for( size_t jj = 1; jj < poly.size(); ++jj )
3418 flatSet.AddHole( poly[jj] );
3419
3420 flatSet.ClearArcs();
3421 flatSet.Fracture();
3422 flatSet.splitSelfTouchingOutlines();
3423
3424 if( !triangulate( flatSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3425 fallbackOk = false;
3426 }
3427
3428 if( !fallbackOk )
3429 {
3430 // Last resort: flatten everything together (loses outline indices)
3431 m_triangulatedPolys.clear();
3432 SHAPE_POLY_SET fallbackSet( *this );
3433 fallbackSet.ClearArcs();
3434 fallbackSet.Fracture();
3435 fallbackSet.splitSelfTouchingOutlines();
3436
3437 if( !triangulate( fallbackSet, -1, m_triangulatedPolys, aHintData, aSubmitter ) )
3438 {
3439 wxLogTrace( TRIANGULATE_TRACE, "Failed to triangulate polygon" );
3440 }
3441 else
3442 {
3443 m_triangulationValid = true;
3444 }
3445 }
3446 else
3447 {
3448 m_triangulationValid = true;
3449 }
3450
3452 {
3453 m_hash = checksum();
3454 m_hashValid = true;
3455 }
3456 else
3457 {
3459 m_failedHashValid = true;
3460 }
3461 }
3462}
3463
3464
3465
3467{
3468 MMH3_HASH hash( 0x68AF835D ); // Arbitrary seed
3469
3470 hash.add( m_polys.size() );
3471
3472 for( const POLYGON& outline : m_polys )
3473 {
3474 hash.add( outline.size() );
3475
3476 for( const SHAPE_LINE_CHAIN& lc : outline )
3477 {
3478 hash.add( lc.PointCount() );
3479
3480 for( int i = 0; i < lc.PointCount(); i++ )
3481 {
3482 VECTOR2I pt = lc.CPoint( i );
3483
3484 hash.add( pt.x );
3485 hash.add( pt.y );
3486 }
3487 }
3488 }
3489
3490 return hash.digest();
3491}
3492
3493
3495{
3496 for( int i = 0; i < OutlineCount(); i++ )
3497 {
3498 if( hasTouchingHoles( CPolygon( i ) ) )
3499 return true;
3500 }
3501
3502 return false;
3503}
3504
3505
3507{
3508 std::set<long long> ptHashes;
3509
3510 for( const SHAPE_LINE_CHAIN& lc : aPoly )
3511 {
3512 for( const VECTOR2I& pt : lc.CPoints() )
3513 {
3514 const long long ptHash = (long long) pt.x << 32 | pt.y;
3515
3516 if( ptHashes.count( ptHash ) > 0 )
3517 return true;
3518
3519 ptHashes.insert( ptHash );
3520 }
3521 }
3522
3523 return false;
3524}
3525
3526
3531
3532
3534{
3535 size_t n = 0;
3536
3537 for( const std::unique_ptr<TRIANGULATED_POLYGON>& t : m_triangulatedPolys )
3538 n += t->GetTriangleCount();
3539
3540 return n;
3541}
3542
3543
3544void SHAPE_POLY_SET::GetIndexableSubshapes( std::vector<const SHAPE*>& aSubshapes ) const
3545{
3546 aSubshapes.reserve( GetIndexableSubshapeCount() );
3547
3548 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
3549 {
3550 for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
3551 aSubshapes.push_back( &tri );
3552 }
3553}
3554
3555
3557{
3558 BOX2I bbox( parent->m_vertices[a] );
3559 bbox.Merge( parent->m_vertices[b] );
3560 bbox.Merge( parent->m_vertices[c] );
3561
3562 if( aClearance != 0 )
3563 bbox.Inflate( aClearance );
3564
3565 return bbox;
3566}
3567
3568
3570{
3571 m_triangles.emplace_back( a, b, c, this );
3572}
3573
3574
3576{
3578 m_vertices = aOther.m_vertices;
3579 m_triangles = aOther.m_triangles;
3580
3581 for( TRI& tri : m_triangles )
3582 tri.parent = this;
3583}
3584
3585
3587{
3589 m_vertices = aOther.m_vertices;
3590 m_triangles = aOther.m_triangles;
3591
3592 for( TRI& tri : m_triangles )
3593 tri.parent = this;
3594
3595 return *this;
3596}
3597
3598
3600 m_sourceOutline( aSourceOutline )
3601{
3602}
3603
3604
3608
3609
3610void SHAPE_POLY_SET::Scale( double aScaleFactorX, double aScaleFactorY, const VECTOR2I& aCenter )
3611{
3612 for( POLYGON& poly : m_polys )
3613 {
3614 for( SHAPE_LINE_CHAIN& path : poly )
3615 {
3616 for( int i = 0; i < path.PointCount(); i++ )
3617 {
3618 VECTOR2I pt = path.CPoint( i );
3619 VECTOR2D vec;
3620 vec.x = ( pt.x - aCenter.x ) * aScaleFactorX;
3621 vec.y = ( pt.y - aCenter.y ) * aScaleFactorY;
3622 pt.x = KiROUND<double, int>( aCenter.x + vec.x );
3623 pt.y = KiROUND<double, int>( aCenter.y + vec.y );
3624 path.SetPoint( i, pt );
3625 }
3626 }
3627 }
3628
3631}
3632
3633
3634void
3635SHAPE_POLY_SET::BuildPolysetFromOrientedPaths( const std::vector<SHAPE_LINE_CHAIN>& aPaths,
3636 bool aEvenOdd )
3637{
3638 Clipper2Lib::Clipper64 clipper;
3639 Clipper2Lib::PolyTree64 tree;
3640 Clipper2Lib::Paths64 paths;
3641
3642 for( const SHAPE_LINE_CHAIN& path : aPaths )
3643 {
3644 Clipper2Lib::Path64 lc;
3645 lc.reserve( path.PointCount() );
3646
3647 for( int i = 0; i < path.PointCount(); i++ )
3648 lc.emplace_back( path.CPoint( i ).x, path.CPoint( i ).y );
3649
3650 paths.push_back( std::move( lc ) );
3651 }
3652
3653 clipper.AddSubject( paths );
3654 clipper.Execute( Clipper2Lib::ClipType::Union, aEvenOdd ? Clipper2Lib::FillRule::EvenOdd
3655 : Clipper2Lib::FillRule::NonZero, tree );
3656
3657 std::vector<CLIPPER_Z_VALUE> zValues;
3658 std::vector<SHAPE_ARC> arcBuffer;
3659
3660 importTree( tree, zValues, arcBuffer );
3661 tree.Clear(); // Free used memory (not done in dtor)
3662}
3663
3664
3665bool SHAPE_POLY_SET::PointInside( const VECTOR2I& aPt, int aAccuracy, bool aUseBBoxCache ) const
3666{
3667 for( int idx = 0; idx < OutlineCount(); idx++ )
3668 {
3669 if( COutline( idx ).PointInside( aPt, aAccuracy, aUseBBoxCache ) )
3670 return true;
3671 }
3672
3673 return false;
3674}
3675
3676
3677const std::vector<SEG> SHAPE_POLY_SET::GenerateHatchLines( const std::vector<double>& aSlopes,
3678 int aSpacing, int aLineLength ) const
3679{
3680 std::vector<SEG> hatchLines;
3681
3682 if( OutlineCount() == 0 || TotalVertices() == 0 )
3683 return hatchLines;
3684
3685 // define range for hatch lines
3686 int min_x = CVertex( 0 ).x;
3687 int max_x = CVertex( 0 ).x;
3688 int min_y = CVertex( 0 ).y;
3689 int max_y = CVertex( 0 ).y;
3690
3691 for( auto iterator = CIterateWithHoles(); iterator; iterator++ )
3692 {
3693 if( iterator->x < min_x )
3694 min_x = iterator->x;
3695
3696 if( iterator->x > max_x )
3697 max_x = iterator->x;
3698
3699 if( iterator->y < min_y )
3700 min_y = iterator->y;
3701
3702 if( iterator->y > max_y )
3703 max_y = iterator->y;
3704 }
3705
3706 auto sortEndsByDescendingX =
3707 []( const VECTOR2I& ref, const VECTOR2I& tst )
3708 {
3709 return tst.x < ref.x;
3710 };
3711
3712 for( double slope : aSlopes )
3713 {
3714 int64_t max_a, min_a;
3715
3716 if( slope > 0 )
3717 {
3718 max_a = KiROUND<double, int64_t>( max_y - slope * min_x );
3719 min_a = KiROUND<double, int64_t>( min_y - slope * max_x );
3720 }
3721 else
3722 {
3723 max_a = KiROUND<double, int64_t>( max_y - slope * max_x );
3724 min_a = KiROUND<double, int64_t>( min_y - slope * min_x );
3725 }
3726
3727 min_a = ( min_a / aSpacing ) * aSpacing;
3728
3729 // loop through hatch lines
3730 std::vector<VECTOR2I> pointbuffer;
3731 pointbuffer.reserve( 256 );
3732
3733 for( int64_t a = min_a; a < max_a; a += aSpacing )
3734 {
3735 pointbuffer.clear();
3736
3737 // Iterate through all vertices
3738 for( auto iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
3739 {
3740 const SEG seg = *iterator;
3741 VECTOR2I pt;
3742
3743 if( seg.IntersectsLine( slope, a, pt ) )
3744 {
3745 // If the intersection point is outside the polygon, skip it
3746 if( pt.x < min_x || pt.x > max_x || pt.y < min_y || pt.y > max_y )
3747 continue;
3748
3749 // Add the intersection point to the buffer
3750 pointbuffer.emplace_back( KiROUND( pt.x ), KiROUND( pt.y ) );
3751 }
3752 }
3753
3754 // sort points in order of descending x (if more than 2) to
3755 // ensure the starting point and the ending point of the same segment
3756 // are stored one just after the other.
3757 if( pointbuffer.size() > 2 )
3758 sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX );
3759
3760 // creates lines or short segments inside the complex polygon
3761 for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip++ )
3762 {
3763 const VECTOR2I& p1 = pointbuffer[ip];
3764 const VECTOR2I& p2 = pointbuffer[ip + 1];
3765
3766 // Avoid duplicated intersections or segments
3767 if( p1 == p2 )
3768 continue;
3769
3770 SEG candidate( p1, p2 );
3771
3772 VECTOR2I mid( ( candidate.A.x + candidate.B.x ) / 2, ( candidate.A.y + candidate.B.y ) / 2 );
3773
3774 // Check if segment is inside the polygon by checking its middle point
3775 if( Contains( mid, -1, 1, true ) )
3776 {
3777 int dx = p2.x - p1.x;
3778
3779 // Push only one line for diagonal hatch or for small lines < twice
3780 // the line length; else push 2 small lines
3781 if( aLineLength == -1 || std::abs( dx ) < 2 * aLineLength )
3782 {
3783 hatchLines.emplace_back( candidate );
3784 }
3785 else
3786 {
3787 double dy = p2.y - p1.y;
3788 slope = dy / dx;
3789
3790 if( dx > 0 )
3791 dx = aLineLength;
3792 else
3793 dx = -aLineLength;
3794
3795 int x1 = KiROUND( p1.x + dx );
3796 int x2 = KiROUND( p2.x - dx );
3797 int y1 = KiROUND( p1.y + dx * slope );
3798 int y2 = KiROUND( p2.y - dx * slope );
3799
3800 hatchLines.emplace_back( SEG( p1.x, p1.y, x1, y1 ) );
3801
3802 hatchLines.emplace_back( SEG( p2.x, p2.y, x2, y2 ) );
3803 }
3804 }
3805 }
3806 }
3807 }
3808
3809 return hatchLines;
3810}
int index
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
bool operator!=(const BOM_FIELD &lhs, const BOM_FIELD &rhs)
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
BOX2< VECTOR2D > BOX2D
Definition box2.h:919
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:554
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:654
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr coord_type GetBottom() const
Definition box2.h:218
Dynamic R*-tree with SoA node layout and stored insertion bounding boxes.
int Search(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], VISITOR &aVisitor) const
Search for items whose bounding boxes overlap the query rectangle.
void Insert(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], const DATATYPE &aData)
Insert an item with the given bounding box.
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:56
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:117
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:136
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
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
ecoord SquaredDistance(const SEG &aSeg) const
Definition seg.cpp:76
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:453
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:357
static SEG::ecoord Square(int a)
Definition seg.h:119
bool Collide(const SEG &aSeg, int aClearance, int *aActual=nullptr) const
Definition seg.cpp:538
bool ApproxCollinear(const SEG &aSeg, int aDistanceThreshold=1) const
Definition seg.cpp:791
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.
int SegmentCount() const
Return the number of segments in this 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.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
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.
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.
void cacheTriangulation(bool aSimplify, std::vector< std::unique_ptr< TRIANGULATED_POLYGON > > *aHintData, const TASK_SUBMITTER &aSubmitter={})
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)
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).
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)
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={})
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
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...
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
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:179
VECTOR2I::extended_type ecoord
Definition shape.h:299
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:279
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:310
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:381
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:409
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
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
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 void fractureSingleSlow(SHAPE_POLY_SET::POLYGON &paths)
static FractureEdge * processHole(FractureEdgeSet &edges, FractureEdge::Index provokingIndex, FractureEdge::Index edgeIndex, FractureEdge::Index bridgeIndex)
std::vector< FractureEdge > FractureEdgeSet
std::vector< FractureEdgeSlow * > FractureEdgeSetSlow
#define SEG_CNT_MAX
#define ENABLECACHEFRIENDLYFRACTURE
static int processEdge(FractureEdgeSetSlow &edges, FractureEdgeSlow *edge)
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:32
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
const uint32_t seed
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
T rescale(T aNumerator, T aValue, T aDenominator)
Scale a number (value) by rational (numerator/denominator).
Definition util.h:135
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682