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