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