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