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