KiCad PCB EDA Suite
Loading...
Searching...
No Matches
shape_poly_set.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2015-2019 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 * @author Alejandro GarcĂ­a Montoro <[email protected]>
9 *
10 * Point in polygon algorithm adapted from Clipper Library (C) Angus Johnson,
11 * subject to Clipper library license.
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25 */
26
27#include <algorithm>
28#include <assert.h> // for assert
29#include <cmath> // for sqrt, cos, hypot, isinf
30#include <cstdio>
31#include <istream> // for operator<<, operator>>
32#include <limits> // for numeric_limits
33#include <map>
34#include <memory>
35#include <set>
36#include <string> // for char_traits, operator!=
37#include <unordered_set>
38#include <thread>
39#include <utility> // for swap, move
40#include <vector>
41#include <array>
42
43#include <clipper2/clipper.h>
47#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
48#include <geometry/shape.h>
52#include <math/box2.h> // for BOX2I
53#include <math/util.h> // for KiROUND, rescale
54#include <math/vector2d.h> // for VECTOR2I, VECTOR2D, VECTOR2
55#include <hash.h>
56#include <mmh3_hash.h>
59
60#include <wx/log.h>
61
62// ADVANCED_CFG::GetCfg() cannot be used on msys2/mingw builds (link failure)
63// So we use the ADVANCED_CFG default values
64#if defined( __MINGW32__ )
65 #define TRIANGULATESIMPLIFICATIONLEVEL 50
66 #define ENABLECACHEFRIENDLYFRACTURE true
67#else
68 #define TRIANGULATESIMPLIFICATIONLEVEL ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel
69 #define ENABLECACHEFRIENDLYFRACTURE ADVANCED_CFG::GetCfg().m_EnableCacheFriendlyFracture
70#endif
71
76
77
80{
81 NewOutline();
82 Append( VECTOR2I( aRect.GetLeft(), aRect.GetTop() ) );
83 Append( VECTOR2I( aRect.GetRight(), aRect.GetTop() ) );
84 Append( VECTOR2I( aRect.GetRight(), aRect.GetBottom() ) );
85 Append( VECTOR2I( aRect.GetLeft(), aRect.GetBottom() ) );
86 Outline( 0 ).SetClosed( true );
87}
88
89
92{
93 AddOutline( aOutline );
94}
95
96
99{
100 AddPolygon( aPolygon );
101}
102
103
105 SHAPE( aOther ),
106 m_polys( aOther.m_polys )
107{
108 if( aOther.IsTriangulationUpToDate() )
109 {
110 m_triangulatedPolys.reserve( aOther.TriangulatedPolyCount() );
111
112 for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
113 {
114 const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
115 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
116 }
117
118 m_hash = aOther.GetHash();
119 m_hashValid = true;
121 }
122 else
123 {
124 m_hash.Clear();
125 m_hashValid = false;
126 m_triangulationValid = false;
127 }
128
129 m_failedHash = aOther.m_failedHash;
130 m_failedHashValid.store( aOther.m_failedHashValid.load() );
131}
132
133
135 SHAPE( aOther ),
136 m_polys( aOther.m_polys )
137{
138 m_hash.Clear();
139 m_hashValid = false;
140 m_triangulationValid = false;
141}
142
143
147
148
150{
151 return new SHAPE_POLY_SET( *this );
152}
153
154
159
160
162 SHAPE_POLY_SET::VERTEX_INDEX* aRelativeIndices ) const
163{
164 int polygonIdx = 0;
165 unsigned int contourIdx = 0;
166 int vertexIdx = 0;
167
168 int currentGlobalIdx = 0;
169
170 for( polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
171 {
172 const POLYGON& currentPolygon = CPolygon( polygonIdx );
173
174 for( contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
175 {
176 const SHAPE_LINE_CHAIN& currentContour = currentPolygon[contourIdx];
177 int totalPoints = currentContour.PointCount();
178
179 for( vertexIdx = 0; vertexIdx < totalPoints; vertexIdx++ )
180 {
181 // Check if the current vertex is the globally indexed as aGlobalIdx
182 if( currentGlobalIdx == aGlobalIdx )
183 {
184 aRelativeIndices->m_polygon = polygonIdx;
185 aRelativeIndices->m_contour = contourIdx;
186 aRelativeIndices->m_vertex = vertexIdx;
187
188 return true;
189 }
190
191 // Advance
192 currentGlobalIdx++;
193 }
194 }
195 }
196
197 return false;
198}
199
200
202 int& aGlobalIdx ) const
203{
204 int selectedVertex = aRelativeIndices.m_vertex;
205 unsigned int selectedContour = aRelativeIndices.m_contour;
206 unsigned int selectedPolygon = aRelativeIndices.m_polygon;
207
208 // Check whether the vertex indices make sense in this poly set
209 if( selectedPolygon < m_polys.size() && selectedContour < m_polys[selectedPolygon].size()
210 && selectedVertex < m_polys[selectedPolygon][selectedContour].PointCount() )
211 {
212 POLYGON currentPolygon;
213
214 aGlobalIdx = 0;
215
216 for( unsigned int polygonIdx = 0; polygonIdx < selectedPolygon; polygonIdx++ )
217 {
218 currentPolygon = Polygon( polygonIdx );
219
220 for( unsigned int contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
221 aGlobalIdx += currentPolygon[contourIdx].PointCount();
222 }
223
224 currentPolygon = Polygon( selectedPolygon );
225
226 for( unsigned int contourIdx = 0; contourIdx < selectedContour; contourIdx++ )
227 aGlobalIdx += currentPolygon[contourIdx].PointCount();
228
229 aGlobalIdx += selectedVertex;
230
231 return true;
232 }
233 else
234 {
235 return false;
236 }
237}
238
239
241{
242 SHAPE_LINE_CHAIN empty_path;
243 POLYGON poly;
244
245 empty_path.SetClosed( true );
246 poly.push_back( empty_path );
247 m_polys.push_back( std::move( poly ) );
248 return m_polys.size() - 1;
249}
250
251
252int SHAPE_POLY_SET::NewHole( int aOutline )
253{
254 SHAPE_LINE_CHAIN empty_path;
255
256 empty_path.SetClosed( true );
257
258 // Default outline is the last one
259 if( aOutline < 0 )
260 aOutline += m_polys.size();
261
262 // Add hole to the selected outline
263 m_polys[aOutline].push_back( empty_path );
264
265 return m_polys.back().size() - 2;
266}
267
268
269int SHAPE_POLY_SET::Append( int x, int y, int aOutline, int aHole, bool aAllowDuplication )
270{
271 assert( m_polys.size() );
272
273 if( aOutline < 0 )
274 aOutline += m_polys.size();
275
276 int idx;
277
278 if( aHole < 0 )
279 idx = 0;
280 else
281 idx = aHole + 1;
282
283 assert( aOutline < (int) m_polys.size() );
284 assert( idx < (int) m_polys[aOutline].size() );
285
286 m_polys[aOutline][idx].Append( x, y, aAllowDuplication );
287
288 return m_polys[aOutline][idx].PointCount();
289}
290
291
292int SHAPE_POLY_SET::Append( const SHAPE_ARC& aArc, int aOutline, int aHole,
293 std::optional<int> aMaxError )
294{
295 assert( m_polys.size() );
296
297 if( aOutline < 0 )
298 aOutline += m_polys.size();
299
300 int idx;
301
302 if( aHole < 0 )
303 idx = 0;
304 else
305 idx = aHole + 1;
306
307 assert( aOutline < (int) m_polys.size() );
308 assert( idx < (int) m_polys[aOutline].size() );
309
310 if( aMaxError.has_value() )
311 m_polys[aOutline][idx].Append( aArc, aMaxError.value() );
312 else
313 m_polys[aOutline][idx].Append( aArc );
314
315 return m_polys[aOutline][idx].PointCount();
316}
317
318
319void SHAPE_POLY_SET::InsertVertex( int aGlobalIndex, const VECTOR2I& aNewVertex )
320{
322
323 if( aGlobalIndex < 0 )
324 aGlobalIndex = 0;
325
326 if( aGlobalIndex >= TotalVertices() )
327 {
328 Append( aNewVertex );
329 }
330 else
331 {
332 // Assure the position to be inserted exists; throw an exception otherwise
333 if( GetRelativeIndices( aGlobalIndex, &index ) )
334 m_polys[index.m_polygon][index.m_contour].Insert( index.m_vertex, aNewVertex );
335 else
336 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
337 }
338}
339
340
341int SHAPE_POLY_SET::VertexCount( int aOutline, int aHole ) const
342{
343 if( m_polys.size() == 0 ) // Empty poly set
344 return 0;
345
346 if( aOutline < 0 ) // Use last outline
347 aOutline += m_polys.size();
348
349 int idx;
350
351 if( aHole < 0 )
352 idx = 0;
353 else
354 idx = aHole + 1;
355
356 if( aOutline >= (int) m_polys.size() ) // not existing outline
357 return 0;
358
359 if( idx >= (int) m_polys[aOutline].size() ) // not existing hole
360 return 0;
361
362 return m_polys[aOutline][idx].PointCount();
363}
364
365
367{
368 int full_count = 0;
369
370 if( m_polys.size() == 0 ) // Empty poly set
371 return full_count;
372
373 for( int ii = 0; ii < OutlineCount(); ii++ )
374 {
375 // the first polygon in m_polys[ii] is the main contour,
376 // only others are holes:
377 for( int idx = 0; idx <= HoleCount( ii ); idx++ )
378 {
379 full_count += m_polys[ii][idx].PointCount();
380 }
381 }
382
383 return full_count;
384}
385
386
387SHAPE_POLY_SET SHAPE_POLY_SET::Subset( int aFirstPolygon, int aLastPolygon )
388{
389 assert( aFirstPolygon >= 0 && aLastPolygon <= OutlineCount() );
390
391 SHAPE_POLY_SET newPolySet;
392
393 for( int index = aFirstPolygon; index < aLastPolygon; index++ )
394 newPolySet.m_polys.push_back( Polygon( index ) );
395
396 return newPolySet;
397}
398
399
400const VECTOR2I& SHAPE_POLY_SET::CVertex( int aIndex, int aOutline, int aHole ) const
401{
402 if( aOutline < 0 )
403 aOutline += m_polys.size();
404
405 int idx;
406
407 if( aHole < 0 )
408 idx = 0;
409 else
410 idx = aHole + 1;
411
412 assert( aOutline < (int) m_polys.size() );
413 assert( idx < (int) m_polys[aOutline].size() );
414
415 return m_polys[aOutline][idx].CPoint( aIndex );
416}
417
418
419const VECTOR2I& SHAPE_POLY_SET::CVertex( int aGlobalIndex ) const
420{
422
423 // Assure the passed index references a legal position; abort otherwise
424 if( !GetRelativeIndices( aGlobalIndex, &index ) )
425 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
426
427 return m_polys[index.m_polygon][index.m_contour].CPoint( index.m_vertex );
428}
429
430
432{
433 return CVertex( index.m_vertex, index.m_polygon, index.m_contour - 1 );
434}
435
436
437bool SHAPE_POLY_SET::GetNeighbourIndexes( int aGlobalIndex, int* aPrevious, int* aNext ) const
438{
440
441 // If the edge does not exist, throw an exception, it is an illegal access memory error
442 if( !GetRelativeIndices( aGlobalIndex, &index ) )
443 return false;
444
445 // Calculate the previous and next index of aGlobalIndex, corresponding to
446 // the same contour;
447 VERTEX_INDEX inext = index;
448 int lastpoint = m_polys[index.m_polygon][index.m_contour].SegmentCount();
449
450 if( index.m_vertex == 0 )
451 {
452 index.m_vertex = lastpoint - 1;
453 inext.m_vertex = 1;
454 }
455 else if( index.m_vertex == lastpoint )
456 {
457 index.m_vertex--;
458 inext.m_vertex = 0;
459 }
460 else
461 {
462 inext.m_vertex++;
463 index.m_vertex--;
464
465 if( inext.m_vertex == lastpoint )
466 inext.m_vertex = 0;
467 }
468
469 if( aPrevious )
470 {
471 int previous;
472 GetGlobalIndex( index, previous );
473 *aPrevious = previous;
474 }
475
476 if( aNext )
477 {
478 int next;
479 GetGlobalIndex( inext, next );
480 *aNext = next;
481 }
482
483 return true;
484}
485
486
487bool SHAPE_POLY_SET::IsPolygonSelfIntersecting( int aPolygonIndex ) const
488{
489 std::vector<SEG> segments;
490 segments.reserve( FullPointCount() );
491
492 for( CONST_SEGMENT_ITERATOR it = CIterateSegmentsWithHoles( aPolygonIndex ); it; it++ )
493 segments.emplace_back( *it );
494
495 std::sort( segments.begin(), segments.end(), []( const SEG& a, const SEG& b )
496 {
497 int min_a_x = std::min( a.A.x, a.B.x );
498 int min_b_x = std::min( b.A.x, b.B.x );
499
500 return min_a_x < min_b_x || ( min_a_x == min_b_x && std::min( a.A.y, a.B.y ) < std::min( b.A.y, b.B.y ) );
501 } );
502
503 for( auto it = segments.begin(); it != segments.end(); ++it )
504 {
505 SEG& firstSegment = *it;
506
507 // Iterate through all remaining segments.
508 auto innerIterator = it;
509 int max_x = std::max( firstSegment.A.x, firstSegment.B.x );
510 int max_y = std::max( firstSegment.A.y, firstSegment.B.y );
511
512 // Start in the next segment, we don't want to check collision between a segment and itself
513 for( innerIterator++; innerIterator != segments.end(); innerIterator++ )
514 {
515 SEG& secondSegment = *innerIterator;
516 int min_x = std::min( secondSegment.A.x, secondSegment.B.x );
517 int min_y = std::min( secondSegment.A.y, secondSegment.B.y );
518
519 // We are ordered in minimum point order, so checking the static max (first segment) against
520 // the ordered min will tell us if any of the following segments are withing the BBox
521 if( max_x < min_x || ( max_x == min_x && max_y < min_y ) )
522 break;
523
524 int index_diff = std::abs( firstSegment.Index() - secondSegment.Index() );
525 bool adjacent = ( index_diff == 1) || (index_diff == ((int)segments.size() - 1) );
526
527 // Check whether the two segments built collide, only when they are not adjacent.
528 if( !adjacent && firstSegment.Collide( secondSegment, 0 ) )
529 return true;
530 }
531 }
532
533 return false;
534}
535
536
538{
539 for( unsigned int polygon = 0; polygon < m_polys.size(); polygon++ )
540 {
541 if( IsPolygonSelfIntersecting( polygon ) )
542 return true;
543 }
544
545 return false;
546}
547
548
550{
551 POLYGON poly;
552
553 poly.push_back( aOutline );
554
555 // This is an assertion because if it's generated by KiCad code, it probably
556 // indicates a bug elsewhere that should be fixed, but we also auto-fix it here
557 // for SWIG plugins that might mess it up
558 wxCHECK2_MSG( aOutline.IsClosed(), poly.back().SetClosed( true ),
559 "Warning: non-closed outline added to SHAPE_POLY_SET" );
560
561 m_polys.push_back( std::move( poly ) );
562
563 return (int) m_polys.size() - 1;
564}
565
566
567int SHAPE_POLY_SET::AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline )
568{
569 assert( m_polys.size() );
570
571 if( aOutline < 0 )
572 aOutline += (int) m_polys.size();
573
574 assert( aOutline < (int)m_polys.size() );
575
576 POLYGON& poly = m_polys[aOutline];
577
578 assert( poly.size() );
579
580 poly.push_back( aHole );
581
582 return (int) poly.size() - 2;
583}
584
585
587{
588 m_polys.push_back( apolygon );
589
590 return m_polys.size() - 1;
591}
592
593
595{
596 double area = 0.0;
597
598 for( int i = 0; i < OutlineCount(); i++ )
599 {
600 area += Outline( i ).Area( true );
601
602 for( int j = 0; j < HoleCount( i ); j++ )
603 area -= Hole( i, j ).Area( true );
604 }
605
606 return area;
607}
608
609
611{
612 int retval = 0;
613
614 for( const POLYGON& poly : m_polys )
615 {
616 for( size_t i = 0; i < poly.size(); i++ )
617 retval += poly[i].ArcCount();
618 }
619
620 return retval;
621}
622
623
624void SHAPE_POLY_SET::GetArcs( std::vector<SHAPE_ARC>& aArcBuffer ) const
625{
626 for( const POLYGON& poly : m_polys )
627 {
628 for( size_t i = 0; i < poly.size(); i++ )
629 {
630 for( SHAPE_ARC arc : poly[i].m_arcs )
631 aArcBuffer.push_back( arc );
632 }
633 }
634}
635
636
638{
639 for( POLYGON& poly : m_polys )
640 {
641 for( size_t i = 0; i < poly.size(); i++ )
642 poly[i].ClearArcs();
643 }
644}
645
646
648{
649 std::vector<SHAPE_LINE_CHAIN> contours;
650
651 for( const POLYGON& poly : m_polys )
652 contours.insert( contours.end(), poly.begin(), poly.end() );
653
654 std::map<int, std::set<int>> parentToChildren;
655 std::map<int, std::set<int>> childToParents;
656
657 for( SHAPE_LINE_CHAIN& contour : contours )
658 contour.GenerateBBoxCache();
659
660 for( size_t i = 0; i < contours.size(); i++ )
661 {
662 const SHAPE_LINE_CHAIN& outline = contours[i];
663
664 for( size_t j = 0; j < contours.size(); j++ )
665 {
666 if( i == j )
667 continue;
668
669 const SHAPE_LINE_CHAIN& candidate = contours[j];
670 const VECTOR2I& pt0 = candidate.CPoint( 0 );
671
672 if( outline.PointInside( pt0, 0, true ) )
673 {
674 parentToChildren[i].emplace( j );
675 childToParents[j].emplace( i );
676 }
677 }
678 }
679
680 std::set<int> topLevelParents;
681
682 for( size_t i = 0; i < contours.size(); i++ )
683 {
684 if( childToParents[i].size() == 0 )
685 {
686 topLevelParents.emplace( i );
687 }
688 }
689
691
692 std::function<void( int, int, std::vector<int> )> process;
693
694 process =
695 [&]( int myId, int parentOutlineId, const std::vector<int>& path )
696 {
697 std::set<int> relParents = childToParents[myId];
698
699 for( int pathId : path )
700 {
701 int erased = relParents.erase( pathId );
702 wxASSERT( erased > 0 );
703 }
704
705 wxASSERT( relParents.size() == 0 );
706
707 int myOutline = -1;
708
709 bool isOutline = path.size() % 2 == 0;
710
711 if( isOutline )
712 {
713 int outlineId = result.AddOutline( contours[myId] );
714 myOutline = outlineId;
715 }
716 else
717 {
718 wxASSERT( parentOutlineId != -1 );
719 result.AddHole( contours[myId], parentOutlineId );
720 }
721
722 auto it = parentToChildren.find( myId );
723 if( it != parentToChildren.end() )
724 {
725 std::vector<int> thisPath = path;
726 thisPath.emplace_back( myId );
727
728 std::set<int> thisPathSet;
729 thisPathSet.insert( thisPath.begin(), thisPath.end() );
730
731 for( int childId : it->second )
732 {
733 const std::set<int>& childPathSet = childToParents[childId];
734
735 if( thisPathSet != childPathSet )
736 continue; // Only interested in immediate children
737
738 process( childId, myOutline, thisPath );
739 }
740 }
741 };
742
743 for( int topParentId : topLevelParents )
744 {
745 std::vector<int> path;
746 process( topParentId, -1, std::move( path ) );
747 }
748
749 *this = std::move( result );
750}
751
752
753void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aOtherShape )
754{
755 booleanOp( aType, *this, aOtherShape );
756}
757
758
759void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aShape,
760 const SHAPE_POLY_SET& aOtherShape )
761{
762 if( ( aShape.OutlineCount() > 1 || aOtherShape.OutlineCount() > 0 )
763 && ( aShape.ArcCount() > 0 || aOtherShape.ArcCount() > 0 ) )
764 {
765 wxFAIL_MSG( wxT( "Boolean ops on curved polygons are not supported. You should call "
766 "ClearArcs() before carrying out the boolean operation." ) );
767 }
768
769 Clipper2Lib::Clipper64 c;
770
771 std::vector<CLIPPER_Z_VALUE> zValues;
772 std::vector<SHAPE_ARC> arcBuffer;
773 std::map<VECTOR2I, CLIPPER_Z_VALUE> newIntersectPoints;
774
775 Clipper2Lib::Paths64 paths;
776 Clipper2Lib::Paths64 clips;
777
778 for( const POLYGON& poly : aShape.m_polys )
779 {
780 for( size_t i = 0; i < poly.size(); i++ )
781 {
782 paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
783 }
784 }
785
786 for( const POLYGON& poly : aOtherShape.m_polys )
787 {
788 for( size_t i = 0; i < poly.size(); i++ )
789 {
790 clips.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
791 }
792 }
793
794 c.AddSubject( paths );
795 c.AddClip( clips );
796
797 Clipper2Lib::PolyTree64 solution;
798
799 Clipper2Lib::ZCallback64 callback =
800 [&]( const Clipper2Lib::Point64 & e1bot, const Clipper2Lib::Point64 & e1top,
801 const Clipper2Lib::Point64 & e2bot, const Clipper2Lib::Point64 & e2top,
802 Clipper2Lib::Point64 & pt )
803 {
804 auto arcIndex =
805 [&]( const ssize_t& aZvalue, const ssize_t& aCompareVal = -1 ) -> ssize_t
806 {
807 ssize_t retval;
808
809 retval = zValues.at( aZvalue ).m_SecondArcIdx;
810
811 if( retval == -1 || ( aCompareVal > 0 && retval != aCompareVal ) )
812 retval = zValues.at( aZvalue ).m_FirstArcIdx;
813
814 return retval;
815 };
816
817 auto arcSegment =
818 [&]( const ssize_t& aBottomZ, const ssize_t aTopZ ) -> ssize_t
819 {
820 ssize_t retval = arcIndex( aBottomZ );
821
822 if( retval != -1 )
823 {
824 if( retval != arcIndex( aTopZ, retval ) )
825 retval = -1; // Not an arc segment as the two indices do not match
826 }
827
828 return retval;
829 };
830
831 ssize_t e1ArcSegmentIndex = arcSegment( e1bot.z, e1top.z );
832 ssize_t e2ArcSegmentIndex = arcSegment( e2bot.z, e2top.z );
833
834 CLIPPER_Z_VALUE newZval;
835
836 if( e1ArcSegmentIndex != -1 )
837 {
838 newZval.m_FirstArcIdx = e1ArcSegmentIndex;
839 newZval.m_SecondArcIdx = e2ArcSegmentIndex;
840 }
841 else
842 {
843 newZval.m_FirstArcIdx = e2ArcSegmentIndex;
844 newZval.m_SecondArcIdx = -1;
845 }
846
847 size_t z_value_ptr = zValues.size();
848 zValues.push_back( newZval );
849
850 // Only worry about arc segments for later processing
851 if( newZval.m_FirstArcIdx != -1 )
852 newIntersectPoints.insert( { VECTOR2I( pt.x, pt.y ), newZval } );
853
854 pt.z = z_value_ptr;
855 //@todo amend X,Y values to true intersection between arcs or arc and segment
856 };
857
858 c.SetZCallback( std::move( callback ) ); // register callback
859
860 c.Execute( aType, Clipper2Lib::FillRule::NonZero, solution );
861
862 importTree( solution, zValues, arcBuffer );
863 solution.Clear(); // Free used memory (not done in dtor)
864}
865
866
868{
869 booleanOp( Clipper2Lib::ClipType::Union, b );
870}
871
872
874{
875 booleanOp( Clipper2Lib::ClipType::Difference, b );
876}
877
878
880{
881 booleanOp( Clipper2Lib::ClipType::Intersection, b );
882}
883
884
886{
887 booleanOp( Clipper2Lib::ClipType::Xor, b );
888}
889
890
892{
893 booleanOp( Clipper2Lib::ClipType::Union, a, b );
894}
895
896
898{
899 booleanOp( Clipper2Lib::ClipType::Difference, a, b );
900}
901
902
904{
905 booleanOp( Clipper2Lib::ClipType::Intersection, a, b );
906}
907
908
910{
911 booleanOp( Clipper2Lib::ClipType::Xor, a, b );
912}
913
914
916 int aMaxError )
917{
918 Unfracture();
919 Inflate( aFactor, aCornerStrategy, aMaxError );
920 Fracture();
921}
922
923
924void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy,
925 bool aSimplify )
926{
927 using namespace Clipper2Lib;
928 // A thread-local table to avoid repetitive calculations of the coefficient
929 // 1.0 - cos( M_PI / aCircleSegCount )
930 // aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
931 #define SEG_CNT_MAX 64
932 static thread_local double arc_tolerance_factor[SEG_CNT_MAX + 1];
933
934 ClipperOffset c;
935
936 // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
937 // and are not what you'd think they are.
938 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
939 JoinType joinType = JoinType::Round; // The way corners are offsetted
940 double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
941
942 switch( aCornerStrategy )
943 {
945 joinType = JoinType::Miter;
946 miterLimit = 10; // Allows large spikes
947 break;
948
949 case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
950 joinType = JoinType::Miter;
951 break;
952
953 case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
954 joinType = JoinType::Miter;
955 break;
956
957 case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
958 joinType = JoinType::Square;
959 break;
960
961 case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
962 joinType = JoinType::Round;
963 break;
964 }
965
966 std::vector<CLIPPER_Z_VALUE> zValues;
967 std::vector<SHAPE_ARC> arcBuffer;
968
969 for( const POLYGON& poly : m_polys )
970 {
971 Paths64 paths;
972
973 for( size_t i = 0; i < poly.size(); i++ )
974 paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
975
976 c.AddPaths( paths, joinType, EndType::Polygon );
977 }
978
979 // Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
980 // nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
981 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
982
983 if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
984 aCircleSegCount = 6;
985
986 double coeff;
987
988 if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
989 {
990 coeff = 1.0 - cos( M_PI / aCircleSegCount );
991
992 if( aCircleSegCount <= SEG_CNT_MAX )
993 arc_tolerance_factor[aCircleSegCount] = coeff;
994 }
995 else
996 {
997 coeff = arc_tolerance_factor[aCircleSegCount];
998 }
999
1000 c.ArcTolerance( std::abs( aAmount ) * coeff );
1001 c.MiterLimit( miterLimit );
1002
1003 PolyTree64 tree;
1004
1005 if( aSimplify )
1006 {
1007 Paths64 paths;
1008 c.Execute( aAmount, paths );
1009
1010 Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, true );
1011
1012 Clipper64 c2;
1013 c2.PreserveCollinear( false );
1014 c2.ReverseSolution( false );
1015 c2.AddSubject( paths );
1016 c2.Execute(ClipType::Union, FillRule::Positive, tree);
1017 }
1018 else
1019 {
1020 c.Execute( aAmount, tree );
1021 }
1022
1023 importTree( tree, zValues, arcBuffer );
1024 tree.Clear();
1025}
1026
1027
1028void SHAPE_POLY_SET::inflateLine2( const SHAPE_LINE_CHAIN& aLine, int aAmount, int aCircleSegCount,
1029 CORNER_STRATEGY aCornerStrategy, bool aSimplify )
1030{
1031 using namespace Clipper2Lib;
1032 // A thread-local table to avoid repetitive calculations of the coefficient
1033 // 1.0 - cos( M_PI / aCircleSegCount )
1034 // aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
1035 #define SEG_CNT_MAX 64
1036 static thread_local double arc_tolerance_factor[SEG_CNT_MAX + 1];
1037
1038 ClipperOffset c;
1039
1040 // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
1041 // and are not what you'd think they are.
1042 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
1043 JoinType joinType = JoinType::Round; // The way corners are offsetted
1044 double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
1045
1046 switch( aCornerStrategy )
1047 {
1049 joinType = JoinType::Miter;
1050 miterLimit = 10; // Allows large spikes
1051 break;
1052
1053 case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
1054 joinType = JoinType::Miter;
1055 break;
1056
1057 case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
1058 joinType = JoinType::Miter;
1059 break;
1060
1061 case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
1062 joinType = JoinType::Square;
1063 break;
1064
1065 case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
1066 joinType = JoinType::Round;
1067 break;
1068 }
1069
1070 std::vector<CLIPPER_Z_VALUE> zValues;
1071 std::vector<SHAPE_ARC> arcBuffer;
1072
1073 Path64 path = aLine.convertToClipper2( true, zValues, arcBuffer );
1074 c.AddPath( path, joinType, EndType::Butt );
1075
1076 // Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
1077 // nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
1078 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
1079
1080 if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
1081 aCircleSegCount = 6;
1082
1083 double coeff;
1084
1085 if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
1086 {
1087 coeff = 1.0 - cos( M_PI / aCircleSegCount );
1088
1089 if( aCircleSegCount <= SEG_CNT_MAX )
1090 arc_tolerance_factor[aCircleSegCount] = coeff;
1091 }
1092 else
1093 {
1094 coeff = arc_tolerance_factor[aCircleSegCount];
1095 }
1096
1097 c.ArcTolerance( std::abs( aAmount ) * coeff );
1098 c.MiterLimit( miterLimit );
1099
1100 PolyTree64 tree;
1101
1102 if( aSimplify )
1103 {
1104 Paths64 paths2;
1105 c.Execute( aAmount, paths2 );
1106
1107 Clipper2Lib::SimplifyPaths( paths2, std::abs( aAmount ) * coeff, false );
1108
1109 Clipper64 c2;
1110 c2.PreserveCollinear( false );
1111 c2.ReverseSolution( false );
1112 c2.AddSubject( paths2 );
1113 c2.Execute( ClipType::Union, FillRule::Positive, tree );
1114 }
1115 else
1116 {
1117 c.Execute( aAmount, tree );
1118 }
1119
1120 importTree( tree, zValues, arcBuffer );
1121 tree.Clear();
1122}
1123
1124
1125void SHAPE_POLY_SET::Inflate( int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError,
1126 bool aSimplify )
1127{
1128 int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
1129
1130 inflate2( aAmount, segCount, aCornerStrategy, aSimplify );
1131}
1132
1133
1135 CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify )
1136{
1137 int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
1138
1139 inflateLine2( aLine, aAmount, segCount, aCornerStrategy, aSimplify );
1140}
1141
1142
1143void SHAPE_POLY_SET::importPolyPath( const std::unique_ptr<Clipper2Lib::PolyPath64>& aPolyPath,
1144 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1145 const std::vector<SHAPE_ARC>& aArcBuffer )
1146{
1147 if( !aPolyPath->IsHole() )
1148 {
1149 POLYGON paths;
1150 paths.reserve( aPolyPath->Count() + 1 );
1151 paths.emplace_back( aPolyPath->Polygon(), aZValueBuffer, aArcBuffer );
1152
1153 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& child : *aPolyPath )
1154 {
1155 paths.emplace_back( child->Polygon(), aZValueBuffer, aArcBuffer );
1156
1157 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& grandchild : *child )
1158 importPolyPath( grandchild, aZValueBuffer, aArcBuffer );
1159 }
1160
1161 m_polys.emplace_back( std::move( paths ) );
1162 }
1163}
1164
1165
1166void SHAPE_POLY_SET::importTree( Clipper2Lib::PolyTree64& tree,
1167 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1168 const std::vector<SHAPE_ARC>& aArcBuffer )
1169{
1170 m_polys.clear();
1171
1172 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& n : tree )
1173 importPolyPath( n, aZValueBuffer, aArcBuffer );
1174}
1175
1176
1177void SHAPE_POLY_SET::importPaths( Clipper2Lib::Paths64& aPath,
1178 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1179 const std::vector<SHAPE_ARC>& aArcBuffer )
1180{
1181 m_polys.clear();
1182 POLYGON path;
1183
1184 for( const Clipper2Lib::Path64& n : aPath )
1185 {
1186 if( Clipper2Lib::Area( n ) > 0 )
1187 {
1188 if( !path.empty() )
1189 m_polys.emplace_back( path );
1190
1191 path.clear();
1192 }
1193 else
1194 {
1195 wxCHECK2_MSG( !path.empty(), continue, wxT( "Cannot add a hole before an outline" ) );
1196 }
1197
1198 path.emplace_back( n, aZValueBuffer, aArcBuffer );
1199 }
1200
1201 if( !path.empty() )
1202 m_polys.emplace_back( std::move( path ) );
1203}
1204
1205
1207{
1208 using Index = int;
1209
1210 FractureEdge() = default;
1211
1212 FractureEdge( const VECTOR2I& p1, const VECTOR2I& p2, Index next ) :
1213 m_p1( p1 ),
1214 m_p2( p2 ),
1215 m_next( next )
1216 {
1217 }
1218
1219 bool matches( int y ) const
1220 {
1221 return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
1222 }
1223
1227};
1228
1229
1230typedef std::vector<FractureEdge> FractureEdgeSet;
1231
1232
1234 FractureEdge::Index edgeIndex, FractureEdge::Index bridgeIndex )
1235{
1236 FractureEdge& edge = edges[edgeIndex];
1237 int x = edge.m_p1.x;
1238 int y = edge.m_p1.y;
1239 int min_dist = std::numeric_limits<int>::max();
1240 int x_nearest = 0;
1241
1242 FractureEdge* e_nearest = nullptr;
1243
1244 // Since this function is run for all holes left to right, no need to
1245 // check for any edge beyond the provoking one because they will always be
1246 // further to the right, and unconnected to the outline anyway.
1247 for( FractureEdge::Index i = 0; i < provokingIndex; i++ )
1248 {
1249 FractureEdge& e = edges[i];
1250 // Don't consider this edge if it can't be bridged to, or faces left.
1251 if( !e.matches( y ) )
1252 continue;
1253
1254 int x_intersect;
1255
1256 if( e.m_p1.y == e.m_p2.y ) // horizontal edge
1257 {
1258 x_intersect = std::max( e.m_p1.x, e.m_p2.x );
1259 }
1260 else
1261 {
1262 x_intersect =
1263 e.m_p1.x + rescale( e.m_p2.x - e.m_p1.x, y - e.m_p1.y, e.m_p2.y - e.m_p1.y );
1264 }
1265
1266 int dist = ( x - x_intersect );
1267
1268 if( dist >= 0 && dist < min_dist )
1269 {
1270 min_dist = dist;
1271 x_nearest = x_intersect;
1272 e_nearest = &e;
1273 }
1274 }
1275
1276 if( e_nearest )
1277 {
1278 const FractureEdge::Index outline2hole_index = bridgeIndex;
1279 const FractureEdge::Index hole2outline_index = bridgeIndex + 1;
1280 const FractureEdge::Index split_index = bridgeIndex + 2;
1281 // Make an edge between the split outline edge and the hole...
1282 edges[outline2hole_index] = FractureEdge( VECTOR2I( x_nearest, y ), edge.m_p1, edgeIndex );
1283 // ...between the hole and the edge...
1284 edges[hole2outline_index] =
1285 FractureEdge( edge.m_p1, VECTOR2I( x_nearest, y ), split_index );
1286 // ...and between the split outline edge and the rest.
1287 edges[split_index] =
1288 FractureEdge( VECTOR2I( x_nearest, y ), e_nearest->m_p2, e_nearest->m_next );
1289
1290 // Perform the actual outline edge split
1291 e_nearest->m_p2 = VECTOR2I( x_nearest, y );
1292 e_nearest->m_next = outline2hole_index;
1293
1294 FractureEdge* last = &edge;
1295 for( ; last->m_next != edgeIndex; last = &edges[last->m_next] )
1296 ;
1297 last->m_next = hole2outline_index;
1298 }
1299
1300 return e_nearest;
1301}
1302
1303
1305{
1306 FractureEdgeSet edges;
1307 bool outline = true;
1308
1309 if( paths.size() == 1 )
1310 return;
1311
1312 size_t total_point_count = 0;
1313
1314 for( const SHAPE_LINE_CHAIN& path : paths )
1315 {
1316 total_point_count += path.PointCount();
1317 }
1318
1319 if( total_point_count > (size_t) std::numeric_limits<FractureEdge::Index>::max() )
1320 {
1321 wxLogWarning( wxT( "Polygon has more points than int limit" ) );
1322 return;
1323 }
1324
1325 // Reserve space in the edge set so pointers don't get invalidated during
1326 // the whole fracture process; one for each original edge, plus 3 per
1327 // path to join it to the outline.
1328 edges.reserve( total_point_count + paths.size() * 3 );
1329
1330 // Sort the paths by their lowest X bound before processing them.
1331 // This ensures the processing order for processEdge() is correct.
1332 struct PathInfo
1333 {
1334 int path_or_provoking_index;
1335 FractureEdge::Index leftmost;
1336 int x;
1337 int y_or_bridge;
1338 };
1339 std::vector<PathInfo> sorted_paths;
1340 const int paths_count = static_cast<int>( paths.size() );
1341 sorted_paths.reserve( paths_count );
1342
1343 for( int path_index = 0; path_index < paths_count; path_index++ )
1344 {
1345 const SHAPE_LINE_CHAIN& path = paths[path_index];
1346 const std::vector<VECTOR2I>& points = path.CPoints();
1347 const int point_count = static_cast<int>( points.size() );
1348 int x_min = std::numeric_limits<int>::max();
1349 int y_min = std::numeric_limits<int>::max();
1350 int leftmost = -1;
1351
1352 for( int point_index = 0; point_index < point_count; point_index++ )
1353 {
1354 const VECTOR2I& point = points[point_index];
1355 if( point.x < x_min )
1356 {
1357 x_min = point.x;
1358 leftmost = point_index;
1359 }
1360 if( point.y < y_min )
1361 y_min = point.y;
1362 }
1363
1364 sorted_paths.emplace_back( PathInfo{ path_index, leftmost, x_min, y_min } );
1365 }
1366
1367 std::sort( sorted_paths.begin() + 1, sorted_paths.end(),
1368 []( const PathInfo& a, const PathInfo& b )
1369 {
1370 if( a.x == b.x )
1371 return a.y_or_bridge < b.y_or_bridge;
1372 return a.x < b.x;
1373 } );
1374
1375 FractureEdge::Index edge_index = 0;
1376
1377 for( PathInfo& path_info : sorted_paths )
1378 {
1379 const SHAPE_LINE_CHAIN& path = paths[path_info.path_or_provoking_index];
1380 const std::vector<VECTOR2I>& points = path.CPoints();
1381 const size_t point_count = points.size();
1382
1383 // Index of the provoking (first) edge for this path
1384 const FractureEdge::Index provoking_edge = edge_index;
1385
1386 for( size_t i = 0; i < point_count - 1; i++ )
1387 {
1388 edges.emplace_back( points[i], points[i + 1], edge_index + 1 );
1389 edge_index++;
1390 }
1391
1392 // Create last edge looping back to the provoking one.
1393 edges.emplace_back( points[point_count - 1], points[0], provoking_edge );
1394 edge_index++;
1395
1396 if( !outline )
1397 {
1398 // Repurpose the path sorting data structure to schedule the leftmost edge
1399 // for merging to the outline, which will in turn merge the rest of the path.
1400 path_info.path_or_provoking_index = provoking_edge;
1401 path_info.y_or_bridge = edge_index;
1402
1403 // Reserve 3 additional edges to bridge with the outline.
1404 edge_index += 3;
1405 edges.resize( edge_index );
1406 }
1407
1408 outline = false; // first path is always the outline
1409 }
1410
1411 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); it++ )
1412 {
1413 auto edge = processHole( edges, it->path_or_provoking_index,
1414 it->path_or_provoking_index + it->leftmost, it->y_or_bridge );
1415
1416 // If we can't handle the hole, the zone is broken (maybe)
1417 if( !edge )
1418 {
1419 wxLogWarning( wxT( "Broken polygon, dropping path" ) );
1420
1421 return;
1422 }
1423 }
1424
1425 paths.resize( 1 );
1426 SHAPE_LINE_CHAIN& newPath = paths[0];
1427
1428 newPath.Clear();
1429 newPath.SetClosed( true );
1430
1431 // Root edge is always at index 0
1432 FractureEdge* e = &edges[0];
1433
1434 for( ; e->m_next != 0; e = &edges[e->m_next] )
1435 newPath.Append( e->m_p1 );
1436
1437 newPath.Append( e->m_p1 );
1438}
1439
1440
1442{
1443 FractureEdgeSlow( int y = 0 ) : m_connected( false ), m_next( nullptr ) { m_p1.x = m_p2.y = y; }
1444
1445 FractureEdgeSlow( bool connected, const VECTOR2I& p1, const VECTOR2I& p2 ) :
1446 m_connected( connected ), m_p1( p1 ), m_p2( p2 ), m_next( nullptr )
1447 {
1448 }
1449
1450 bool matches( int y ) const
1451 {
1452 return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
1453 }
1454
1459};
1460
1461
1462typedef std::vector<FractureEdgeSlow*> FractureEdgeSetSlow;
1463
1464
1466{
1467 int x = edge->m_p1.x;
1468 int y = edge->m_p1.y;
1469 int min_dist = std::numeric_limits<int>::max();
1470 int x_nearest = 0;
1471
1472 FractureEdgeSlow* e_nearest = nullptr;
1473
1474 for( FractureEdgeSlow* e : edges )
1475 {
1476 if( !e->matches( y ) )
1477 continue;
1478
1479 int x_intersect;
1480
1481 if( e->m_p1.y == e->m_p2.y ) // horizontal edge
1482 {
1483 x_intersect = std::max( e->m_p1.x, e->m_p2.x );
1484 }
1485 else
1486 {
1487 x_intersect = e->m_p1.x
1488 + rescale( e->m_p2.x - e->m_p1.x, y - e->m_p1.y, e->m_p2.y - e->m_p1.y );
1489 }
1490
1491 int dist = ( x - x_intersect );
1492
1493 if( dist >= 0 && dist < min_dist && e->m_connected )
1494 {
1495 min_dist = dist;
1496 x_nearest = x_intersect;
1497 e_nearest = e;
1498 }
1499 }
1500
1501 if( e_nearest && e_nearest->m_connected )
1502 {
1503 int count = 0;
1504
1505 FractureEdgeSlow* lead1 =
1506 new FractureEdgeSlow( true, VECTOR2I( x_nearest, y ), VECTOR2I( x, y ) );
1507 FractureEdgeSlow* lead2 =
1508 new FractureEdgeSlow( true, VECTOR2I( x, y ), VECTOR2I( x_nearest, y ) );
1509 FractureEdgeSlow* split_2 =
1510 new FractureEdgeSlow( true, VECTOR2I( x_nearest, y ), e_nearest->m_p2 );
1511
1512 edges.push_back( split_2 );
1513 edges.push_back( lead1 );
1514 edges.push_back( lead2 );
1515
1516 FractureEdgeSlow* link = e_nearest->m_next;
1517
1518 e_nearest->m_p2 = VECTOR2I( x_nearest, y );
1519 e_nearest->m_next = lead1;
1520 lead1->m_next = edge;
1521
1522 FractureEdgeSlow* last;
1523
1524 for( last = edge; last->m_next != edge; last = last->m_next )
1525 {
1526 last->m_connected = true;
1527 count++;
1528 }
1529
1530 last->m_connected = true;
1531 last->m_next = lead2;
1532 lead2->m_next = split_2;
1533 split_2->m_next = link;
1534
1535 return count + 1;
1536 }
1537
1538 return 0;
1539}
1540
1541
1543{
1544 FractureEdgeSetSlow edges;
1545 FractureEdgeSetSlow border_edges;
1546 FractureEdgeSlow* root = nullptr;
1547
1548 bool first = true;
1549
1550 if( paths.size() == 1 )
1551 return;
1552
1553 int num_unconnected = 0;
1554
1555 for( const SHAPE_LINE_CHAIN& path : paths )
1556 {
1557 const std::vector<VECTOR2I>& points = path.CPoints();
1558 int pointCount = points.size();
1559
1560 FractureEdgeSlow *prev = nullptr, *first_edge = nullptr;
1561
1562 int x_min = std::numeric_limits<int>::max();
1563
1564 for( int i = 0; i < pointCount; i++ )
1565 {
1566 if( points[i].x < x_min )
1567 x_min = points[i].x;
1568
1569 // Do not use path.CPoint() here; open-coding it using the local variables "points"
1570 // and "pointCount" gives a non-trivial performance boost to zone fill times.
1571 FractureEdgeSlow* fe = new FractureEdgeSlow( first, points[i],
1572 points[i + 1 == pointCount ? 0 : i + 1] );
1573
1574 if( !root )
1575 root = fe;
1576
1577 if( !first_edge )
1578 first_edge = fe;
1579
1580 if( prev )
1581 prev->m_next = fe;
1582
1583 if( i == pointCount - 1 )
1584 fe->m_next = first_edge;
1585
1586 prev = fe;
1587 edges.push_back( fe );
1588
1589 if( !first )
1590 {
1591 if( fe->m_p1.x == x_min )
1592 border_edges.push_back( fe );
1593 }
1594
1595 if( !fe->m_connected )
1596 num_unconnected++;
1597 }
1598
1599 first = false; // first path is always the outline
1600 }
1601
1602 // keep connecting holes to the main outline, until there's no holes left...
1603 while( num_unconnected > 0 )
1604 {
1605 int x_min = std::numeric_limits<int>::max();
1606 auto it = border_edges.begin();
1607
1608 FractureEdgeSlow* smallestX = nullptr;
1609
1610 // find the left-most hole edge and merge with the outline
1611 for( ; it != border_edges.end(); ++it )
1612 {
1613 FractureEdgeSlow* border_edge = *it;
1614 int xt = border_edge->m_p1.x;
1615
1616 if( ( xt <= x_min ) && !border_edge->m_connected )
1617 {
1618 x_min = xt;
1619 smallestX = border_edge;
1620 }
1621 }
1622
1623 int num_processed = processEdge( edges, smallestX );
1624
1625 // If we can't handle the edge, the zone is broken (maybe)
1626 if( !num_processed )
1627 {
1628 wxLogWarning( wxT( "Broken polygon, dropping path" ) );
1629
1630 for( FractureEdgeSlow* edge : edges )
1631 delete edge;
1632
1633 return;
1634 }
1635
1636 num_unconnected -= num_processed;
1637 }
1638
1639 paths.clear();
1640 SHAPE_LINE_CHAIN newPath;
1641
1642 newPath.SetClosed( true );
1643
1645
1646 for( e = root; e->m_next != root; e = e->m_next )
1647 newPath.Append( e->m_p1 );
1648
1649 newPath.Append( e->m_p1 );
1650
1651 for( FractureEdgeSlow* edge : edges )
1652 delete edge;
1653
1654 paths.push_back( std::move( newPath ) );
1655}
1656
1657
1659{
1661 return fractureSingleCacheFriendly( paths );
1662 fractureSingleSlow( paths );
1663}
1664
1665
1666void SHAPE_POLY_SET::Fracture( bool aSimplify )
1667{
1668 if( aSimplify )
1669 Simplify(); // remove overlapping holes/degeneracy
1670
1671 for( POLYGON& paths : m_polys )
1672 fractureSingle( paths );
1673}
1674
1675
1677{
1678 assert( aPoly.size() == 1 );
1679
1680 struct EDGE
1681 {
1682 int m_index = 0;
1683 SHAPE_LINE_CHAIN* m_poly = nullptr;
1684 bool m_duplicate = false;
1685
1686 EDGE( SHAPE_LINE_CHAIN* aPolygon, int aIndex ) :
1687 m_index( aIndex ),
1688 m_poly( aPolygon )
1689 {}
1690
1691 bool compareSegs( const SEG& s1, const SEG& s2 ) const
1692 {
1693 return (s1.A == s2.B && s1.B == s2.A);
1694 }
1695
1696 bool operator==( const EDGE& aOther ) const
1697 {
1698 return compareSegs( m_poly->CSegment( m_index ),
1699 aOther.m_poly->CSegment( aOther.m_index ) );
1700 }
1701
1702 bool operator!=( const EDGE& aOther ) const
1703 {
1704 return !compareSegs( m_poly->CSegment( m_index ),
1705 aOther.m_poly->CSegment( aOther.m_index ) );
1706 }
1707
1708 struct HASH
1709 {
1710 std::size_t operator()( const EDGE& aEdge ) const
1711 {
1712 const SEG& a = aEdge.m_poly->CSegment( aEdge.m_index );
1713 std::size_t seed = 0xa82de1c0;
1714 hash_combine( seed, a.A.x, a.B.x, a.A.y, a.B.y );
1715 return seed;
1716 }
1717 };
1718 };
1719
1720 struct EDGE_LIST_ENTRY
1721 {
1722 int index;
1723 EDGE_LIST_ENTRY* next;
1724 };
1725
1726 std::unordered_set<EDGE, EDGE::HASH> uniqueEdges;
1727
1728 SHAPE_LINE_CHAIN lc = aPoly[0];
1729 lc.Simplify();
1730
1731 auto edgeList = std::make_unique<EDGE_LIST_ENTRY[]>( lc.SegmentCount() );
1732
1733 for( int i = 0; i < lc.SegmentCount(); i++ )
1734 {
1735 edgeList[i].index = i;
1736 edgeList[i].next = &edgeList[ (i != lc.SegmentCount() - 1) ? i + 1 : 0 ];
1737 }
1738
1739 std::unordered_set<EDGE_LIST_ENTRY*> queue;
1740
1741 for( int i = 0; i < lc.SegmentCount(); i++ )
1742 {
1743 EDGE e( &lc, i );
1744 uniqueEdges.insert( e );
1745 }
1746
1747 for( int i = 0; i < lc.SegmentCount(); i++ )
1748 {
1749 EDGE e( &lc, i );
1750 auto it = uniqueEdges.find( e );
1751
1752 if( it != uniqueEdges.end() && it->m_index != i )
1753 {
1754 int e1 = it->m_index;
1755 int e2 = i;
1756
1757 if( e1 > e2 )
1758 std::swap( e1, e2 );
1759
1760 int e1_prev = e1 - 1;
1761
1762 if( e1_prev < 0 )
1763 e1_prev = lc.SegmentCount() - 1;
1764
1765 int e2_prev = e2 - 1;
1766
1767 if( e2_prev < 0 )
1768 e2_prev = lc.SegmentCount() - 1;
1769
1770 int e1_next = e1 + 1;
1771
1772 if( e1_next == lc.SegmentCount() )
1773 e1_next = 0;
1774
1775 int e2_next = e2 + 1;
1776
1777 if( e2_next == lc.SegmentCount() )
1778 e2_next = 0;
1779
1780 edgeList[e1_prev].next = &edgeList[ e2_next ];
1781 edgeList[e2_prev].next = &edgeList[ e1_next ];
1782 edgeList[i].next = nullptr;
1783 edgeList[it->m_index].next = nullptr;
1784 }
1785 }
1786
1787 for( int i = 0; i < lc.SegmentCount(); i++ )
1788 {
1789 if( edgeList[i].next )
1790 queue.insert( &edgeList[i] );
1791 }
1792
1793 auto edgeBuf = std::make_unique<EDGE_LIST_ENTRY* []>( lc.SegmentCount() );
1794
1795 int n = 0;
1796 int outline = -1;
1797
1799 double max_poly = 0.0;
1800
1801 while( queue.size() )
1802 {
1803 EDGE_LIST_ENTRY* e_first = *queue.begin();
1804 EDGE_LIST_ENTRY* e = e_first;
1805 int cnt = 0;
1806
1807 do
1808 {
1809 edgeBuf[cnt++] = e;
1810 e = e->next;
1811 } while( e && e != e_first );
1812
1813 SHAPE_LINE_CHAIN outl;
1814
1815 for( int i = 0; i < cnt; i++ )
1816 {
1817 VECTOR2I p = lc.CPoint( edgeBuf[i]->index );
1818 outl.Append( p );
1819 queue.erase( edgeBuf[i] );
1820 }
1821
1822 outl.SetClosed( true );
1823
1824 double area = std::fabs( outl.Area() );
1825
1826 if( area > max_poly )
1827 {
1828 outline = n;
1829 max_poly = area;
1830 }
1831
1832 result.push_back( outl );
1833 n++;
1834 }
1835
1836 if( outline > 0 )
1837 std::swap( result[0], result[outline] );
1838
1839 aPoly = std::move( result );
1840}
1841
1842
1844{
1845 // Iterate through all the polygons on the set
1846 for( const POLYGON& paths : m_polys )
1847 {
1848 // If any of them has more than one contour, it is a hole.
1849 if( paths.size() > 1 )
1850 return true;
1851 }
1852
1853 // Return false if and only if every polygon has just one outline, without holes.
1854 return false;
1855}
1856
1857
1859{
1860 for( POLYGON& path : m_polys )
1862
1863 Simplify(); // remove overlapping holes/degeneracy
1864}
1865
1866
1867bool SHAPE_POLY_SET::isExteriorWaist( const SEG& aSegA, const SEG& aSegB ) const
1868{
1869 const VECTOR2I da = aSegA.B - aSegA.A;
1870
1871 int axis = std::abs( da.x ) >= std::abs( da.y ) ? 0 : 1;
1872
1873 std::array<VECTOR2I,4> pts = { aSegA.A, aSegA.B, aSegB.A, aSegB.B };
1874
1875 std::sort( pts.begin(), pts.end(), [axis]( const VECTOR2I& p, const VECTOR2I& q )
1876 {
1877 if( axis == 0 )
1878 return p.x < q.x || ( p.x == q.x && p.y < q.y );
1879 else
1880 return p.y < q.y || ( p.y == q.y && p.x < q.x );
1881 } );
1882
1883 VECTOR2I s = pts[1];
1884 VECTOR2I e = pts[2];
1885
1886 // Check if there is polygon material on either side of the overlapping segments
1887 // Get the midpoint between s and e for testing
1888 VECTOR2I midpoint = ( s + e ) / 2;
1889
1890 // Create perpendicular offset vector to check both sides
1891 VECTOR2I segDir = e - s;
1892
1893 if( segDir.EuclideanNorm() > 25 )
1894 {
1895 VECTOR2I perp = segDir.Perpendicular().Resize( 10 );
1896
1897 // Both sides must be outside for this to be an exterior waist. Short-circuit if
1898 // either side is inside the polygon to avoid the second O(N) point-in-polygon test.
1899 if( !PointInside( midpoint + perp ) && !PointInside( midpoint - perp ) )
1900 {
1901 wxLogTrace( wxT( "collinear" ), wxT( "Found exterior waist between (%d,%d)-(%d,%d) and (%d,%d)-(%d,%d)" ),
1902 aSegA.A.x, aSegA.A.y, aSegA.B.x, aSegA.B.y,
1903 aSegB.A.x, aSegB.A.y, aSegB.B.x, aSegB.B.y );
1904 return true;
1905 }
1906 }
1907
1908 return false;
1909}
1910
1911
1913{
1914 for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
1915 {
1916 bool changed = true;
1917
1918 while( changed )
1919 {
1920 changed = false;
1921
1922 SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
1923 intptr_t count = outline.PointCount();
1924
1926
1927 for( intptr_t i = 0; i < count; ++i )
1928 {
1929 const VECTOR2I& a = outline.CPoint( i );
1930 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
1931 intptr_t min[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
1932 intptr_t max[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
1933 rtree.Insert( min, max, i );
1934 }
1935
1936 bool found = false;
1937 int segA = -1;
1938 int segB = -1;
1939
1940 for( intptr_t i = 0; i < count && !found; ++i )
1941 {
1942 const VECTOR2I& a = outline.CPoint( i );
1943 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
1944 SEG seg( a, b );
1945 intptr_t min[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
1946 intptr_t max[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
1947
1948 auto visitor =
1949 [&]( const intptr_t& j ) -> bool
1950 {
1951 if( j == i || j == ( ( i + 1 ) % count ) || j == ( ( i + count - 1 ) % count ) )
1952 return true;
1953
1954 VECTOR2I oa = outline.CPoint( j );
1955 VECTOR2I ob = outline.CPoint( ( j + 1 ) % count );
1956 SEG other( oa, ob );
1957
1958 // Skip segments that share start/end points. This is the case for
1959 // fractured segments
1960 if( oa == a && ob == b )
1961 return true;
1962
1963 if( oa == b && ob == a )
1964 return true;
1965
1966 if( seg.ApproxCollinear( other, 10 ) && isExteriorWaist( seg, other ) )
1967 {
1968 segA = i;
1969 segB = j;
1970 found = true;
1971 return false;
1972 }
1973
1974 return true;
1975 };
1976
1977 rtree.Search( min, max, visitor );
1978 }
1979
1980 if( !found )
1981 break;
1982
1983 int a0 = segA;
1984 int a1 = ( segA + 1 ) % outline.PointCount();
1985 int b0 = segB;
1986 int b1 = ( segB + 1 ) % outline.PointCount();
1987
1988 SHAPE_LINE_CHAIN lc1;
1989 int idx = a1;
1990 lc1.Append( outline.CPoint( idx ) );
1991
1992 while( idx != b0 )
1993 {
1994 idx = ( idx + 1 ) % outline.PointCount();
1995 lc1.Append( outline.CPoint( idx ) );
1996 }
1997
1998 lc1.SetClosed( true );
1999
2000 SHAPE_LINE_CHAIN lc2;
2001 idx = b1;
2002 lc2.Append( outline.CPoint( idx ) );
2003
2004 while( idx != a0 )
2005 {
2006 idx = ( idx + 1 ) % outline.PointCount();
2007 lc2.Append( outline.CPoint( idx ) );
2008 }
2009
2010 lc2.SetClosed( true );
2011
2012 m_polys[polyIdx][0] = std::move( lc1 );
2013
2014 POLYGON np;
2015 np.push_back( std::move( lc2 ) );
2016 m_polys.push_back( std::move( np ) );
2017
2018 changed = true;
2019 }
2020 }
2021}
2022
2023
2025{
2026 for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
2027 {
2028 bool changed = true;
2029
2030 while( changed )
2031 {
2032 changed = false;
2033
2034 SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
2035 const int count = outline.PointCount();
2036
2037 if( count < 4 )
2038 break;
2039
2040 int insertSegIdx = -1;
2041 int insertVertIdx = -1;
2042
2043 // For small polygons, direct O(n²) search is faster than R-tree overhead
2044 constexpr int RTREE_THRESHOLD = 32;
2045
2046 if( count < RTREE_THRESHOLD )
2047 {
2048 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2049 {
2050 const VECTOR2I& pt = outline.CPoint( vertIdx );
2051 const int prevSeg = ( vertIdx + count - 1 ) % count;
2052
2053 for( int segIdx = 0; segIdx < count; ++segIdx )
2054 {
2055 // Skip adjacent segments
2056 if( segIdx == prevSeg || segIdx == vertIdx )
2057 continue;
2058
2059 const VECTOR2I& a = outline.CPoint( segIdx );
2060 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2061
2062 // SquaredDistance returns 0 only when pt lies exactly on the
2063 // segment. Clipper2 rounds corridor-cut vertices to integer
2064 // coordinates; they can land within 1nm of an endpoint but are
2065 // not true pinch points.
2066 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0 )
2067 {
2068 insertSegIdx = segIdx;
2069 insertVertIdx = vertIdx;
2070 break;
2071 }
2072 }
2073 }
2074 }
2075 else
2076 {
2078
2079 for( int i = 0; i < count; ++i )
2080 {
2081 const VECTOR2I& a = outline.CPoint( i );
2082 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
2083 int bmin[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
2084 int bmax[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
2085 rtree.Insert( bmin, bmax, i );
2086 }
2087
2088 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2089 {
2090 const VECTOR2I& pt = outline.CPoint( vertIdx );
2091 const int prevSeg = ( vertIdx + count - 1 ) % count;
2092 int bmin[2] = { pt.x, pt.y };
2093 int bmax[2] = { pt.x, pt.y };
2094
2095 auto pinchVisitor =
2096 [&]( const intptr_t& segIdx ) -> bool
2097 {
2098 if( segIdx == prevSeg || segIdx == vertIdx )
2099 return true;
2100
2101 const VECTOR2I& a = outline.CPoint( segIdx );
2102 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2103
2104 // SquaredDistance returns 0 only when pt lies exactly on the
2105 // segment. Clipper2 rounds corridor-cut vertices to integer
2106 // coordinates; they can land within 1nm of an endpoint but
2107 // are not true pinch points.
2108 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0 )
2109 {
2110 insertSegIdx = segIdx;
2111 insertVertIdx = vertIdx;
2112 return false;
2113 }
2114
2115 return true;
2116 };
2117
2118 rtree.Search( bmin, bmax, pinchVisitor );
2119 }
2120 }
2121
2122 if( insertSegIdx < 0 )
2123 break;
2124
2125 // Split the polygon at the pinch point into two separate polygons.
2126 // Polygon 1: vertices from (insertSegIdx+1) to insertVertIdx
2127 // Polygon 2: vertices from insertVertIdx to insertSegIdx
2128
2129 const int splitStart1 = ( insertSegIdx + 1 ) % count;
2130
2131 // Calculate sizes for each polygon
2132 int size1, size2;
2133
2134 if( insertVertIdx >= splitStart1 )
2135 size1 = insertVertIdx - splitStart1 + 1;
2136 else
2137 size1 = count - splitStart1 + insertVertIdx + 1;
2138
2139 if( insertSegIdx >= insertVertIdx )
2140 size2 = insertSegIdx - insertVertIdx + 1;
2141 else
2142 size2 = count - insertVertIdx + insertSegIdx + 1;
2143
2144 if( size1 < 3 || size2 < 3 )
2145 break;
2146
2147 SHAPE_LINE_CHAIN poly1;
2148 SHAPE_LINE_CHAIN poly2;
2149 poly1.ReservePoints( size1 );
2150 poly2.ReservePoints( size2 );
2151
2152 int idx = splitStart1;
2153
2154 for( int i = 0; i < size1; ++i )
2155 {
2156 poly1.Append( outline.CPoint( idx ) );
2157 idx = ( idx + 1 ) % count;
2158 }
2159
2160 poly1.SetClosed( true );
2161
2162 idx = insertVertIdx;
2163
2164 for( int i = 0; i < size2; ++i )
2165 {
2166 poly2.Append( outline.CPoint( idx ) );
2167 idx = ( idx + 1 ) % count;
2168 }
2169
2170 poly2.SetClosed( true );
2171
2172 m_polys[polyIdx][0] = std::move( poly1 );
2173
2174 POLYGON np;
2175 np.push_back( std::move( poly2 ) );
2176 m_polys.push_back( std::move( np ) );
2177
2178 changed = true;
2179 }
2180 }
2181}
2182
2183
2185{
2187
2189
2190 booleanOp( Clipper2Lib::ClipType::Union, empty );
2191}
2192
2193
2195{
2196 for( POLYGON& paths : m_polys )
2197 {
2198 for( SHAPE_LINE_CHAIN& path : paths )
2199 {
2200 path.Simplify( aTolerance );
2201 }
2202 }
2203}
2204
2205
2207{
2208 // We are expecting only one main outline, but this main outline can have holes
2209 // if holes: combine holes and remove them from the main outline.
2210 // Note also we are usingin polygon
2211 // calculations, but it is not mandatory. It is used mainly
2212 // because there is usually only very few vertices in area outlines
2213 SHAPE_POLY_SET::POLYGON& outline = Polygon( 0 );
2214 SHAPE_POLY_SET holesBuffer;
2215
2216 // Move holes stored in outline to holesBuffer:
2217 // The first SHAPE_LINE_CHAIN is the main outline, others are holes
2218 while( outline.size() > 1 )
2219 {
2220 holesBuffer.AddOutline( outline.back() );
2221 outline.pop_back();
2222 }
2223
2224 Simplify();
2225
2226 // If any hole, subtract it to main outline
2227 if( holesBuffer.OutlineCount() )
2228 {
2229 holesBuffer.Simplify();
2230 BooleanSubtract( holesBuffer );
2231 }
2232
2233 // In degenerate cases, simplify might return no outlines
2234 if( OutlineCount() > 0 )
2236
2237 return OutlineCount();
2238}
2239
2240
2241const std::string SHAPE_POLY_SET::Format( bool aCplusPlus ) const
2242{
2243 std::stringstream ss;
2244
2245 ss << "SHAPE_LINE_CHAIN poly; \n";
2246
2247 for( unsigned i = 0; i < m_polys.size(); i++ )
2248 {
2249 for( unsigned j = 0; j < m_polys[i].size(); j++ )
2250 {
2251
2252 ss << "{ auto tmp = " << m_polys[i][j].Format() << ";\n";
2253
2254 SHAPE_POLY_SET poly;
2255
2256 if( j == 0 )
2257 {
2258 ss << " poly.AddOutline(tmp); } \n";
2259 }
2260 else
2261 {
2262 ss << " poly.AddHole(tmp); } \n";
2263 }
2264
2265 }
2266 }
2267
2268 return ss.str();
2269}
2270
2271
2272bool SHAPE_POLY_SET::Parse( std::stringstream& aStream )
2273{
2274 std::string tmp;
2275
2276 aStream >> tmp;
2277
2278 if( tmp != "polyset" )
2279 return false;
2280
2281 aStream >> tmp;
2282
2283 int n_polys = atoi( tmp.c_str() );
2284
2285 if( n_polys < 0 )
2286 return false;
2287
2288 for( int i = 0; i < n_polys; i++ )
2289 {
2290 POLYGON paths;
2291
2292 aStream >> tmp;
2293
2294 if( tmp != "poly" )
2295 return false;
2296
2297 aStream >> tmp;
2298 int n_outlines = atoi( tmp.c_str() );
2299
2300 if( n_outlines < 0 )
2301 return false;
2302
2303 for( int j = 0; j < n_outlines; j++ )
2304 {
2305 SHAPE_LINE_CHAIN outline;
2306
2307 outline.SetClosed( true );
2308
2309 aStream >> tmp;
2310 int n_vertices = atoi( tmp.c_str() );
2311
2312 for( int v = 0; v < n_vertices; v++ )
2313 {
2314 VECTOR2I p;
2315
2316 aStream >> tmp; p.x = atoi( tmp.c_str() );
2317 aStream >> tmp; p.y = atoi( tmp.c_str() );
2318 outline.Append( p );
2319 }
2320
2321 paths.push_back( std::move( outline ) );
2322 }
2323
2324 m_polys.push_back( std::move( paths ) );
2325 }
2326
2327 return true;
2328}
2329
2330
2331const BOX2I SHAPE_POLY_SET::BBox( int aClearance ) const
2332{
2333 BOX2I bb;
2334
2335 for( unsigned i = 0; i < m_polys.size(); i++ )
2336 {
2337 if( i == 0 )
2338 bb = m_polys[i][0].BBox();
2339 else
2340 bb.Merge( m_polys[i][0].BBox() );
2341 }
2342
2343 bb.Inflate( aClearance );
2344 return bb;
2345}
2346
2347
2349{
2350 BOX2I bb;
2351
2352 for( unsigned i = 0; i < m_polys.size(); i++ )
2353 {
2354 if( i == 0 )
2355 bb = *m_polys[i][0].GetCachedBBox();
2356 else
2357 bb.Merge( *m_polys[i][0].GetCachedBBox() );
2358 }
2359
2360 return bb;
2361}
2362
2363
2364bool SHAPE_POLY_SET::PointOnEdge( const VECTOR2I& aP, int aAccuracy ) const
2365{
2366 // Iterate through all the polygons in the set
2367 for( const POLYGON& polygon : m_polys )
2368 {
2369 // Iterate through all the line chains in the polygon
2370 for( const SHAPE_LINE_CHAIN& lineChain : polygon )
2371 {
2372 if( lineChain.PointOnEdge( aP, aAccuracy ) )
2373 return true;
2374 }
2375 }
2376
2377 return false;
2378}
2379
2380
2381bool SHAPE_POLY_SET::Collide( const SEG& aSeg, int aClearance, int* aActual,
2382 VECTOR2I* aLocation ) const
2383{
2384 VECTOR2I nearest;
2385 ecoord dist_sq = SquaredDistanceToSeg( aSeg, aLocation ? &nearest : nullptr );
2386
2387 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2388 {
2389 if( aLocation )
2390 *aLocation = nearest;
2391
2392 if( aActual )
2393 *aActual = sqrt( dist_sq );
2394
2395 return true;
2396 }
2397
2398 return false;
2399}
2400
2401
2402bool SHAPE_POLY_SET::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
2403 VECTOR2I* aLocation ) const
2404{
2405 if( IsEmpty() || VertexCount() == 0 )
2406 return false;
2407
2408 VECTOR2I nearest;
2409 ecoord dist_sq = SquaredDistance( aP, false, aLocation ? &nearest : nullptr );
2410
2411 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2412 {
2413 if( aLocation )
2414 *aLocation = nearest;
2415
2416 if( aActual )
2417 *aActual = sqrt( dist_sq );
2418
2419 return true;
2420 }
2421
2422 return false;
2423}
2424
2425
2426bool SHAPE_POLY_SET::Collide( const SHAPE* aShape, int aClearance, int* aActual,
2427 VECTOR2I* aLocation ) const
2428{
2429 // A couple of simple cases are worth trying before we fall back on triangulation.
2430
2431 if( aShape->Type() == SH_SEGMENT )
2432 {
2433 const SHAPE_SEGMENT* segment = static_cast<const SHAPE_SEGMENT*>( aShape );
2434 int extra = segment->GetWidth() / 2;
2435
2436 if( Collide( segment->GetSeg(), aClearance + extra, aActual, aLocation ) )
2437 {
2438 if( aActual )
2439 *aActual = std::max( 0, *aActual - extra );
2440
2441 return true;
2442 }
2443
2444 return false;
2445 }
2446
2447 if( aShape->Type() == SH_CIRCLE )
2448 {
2449 const SHAPE_CIRCLE* circle = static_cast<const SHAPE_CIRCLE*>( aShape );
2450 int extra = circle->GetRadius();
2451
2452 if( Collide( circle->GetCenter(), aClearance + extra, aActual, aLocation ) )
2453 {
2454 if( aActual )
2455 *aActual = std::max( 0, *aActual - extra );
2456
2457 return true;
2458 }
2459
2460 return false;
2461 }
2462
2463 const_cast<SHAPE_POLY_SET*>( this )->CacheTriangulation();
2464
2465 int actual = INT_MAX;
2467
2468 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
2469 {
2470 for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
2471 {
2472 if( aActual || aLocation )
2473 {
2474 int triActual;
2475 VECTOR2I triLocation;
2476
2477 if( aShape->Collide( &tri, aClearance, &triActual, &triLocation ) )
2478 {
2479 if( triActual < actual )
2480 {
2481 actual = triActual;
2482 location = triLocation;
2483 }
2484 }
2485 }
2486 else // A much faster version of above
2487 {
2488 if( aShape->Collide( &tri, aClearance ) )
2489 return true;
2490 }
2491 }
2492 }
2493
2494 if( actual < INT_MAX )
2495 {
2496 if( aActual )
2497 *aActual = std::max( 0, actual );
2498
2499 if( aLocation )
2500 *aLocation = location;
2501
2502 return true;
2503 }
2504
2505 return false;
2506}
2507
2508
2510{
2511 m_polys.clear();
2512 m_triangulatedPolys.clear();
2513 m_triangulationValid = false;
2514}
2515
2516
2517void SHAPE_POLY_SET::RemoveContour( int aContourIdx, int aPolygonIdx )
2518{
2519 // Default polygon is the last one
2520 if( aPolygonIdx < 0 )
2521 aPolygonIdx += m_polys.size();
2522
2523 m_polys[aPolygonIdx].erase( m_polys[aPolygonIdx].begin() + aContourIdx );
2524}
2525
2526
2527void SHAPE_POLY_SET::RemoveOutline( int aOutlineIdx )
2528{
2529 m_polys.erase( m_polys.begin() + aOutlineIdx );
2530}
2531
2532
2534{
2535 int removed = 0;
2536
2537 ITERATOR iterator = IterateWithHoles();
2538
2539 VECTOR2I contourStart = *iterator;
2540 VECTOR2I segmentStart, segmentEnd;
2541
2542 VERTEX_INDEX indexStart;
2543 std::vector<VERTEX_INDEX> indices_to_remove;
2544
2545 while( iterator )
2546 {
2547 // Obtain first point and its index
2548 segmentStart = *iterator;
2549 indexStart = iterator.GetIndex();
2550
2551 // Obtain last point
2552 if( iterator.IsEndContour() )
2553 {
2554 segmentEnd = contourStart;
2555
2556 // Advance
2557 iterator++;
2558
2559 // If we have rolled into the next contour, remember its position
2560 // segmentStart and segmentEnd remain valid for comparison here
2561 if( iterator )
2562 contourStart = *iterator;
2563 }
2564 else
2565 {
2566 // Advance
2567 iterator++;
2568
2569 // If we have reached the end of the SHAPE_POLY_SET, something is broken here
2570 wxCHECK_MSG( iterator, removed, wxT( "Invalid polygon. Reached end without noticing. Please report this error" ) );
2571
2572 segmentEnd = *iterator;
2573 }
2574
2575 // Remove segment start if both points are equal
2576 if( segmentStart == segmentEnd )
2577 {
2578 indices_to_remove.push_back( indexStart );
2579 removed++;
2580 }
2581 }
2582
2583 // Proceed in reverse direction to remove the vertices because they are stored as absolute indices in a vector
2584 // Removing in reverse order preserves the remaining index values
2585 for( auto it = indices_to_remove.rbegin(); it != indices_to_remove.rend(); ++it )
2586 RemoveVertex( *it );
2587
2588 return removed;
2589}
2590
2591
2593{
2594 m_polys.erase( m_polys.begin() + aIdx );
2595}
2596
2597
2599{
2600 m_polys.erase( m_polys.begin() + aIdx );
2601
2603 {
2604 for( int ii = m_triangulatedPolys.size() - 1; ii >= 0; --ii )
2605 {
2606 std::unique_ptr<TRIANGULATED_POLYGON>& triangleSet = m_triangulatedPolys[ii];
2607
2608 if( triangleSet->GetSourceOutlineIndex() == aIdx )
2609 m_triangulatedPolys.erase( m_triangulatedPolys.begin() + ii );
2610 else if( triangleSet->GetSourceOutlineIndex() > aIdx )
2611 triangleSet->SetSourceOutlineIndex( triangleSet->GetSourceOutlineIndex() - 1 );
2612 }
2613
2614 if( aUpdateHash )
2615 {
2616 m_hash = checksum();
2617 m_hashValid = true;
2618 }
2619 }
2620}
2621
2622
2628
2629
2631{
2632 m_polys.insert( m_polys.end(), aSet.m_polys.begin(), aSet.m_polys.end() );
2633}
2634
2635
2636void SHAPE_POLY_SET::Append( const VECTOR2I& aP, int aOutline, int aHole )
2637{
2638 Append( aP.x, aP.y, aOutline, aHole );
2639}
2640
2641
2643 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
2644 int aClearance ) const
2645{
2646 // Shows whether there was a collision
2647 bool collision = false;
2648
2649 // Difference vector between each vertex and aPoint.
2651 ecoord distance_squared;
2652 ecoord clearance_squared = SEG::Square( aClearance );
2653
2654 for( CONST_ITERATOR iterator = CIterateWithHoles(); iterator; iterator++ )
2655 {
2656 // Get the difference vector between current vertex and aPoint
2657 delta = *iterator - aPoint;
2658
2659 // Compute distance
2660 distance_squared = delta.SquaredEuclideanNorm();
2661
2662 // Check for collisions
2663 if( distance_squared <= clearance_squared )
2664 {
2665 if( !aClosestVertex )
2666 return true;
2667
2668 collision = true;
2669
2670 // Update clearance to look for closer vertices
2671 clearance_squared = distance_squared;
2672
2673 // Store the indices that identify the vertex
2674 *aClosestVertex = iterator.GetIndex();
2675 }
2676 }
2677
2678 return collision;
2679}
2680
2681
2683 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
2684 int aClearance ) const
2685{
2686 // Shows whether there was a collision
2687 bool collision = false;
2688 ecoord clearance_squared = SEG::Square( aClearance );
2689
2690 for( CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
2691 {
2692 const SEG currentSegment = *iterator;
2693 ecoord distance_squared = currentSegment.SquaredDistance( aPoint );
2694
2695 // Check for collisions
2696 if( distance_squared <= clearance_squared )
2697 {
2698 if( !aClosestVertex )
2699 return true;
2700
2701 collision = true;
2702
2703 // Update clearance to look for closer edges
2704 clearance_squared = distance_squared;
2705
2706 // Store the indices that identify the vertex
2707 *aClosestVertex = iterator.GetIndex();
2708 }
2709 }
2710
2711 return collision;
2712}
2713
2714
2716{
2717 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
2718 {
2719 COutline( polygonIdx ).GenerateBBoxCache();
2720
2721 for( int holeIdx = 0; holeIdx < HoleCount( polygonIdx ); holeIdx++ )
2722 CHole( polygonIdx, holeIdx ).GenerateBBoxCache();
2723 }
2724}
2725
2726
2727bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
2728 bool aUseBBoxCaches ) const
2729{
2730 if( m_polys.empty() )
2731 return false;
2732
2733 // If there is a polygon specified, check the condition against that polygon
2734 if( aSubpolyIndex >= 0 )
2735 return containsSingle( aP, aSubpolyIndex, aAccuracy, aUseBBoxCaches );
2736
2737 // In any other case, check it against all polygons in the set
2738 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
2739 {
2740 if( containsSingle( aP, polygonIdx, aAccuracy, aUseBBoxCaches ) )
2741 return true;
2742 }
2743
2744 return false;
2745}
2746
2747
2748void SHAPE_POLY_SET::RemoveVertex( int aGlobalIndex )
2749{
2751
2752 // Assure the to be removed vertex exists, abort otherwise
2753 if( GetRelativeIndices( aGlobalIndex, &index ) )
2755 else
2756 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
2757}
2758
2759
2761{
2762 m_polys[aIndex.m_polygon][aIndex.m_contour].Remove( aIndex.m_vertex );
2763}
2764
2765
2766void SHAPE_POLY_SET::SetVertex( int aGlobalIndex, const VECTOR2I& aPos )
2767{
2769
2770 if( GetRelativeIndices( aGlobalIndex, &index ) )
2771 SetVertex( index, aPos );
2772 else
2773 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
2774}
2775
2776
2777void SHAPE_POLY_SET::SetVertex( const VERTEX_INDEX& aIndex, const VECTOR2I& aPos )
2778{
2779 m_polys[aIndex.m_polygon][aIndex.m_contour].SetPoint( aIndex.m_vertex, aPos );
2780}
2781
2782
2783bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
2784 bool aUseBBoxCaches ) const
2785{
2786 // Check that the point is inside the outline
2787 if( m_polys[aSubpolyIndex][0].PointInside( aP, aAccuracy ) )
2788 {
2789 // Check that the point is not in any of the holes
2790 for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
2791 {
2792 const SHAPE_LINE_CHAIN& hole = CHole( aSubpolyIndex, holeIdx );
2793
2794 // If the point is inside a hole it is outside of the polygon. Do not use aAccuracy
2795 // here as it's meaning would be inverted.
2796 if( hole.PointInside( aP, 1, aUseBBoxCaches ) )
2797 return false;
2798 }
2799
2800 return true;
2801 }
2802
2803 return false;
2804}
2805
2806
2807void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
2808{
2809 for( POLYGON& poly : m_polys )
2810 {
2811 for( SHAPE_LINE_CHAIN& path : poly )
2812 path.Move( aVector );
2813 }
2814
2815 for( std::unique_ptr<TRIANGULATED_POLYGON>& tri : m_triangulatedPolys )
2816 tri->Move( aVector );
2817
2818 m_hash = checksum();
2819 m_hashValid = true;
2820}
2821
2822
2823void SHAPE_POLY_SET::Mirror( const VECTOR2I& aRef, FLIP_DIRECTION aFlipDirection )
2824{
2825 for( POLYGON& poly : m_polys )
2826 {
2827 for( SHAPE_LINE_CHAIN& path : poly )
2828 path.Mirror( aRef, aFlipDirection );
2829 }
2830
2833}
2834
2835
2836void SHAPE_POLY_SET::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
2837{
2838 for( POLYGON& poly : m_polys )
2839 {
2840 for( SHAPE_LINE_CHAIN& path : poly )
2841 path.Rotate( aAngle, aCenter );
2842 }
2843
2844 // Don't re-cache if the triangulation is already invalid
2847}
2848
2849
2851{
2852 int c = 0;
2853
2854 for( const POLYGON& poly : m_polys )
2855 {
2856 for( const SHAPE_LINE_CHAIN& path : poly )
2857 c += path.PointCount();
2858 }
2859
2860 return c;
2861}
2862
2863
2864SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex )
2865{
2866 return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0 );
2867}
2868
2869
2870SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax,
2871 int aIndex )
2872{
2873 return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax );
2874}
2875
2876
2878 VECTOR2I* aNearest ) const
2879{
2880 // We calculate the min dist between the segment and each outline segment. However, if the
2881 // segment to test is inside the outline, and does not cross any edge, it can be seen outside
2882 // the polygon. Therefore test if a segment end is inside (testing only one end is enough).
2883 // Use an accuracy of "1" to say that we don't care if it's exactly on the edge or not.
2884 if( containsSingle( aPoint, aPolygonIndex, 1 ) )
2885 {
2886 if( aNearest )
2887 *aNearest = aPoint;
2888
2889 return 0;
2890 }
2891
2892 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
2893
2894 SEG::ecoord minDistance = (*iterator).SquaredDistance( aPoint );
2895
2896 if( aNearest )
2897 *aNearest = ( *iterator ).NearestPoint( 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 )
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{
3602 const size_t triCount = m_triangles.size();
3603
3604 // Indices are int; skip an implausibly huge mesh rather than overflow.
3605 if( triCount < 2 || triCount > static_cast<size_t>( std::numeric_limits<int>::max() ) / 6 )
3606 return;
3607
3608 const int halfEdgeCount = static_cast<int>( 3 * triCount );
3609
3610 // heVertex[3k+j] is vertex j of triangle k.
3611 std::vector<int> heVertex( halfEdgeCount );
3612
3613 for( size_t k = 0; k < triCount; k++ )
3614 {
3615 heVertex[3 * k + 0] = m_triangles[k].a;
3616 heVertex[3 * k + 1] = m_triangles[k].b;
3617 heVertex[3 * k + 2] = m_triangles[k].c;
3618 }
3619
3620 auto nextInTriangle = []( int aEdge ) { return aEdge - aEdge % 3 + ( aEdge + 1 ) % 3; };
3621
3622 // An untwinned edge is a polygon boundary and must never flip. Slots are never tombstoned so
3623 // probe chains stay intact.
3624 std::vector<int> twin( halfEdgeCount, -1 );
3625
3626 int tableSize = 1;
3627
3628 while( tableSize < halfEdgeCount * 2 )
3629 tableSize <<= 1;
3630
3631 const uint32_t tableMask = static_cast<uint32_t>( tableSize - 1 );
3632 std::vector<int> slotEdge( tableSize, -1 );
3633
3634 // A non-manifold edge (three or more triangles, e.g. a hole-bridge pinch) is retired so no
3635 // triangle straddling it can flip.
3636 std::vector<char> retired( tableSize, 0 );
3637 std::vector<int> toVisit;
3638 std::vector<char> queued( halfEdgeCount, 0 );
3639
3640 for( int edge = 0; edge < halfEdgeCount; edge++ )
3641 {
3642 int from = heVertex[edge];
3643 int to = heVertex[nextInTriangle( edge )];
3644 uint32_t keyLow = static_cast<uint32_t>( from < to ? from : to );
3645 uint32_t keyHigh = static_cast<uint32_t>( from < to ? to : from );
3646 uint32_t slot = ( ( keyLow * 0x9e3779b1u ) ^ ( keyHigh * 0x85ebca6bu ) ) & tableMask;
3647 bool handled = false;
3648
3649 while( slotEdge[slot] != -1 )
3650 {
3651 int other = slotEdge[slot];
3652 int otherFrom = heVertex[other];
3653 int otherTo = heVertex[nextInTriangle( other )];
3654 uint32_t otherLow = static_cast<uint32_t>( otherFrom < otherTo ? otherFrom : otherTo );
3655 uint32_t otherHigh = static_cast<uint32_t>( otherFrom < otherTo ? otherTo : otherFrom );
3656
3657 if( otherLow == keyLow && otherHigh == keyHigh )
3658 {
3659 if( retired[slot] )
3660 {
3661 // Non-manifold; stays a boundary.
3662 }
3663 else if( twin[other] == -1 )
3664 {
3665 twin[edge] = other;
3666 twin[other] = edge;
3667 queued[other] = 1;
3668 toVisit.push_back( other );
3669 }
3670 else
3671 {
3672 // Third half-edge here: retire the edge and unlink the pair already formed.
3673 int mate = twin[other];
3674 twin[other] = -1;
3675 twin[mate] = -1;
3676 retired[slot] = 1;
3677 }
3678
3679 handled = true;
3680 break;
3681 }
3682
3683 slot = ( slot + 1 ) & tableMask;
3684 }
3685
3686 if( !handled )
3687 slotEdge[slot] = edge;
3688 }
3689
3690 while( !toVisit.empty() )
3691 {
3692 int edge = toVisit.back();
3693 toVisit.pop_back();
3694 queued[edge] = 0;
3695
3696 int twinEdge = twin[edge];
3697
3698 if( twinEdge == -1 )
3699 continue;
3700
3701 int tri = edge - edge % 3;
3702 int twinTri = twinEdge - twinEdge % 3;
3703
3704 int apexAEdge = tri + ( edge + 2 ) % 3;
3705 int sharedEndEdge = tri + ( edge + 1 ) % 3;
3706 int apexBEdge = twinTri + ( twinEdge + 2 ) % 3;
3707 int twinSharedEdge = twinTri + ( twinEdge + 1 ) % 3;
3708
3709 int apexA = heVertex[apexAEdge];
3710 int sharedFrom = heVertex[edge];
3711 int sharedTo = heVertex[sharedEndEdge];
3712 int apexB = heVertex[apexBEdge];
3713
3714 const VECTOR2I& apexAPt = m_vertices[apexA];
3715 const VECTOR2I& sharedFromPt = m_vertices[sharedFrom];
3716 const VECTOR2I& sharedToPt = m_vertices[sharedTo];
3717 const VECTOR2I& apexBPt = m_vertices[apexB];
3718
3719 // A non-convex quad would flip a triangle outside the polygon.
3720 if( KIGEOM::OrientationSign( apexAPt, sharedFromPt, apexBPt ) <= 0 )
3721 continue;
3722
3723 if( KIGEOM::OrientationSign( apexAPt, apexBPt, sharedToPt ) <= 0 )
3724 continue;
3725
3726 // Prefer the diagonal with fewer slivers, Delaunay breaks ties. Each flip lowers
3727 // (slivers, then -min-angle) lexicographically, so the loop terminates.
3728 bool legal = KIGEOM::InCircleDelaunayLegal( apexAPt, sharedFromPt, sharedToPt, apexBPt );
3729
3730 int curSlivers = KIGEOM::IsSliverTriangle( apexAPt, sharedFromPt, sharedToPt )
3731 + KIGEOM::IsSliverTriangle( apexBPt, sharedToPt, sharedFromPt );
3732
3733 // Nothing to gain when already Delaunay and neither triangle is a sliver.
3734 if( legal && curSlivers == 0 )
3735 continue;
3736
3737 int flipSlivers = KIGEOM::IsSliverTriangle( apexAPt, sharedFromPt, apexBPt )
3738 + KIGEOM::IsSliverTriangle( apexAPt, apexBPt, sharedToPt );
3739
3740 bool doFlip = ( flipSlivers != curSlivers ) ? ( flipSlivers < curSlivers ) : !legal;
3741
3742 if( !doFlip )
3743 continue;
3744
3745 heVertex[edge] = apexB;
3746 heVertex[twinEdge] = apexA;
3747
3748 int apexBOuterTwin = twin[apexBEdge];
3749 int apexAOuterTwin = twin[apexAEdge];
3750
3751 twin[edge] = apexBOuterTwin;
3752
3753 if( apexBOuterTwin != -1 )
3754 twin[apexBOuterTwin] = edge;
3755
3756 twin[twinEdge] = apexAOuterTwin;
3757
3758 if( apexAOuterTwin != -1 )
3759 twin[apexAOuterTwin] = twinEdge;
3760
3761 twin[apexAEdge] = apexBEdge;
3762 twin[apexBEdge] = apexAEdge;
3763
3764 // The flip may have made the quad's four outer edges illegal.
3765 for( int outer : { edge, twinEdge, sharedEndEdge, twinSharedEdge } )
3766 {
3767 if( twin[outer] != -1 && !queued[outer] )
3768 {
3769 queued[outer] = 1;
3770 toVisit.push_back( outer );
3771 }
3772 }
3773 }
3774
3775 std::deque<TRI> refined;
3776
3777 for( size_t i = 0; i < triCount; i++ )
3778 refined.emplace_back( heVertex[3 * i + 0], heVertex[3 * i + 1], heVertex[3 * i + 2], this );
3779
3780 m_triangles = std::move( refined );
3781}
3782
3783
3785 m_sourceOutline( aSourceOutline )
3786{
3787}
3788
3789
3793
3794
3795void SHAPE_POLY_SET::Scale( double aScaleFactorX, double aScaleFactorY, const VECTOR2I& aCenter )
3796{
3797 for( POLYGON& poly : m_polys )
3798 {
3799 for( SHAPE_LINE_CHAIN& path : poly )
3800 {
3801 for( int i = 0; i < path.PointCount(); i++ )
3802 {
3803 VECTOR2I pt = path.CPoint( i );
3804 VECTOR2D vec;
3805 vec.x = ( pt.x - aCenter.x ) * aScaleFactorX;
3806 vec.y = ( pt.y - aCenter.y ) * aScaleFactorY;
3807 pt.x = KiROUND<double, int>( aCenter.x + vec.x );
3808 pt.y = KiROUND<double, int>( aCenter.y + vec.y );
3809 path.SetPoint( i, pt );
3810 }
3811 }
3812 }
3813
3816}
3817
3818
3819void
3820SHAPE_POLY_SET::BuildPolysetFromOrientedPaths( const std::vector<SHAPE_LINE_CHAIN>& aPaths,
3821 bool aEvenOdd )
3822{
3823 Clipper2Lib::Clipper64 clipper;
3824 Clipper2Lib::PolyTree64 tree;
3825 Clipper2Lib::Paths64 paths;
3826
3827 for( const SHAPE_LINE_CHAIN& path : aPaths )
3828 {
3829 Clipper2Lib::Path64 lc;
3830 lc.reserve( path.PointCount() );
3831
3832 for( int i = 0; i < path.PointCount(); i++ )
3833 lc.emplace_back( path.CPoint( i ).x, path.CPoint( i ).y );
3834
3835 paths.push_back( std::move( lc ) );
3836 }
3837
3838 clipper.AddSubject( paths );
3839 clipper.Execute( Clipper2Lib::ClipType::Union, aEvenOdd ? Clipper2Lib::FillRule::EvenOdd
3840 : Clipper2Lib::FillRule::NonZero, tree );
3841
3842 std::vector<CLIPPER_Z_VALUE> zValues;
3843 std::vector<SHAPE_ARC> arcBuffer;
3844
3845 importTree( tree, zValues, arcBuffer );
3846 tree.Clear(); // Free used memory (not done in dtor)
3847}
3848
3849
3850bool SHAPE_POLY_SET::PointInside( const VECTOR2I& aPt, int aAccuracy, bool aUseBBoxCache ) const
3851{
3852 for( int idx = 0; idx < OutlineCount(); idx++ )
3853 {
3854 if( COutline( idx ).PointInside( aPt, aAccuracy, aUseBBoxCache ) )
3855 return true;
3856 }
3857
3858 return false;
3859}
3860
3861
3862const std::vector<SEG> SHAPE_POLY_SET::GenerateHatchLines( const std::vector<double>& aSlopes,
3863 int aSpacing, int aLineLength ) const
3864{
3865 std::vector<SEG> hatchLines;
3866
3867 if( OutlineCount() == 0 || TotalVertices() == 0 )
3868 return hatchLines;
3869
3870 // define range for hatch lines
3871 int min_x = CVertex( 0 ).x;
3872 int max_x = CVertex( 0 ).x;
3873 int min_y = CVertex( 0 ).y;
3874 int max_y = CVertex( 0 ).y;
3875
3876 for( auto iterator = CIterateWithHoles(); iterator; iterator++ )
3877 {
3878 if( iterator->x < min_x )
3879 min_x = iterator->x;
3880
3881 if( iterator->x > max_x )
3882 max_x = iterator->x;
3883
3884 if( iterator->y < min_y )
3885 min_y = iterator->y;
3886
3887 if( iterator->y > max_y )
3888 max_y = iterator->y;
3889 }
3890
3891 auto sortEndsByDescendingX =
3892 []( const VECTOR2I& ref, const VECTOR2I& tst )
3893 {
3894 return tst.x < ref.x;
3895 };
3896
3897 for( double slope : aSlopes )
3898 {
3899 int64_t max_a, min_a;
3900
3901 if( slope > 0 )
3902 {
3903 max_a = KiROUND<double, int64_t>( max_y - slope * min_x );
3904 min_a = KiROUND<double, int64_t>( min_y - slope * max_x );
3905 }
3906 else
3907 {
3908 max_a = KiROUND<double, int64_t>( max_y - slope * max_x );
3909 min_a = KiROUND<double, int64_t>( min_y - slope * min_x );
3910 }
3911
3912 min_a = ( min_a / aSpacing ) * aSpacing;
3913
3914 // loop through hatch lines
3915 std::vector<VECTOR2I> pointbuffer;
3916 pointbuffer.reserve( 256 );
3917
3918 for( int64_t a = min_a; a < max_a; a += aSpacing )
3919 {
3920 pointbuffer.clear();
3921
3922 // Iterate through all vertices
3923 for( auto iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
3924 {
3925 const SEG seg = *iterator;
3926 VECTOR2I pt;
3927
3928 if( seg.IntersectsLine( slope, a, pt ) )
3929 {
3930 // If the intersection point is outside the polygon, skip it
3931 if( pt.x < min_x || pt.x > max_x || pt.y < min_y || pt.y > max_y )
3932 continue;
3933
3934 // Add the intersection point to the buffer
3935 pointbuffer.emplace_back( KiROUND( pt.x ), KiROUND( pt.y ) );
3936 }
3937 }
3938
3939 // sort points in order of descending x (if more than 2) to
3940 // ensure the starting point and the ending point of the same segment
3941 // are stored one just after the other.
3942 if( pointbuffer.size() > 2 )
3943 sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX );
3944
3945 // creates lines or short segments inside the complex polygon
3946 for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip++ )
3947 {
3948 const VECTOR2I& p1 = pointbuffer[ip];
3949 const VECTOR2I& p2 = pointbuffer[ip + 1];
3950
3951 // Avoid duplicated intersections or segments
3952 if( p1 == p2 )
3953 continue;
3954
3955 SEG candidate( p1, p2 );
3956
3957 VECTOR2I mid( ( candidate.A.x + candidate.B.x ) / 2, ( candidate.A.y + candidate.B.y ) / 2 );
3958
3959 // Check if segment is inside the polygon by checking its middle point
3960 if( Contains( mid, -1, 1, true ) )
3961 {
3962 int dx = p2.x - p1.x;
3963
3964 // Push only one line for diagonal hatch or for small lines < twice
3965 // the line length; else push 2 small lines
3966 if( aLineLength == -1 || std::abs( dx ) < 2 * aLineLength )
3967 {
3968 hatchLines.emplace_back( candidate );
3969 }
3970 else
3971 {
3972 double dy = p2.y - p1.y;
3973 slope = dy / dx;
3974
3975 if( dx > 0 )
3976 dx = aLineLength;
3977 else
3978 dx = -aLineLength;
3979
3980 int x1 = KiROUND( p1.x + dx );
3981 int x2 = KiROUND( p2.x - dx );
3982 int y1 = KiROUND( p1.y + dx * slope );
3983 int y2 = KiROUND( p2.y - dx * slope );
3984
3985 hatchLines.emplace_back( SEG( p1.x, p1.y, x1, y1 ) );
3986
3987 hatchLines.emplace_back( SEG( p2.x, p2.y, x2, y2 ) );
3988 }
3989 }
3990 }
3991 }
3992 }
3993
3994 return hatchLines;
3995}
int index
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
bool operator!=(const BOM_FIELD &lhs, const BOM_FIELD &rhs)
BOX2< VECTOR2I > BOX2I
Definition box2.h:918
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
BOX2< VECTOR2D > BOX2D
Definition box2.h:919
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:554
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:654
constexpr coord_type GetLeft() const
Definition box2.h:224
constexpr coord_type GetRight() const
Definition box2.h:213
constexpr coord_type GetTop() const
Definition box2.h:225
constexpr coord_type GetBottom() const
Definition box2.h:218
Dynamic R*-tree with SoA node layout and stored insertion bounding boxes.
int Search(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], VISITOR &aVisitor) const
Search for items whose bounding boxes overlap the query rectangle.
void Insert(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], const DATATYPE &aData)
Insert an item with the given bounding box.
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:56
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:117
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:136
bool TesselatePolygon(const SHAPE_POLY_SET::POLYGON &aPolygon, SHAPE_POLY_SET::TRIANGULATED_POLYGON *aHintData)
Triangulate a polygon with holes by bridging holes directly into the outer ring's VERTEX linked list,...
std::vector< SHAPE_LINE_CHAIN > partitionPolygonBalanced(const SHAPE_LINE_CHAIN &aPoly, size_t aTargetLeaves) const
size_t suggestedPartitionLeafCount(const SHAPE_LINE_CHAIN &aPoly) const
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
ecoord SquaredDistance(const SEG &aSeg) const
Definition seg.cpp:76
bool IntersectsLine(double aSlope, double aOffset, VECTOR2I &aIntersection) const
Check if this segment intersects a line defined by slope aSlope and offset aOffset.
Definition seg.cpp:453
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
int Index() const
Return the index of this segment in its parent shape (applicable only to non-local segments).
Definition seg.h:357
static SEG::ecoord Square(int a)
Definition seg.h:119
bool Collide(const SEG &aSeg, int aClearance, int *aActual=nullptr) const
Definition seg.cpp:538
bool ApproxCollinear(const SEG &aSeg, int aDistanceThreshold=1) const
Definition seg.cpp:791
SHAPE_TYPE Type() const
Return the type of the shape.
Definition shape.h:96
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
void GenerateBBoxCache() const
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void ReservePoints(size_t aSize)
Allocate a number of points all at once (for performance).
void Clear()
Remove all points from the line chain.
void Simplify(int aTolerance=0)
Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
double Area(bool aAbsolute=true) const
Return the area of this chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
Clipper2Lib::Path64 convertToClipper2(bool aRequiredOrientation, std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, std::vector< SHAPE_ARC > &aArcBuffer) const
Create a new Clipper2 path from the SHAPE_LINE_CHAIN in a given orientation.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
void Refine()
Refine this triangulation in place with boundary-preserving Lawson edge flips, minimizing the number ...
void AddTriangle(int a, int b, int c)
TRIANGULATED_POLYGON & operator=(const TRIANGULATED_POLYGON &aOther)
std::mutex m_triangulationMutex
virtual bool HasIndexableSubshapes() const override
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
SHAPE_POLY_SET Chamfer(int aDistance)
Return a chamfered version of the polygon set.
void RemoveOutline(int aOutlineIdx)
Delete the aOutlineIdx-th outline of the set including its contours and holes.
void Scale(double aScaleFactorX, double aScaleFactorY, const VECTOR2I &aCenter)
bool CollideEdge(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any edge of any of the contours of the polygon.
HASH_128 GetHash() const
virtual void GetIndexableSubshapes(std::vector< const SHAPE * > &aSubshapes) const override
void BooleanXor(const SHAPE_POLY_SET &b)
Perform boolean polyset exclusive or.
ITERATOR_TEMPLATE< VECTOR2I > ITERATOR
void fractureSingle(POLYGON &paths)
bool HasHoles() const
Return true if the polygon set has any holes.
CONST_ITERATOR CIterateWithHoles() const
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
ITERATOR IterateWithHoles()
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
bool IsTriangulationUpToDate() const
void importPaths(Clipper2Lib::Paths64 &paths, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffe)
void InsertVertex(int aGlobalIndex, const VECTOR2I &aNewVertex)
Adds a vertex in the globally indexed position aGlobalIndex.
std::atomic< bool > m_failedHashValid
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of vertices in a given outline/hole.
void DeletePolygon(int aIdx)
Delete aIdx-th polygon from the set.
void cacheTriangulation(bool aSimplify, std::vector< std::unique_ptr< TRIANGULATED_POLYGON > > *aHintData, const TASK_SUBMITTER &aSubmitter={})
double Area()
Return the area of this poly set.
void SetVertex(const VERTEX_INDEX &aIndex, const VECTOR2I &aPos)
Accessor function to set the position of a specific point.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
void BuildPolysetFromOrientedPaths(const std::vector< SHAPE_LINE_CHAIN > &aPaths, bool aEvenOdd=false)
Build a SHAPE_POLY_SET from a bunch of outlines in provided in random order.
bool Parse(std::stringstream &aStream) override
int TotalVertices() const
Return total number of vertices stored in the set.
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
int FullPointCount() const
Return the number of points in the shape poly set.
void GetArcs(std::vector< SHAPE_ARC > &aArcBuffer) const
Appends all the arcs in this polyset to aArcBuffer.
bool IsVertexInHole(int aGlobalIdx)
Check whether the aGlobalIndex-th vertex belongs to a hole.
int NormalizeAreaOutlines()
Convert a self-intersecting polygon to one (or more) non self-intersecting polygon(s).
void RemoveVertex(int aGlobalIndex)
Delete the aGlobalIndex-th vertex.
void unfractureSingle(POLYGON &path)
void inflateLine2(const SHAPE_LINE_CHAIN &aLine, int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy, bool aSimplify=false)
bool GetRelativeIndices(int aGlobalIdx, VERTEX_INDEX *aRelativeIndices) const
Convert a global vertex index —i.e., a number that globally identifies a vertex in a concatenated lis...
bool IsPolygonSelfIntersecting(int aPolygonIndex) const
Check whether the aPolygonIndex-th polygon in the set is self intersecting.
SHAPE_POLY_SET Subset(int aFirstPolygon, int aLastPolygon)
Return a subset of the polygons in this set, the ones between aFirstPolygon and aLastPolygon.
int RemoveNullSegments()
Look for null segments; ie, segments whose ends are exactly the same and deletes them.
HASH_128 checksum() const
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={})
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
int AddPolygon(const POLYGON &apolygon)
Adds a polygon to the set.
const std::vector< SEG > GenerateHatchLines(const std::vector< double > &aSlopes, int aSpacing, int aLineLength) const
const std::string Format(bool aCplusPlus=true) const override
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
std::vector< std::unique_ptr< TRIANGULATED_POLYGON > > m_triangulatedPolys
ITERATOR_TEMPLATE< const VECTOR2I > CONST_ITERATOR
void inflate2(int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy, bool aSimplify=false)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SEG::ecoord SquaredDistance(const VECTOR2I &aPoint, bool aOutlineOnly, VECTOR2I *aNearest) const
Compute the minimum distance squared between aPoint and all the polygons in the set.
void RemoveContour(int aContourIdx, int aPolygonIdx=-1)
Delete the aContourIdx-th contour of the aPolygonIdx-th polygon in the set.
void Unfracture()
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
int ArcCount() const
Count the number of arc shapes present.
bool GetGlobalIndex(VERTEX_INDEX aRelativeIndices, int &aGlobalIdx) const
Compute the global index of a vertex from the relative indices of polygon, contour and vertex.
bool GetNeighbourIndexes(int aGlobalIndex, int *aPrevious, int *aNext) const
Return the global indexes of the previous and the next corner of the aGlobalIndex-th corner of a cont...
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void SimplifyOutlines(int aMaxError=0)
Simplifies the lines in the polyset.
void booleanOp(Clipper2Lib::ClipType aType, const SHAPE_POLY_SET &aOtherShape)
This is the engine to execute all polygon boolean transforms (AND, OR, ... and polygon simplification...
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
bool hasTouchingHoles(const POLYGON &aPoly) const
Return true if the polygon set has any holes that touch share a vertex.
bool PointOnEdge(const VECTOR2I &aP, int aAccuracy=0) const
Check if point aP lies on an edge or vertex of some of the outlines or holes.
std::atomic< bool > m_hashValid
bool CollideVertex(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any vertex of any of the contours of the polygon.
void DeletePolygonAndTriangulationData(int aIdx, bool aUpdateHash=true)
Delete aIdx-th polygon and its triangulation data from the set.
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
std::atomic< bool > m_triangulationValid
void UpdateTriangulationDataHash()
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int NewHole(int aOutline=-1)
Creates a new hole in a given outline.
SEG::ecoord SquaredDistanceToPolygon(VECTOR2I aPoint, int aIndex, VECTOR2I *aNearest) const
Compute the minimum distance between the aIndex-th polygon and aPoint.
CONST_SEGMENT_ITERATOR CIterateSegmentsWithHoles() const
Return an iterator object, for the aOutline-th outline in the set (with holes).
virtual size_t GetIndexableSubshapeCount() const override
SEG::ecoord SquaredDistanceToSeg(const SEG &aSegment, VECTOR2I *aNearest=nullptr) const
Compute the minimum distance squared between aSegment and all the polygons in the set.
void importPolyPath(const std::unique_ptr< Clipper2Lib::PolyPath64 > &aPolyPath, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffer)
void RebuildHolesFromContours()
Extract all contours from this polygon set, then recreate polygons with holes.
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the line points about y or x (or both)
void OffsetLineChain(const SHAPE_LINE_CHAIN &aLine, int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify)
Perform offsetting of a line chain.
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
std::vector< POLYGON > m_polys
void splitSelfTouchingOutlines()
Split outline segments at vertices that lie on them (self-touching polygons).
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
POLYGON FilletPolygon(unsigned int aRadius, int aErrorMax, int aIndex)
Return a filleted version of the aIndex-th polygon.
bool containsSingle(const VECTOR2I &aP, int aSubpolyIndex, int aAccuracy, bool aUseBBoxCaches=false) const
Check whether the point aP is inside the aSubpolyIndex-th polygon of the polyset.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
int OutlineCount() const
Return the number of outlines in the set.
void InflateWithLinkedHoles(int aFactor, CORNER_STRATEGY aCornerStrategy, int aMaxError)
Perform outline inflation/deflation, using round corners.
POLYGON chamferFilletPolygon(CORNER_MODE aMode, unsigned int aDistance, int aIndex, int aErrorMax)
Return the chamfered or filleted version of the aIndex-th polygon in the set, depending on the aMode ...
SHAPE_POLY_SET Fillet(int aRadius, int aErrorMax)
Return a filleted version of the polygon set.
void Move(const VECTOR2I &aVector) override
bool HasTouchingHoles() const
Return true if the polygon set has any holes that share a vertex.
SHAPE * Clone() const override
Return a dynamically allocated copy of the shape.
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
SHAPE_POLY_SET & operator=(const SHAPE_POLY_SET &aOther)
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
SHAPE_POLY_SET CloneDropTriangulation() const
bool isExteriorWaist(const SEG &aSegA, const SEG &aSegB) const
Check if two line segments are collinear and overlap.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const POLYGON & CPolygon(int aIndex) const
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
POLYGON ChamferPolygon(unsigned int aDistance, int aIndex)
Return a chamfered version of the aIndex-th polygon.
std::function< void(std::function< void()>)> TASK_SUBMITTER
Callback that submits a unit of work for asynchronous execution.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
const BOX2I BBoxFromCaches() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
void importTree(Clipper2Lib::PolyTree64 &tree, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffe)
SEGMENT_ITERATOR_TEMPLATE< const SEG > CONST_SEGMENT_ITERATOR
bool IsSelfIntersecting() const
Check whether any of the polygons in the set is self intersecting.
const SEG & GetSeg() const
int GetWidth() const override
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:179
VECTOR2I::extended_type ecoord
Definition shape.h:299
SHAPE(SHAPE_TYPE aType)
Create an empty shape of type aType.
Definition shape.h:134
static constexpr extended_type ECOORD_MAX
Definition vector2d.h:72
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:279
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:310
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:381
CORNER_STRATEGY
define how inflate transform build inflated polygon
@ ROUND_ACUTE_CORNERS
Acute angles are rounded.
@ CHAMFER_ACUTE_CORNERS
Acute angles are chamfered.
@ CHAMFER_ALL_CORNERS
All angles are chamfered.
@ ROUND_ALL_CORNERS
All angles are rounded.
@ ALLOW_ACUTE_CORNERS
just inflate the polygon. Acute angles create spikes
static bool empty(const wxTextEntryBase *aCtrl)
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:409
Exact orientation and in-circle predicates over integer coordinates.
a few functions useful in geometry calculations.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
static constexpr void hash_combine(std::size_t &seed)
This is a dummy function to take the final case of hash_combine below.
Definition hash.h:28
FLIP_DIRECTION
Definition mirror.h:23
int OrientationSign(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
Orientation of triangle (a, b, c): +1 counter-clockwise, -1 clockwise, 0 collinear.
bool InCircleDelaunayLegal(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c, const VECTOR2I &p)
True when p is outside the circumcircle of CCW triangle (a, b, c): the shared edge is already Delauna...
bool IsSliverTriangle(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
A triangle is a sliver when its longest edge exceeds ten times its shortest.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
static PGM_BASE * process
#define TRIANGULATE_TRACE
#define TRIANGULATESIMPLIFICATIONLEVEL
CITER next(CITER it)
Definition ptree.cpp:120
@ SH_POLY_SET
set of polygons (with holes, etc.)
Definition shape.h:48
@ SH_CIRCLE
circle
Definition shape.h:46
@ SH_SEGMENT
line segment
Definition shape.h:44
static void fractureSingleCacheFriendly(SHAPE_POLY_SET::POLYGON &paths)
static void fractureSingleSlow(SHAPE_POLY_SET::POLYGON &paths)
static FractureEdge * processHole(FractureEdgeSet &edges, FractureEdge::Index provokingIndex, FractureEdge::Index edgeIndex, FractureEdge::Index bridgeIndex)
std::vector< FractureEdge > FractureEdgeSet
std::vector< FractureEdgeSlow * > FractureEdgeSetSlow
#define SEG_CNT_MAX
#define ENABLECACHEFRIENDLYFRACTURE
static int processEdge(FractureEdgeSetSlow &edges, FractureEdgeSlow *edge)
Holds information on each point of a SHAPE_LINE_CHAIN that is retrievable after an operation with Cli...
FractureEdgeSlow * m_next
bool matches(int y) const
FractureEdgeSlow(bool connected, const VECTOR2I &p1, const VECTOR2I &p2)
FractureEdge(const VECTOR2I &p1, const VECTOR2I &p2, Index next)
FractureEdge()=default
bool matches(int y) const
A storage class for 128-bit hash value.
Definition hash_128.h:32
virtual const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Structure to hold the necessary information in order to index a vertex on a SHAPE_POLY_SET object: th...
std::string path
const uint32_t seed
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
VECTOR2I location
int actual
wxString result
Test unit parsing edge cases and error handling.
int delta
#define M_PI
T rescale(T aNumerator, T aValue, T aDenominator)
Scale a number (value) by rational (numerator/denominator).
Definition util.h:135
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682