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