KiCad PCB EDA Suite
Loading...
Searching...
No Matches
shape_poly_set.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2015-2019 CERN
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 * @author Alejandro GarcĂ­a Montoro <[email protected]>
9 *
10 * Point in polygon algorithm adapted from Clipper Library (C) Angus Johnson,
11 * subject to Clipper library license.
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25 */
26
27#include <algorithm>
28#include <assert.h> // for assert
29#include <cmath> // for sqrt, cos, hypot, isinf
30#include <cstdio>
31#include <istream> // for operator<<, operator>>
32#include <limits> // for numeric_limits
33#include <map>
34#include <memory>
35#include <set>
36#include <string> // for char_traits, operator!=
37#include <unordered_map>
38#include <unordered_set>
39#include <thread>
40#include <utility> // for swap, move
41#include <vector>
42#include <array>
43
44#include <clipper2/clipper.h>
48#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
49#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>
61
62#include <wx/log.h>
63
64// ADVANCED_CFG::GetCfg() cannot be used on msys2/mingw builds (link failure)
65// So we use the ADVANCED_CFG default values
66#if defined( __MINGW32__ )
67 #define TRIANGULATESIMPLIFICATIONLEVEL 50
68 #define ENABLECACHEFRIENDLYFRACTURE true
69 #define ENABLEFRACTUREEDGEINDEX true
70#else
71 #define TRIANGULATESIMPLIFICATIONLEVEL ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel
72 #define ENABLECACHEFRIENDLYFRACTURE ADVANCED_CFG::GetCfg().m_EnableCacheFriendlyFracture
73 #define ENABLEFRACTUREEDGEINDEX ADVANCED_CFG::GetCfg().m_EnableFractureEdgeIndex
74#endif
75
80
81
84{
85 NewOutline();
86 Append( VECTOR2I( aRect.GetLeft(), aRect.GetTop() ) );
87 Append( VECTOR2I( aRect.GetRight(), aRect.GetTop() ) );
88 Append( VECTOR2I( aRect.GetRight(), aRect.GetBottom() ) );
89 Append( VECTOR2I( aRect.GetLeft(), aRect.GetBottom() ) );
90 Outline( 0 ).SetClosed( true );
91}
92
93
96{
97 AddOutline( aOutline );
98}
99
100
103{
104 AddPolygon( aPolygon );
105}
106
107
109 SHAPE( aOther ),
110 m_polys( aOther.m_polys )
111{
112 if( aOther.IsTriangulationUpToDate() )
113 {
114 m_triangulatedPolys.reserve( aOther.TriangulatedPolyCount() );
115
116 for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
117 {
118 const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
119 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
120 }
121
122 m_hash = aOther.GetHash();
123 m_hashValid = true;
125 }
126 else
127 {
128 m_hash.Clear();
129 m_hashValid = false;
130 m_triangulationValid = false;
131 }
132
133 m_failedHash = aOther.m_failedHash;
134 m_failedHashValid.store( aOther.m_failedHashValid.load() );
135}
136
137
139 SHAPE( aOther ),
140 m_polys( aOther.m_polys )
141{
142 m_hash.Clear();
143 m_hashValid = false;
144 m_triangulationValid = false;
145}
146
147
151
152
154{
155 return new SHAPE_POLY_SET( *this );
156}
157
158
163
164
166 SHAPE_POLY_SET::VERTEX_INDEX* aRelativeIndices ) const
167{
168 int polygonIdx = 0;
169 unsigned int contourIdx = 0;
170 int vertexIdx = 0;
171
172 int currentGlobalIdx = 0;
173
174 for( polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
175 {
176 const POLYGON& currentPolygon = CPolygon( polygonIdx );
177
178 for( contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
179 {
180 const SHAPE_LINE_CHAIN& currentContour = currentPolygon[contourIdx];
181 int totalPoints = currentContour.PointCount();
182
183 for( vertexIdx = 0; vertexIdx < totalPoints; vertexIdx++ )
184 {
185 // Check if the current vertex is the globally indexed as aGlobalIdx
186 if( currentGlobalIdx == aGlobalIdx )
187 {
188 aRelativeIndices->m_polygon = polygonIdx;
189 aRelativeIndices->m_contour = contourIdx;
190 aRelativeIndices->m_vertex = vertexIdx;
191
192 return true;
193 }
194
195 // Advance
196 currentGlobalIdx++;
197 }
198 }
199 }
200
201 return false;
202}
203
204
206 int& aGlobalIdx ) const
207{
208 int selectedVertex = aRelativeIndices.m_vertex;
209 unsigned int selectedContour = aRelativeIndices.m_contour;
210 unsigned int selectedPolygon = aRelativeIndices.m_polygon;
211
212 // Check whether the vertex indices make sense in this poly set
213 if( selectedPolygon < m_polys.size() && selectedContour < m_polys[selectedPolygon].size()
214 && selectedVertex < m_polys[selectedPolygon][selectedContour].PointCount() )
215 {
216 POLYGON currentPolygon;
217
218 aGlobalIdx = 0;
219
220 for( unsigned int polygonIdx = 0; polygonIdx < selectedPolygon; polygonIdx++ )
221 {
222 currentPolygon = Polygon( polygonIdx );
223
224 for( unsigned int contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
225 aGlobalIdx += currentPolygon[contourIdx].PointCount();
226 }
227
228 currentPolygon = Polygon( selectedPolygon );
229
230 for( unsigned int contourIdx = 0; contourIdx < selectedContour; contourIdx++ )
231 aGlobalIdx += currentPolygon[contourIdx].PointCount();
232
233 aGlobalIdx += selectedVertex;
234
235 return true;
236 }
237 else
238 {
239 return false;
240 }
241}
242
243
245{
246 SHAPE_LINE_CHAIN empty_path;
247 POLYGON poly;
248
249 empty_path.SetClosed( true );
250 poly.push_back( empty_path );
251 m_polys.push_back( std::move( poly ) );
252 return m_polys.size() - 1;
253}
254
255
256int SHAPE_POLY_SET::NewHole( int aOutline )
257{
258 SHAPE_LINE_CHAIN empty_path;
259
260 empty_path.SetClosed( true );
261
262 // Default outline is the last one
263 if( aOutline < 0 )
264 aOutline += m_polys.size();
265
266 // Add hole to the selected outline
267 m_polys[aOutline].push_back( empty_path );
268
269 return m_polys.back().size() - 2;
270}
271
272
273int SHAPE_POLY_SET::Append( int x, int y, int aOutline, int aHole, bool aAllowDuplication )
274{
275 assert( m_polys.size() );
276
277 if( aOutline < 0 )
278 aOutline += m_polys.size();
279
280 int idx;
281
282 if( aHole < 0 )
283 idx = 0;
284 else
285 idx = aHole + 1;
286
287 assert( aOutline < (int) m_polys.size() );
288 assert( idx < (int) m_polys[aOutline].size() );
289
290 m_polys[aOutline][idx].Append( x, y, aAllowDuplication );
291
292 return m_polys[aOutline][idx].PointCount();
293}
294
295
296int SHAPE_POLY_SET::Append( const SHAPE_ARC& aArc, int aOutline, int aHole,
297 std::optional<int> aMaxError )
298{
299 assert( m_polys.size() );
300
301 if( aOutline < 0 )
302 aOutline += m_polys.size();
303
304 int idx;
305
306 if( aHole < 0 )
307 idx = 0;
308 else
309 idx = aHole + 1;
310
311 assert( aOutline < (int) m_polys.size() );
312 assert( idx < (int) m_polys[aOutline].size() );
313
314 if( aMaxError.has_value() )
315 m_polys[aOutline][idx].Append( aArc, aMaxError.value() );
316 else
317 m_polys[aOutline][idx].Append( aArc );
318
319 return m_polys[aOutline][idx].PointCount();
320}
321
322
323void SHAPE_POLY_SET::InsertVertex( int aGlobalIndex, const VECTOR2I& aNewVertex )
324{
326
327 if( aGlobalIndex < 0 )
328 aGlobalIndex = 0;
329
330 if( aGlobalIndex >= TotalVertices() )
331 {
332 Append( aNewVertex );
333 }
334 else
335 {
336 // Assure the position to be inserted exists; throw an exception otherwise
337 if( GetRelativeIndices( aGlobalIndex, &index ) )
338 m_polys[index.m_polygon][index.m_contour].Insert( index.m_vertex, aNewVertex );
339 else
340 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
341 }
342}
343
344
345int SHAPE_POLY_SET::VertexCount( int aOutline, int aHole ) const
346{
347 if( m_polys.size() == 0 ) // Empty poly set
348 return 0;
349
350 if( aOutline < 0 ) // Use last outline
351 aOutline += m_polys.size();
352
353 int idx;
354
355 if( aHole < 0 )
356 idx = 0;
357 else
358 idx = aHole + 1;
359
360 if( aOutline >= (int) m_polys.size() ) // not existing outline
361 return 0;
362
363 if( idx >= (int) m_polys[aOutline].size() ) // not existing hole
364 return 0;
365
366 return m_polys[aOutline][idx].PointCount();
367}
368
369
371{
372 int full_count = 0;
373
374 if( m_polys.size() == 0 ) // Empty poly set
375 return full_count;
376
377 for( int ii = 0; ii < OutlineCount(); ii++ )
378 {
379 // the first polygon in m_polys[ii] is the main contour,
380 // only others are holes:
381 for( int idx = 0; idx <= HoleCount( ii ); idx++ )
382 {
383 full_count += m_polys[ii][idx].PointCount();
384 }
385 }
386
387 return full_count;
388}
389
390
391SHAPE_POLY_SET SHAPE_POLY_SET::Subset( int aFirstPolygon, int aLastPolygon )
392{
393 assert( aFirstPolygon >= 0 && aLastPolygon <= OutlineCount() );
394
395 SHAPE_POLY_SET newPolySet;
396
397 for( int index = aFirstPolygon; index < aLastPolygon; index++ )
398 newPolySet.m_polys.push_back( Polygon( index ) );
399
400 return newPolySet;
401}
402
403
404const VECTOR2I& SHAPE_POLY_SET::CVertex( int aIndex, int aOutline, int aHole ) const
405{
406 if( aOutline < 0 )
407 aOutline += m_polys.size();
408
409 int idx;
410
411 if( aHole < 0 )
412 idx = 0;
413 else
414 idx = aHole + 1;
415
416 assert( aOutline < (int) m_polys.size() );
417 assert( idx < (int) m_polys[aOutline].size() );
418
419 return m_polys[aOutline][idx].CPoint( aIndex );
420}
421
422
423const VECTOR2I& SHAPE_POLY_SET::CVertex( int aGlobalIndex ) const
424{
426
427 // Assure the passed index references a legal position; abort otherwise
428 if( !GetRelativeIndices( aGlobalIndex, &index ) )
429 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
430
431 return m_polys[index.m_polygon][index.m_contour].CPoint( index.m_vertex );
432}
433
434
436{
437 return CVertex( index.m_vertex, index.m_polygon, index.m_contour - 1 );
438}
439
440
441bool SHAPE_POLY_SET::GetNeighbourIndexes( int aGlobalIndex, int* aPrevious, int* aNext ) const
442{
444
445 // If the edge does not exist, throw an exception, it is an illegal access memory error
446 if( !GetRelativeIndices( aGlobalIndex, &index ) )
447 return false;
448
449 // Calculate the previous and next index of aGlobalIndex, corresponding to
450 // the same contour;
451 VERTEX_INDEX inext = index;
452 int lastpoint = m_polys[index.m_polygon][index.m_contour].SegmentCount();
453
454 if( index.m_vertex == 0 )
455 {
456 index.m_vertex = lastpoint - 1;
457 inext.m_vertex = 1;
458 }
459 else if( index.m_vertex == lastpoint )
460 {
461 index.m_vertex--;
462 inext.m_vertex = 0;
463 }
464 else
465 {
466 inext.m_vertex++;
467 index.m_vertex--;
468
469 if( inext.m_vertex == lastpoint )
470 inext.m_vertex = 0;
471 }
472
473 if( aPrevious )
474 {
475 int previous;
476 GetGlobalIndex( index, previous );
477 *aPrevious = previous;
478 }
479
480 if( aNext )
481 {
482 int next;
483 GetGlobalIndex( inext, next );
484 *aNext = next;
485 }
486
487 return true;
488}
489
490
491bool SHAPE_POLY_SET::IsPolygonSelfIntersecting( int aPolygonIndex ) const
492{
493 std::vector<SEG> segments;
494 segments.reserve( FullPointCount() );
495
496 for( CONST_SEGMENT_ITERATOR it = CIterateSegmentsWithHoles( aPolygonIndex ); it; it++ )
497 segments.emplace_back( *it );
498
499 std::sort( segments.begin(), segments.end(), []( const SEG& a, const SEG& b )
500 {
501 int min_a_x = std::min( a.A.x, a.B.x );
502 int min_b_x = std::min( b.A.x, b.B.x );
503
504 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 ) );
505 } );
506
507 for( auto it = segments.begin(); it != segments.end(); ++it )
508 {
509 SEG& firstSegment = *it;
510
511 // Iterate through all remaining segments.
512 auto innerIterator = it;
513 int max_x = std::max( firstSegment.A.x, firstSegment.B.x );
514 int max_y = std::max( firstSegment.A.y, firstSegment.B.y );
515
516 // Start in the next segment, we don't want to check collision between a segment and itself
517 for( innerIterator++; innerIterator != segments.end(); innerIterator++ )
518 {
519 SEG& secondSegment = *innerIterator;
520 int min_x = std::min( secondSegment.A.x, secondSegment.B.x );
521 int min_y = std::min( secondSegment.A.y, secondSegment.B.y );
522
523 // We are ordered in minimum point order, so checking the static max (first segment) against
524 // the ordered min will tell us if any of the following segments are withing the BBox
525 if( max_x < min_x || ( max_x == min_x && max_y < min_y ) )
526 break;
527
528 int index_diff = std::abs( firstSegment.Index() - secondSegment.Index() );
529 bool adjacent = ( index_diff == 1) || (index_diff == ((int)segments.size() - 1) );
530
531 // Check whether the two segments built collide, only when they are not adjacent.
532 if( !adjacent && firstSegment.Collide( secondSegment, 0 ) )
533 return true;
534 }
535 }
536
537 return false;
538}
539
540
542{
543 for( unsigned int polygon = 0; polygon < m_polys.size(); polygon++ )
544 {
545 if( IsPolygonSelfIntersecting( polygon ) )
546 return true;
547 }
548
549 return false;
550}
551
552
554{
555 POLYGON poly;
556
557 poly.push_back( aOutline );
558
559 // This is an assertion because if it's generated by KiCad code, it probably
560 // indicates a bug elsewhere that should be fixed, but we also auto-fix it here
561 // for SWIG plugins that might mess it up
562 wxCHECK2_MSG( aOutline.IsClosed(), poly.back().SetClosed( true ),
563 "Warning: non-closed outline added to SHAPE_POLY_SET" );
564
565 m_polys.push_back( std::move( poly ) );
566
567 return (int) m_polys.size() - 1;
568}
569
570
571int SHAPE_POLY_SET::AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline )
572{
573 assert( m_polys.size() );
574
575 if( aOutline < 0 )
576 aOutline += (int) m_polys.size();
577
578 assert( aOutline < (int)m_polys.size() );
579
580 POLYGON& poly = m_polys[aOutline];
581
582 assert( poly.size() );
583
584 poly.push_back( aHole );
585
586 return (int) poly.size() - 2;
587}
588
589
591{
592 m_polys.push_back( apolygon );
593
594 return m_polys.size() - 1;
595}
596
597
599{
600 double area = 0.0;
601
602 for( int i = 0; i < OutlineCount(); i++ )
603 {
604 area += Outline( i ).Area( true );
605
606 for( int j = 0; j < HoleCount( i ); j++ )
607 area -= Hole( i, j ).Area( true );
608 }
609
610 return area;
611}
612
613
615{
616 int retval = 0;
617
618 for( const POLYGON& poly : m_polys )
619 {
620 for( size_t i = 0; i < poly.size(); i++ )
621 retval += poly[i].ArcCount();
622 }
623
624 return retval;
625}
626
627
628void SHAPE_POLY_SET::GetArcs( std::vector<SHAPE_ARC>& aArcBuffer ) const
629{
630 for( const POLYGON& poly : m_polys )
631 {
632 for( size_t i = 0; i < poly.size(); i++ )
633 {
634 for( SHAPE_ARC arc : poly[i].m_arcs )
635 aArcBuffer.push_back( arc );
636 }
637 }
638}
639
640
642{
643 for( POLYGON& poly : m_polys )
644 {
645 for( size_t i = 0; i < poly.size(); i++ )
646 poly[i].ClearArcs();
647 }
648}
649
650
652{
653 std::vector<SHAPE_LINE_CHAIN> contours;
654
655 for( const POLYGON& poly : m_polys )
656 contours.insert( contours.end(), poly.begin(), poly.end() );
657
658 std::map<int, std::set<int>> parentToChildren;
659 std::map<int, std::set<int>> childToParents;
660
661 for( SHAPE_LINE_CHAIN& contour : contours )
662 contour.GenerateBBoxCache();
663
664 for( size_t i = 0; i < contours.size(); i++ )
665 {
666 const SHAPE_LINE_CHAIN& outline = contours[i];
667
668 for( size_t j = 0; j < contours.size(); j++ )
669 {
670 if( i == j )
671 continue;
672
673 const SHAPE_LINE_CHAIN& candidate = contours[j];
674 const VECTOR2I& pt0 = candidate.CPoint( 0 );
675
676 if( outline.PointInside( pt0, 0, true ) )
677 {
678 parentToChildren[i].emplace( j );
679 childToParents[j].emplace( i );
680 }
681 }
682 }
683
684 std::set<int> topLevelParents;
685
686 for( size_t i = 0; i < contours.size(); i++ )
687 {
688 if( childToParents[i].size() == 0 )
689 {
690 topLevelParents.emplace( i );
691 }
692 }
693
695
696 std::function<void( int, int, std::vector<int> )> process;
697
698 process =
699 [&]( int myId, int parentOutlineId, const std::vector<int>& path )
700 {
701 std::set<int> relParents = childToParents[myId];
702
703 for( int pathId : path )
704 {
705 int erased = relParents.erase( pathId );
706 wxASSERT( erased > 0 );
707 }
708
709 wxASSERT( relParents.size() == 0 );
710
711 int myOutline = -1;
712
713 bool isOutline = path.size() % 2 == 0;
714
715 if( isOutline )
716 {
717 int outlineId = result.AddOutline( contours[myId] );
718 myOutline = outlineId;
719 }
720 else
721 {
722 wxASSERT( parentOutlineId != -1 );
723 result.AddHole( contours[myId], parentOutlineId );
724 }
725
726 auto it = parentToChildren.find( myId );
727 if( it != parentToChildren.end() )
728 {
729 std::vector<int> thisPath = path;
730 thisPath.emplace_back( myId );
731
732 std::set<int> thisPathSet;
733 thisPathSet.insert( thisPath.begin(), thisPath.end() );
734
735 for( int childId : it->second )
736 {
737 const std::set<int>& childPathSet = childToParents[childId];
738
739 if( thisPathSet != childPathSet )
740 continue; // Only interested in immediate children
741
742 process( childId, myOutline, thisPath );
743 }
744 }
745 };
746
747 for( int topParentId : topLevelParents )
748 {
749 std::vector<int> path;
750 process( topParentId, -1, std::move( path ) );
751 }
752
753 *this = std::move( result );
754}
755
756
757void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aOtherShape )
758{
759 booleanOp( aType, *this, aOtherShape );
760}
761
762
769static bool splitAtBridges( const SHAPE_LINE_CHAIN& aChain, std::vector<SHAPE_LINE_CHAIN>& aRings )
770{
771 struct DIRECTED_EDGE
772 {
773 VECTOR2I from;
774 VECTOR2I to;
775
776 bool operator==( const DIRECTED_EDGE& aOther ) const
777 {
778 return from == aOther.from && to == aOther.to;
779 }
780 };
781
782 struct DIRECTED_EDGE_HASH
783 {
784 std::size_t operator()( const DIRECTED_EDGE& aEdge ) const
785 {
786 std::size_t seed = 0x51ed27a3;
787 hash_combine( seed, aEdge.from.x, aEdge.from.y, aEdge.to.x, aEdge.to.y );
788 return seed;
789 }
790 };
791
792 const std::vector<VECTOR2I>& pts = aChain.CPoints();
793 const int count = static_cast<int>( pts.size() );
794
795 // A bucket per directed edge so repeated bridges along one line all find a partner
796 std::unordered_map<DIRECTED_EDGE, std::vector<int>, DIRECTED_EDGE_HASH> unpaired;
797 std::vector<int> partner( count, -1 );
798 bool hasBridge = false;
799
800 unpaired.reserve( count );
801
802 for( int ii = 0; ii < count; ++ii )
803 {
804 const VECTOR2I& a = pts[ii];
805 const VECTOR2I& b = pts[( ii + 1 ) % count];
806
807 if( a == b )
808 continue;
809
810 auto twin = unpaired.find( { b, a } );
811
812 if( twin != unpaired.end() )
813 {
814 const int jj = twin->second.back();
815
816 twin->second.pop_back();
817
818 if( twin->second.empty() )
819 unpaired.erase( twin );
820
821 partner[ii] = jj;
822 partner[jj] = ii;
823 hasBridge = true;
824 }
825 else
826 {
827 unpaired[{ a, b }].push_back( ii );
828 }
829 }
830
831 if( !hasBridge )
832 return false;
833
834 // The edge that continues from the end of aEdge once bridge pairs are skipped
835 auto nextEdge =
836 [&]( int aEdge ) -> int
837 {
838 int next = ( aEdge + 1 ) % count;
839
840 for( int guard = 0; partner[next] >= 0; ++guard )
841 {
842 if( guard > count )
843 return -1;
844
845 next = ( partner[next] + 1 ) % count;
846 }
847
848 return next;
849 };
850
851 std::vector<bool> visited( count, false );
852
853 for( int start = 0; start < count; ++start )
854 {
855 if( visited[start] || partner[start] >= 0 )
856 continue;
857
858 SHAPE_LINE_CHAIN ring;
859 int edge = start;
860
861 do
862 {
863 if( edge < 0 || visited[edge] )
864 {
865 aRings.clear();
866 return false;
867 }
868
869 visited[edge] = true;
870 ring.Append( pts[edge], true );
871 edge = nextEdge( edge );
872 } while( edge != start );
873
874 if( ring.PointCount() >= 3 )
875 {
876 ring.SetClosed( true );
877 aRings.push_back( std::move( ring ) );
878 }
879 }
880
881 return true;
882}
883
884
885bool SHAPE_POLY_SET::appendBridgeFreePaths( const SHAPE_LINE_CHAIN& aChain, Clipper2Lib::Paths64& aPaths,
886 std::vector<CLIPPER_Z_VALUE>& aZValues,
887 std::vector<SHAPE_ARC>& aArcBuffer )
888{
889 // Small rings are cheap for Clipper and dominate the boolean call count
890 constexpr int kMinPoints = 1024;
891
892 if( aChain.PointCount() < kMinPoints || aChain.ArcCount() > 0 )
893 return false;
894
895 std::vector<SHAPE_LINE_CHAIN> rings;
896
897 if( !splitAtBridges( aChain, rings ) )
898 return false;
899
900 // convertToClipper2() orients an outline by reversing the whole ring, so every piece of the
901 // ring follows the same flip to keep its winding relative to the others
902 const bool flip = aChain.Area( false ) < 0;
903
904 for( const SHAPE_LINE_CHAIN& ring : rings )
905 aPaths.push_back( ring.convertToClipper2( ( ring.Area( false ) >= 0 ) != flip, aZValues, aArcBuffer ) );
906
907 return true;
908}
909
910
911void SHAPE_POLY_SET::booleanOp( Clipper2Lib::ClipType aType, const SHAPE_POLY_SET& aShape,
912 const SHAPE_POLY_SET& aOtherShape )
913{
914 if( ( aShape.OutlineCount() > 1 || aOtherShape.OutlineCount() > 0 )
915 && ( aShape.ArcCount() > 0 || aOtherShape.ArcCount() > 0 ) )
916 {
917 wxFAIL_MSG( wxT( "Boolean ops on curved polygons are not supported. You should call "
918 "ClearArcs() before carrying out the boolean operation." ) );
919 }
920
921 Clipper2Lib::Clipper64 c;
922
923 std::vector<CLIPPER_Z_VALUE> zValues;
924 std::vector<SHAPE_ARC> arcBuffer;
925 std::map<VECTOR2I, CLIPPER_Z_VALUE> newIntersectPoints;
926
927 Clipper2Lib::Paths64 paths;
928 Clipper2Lib::Paths64 clips;
929
930 for( const POLYGON& poly : aShape.m_polys )
931 {
932 if( poly.size() == 1 && appendBridgeFreePaths( poly[0], paths, zValues, arcBuffer ) )
933 continue;
934
935 for( size_t i = 0; i < poly.size(); i++ )
936 {
937 paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
938 }
939 }
940
941 for( const POLYGON& poly : aOtherShape.m_polys )
942 {
943 if( poly.size() == 1 && appendBridgeFreePaths( poly[0], clips, zValues, arcBuffer ) )
944 continue;
945
946 for( size_t i = 0; i < poly.size(); i++ )
947 {
948 clips.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
949 }
950 }
951
952 c.AddSubject( paths );
953 c.AddClip( clips );
954
955 Clipper2Lib::PolyTree64 solution;
956
957 Clipper2Lib::ZCallback64 callback =
958 [&]( const Clipper2Lib::Point64 & e1bot, const Clipper2Lib::Point64 & e1top,
959 const Clipper2Lib::Point64 & e2bot, const Clipper2Lib::Point64 & e2top,
960 Clipper2Lib::Point64 & pt )
961 {
962 auto arcIndex =
963 [&]( const ssize_t& aZvalue, const ssize_t& aCompareVal = -1 ) -> ssize_t
964 {
965 ssize_t retval;
966
967 retval = zValues.at( aZvalue ).m_SecondArcIdx;
968
969 if( retval == -1 || ( aCompareVal > 0 && retval != aCompareVal ) )
970 retval = zValues.at( aZvalue ).m_FirstArcIdx;
971
972 return retval;
973 };
974
975 auto arcSegment =
976 [&]( const ssize_t& aBottomZ, const ssize_t aTopZ ) -> ssize_t
977 {
978 ssize_t retval = arcIndex( aBottomZ );
979
980 if( retval != -1 )
981 {
982 if( retval != arcIndex( aTopZ, retval ) )
983 retval = -1; // Not an arc segment as the two indices do not match
984 }
985
986 return retval;
987 };
988
989 ssize_t e1ArcSegmentIndex = arcSegment( e1bot.z, e1top.z );
990 ssize_t e2ArcSegmentIndex = arcSegment( e2bot.z, e2top.z );
991
992 CLIPPER_Z_VALUE newZval;
993
994 if( e1ArcSegmentIndex != -1 )
995 {
996 newZval.m_FirstArcIdx = e1ArcSegmentIndex;
997 newZval.m_SecondArcIdx = e2ArcSegmentIndex;
998 }
999 else
1000 {
1001 newZval.m_FirstArcIdx = e2ArcSegmentIndex;
1002 newZval.m_SecondArcIdx = -1;
1003 }
1004
1005 size_t z_value_ptr = zValues.size();
1006 zValues.push_back( newZval );
1007
1008 // Only worry about arc segments for later processing
1009 if( newZval.m_FirstArcIdx != -1 )
1010 newIntersectPoints.insert( { VECTOR2I( pt.x, pt.y ), newZval } );
1011
1012 pt.z = z_value_ptr;
1013 //@todo amend X,Y values to true intersection between arcs or arc and segment
1014 };
1015
1016 c.SetZCallback( std::move( callback ) ); // register callback
1017
1018 c.Execute( aType, Clipper2Lib::FillRule::NonZero, solution );
1019
1020 importTree( solution, zValues, arcBuffer );
1021 solution.Clear(); // Free used memory (not done in dtor)
1022}
1023
1024
1026{
1027 booleanOp( Clipper2Lib::ClipType::Union, b );
1028}
1029
1030
1032{
1033 booleanOp( Clipper2Lib::ClipType::Difference, b );
1034}
1035
1036
1038{
1039 booleanOp( Clipper2Lib::ClipType::Intersection, b );
1040}
1041
1042
1044{
1045 booleanOp( Clipper2Lib::ClipType::Xor, b );
1046}
1047
1048
1050{
1051 booleanOp( Clipper2Lib::ClipType::Union, a, b );
1052}
1053
1054
1056{
1057 booleanOp( Clipper2Lib::ClipType::Difference, a, b );
1058}
1059
1060
1062{
1063 booleanOp( Clipper2Lib::ClipType::Intersection, a, b );
1064}
1065
1066
1068{
1069 booleanOp( Clipper2Lib::ClipType::Xor, a, b );
1070}
1071
1072
1074 int aMaxError )
1075{
1076 Unfracture();
1077 Inflate( aFactor, aCornerStrategy, aMaxError );
1078 Fracture();
1079}
1080
1081
1082void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy,
1083 bool aSimplify )
1084{
1085 using namespace Clipper2Lib;
1086 // A thread-local table to avoid repetitive calculations of the coefficient
1087 // 1.0 - cos( M_PI / aCircleSegCount )
1088 // aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
1089 #define SEG_CNT_MAX 64
1090 static thread_local double arc_tolerance_factor[SEG_CNT_MAX + 1];
1091
1092 ClipperOffset c;
1093
1094 // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
1095 // and are not what you'd think they are.
1096 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
1097 JoinType joinType = JoinType::Round; // The way corners are offsetted
1098 double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
1099
1100 switch( aCornerStrategy )
1101 {
1103 joinType = JoinType::Miter;
1104 miterLimit = 10; // Allows large spikes
1105 break;
1106
1107 case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
1108 joinType = JoinType::Miter;
1109 break;
1110
1111 case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
1112 joinType = JoinType::Miter;
1113 break;
1114
1115 case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
1116 joinType = JoinType::Square;
1117 break;
1118
1119 case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
1120 joinType = JoinType::Round;
1121 break;
1122 }
1123
1124 std::vector<CLIPPER_Z_VALUE> zValues;
1125 std::vector<SHAPE_ARC> arcBuffer;
1126
1127 for( const POLYGON& poly : m_polys )
1128 {
1129 Paths64 paths;
1130
1131 for( size_t i = 0; i < poly.size(); i++ )
1132 paths.push_back( poly[i].convertToClipper2( i == 0, zValues, arcBuffer ) );
1133
1134 c.AddPaths( paths, joinType, EndType::Polygon );
1135 }
1136
1137 // Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
1138 // nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
1139 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
1140
1141 if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
1142 aCircleSegCount = 6;
1143
1144 double coeff;
1145
1146 if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
1147 {
1148 coeff = 1.0 - cos( M_PI / aCircleSegCount );
1149
1150 if( aCircleSegCount <= SEG_CNT_MAX )
1151 arc_tolerance_factor[aCircleSegCount] = coeff;
1152 }
1153 else
1154 {
1155 coeff = arc_tolerance_factor[aCircleSegCount];
1156 }
1157
1158 c.ArcTolerance( std::abs( aAmount ) * coeff );
1159 c.MiterLimit( miterLimit );
1160
1161 PolyTree64 tree;
1162
1163 if( aSimplify )
1164 {
1165 Paths64 paths;
1166 c.Execute( aAmount, paths );
1167
1168 Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, true );
1169
1170 Clipper64 c2;
1171 c2.PreserveCollinear( false );
1172 c2.ReverseSolution( false );
1173 c2.AddSubject( paths );
1174 c2.Execute(ClipType::Union, FillRule::Positive, tree);
1175 }
1176 else
1177 {
1178 c.Execute( aAmount, tree );
1179 }
1180
1181 importTree( tree, zValues, arcBuffer );
1182 tree.Clear();
1183}
1184
1185
1186void SHAPE_POLY_SET::inflateLine2( const SHAPE_LINE_CHAIN& aLine, int aAmount, int aCircleSegCount,
1187 CORNER_STRATEGY aCornerStrategy, bool aSimplify )
1188{
1189 using namespace Clipper2Lib;
1190 // A thread-local table to avoid repetitive calculations of the coefficient
1191 // 1.0 - cos( M_PI / aCircleSegCount )
1192 // aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
1193 #define SEG_CNT_MAX 64
1194 static thread_local double arc_tolerance_factor[SEG_CNT_MAX + 1];
1195
1196 ClipperOffset c;
1197
1198 // N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
1199 // and are not what you'd think they are.
1200 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
1201 JoinType joinType = JoinType::Round; // The way corners are offsetted
1202 double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
1203
1204 switch( aCornerStrategy )
1205 {
1207 joinType = JoinType::Miter;
1208 miterLimit = 10; // Allows large spikes
1209 break;
1210
1211 case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
1212 joinType = JoinType::Miter;
1213 break;
1214
1215 case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
1216 joinType = JoinType::Miter;
1217 break;
1218
1219 case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
1220 joinType = JoinType::Square;
1221 break;
1222
1223 case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
1224 joinType = JoinType::Round;
1225 break;
1226 }
1227
1228 std::vector<CLIPPER_Z_VALUE> zValues;
1229 std::vector<SHAPE_ARC> arcBuffer;
1230
1231 Path64 path = aLine.convertToClipper2( true, zValues, arcBuffer );
1232 c.AddPath( path, joinType, EndType::Butt );
1233
1234 // Calculate the arc tolerance (arc error) from the seg count by circle. The seg count is
1235 // nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aAmount))
1236 // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
1237
1238 if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
1239 aCircleSegCount = 6;
1240
1241 double coeff;
1242
1243 if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
1244 {
1245 coeff = 1.0 - cos( M_PI / aCircleSegCount );
1246
1247 if( aCircleSegCount <= SEG_CNT_MAX )
1248 arc_tolerance_factor[aCircleSegCount] = coeff;
1249 }
1250 else
1251 {
1252 coeff = arc_tolerance_factor[aCircleSegCount];
1253 }
1254
1255 c.ArcTolerance( std::abs( aAmount ) * coeff );
1256 c.MiterLimit( miterLimit );
1257
1258 PolyTree64 tree;
1259
1260 if( aSimplify )
1261 {
1262 Paths64 paths2;
1263 c.Execute( aAmount, paths2 );
1264
1265 Clipper2Lib::SimplifyPaths( paths2, std::abs( aAmount ) * coeff, false );
1266
1267 Clipper64 c2;
1268 c2.PreserveCollinear( false );
1269 c2.ReverseSolution( false );
1270 c2.AddSubject( paths2 );
1271 c2.Execute( ClipType::Union, FillRule::Positive, tree );
1272 }
1273 else
1274 {
1275 c.Execute( aAmount, tree );
1276 }
1277
1278 importTree( tree, zValues, arcBuffer );
1279 tree.Clear();
1280}
1281
1282
1283void SHAPE_POLY_SET::Inflate( int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError,
1284 bool aSimplify )
1285{
1286 int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
1287
1288 inflate2( aAmount, segCount, aCornerStrategy, aSimplify );
1289}
1290
1291
1293 CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify )
1294{
1295 int segCount = GetArcToSegmentCount( std::abs( aAmount ), aMaxError, FULL_CIRCLE );
1296
1297 inflateLine2( aLine, aAmount, segCount, aCornerStrategy, aSimplify );
1298}
1299
1300
1301void SHAPE_POLY_SET::importPolyPath( const std::unique_ptr<Clipper2Lib::PolyPath64>& aPolyPath,
1302 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1303 const std::vector<SHAPE_ARC>& aArcBuffer )
1304{
1305 if( !aPolyPath->IsHole() )
1306 {
1307 POLYGON paths;
1308 paths.reserve( aPolyPath->Count() + 1 );
1309 paths.emplace_back( aPolyPath->Polygon(), aZValueBuffer, aArcBuffer );
1310
1311 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& child : *aPolyPath )
1312 {
1313 paths.emplace_back( child->Polygon(), aZValueBuffer, aArcBuffer );
1314
1315 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& grandchild : *child )
1316 importPolyPath( grandchild, aZValueBuffer, aArcBuffer );
1317 }
1318
1319 m_polys.emplace_back( std::move( paths ) );
1320 }
1321}
1322
1323
1324void SHAPE_POLY_SET::importTree( Clipper2Lib::PolyTree64& tree,
1325 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1326 const std::vector<SHAPE_ARC>& aArcBuffer )
1327{
1328 m_polys.clear();
1329
1330 for( const std::unique_ptr<Clipper2Lib::PolyPath64>& n : tree )
1331 importPolyPath( n, aZValueBuffer, aArcBuffer );
1332}
1333
1334
1335void SHAPE_POLY_SET::importPaths( Clipper2Lib::Paths64& aPath,
1336 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
1337 const std::vector<SHAPE_ARC>& aArcBuffer )
1338{
1339 m_polys.clear();
1340 POLYGON path;
1341
1342 for( const Clipper2Lib::Path64& n : aPath )
1343 {
1344 if( Clipper2Lib::Area( n ) > 0 )
1345 {
1346 if( !path.empty() )
1347 m_polys.emplace_back( path );
1348
1349 path.clear();
1350 }
1351 else
1352 {
1353 wxCHECK2_MSG( !path.empty(), continue, wxT( "Cannot add a hole before an outline" ) );
1354 }
1355
1356 path.emplace_back( n, aZValueBuffer, aArcBuffer );
1357 }
1358
1359 if( !path.empty() )
1360 m_polys.emplace_back( std::move( path ) );
1361}
1362
1363
1365{
1366 using Index = int;
1367
1368 FractureEdge() = default;
1369
1370 FractureEdge( const VECTOR2I& p1, const VECTOR2I& p2, Index next ) :
1371 m_p1( p1 ),
1372 m_p2( p2 ),
1373 m_next( next )
1374 {
1375 }
1376
1377 bool matches( int y ) const
1378 {
1379 return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
1380 }
1381
1385};
1386
1387
1388typedef std::vector<FractureEdge> FractureEdgeSet;
1389
1390
1392{
1393public:
1395
1396 struct NODE
1397 {
1398 uint32_t edge;
1399 uint32_t next;
1400 };
1401
1402 template <typename Originals>
1403 FRACTURE_EDGE_INDEX( FractureEdgeSet& aEdges, Originals&& aOriginals, int aMinY, int aMaxY,
1404 uint32_t aStripeCount, size_t aHoleCount ) :
1405 m_edges( aEdges ),
1406 m_minY( aMinY ),
1407 m_maxY( aMaxY ),
1408 m_stripeCount( aStripeCount ),
1409 m_maxNodes( ( KIGEOM::FRACTURE_INDEX::MAX_BUCKET_SPAN + 2 ) * aHoleCount ),
1410 m_budget( KIGEOM::FRACTURE_INDEX::CapacityBudget( aEdges.size(), sizeof( FractureEdge ) ) )
1411 {
1412 std::vector<uint32_t> counts( m_stripeCount );
1413 size_t bucketIds = 0;
1414 size_t longIds = 0;
1415
1416 aOriginals(
1417 [&]( uint32_t, uint32_t aFirst, uint32_t aLast )
1418 {
1419 if( aLast - aFirst + 1 > KIGEOM::FRACTURE_INDEX::MAX_BUCKET_SPAN )
1420 {
1421 ++longIds;
1422 }
1423 else
1424 {
1425 bucketIds += aLast - aFirst + 1;
1426
1427 for( uint32_t stripe = aFirst; stripe <= aLast; ++stripe )
1428 ++counts[stripe];
1429 }
1430 } );
1431
1432 if( !KIGEOM::FRACTURE_INDEX::CapacityFits( bucketIds, longIds, m_stripeCount, aHoleCount, sizeof( NODE ),
1433 m_budget ) )
1434 {
1435 return;
1436 }
1437
1438 m_offsets.resize( m_stripeCount + 1 );
1439
1440 for( uint32_t stripe = 0; stripe < m_stripeCount; ++stripe )
1441 m_offsets[stripe + 1] = m_offsets[stripe] + counts[stripe];
1442
1443 m_bucketIds.resize( bucketIds );
1444 m_longIds.reserve( longIds );
1445
1446 std::copy( m_offsets.begin(), m_offsets.end() - 1, counts.begin() );
1447
1448 aOriginals(
1449 [&]( uint32_t aEdge, uint32_t aFirst, uint32_t aLast )
1450 {
1451 if( aLast - aFirst + 1 > KIGEOM::FRACTURE_INDEX::MAX_BUCKET_SPAN )
1452 {
1453 m_longIds.push_back( aEdge );
1454 }
1455 else
1456 {
1457 for( uint32_t stripe = aFirst; stripe <= aLast; ++stripe )
1458 m_bucketIds[counts[stripe]++] = aEdge;
1459 }
1460 } );
1461
1462 m_heads.assign( m_stripeCount + 1, INVALID );
1463 m_nodes.reserve( m_maxNodes );
1464
1465 // The ascending order the Query breaks rely on, asserted once rather than per visit
1466#ifndef NDEBUG
1467 for( uint32_t stripe = 0; stripe < m_stripeCount; ++stripe )
1468 {
1469 for( uint32_t pos = m_offsets[stripe] + 1; pos < m_offsets[stripe + 1]; ++pos )
1470 assert( m_bucketIds[pos] >= m_bucketIds[pos - 1] );
1471 }
1472
1473 for( size_t pos = 1; pos < m_longIds.size(); ++pos )
1474 assert( m_longIds[pos] >= m_longIds[pos - 1] );
1475#endif
1476
1477 // The estimate prevents an over-budget allocation; this check catches allocator over-capacity.
1478 size_t allocated_bytes = 0;
1479
1481 m_offsets.capacity(), counts.capacity(), m_heads.capacity(),
1482 m_nodes.capacity(), sizeof( NODE ), m_budget,
1483 &allocated_bytes ) )
1484 {
1485 return;
1486 }
1487
1488 m_valid = true;
1489 logAccepted( aEdges.size(), aHoleCount, m_stripeCount, allocated_bytes );
1490 }
1491
1492 bool IsValid() const { return m_valid; }
1493
1494 template <typename Visitor>
1495 void Query( int aY, Index aProvokingIndex, Visitor&& aVisitor ) const
1496 {
1497 const uint32_t stripe = map( aY );
1498
1499 // Buckets and the long list are ascending, so the first out-of-range id ends the scan
1500 for( uint32_t pos = m_offsets[stripe]; pos < m_offsets[stripe + 1]; ++pos )
1501 {
1502 const uint32_t edge = m_bucketIds[pos];
1503
1504 if( edge >= static_cast<uint32_t>( aProvokingIndex ) )
1505 break;
1506
1507 aVisitor( static_cast<Index>( edge ) );
1508 }
1509
1510 for( uint32_t edge : m_longIds )
1511 {
1512 if( edge >= static_cast<uint32_t>( aProvokingIndex ) )
1513 break;
1514
1515 aVisitor( static_cast<Index>( edge ) );
1516 }
1517
1518 visitOverflow( m_heads[stripe], aProvokingIndex, aVisitor );
1519 visitOverflow( m_heads[m_stripeCount], aProvokingIndex, aVisitor );
1520 }
1521
1522 void InsertBridges( Index aFirst )
1523 {
1524 for( Index edge = aFirst; edge < aFirst + 3; ++edge )
1525 {
1526 const auto [first, last] = span( m_edges[edge] );
1527
1528 if( last - first + 1 > KIGEOM::FRACTURE_INDEX::MAX_BUCKET_SPAN )
1529 {
1530 insert( static_cast<uint32_t>( edge ), m_stripeCount );
1531 }
1532 else
1533 {
1534 for( uint32_t stripe = first; stripe <= last; ++stripe )
1535 insert( static_cast<uint32_t>( edge ), stripe );
1536 }
1537 }
1538 }
1539
1540private:
1541 static constexpr uint32_t INVALID = std::numeric_limits<uint32_t>::max();
1542
1543 uint32_t map( int aY ) const { return KIGEOM::FRACTURE_INDEX::MapYToStripe( aY, m_minY, m_maxY, m_stripeCount ); }
1544
1545 std::pair<uint32_t, uint32_t> span( const FractureEdge& aEdge ) const
1546 {
1548 }
1549
1550 template <typename Visitor>
1551 void visitOverflow( uint32_t aNode, Index aProvokingIndex, Visitor&& aVisitor ) const
1552 {
1553 // Bridges are only ever inserted for already processed holes, so the assert should hold,
1554 // but the filter keeps the release build honest if that ordering ever changes
1555 while( aNode != INVALID )
1556 {
1557 const NODE& node = m_nodes[aNode];
1558 assert( node.edge < static_cast<uint32_t>( aProvokingIndex ) );
1559
1560 if( node.edge < static_cast<uint32_t>( aProvokingIndex ) )
1561 aVisitor( static_cast<Index>( node.edge ) );
1562
1563 aNode = node.next;
1564 }
1565 }
1566
1567 void insert( uint32_t aEdge, uint32_t aHead )
1568 {
1569 assert( m_nodes.size() < m_maxNodes );
1570 m_nodes.push_back( { aEdge, m_heads[aHead] } );
1571 m_heads[aHead] = static_cast<uint32_t>( m_nodes.size() - 1 );
1572 }
1573
1574 static void logAccepted( size_t aEdges, size_t aHoles, uint32_t aStripes, size_t aBytes )
1575 {
1576 wxLogTrace( wxS( "KICAD_FRACTURE_INDEX" ),
1577 wxS( "Using fracture edge index: edges=%zu, holes=%zu, stripes=%u, allocated=%zu bytes" ),
1578 aEdges, aHoles, aStripes, aBytes );
1579 }
1580
1586 size_t m_budget;
1587 bool m_valid = false;
1588 std::vector<uint32_t> m_offsets;
1589 std::vector<uint32_t> m_bucketIds;
1590 std::vector<uint32_t> m_longIds;
1591 std::vector<uint32_t> m_heads;
1592 std::vector<NODE> m_nodes;
1593};
1594
1595
1596// Where a horizontal ray at aY crosses aEdge, valid only when aEdge.matches( aY )
1597static inline int fractureIntersectX( const FractureEdge& aEdge, int aY )
1598{
1599 if( aEdge.m_p1.y == aEdge.m_p2.y )
1600 return std::max( aEdge.m_p1.x, aEdge.m_p2.x );
1601
1602 return aEdge.m_p1.x
1603 + rescale( aEdge.m_p2.x - aEdge.m_p1.x, aY - aEdge.m_p1.y, aEdge.m_p2.y - aEdge.m_p1.y );
1604}
1605
1606
1608 FractureEdge::Index edgeIndex, FractureEdge::Index bridgeIndex,
1609 FRACTURE_EDGE_INDEX* aIndex )
1610{
1611 FractureEdge& edge = edges[edgeIndex];
1612 int x = edge.m_p1.x;
1613 int y = edge.m_p1.y;
1614 int min_dist = std::numeric_limits<int>::max();
1615 int x_nearest = 0;
1616
1617 FractureEdge* e_nearest = nullptr;
1618
1619 // Since this function is run for all holes left to right, no need to
1620 // check for any edge beyond the provoking one because they will always be
1621 // further to the right, and unconnected to the outline anyway.
1622 if( aIndex )
1623 {
1624 FractureEdge::Index nearest_index = -1;
1625
1626 auto consider = [&]( FractureEdge::Index aCandidateIndex )
1627 {
1628 FractureEdge& candidate = edges[aCandidateIndex];
1629
1630 if( !candidate.matches( y ) )
1631 return;
1632
1633 int x_intersect = fractureIntersectX( candidate, y );
1634 int dist = x - x_intersect;
1635
1636 if( dist >= 0
1637 && ( dist < min_dist
1638 || ( nearest_index >= 0 && dist == min_dist && aCandidateIndex < nearest_index ) ) )
1639 {
1640 min_dist = dist;
1641 x_nearest = x_intersect;
1642 nearest_index = aCandidateIndex;
1643 }
1644 };
1645
1646 aIndex->Query( y, provokingIndex, consider );
1647
1648 if( nearest_index >= 0 )
1649 e_nearest = &edges[nearest_index];
1650 }
1651 else
1652 {
1653 for( FractureEdge::Index i = 0; i < provokingIndex; ++i )
1654 {
1655 FractureEdge& candidate = edges[i];
1656
1657 if( !candidate.matches( y ) )
1658 continue;
1659
1660 int x_intersect = fractureIntersectX( candidate, y );
1661 int dist = x - x_intersect;
1662
1663 if( dist >= 0 && dist < min_dist )
1664 {
1665 min_dist = dist;
1666 x_nearest = x_intersect;
1667 e_nearest = &candidate;
1668 }
1669 }
1670 }
1671
1672 if( e_nearest )
1673 {
1674 const FractureEdge::Index outline2hole_index = bridgeIndex;
1675 const FractureEdge::Index hole2outline_index = bridgeIndex + 1;
1676 const FractureEdge::Index split_index = bridgeIndex + 2;
1677 // Make an edge between the split outline edge and the hole...
1678 edges[outline2hole_index] = FractureEdge( VECTOR2I( x_nearest, y ), edge.m_p1, edgeIndex );
1679 // ...between the hole and the edge...
1680 edges[hole2outline_index] =
1681 FractureEdge( edge.m_p1, VECTOR2I( x_nearest, y ), split_index );
1682 // ...and between the split outline edge and the rest.
1683 edges[split_index] =
1684 FractureEdge( VECTOR2I( x_nearest, y ), e_nearest->m_p2, e_nearest->m_next );
1685
1686 // Perform the actual outline edge split
1687 // Existing stripe membership remains conservative because the selected y is inside the
1688 // edge's current interval, while newly initialized bridge edges are inserted below.
1689 e_nearest->m_p2 = VECTOR2I( x_nearest, y );
1690 e_nearest->m_next = outline2hole_index;
1691
1692 FractureEdge* last = &edge;
1693 for( ; last->m_next != edgeIndex; last = &edges[last->m_next] )
1694 ;
1695 last->m_next = hole2outline_index;
1696
1697 if( aIndex )
1698 aIndex->InsertBridges( bridgeIndex );
1699 }
1700
1701 return e_nearest;
1702}
1703
1704
1706{
1707 FractureEdgeSet edges;
1708 bool outline = true;
1709
1710 if( paths.size() == 1 )
1711 return;
1712
1713 size_t total_point_count = 0;
1714
1715 for( const SHAPE_LINE_CHAIN& path : paths )
1716 {
1717 total_point_count += path.PointCount();
1718 }
1719
1720 if( total_point_count > (size_t) std::numeric_limits<FractureEdge::Index>::max() )
1721 {
1722 wxLogWarning( wxT( "Polygon has more points than int limit" ) );
1723 return;
1724 }
1725
1726 // Reserve space in the edge set so pointers don't get invalidated during
1727 // the whole fracture process; one for each original edge, plus 3 per
1728 // path to join it to the outline.
1729 edges.reserve( total_point_count + paths.size() * 3 );
1730
1731 // Sort the paths by their lowest X bound before processing them.
1732 // This ensures the processing order for processEdge() is correct.
1733 struct PathInfo
1734 {
1735 int path_or_provoking_index;
1736 FractureEdge::Index leftmost;
1737 int x;
1738 int y_or_bridge;
1739 };
1740 std::vector<PathInfo> sorted_paths;
1741 const int paths_count = static_cast<int>( paths.size() );
1742 sorted_paths.reserve( paths_count );
1743
1744 for( int path_index = 0; path_index < paths_count; path_index++ )
1745 {
1746 const SHAPE_LINE_CHAIN& path = paths[path_index];
1747 const std::vector<VECTOR2I>& points = path.CPoints();
1748 const int point_count = static_cast<int>( points.size() );
1749 int x_min = std::numeric_limits<int>::max();
1750 int y_min = std::numeric_limits<int>::max();
1751 int leftmost = -1;
1752
1753 for( int point_index = 0; point_index < point_count; point_index++ )
1754 {
1755 const VECTOR2I& point = points[point_index];
1756 if( point.x < x_min )
1757 {
1758 x_min = point.x;
1759 leftmost = point_index;
1760 }
1761 if( point.y < y_min )
1762 y_min = point.y;
1763 }
1764
1765 sorted_paths.emplace_back( PathInfo{ path_index, leftmost, x_min, y_min } );
1766 }
1767
1768 std::sort( sorted_paths.begin() + 1, sorted_paths.end(),
1769 []( const PathInfo& a, const PathInfo& b )
1770 {
1771 if( a.x == b.x )
1772 return a.y_or_bridge < b.y_or_bridge;
1773 return a.x < b.x;
1774 } );
1775
1776 FractureEdge::Index edge_index = 0;
1777
1778 for( PathInfo& path_info : sorted_paths )
1779 {
1780 const SHAPE_LINE_CHAIN& path = paths[path_info.path_or_provoking_index];
1781 const std::vector<VECTOR2I>& points = path.CPoints();
1782 const size_t point_count = points.size();
1783
1784 // Index of the provoking (first) edge for this path
1785 const FractureEdge::Index provoking_edge = edge_index;
1786
1787 for( size_t i = 0; i < point_count - 1; i++ )
1788 {
1789 edges.emplace_back( points[i], points[i + 1], edge_index + 1 );
1790 edge_index++;
1791 }
1792
1793 // Create last edge looping back to the provoking one.
1794 edges.emplace_back( points[point_count - 1], points[0], provoking_edge );
1795 edge_index++;
1796
1797 if( !outline )
1798 {
1799 // Repurpose the path sorting data structure to schedule the leftmost edge
1800 // for merging to the outline, which will in turn merge the rest of the path.
1801 path_info.path_or_provoking_index = provoking_edge;
1802 path_info.y_or_bridge = edge_index;
1803
1804 // Reserve 3 additional edges to bridge with the outline.
1805 edge_index += 3;
1806 edges.resize( edge_index );
1807 }
1808
1809 outline = false; // first path is always the outline
1810 }
1811
1812 assert( edges.size() == total_point_count + 3 * ( paths.size() - 1 ) );
1813#ifndef NDEBUG
1814 FractureEdge::Index expected_edge = paths[0].PointCount();
1815
1816 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); ++it )
1817 {
1818 assert( it->path_or_provoking_index == expected_edge );
1819 expected_edge = it->y_or_bridge + 3;
1820 }
1821
1822 assert( expected_edge == static_cast<FractureEdge::Index>( edges.size() ) );
1823#endif
1824
1825 uint64_t estimated_visits = 0;
1826
1827 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); ++it )
1828 estimated_visits += static_cast<uint64_t>( it->path_or_provoking_index );
1829
1830 std::unique_ptr<FRACTURE_EDGE_INDEX> index;
1831
1832 if( ENABLEFRACTUREEDGEINDEX && KIGEOM::FRACTURE_INDEX::ShouldIndex( estimated_visits, paths.size() - 1 ) )
1833 {
1834 int min_y = std::numeric_limits<int>::max();
1835 int max_y = std::numeric_limits<int>::min();
1836 const uint32_t stripe_count = KIGEOM::FRACTURE_INDEX::StripeCountFor( edges.size() );
1837
1838 for( const SHAPE_LINE_CHAIN& path : paths )
1839 {
1840 for( const VECTOR2I& point : path.CPoints() )
1841 {
1842 min_y = std::min( min_y, point.y );
1843 max_y = std::max( max_y, point.y );
1844 }
1845 }
1846
1847 auto visitOriginals = [&]( auto&& aVisitor )
1848 {
1849 auto visitRange = [&]( FractureEdge::Index aBegin, FractureEdge::Index aEnd )
1850 {
1851 for( FractureEdge::Index edge = aBegin; edge < aEnd; ++edge )
1852 {
1853 const FractureEdge& original = edges[edge];
1854 const auto [first, last] = KIGEOM::FRACTURE_INDEX::StripeSpan( original.m_p1.y, original.m_p2.y,
1855 min_y, max_y, stripe_count );
1856 aVisitor( static_cast<uint32_t>( edge ), first, last );
1857 }
1858 };
1859
1860 visitRange( 0, paths[0].PointCount() );
1861
1862 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); ++it )
1863 visitRange( it->path_or_provoking_index, it->y_or_bridge );
1864 };
1865
1866 index = std::make_unique<FRACTURE_EDGE_INDEX>( edges, visitOriginals, min_y, max_y, stripe_count,
1867 paths.size() - 1 );
1868
1869 if( !index->IsValid() )
1870 index.reset();
1871 }
1872
1873 for( auto it = sorted_paths.begin() + 1; it != sorted_paths.end(); it++ )
1874 {
1875 auto edge = processHole( edges, it->path_or_provoking_index, it->path_or_provoking_index + it->leftmost,
1876 it->y_or_bridge, index.get() );
1877
1878 // If we can't handle the hole, the zone is broken (maybe)
1879 if( !edge )
1880 {
1881 wxLogWarning( wxT( "Broken polygon, dropping path" ) );
1882
1883 return;
1884 }
1885 }
1886
1887 paths.resize( 1 );
1888 SHAPE_LINE_CHAIN& newPath = paths[0];
1889
1890 newPath.Clear();
1891 newPath.SetClosed( true );
1892
1893 // Root edge is always at index 0
1894 FractureEdge* e = &edges[0];
1895
1896 for( ; e->m_next != 0; e = &edges[e->m_next] )
1897 newPath.Append( e->m_p1 );
1898
1899 newPath.Append( e->m_p1 );
1900}
1901
1902
1904{
1905 FractureEdgeSlow( int y = 0 ) : m_connected( false ), m_next( nullptr ) { m_p1.x = m_p2.y = y; }
1906
1907 FractureEdgeSlow( bool connected, const VECTOR2I& p1, const VECTOR2I& p2 ) :
1908 m_connected( connected ), m_p1( p1 ), m_p2( p2 ), m_next( nullptr )
1909 {
1910 }
1911
1912 bool matches( int y ) const
1913 {
1914 return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
1915 }
1916
1921};
1922
1923
1924typedef std::vector<FractureEdgeSlow*> FractureEdgeSetSlow;
1925
1926
1928{
1929 int x = edge->m_p1.x;
1930 int y = edge->m_p1.y;
1931 int min_dist = std::numeric_limits<int>::max();
1932 int x_nearest = 0;
1933
1934 FractureEdgeSlow* e_nearest = nullptr;
1935
1936 for( FractureEdgeSlow* e : edges )
1937 {
1938 if( !e->matches( y ) )
1939 continue;
1940
1941 int x_intersect;
1942
1943 if( e->m_p1.y == e->m_p2.y ) // horizontal edge
1944 {
1945 x_intersect = std::max( e->m_p1.x, e->m_p2.x );
1946 }
1947 else
1948 {
1949 x_intersect = e->m_p1.x
1950 + rescale( e->m_p2.x - e->m_p1.x, y - e->m_p1.y, e->m_p2.y - e->m_p1.y );
1951 }
1952
1953 int dist = ( x - x_intersect );
1954
1955 if( dist >= 0 && dist < min_dist && e->m_connected )
1956 {
1957 min_dist = dist;
1958 x_nearest = x_intersect;
1959 e_nearest = e;
1960 }
1961 }
1962
1963 if( e_nearest && e_nearest->m_connected )
1964 {
1965 int count = 0;
1966
1967 FractureEdgeSlow* lead1 =
1968 new FractureEdgeSlow( true, VECTOR2I( x_nearest, y ), VECTOR2I( x, y ) );
1969 FractureEdgeSlow* lead2 =
1970 new FractureEdgeSlow( true, VECTOR2I( x, y ), VECTOR2I( x_nearest, y ) );
1971 FractureEdgeSlow* split_2 =
1972 new FractureEdgeSlow( true, VECTOR2I( x_nearest, y ), e_nearest->m_p2 );
1973
1974 edges.push_back( split_2 );
1975 edges.push_back( lead1 );
1976 edges.push_back( lead2 );
1977
1978 FractureEdgeSlow* link = e_nearest->m_next;
1979
1980 e_nearest->m_p2 = VECTOR2I( x_nearest, y );
1981 e_nearest->m_next = lead1;
1982 lead1->m_next = edge;
1983
1984 FractureEdgeSlow* last;
1985
1986 for( last = edge; last->m_next != edge; last = last->m_next )
1987 {
1988 last->m_connected = true;
1989 count++;
1990 }
1991
1992 last->m_connected = true;
1993 last->m_next = lead2;
1994 lead2->m_next = split_2;
1995 split_2->m_next = link;
1996
1997 return count + 1;
1998 }
1999
2000 return 0;
2001}
2002
2003
2005{
2006 FractureEdgeSetSlow edges;
2007 FractureEdgeSetSlow border_edges;
2008 FractureEdgeSlow* root = nullptr;
2009
2010 bool first = true;
2011
2012 if( paths.size() == 1 )
2013 return;
2014
2015 int num_unconnected = 0;
2016
2017 for( const SHAPE_LINE_CHAIN& path : paths )
2018 {
2019 const std::vector<VECTOR2I>& points = path.CPoints();
2020 int pointCount = points.size();
2021
2022 FractureEdgeSlow *prev = nullptr, *first_edge = nullptr;
2023
2024 int x_min = std::numeric_limits<int>::max();
2025
2026 for( int i = 0; i < pointCount; i++ )
2027 {
2028 if( points[i].x < x_min )
2029 x_min = points[i].x;
2030
2031 // Do not use path.CPoint() here; open-coding it using the local variables "points"
2032 // and "pointCount" gives a non-trivial performance boost to zone fill times.
2033 FractureEdgeSlow* fe = new FractureEdgeSlow( first, points[i],
2034 points[i + 1 == pointCount ? 0 : i + 1] );
2035
2036 if( !root )
2037 root = fe;
2038
2039 if( !first_edge )
2040 first_edge = fe;
2041
2042 if( prev )
2043 prev->m_next = fe;
2044
2045 if( i == pointCount - 1 )
2046 fe->m_next = first_edge;
2047
2048 prev = fe;
2049 edges.push_back( fe );
2050
2051 if( !first )
2052 {
2053 if( fe->m_p1.x == x_min )
2054 border_edges.push_back( fe );
2055 }
2056
2057 if( !fe->m_connected )
2058 num_unconnected++;
2059 }
2060
2061 first = false; // first path is always the outline
2062 }
2063
2064 // keep connecting holes to the main outline, until there's no holes left...
2065 while( num_unconnected > 0 )
2066 {
2067 int x_min = std::numeric_limits<int>::max();
2068 auto it = border_edges.begin();
2069
2070 FractureEdgeSlow* smallestX = nullptr;
2071
2072 // find the left-most hole edge and merge with the outline
2073 for( ; it != border_edges.end(); ++it )
2074 {
2075 FractureEdgeSlow* border_edge = *it;
2076 int xt = border_edge->m_p1.x;
2077
2078 if( ( xt <= x_min ) && !border_edge->m_connected )
2079 {
2080 x_min = xt;
2081 smallestX = border_edge;
2082 }
2083 }
2084
2085 int num_processed = processEdge( edges, smallestX );
2086
2087 // If we can't handle the edge, the zone is broken (maybe)
2088 if( !num_processed )
2089 {
2090 wxLogWarning( wxT( "Broken polygon, dropping path" ) );
2091
2092 for( FractureEdgeSlow* edge : edges )
2093 delete edge;
2094
2095 return;
2096 }
2097
2098 num_unconnected -= num_processed;
2099 }
2100
2101 paths.clear();
2102 SHAPE_LINE_CHAIN newPath;
2103
2104 newPath.SetClosed( true );
2105
2107
2108 for( e = root; e->m_next != root; e = e->m_next )
2109 newPath.Append( e->m_p1 );
2110
2111 newPath.Append( e->m_p1 );
2112
2113 for( FractureEdgeSlow* edge : edges )
2114 delete edge;
2115
2116 paths.push_back( std::move( newPath ) );
2117}
2118
2119
2121{
2123 return fractureSingleCacheFriendly( paths );
2124 fractureSingleSlow( paths );
2125}
2126
2127
2128void SHAPE_POLY_SET::Fracture( bool aSimplify )
2129{
2130 if( aSimplify )
2131 Simplify(); // remove overlapping holes/degeneracy
2132
2133 for( POLYGON& paths : m_polys )
2134 fractureSingle( paths );
2135}
2136
2137
2139{
2140 assert( aPoly.size() == 1 );
2141
2142 SHAPE_LINE_CHAIN lc = aPoly[0];
2143 lc.Simplify();
2144
2145 // Rings are rebuilt from bare vertices, so arcs never survive unfracturing
2146 lc = SHAPE_LINE_CHAIN( lc.CPoints(), true );
2147
2148 std::vector<SHAPE_LINE_CHAIN> rings;
2149
2150 if( !splitAtBridges( lc, rings ) || rings.empty() )
2151 {
2152 aPoly[0] = std::move( lc );
2153 return;
2154 }
2155
2156 auto outline = std::max_element( rings.begin(), rings.end(),
2157 []( const SHAPE_LINE_CHAIN& aA, const SHAPE_LINE_CHAIN& aB )
2158 {
2159 return std::fabs( aA.Area() ) < std::fabs( aB.Area() );
2160 } );
2161
2162 std::iter_swap( rings.begin(), outline );
2163 aPoly = std::move( rings );
2164}
2165
2166
2168{
2169 // Iterate through all the polygons on the set
2170 for( const POLYGON& paths : m_polys )
2171 {
2172 // If any of them has more than one contour, it is a hole.
2173 if( paths.size() > 1 )
2174 return true;
2175 }
2176
2177 // Return false if and only if every polygon has just one outline, without holes.
2178 return false;
2179}
2180
2181
2183{
2184 for( POLYGON& path : m_polys )
2186
2187 Simplify(); // remove overlapping holes/degeneracy
2188}
2189
2190
2191bool SHAPE_POLY_SET::isExteriorWaist( const SEG& aSegA, const SEG& aSegB ) const
2192{
2193 const VECTOR2I da = aSegA.B - aSegA.A;
2194
2195 int axis = std::abs( da.x ) >= std::abs( da.y ) ? 0 : 1;
2196
2197 std::array<VECTOR2I,4> pts = { aSegA.A, aSegA.B, aSegB.A, aSegB.B };
2198
2199 std::sort( pts.begin(), pts.end(), [axis]( const VECTOR2I& p, const VECTOR2I& q )
2200 {
2201 if( axis == 0 )
2202 return p.x < q.x || ( p.x == q.x && p.y < q.y );
2203 else
2204 return p.y < q.y || ( p.y == q.y && p.x < q.x );
2205 } );
2206
2207 VECTOR2I s = pts[1];
2208 VECTOR2I e = pts[2];
2209
2210 // Check if there is polygon material on either side of the overlapping segments
2211 // Get the midpoint between s and e for testing
2212 VECTOR2I midpoint = ( s + e ) / 2;
2213
2214 // Create perpendicular offset vector to check both sides
2215 VECTOR2I segDir = e - s;
2216
2217 if( segDir.EuclideanNorm() > 25 )
2218 {
2219 VECTOR2I perp = segDir.Perpendicular().Resize( 10 );
2220
2221 // Both sides must be outside for this to be an exterior waist. Short-circuit if
2222 // either side is inside the polygon to avoid the second O(N) point-in-polygon test.
2223 if( !PointInside( midpoint + perp ) && !PointInside( midpoint - perp ) )
2224 {
2225 wxLogTrace( wxT( "collinear" ), wxT( "Found exterior waist between (%d,%d)-(%d,%d) and (%d,%d)-(%d,%d)" ),
2226 aSegA.A.x, aSegA.A.y, aSegA.B.x, aSegA.B.y,
2227 aSegB.A.x, aSegB.A.y, aSegB.B.x, aSegB.B.y );
2228 return true;
2229 }
2230 }
2231
2232 return false;
2233}
2234
2235
2237{
2238 for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
2239 {
2240 bool changed = true;
2241
2242 while( changed )
2243 {
2244 changed = false;
2245
2246 SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
2247 intptr_t count = outline.PointCount();
2248
2249 std::vector<SEG> segs;
2250 segs.reserve( count );
2251
2252 for( intptr_t i = 0; i < count; ++i )
2253 segs.emplace_back( outline.CPoint( i ), outline.CPoint( ( i + 1 ) % count ) );
2254
2255 SEGMENT_INDEX index( std::move( segs ) );
2256
2257 bool found = false;
2258 int segA = -1;
2259 int segB = -1;
2260
2261 for( intptr_t i = 0; i < count && !found; ++i )
2262 {
2263 const VECTOR2I& a = outline.CPoint( i );
2264 const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
2265 SEG seg( a, b );
2266
2267 auto visitor =
2268 [&]( int j ) -> bool
2269 {
2270 if( j == i || j == ( ( i + 1 ) % count ) || j == ( ( i + count - 1 ) % count ) )
2271 return true;
2272
2273 VECTOR2I oa = outline.CPoint( j );
2274 VECTOR2I ob = outline.CPoint( ( j + 1 ) % count );
2275 SEG other( oa, ob );
2276
2277 // Skip segments that share start/end points. This is the case for
2278 // fractured segments
2279 if( oa == a && ob == b )
2280 return true;
2281
2282 if( oa == b && ob == a )
2283 return true;
2284
2285 if( seg.ApproxCollinear( other, 10 ) && isExteriorWaist( seg, other ) )
2286 {
2287 segA = i;
2288 segB = j;
2289 found = true;
2290 return false;
2291 }
2292
2293 return true;
2294 };
2295
2296 index.VisitCandidates( seg, 0, visitor );
2297 }
2298
2299 if( !found )
2300 break;
2301
2302 int a0 = segA;
2303 int a1 = ( segA + 1 ) % outline.PointCount();
2304 int b0 = segB;
2305 int b1 = ( segB + 1 ) % outline.PointCount();
2306
2307 SHAPE_LINE_CHAIN lc1;
2308 int idx = a1;
2309 lc1.Append( outline.CPoint( idx ) );
2310
2311 while( idx != b0 )
2312 {
2313 idx = ( idx + 1 ) % outline.PointCount();
2314 lc1.Append( outline.CPoint( idx ) );
2315 }
2316
2317 lc1.SetClosed( true );
2318
2319 SHAPE_LINE_CHAIN lc2;
2320 idx = b1;
2321 lc2.Append( outline.CPoint( idx ) );
2322
2323 while( idx != a0 )
2324 {
2325 idx = ( idx + 1 ) % outline.PointCount();
2326 lc2.Append( outline.CPoint( idx ) );
2327 }
2328
2329 lc2.SetClosed( true );
2330
2331 m_polys[polyIdx][0] = std::move( lc1 );
2332
2333 POLYGON np;
2334 np.push_back( std::move( lc2 ) );
2335 m_polys.push_back( std::move( np ) );
2336
2337 changed = true;
2338 }
2339 }
2340}
2341
2342
2344{
2345 for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
2346 {
2347 bool changed = true;
2348
2349 while( changed )
2350 {
2351 changed = false;
2352
2353 SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
2354 const int count = outline.PointCount();
2355
2356 if( count < 4 )
2357 break;
2358
2359 // Prefix trapezoid terms (SHAPE_LINE_CHAIN::Area()) to sign a candidate's halves
2360 // in O(1). Built on demand; most outlines never produce a candidate.
2361 std::vector<double> prefix;
2362
2363 auto ringTerm =
2364 [&]( int aFrom, int aTo ) -> double
2365 {
2366 if( prefix.empty() )
2367 {
2368 prefix.resize( count + 1, 0.0 );
2369
2370 for( int ii = 0; ii < count; ++ii )
2371 {
2372 const VECTOR2I& a = outline.CPoint( ii );
2373 const VECTOR2I& b = outline.CPoint( ( ii + 1 ) % count );
2374
2375 prefix[ii + 1] = prefix[ii]
2376 + ( (double) a.x + b.x ) * ( (double) a.y - b.y );
2377 }
2378 }
2379
2380 double term = aFrom <= aTo ? prefix[aTo] - prefix[aFrom]
2381 : prefix[count] - prefix[aFrom] + prefix[aTo];
2382
2383 const VECTOR2I& last = outline.CPoint( aTo );
2384 const VECTOR2I& first = outline.CPoint( aFrom );
2385
2386 return term + ( (double) last.x + first.x ) * ( (double) last.y - first.y );
2387 };
2388
2389 auto ringSize =
2390 [&]( int aFrom, int aTo )
2391 {
2392 return aFrom <= aTo ? aTo - aFrom + 1 : count - aFrom + aTo + 1;
2393 };
2394
2395 // Both halves of a real self-touch keep the parent winding. An inverted half is a
2396 // Fracture() corridor; splitting there returns the hole ring as an outline.
2397 auto splittable =
2398 [&]( int aVertIdx, int aSegIdx )
2399 {
2400 const int start = ( aSegIdx + 1 ) % count;
2401
2402 if( ringSize( start, aVertIdx ) < 3 || ringSize( aVertIdx, aSegIdx ) < 3 )
2403 return false;
2404
2405 const bool sign = ringTerm( 0, count - 1 ) > 0;
2406 const double term1 = ringTerm( start, aVertIdx );
2407 const double term2 = ringTerm( aVertIdx, aSegIdx );
2408
2409 return term1 != 0.0 && term2 != 0.0 && ( term1 > 0 ) == sign
2410 && ( term2 > 0 ) == sign;
2411 };
2412
2413 int insertSegIdx = -1;
2414 int insertVertIdx = -1;
2415
2416 // For small polygons, direct O(n²) search is faster than R-tree overhead
2417 constexpr int RTREE_THRESHOLD = 32;
2418
2419 if( count < RTREE_THRESHOLD )
2420 {
2421 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2422 {
2423 const VECTOR2I& pt = outline.CPoint( vertIdx );
2424 const int prevSeg = ( vertIdx + count - 1 ) % count;
2425
2426 for( int segIdx = 0; segIdx < count; ++segIdx )
2427 {
2428 // Skip adjacent segments
2429 if( segIdx == prevSeg || segIdx == vertIdx )
2430 continue;
2431
2432 const VECTOR2I& a = outline.CPoint( segIdx );
2433 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2434
2435 // SquaredDistance returns 0 only when pt lies exactly on the
2436 // segment. Clipper2 rounds corridor-cut vertices to integer
2437 // coordinates; they can land within 1nm of an endpoint but are
2438 // not true pinch points.
2439 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0
2440 && splittable( vertIdx, segIdx ) )
2441 {
2442 insertSegIdx = segIdx;
2443 insertVertIdx = vertIdx;
2444 break;
2445 }
2446 }
2447 }
2448 }
2449 else
2450 {
2451 std::vector<SEG> segs;
2452 segs.reserve( count );
2453
2454 for( int i = 0; i < count; ++i )
2455 segs.emplace_back( outline.CPoint( i ), outline.CPoint( ( i + 1 ) % count ) );
2456
2457 SEGMENT_INDEX index( std::move( segs ) );
2458
2459 for( int vertIdx = 0; vertIdx < count && insertSegIdx < 0; ++vertIdx )
2460 {
2461 const VECTOR2I& pt = outline.CPoint( vertIdx );
2462 const int prevSeg = ( vertIdx + count - 1 ) % count;
2463
2464 auto pinchVisitor =
2465 [&]( int segIdx ) -> bool
2466 {
2467 if( segIdx == prevSeg || segIdx == vertIdx )
2468 return true;
2469
2470 const VECTOR2I& a = outline.CPoint( segIdx );
2471 const VECTOR2I& b = outline.CPoint( ( segIdx + 1 ) % count );
2472
2473 // SquaredDistance returns 0 only when pt lies exactly on the
2474 // segment. Clipper2 rounds corridor-cut vertices to integer
2475 // coordinates; they can land within 1nm of an endpoint but
2476 // are not true pinch points.
2477 if( pt != a && pt != b && SEG( a, b ).SquaredDistance( pt ) == 0
2478 && splittable( vertIdx, segIdx ) )
2479 {
2480 insertSegIdx = segIdx;
2481 insertVertIdx = vertIdx;
2482 return false;
2483 }
2484
2485 return true;
2486 };
2487
2488 index.VisitCandidates( SEG( pt, pt ), 0, pinchVisitor );
2489 }
2490 }
2491
2492 if( insertSegIdx < 0 )
2493 break;
2494
2495 // Split the polygon at the pinch point into two separate polygons.
2496 // Polygon 1: vertices from (insertSegIdx+1) to insertVertIdx
2497 // Polygon 2: vertices from insertVertIdx to insertSegIdx
2498
2499 const int splitStart1 = ( insertSegIdx + 1 ) % count;
2500 const int size1 = ringSize( splitStart1, insertVertIdx );
2501 const int size2 = ringSize( insertVertIdx, insertSegIdx );
2502
2503 SHAPE_LINE_CHAIN poly1;
2504 SHAPE_LINE_CHAIN poly2;
2505 poly1.ReservePoints( size1 );
2506 poly2.ReservePoints( size2 );
2507
2508 int idx = splitStart1;
2509
2510 for( int i = 0; i < size1; ++i )
2511 {
2512 poly1.Append( outline.CPoint( idx ) );
2513 idx = ( idx + 1 ) % count;
2514 }
2515
2516 poly1.SetClosed( true );
2517
2518 idx = insertVertIdx;
2519
2520 for( int i = 0; i < size2; ++i )
2521 {
2522 poly2.Append( outline.CPoint( idx ) );
2523 idx = ( idx + 1 ) % count;
2524 }
2525
2526 poly2.SetClosed( true );
2527
2528 m_polys[polyIdx][0] = std::move( poly1 );
2529
2530 POLYGON np;
2531 np.push_back( std::move( poly2 ) );
2532 m_polys.push_back( std::move( np ) );
2533
2534 changed = true;
2535 }
2536 }
2537}
2538
2539
2541{
2543
2545
2546 booleanOp( Clipper2Lib::ClipType::Union, empty );
2547}
2548
2549
2551{
2552 for( POLYGON& paths : m_polys )
2553 {
2554 for( SHAPE_LINE_CHAIN& path : paths )
2555 {
2556 path.Simplify( aTolerance );
2557 }
2558 }
2559}
2560
2561
2563{
2564 // We are expecting only one main outline, but this main outline can have holes
2565 // if holes: combine holes and remove them from the main outline.
2566 // Note also we are usingin polygon
2567 // calculations, but it is not mandatory. It is used mainly
2568 // because there is usually only very few vertices in area outlines
2569 SHAPE_POLY_SET::POLYGON& outline = Polygon( 0 );
2570 SHAPE_POLY_SET holesBuffer;
2571
2572 // Move holes stored in outline to holesBuffer:
2573 // The first SHAPE_LINE_CHAIN is the main outline, others are holes
2574 while( outline.size() > 1 )
2575 {
2576 holesBuffer.AddOutline( outline.back() );
2577 outline.pop_back();
2578 }
2579
2580 Simplify();
2581
2582 // If any hole, subtract it to main outline
2583 if( holesBuffer.OutlineCount() )
2584 {
2585 holesBuffer.Simplify();
2586 BooleanSubtract( holesBuffer );
2587 }
2588
2589 // In degenerate cases, simplify might return no outlines
2590 if( OutlineCount() > 0 )
2592
2593 return OutlineCount();
2594}
2595
2596
2597const std::string SHAPE_POLY_SET::Format( bool aCplusPlus ) const
2598{
2599 std::stringstream ss;
2600
2601 ss << "SHAPE_LINE_CHAIN poly; \n";
2602
2603 for( unsigned i = 0; i < m_polys.size(); i++ )
2604 {
2605 for( unsigned j = 0; j < m_polys[i].size(); j++ )
2606 {
2607
2608 ss << "{ auto tmp = " << m_polys[i][j].Format() << ";\n";
2609
2610 SHAPE_POLY_SET poly;
2611
2612 if( j == 0 )
2613 {
2614 ss << " poly.AddOutline(tmp); } \n";
2615 }
2616 else
2617 {
2618 ss << " poly.AddHole(tmp); } \n";
2619 }
2620
2621 }
2622 }
2623
2624 return ss.str();
2625}
2626
2627
2628bool SHAPE_POLY_SET::Parse( std::stringstream& aStream )
2629{
2630 std::string tmp;
2631
2632 aStream >> tmp;
2633
2634 if( tmp != "polyset" )
2635 return false;
2636
2637 aStream >> tmp;
2638
2639 int n_polys = atoi( tmp.c_str() );
2640
2641 if( n_polys < 0 )
2642 return false;
2643
2644 for( int i = 0; i < n_polys; i++ )
2645 {
2646 POLYGON paths;
2647
2648 aStream >> tmp;
2649
2650 if( tmp != "poly" )
2651 return false;
2652
2653 aStream >> tmp;
2654 int n_outlines = atoi( tmp.c_str() );
2655
2656 if( n_outlines < 0 )
2657 return false;
2658
2659 for( int j = 0; j < n_outlines; j++ )
2660 {
2661 SHAPE_LINE_CHAIN outline;
2662
2663 outline.SetClosed( true );
2664
2665 aStream >> tmp;
2666 int n_vertices = atoi( tmp.c_str() );
2667
2668 for( int v = 0; v < n_vertices; v++ )
2669 {
2670 VECTOR2I p;
2671
2672 aStream >> tmp; p.x = atoi( tmp.c_str() );
2673 aStream >> tmp; p.y = atoi( tmp.c_str() );
2674 outline.Append( p );
2675 }
2676
2677 paths.push_back( std::move( outline ) );
2678 }
2679
2680 m_polys.push_back( std::move( paths ) );
2681 }
2682
2683 return true;
2684}
2685
2686
2687const BOX2I SHAPE_POLY_SET::BBox( int aClearance ) const
2688{
2689 BOX2I bb;
2690
2691 for( unsigned i = 0; i < m_polys.size(); i++ )
2692 {
2693 if( i == 0 )
2694 bb = m_polys[i][0].BBox();
2695 else
2696 bb.Merge( m_polys[i][0].BBox() );
2697 }
2698
2699 bb.Inflate( aClearance );
2700 return bb;
2701}
2702
2703
2705{
2706 BOX2I bb;
2707
2708 for( unsigned i = 0; i < m_polys.size(); i++ )
2709 {
2710 if( i == 0 )
2711 bb = *m_polys[i][0].GetCachedBBox();
2712 else
2713 bb.Merge( *m_polys[i][0].GetCachedBBox() );
2714 }
2715
2716 return bb;
2717}
2718
2719
2720bool SHAPE_POLY_SET::PointOnEdge( const VECTOR2I& aP, int aAccuracy ) const
2721{
2722 // Iterate through all the polygons in the set
2723 for( const POLYGON& polygon : m_polys )
2724 {
2725 // Iterate through all the line chains in the polygon
2726 for( const SHAPE_LINE_CHAIN& lineChain : polygon )
2727 {
2728 if( lineChain.PointOnEdge( aP, aAccuracy ) )
2729 return true;
2730 }
2731 }
2732
2733 return false;
2734}
2735
2736
2737bool SHAPE_POLY_SET::Collide( const SEG& aSeg, int aClearance, int* aActual,
2738 VECTOR2I* aLocation ) const
2739{
2740 VECTOR2I nearest;
2741 ecoord dist_sq = SquaredDistanceToSeg( aSeg, aLocation ? &nearest : nullptr );
2742
2743 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2744 {
2745 if( aLocation )
2746 *aLocation = nearest;
2747
2748 if( aActual )
2749 *aActual = sqrt( dist_sq );
2750
2751 return true;
2752 }
2753
2754 return false;
2755}
2756
2757
2758bool SHAPE_POLY_SET::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
2759 VECTOR2I* aLocation ) const
2760{
2761 if( IsEmpty() || VertexCount() == 0 )
2762 return false;
2763
2764 VECTOR2I nearest;
2765 ecoord dist_sq = SquaredDistance( aP, false, aLocation ? &nearest : nullptr );
2766
2767 if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
2768 {
2769 if( aLocation )
2770 *aLocation = nearest;
2771
2772 if( aActual )
2773 *aActual = sqrt( dist_sq );
2774
2775 return true;
2776 }
2777
2778 return false;
2779}
2780
2781
2782bool SHAPE_POLY_SET::Collide( const SHAPE* aShape, int aClearance, int* aActual,
2783 VECTOR2I* aLocation ) const
2784{
2785 // A couple of simple cases are worth trying before we fall back on triangulation.
2786
2787 if( aShape->Type() == SH_SEGMENT )
2788 {
2789 const SHAPE_SEGMENT* segment = static_cast<const SHAPE_SEGMENT*>( aShape );
2790 int extra = segment->GetWidth() / 2;
2791
2792 if( Collide( segment->GetSeg(), aClearance + extra, aActual, aLocation ) )
2793 {
2794 if( aActual )
2795 *aActual = std::max( 0, *aActual - extra );
2796
2797 return true;
2798 }
2799
2800 return false;
2801 }
2802
2803 if( aShape->Type() == SH_CIRCLE )
2804 {
2805 const SHAPE_CIRCLE* circle = static_cast<const SHAPE_CIRCLE*>( aShape );
2806 int extra = circle->GetRadius();
2807
2808 if( Collide( circle->GetCenter(), aClearance + extra, aActual, aLocation ) )
2809 {
2810 if( aActual )
2811 *aActual = std::max( 0, *aActual - extra );
2812
2813 return true;
2814 }
2815
2816 return false;
2817 }
2818
2819 const_cast<SHAPE_POLY_SET*>( this )->CacheTriangulation();
2820
2821 int actual = INT_MAX;
2823
2824 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
2825 {
2826 for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
2827 {
2828 if( aActual || aLocation )
2829 {
2830 int triActual;
2831 VECTOR2I triLocation;
2832
2833 if( aShape->Collide( &tri, aClearance, &triActual, &triLocation ) )
2834 {
2835 if( triActual < actual )
2836 {
2837 actual = triActual;
2838 location = triLocation;
2839 }
2840 }
2841 }
2842 else // A much faster version of above
2843 {
2844 if( aShape->Collide( &tri, aClearance ) )
2845 return true;
2846 }
2847 }
2848 }
2849
2850 if( actual < INT_MAX )
2851 {
2852 if( aActual )
2853 *aActual = std::max( 0, actual );
2854
2855 if( aLocation )
2856 *aLocation = location;
2857
2858 return true;
2859 }
2860
2861 return false;
2862}
2863
2864
2866{
2867 m_polys.clear();
2868 m_triangulatedPolys.clear();
2869 m_triangulationValid = false;
2870}
2871
2872
2873void SHAPE_POLY_SET::RemoveContour( int aContourIdx, int aPolygonIdx )
2874{
2875 // Default polygon is the last one
2876 if( aPolygonIdx < 0 )
2877 aPolygonIdx += m_polys.size();
2878
2879 m_polys[aPolygonIdx].erase( m_polys[aPolygonIdx].begin() + aContourIdx );
2880}
2881
2882
2883void SHAPE_POLY_SET::RemoveOutline( int aOutlineIdx )
2884{
2885 m_polys.erase( m_polys.begin() + aOutlineIdx );
2886}
2887
2888
2890{
2891 int removed = 0;
2892
2893 ITERATOR iterator = IterateWithHoles();
2894
2895 VECTOR2I contourStart = *iterator;
2896 VECTOR2I segmentStart, segmentEnd;
2897
2898 VERTEX_INDEX indexStart;
2899 std::vector<VERTEX_INDEX> indices_to_remove;
2900
2901 while( iterator )
2902 {
2903 // Obtain first point and its index
2904 segmentStart = *iterator;
2905 indexStart = iterator.GetIndex();
2906
2907 // Obtain last point
2908 if( iterator.IsEndContour() )
2909 {
2910 segmentEnd = contourStart;
2911
2912 // Advance
2913 iterator++;
2914
2915 // If we have rolled into the next contour, remember its position
2916 // segmentStart and segmentEnd remain valid for comparison here
2917 if( iterator )
2918 contourStart = *iterator;
2919 }
2920 else
2921 {
2922 // Advance
2923 iterator++;
2924
2925 // If we have reached the end of the SHAPE_POLY_SET, something is broken here
2926 wxCHECK_MSG( iterator, removed, wxT( "Invalid polygon. Reached end without noticing. Please report this error" ) );
2927
2928 segmentEnd = *iterator;
2929 }
2930
2931 // Remove segment start if both points are equal
2932 if( segmentStart == segmentEnd )
2933 {
2934 indices_to_remove.push_back( indexStart );
2935 removed++;
2936 }
2937 }
2938
2939 // Proceed in reverse direction to remove the vertices because they are stored as absolute indices in a vector
2940 // Removing in reverse order preserves the remaining index values
2941 for( auto it = indices_to_remove.rbegin(); it != indices_to_remove.rend(); ++it )
2942 RemoveVertex( *it );
2943
2944 return removed;
2945}
2946
2947
2949{
2950 m_polys.erase( m_polys.begin() + aIdx );
2951}
2952
2953
2955{
2956 m_polys.erase( m_polys.begin() + aIdx );
2957
2959 {
2960 for( int ii = m_triangulatedPolys.size() - 1; ii >= 0; --ii )
2961 {
2962 std::unique_ptr<TRIANGULATED_POLYGON>& triangleSet = m_triangulatedPolys[ii];
2963
2964 if( triangleSet->GetSourceOutlineIndex() == aIdx )
2965 m_triangulatedPolys.erase( m_triangulatedPolys.begin() + ii );
2966 else if( triangleSet->GetSourceOutlineIndex() > aIdx )
2967 triangleSet->SetSourceOutlineIndex( triangleSet->GetSourceOutlineIndex() - 1 );
2968 }
2969
2970 if( aUpdateHash )
2971 {
2972 m_hash = checksum();
2973 m_hashValid = true;
2974 }
2975 }
2976}
2977
2978
2984
2985
2987{
2988 m_polys.insert( m_polys.end(), aSet.m_polys.begin(), aSet.m_polys.end() );
2989}
2990
2991
2992void SHAPE_POLY_SET::Append( const VECTOR2I& aP, int aOutline, int aHole )
2993{
2994 Append( aP.x, aP.y, aOutline, aHole );
2995}
2996
2997
2999 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
3000 int aClearance ) const
3001{
3002 // Shows whether there was a collision
3003 bool collision = false;
3004
3005 // Difference vector between each vertex and aPoint.
3007 ecoord distance_squared;
3008 ecoord clearance_squared = SEG::Square( aClearance );
3009
3010 for( CONST_ITERATOR iterator = CIterateWithHoles(); iterator; iterator++ )
3011 {
3012 // Get the difference vector between current vertex and aPoint
3013 delta = *iterator - aPoint;
3014
3015 // Compute distance
3016 distance_squared = delta.SquaredEuclideanNorm();
3017
3018 // Check for collisions
3019 if( distance_squared <= clearance_squared )
3020 {
3021 if( !aClosestVertex )
3022 return true;
3023
3024 collision = true;
3025
3026 // Update clearance to look for closer vertices
3027 clearance_squared = distance_squared;
3028
3029 // Store the indices that identify the vertex
3030 *aClosestVertex = iterator.GetIndex();
3031 }
3032 }
3033
3034 return collision;
3035}
3036
3037
3039 SHAPE_POLY_SET::VERTEX_INDEX* aClosestVertex,
3040 int aClearance ) const
3041{
3042 // Shows whether there was a collision
3043 bool collision = false;
3044 ecoord clearance_squared = SEG::Square( aClearance );
3045
3046 for( CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
3047 {
3048 const SEG currentSegment = *iterator;
3049 ecoord distance_squared = currentSegment.SquaredDistance( aPoint );
3050
3051 // Check for collisions
3052 if( distance_squared <= clearance_squared )
3053 {
3054 if( !aClosestVertex )
3055 return true;
3056
3057 collision = true;
3058
3059 // Update clearance to look for closer edges
3060 clearance_squared = distance_squared;
3061
3062 // Store the indices that identify the vertex
3063 *aClosestVertex = iterator.GetIndex();
3064 }
3065 }
3066
3067 return collision;
3068}
3069
3070
3072{
3073 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
3074 {
3075 COutline( polygonIdx ).GenerateBBoxCache();
3076
3077 for( int holeIdx = 0; holeIdx < HoleCount( polygonIdx ); holeIdx++ )
3078 CHole( polygonIdx, holeIdx ).GenerateBBoxCache();
3079 }
3080}
3081
3082
3083bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
3084 bool aUseBBoxCaches ) const
3085{
3086 if( m_polys.empty() )
3087 return false;
3088
3089 // If there is a polygon specified, check the condition against that polygon
3090 if( aSubpolyIndex >= 0 )
3091 return containsSingle( aP, aSubpolyIndex, aAccuracy, aUseBBoxCaches );
3092
3093 // In any other case, check it against all polygons in the set
3094 for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
3095 {
3096 if( containsSingle( aP, polygonIdx, aAccuracy, aUseBBoxCaches ) )
3097 return true;
3098 }
3099
3100 return false;
3101}
3102
3103
3104void SHAPE_POLY_SET::RemoveVertex( int aGlobalIndex )
3105{
3107
3108 // Assure the to be removed vertex exists, abort otherwise
3109 if( GetRelativeIndices( aGlobalIndex, &index ) )
3111 else
3112 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
3113}
3114
3115
3117{
3118 m_polys[aIndex.m_polygon][aIndex.m_contour].Remove( aIndex.m_vertex );
3119}
3120
3121
3122void SHAPE_POLY_SET::SetVertex( int aGlobalIndex, const VECTOR2I& aPos )
3123{
3125
3126 if( GetRelativeIndices( aGlobalIndex, &index ) )
3127 SetVertex( index, aPos );
3128 else
3129 throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
3130}
3131
3132
3133void SHAPE_POLY_SET::SetVertex( const VERTEX_INDEX& aIndex, const VECTOR2I& aPos )
3134{
3135 m_polys[aIndex.m_polygon][aIndex.m_contour].SetPoint( aIndex.m_vertex, aPos );
3136}
3137
3138
3139bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
3140 bool aUseBBoxCaches ) const
3141{
3142 // Check that the point is inside the outline
3143 if( m_polys[aSubpolyIndex][0].PointInside( aP, aAccuracy ) )
3144 {
3145 // Check that the point is not in any of the holes
3146 for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
3147 {
3148 const SHAPE_LINE_CHAIN& hole = CHole( aSubpolyIndex, holeIdx );
3149
3150 // If the point is inside a hole it is outside of the polygon. Do not use aAccuracy
3151 // here as it's meaning would be inverted.
3152 if( hole.PointInside( aP, 1, aUseBBoxCaches ) )
3153 return false;
3154 }
3155
3156 return true;
3157 }
3158
3159 return false;
3160}
3161
3162
3163void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
3164{
3165 for( POLYGON& poly : m_polys )
3166 {
3167 for( SHAPE_LINE_CHAIN& path : poly )
3168 path.Move( aVector );
3169 }
3170
3171 for( std::unique_ptr<TRIANGULATED_POLYGON>& tri : m_triangulatedPolys )
3172 tri->Move( aVector );
3173
3174 m_hash = checksum();
3175 m_hashValid = true;
3176}
3177
3178
3179void SHAPE_POLY_SET::Mirror( const VECTOR2I& aRef, FLIP_DIRECTION aFlipDirection )
3180{
3181 for( POLYGON& poly : m_polys )
3182 {
3183 for( SHAPE_LINE_CHAIN& path : poly )
3184 path.Mirror( aRef, aFlipDirection );
3185 }
3186
3189}
3190
3191
3192void SHAPE_POLY_SET::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
3193{
3194 for( POLYGON& poly : m_polys )
3195 {
3196 for( SHAPE_LINE_CHAIN& path : poly )
3197 path.Rotate( aAngle, aCenter );
3198 }
3199
3200 // Don't re-cache if the triangulation is already invalid
3203}
3204
3205
3207{
3208 int c = 0;
3209
3210 for( const POLYGON& poly : m_polys )
3211 {
3212 for( const SHAPE_LINE_CHAIN& path : poly )
3213 c += path.PointCount();
3214 }
3215
3216 return c;
3217}
3218
3219
3220SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex )
3221{
3222 return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0 );
3223}
3224
3225
3226SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax,
3227 int aIndex )
3228{
3229 return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax );
3230}
3231
3232
3234 VECTOR2I* aNearest ) const
3235{
3236 // We calculate the min dist between the segment and each outline segment. However, if the
3237 // segment to test is inside the outline, and does not cross any edge, it can be seen outside
3238 // the polygon. Therefore test if a segment end is inside (testing only one end is enough).
3239 // Use an accuracy of "1" to say that we don't care if it's exactly on the edge or not.
3240 if( containsSingle( aPoint, aPolygonIndex, 1 ) )
3241 {
3242 if( aNearest )
3243 *aNearest = aPoint;
3244
3245 return 0;
3246 }
3247
3248 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
3249
3250 SEG::ecoord minDistance = (*iterator).SquaredDistance( aPoint );
3251
3252 if( aNearest )
3253 *aNearest = ( *iterator ).NearestPoint( aPoint );
3254
3255 for( iterator++; iterator && minDistance > 0; iterator++ )
3256 {
3257 SEG::ecoord currentDistance = (*iterator).SquaredDistance( aPoint );
3258
3259 if( currentDistance < minDistance )
3260 {
3261 if( aNearest )
3262 *aNearest = (*iterator).NearestPoint( aPoint );
3263
3264 minDistance = currentDistance;
3265 }
3266 }
3267
3268 return minDistance;
3269}
3270
3271
3273 VECTOR2I* aNearest ) const
3274{
3275 // Check if the segment is fully-contained. If so, its midpoint is a good-enough nearest point.
3276 if( containsSingle( aSegment.A, aPolygonIndex, 1 ) &&
3277 containsSingle( aSegment.B, aPolygonIndex, 1 ) )
3278 {
3279 if( aNearest )
3280 *aNearest = ( aSegment.A + aSegment.B ) / 2;
3281
3282 return 0;
3283 }
3284
3285 CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
3286 SEG::ecoord minDistance = (*iterator).SquaredDistance( aSegment );
3287
3288 if( aNearest )
3289 *aNearest = ( *iterator ).NearestPoint( aSegment );
3290
3291 for( iterator++; iterator && minDistance > 0; iterator++ )
3292 {
3293 SEG::ecoord currentDistance = (*iterator).SquaredDistance( aSegment );
3294
3295 if( currentDistance < minDistance )
3296 {
3297 if( aNearest )
3298 *aNearest = (*iterator).NearestPoint( aSegment );
3299
3300 minDistance = currentDistance;
3301 }
3302 }
3303
3304 // Return the maximum of minDistance and zero
3305 return minDistance < 0 ? 0 : minDistance;
3306}
3307
3308
3309SEG::ecoord SHAPE_POLY_SET::SquaredDistance( const VECTOR2I& aPoint, bool aOutlineOnly,
3310 VECTOR2I* aNearest ) const
3311{
3312 wxASSERT_MSG( !aOutlineOnly, wxT( "Warning: SHAPE_POLY_SET::SquaredDistance does not yet "
3313 "support aOutlineOnly==true" ) );
3314
3315 SEG::ecoord currentDistance_sq;
3316 SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
3317 VECTOR2I nearest;
3318
3319 // Iterate through all the polygons and get the minimum distance.
3320 for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
3321 {
3322 currentDistance_sq = SquaredDistanceToPolygon( aPoint, polygonIdx,
3323 aNearest ? &nearest : nullptr );
3324
3325 if( currentDistance_sq < minDistance_sq )
3326 {
3327 if( aNearest )
3328 *aNearest = nearest;
3329
3330 minDistance_sq = currentDistance_sq;
3331 }
3332 }
3333
3334 return minDistance_sq;
3335}
3336
3337
3339{
3340 SEG::ecoord currentDistance_sq;
3341 SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
3342 VECTOR2I nearest;
3343
3344 // Iterate through all the polygons and get the minimum distance.
3345 for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
3346 {
3347 currentDistance_sq = SquaredDistanceToPolygon( aSegment, polygonIdx,
3348 aNearest ? &nearest : nullptr );
3349
3350 if( currentDistance_sq < minDistance_sq )
3351 {
3352 if( aNearest )
3353 *aNearest = nearest;
3354
3355 minDistance_sq = currentDistance_sq;
3356 }
3357 }
3358
3359 return minDistance_sq;
3360}
3361
3362
3364{
3366
3367 // Get the polygon and contour where the vertex is. If the vertex does not exist, return false
3368 if( !GetRelativeIndices( aGlobalIdx, &index ) )
3369 return false;
3370
3371 // The contour is a hole if its index is greater than zero
3372 return index.m_contour > 0;
3373}
3374
3375
3377{
3378 SHAPE_POLY_SET chamfered;
3379
3380 for( unsigned int idx = 0; idx < m_polys.size(); idx++ )
3381 chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx ) );
3382
3383 return chamfered;
3384}
3385
3386
3387SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax )
3388{
3389 SHAPE_POLY_SET filleted;
3390
3391 for( size_t idx = 0; idx < m_polys.size(); idx++ )
3392 filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx ) );
3393
3394 return filleted;
3395}
3396
3397
3399{
3400 SHAPE::operator=( aOther );
3401 m_polys = aOther.m_polys;
3402
3403 m_triangulatedPolys.clear();
3404
3405 if( aOther.IsTriangulationUpToDate() )
3406 {
3407 m_triangulatedPolys.reserve( aOther.TriangulatedPolyCount() );
3408
3409 for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
3410 {
3411 const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
3412 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
3413 }
3414
3415 m_hash = aOther.m_hash;
3416 m_hashValid.store( aOther.m_hashValid.load() );
3418 }
3419 else
3420 {
3421 m_hash.Clear();
3422 m_hashValid = false;
3423 m_triangulationValid = false;
3424 }
3425
3426 m_failedHash = aOther.m_failedHash;
3427 m_failedHashValid.store( aOther.m_failedHashValid.load() );
3428
3429 return *this;
3430}
3431
3432
3434{
3435 if( !m_hashValid )
3436 return checksum();
3437
3438 return m_hash;
3439}
3440
3441
3443{
3445 return false;
3446
3447 if( !m_hashValid )
3448 return false;
3449
3450 HASH_128 hash = checksum();
3451
3452 return hash == m_hash;
3453}
3454
3455
3457 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* aHintData,
3458 const TASK_SUBMITTER& aSubmitter )
3459{
3460 // if( m_triangulationValid && m_hashValid && m_hash == checksum() )
3461 // return;
3462 // if( m_failedHashValid && m_failedHash == checksum() )
3463 // return;
3464
3465 std::unique_lock<std::mutex> lock( m_triangulationMutex );
3466
3468 return;
3470 return;
3471
3472 // Invalidate, in case anything goes wrong below
3473 m_triangulationValid = false;
3474 m_hashValid = false;
3475 m_failedHashValid = false;
3476
3477 auto triangulate =
3478 []( SHAPE_POLY_SET& polySet, int forOutline,
3479 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>& dest,
3480 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* hintData,
3481 const TASK_SUBMITTER& taskSubmitter )
3482 {
3483 bool triangulationValid = false;
3484 int pass = 0;
3485 int index = 0;
3486
3487 if( hintData && hintData->size() != (unsigned) polySet.OutlineCount() )
3488 hintData = nullptr;
3489
3490 while( polySet.OutlineCount() > 0 )
3491 {
3492 if( !dest.empty() && dest.back()->GetTriangleCount() == 0 )
3493 dest.erase( dest.end() - 1 );
3494
3495 {
3496 const SHAPE_LINE_CHAIN& outline = polySet.Polygon( 0 ).front();
3497 TRIANGULATED_POLYGON partitionScratch( forOutline );
3498 POLYGON_TRIANGULATION partitioner( partitionScratch );
3499 size_t partitionLeaves = partitioner.suggestedPartitionLeafCount( outline );
3500
3501 if( partitionLeaves > 1 )
3502 {
3503 std::vector<SHAPE_LINE_CHAIN> partitions =
3504 partitioner.partitionPolygonBalanced( outline, partitionLeaves );
3505
3506 if( partitions.size() > 1 )
3507 {
3508 polySet.DeletePolygon( 0 );
3509
3510 if( taskSubmitter && partitions.size() > 2 )
3511 {
3512 size_t leafCount = partitions.size();
3513
3514 struct WorkStealState
3515 {
3516 std::unique_ptr<std::atomic<bool>[]> claimed;
3517 std::unique_ptr<std::atomic<bool>[]> done;
3518 std::unique_ptr<std::atomic<bool>[]> ok;
3519
3520 explicit WorkStealState( size_t n ) :
3521 claimed( new std::atomic<bool>[n] ),
3522 done( new std::atomic<bool>[n] ),
3523 ok( new std::atomic<bool>[n] )
3524 {
3525 for( size_t i = 0; i < n; ++i )
3526 {
3527 claimed[i].store( false );
3528 done[i].store( false );
3529 ok[i].store( false );
3530 }
3531 }
3532 };
3533
3534 auto state = std::make_shared<WorkStealState>( leafCount );
3535
3536 std::vector<std::unique_ptr<TRIANGULATED_POLYGON>> results( leafCount );
3537
3538 for( size_t i = 0; i < leafCount; ++i )
3539 {
3540 results[i] = std::make_unique<TRIANGULATED_POLYGON>( forOutline );
3541 }
3542
3543 for( size_t i = 0; i < leafCount; ++i )
3544 {
3545 auto* triPoly = results[i].get();
3546 auto* leaf = &partitions[i];
3547
3548 taskSubmitter( [state, i, triPoly, leaf]()
3549 {
3550 if( state->claimed[i].exchange( true ) )
3551 return;
3552
3553 POLYGON_TRIANGULATION tess( *triPoly );
3554 state->ok[i].store( tess.TesselatePolygon( *leaf, nullptr ) );
3555 state->done[i].store( true, std::memory_order_release );
3556 } );
3557 }
3558
3559 for( size_t i = 0; i < leafCount; ++i )
3560 {
3561 if( state->claimed[i].exchange( true ) )
3562 continue;
3563
3564 POLYGON_TRIANGULATION tess( *results[i] );
3565 state->ok[i].store( tess.TesselatePolygon( partitions[i], nullptr ) );
3566 state->done[i].store( true, std::memory_order_release );
3567 }
3568
3569 for( size_t i = 0; i < leafCount; ++i )
3570 {
3571 while( !state->done[i].load( std::memory_order_acquire ) )
3572 {
3573 std::this_thread::yield();
3574 }
3575 }
3576
3577 bool allOk = true;
3578
3579 for( size_t i = 0; i < leafCount; ++i )
3580 allOk = allOk && state->ok[i].load();
3581
3582 if( allOk )
3583 {
3584 for( auto& r : results )
3585 {
3586 if( r->GetTriangleCount() > 0 )
3587 dest.push_back( std::move( r ) );
3588 }
3589
3590 triangulationValid = true;
3591 hintData = nullptr;
3592 continue;
3593 }
3594
3595 }
3596
3597 for( auto it = partitions.rbegin(); it != partitions.rend(); ++it )
3598 polySet.AddOutline( *it );
3599
3600 hintData = nullptr;
3601 continue;
3602 }
3603 }
3604 }
3605
3606 dest.push_back( std::make_unique<TRIANGULATED_POLYGON>( forOutline ) );
3607 POLYGON_TRIANGULATION tess( *dest.back() );
3608
3609 // If the tessellation fails, we re-fracture the polygon, which will
3610 // first simplify the system before fracturing and removing the holes
3611 // This may result in multiple, disjoint polygons.
3612 if( !tess.TesselatePolygon( polySet.Polygon( 0 ).front(),
3613 hintData ? hintData->at( index ).get() : nullptr ) )
3614 {
3615 ++pass;
3616
3617 if( pass == 1 )
3618 {
3620 }
3621 // In Clipper2, there is only one type of simplification
3622 else
3623 {
3624 break;
3625 }
3626
3627 triangulationValid = false;
3628 hintData = nullptr;
3629 continue;
3630 }
3631
3632 polySet.DeletePolygon( 0 );
3633 index++;
3634 triangulationValid = true;
3635 }
3636
3637 return triangulationValid;
3638 };
3639
3640 m_triangulatedPolys.clear();
3641
3642 const SHAPE_POLY_SET* srcSet = this;
3643 SHAPE_POLY_SET tmpSet;
3644
3645 if( ArcCount() > 0 || aSimplify )
3646 {
3647 tmpSet = SHAPE_POLY_SET( *this );
3648 tmpSet.ClearArcs();
3649
3650 if( aSimplify )
3651 tmpSet.Simplify();
3652
3653 srcSet = &tmpSet;
3654 }
3655
3656 bool directOk = true;
3657
3658 for( int ii = 0; ii < srcSet->OutlineCount() && directOk; ++ii )
3659 {
3660 const POLYGON& poly = srcSet->CPolygon( ii );
3661 size_t prevCount = m_triangulatedPolys.size();
3662
3663 if( poly.size() > 1 )
3664 {
3665 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3667
3668 if( tess.TesselatePolygon( poly, nullptr ) )
3669 continue;
3670
3671 // Hole bridging failed; fracture to merge holes into the outline
3672 m_triangulatedPolys.resize( prevCount );
3673
3674 SHAPE_POLY_SET flatSet( poly.front() );
3675
3676 for( size_t jj = 1; jj < poly.size(); ++jj )
3677 flatSet.AddHole( poly[jj] );
3678
3679 flatSet.Fracture();
3680 flatSet.splitSelfTouchingOutlines();
3681
3682 if( triangulate( flatSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3683 continue;
3684
3685 m_triangulatedPolys.resize( prevCount );
3686 directOk = false;
3687 continue;
3688 }
3689
3690 {
3691 TRIANGULATED_POLYGON partScratch( -1 );
3692 POLYGON_TRIANGULATION partChecker( partScratch );
3693
3694 if( partChecker.suggestedPartitionLeafCount( poly.front() ) > 1 )
3695 {
3696 SHAPE_POLY_SET partSet;
3697 partSet.AddOutline( poly.front() );
3698
3699 if( triangulate( partSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3700 {
3701 continue;
3702 }
3703
3704 m_triangulatedPolys.resize( prevCount );
3705 }
3706 }
3707
3708 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3710
3711 bool ok = tess.TesselatePolygon( poly.front(), nullptr );
3712
3713 // Self-touching outlines produce overlapping triangles; detect via area coverage
3714 if( ok )
3715 {
3716 double originalArea = std::abs( poly.front().Area() );
3717
3718 if( originalArea > 0.0 )
3719 {
3720 double triArea = 0.0;
3721
3722 for( const auto& tri : m_triangulatedPolys.back()->Triangles() )
3723 triArea += std::abs( tri.Area() );
3724
3725 double coverage = triArea / originalArea;
3726
3727 if( coverage > 1.01 || coverage < 0.99 )
3728 ok = false;
3729 }
3730 }
3731
3732 if( !ok )
3733 {
3734 m_triangulatedPolys.resize( prevCount );
3735
3736 SHAPE_POLY_SET splitSet;
3737 splitSet.AddOutline( poly[0] );
3738 splitSet.splitSelfTouchingOutlines();
3739
3740 bool splitOk = true;
3741
3742 for( int jj = 0; jj < splitSet.OutlineCount() && splitOk; ++jj )
3743 {
3744 m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( ii ) );
3745 POLYGON_TRIANGULATION splitTess( *m_triangulatedPolys.back() );
3746 splitOk = splitTess.TesselatePolygon( splitSet.CPolygon( jj ).front(), nullptr );
3747 }
3748
3749 if( !splitOk )
3750 directOk = false;
3751 }
3752 }
3753
3754 if( !m_triangulatedPolys.empty() && m_triangulatedPolys.back()->GetTriangleCount() == 0 )
3755 m_triangulatedPolys.pop_back();
3756
3757 if( directOk && !m_triangulatedPolys.empty() )
3758 {
3759 m_hash = checksum();
3760 m_hashValid = true;
3761 m_triangulationValid = true;
3762 }
3763 else
3764 {
3765 // Fracture each outline individually to preserve source outline indices
3766 m_triangulatedPolys.clear();
3767 bool fallbackOk = true;
3768
3769 for( int ii = 0; ii < srcSet->OutlineCount() && fallbackOk; ++ii )
3770 {
3771 const POLYGON& poly = srcSet->CPolygon( ii );
3772 SHAPE_POLY_SET flatSet( poly.front() );
3773
3774 for( size_t jj = 1; jj < poly.size(); ++jj )
3775 flatSet.AddHole( poly[jj] );
3776
3777 flatSet.ClearArcs();
3778 flatSet.Fracture();
3779 flatSet.splitSelfTouchingOutlines();
3780
3781 if( !triangulate( flatSet, ii, m_triangulatedPolys, nullptr, aSubmitter ) )
3782 fallbackOk = false;
3783 }
3784
3785 if( !fallbackOk )
3786 {
3787 // Last resort: flatten everything together (loses outline indices)
3788 m_triangulatedPolys.clear();
3789 SHAPE_POLY_SET fallbackSet( *this );
3790 fallbackSet.ClearArcs();
3791 fallbackSet.Fracture();
3792 fallbackSet.splitSelfTouchingOutlines();
3793
3794 if( !triangulate( fallbackSet, -1, m_triangulatedPolys, aHintData, aSubmitter ) )
3795 {
3796 wxLogTrace( TRIANGULATE_TRACE, "Failed to triangulate polygon" );
3797 }
3798 else
3799 {
3800 m_triangulationValid = true;
3801 }
3802 }
3803 else
3804 {
3805 m_triangulationValid = true;
3806 }
3807
3809 {
3810 m_hash = checksum();
3811 m_hashValid = true;
3812 }
3813 else
3814 {
3816 m_failedHashValid = true;
3817 }
3818 }
3819}
3820
3821
3822
3824{
3825 MMH3_HASH hash( 0x68AF835D ); // Arbitrary seed
3826
3827 hash.add( m_polys.size() );
3828
3829 for( const POLYGON& outline : m_polys )
3830 {
3831 hash.add( outline.size() );
3832
3833 for( const SHAPE_LINE_CHAIN& lc : outline )
3834 {
3835 hash.add( lc.PointCount() );
3836
3837 for( int i = 0; i < lc.PointCount(); i++ )
3838 {
3839 VECTOR2I pt = lc.CPoint( i );
3840
3841 hash.add( pt.x );
3842 hash.add( pt.y );
3843 }
3844 }
3845 }
3846
3847 return hash.digest();
3848}
3849
3850
3852{
3853 for( int i = 0; i < OutlineCount(); i++ )
3854 {
3855 if( hasTouchingHoles( CPolygon( i ) ) )
3856 return true;
3857 }
3858
3859 return false;
3860}
3861
3862
3864{
3865 std::set<long long> ptHashes;
3866
3867 for( const SHAPE_LINE_CHAIN& lc : aPoly )
3868 {
3869 for( const VECTOR2I& pt : lc.CPoints() )
3870 {
3871 const long long ptHash = (long long) pt.x << 32 | pt.y;
3872
3873 if( ptHashes.count( ptHash ) > 0 )
3874 return true;
3875
3876 ptHashes.insert( ptHash );
3877 }
3878 }
3879
3880 return false;
3881}
3882
3883
3888
3889
3891{
3892 size_t n = 0;
3893
3894 for( const std::unique_ptr<TRIANGULATED_POLYGON>& t : m_triangulatedPolys )
3895 n += t->GetTriangleCount();
3896
3897 return n;
3898}
3899
3900
3901void SHAPE_POLY_SET::GetIndexableSubshapes( std::vector<const SHAPE*>& aSubshapes ) const
3902{
3903 aSubshapes.reserve( GetIndexableSubshapeCount() );
3904
3905 for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
3906 {
3907 for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
3908 aSubshapes.push_back( &tri );
3909 }
3910}
3911
3912
3914{
3915 BOX2I bbox( parent->m_vertices[a] );
3916 bbox.Merge( parent->m_vertices[b] );
3917 bbox.Merge( parent->m_vertices[c] );
3918
3919 if( aClearance != 0 )
3920 bbox.Inflate( aClearance );
3921
3922 return bbox;
3923}
3924
3925
3927{
3928 m_triangles.emplace_back( a, b, c, this );
3929}
3930
3931
3933{
3935 m_vertices = aOther.m_vertices;
3936 m_triangles = aOther.m_triangles;
3937
3938 for( TRI& tri : m_triangles )
3939 tri.parent = this;
3940}
3941
3942
3944{
3946 m_vertices = aOther.m_vertices;
3947 m_triangles = aOther.m_triangles;
3948
3949 for( TRI& tri : m_triangles )
3950 tri.parent = this;
3951
3952 return *this;
3953}
3954
3955
3957{
3958 const size_t triCount = m_triangles.size();
3959
3960 // Indices are int; skip an implausibly huge mesh rather than overflow.
3961 if( triCount < 2 || triCount > static_cast<size_t>( std::numeric_limits<int>::max() ) / 6 )
3962 return;
3963
3964 const int halfEdgeCount = static_cast<int>( 3 * triCount );
3965
3966 // heVertex[3k+j] is vertex j of triangle k.
3967 std::vector<int> heVertex( halfEdgeCount );
3968
3969 for( size_t k = 0; k < triCount; k++ )
3970 {
3971 heVertex[3 * k + 0] = m_triangles[k].a;
3972 heVertex[3 * k + 1] = m_triangles[k].b;
3973 heVertex[3 * k + 2] = m_triangles[k].c;
3974 }
3975
3976 auto nextInTriangle = []( int aEdge ) { return aEdge - aEdge % 3 + ( aEdge + 1 ) % 3; };
3977
3978 // An untwinned edge is a polygon boundary and must never flip. Slots are never tombstoned so
3979 // probe chains stay intact.
3980 std::vector<int> twin( halfEdgeCount, -1 );
3981
3982 int tableSize = 1;
3983
3984 while( tableSize < halfEdgeCount * 2 )
3985 tableSize <<= 1;
3986
3987 const uint32_t tableMask = static_cast<uint32_t>( tableSize - 1 );
3988 std::vector<int> slotEdge( tableSize, -1 );
3989
3990 // A non-manifold edge (three or more triangles, e.g. a hole-bridge pinch) is retired so no
3991 // triangle straddling it can flip.
3992 std::vector<char> retired( tableSize, 0 );
3993 std::vector<int> toVisit;
3994 std::vector<char> queued( halfEdgeCount, 0 );
3995
3996 for( int edge = 0; edge < halfEdgeCount; edge++ )
3997 {
3998 int from = heVertex[edge];
3999 int to = heVertex[nextInTriangle( edge )];
4000 uint32_t keyLow = static_cast<uint32_t>( from < to ? from : to );
4001 uint32_t keyHigh = static_cast<uint32_t>( from < to ? to : from );
4002 uint32_t slot = ( ( keyLow * 0x9e3779b1u ) ^ ( keyHigh * 0x85ebca6bu ) ) & tableMask;
4003 bool handled = false;
4004
4005 while( slotEdge[slot] != -1 )
4006 {
4007 int other = slotEdge[slot];
4008 int otherFrom = heVertex[other];
4009 int otherTo = heVertex[nextInTriangle( other )];
4010 uint32_t otherLow = static_cast<uint32_t>( otherFrom < otherTo ? otherFrom : otherTo );
4011 uint32_t otherHigh = static_cast<uint32_t>( otherFrom < otherTo ? otherTo : otherFrom );
4012
4013 if( otherLow == keyLow && otherHigh == keyHigh )
4014 {
4015 if( retired[slot] )
4016 {
4017 // Non-manifold; stays a boundary.
4018 }
4019 else if( twin[other] == -1 )
4020 {
4021 twin[edge] = other;
4022 twin[other] = edge;
4023 queued[other] = 1;
4024 toVisit.push_back( other );
4025 }
4026 else
4027 {
4028 // Third half-edge here: retire the edge and unlink the pair already formed.
4029 int mate = twin[other];
4030 twin[other] = -1;
4031 twin[mate] = -1;
4032 retired[slot] = 1;
4033 }
4034
4035 handled = true;
4036 break;
4037 }
4038
4039 slot = ( slot + 1 ) & tableMask;
4040 }
4041
4042 if( !handled )
4043 slotEdge[slot] = edge;
4044 }
4045
4046 while( !toVisit.empty() )
4047 {
4048 int edge = toVisit.back();
4049 toVisit.pop_back();
4050 queued[edge] = 0;
4051
4052 int twinEdge = twin[edge];
4053
4054 if( twinEdge == -1 )
4055 continue;
4056
4057 int tri = edge - edge % 3;
4058 int twinTri = twinEdge - twinEdge % 3;
4059
4060 int apexAEdge = tri + ( edge + 2 ) % 3;
4061 int sharedEndEdge = tri + ( edge + 1 ) % 3;
4062 int apexBEdge = twinTri + ( twinEdge + 2 ) % 3;
4063 int twinSharedEdge = twinTri + ( twinEdge + 1 ) % 3;
4064
4065 int apexA = heVertex[apexAEdge];
4066 int sharedFrom = heVertex[edge];
4067 int sharedTo = heVertex[sharedEndEdge];
4068 int apexB = heVertex[apexBEdge];
4069
4070 const VECTOR2I& apexAPt = m_vertices[apexA];
4071 const VECTOR2I& sharedFromPt = m_vertices[sharedFrom];
4072 const VECTOR2I& sharedToPt = m_vertices[sharedTo];
4073 const VECTOR2I& apexBPt = m_vertices[apexB];
4074
4075 // A non-convex quad would flip a triangle outside the polygon.
4076 if( KIGEOM::OrientationSign( apexAPt, sharedFromPt, apexBPt ) <= 0 )
4077 continue;
4078
4079 if( KIGEOM::OrientationSign( apexAPt, apexBPt, sharedToPt ) <= 0 )
4080 continue;
4081
4082 // Prefer the diagonal with fewer slivers, Delaunay breaks ties. Each flip lowers
4083 // (slivers, then -min-angle) lexicographically, so the loop terminates.
4084 bool legal = KIGEOM::InCircleDelaunayLegal( apexAPt, sharedFromPt, sharedToPt, apexBPt );
4085
4086 int curSlivers = KIGEOM::IsSliverTriangle( apexAPt, sharedFromPt, sharedToPt )
4087 + KIGEOM::IsSliverTriangle( apexBPt, sharedToPt, sharedFromPt );
4088
4089 // Nothing to gain when already Delaunay and neither triangle is a sliver.
4090 if( legal && curSlivers == 0 )
4091 continue;
4092
4093 int flipSlivers = KIGEOM::IsSliverTriangle( apexAPt, sharedFromPt, apexBPt )
4094 + KIGEOM::IsSliverTriangle( apexAPt, apexBPt, sharedToPt );
4095
4096 bool doFlip = ( flipSlivers != curSlivers ) ? ( flipSlivers < curSlivers ) : !legal;
4097
4098 if( !doFlip )
4099 continue;
4100
4101 heVertex[edge] = apexB;
4102 heVertex[twinEdge] = apexA;
4103
4104 int apexBOuterTwin = twin[apexBEdge];
4105 int apexAOuterTwin = twin[apexAEdge];
4106
4107 twin[edge] = apexBOuterTwin;
4108
4109 if( apexBOuterTwin != -1 )
4110 twin[apexBOuterTwin] = edge;
4111
4112 twin[twinEdge] = apexAOuterTwin;
4113
4114 if( apexAOuterTwin != -1 )
4115 twin[apexAOuterTwin] = twinEdge;
4116
4117 twin[apexAEdge] = apexBEdge;
4118 twin[apexBEdge] = apexAEdge;
4119
4120 // The flip may have made the quad's four outer edges illegal.
4121 for( int outer : { edge, twinEdge, sharedEndEdge, twinSharedEdge } )
4122 {
4123 if( twin[outer] != -1 && !queued[outer] )
4124 {
4125 queued[outer] = 1;
4126 toVisit.push_back( outer );
4127 }
4128 }
4129 }
4130
4131 std::deque<TRI> refined;
4132
4133 for( size_t i = 0; i < triCount; i++ )
4134 refined.emplace_back( heVertex[3 * i + 0], heVertex[3 * i + 1], heVertex[3 * i + 2], this );
4135
4136 m_triangles = std::move( refined );
4137}
4138
4139
4141 m_sourceOutline( aSourceOutline )
4142{
4143}
4144
4145
4149
4150
4151void SHAPE_POLY_SET::Scale( double aScaleFactorX, double aScaleFactorY, const VECTOR2I& aCenter )
4152{
4153 for( POLYGON& poly : m_polys )
4154 {
4155 for( SHAPE_LINE_CHAIN& path : poly )
4156 {
4157 for( int i = 0; i < path.PointCount(); i++ )
4158 {
4159 VECTOR2I pt = path.CPoint( i );
4160 VECTOR2D vec;
4161 vec.x = ( pt.x - aCenter.x ) * aScaleFactorX;
4162 vec.y = ( pt.y - aCenter.y ) * aScaleFactorY;
4163 pt.x = KiROUND<double, int>( aCenter.x + vec.x );
4164 pt.y = KiROUND<double, int>( aCenter.y + vec.y );
4165 path.SetPoint( i, pt );
4166 }
4167 }
4168 }
4169
4172}
4173
4174
4175void
4176SHAPE_POLY_SET::BuildPolysetFromOrientedPaths( const std::vector<SHAPE_LINE_CHAIN>& aPaths,
4177 bool aEvenOdd )
4178{
4179 Clipper2Lib::Clipper64 clipper;
4180 Clipper2Lib::PolyTree64 tree;
4181 Clipper2Lib::Paths64 paths;
4182
4183 for( const SHAPE_LINE_CHAIN& path : aPaths )
4184 {
4185 Clipper2Lib::Path64 lc;
4186 lc.reserve( path.PointCount() );
4187
4188 for( int i = 0; i < path.PointCount(); i++ )
4189 lc.emplace_back( path.CPoint( i ).x, path.CPoint( i ).y );
4190
4191 paths.push_back( std::move( lc ) );
4192 }
4193
4194 clipper.AddSubject( paths );
4195 clipper.Execute( Clipper2Lib::ClipType::Union, aEvenOdd ? Clipper2Lib::FillRule::EvenOdd
4196 : Clipper2Lib::FillRule::NonZero, tree );
4197
4198 std::vector<CLIPPER_Z_VALUE> zValues;
4199 std::vector<SHAPE_ARC> arcBuffer;
4200
4201 importTree( tree, zValues, arcBuffer );
4202 tree.Clear(); // Free used memory (not done in dtor)
4203}
4204
4205
4206bool SHAPE_POLY_SET::PointInside( const VECTOR2I& aPt, int aAccuracy, bool aUseBBoxCache ) const
4207{
4208 for( int idx = 0; idx < OutlineCount(); idx++ )
4209 {
4210 if( COutline( idx ).PointInside( aPt, aAccuracy, aUseBBoxCache ) )
4211 return true;
4212 }
4213
4214 return false;
4215}
4216
4217
4218const std::vector<SEG> SHAPE_POLY_SET::GenerateHatchLines( const std::vector<double>& aSlopes,
4219 int aSpacing, int aLineLength ) const
4220{
4221 std::vector<SEG> hatchLines;
4222
4223 if( OutlineCount() == 0 || TotalVertices() == 0 )
4224 return hatchLines;
4225
4226 // define range for hatch lines
4227 int min_x = CVertex( 0 ).x;
4228 int max_x = CVertex( 0 ).x;
4229 int min_y = CVertex( 0 ).y;
4230 int max_y = CVertex( 0 ).y;
4231
4232 for( auto iterator = CIterateWithHoles(); iterator; iterator++ )
4233 {
4234 if( iterator->x < min_x )
4235 min_x = iterator->x;
4236
4237 if( iterator->x > max_x )
4238 max_x = iterator->x;
4239
4240 if( iterator->y < min_y )
4241 min_y = iterator->y;
4242
4243 if( iterator->y > max_y )
4244 max_y = iterator->y;
4245 }
4246
4247 auto sortEndsByDescendingX =
4248 []( const VECTOR2I& ref, const VECTOR2I& tst )
4249 {
4250 return tst.x < ref.x;
4251 };
4252
4253 for( double slope : aSlopes )
4254 {
4255 int64_t max_a, min_a;
4256
4257 if( slope > 0 )
4258 {
4259 max_a = KiROUND<double, int64_t>( max_y - slope * min_x );
4260 min_a = KiROUND<double, int64_t>( min_y - slope * max_x );
4261 }
4262 else
4263 {
4264 max_a = KiROUND<double, int64_t>( max_y - slope * max_x );
4265 min_a = KiROUND<double, int64_t>( min_y - slope * min_x );
4266 }
4267
4268 min_a = ( min_a / aSpacing ) * aSpacing;
4269
4270 // loop through hatch lines
4271 std::vector<VECTOR2I> pointbuffer;
4272 pointbuffer.reserve( 256 );
4273
4274 for( int64_t a = min_a; a < max_a; a += aSpacing )
4275 {
4276 pointbuffer.clear();
4277
4278 // Iterate through all vertices
4279 for( auto iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
4280 {
4281 const SEG seg = *iterator;
4282 VECTOR2I pt;
4283
4284 if( seg.IntersectsLine( slope, a, pt ) )
4285 {
4286 // If the intersection point is outside the polygon, skip it
4287 if( pt.x < min_x || pt.x > max_x || pt.y < min_y || pt.y > max_y )
4288 continue;
4289
4290 // Add the intersection point to the buffer
4291 pointbuffer.emplace_back( KiROUND( pt.x ), KiROUND( pt.y ) );
4292 }
4293 }
4294
4295 // sort points in order of descending x (if more than 2) to
4296 // ensure the starting point and the ending point of the same segment
4297 // are stored one just after the other.
4298 if( pointbuffer.size() > 2 )
4299 sort( pointbuffer.begin(), pointbuffer.end(), sortEndsByDescendingX );
4300
4301 // creates lines or short segments inside the complex polygon
4302 for( size_t ip = 0; ip + 1 < pointbuffer.size(); ip++ )
4303 {
4304 const VECTOR2I& p1 = pointbuffer[ip];
4305 const VECTOR2I& p2 = pointbuffer[ip + 1];
4306
4307 // Avoid duplicated intersections or segments
4308 if( p1 == p2 )
4309 continue;
4310
4311 SEG candidate( p1, p2 );
4312
4313 VECTOR2I mid( ( candidate.A.x + candidate.B.x ) / 2, ( candidate.A.y + candidate.B.y ) / 2 );
4314
4315 // Check if segment is inside the polygon by checking its middle point
4316 if( Contains( mid, -1, 1, true ) )
4317 {
4318 int dx = p2.x - p1.x;
4319
4320 // Push only one line for diagonal hatch or for small lines < twice
4321 // the line length; else push 2 small lines
4322 if( aLineLength == -1 || std::abs( dx ) < 2 * aLineLength )
4323 {
4324 hatchLines.emplace_back( candidate );
4325 }
4326 else
4327 {
4328 double dy = p2.y - p1.y;
4329 slope = dy / dx;
4330
4331 if( dx > 0 )
4332 dx = aLineLength;
4333 else
4334 dx = -aLineLength;
4335
4336 int x1 = KiROUND( p1.x + dx );
4337 int x2 = KiROUND( p2.x - dx );
4338 int y1 = KiROUND( p1.y + dx * slope );
4339 int y2 = KiROUND( p2.y - dx * slope );
4340
4341 hatchLines.emplace_back( SEG( p1.x, p1.y, x1, y1 ) );
4342
4343 hatchLines.emplace_back( SEG( p2.x, p2.y, x2, y2 ) );
4344 }
4345 }
4346 }
4347 }
4348 }
4349
4350 return hatchLines;
4351}
int index
bool operator==(const wxAuiPaneInfo &aLhs, const wxAuiPaneInfo &aRhs)
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
BOX2< VECTOR2D > BOX2D
Definition box2.h:915
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition box2.h:594
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
void InsertBridges(Index aFirst)
void insert(uint32_t aEdge, uint32_t aHead)
std::vector< NODE > m_nodes
std::vector< uint32_t > m_bucketIds
FractureEdge::Index Index
void Query(int aY, Index aProvokingIndex, Visitor &&aVisitor) const
std::vector< uint32_t > m_longIds
std::vector< uint32_t > m_offsets
static void logAccepted(size_t aEdges, size_t aHoles, uint32_t aStripes, size_t aBytes)
std::pair< uint32_t, uint32_t > span(const FractureEdge &aEdge) const
FRACTURE_EDGE_INDEX(FractureEdgeSet &aEdges, Originals &&aOriginals, int aMinY, int aMaxY, uint32_t aStripeCount, size_t aHoleCount)
uint32_t map(int aY) const
static constexpr uint32_t INVALID
void visitOverflow(uint32_t aNode, Index aProvokingIndex, Visitor &&aVisitor) const
FractureEdgeSet & m_edges
std::vector< uint32_t > m_heads
A streaming C++ equivalent for MurmurHash3_x64_128.
Definition mmh3_hash.h:56
FORCE_INLINE void add(const std::string &input)
Definition mmh3_hash.h:117
FORCE_INLINE HASH_128 digest()
Definition mmh3_hash.h:136
bool TesselatePolygon(const SHAPE_POLY_SET::POLYGON &aPolygon, SHAPE_POLY_SET::TRIANGULATED_POLYGON *aHintData)
Triangulate a polygon with holes by bridging holes directly into the outer ring's VERTEX linked list,...
std::vector< SHAPE_LINE_CHAIN > partitionPolygonBalanced(const SHAPE_LINE_CHAIN &aPoly, size_t aTargetLeaves) const
size_t suggestedPartitionLeafCount(const SHAPE_LINE_CHAIN &aPoly) const
Immutable owning spatial snapshot of straight segments.
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
ecoord SquaredDistance(const SEG &aSeg) const
Definition seg.cpp:35
bool IntersectsLine(double aSlope, double aOffset, VECTOR2I &aIntersection) const
Check if this segment intersects a line defined by slope aSlope and offset aOffset.
Definition seg.cpp:412
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
int Index() const
Return the index of this segment in its parent shape (applicable only to non-local segments).
Definition seg.h:358
static SEG::ecoord Square(int a)
Definition seg.h:119
bool Collide(const SEG &aSeg, int aClearance, int *aActual=nullptr) const
Definition seg.cpp:497
bool ApproxCollinear(const SEG &aSeg, int aDistanceThreshold=1) const
Definition seg.cpp:759
SHAPE_TYPE Type() const
Return the type of the shape.
Definition shape.h:96
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
void GenerateBBoxCache() const
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
void ReservePoints(size_t aSize)
Allocate a number of points all at once (for performance).
void Clear()
Remove all points from the line chain.
void Simplify(int aTolerance=0)
Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
double Area(bool aAbsolute=true) const
Return the area of this chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
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.
size_t ArcCount() const
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
const std::vector< VECTOR2I > & CPoints() const
void Refine()
Refine this triangulation in place with boundary-preserving Lawson edge flips, minimizing the number ...
void AddTriangle(int a, int b, int c)
TRIANGULATED_POLYGON & operator=(const TRIANGULATED_POLYGON &aOther)
std::mutex m_triangulationMutex
virtual bool HasIndexableSubshapes() const override
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void RemoveAllContours()
Remove all outlines & holes (clears) the polygon set.
SHAPE_POLY_SET Chamfer(int aDistance)
Return a chamfered version of the polygon set.
void RemoveOutline(int aOutlineIdx)
Delete the aOutlineIdx-th outline of the set including its contours and holes.
void Scale(double aScaleFactorX, double aScaleFactorY, const VECTOR2I &aCenter)
bool CollideEdge(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any edge of any of the contours of the polygon.
HASH_128 GetHash() const
virtual void GetIndexableSubshapes(std::vector< const SHAPE * > &aSubshapes) const override
void BooleanXor(const SHAPE_POLY_SET &b)
Perform boolean polyset exclusive or.
ITERATOR_TEMPLATE< VECTOR2I > ITERATOR
void fractureSingle(POLYGON &paths)
bool HasHoles() const
Return true if the polygon set has any holes.
CONST_ITERATOR CIterateWithHoles() const
void BooleanAdd(const SHAPE_POLY_SET &b)
Perform boolean polyset union.
ITERATOR IterateWithHoles()
void ClearArcs()
Removes all arc references from all the outlines and holes in the polyset.
bool IsTriangulationUpToDate() const
void importPaths(Clipper2Lib::Paths64 &paths, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffe)
void InsertVertex(int aGlobalIndex, const VECTOR2I &aNewVertex)
Adds a vertex in the globally indexed position aGlobalIndex.
std::atomic< bool > m_failedHashValid
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of vertices in a given outline/hole.
void DeletePolygon(int aIdx)
Delete aIdx-th polygon from the set.
void cacheTriangulation(bool aSimplify, std::vector< std::unique_ptr< TRIANGULATED_POLYGON > > *aHintData, const TASK_SUBMITTER &aSubmitter={})
double Area()
Return the area of this poly set.
void SetVertex(const VERTEX_INDEX &aIndex, const VECTOR2I &aPos)
Accessor function to set the position of a specific point.
bool IsEmpty() const
Return true if the set is empty (no polygons at all)
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
void BuildPolysetFromOrientedPaths(const std::vector< SHAPE_LINE_CHAIN > &aPaths, bool aEvenOdd=false)
Build a SHAPE_POLY_SET from a bunch of outlines in provided in random order.
bool Parse(std::stringstream &aStream) override
int TotalVertices() const
Return total number of vertices stored in the set.
POLYGON & Polygon(int aIndex)
Return the aIndex-th subpolygon in the set.
int FullPointCount() const
Return the number of points in the shape poly set.
void GetArcs(std::vector< SHAPE_ARC > &aArcBuffer) const
Appends all the arcs in this polyset to aArcBuffer.
bool IsVertexInHole(int aGlobalIdx)
Check whether the aGlobalIndex-th vertex belongs to a hole.
int NormalizeAreaOutlines()
Convert a self-intersecting polygon to one (or more) non self-intersecting polygon(s).
static bool appendBridgeFreePaths(const SHAPE_LINE_CHAIN &aChain, Clipper2Lib::Paths64 &aPaths, std::vector< CLIPPER_Z_VALUE > &aZValues, std::vector< SHAPE_ARC > &aArcBuffer)
Append the rings left after cutting the fracture bridges out of aChain.
void RemoveVertex(int aGlobalIndex)
Delete the aGlobalIndex-th vertex.
void unfractureSingle(POLYGON &path)
void inflateLine2(const SHAPE_LINE_CHAIN &aLine, int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy, bool aSimplify=false)
bool GetRelativeIndices(int aGlobalIdx, VERTEX_INDEX *aRelativeIndices) const
Convert a global vertex index —i.e., a number that globally identifies a vertex in a concatenated lis...
bool IsPolygonSelfIntersecting(int aPolygonIndex) const
Check whether the aPolygonIndex-th polygon in the set is self intersecting.
SHAPE_POLY_SET Subset(int aFirstPolygon, int aLastPolygon)
Return a subset of the polygons in this set, the ones between aFirstPolygon and aLastPolygon.
int RemoveNullSegments()
Look for null segments; ie, segments whose ends are exactly the same and deletes them.
HASH_128 checksum() const
void Inflate(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify=false)
Perform outline inflation/deflation.
int HoleCount(int aOutline) const
Returns the number of holes in a given outline.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
virtual void CacheTriangulation(bool aSimplify=false, const TASK_SUBMITTER &aSubmitter={})
Build a polygon triangulation, needed to draw a polygon on OpenGL and in some other calculations.
int AddPolygon(const POLYGON &apolygon)
Adds a polygon to the set.
const std::vector< SEG > GenerateHatchLines(const std::vector< double > &aSlopes, int aSpacing, int aLineLength) const
const std::string Format(bool aCplusPlus=true) const override
void Simplify()
Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections)
std::vector< SHAPE_LINE_CHAIN > POLYGON
represents a single polygon outline with holes.
std::vector< std::unique_ptr< TRIANGULATED_POLYGON > > m_triangulatedPolys
ITERATOR_TEMPLATE< const VECTOR2I > CONST_ITERATOR
void inflate2(int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy, bool aSimplify=false)
int AddHole(const SHAPE_LINE_CHAIN &aHole, int aOutline=-1)
Adds a new hole to the given outline (default: last) and returns its index.
SEG::ecoord SquaredDistance(const VECTOR2I &aPoint, bool aOutlineOnly, VECTOR2I *aNearest) const
Compute the minimum distance squared between aPoint and all the polygons in the set.
void RemoveContour(int aContourIdx, int aPolygonIdx=-1)
Delete the aContourIdx-th contour of the aPolygonIdx-th polygon in the set.
void Unfracture()
Convert a single outline slitted ("fractured") polygon into a set ouf outlines with holes.
int ArcCount() const
Count the number of arc shapes present.
bool GetGlobalIndex(VERTEX_INDEX aRelativeIndices, int &aGlobalIdx) const
Compute the global index of a vertex from the relative indices of polygon, contour and vertex.
bool GetNeighbourIndexes(int aGlobalIndex, int *aPrevious, int *aNext) const
Return the global indexes of the previous and the next corner of the aGlobalIndex-th corner of a cont...
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void SimplifyOutlines(int aMaxError=0)
Simplifies the lines in the polyset.
void booleanOp(Clipper2Lib::ClipType aType, const SHAPE_POLY_SET &aOtherShape)
This is the engine to execute all polygon boolean transforms (AND, OR, ... and polygon simplification...
const TRIANGULATED_POLYGON * TriangulatedPolygon(int aIndex) const
bool hasTouchingHoles(const POLYGON &aPoly) const
Return true if the polygon set has any holes that touch share a vertex.
bool PointOnEdge(const VECTOR2I &aP, int aAccuracy=0) const
Check if point aP lies on an edge or vertex of some of the outlines or holes.
std::atomic< bool > m_hashValid
bool CollideVertex(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any vertex of any of the contours of the polygon.
void DeletePolygonAndTriangulationData(int aIdx, bool aUpdateHash=true)
Delete aIdx-th polygon and its triangulation data from the set.
unsigned int TriangulatedPolyCount() const
Return the number of triangulated polygons.
std::atomic< bool > m_triangulationValid
void UpdateTriangulationDataHash()
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int NewHole(int aOutline=-1)
Creates a new hole in a given outline.
SEG::ecoord SquaredDistanceToPolygon(VECTOR2I aPoint, int aIndex, VECTOR2I *aNearest) const
Compute the minimum distance between the aIndex-th polygon and aPoint.
CONST_SEGMENT_ITERATOR CIterateSegmentsWithHoles() const
Return an iterator object, for the aOutline-th outline in the set (with holes).
virtual size_t GetIndexableSubshapeCount() const override
SEG::ecoord SquaredDistanceToSeg(const SEG &aSegment, VECTOR2I *aNearest=nullptr) const
Compute the minimum distance squared between aSegment and all the polygons in the set.
void importPolyPath(const std::unique_ptr< Clipper2Lib::PolyPath64 > &aPolyPath, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffer)
void RebuildHolesFromContours()
Extract all contours from this polygon set, then recreate polygons with holes.
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the line points about y or x (or both)
void OffsetLineChain(const SHAPE_LINE_CHAIN &aLine, int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify)
Perform offsetting of a line chain.
void BuildBBoxCaches() const
Construct BBoxCaches for Contains(), below.
std::vector< POLYGON > m_polys
void splitSelfTouchingOutlines()
Split outline segments at vertices that lie on them (self-touching polygons).
const SHAPE_LINE_CHAIN & CHole(int aOutline, int aHole) const
POLYGON FilletPolygon(unsigned int aRadius, int aErrorMax, int aIndex)
Return a filleted version of the aIndex-th polygon.
bool containsSingle(const VECTOR2I &aP, int aSubpolyIndex, int aAccuracy, bool aUseBBoxCaches=false) const
Check whether the point aP is inside the aSubpolyIndex-th polygon of the polyset.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the index-th vertex in a given hole outline within a given outline.
int OutlineCount() const
Return the number of outlines in the set.
void InflateWithLinkedHoles(int aFactor, CORNER_STRATEGY aCornerStrategy, int aMaxError)
Perform outline inflation/deflation, using round corners.
POLYGON chamferFilletPolygon(CORNER_MODE aMode, unsigned int aDistance, int aIndex, int aErrorMax)
Return the chamfered or filleted version of the aIndex-th polygon in the set, depending on the aMode ...
SHAPE_POLY_SET Fillet(int aRadius, int aErrorMax)
Return a filleted version of the polygon set.
void Move(const VECTOR2I &aVector) override
bool HasTouchingHoles() const
Return true if the polygon set has any holes that share a vertex.
SHAPE * Clone() const override
Return a dynamically allocated copy of the shape.
void Fracture(bool aSimplify=true)
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
SHAPE_POLY_SET & operator=(const SHAPE_POLY_SET &aOther)
bool Contains(const VECTOR2I &aP, int aSubpolyIndex=-1, int aAccuracy=0, bool aUseBBoxCaches=false) const
Return true if a given subpolygon contains the point aP.
SHAPE_POLY_SET CloneDropTriangulation() const
bool isExteriorWaist(const SEG &aSegA, const SEG &aSegB) const
Check if two line segments are collinear and overlap.
void BooleanSubtract(const SHAPE_POLY_SET &b)
Perform boolean polyset difference.
const POLYGON & CPolygon(int aIndex) const
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
POLYGON ChamferPolygon(unsigned int aDistance, int aIndex)
Return a chamfered version of the aIndex-th polygon.
std::function< void(std::function< void()>)> TASK_SUBMITTER
Callback that submits a unit of work for asynchronous execution.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
const BOX2I BBoxFromCaches() const
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
void importTree(Clipper2Lib::PolyTree64 &tree, const std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, const std::vector< SHAPE_ARC > &aArcBuffe)
SEGMENT_ITERATOR_TEMPLATE< const SEG > CONST_SEGMENT_ITERATOR
bool IsSelfIntersecting() const
Check whether any of the polygons in the set is self intersecting.
const SEG & GetSeg() const
int GetWidth() const override
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const
Check if the boundary of shape (this) lies closer to the point aP than aClearance,...
Definition shape.h:181
VECTOR2I::extended_type ecoord
Definition shape.h:311
SHAPE(SHAPE_TYPE aType)
Create an empty shape of type aType.
Definition shape.h:134
static constexpr extended_type ECOORD_MAX
Definition vector2d.h:72
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:281
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:335
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:406
CORNER_STRATEGY
define how inflate transform build inflated polygon
@ ROUND_ACUTE_CORNERS
Acute angles are rounded.
@ CHAMFER_ACUTE_CORNERS
Acute angles are chamfered.
@ CHAMFER_ALL_CORNERS
All angles are chamfered.
@ ROUND_ALL_CORNERS
All angles are rounded.
@ ALLOW_ACUTE_CORNERS
just inflate the polygon. Acute angles create spikes
static bool empty(const wxTextEntryBase *aCtrl)
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:446
Exact orientation and in-circle predicates over integer coordinates.
a few functions useful in geometry calculations.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
static constexpr void hash_combine(std::size_t &seed)
This is a dummy function to take the final case of hash_combine below.
Definition hash.h:28
FLIP_DIRECTION
Definition mirror.h:23
std::pair< uint32_t, uint32_t > StripeSpan(int aY1, int aY2, int aMinY, int aMaxY, uint32_t aStripeCount)
bool ActualCapacityFits(size_t aBucketIds, size_t aLongIds, size_t aOffsets, size_t aScratch, size_t aHeads, size_t aNodes, size_t aNodeSize, size_t aBudget, size_t *aBytes=nullptr)
bool ShouldIndex(uint64_t aEstimatedVisits, size_t aHoleCount)
bool CapacityFits(size_t aBucketIds, size_t aLongIds, size_t aStripeCount, size_t aHoleCount, size_t aNodeSize, size_t aBudget)
uint32_t MapYToStripe(int aY, int aMinY, int aMaxY, uint32_t aStripeCount)
uint32_t StripeCountFor(size_t aEdgeCount)
Construction helpers for the interactive arc drawing modes.
int OrientationSign(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
Orientation of triangle (a, b, c): +1 counter-clockwise, -1 clockwise, 0 collinear.
bool InCircleDelaunayLegal(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c, const VECTOR2I &p)
True when p is outside the circumcircle of CCW triangle (a, b, c): the shared edge is already Delauna...
bool IsSliverTriangle(const VECTOR2I &a, const VECTOR2I &b, const VECTOR2I &c)
A triangle is a sliver when its longest edge exceeds ten times its shortest.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
static PGM_BASE * process
#define TRIANGULATE_TRACE
#define TRIANGULATESIMPLIFICATIONLEVEL
CITER next(CITER it)
Definition ptree.cpp:120
@ SH_POLY_SET
set of polygons (with holes, etc.)
Definition shape.h:48
@ SH_CIRCLE
circle
Definition shape.h:46
@ SH_SEGMENT
line segment
Definition shape.h:44
static void fractureSingleCacheFriendly(SHAPE_POLY_SET::POLYGON &paths)
static void fractureSingleSlow(SHAPE_POLY_SET::POLYGON &paths)
static int fractureIntersectX(const FractureEdge &aEdge, int aY)
std::vector< FractureEdge > FractureEdgeSet
std::vector< FractureEdgeSlow * > FractureEdgeSetSlow
#define ENABLEFRACTUREEDGEINDEX
static bool splitAtBridges(const SHAPE_LINE_CHAIN &aChain, std::vector< SHAPE_LINE_CHAIN > &aRings)
Split a closed ring into the rings left once pairs of coincident opposite edges are removed.
#define SEG_CNT_MAX
#define ENABLECACHEFRIENDLYFRACTURE
static FractureEdge * processHole(FractureEdgeSet &edges, FractureEdge::Index provokingIndex, FractureEdge::Index edgeIndex, FractureEdge::Index bridgeIndex, FRACTURE_EDGE_INDEX *aIndex)
static int processEdge(FractureEdgeSetSlow &edges, FractureEdgeSlow *edge)
Holds information on each point of a SHAPE_LINE_CHAIN that is retrievable after an operation with Cli...
FractureEdgeSlow * m_next
bool matches(int y) const
FractureEdgeSlow(bool connected, const VECTOR2I &p1, const VECTOR2I &p2)
FractureEdge(const VECTOR2I &p1, const VECTOR2I &p2, Index next)
FractureEdge()=default
bool matches(int y) const
A storage class for 128-bit hash value.
Definition hash_128.h:32
virtual const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
Structure to hold the necessary information in order to index a vertex on a SHAPE_POLY_SET object: th...
std::string path
KIBIS_PIN * partner
SHAPE_CIRCLE circle(c.m_circle_center, c.m_circle_radius)
VECTOR2I location
int actual
wxString result
Test unit parsing edge cases and error handling.
int delta
#define M_PI
constexpr int sign(T val)
Definition util.h:166
T rescale(T aNumerator, T aValue, T aDenominator)
Scale a number (value) by rational (numerator/denominator).
Definition util.h:160
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707