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 // Prefix trapezoid terms (SHAPE_LINE_CHAIN::Area()) to sign a candidate's halves
2041 // in O(1). Built on demand; most outlines never produce a candidate.
2042 std::vector<double> prefix;
2043
2044 auto ringTerm =
2045 [&]( int aFrom, int aTo ) -> double
2046 {
2047 if( prefix.empty() )
2048 {
2049 prefix.resize( count + 1, 0.0 );
2050
2051 for( int ii = 0; ii < count; ++ii )
2052 {
2053 const VECTOR2I& a = outline.CPoint( ii );
2054 const VECTOR2I& b = outline.CPoint( ( ii + 1 ) % count );
2055
2056 prefix[ii + 1] = prefix[ii]
2057 + ( (double) a.x + b.x ) * ( (double) a.y - b.y );
2058 }
2059 }
2060
2061 double term = aFrom <= aTo ? prefix[aTo] - prefix[aFrom]
2062 : prefix[count] - prefix[aFrom] + prefix[aTo];
2063
2064 const VECTOR2I& last = outline.CPoint( aTo );
2065 const VECTOR2I& first = outline.CPoint( aFrom );
2066
2067 return term + ( (double) last.x + first.x ) * ( (double) last.y - first.y );
2068 };
2069
2070 auto ringSize =
2071 [&]( int aFrom, int aTo )
2072 {
2073 return aFrom <= aTo ? aTo - aFrom + 1 : count - aFrom + aTo + 1;
2074 };
2075
2076 // Both halves of a real self-touch keep the parent winding. An inverted half is a
2077 // Fracture() corridor; splitting there returns the hole ring as an outline.
2078 auto splittable =
2079 [&]( int aVertIdx, int aSegIdx )
2080 {
2081 const int start = ( aSegIdx + 1 ) % count;
2082
2083 if( ringSize( start, aVertIdx ) < 3 || ringSize( aVertIdx, aSegIdx ) < 3 )
2084 return false;
2085
2086 const bool sign = ringTerm( 0, count - 1 ) > 0;
2087 const double term1 = ringTerm( start, aVertIdx );
2088 const double term2 = ringTerm( aVertIdx, aSegIdx );
2089
2090 return term1 != 0.0 && term2 != 0.0 && ( term1 > 0 ) == sign
2091 && ( term2 > 0 ) == sign;
2092 };
2093
2094 int insertSegIdx = -1;
2095 int insertVertIdx = -1;
2096
2097 // For small polygons, direct O(n²) search is faster than R-tree overhead
2098 constexpr int RTREE_THRESHOLD = 32;
2099
2100 if( count < RTREE_THRESHOLD )
2101 {
2102 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2103 {
2104 const VECTOR2I& pt = outline.CPoint( vertIdx );
2105 const int prevSeg = ( vertIdx + count - 1 ) % count;
2106
2107 for( int segIdx = 0; segIdx < count; ++segIdx )
2108 {
2109 // Skip adjacent segments
2110 if( segIdx == prevSeg || segIdx == vertIdx )
2111 continue;
2112
2113 const VECTOR2I& a = outline.CPoint( segIdx );
2114 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2115
2116 // SquaredDistance returns 0 only when pt lies exactly on the
2117 // segment. Clipper2 rounds corridor-cut vertices to integer
2118 // coordinates; they can land within 1nm of an endpoint but are
2119 // not true pinch points.
2120 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0
2121 && splittable( vertIdx, segIdx ) )
2122 {
2123 insertSegIdx = segIdx;
2124 insertVertIdx = vertIdx;
2125 break;
2126 }
2127 }
2128 }
2129 }
2130 else
2131 {
2133
2134 for( int i = 0; i < count; ++i )
2135 {
2136 const VECTOR2I& a = outline.CPoint( i );
2137 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
2138 int bmin[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
2139 int bmax[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
2140 rtree.Insert( bmin, bmax, i );
2141 }
2142
2143 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2144 {
2145 const VECTOR2I& pt = outline.CPoint( vertIdx );
2146 const int prevSeg = ( vertIdx + count - 1 ) % count;
2147 int bmin[2] = { pt.x, pt.y };
2148 int bmax[2] = { pt.x, pt.y };
2149
2150 auto pinchVisitor =
2151 [&]( const intptr_t& segIdx ) -> bool
2152 {
2153 if( segIdx == prevSeg || segIdx == vertIdx )
2154 return true;
2155
2156 const VECTOR2I& a = outline.CPoint( segIdx );
2157 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2158
2159 // SquaredDistance returns 0 only when pt lies exactly on the
2160 // segment. Clipper2 rounds corridor-cut vertices to integer
2161 // coordinates; they can land within 1nm of an endpoint but
2162 // are not true pinch points.
2163 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0
2164 && splittable( vertIdx, static_cast<int>( segIdx ) ) )
2165 {
2166 insertSegIdx = segIdx;
2167 insertVertIdx = vertIdx;
2168 return false;
2169 }
2170
2171 return true;
2172 };
2173
2174 rtree.Search( bmin, bmax, pinchVisitor );
2175 }
2176 }
2177
2178 if( insertSegIdx < 0 )
2179 break;
2180
2181 // Split the polygon at the pinch point into two separate polygons.
2182 // Polygon 1: vertices from (insertSegIdx+1) to insertVertIdx
2183 // Polygon 2: vertices from insertVertIdx to insertSegIdx
2184
2185 const int splitStart1 = ( insertSegIdx + 1 ) % count;
2186 const int size1 = ringSize( splitStart1, insertVertIdx );
2187 const int size2 = ringSize( insertVertIdx, insertSegIdx );
2188
2189 SHAPE_LINE_CHAIN poly1;
2190 SHAPE_LINE_CHAIN poly2;
2191 poly1.ReservePoints( size1 );
2192 poly2.ReservePoints( size2 );
2193
2194 int idx = splitStart1;
2195
2196 for( int i = 0; i < size1; ++i )
2197 {
2198 poly1.Append( outline.CPoint( idx ) );
2199 idx = ( idx + 1 ) % count;
2200 }
2201
2202 poly1.SetClosed( true );
2203
2204 idx = insertVertIdx;
2205
2206 for( int i = 0; i < size2; ++i )
2207 {
2208 poly2.Append( outline.CPoint( idx ) );
2209 idx = ( idx + 1 ) % count;
2210 }
2211
2212 poly2.SetClosed( true );
2213
2214 m_polys[polyIdx][0] = std::move( poly1 );
2215
2216 POLYGON np;
2217 np.push_back( std::move( poly2 ) );
2218 m_polys.push_back( std::move( np ) );
2219
2220 changed = true;
2221 }
2222 }
2223}
2224
2225
2227{
2229
2231
2232 booleanOp( Clipper2Lib::ClipType::Union, empty );
2233}
2234
2235
2237{
2238 for( POLYGON& paths : m_polys )
2239 {
2240 for( SHAPE_LINE_CHAIN& path : paths )
2241 {
2242 path.Simplify( aTolerance );
2243 }
2244 }
2245}
2246
2247
2249{
2250 // We are expecting only one main outline, but this main outline can have holes
2251 // if holes: combine holes and remove them from the main outline.
2252 // Note also we are usingin polygon
2253 // calculations, but it is not mandatory. It is used mainly
2254 // because there is usually only very few vertices in area outlines
2255 SHAPE_POLY_SET::POLYGON& outline = Polygon( 0 );
2256 SHAPE_POLY_SET holesBuffer;
2257
2258 // Move holes stored in outline to holesBuffer:
2259 // The first SHAPE_LINE_CHAIN is the main outline, others are holes
2260 while( outline.size() > 1 )
2261 {
2262 holesBuffer.AddOutline( outline.back() );
2263 outline.pop_back();
2264 }
2265
2266 Simplify();
2267
2268 // If any hole, subtract it to main outline
2269 if( holesBuffer.OutlineCount() )
2270 {
2271 holesBuffer.Simplify();
2272 BooleanSubtract( holesBuffer );
2273 }
2274
2275 // In degenerate cases, simplify might return no outlines
2276 if( OutlineCount() > 0 )
2278
2279 return OutlineCount();
2280}
2281
2282
2283const std::string SHAPE_POLY_SET::Format( bool aCplusPlus ) const
2284{
2285 std::stringstream ss;
2286
2287 ss << "SHAPE_LINE_CHAIN poly; \n";
2288
2289 for( unsigned i = 0; i < m_polys.size(); i++ )
2290 {
2291 for( unsigned j = 0; j < m_polys[i].size(); j++ )
2292 {
2293
2294 ss << "{ auto tmp = " << m_polys[i][j].Format() << ";\n";
2295
2296 SHAPE_POLY_SET poly;
2297
2298 if( j == 0 )
2299 {
2300 ss << " poly.AddOutline(tmp); } \n";
2301 }
2302 else
2303 {
2304 ss << " poly.AddHole(tmp); } \n";
2305 }
2306
2307 }
2308 }
2309
2310 return ss.str();
2311}
2312
2313
2314bool SHAPE_POLY_SET::Parse( std::stringstream& aStream )
2315{
2316 std::string tmp;
2317
2318 aStream >> tmp;
2319
2320 if( tmp != "polyset" )
2321 return false;
2322
2323 aStream >> tmp;
2324
2325 int n_polys = atoi( tmp.c_str() );
2326
2327 if( n_polys < 0 )
2328 return false;
2329
2330 for( int i = 0; i < n_polys; i++ )
2331 {
2332 POLYGON paths;
2333
2334 aStream >> tmp;
2335
2336 if( tmp != "poly" )
2337 return false;
2338
2339 aStream >> tmp;
2340 int n_outlines = atoi( tmp.c_str() );
2341
2342 if( n_outlines < 0 )
2343 return false;
2344
2345 for( int j = 0; j < n_outlines; j++ )
2346 {
2347 SHAPE_LINE_CHAIN outline;
2348
2349 outline.SetClosed( true );
2350
2351 aStream >> tmp;
2352 int n_vertices = atoi( tmp.c_str() );
2353
2354 for( int v = 0; v < n_vertices; v++ )
2355 {
2356 VECTOR2I p;
2357
2358 aStream >> tmp; p.x = atoi( tmp.c_str() );
2359 aStream >> tmp; p.y = atoi( tmp.c_str() );
2360 outline.Append( p );
2361 }
2362
2363 paths.push_back( std::move( outline ) );
2364 }
2365
2366 m_polys.push_back( std::move( paths ) );
2367 }
2368
2369 return true;
2370}
2371
2372
2373const BOX2I SHAPE_POLY_SET::BBox( int aClearance ) const
2374{
2375 BOX2I bb;
2376
2377 for( unsigned i = 0; i < m_polys.size(); i++ )
2378 {
2379 if( i == 0 )
2380 bb = m_polys[i][0].BBox();
2381 else
2382 bb.Merge( m_polys[i][0].BBox() );
2383 }
2384
2385 bb.Inflate( aClearance );
2386 return bb;
2387}
2388
2389
2391{
2392 BOX2I bb;
2393
2394 for( unsigned i = 0; i < m_polys.size(); i++ )
2395 {
2396 if( i == 0 )
2397 bb = *m_polys[i][0].GetCachedBBox();
2398 else
2399 bb.Merge( *m_polys[i][0].GetCachedBBox() );
2400 }
2401
2402 return bb;
2403}
2404
2405
2406bool SHAPE_POLY_SET::PointOnEdge( const VECTOR2I& aP, int aAccuracy ) const
2407{
2408 // Iterate through all the polygons in the set
2409 for( const POLYGON& polygon : m_polys )
2410 {
2411 // Iterate through all the line chains in the polygon
2412 for( const SHAPE_LINE_CHAIN& lineChain : polygon )
2413 {
2414 if( lineChain.PointOnEdge( aP, aAccuracy ) )
2415 return true;
2416 }
2417 }
2418
2419 return false;
2420}
2421
2422
2423bool SHAPE_POLY_SET::Collide( const SEG& aSeg, int aClearance, int* aActual,
2424 VECTOR2I* aLocation ) const
2425{
2426 VECTOR2I nearest;
2427 ecoord dist_sq = SquaredDistanceToSeg( aSeg, aLocation ? &nearest : nullptr );
2428
2429 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2430 {
2431 if( aLocation )
2432 *aLocation = nearest;
2433
2434 if( aActual )
2435 *aActual = sqrt( dist_sq );
2436
2437 return true;
2438 }
2439
2440 return false;
2441}
2442
2443
2444bool SHAPE_POLY_SET::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
2445 VECTOR2I* aLocation ) const
2446{
2447 if( IsEmpty() || VertexCount() == 0 )
2448 return false;
2449
2450 VECTOR2I nearest;
2451 ecoord dist_sq = SquaredDistance( aP, false, aLocation ? &nearest : nullptr );
2452
2453 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2454 {
2455 if( aLocation )
2456 *aLocation = nearest;
2457
2458 if( aActual )
2459 *aActual = sqrt( dist_sq );
2460
2461 return true;
2462 }
2463
2464 return false;
2465}
2466
2467
2468bool SHAPE_POLY_SET::Collide( const SHAPE* aShape, int aClearance, int* aActual,
2469 VECTOR2I* aLocation ) const
2470{
2471 // A couple of simple cases are worth trying before we fall back on triangulation.
2472
2473 if( aShape->Type() == SH_SEGMENT )
2474 {
2475 const SHAPE_SEGMENT* segment = static_cast<const SHAPE_SEGMENT*>( aShape );
2476 int extra = segment->GetWidth() / 2;
2477
2478 if( Collide( segment->GetSeg(), aClearance + extra, aActual, aLocation ) )
2479 {
2480 if( aActual )
2481 *aActual = std::max( 0, *aActual - extra );
2482
2483 return true;
2484 }
2485
2486 return false;
2487 }
2488
2489 if( aShape->Type() == SH_CIRCLE )
2490 {
2491 const SHAPE_CIRCLE* circle = static_cast<const SHAPE_CIRCLE*>( aShape );
2492 int extra = circle->GetRadius();
2493
2494 if( Collide( circle->GetCenter(), aClearance + extra, aActual, aLocation ) )
2495 {
2496 if( aActual )
2497 *aActual = std::max( 0, *aActual - extra );
2498
2499 return true;
2500 }
2501
2502 return false;
2503 }
2504
2505 const_cast<SHAPE_POLY_SET*>( this )->CacheTriangulation();
2506
2507 int actual = INT_MAX;
2509
2510 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
2511 {
2512 for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
2513 {
2514 if( aActual || aLocation )
2515 {
2516 int triActual;
2517 VECTOR2I triLocation;
2518
2519 if( aShape->Collide( &tri, aClearance, &triActual, &triLocation ) )
2520 {
2521 if( triActual < actual )
2522 {
2523 actual = triActual;
2524 location = triLocation;
2525 }
2526 }
2527 }
2528 else // A much faster version of above
2529 {
2530 if( aShape->Collide( &tri, aClearance ) )
2531 return true;
2532 }
2533 }
2534 }
2535
2536 if( actual < INT_MAX )
2537 {
2538 if( aActual )
2539 *aActual = std::max( 0, actual );
2540
2541 if( aLocation )
2542 *aLocation = location;
2543
2544 return true;
2545 }
2546
2547 return false;
2548}
2549
2550
2552{
2553 m_polys.clear();
2554 m_triangulatedPolys.clear();
2555 m_triangulationValid = false;
2556}
2557
2558
2559void SHAPE_POLY_SET::RemoveContour( int aContourIdx, int aPolygonIdx )
2560{
2561 // Default polygon is the last one
2562 if( aPolygonIdx < 0 )
2563 aPolygonIdx += m_polys.size();
2564
2565 m_polys[aPolygonIdx].erase( m_polys[aPolygonIdx].begin() + aContourIdx );
2566}
2567
2568
2569void SHAPE_POLY_SET::RemoveOutline( int aOutlineIdx )
2570{
2571 m_polys.erase( m_polys.begin() + aOutlineIdx );
2572}
2573
2574
2576{
2577 int removed = 0;
2578
2579 ITERATOR iterator = IterateWithHoles();
2580
2581 VECTOR2I contourStart = *iterator;
2582 VECTOR2I segmentStart, segmentEnd;
2583
2584 VERTEX_INDEX indexStart;
2585 std::vector<VERTEX_INDEX> indices_to_remove;
2586
2587 while( iterator )
2588 {
2589 // Obtain first point and its index
2590 segmentStart = *iterator;
2591 indexStart = iterator.GetIndex();
2592
2593 // Obtain last point
2594 if( iterator.IsEndContour() )
2595 {
2596 segmentEnd = contourStart;
2597
2598 // Advance
2599 iterator++;
2600
2601 // If we have rolled into the next contour, remember its position
2602 // segmentStart and segmentEnd remain valid for comparison here
2603 if( iterator )
2604 contourStart = *iterator;
2605 }
2606 else
2607 {
2608 // Advance
2609 iterator++;
2610
2611 // If we have reached the end of the SHAPE_POLY_SET, something is broken here
2612 wxCHECK_MSG( iterator, removed, wxT( "Invalid polygon. Reached end without noticing. Please report this error" ) );
2613
2614 segmentEnd = *iterator;
2615 }
2616
2617 // Remove segment start if both points are equal
2618 if( segmentStart == segmentEnd )
2619 {
2620 indices_to_remove.push_back( indexStart );
2621 removed++;
2622 }
2623 }
2624
2625 // Proceed in reverse direction to remove the vertices because they are stored as absolute indices in a vector
2626 // Removing in reverse order preserves the remaining index values
2627 for( auto it = indices_to_remove.rbegin(); it != indices_to_remove.rend(); ++it )
2628 RemoveVertex( *it );
2629
2630 return removed;
2631}
2632
2633
2635{
2636 m_polys.erase( m_polys.begin() + aIdx );
2637}
2638
2639
2641{
2642 m_polys.erase( m_polys.begin() + aIdx );
2643
2645 {
2646 for( int ii = m_triangulatedPolys.size() - 1; ii >= 0; --ii )
2647 {
2648 std::unique_ptr<TRIANGULATED_POLYGON>& triangleSet = m_triangulatedPolys[ii];
2649
2650 if( triangleSet->GetSourceOutlineIndex() == aIdx )
2651 m_triangulatedPolys.erase( m_triangulatedPolys.begin() + ii );
2652 else if( triangleSet->GetSourceOutlineIndex() > aIdx )
2653 triangleSet->SetSourceOutlineIndex( triangleSet->GetSourceOutlineIndex() - 1 );
2654 }
2655
2656 if( aUpdateHash )
2657 {
2658 m_hash = checksum();
2659 m_hashValid = true;
2660 }
2661 }
2662}
2663
2664
2670
2671
2673{
2674 m_polys.insert( m_polys.end(), aSet.m_polys.begin(), aSet.m_polys.end() );
2675}
2676
2677
2678void SHAPE_POLY_SET::Append( const VECTOR2I& aP, int aOutline, int aHole )
2679{
2680 Append( aP.x, aP.y, aOutline, aHole );
2681}
2682
2683
2685 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
2686 int aClearance ) const
2687{
2688 // Shows whether there was a collision
2689 bool collision = false;
2690
2691 // Difference vector between each vertex and aPoint.
2693 ecoord distance_squared;
2694 ecoord clearance_squared = SEG::Square( aClearance );
2695
2696 for( CONST_ITERATOR iterator = CIterateWithHoles(); iterator; iterator++ )
2697 {
2698 // Get the difference vector between current vertex and aPoint
2699 delta = *iterator - aPoint;
2700
2701 // Compute distance
2702 distance_squared = delta.SquaredEuclideanNorm();
2703
2704 // Check for collisions
2705 if( distance_squared <= clearance_squared )
2706 {
2707 if( !aClosestVertex )
2708 return true;
2709
2710 collision = true;
2711
2712 // Update clearance to look for closer vertices
2713 clearance_squared = distance_squared;
2714
2715 // Store the indices that identify the vertex
2716 *aClosestVertex = iterator.GetIndex();
2717 }
2718 }
2719
2720 return collision;
2721}
2722
2723
2725 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
2726 int aClearance ) const
2727{
2728 // Shows whether there was a collision
2729 bool collision = false;
2730 ecoord clearance_squared = SEG::Square( aClearance );
2731
2732 for( CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
2733 {
2734 const SEG currentSegment = *iterator;
2735 ecoord distance_squared = currentSegment.SquaredDistance( aPoint );
2736
2737 // Check for collisions
2738 if( distance_squared <= clearance_squared )
2739 {
2740 if( !aClosestVertex )
2741 return true;
2742
2743 collision = true;
2744
2745 // Update clearance to look for closer edges
2746 clearance_squared = distance_squared;
2747
2748 // Store the indices that identify the vertex
2749 *aClosestVertex = iterator.GetIndex();
2750 }
2751 }
2752
2753 return collision;
2754}
2755
2756
2758{
2759 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
2760 {
2761 COutline( polygonIdx ).GenerateBBoxCache();
2762
2763 for( int holeIdx = 0; holeIdx < HoleCount( polygonIdx ); holeIdx++ )
2764 CHole( polygonIdx, holeIdx ).GenerateBBoxCache();
2765 }
2766}
2767
2768
2769bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
2770 bool aUseBBoxCaches ) const
2771{
2772 if( m_polys.empty() )
2773 return false;
2774
2775 // If there is a polygon specified, check the condition against that polygon
2776 if( aSubpolyIndex >= 0 )
2777 return containsSingle( aP, aSubpolyIndex, aAccuracy, aUseBBoxCaches );
2778
2779 // In any other case, check it against all polygons in the set
2780 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
2781 {
2782 if( containsSingle( aP, polygonIdx, aAccuracy, aUseBBoxCaches ) )
2783 return true;
2784 }
2785
2786 return false;
2787}
2788
2789
2790void SHAPE_POLY_SET::RemoveVertex( int aGlobalIndex )
2791{
2793
2794 // Assure the to be removed vertex exists, abort otherwise
2795 if( GetRelativeIndices( aGlobalIndex, &index ) )
2797 else
2798 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
2799}
2800
2801
2803{
2804 m_polys[aIndex.m_polygon][aIndex.m_contour].Remove( aIndex.m_vertex );
2805}
2806
2807
2808void SHAPE_POLY_SET::SetVertex( int aGlobalIndex, const VECTOR2I& aPos )
2809{
2811
2812 if( GetRelativeIndices( aGlobalIndex, &index ) )
2813 SetVertex( index, aPos );
2814 else
2815 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
2816}
2817
2818
2819void SHAPE_POLY_SET::SetVertex( const VERTEX_INDEX& aIndex, const VECTOR2I& aPos )
2820{
2821 m_polys[aIndex.m_polygon][aIndex.m_contour].SetPoint( aIndex.m_vertex, aPos );
2822}
2823
2824
2825bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
2826 bool aUseBBoxCaches ) const
2827{
2828 // Check that the point is inside the outline
2829 if( m_polys[aSubpolyIndex][0].PointInside( aP, aAccuracy ) )
2830 {
2831 // Check that the point is not in any of the holes
2832 for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
2833 {
2834 const SHAPE_LINE_CHAIN& hole = CHole( aSubpolyIndex, holeIdx );
2835
2836 // If the point is inside a hole it is outside of the polygon. Do not use aAccuracy
2837 // here as it's meaning would be inverted.
2838 if( hole.PointInside( aP, 1, aUseBBoxCaches ) )
2839 return false;
2840 }
2841
2842 return true;
2843 }
2844
2845 return false;
2846}
2847
2848
2849void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
2850{
2851 for( POLYGON& poly : m_polys )
2852 {
2853 for( SHAPE_LINE_CHAIN& path : poly )
2854 path.Move( aVector );
2855 }
2856
2857 for( std::unique_ptr<TRIANGULATED_POLYGON>& tri : m_triangulatedPolys )
2858 tri->Move( aVector );
2859
2860 m_hash = checksum();
2861 m_hashValid = true;
2862}
2863
2864
2865void SHAPE_POLY_SET::Mirror( const VECTOR2I& aRef, FLIP_DIRECTION aFlipDirection )
2866{
2867 for( POLYGON& poly : m_polys )
2868 {
2869 for( SHAPE_LINE_CHAIN& path : poly )
2870 path.Mirror( aRef, aFlipDirection );
2871 }
2872
2875}
2876
2877
2878void SHAPE_POLY_SET::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
2879{
2880 for( POLYGON& poly : m_polys )
2881 {
2882 for( SHAPE_LINE_CHAIN& path : poly )
2883 path.Rotate( aAngle, aCenter );
2884 }
2885
2886 // Don't re-cache if the triangulation is already invalid
2889}
2890
2891
2893{
2894 int c = 0;
2895
2896 for( const POLYGON& poly : m_polys )
2897 {
2898 for( const SHAPE_LINE_CHAIN& path : poly )
2899 c += path.PointCount();
2900 }
2901
2902 return c;
2903}
2904
2905
2906SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex )
2907{
2908 return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0 );
2909}
2910
2911
2912SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax,
2913 int aIndex )
2914{
2915 return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax );
2916}
2917
2918
2920 VECTOR2I* aNearest ) const
2921{
2922 // We calculate the min dist between the segment and each outline segment. However, if the
2923 // segment to test is inside the outline, and does not cross any edge, it can be seen outside
2924 // the polygon. Therefore test if a segment end is inside (testing only one end is enough).
2925 // Use an accuracy of "1" to say that we don't care if it's exactly on the edge or not.
2926 if( containsSingle( aPoint, aPolygonIndex, 1 ) )
2927 {
2928 if( aNearest )
2929 *aNearest = aPoint;
2930
2931 return 0;
2932 }
2933
2934 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
2935
2936 SEG::ecoord minDistance = (*iterator).SquaredDistance( aPoint );
2937
2938 if( aNearest )
2939 *aNearest = ( *iterator ).NearestPoint( aPoint );
2940
2941 for( iterator++; iterator && minDistance > 0; iterator++ )
2942 {
2943 SEG::ecoord currentDistance = (*iterator).SquaredDistance( aPoint );
2944
2945 if( currentDistance < minDistance )
2946 {
2947 if( aNearest )
2948 *aNearest = (*iterator).NearestPoint( aPoint );
2949
2950 minDistance = currentDistance;
2951 }
2952 }
2953
2954 return minDistance;
2955}
2956
2957
2959 VECTOR2I* aNearest ) const
2960{
2961 // Check if the segment is fully-contained. If so, its midpoint is a good-enough nearest point.
2962 if( containsSingle( aSegment.A, aPolygonIndex, 1 ) &&
2963 containsSingle( aSegment.B, aPolygonIndex, 1 ) )
2964 {
2965 if( aNearest )
2966 *aNearest = ( aSegment.A + aSegment.B ) / 2;
2967
2968 return 0;
2969 }
2970
2971 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
2972 SEG::ecoord minDistance = (*iterator).SquaredDistance( aSegment );
2973
2974 if( aNearest )
2975 *aNearest = ( *iterator ).NearestPoint( aSegment );
2976
2977 for( iterator++; iterator && minDistance > 0; iterator++ )
2978 {
2979 SEG::ecoord currentDistance = (*iterator).SquaredDistance( aSegment );
2980
2981 if( currentDistance < minDistance )
2982 {
2983 if( aNearest )
2984 *aNearest = (*iterator).NearestPoint( aSegment );
2985
2986 minDistance = currentDistance;
2987 }
2988 }
2989
2990 // Return the maximum of minDistance and zero
2991 return minDistance < 0 ? 0 : minDistance;
2992}
2993
2994
2995SEG::ecoord SHAPE_POLY_SET::SquaredDistance( const VECTOR2I& aPoint, bool aOutlineOnly,
2996 VECTOR2I* aNearest ) const
2997{
2998 wxASSERT_MSG( !aOutlineOnly, wxT( "Warning: SHAPE_POLY_SET::SquaredDistance does not yet "
2999 "support aOutlineOnly==true" ) );
3000
3001 SEG::ecoord currentDistance_sq;
3002 SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
3003 VECTOR2I nearest;
3004
3005 // Iterate through all the polygons and get the minimum distance.
3006 for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
3007 {
3008 currentDistance_sq = SquaredDistanceToPolygon( aPoint, polygonIdx,
3009 aNearest ? &nearest : nullptr );
3010
3011 if( currentDistance_sq < minDistance_sq )
3012 {
3013 if( aNearest )
3014 *aNearest = nearest;
3015
3016 minDistance_sq = currentDistance_sq;
3017 }
3018 }
3019
3020 return minDistance_sq;
3021}
3022
3023
3025{
3026 SEG::ecoord currentDistance_sq;
3027 SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
3028 VECTOR2I nearest;
3029
3030 // Iterate through all the polygons and get the minimum distance.
3031 for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
3032 {
3033 currentDistance_sq = SquaredDistanceToPolygon( aSegment, polygonIdx,
3034 aNearest ? &nearest : nullptr );
3035
3036 if( currentDistance_sq < minDistance_sq )
3037 {
3038 if( aNearest )
3039 *aNearest = nearest;
3040
3041 minDistance_sq = currentDistance_sq;
3042 }
3043 }
3044
3045 return minDistance_sq;
3046}
3047
3048
3050{
3052
3053 // Get the polygon and contour where the vertex is. If the vertex does not exist, return false
3054 if( !GetRelativeIndices( aGlobalIdx, &index ) )
3055 return false;
3056
3057 // The contour is a hole if its index is greater than zero
3058 return index.m_contour > 0;
3059}
3060
3061
3063{
3064 SHAPE_POLY_SET chamfered;
3065
3066 for( unsigned int idx = 0; idx < m_polys.size(); idx++ )
3067 chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx ) );
3068
3069 return chamfered;
3070}
3071
3072
3073SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax )
3074{
3075 SHAPE_POLY_SET filleted;
3076
3077 for( size_t idx = 0; idx < m_polys.size(); idx++ )
3078 filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx ) );
3079
3080 return filleted;
3081}
3082
3083
3085{
3086 SHAPE::operator=( aOther );
3087 m_polys = aOther.m_polys;
3088
3089 m_triangulatedPolys.clear();
3090
3091 if( aOther.IsTriangulationUpToDate() )
3092 {
3093 m_triangulatedPolys.reserve( aOther.TriangulatedPolyCount() );
3094
3095 for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
3096 {
3097 const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
3098 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
3099 }
3100
3101 m_hash = aOther.m_hash;
3102 m_hashValid.store( aOther.m_hashValid.load() );
3104 }
3105 else
3106 {
3107 m_hash.Clear();
3108 m_hashValid = false;
3109 m_triangulationValid = false;
3110 }
3111
3112 m_failedHash = aOther.m_failedHash;
3113 m_failedHashValid.store( aOther.m_failedHashValid.load() );
3114
3115 return *this;
3116}
3117
3118
3120{
3121 if( !m_hashValid )
3122 return checksum();
3123
3124 return m_hash;
3125}
3126
3127
3129{
3131 return false;
3132
3133 if( !m_hashValid )
3134 return false;
3135
3136 HASH_128 hash = checksum();
3137
3138 return hash == m_hash;
3139}
3140
3141
3143 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* aHintData,
3144 const TASK_SUBMITTER& aSubmitter )
3145{
3146 // if( m_triangulationValid && m_hashValid && m_hash == checksum() )
3147 // return;
3148 // if( m_failedHashValid && m_failedHash == checksum() )
3149 // return;
3150
3151 std::unique_lock<std::mutex> lock( m_triangulationMutex );
3152
3154 return;
3156 return;
3157
3158 // Invalidate, in case anything goes wrong below
3159 m_triangulationValid = false;
3160 m_hashValid = false;
3161 m_failedHashValid = false;
3162
3163 auto triangulate =
3164 []( SHAPE_POLY_SET& polySet, int forOutline,
3165 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>& dest,
3166 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* hintData,
3167 const TASK_SUBMITTER& taskSubmitter )
3168 {
3169 bool triangulationValid = false;
3170 int pass = 0;
3171 int index = 0;
3172
3173 if( hintData && hintData->size() != (unsigned) polySet.OutlineCount() )
3174 hintData = nullptr;
3175
3176 while( polySet.OutlineCount() > 0 )
3177 {
3178 if( !dest.empty() && dest.back()->GetTriangleCount() == 0 )
3179 dest.erase( dest.end() - 1 );
3180
3181 {
3182 const SHAPE_LINE_CHAIN& outline = polySet.Polygon( 0 ).front();
3183 TRIANGULATED_POLYGON partitionScratch( forOutline );
3184 POLYGON_TRIANGULATION partitioner( partitionScratch );
3185 size_t partitionLeaves = partitioner.suggestedPartitionLeafCount( outline );
3186
3187 if( partitionLeaves > 1 )
3188 {
3189 std::vector<SHAPE_LINE_CHAIN> partitions =
3190 partitioner.partitionPolygonBalanced( outline, partitionLeaves );
3191
3192 if( partitions.size() > 1 )
3193 {
3194 polySet.DeletePolygon( 0 );
3195
3196 if( taskSubmitter && partitions.size() > 2 )
3197 {
3198 size_t leafCount = partitions.size();
3199
3200 struct WorkStealState
3201 {
3202 std::unique_ptr<std::atomic<bool>[]> claimed;
3203 std::unique_ptr<std::atomic<bool>[]> done;
3204 std::unique_ptr<std::atomic<bool>[]> ok;
3205
3206 explicit WorkStealState( size_t n ) :
3207 claimed( new std::atomic<bool>[n] ),
3208 done( new std::atomic<bool>[n] ),
3209 ok( new std::atomic<bool>[n] )
3210 {
3211 for( size_t i = 0; i < n; ++i )
3212 {
3213 claimed[i].store( false );
3214 done[i].store( false );
3215 ok[i].store( false );
3216 }
3217 }
3218 };
3219
3220 auto state = std::make_shared<WorkStealState>( leafCount );
3221
3222 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>> results( leafCount );
3223
3224 for( size_t i = 0; i < leafCount; ++i )
3225 {
3226 results[i] = std::make_unique<TRIANGULATED_POLYGON>( forOutline );
3227 }
3228
3229 for( size_t i = 0; i < leafCount; ++i )
3230 {
3231 auto* triPoly = results[i].get();
3232 auto* leaf = &partitions[i];
3233
3234 taskSubmitter( [state, i, triPoly, leaf]()
3235 {
3236 if( state->claimed[i].exchange( true ) )
3237 return;
3238
3239 POLYGON_TRIANGULATION tess( *triPoly );
3240 state->ok[i].store( tess.TesselatePolygon( *leaf, nullptr ) );
3241 state->done[i].store( true, std::memory_order_release );
3242 } );
3243 }
3244
3245 for( size_t i = 0; i < leafCount; ++i )
3246 {
3247 if( state->claimed[i].exchange( true ) )
3248 continue;
3249
3250 POLYGON_TRIANGULATION tess( *results[i] );
3251 state->ok[i].store( tess.TesselatePolygon( partitions[i], nullptr ) );
3252 state->done[i].store( true, std::memory_order_release );
3253 }
3254
3255 for( size_t i = 0; i < leafCount; ++i )
3256 {
3257 while( !state->done[i].load( std::memory_order_acquire ) )
3258 {
3259 std::this_thread::yield();
3260 }
3261 }
3262
3263 bool allOk = true;
3264
3265 for( size_t i = 0; i < leafCount; ++i )
3266 allOk = allOk && state->ok[i].load();
3267
3268 if( allOk )
3269 {
3270 for( auto& r : results )
3271 {
3272 if( r->GetTriangleCount() > 0 )
3273 dest.push_back( std::move( r ) );
3274 }
3275
3276 triangulationValid = true;
3277 hintData = nullptr;
3278 continue;
3279 }
3280
3281 }
3282
3283 for( auto it = partitions.rbegin(); it != partitions.rend(); ++it )
3284 polySet.AddOutline( *it );
3285
3286 hintData = nullptr;
3287 continue;
3288 }
3289 }
3290 }
3291
3292 dest.push_back( std::make_unique<TRIANGULATED_POLYGON>( forOutline ) );
3293 POLYGON_TRIANGULATION tess( *dest.back() );
3294
3295 // If the tessellation fails, we re-fracture the polygon, which will
3296 // first simplify the system before fracturing and removing the holes
3297 // This may result in multiple, disjoint polygons.
3298 if( !tess.TesselatePolygon( polySet.Polygon( 0 ).front(),
3299 hintData ? hintData->at( index ).get() : nullptr ) )
3300 {
3301 ++pass;
3302
3303 if( pass == 1 )
3304 {
3306 }
3307 // In Clipper2, there is only one type of simplification
3308 else
3309 {
3310 break;
3311 }
3312
3313 triangulationValid = false;
3314 hintData = nullptr;
3315 continue;
3316 }
3317
3318 polySet.DeletePolygon( 0 );
3319 index++;
3320 triangulationValid = true;
3321 }
3322
3323 return triangulationValid;
3324 };
3325
3326 m_triangulatedPolys.clear();
3327
3328 const SHAPE_POLY_SET* srcSet = this;
3329 SHAPE_POLY_SET tmpSet;
3330
3331 if( ArcCount() > 0 || aSimplify )
3332 {
3333 tmpSet = SHAPE_POLY_SET( *this );
3334 tmpSet.ClearArcs();
3335
3336 if( aSimplify )
3337 tmpSet.Simplify();
3338
3339 srcSet = &tmpSet;
3340 }
3341
3342 bool directOk = true;
3343
3344 for( int ii = 0; ii < srcSet->OutlineCount() && directOk; ++ii )
3345 {
3346 const POLYGON& poly = srcSet->CPolygon( ii );
3347 size_t prevCount = m_triangulatedPolys.size();
3348
3349 if( poly.size() > 1 )
3350 {
3351 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3353
3354 if( tess.TesselatePolygon( poly, nullptr ) )
3355 continue;
3356
3357 // Hole bridging failed; fracture to merge holes into the outline
3358 m_triangulatedPolys.resize( prevCount );
3359
3360 SHAPE_POLY_SET flatSet( poly.front() );
3361
3362 for( size_t jj = 1; jj < poly.size(); ++jj )
3363 flatSet.AddHole( poly[jj] );
3364
3365 flatSet.Fracture();
3366 flatSet.splitSelfTouchingOutlines();
3367
3368 if( triangulate( flatSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3369 continue;
3370
3371 m_triangulatedPolys.resize( prevCount );
3372 directOk = false;
3373 continue;
3374 }
3375
3376 {
3377 TRIANGULATED_POLYGON partScratch( -1 );
3378 POLYGON_TRIANGULATION partChecker( partScratch );
3379
3380 if( partChecker.suggestedPartitionLeafCount( poly.front() ) > 1 )
3381 {
3382 SHAPE_POLY_SET partSet;
3383 partSet.AddOutline( poly.front() );
3384
3385 if( triangulate( partSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3386 {
3387 continue;
3388 }
3389
3390 m_triangulatedPolys.resize( prevCount );
3391 }
3392 }
3393
3394 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3396
3397 bool ok = tess.TesselatePolygon( poly.front(), nullptr );
3398
3399 // Self-touching outlines produce overlapping triangles; detect via area coverage
3400 if( ok )
3401 {
3402 double originalArea = std::abs( poly.front().Area() );
3403
3404 if( originalArea > 0.0 )
3405 {
3406 double triArea = 0.0;
3407
3408 for( const auto& tri : m_triangulatedPolys.back()->Triangles() )
3409 triArea += std::abs( tri.Area() );
3410
3411 double coverage = triArea / originalArea;
3412
3413 if( coverage > 1.01 || coverage < 0.99 )
3414 ok = false;
3415 }
3416 }
3417
3418 if( !ok )
3419 {
3420 m_triangulatedPolys.resize( prevCount );
3421
3422 SHAPE_POLY_SET splitSet;
3423 splitSet.AddOutline( poly[0] );
3424 splitSet.splitSelfTouchingOutlines();
3425
3426 bool splitOk = true;
3427
3428 for( int jj = 0; jj < splitSet.OutlineCount() && splitOk; ++jj )
3429 {
3430 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3431 POLYGON_TRIANGULATION splitTess( *m_triangulatedPolys.back() );
3432 splitOk = splitTess.TesselatePolygon( splitSet.CPolygon( jj ).front(), nullptr );
3433 }
3434
3435 if( !splitOk )
3436 directOk = false;
3437 }
3438 }
3439
3440 if( !m_triangulatedPolys.empty() && m_triangulatedPolys.back()->GetTriangleCount() == 0 )
3441 m_triangulatedPolys.pop_back();
3442
3443 if( directOk && !m_triangulatedPolys.empty() )
3444 {
3445 m_hash = checksum();
3446 m_hashValid = true;
3447 m_triangulationValid = true;
3448 }
3449 else
3450 {
3451 // Fracture each outline individually to preserve source outline indices
3452 m_triangulatedPolys.clear();
3453 bool fallbackOk = true;
3454
3455 for( int ii = 0; ii < srcSet->OutlineCount() && fallbackOk; ++ii )
3456 {
3457 const POLYGON& poly = srcSet->CPolygon( ii );
3458 SHAPE_POLY_SET flatSet( poly.front() );
3459
3460 for( size_t jj = 1; jj < poly.size(); ++jj )
3461 flatSet.AddHole( poly[jj] );
3462
3463 flatSet.ClearArcs();
3464 flatSet.Fracture();
3465 flatSet.splitSelfTouchingOutlines();
3466
3467 if( !triangulate( flatSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3468 fallbackOk = false;
3469 }
3470
3471 if( !fallbackOk )
3472 {
3473 // Last resort: flatten everything together (loses outline indices)
3474 m_triangulatedPolys.clear();
3475 SHAPE_POLY_SET fallbackSet( *this );
3476 fallbackSet.ClearArcs();
3477 fallbackSet.Fracture();
3478 fallbackSet.splitSelfTouchingOutlines();
3479
3480 if( !triangulate( fallbackSet, -1, m_triangulatedPolys, aHintData, aSubmitter ) )
3481 {
3482 wxLogTrace( TRIANGULATE_TRACE, "Failed to triangulate polygon" );
3483 }
3484 else
3485 {
3486 m_triangulationValid = true;
3487 }
3488 }
3489 else
3490 {
3491 m_triangulationValid = true;
3492 }
3493
3495 {
3496 m_hash = checksum();
3497 m_hashValid = true;
3498 }
3499 else
3500 {
3502 m_failedHashValid = true;
3503 }
3504 }
3505}
3506
3507
3508
3510{
3511 MMH3_HASH hash( 0x68AF835D ); // Arbitrary seed
3512
3513 hash.add( m_polys.size() );
3514
3515 for( const POLYGON& outline : m_polys )
3516 {
3517 hash.add( outline.size() );
3518
3519 for( const SHAPE_LINE_CHAIN& lc : outline )
3520 {
3521 hash.add( lc.PointCount() );
3522
3523 for( int i = 0; i < lc.PointCount(); i++ )
3524 {
3525 VECTOR2I pt = lc.CPoint( i );
3526
3527 hash.add( pt.x );
3528 hash.add( pt.y );
3529 }
3530 }
3531 }
3532
3533 return hash.digest();
3534}
3535
3536
3538{
3539 for( int i = 0; i < OutlineCount(); i++ )
3540 {
3541 if( hasTouchingHoles( CPolygon( i ) ) )
3542 return true;
3543 }
3544
3545 return false;
3546}
3547
3548
3550{
3551 std::set<long long> ptHashes;
3552
3553 for( const SHAPE_LINE_CHAIN& lc : aPoly )
3554 {
3555 for( const VECTOR2I& pt : lc.CPoints() )
3556 {
3557 const long long ptHash = (long long) pt.x << 32 | pt.y;
3558
3559 if( ptHashes.count( ptHash ) > 0 )
3560 return true;
3561
3562 ptHashes.insert( ptHash );
3563 }
3564 }
3565
3566 return false;
3567}
3568
3569
3574
3575
3577{
3578 size_t n = 0;
3579
3580 for( const std::unique_ptr<TRIANGULATED_POLYGON>& t : m_triangulatedPolys )
3581 n += t->GetTriangleCount();
3582
3583 return n;
3584}
3585
3586
3587void SHAPE_POLY_SET::GetIndexableSubshapes( std::vector<const SHAPE*>& aSubshapes ) const
3588{
3589 aSubshapes.reserve( GetIndexableSubshapeCount() );
3590
3591 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
3592 {
3593 for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
3594 aSubshapes.push_back( &tri );
3595 }
3596}
3597
3598
3600{
3601 BOX2I bbox( parent->m_vertices[a] );
3602 bbox.Merge( parent->m_vertices[b] );
3603 bbox.Merge( parent->m_vertices[c] );
3604
3605 if( aClearance != 0 )
3606 bbox.Inflate( aClearance );
3607
3608 return bbox;
3609}
3610
3611
3613{
3614 m_triangles.emplace_back( a, b, c, this );
3615}
3616
3617
3619{
3621 m_vertices = aOther.m_vertices;
3622 m_triangles = aOther.m_triangles;
3623
3624 for( TRI& tri : m_triangles )
3625 tri.parent = this;
3626}
3627
3628
3630{
3632 m_vertices = aOther.m_vertices;
3633 m_triangles = aOther.m_triangles;
3634
3635 for( TRI& tri : m_triangles )
3636 tri.parent = this;
3637
3638 return *this;
3639}
3640
3641
3643{
3644 const size_t triCount = m_triangles.size();
3645
3646 // Indices are int; skip an implausibly huge mesh rather than overflow.
3647 if( triCount < 2 || triCount > static_cast<size_t>( std::numeric_limits<int>::max() ) / 6 )
3648 return;
3649
3650 const int halfEdgeCount = static_cast<int>( 3 * triCount );
3651
3652 // heVertex[3k+j] is vertex j of triangle k.
3653 std::vector<int> heVertex( halfEdgeCount );
3654
3655 for( size_t k = 0; k < triCount; k++ )
3656 {
3657 heVertex[3 * k + 0] = m_triangles[k].a;
3658 heVertex[3 * k + 1] = m_triangles[k].b;
3659 heVertex[3 * k + 2] = m_triangles[k].c;
3660 }
3661
3662 auto nextInTriangle = []( int aEdge ) { return aEdge - aEdge % 3 + ( aEdge + 1 ) % 3; };
3663
3664 // An untwinned edge is a polygon boundary and must never flip. Slots are never tombstoned so
3665 // probe chains stay intact.
3666 std::vector<int> twin( halfEdgeCount, -1 );
3667
3668 int tableSize = 1;
3669
3670 while( tableSize < halfEdgeCount * 2 )
3671 tableSize <<= 1;
3672
3673 const uint32_t tableMask = static_cast<uint32_t>( tableSize - 1 );
3674 std::vector<int> slotEdge( tableSize, -1 );
3675
3676 // A non-manifold edge (three or more triangles, e.g. a hole-bridge pinch) is retired so no
3677 // triangle straddling it can flip.
3678 std::vector<char> retired( tableSize, 0 );
3679 std::vector<int> toVisit;
3680 std::vector<char> queued( halfEdgeCount, 0 );
3681
3682 for( int edge = 0; edge < halfEdgeCount; edge++ )
3683 {
3684 int from = heVertex[edge];
3685 int to = heVertex[nextInTriangle( edge )];
3686 uint32_t keyLow = static_cast<uint32_t>( from < to ? from : to );
3687 uint32_t keyHigh = static_cast<uint32_t>( from < to ? to : from );
3688 uint32_t slot = ( ( keyLow * 0x9e3779b1u ) ^ ( keyHigh * 0x85ebca6bu ) ) & tableMask;
3689 bool handled = false;
3690
3691 while( slotEdge[slot] != -1 )
3692 {
3693 int other = slotEdge[slot];
3694 int otherFrom = heVertex[other];
3695 int otherTo = heVertex[nextInTriangle( other )];
3696 uint32_t otherLow = static_cast<uint32_t>( otherFrom < otherTo ? otherFrom : otherTo );
3697 uint32_t otherHigh = static_cast<uint32_t>( otherFrom < otherTo ? otherTo : otherFrom );
3698
3699 if( otherLow == keyLow && otherHigh == keyHigh )
3700 {
3701 if( retired[slot] )
3702 {
3703 // Non-manifold; stays a boundary.
3704 }
3705 else if( twin[other] == -1 )
3706 {
3707 twin[edge] = other;
3708 twin[other] = edge;
3709 queued[other] = 1;
3710 toVisit.push_back( other );
3711 }
3712 else
3713 {
3714 // Third half-edge here: retire the edge and unlink the pair already formed.
3715 int mate = twin[other];
3716 twin[other] = -1;
3717 twin[mate] = -1;
3718 retired[slot] = 1;
3719 }
3720
3721 handled = true;
3722 break;
3723 }
3724
3725 slot = ( slot + 1 ) & tableMask;
3726 }
3727
3728 if( !handled )
3729 slotEdge[slot] = edge;
3730 }
3731
3732 while( !toVisit.empty() )
3733 {
3734 int edge = toVisit.back();
3735 toVisit.pop_back();
3736 queued[edge] = 0;
3737
3738 int twinEdge = twin[edge];
3739
3740 if( twinEdge == -1 )
3741 continue;
3742
3743 int tri = edge - edge % 3;
3744 int twinTri = twinEdge - twinEdge % 3;
3745
3746 int apexAEdge = tri + ( edge + 2 ) % 3;
3747 int sharedEndEdge = tri + ( edge + 1 ) % 3;
3748 int apexBEdge = twinTri + ( twinEdge + 2 ) % 3;
3749 int twinSharedEdge = twinTri + ( twinEdge + 1 ) % 3;
3750
3751 int apexA = heVertex[apexAEdge];
3752 int sharedFrom = heVertex[edge];
3753 int sharedTo = heVertex[sharedEndEdge];
3754 int apexB = heVertex[apexBEdge];
3755
3756 const VECTOR2I& apexAPt = m_vertices[apexA];
3757 const VECTOR2I& sharedFromPt = m_vertices[sharedFrom];
3758 const VECTOR2I& sharedToPt = m_vertices[sharedTo];
3759 const VECTOR2I& apexBPt = m_vertices[apexB];
3760
3761 // A non-convex quad would flip a triangle outside the polygon.
3762 if( KIGEOM::OrientationSign( apexAPt, sharedFromPt, apexBPt ) <= 0 )
3763 continue;
3764
3765 if( KIGEOM::OrientationSign( apexAPt, apexBPt, sharedToPt ) <= 0 )
3766 continue;
3767
3768 // Prefer the diagonal with fewer slivers, Delaunay breaks ties. Each flip lowers
3769 // (slivers, then -min-angle) lexicographically, so the loop terminates.
3770 bool legal = KIGEOM::InCircleDelaunayLegal( apexAPt, sharedFromPt, sharedToPt, apexBPt );
3771
3772 int curSlivers = KIGEOM::IsSliverTriangle( apexAPt, sharedFromPt, sharedToPt )
3773 + KIGEOM::IsSliverTriangle( apexBPt, sharedToPt, sharedFromPt );
3774
3775 // Nothing to gain when already Delaunay and neither triangle is a sliver.
3776 if( legal && curSlivers == 0 )
3777 continue;
3778
3779 int flipSlivers = KIGEOM::IsSliverTriangle( apexAPt, sharedFromPt, apexBPt )
3780 + KIGEOM::IsSliverTriangle( apexAPt, apexBPt, sharedToPt );
3781
3782 bool doFlip = ( flipSlivers != curSlivers ) ? ( flipSlivers < curSlivers ) : !legal;
3783
3784 if( !doFlip )
3785 continue;
3786
3787 heVertex[edge] = apexB;
3788 heVertex[twinEdge] = apexA;
3789
3790 int apexBOuterTwin = twin[apexBEdge];
3791 int apexAOuterTwin = twin[apexAEdge];
3792
3793 twin[edge] = apexBOuterTwin;
3794
3795 if( apexBOuterTwin != -1 )
3796 twin[apexBOuterTwin] = edge;
3797
3798 twin[twinEdge] = apexAOuterTwin;
3799
3800 if( apexAOuterTwin != -1 )
3801 twin[apexAOuterTwin] = twinEdge;
3802
3803 twin[apexAEdge] = apexBEdge;
3804 twin[apexBEdge] = apexAEdge;
3805
3806 // The flip may have made the quad's four outer edges illegal.
3807 for( int outer : { edge, twinEdge, sharedEndEdge, twinSharedEdge } )
3808 {
3809 if( twin[outer] != -1 && !queued[outer] )
3810 {
3811 queued[outer] = 1;
3812 toVisit.push_back( outer );
3813 }
3814 }
3815 }
3816
3817 std::deque<TRI> refined;
3818
3819 for( size_t i = 0; i < triCount; i++ )
3820 refined.emplace_back( heVertex[3 * i + 0], heVertex[3 * i + 1], heVertex[3 * i + 2], this );
3821
3822 m_triangles = std::move( refined );
3823}
3824
3825
3827 m_sourceOutline( aSourceOutline )
3828{
3829}
3830
3831
3835
3836
3837void SHAPE_POLY_SET::Scale( double aScaleFactorX, double aScaleFactorY, const VECTOR2I& aCenter )
3838{
3839 for( POLYGON& poly : m_polys )
3840 {
3841 for( SHAPE_LINE_CHAIN& path : poly )
3842 {
3843 for( int i = 0; i < path.PointCount(); i++ )
3844 {
3845 VECTOR2I pt = path.CPoint( i );
3846 VECTOR2D vec;
3847 vec.x = ( pt.x - aCenter.x ) * aScaleFactorX;
3848 vec.y = ( pt.y - aCenter.y ) * aScaleFactorY;
3849 pt.x = KiROUND<double, int>( aCenter.x + vec.x );
3850 pt.y = KiROUND<double, int>( aCenter.y + vec.y );
3851 path.SetPoint( i, pt );
3852 }
3853 }
3854 }
3855
3858}
3859
3860
3861void
3862SHAPE_POLY_SET::BuildPolysetFromOrientedPaths( const std::vector<SHAPE_LINE_CHAIN>& aPaths,
3863 bool aEvenOdd )
3864{
3865 Clipper2Lib::Clipper64 clipper;
3866 Clipper2Lib::PolyTree64 tree;
3867 Clipper2Lib::Paths64 paths;
3868
3869 for( const SHAPE_LINE_CHAIN& path : aPaths )
3870 {
3871 Clipper2Lib::Path64 lc;
3872 lc.reserve( path.PointCount() );
3873
3874 for( int i = 0; i < path.PointCount(); i++ )
3875 lc.emplace_back( path.CPoint( i ).x, path.CPoint( i ).y );
3876
3877 paths.push_back( std::move( lc ) );
3878 }
3879
3880 clipper.AddSubject( paths );
3881 clipper.Execute( Clipper2Lib::ClipType::Union, aEvenOdd ? Clipper2Lib::FillRule::EvenOdd
3882 : Clipper2Lib::FillRule::NonZero, tree );
3883
3884 std::vector<CLIPPER_Z_VALUE> zValues;
3885 std::vector<SHAPE_ARC> arcBuffer;
3886
3887 importTree( tree, zValues, arcBuffer );
3888 tree.Clear(); // Free used memory (not done in dtor)
3889}
3890
3891
3892bool SHAPE_POLY_SET::PointInside( const VECTOR2I& aPt, int aAccuracy, bool aUseBBoxCache ) const
3893{
3894 for( int idx = 0; idx < OutlineCount(); idx++ )
3895 {
3896 if( COutline( idx ).PointInside( aPt, aAccuracy, aUseBBoxCache ) )
3897 return true;
3898 }
3899
3900 return false;
3901}
3902
3903
3904const std::vector<SEG> SHAPE_POLY_SET::GenerateHatchLines( const std::vector<double>& aSlopes,
3905 int aSpacing, int aLineLength ) const
3906{
3907 std::vector<SEG> hatchLines;
3908
3909 if( OutlineCount() == 0 || TotalVertices() == 0 )
3910 return hatchLines;
3911
3912 // define range for hatch lines
3913 int min_x = CVertex( 0 ).x;
3914 int max_x = CVertex( 0 ).x;
3915 int min_y = CVertex( 0 ).y;
3916 int max_y = CVertex( 0 ).y;
3917
3918 for( auto iterator = CIterateWithHoles(); iterator; iterator++ )
3919 {
3920 if( iterator->x < min_x )
3921 min_x = iterator->x;
3922
3923 if( iterator->x > max_x )
3924 max_x = iterator->x;
3925
3926 if( iterator->y < min_y )
3927 min_y = iterator->y;
3928
3929 if( iterator->y > max_y )
3930 max_y = iterator->y;
3931 }
3932
3933 auto sortEndsByDescendingX =
3934 []( const VECTOR2I& ref, const VECTOR2I& tst )
3935 {
3936 return tst.x < ref.x;
3937 };
3938
3939 for( double slope : aSlopes )
3940 {
3941 int64_t max_a, min_a;
3942
3943 if( slope > 0 )
3944 {
3945 max_a = KiROUND<double, int64_t>( max_y - slope * min_x );
3946 min_a = KiROUND<double, int64_t>( min_y - slope * max_x );
3947 }
3948 else
3949 {
3950 max_a = KiROUND<double, int64_t>( max_y - slope * max_x );
3951 min_a = KiROUND<double, int64_t>( min_y - slope * min_x );
3952 }
3953
3954 min_a = ( min_a / aSpacing ) * aSpacing;
3955
3956 // loop through hatch lines
3957 std::vector<VECTOR2I> pointbuffer;
3958 pointbuffer.reserve( 256 );
3959
3960 for( int64_t a = min_a; a < max_a; a += aSpacing )
3961 {
3962 pointbuffer.clear();
3963
3964 // Iterate through all vertices
3965 for( auto iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
3966 {
3967 const SEG seg = *iterator;
3968 VECTOR2I pt;
3969
3970 if( seg.IntersectsLine( slope, a, pt ) )
3971 {
3972 // If the intersection point is outside the polygon, skip it
3973 if( pt.x < min_x || pt.x > max_x || pt.y < min_y || pt.y > max_y )
3974 continue;
3975
3976 // Add the intersection point to the buffer
3977 pointbuffer.emplace_back( KiROUND( pt.x ), KiROUND( pt.y ) );
3978 }
3979 }
3980
3981 // sort points in order of descending x (if more than 2) to
3982 // ensure the starting point and the ending point of the same segment
3983 // are stored one just after the other.
3984 if( pointbuffer.size() > 2 )
3985 sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX );
3986
3987 // creates lines or short segments inside the complex polygon
3988 for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip++ )
3989 {
3990 const VECTOR2I& p1 = pointbuffer[ip];
3991 const VECTOR2I& p2 = pointbuffer[ip + 1];
3992
3993 // Avoid duplicated intersections or segments
3994 if( p1 == p2 )
3995 continue;
3996
3997 SEG candidate( p1, p2 );
3998
3999 VECTOR2I mid( ( candidate.A.x + candidate.B.x ) / 2, ( candidate.A.y + candidate.B.y ) / 2 );
4000
4001 // Check if segment is inside the polygon by checking its middle point
4002 if( Contains( mid, -1, 1, true ) )
4003 {
4004 int dx = p2.x - p1.x;
4005
4006 // Push only one line for diagonal hatch or for small lines < twice
4007 // the line length; else push 2 small lines
4008 if( aLineLength == -1 || std::abs( dx ) < 2 * aLineLength )
4009 {
4010 hatchLines.emplace_back( candidate );
4011 }
4012 else
4013 {
4014 double dy = p2.y - p1.y;
4015 slope = dy / dx;
4016
4017 if( dx > 0 )
4018 dx = aLineLength;
4019 else
4020 dx = -aLineLength;
4021
4022 int x1 = KiROUND( p1.x + dx );
4023 int x2 = KiROUND( p2.x - dx );
4024 int y1 = KiROUND( p1.y + dx * slope );
4025 int y2 = KiROUND( p2.y - dx * slope );
4026
4027 hatchLines.emplace_back( SEG( p1.x, p1.y, x1, y1 ) );
4028
4029 hatchLines.emplace_back( SEG( p2.x, p2.y, x2, y2 ) );
4030 }
4031 }
4032 }
4033 }
4034 }
4035
4036 return hatchLines;
4037}
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:927
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:995
BOX2< VECTOR2D > BOX2D
Definition box2.h:928
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:653
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
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:802
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:420
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:411
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
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
VECTOR2I location
int actual
wxString result
Test unit parsing edge cases and error handling.
int delta
#define M_PI
constexpr int sign(T val)
Definition util.h:141
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