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