KiCad PCB EDA Suite
Loading...
Searching...
No Matches
shape_line_chain.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) 2013-2017 CERN
5 * Copyright (C) 2013-2024 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * @author Tomasz Wlostowski <[email protected]>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27#include <limits>
28#include <math.h> // for hypot
29#include <map>
30#include <string> // for basic_string
31
32#include <clipper.hpp>
33#include <clipper2/clipper.h>
34#include <core/kicad_algo.h> // for alg::run_on_pair
35#include <geometry/circle.h>
36#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
39#include <math/box2.h> // for BOX2I
40#include <math/util.h> // for rescale
41#include <math/vector2d.h> // for VECTOR2, VECTOR2I
42#include <math/box2_minmax.h>
43#include <trigo.h> // for RotatePoint
44
45class SHAPE;
46
47const ssize_t SHAPE_LINE_CHAIN::SHAPE_IS_PT = -1;
48const std::pair<ssize_t, ssize_t> SHAPE_LINE_CHAIN::SHAPES_ARE_PT = { SHAPE_IS_PT, SHAPE_IS_PT };
49
50
51SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const std::vector<int>& aV)
52 : SHAPE_LINE_CHAIN_BASE( SH_LINE_CHAIN ), m_closed( false ), m_width( 0 )
53{
54 for(size_t i = 0; i < aV.size(); i+= 2 )
55 {
56 Append( aV[i], aV[i+1] );
57 }
58}
59
60
61SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const std::vector<VECTOR2I>& aV, bool aClosed ) :
63 m_closed( false ),
64 m_width( 0 )
65{
66 m_points = aV;
67 m_shapes = std::vector<std::pair<ssize_t, ssize_t>>( aV.size(), SHAPES_ARE_PT );
68 SetClosed( aClosed );
69}
70
71
72SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const SHAPE_ARC& aArc, bool aClosed ) :
74 m_closed( false ),
75 m_width( aArc.GetWidth() )
76{
77 Append( aArc );
78 SetClosed( aClosed );
79}
80
81
82SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const ClipperLib::Path& aPath,
83 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
84 const std::vector<SHAPE_ARC>& aArcBuffer ) :
86 m_closed( true ), m_width( 0 )
87{
88 std::map<ssize_t, ssize_t> loadedArcs;
89 m_points.reserve( aPath.size() );
90 m_shapes.reserve( aPath.size() );
91
92 auto loadArc =
93 [&]( ssize_t aArcIndex ) -> ssize_t
94 {
95 if( aArcIndex == SHAPE_IS_PT )
96 {
97 return SHAPE_IS_PT;
98 }
99 else if( loadedArcs.count( aArcIndex ) == 0 )
100 {
101 loadedArcs.insert( { aArcIndex, m_arcs.size() } );
102 m_arcs.push_back( aArcBuffer.at( aArcIndex ) );
103 }
104
105 return loadedArcs.at( aArcIndex );
106 };
107
108 for( size_t ii = 0; ii < aPath.size(); ++ii )
109 {
110 Append( aPath[ii].X, aPath[ii].Y );
111
112 m_shapes[ii].first = loadArc( aZValueBuffer[aPath[ii].Z].m_FirstArcIdx );
113 m_shapes[ii].second = loadArc( aZValueBuffer[aPath[ii].Z].m_SecondArcIdx );
114 }
115
116 // Clipper shouldn't return duplicate contiguous points. if it did, these would be
117 // removed during Append() and we would have different number of shapes to points
118 wxASSERT( m_shapes.size() == m_points.size() );
119
120 // Clipper might mess up the rotation of the indices such that an arc can be split between
121 // the end point and wrap around to the start point. Lets fix the indices up now
123}
124
125
126SHAPE_LINE_CHAIN::SHAPE_LINE_CHAIN( const Clipper2Lib::Path64& aPath,
127 const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
128 const std::vector<SHAPE_ARC>& aArcBuffer ) :
130 m_closed( true ), m_width( 0 )
131{
132 std::map<ssize_t, ssize_t> loadedArcs;
133 m_points.reserve( aPath.size() );
134 m_shapes.reserve( aPath.size() );
135
136 auto loadArc =
137 [&]( ssize_t aArcIndex ) -> ssize_t
138 {
139 if( aArcIndex == SHAPE_IS_PT )
140 {
141 return SHAPE_IS_PT;
142 }
143 else if( loadedArcs.count( aArcIndex ) == 0 )
144 {
145 loadedArcs.insert( { aArcIndex, m_arcs.size() } );
146 m_arcs.push_back( aArcBuffer.at( aArcIndex ) );
147 }
148
149 return loadedArcs.at( aArcIndex );
150 };
151
152 for( size_t ii = 0; ii < aPath.size(); ++ii )
153 {
154 Append( aPath[ii].x, aPath[ii].y );
155
156 m_shapes[ii].first = loadArc( aZValueBuffer[aPath[ii].z].m_FirstArcIdx );
157 m_shapes[ii].second = loadArc( aZValueBuffer[aPath[ii].z].m_SecondArcIdx );
158 }
159
160 // Clipper shouldn't return duplicate contiguous points. if it did, these would be
161 // removed during Append() and we would have different number of shapes to points
162 wxASSERT( m_shapes.size() == m_points.size() );
163
164 // Clipper might mess up the rotation of the indices such that an arc can be split between
165 // the end point and wrap around to the start point. Lets fix the indices up now
167}
168
169
170ClipperLib::Path SHAPE_LINE_CHAIN::convertToClipper( bool aRequiredOrientation,
171 std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
172 std::vector<SHAPE_ARC>& aArcBuffer ) const
173{
174 ClipperLib::Path c_path;
175 SHAPE_LINE_CHAIN input;
176 bool orientation = Area( false ) >= 0;
177 ssize_t shape_offset = aArcBuffer.size();
178
179 if( orientation != aRequiredOrientation )
180 input = Reverse();
181 else
182 input = *this;
183
184 int pointCount = input.PointCount();
185 c_path.reserve( pointCount );
186
187 for( int i = 0; i < pointCount; i++ )
188 {
189 const VECTOR2I& vertex = input.CPoint( i );
190
191 CLIPPER_Z_VALUE z_value( input.m_shapes[i], shape_offset );
192 size_t z_value_ptr = aZValueBuffer.size();
193 aZValueBuffer.push_back( z_value );
194
195 c_path.emplace_back( vertex.x, vertex.y, z_value_ptr );
196 }
197
198 aArcBuffer.insert( aArcBuffer.end(), input.m_arcs.begin(), input.m_arcs.end() );
199
200 return c_path;
201}
202
203
204Clipper2Lib::Path64 SHAPE_LINE_CHAIN::convertToClipper2( bool aRequiredOrientation,
205 std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
206 std::vector<SHAPE_ARC>& aArcBuffer ) const
207{
208 Clipper2Lib::Path64 c_path;
209 SHAPE_LINE_CHAIN input;
210 bool orientation = Area( false ) >= 0;
211 ssize_t shape_offset = aArcBuffer.size();
212
213 if( orientation != aRequiredOrientation )
214 input = Reverse();
215 else
216 input = *this;
217
218 int pointCount = input.PointCount();
219 c_path.reserve( pointCount );
220
221 for( int i = 0; i < pointCount; i++ )
222 {
223 const VECTOR2I& vertex = input.CPoint( i );
224
225 CLIPPER_Z_VALUE z_value( input.m_shapes[i], shape_offset );
226 size_t z_value_ptr = aZValueBuffer.size();
227 aZValueBuffer.push_back( z_value );
228
229 c_path.emplace_back( vertex.x, vertex.y, z_value_ptr );
230 }
231
232 aArcBuffer.insert( aArcBuffer.end(), input.m_arcs.begin(), input.m_arcs.end() );
233
234 return c_path;
235}
236
237
239{
240 wxCHECK( m_shapes.size() == m_points.size(), /*void*/ );
241
242 if( m_shapes.size() <= 1 )
243 return;
244
245 size_t rotations = 0;
246
247 while( ArcIndex( 0 ) != SHAPE_IS_PT
248 && !IsArcStart( 0 ) )
249 {
250 // Rotate right
251 std::rotate( m_points.rbegin(), m_points.rbegin() + 1, m_points.rend() );
252 std::rotate( m_shapes.rbegin(), m_shapes.rbegin() + 1, m_shapes.rend() );
253
254 // Sanity check - avoid infinite loops (NB: wxCHECK is not thread-safe)
255 if( rotations++ > m_shapes.size() )
256 return;
257 }
258}
259
260
262{
263 if( m_closed )
264 {
265 if( m_points.size() > 1 && m_points.front() == m_points.back() )
266 {
267 if( ArcIndex( m_shapes.size() - 1 ) != SHAPE_IS_PT )
268 {
269 m_shapes.front().second = m_shapes.front().first;
270 m_shapes.front().first = ArcIndex( m_shapes.size() - 1 ) ;
271 }
272
273 m_points.pop_back();
274 m_shapes.pop_back();
275
277 }
278 }
279 else
280 {
281 if( m_points.size() > 1 && IsSharedPt( 0 ) )
282 {
283 // Create a duplicate point at the end
284 m_points.push_back( m_points.front() );
285 m_shapes.push_back( { m_shapes.front().first, SHAPE_IS_PT } );
286 m_shapes.front().first = m_shapes.front().second;
287 m_shapes.front().second = SHAPE_IS_PT;
288 }
289 }
290}
291
292
293void SHAPE_LINE_CHAIN::convertArc( ssize_t aArcIndex )
294{
295 if( aArcIndex < 0 )
296 aArcIndex += m_arcs.size();
297
298 if( aArcIndex >= static_cast<ssize_t>( m_arcs.size() ) )
299 return;
300
301 // Clear the shapes references
302 for( auto& sh : m_shapes )
303 {
305 [&]( ssize_t& aShapeIndex )
306 {
307 if( aShapeIndex == aArcIndex )
308 aShapeIndex = SHAPE_IS_PT;
309
310 if( aShapeIndex > aArcIndex )
311 --aShapeIndex;
312 } );
313
314 if( sh.second != SHAPE_IS_PT && sh.first == SHAPE_IS_PT )
315 std::swap( sh.first, sh.second );
316 }
317
318 m_arcs.erase( m_arcs.begin() + aArcIndex );
319}
320
321
322void SHAPE_LINE_CHAIN::amendArc( size_t aArcIndex, const VECTOR2I& aNewStart,
323 const VECTOR2I& aNewEnd )
324{
325 wxCHECK_MSG( aArcIndex < m_arcs.size(), /* void */,
326 wxT( "Invalid arc index requested." ) );
327
328 SHAPE_ARC& theArc = m_arcs[aArcIndex];
329
330 // Try to preseve the centre of the original arc
331 SHAPE_ARC newArc;
332 newArc.ConstructFromStartEndCenter( aNewStart, aNewEnd, theArc.GetCenter(),
333 theArc.IsClockwise() );
334
335 m_arcs[aArcIndex] = newArc;
336}
337
338
339void SHAPE_LINE_CHAIN::splitArc( ssize_t aPtIndex, bool aCoincident )
340{
341 if( aPtIndex < 0 )
342 aPtIndex += m_shapes.size();
343
344 if( !IsSharedPt( aPtIndex ) && IsArcStart( aPtIndex ) )
345 return; // Nothing to do
346
347 if( !IsPtOnArc( aPtIndex ) )
348 return; // Nothing to do
349
350 wxCHECK_MSG( aPtIndex < static_cast<ssize_t>( m_shapes.size() ), /* void */,
351 wxT( "Invalid point index requested." ) );
352
353 if( IsSharedPt( aPtIndex ) || IsArcEnd( aPtIndex ) )
354 {
355 if( aCoincident || aPtIndex == 0 )
356 return; // nothing to do
357
358 ssize_t firstArcIndex = m_shapes[aPtIndex].first;
359
360 const VECTOR2I& newStart = m_arcs[firstArcIndex].GetP0(); // don't amend the start
361 const VECTOR2I& newEnd = m_points[aPtIndex - 1];
362 amendArc( firstArcIndex, newStart, newEnd );
363
364 if( IsSharedPt( aPtIndex ) )
365 {
366 m_shapes[aPtIndex].first = m_shapes[aPtIndex].second;
367 m_shapes[aPtIndex].second = SHAPE_IS_PT;
368 }
369 else
370 {
371 m_shapes[aPtIndex] = SHAPES_ARE_PT;
372 }
373
374 return;
375 }
376
377 ssize_t currArcIdx = ArcIndex( aPtIndex );
378 SHAPE_ARC& currentArc = m_arcs[currArcIdx];
379
380 SHAPE_ARC newArc1;
381 SHAPE_ARC newArc2;
382
383 VECTOR2I arc1End = ( aCoincident ) ? m_points[aPtIndex] : m_points[aPtIndex - 1];
384 VECTOR2I arc2Start = m_points[aPtIndex];
385
386 newArc1.ConstructFromStartEndCenter( currentArc.GetP0(), arc1End, currentArc.GetCenter(),
387 currentArc.IsClockwise() );
388
389 newArc2.ConstructFromStartEndCenter( arc2Start, currentArc.GetP1(), currentArc.GetCenter(),
390 currentArc.IsClockwise() );
391
392 if( !aCoincident && ArcIndex( aPtIndex - 1 ) != currArcIdx )
393 {
394 //Ignore newArc1 as it has zero points
395 m_arcs[currArcIdx] = newArc2;
396 }
397 else
398 {
399 m_arcs[currArcIdx] = newArc1;
400 m_arcs.insert( m_arcs.begin() + currArcIdx + 1, newArc2 );
401
402 if( aCoincident )
403 {
404 m_shapes[aPtIndex].second = currArcIdx + 1;
405 aPtIndex++;
406 }
407
408 // Only change the arc indices for the second half of the point range
409 for( int i = aPtIndex; i < PointCount(); i++ )
410 {
411 alg::run_on_pair( m_shapes[i], [&]( ssize_t& aIndex ) {
412 if( aIndex != SHAPE_IS_PT )
413 aIndex++;
414 } );
415 }
416 }
417}
418
419
420bool SHAPE_LINE_CHAIN_BASE::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
421 VECTOR2I* aLocation ) const
422{
423 if( IsClosed() && PointInside( aP, aClearance ) )
424 {
425 if( aLocation )
426 *aLocation = aP;
427
428 if( aActual )
429 *aActual = 0;
430
431 return true;
432 }
433
434 SEG::ecoord closest_dist_sq = VECTOR2I::ECOORD_MAX;
435 SEG::ecoord clearance_sq = SEG::Square( aClearance );
436 VECTOR2I nearest;
437
438 for( size_t i = 0; i < GetSegmentCount(); i++ )
439 {
440 const SEG& s = GetSegment( i );
441 VECTOR2I pn = s.NearestPoint( aP );
442 SEG::ecoord dist_sq = ( pn - aP ).SquaredEuclideanNorm();
443
444 if( dist_sq < closest_dist_sq )
445 {
446 nearest = pn;
447 closest_dist_sq = dist_sq;
448
449 if( closest_dist_sq == 0 )
450 break;
451
452 // If we're not looking for aActual then any collision will do
453 if( closest_dist_sq < clearance_sq && !aActual )
454 break;
455 }
456 }
457
458 if( closest_dist_sq == 0 || closest_dist_sq < clearance_sq )
459 {
460 if( aLocation )
461 *aLocation = nearest;
462
463 if( aActual )
464 *aActual = sqrt( closest_dist_sq );
465
466 return true;
467 }
468
469 return false;
470}
471
472
473bool SHAPE_LINE_CHAIN::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
474 VECTOR2I* aLocation ) const
475{
476 if( IsClosed() && PointInside( aP, aClearance ) )
477 {
478 if( aLocation )
479 *aLocation = aP;
480
481 if( aActual )
482 *aActual = 0;
483
484 return true;
485 }
486
487 SEG::ecoord closest_dist_sq = VECTOR2I::ECOORD_MAX;
488 SEG::ecoord clearance_sq = SEG::Square( aClearance );
489 VECTOR2I nearest;
490
491 // Collide line segments
492 for( size_t i = 0; i < GetSegmentCount(); i++ )
493 {
494 if( IsArcSegment( i ) )
495 continue;
496
497 const SEG& s = GetSegment( i );
498 VECTOR2I pn = s.NearestPoint( aP );
499 SEG::ecoord dist_sq = ( pn - aP ).SquaredEuclideanNorm();
500
501 if( dist_sq < closest_dist_sq )
502 {
503 nearest = pn;
504 closest_dist_sq = dist_sq;
505
506 if( closest_dist_sq == 0 )
507 break;
508
509 // If we're not looking for aActual then any collision will do
510 if( closest_dist_sq < clearance_sq && !aActual )
511 break;
512 }
513 }
514
515 if( closest_dist_sq == 0 || closest_dist_sq < clearance_sq )
516 {
517 if( aLocation )
518 *aLocation = nearest;
519
520 if( aActual )
521 *aActual = sqrt( closest_dist_sq );
522
523 return true;
524 }
525
526 // Collide arc segments
527 for( size_t i = 0; i < ArcCount(); i++ )
528 {
529 const SHAPE_ARC& arc = Arc( i );
530
531 // The arcs in the chain should have zero width
532 wxASSERT_MSG( arc.GetWidth() == 0, wxT( "Invalid arc width - should be zero" ) );
533
534 if( arc.Collide( aP, aClearance, aActual, aLocation ) )
535 return true;
536 }
537
538 return false;
539}
540
541
542void SHAPE_LINE_CHAIN::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
543{
544 for( VECTOR2I& pt : m_points )
545 RotatePoint( pt, aCenter, aAngle );
546
547 for( SHAPE_ARC& arc : m_arcs )
548 arc.Rotate( aAngle, aCenter );
549}
550
551
553 VECTOR2I& aPt1 ) const
554{
555 const std::vector<VECTOR2I>& myPts = m_points;
556 const std::vector<VECTOR2I>& otherPts = aOther.m_points;
557
558 const int c_maxBoxes = 100;
559 const int c_minPtsPerBox = 20;
560
561 int myPointsPerBox = std::max( c_minPtsPerBox, int( myPts.size() / c_maxBoxes ) + 1 );
562 int otherPointsPerBox = std::max( c_minPtsPerBox, int( otherPts.size() / c_maxBoxes ) + 1 );
563
564 int myNumBoxes = ( myPts.size() + myPointsPerBox - 1 ) / myPointsPerBox;
565 int otherNumBoxes = ( otherPts.size() + otherPointsPerBox - 1 ) / otherPointsPerBox;
566
567 struct BOX
568 {
569 BOX2I_MINMAX bbox;
570 VECTOR2I center;
571 int radius;
572 bool valid = false;
573 };
574
575 std::vector<BOX> myBoxes( myNumBoxes );
576 std::vector<BOX> otherBoxes( otherNumBoxes );
577
578 // Calculate bounding boxes
579 for( size_t i = 0; i < myPts.size(); i++ )
580 {
581 const VECTOR2I pt = myPts[i];
582 BOX& box = myBoxes[i / myPointsPerBox];
583
584 if( box.valid )
585 {
586 box.bbox.Merge( pt );
587 }
588 else
589 {
590 box.bbox = BOX2I_MINMAX( pt );
591 box.valid = true;
592 }
593 }
594
595 for( size_t i = 0; i < otherPts.size(); i++ )
596 {
597 const VECTOR2I pt = otherPts[i];
598 BOX& box = otherBoxes[i / otherPointsPerBox];
599
600 if( box.valid )
601 {
602 box.bbox.Merge( pt );
603 }
604 else
605 {
606 box.bbox = BOX2I_MINMAX( pt );
607 box.valid = true;
608 }
609 }
610
611 // Store centers and radiuses
612 for( BOX& box : myBoxes )
613 {
614 box.center = box.bbox.GetCenter();
615 box.radius = int( box.bbox.GetDiameter() / 2 );
616 }
617
618 for( BOX& box : otherBoxes )
619 {
620 box.center = box.bbox.GetCenter();
621 box.radius = int( box.bbox.GetDiameter() / 2 );
622 }
623
624 // Find closest pairs
625 struct DIST_PAIR
626 {
627 DIST_PAIR( int64_t aDistSq, size_t aIdA, size_t aIdB ) :
628 dist( aDistSq ), idA( aIdA ), idB( aIdB )
629 {
630 }
631
632 int64_t dist;
633 size_t idA;
634 size_t idB;
635 };
636
637 std::vector<DIST_PAIR> pairsToTest;
638
639 for( size_t ia = 0; ia < myBoxes.size(); ia++ )
640 {
641 for( size_t ib = 0; ib < otherBoxes.size(); ib++ )
642 {
643 const BOX& ca = myBoxes[ia];
644 const BOX& cb = otherBoxes[ib];
645
646 if( !ca.valid || !cb.valid )
647 continue;
648
649 VECTOR2L pA( ca.center );
650 VECTOR2L pB( cb.center );
651
652 int64_t dist = ( pB - pA ).EuclideanNorm();
653
654 dist -= ca.radius;
655 dist -= cb.radius;
656
657 pairsToTest.emplace_back( dist, ia, ib );
658 }
659 }
660
661 std::sort( pairsToTest.begin(), pairsToTest.end(),
662 []( const DIST_PAIR& a, const DIST_PAIR& b )
663 {
664 return a.dist < b.dist;
665 } );
666
667 const int c_polyPairsLimit = 5;
668
669 // Find closest segments in tested pairs
670 int64_t total_closest_dist_sq = VECTOR2I::ECOORD_MAX;
671
672 for( size_t pairId = 0; pairId < pairsToTest.size() && pairId < c_polyPairsLimit; pairId++ )
673 {
674 const DIST_PAIR& pair = pairsToTest[pairId];
675
676 VECTOR2I ptA;
677 VECTOR2I ptB;
678 int64_t dist_sq;
679
680 size_t myStartId = pair.idA * myPointsPerBox;
681 size_t myEndId = myStartId + myPointsPerBox;
682
683 if( myEndId > myPts.size() )
684 myEndId = myPts.size();
685
686 VECTOR2I myPrevPt = myPts[myStartId == 0 ? myPts.size() - 1 : myStartId - 1];
687
688 size_t otherStartId = pair.idB * otherPointsPerBox;
689 size_t otherEndId = otherStartId + otherPointsPerBox;
690
691 if( otherEndId > otherPts.size() )
692 otherEndId = otherPts.size();
693
694 VECTOR2I otherPrevPt = otherPts[otherStartId == 0 ? otherPts.size() - 1 : otherStartId - 1];
695
696 if( ClosestSegments( myPrevPt, myPts.begin() + myStartId, myPts.begin() + myEndId,
697 otherPrevPt, otherPts.begin() + otherStartId,
698 otherPts.begin() + otherEndId, ptA, ptB, dist_sq ) )
699 {
700 if( dist_sq < total_closest_dist_sq )
701 {
702 total_closest_dist_sq = dist_sq;
703 aPt0 = ptA;
704 aPt1 = ptB;
705 }
706 }
707 }
708
709 return total_closest_dist_sq != VECTOR2I::ECOORD_MAX;
710}
711
712
713bool SHAPE_LINE_CHAIN::ClosestSegments( const VECTOR2I& aMyPrevPt, const point_citer& aMyStart,
714 const point_citer& aMyEnd, const VECTOR2I& aOtherPrevPt,
715 const point_citer& aOtherStart,
716 const point_citer& aOtherEnd, VECTOR2I& aPt0,
717 VECTOR2I& aPt1, int64_t& aDistSq )
718{
719 if( aMyStart == aMyEnd )
720 return false;
721
722 if( aOtherStart == aOtherEnd )
723 return false;
724
725 int64_t closest_dist_sq = VECTOR2I::ECOORD_MAX;
726 VECTOR2I lastPtA = aMyPrevPt;
727
728 for( point_citer itA = aMyStart; itA != aMyEnd; itA++ )
729 {
730 const VECTOR2I& ptA = *itA;
731 VECTOR2I lastPtB = aOtherPrevPt;
732
733 for( point_citer itB = aOtherStart; itB != aOtherEnd; itB++ )
734 {
735 const VECTOR2I& ptB = *itB;
736
737 SEG segA( lastPtA, ptA );
738 SEG segB( lastPtB, ptB );
739
740 VECTOR2I nearestA, nearestB;
741
742 int64_t dist_sq;
743
744 if( segA.NearestPoints( segB, nearestA, nearestB, dist_sq ) )
745 {
746 if( dist_sq < closest_dist_sq )
747 {
748 closest_dist_sq = dist_sq;
749 aPt0 = nearestA;
750 aPt1 = nearestB;
751 }
752 }
753
754 lastPtB = ptB;
755 }
756
757 lastPtA = ptA;
758 }
759
760 aDistSq = closest_dist_sq;
761 return closest_dist_sq != VECTOR2I::ECOORD_MAX;
762}
763
764
765bool SHAPE_LINE_CHAIN::ClosestPoints( const point_citer& aMyStart, const point_citer& aMyEnd,
766 const point_citer& aOtherStart, const point_citer& aOtherEnd,
767 VECTOR2I& aPt0, VECTOR2I& aPt1, int64_t& aDistSq )
768{
769 int64_t closest_dist_sq = VECTOR2I::ECOORD_MAX;
770
771 for( point_citer itA = aMyStart; itA != aMyEnd; itA++ )
772 {
773 const VECTOR2I& ptA = *itA;
774
775 for( point_citer itB = aOtherStart; itB != aOtherEnd; itB++ )
776 {
777 const VECTOR2I& ptB = *itB;
778
779 ecoord dx = (ecoord) ptB.x - ptA.x;
780 ecoord dy = (ecoord) ptB.y - ptA.y;
781
782 SEG::ecoord dist_sq = dx * dx + dy * dy;
783
784 if( dist_sq < closest_dist_sq )
785 {
786 closest_dist_sq = dist_sq;
787 aPt0 = ptA;
788 aPt1 = ptB;
789 }
790 }
791 }
792
793 aDistSq = closest_dist_sq;
794 return closest_dist_sq != VECTOR2I::ECOORD_MAX;
795}
796
797
799 VECTOR2I& aPt1 ) const
800{
801 ecoord dist_sq;
802
803 return ClosestPoints( m_points.cbegin(), m_points.cend(), aOther.m_points.cbegin(),
804 aOther.m_points.cend(), aPt0, aPt1, dist_sq );
805}
806
807
808bool SHAPE_LINE_CHAIN_BASE::Collide( const SEG& aSeg, int aClearance, int* aActual,
809 VECTOR2I* aLocation ) const
810{
811 if( IsClosed() && PointInside( aSeg.A ) )
812 {
813 if( aLocation )
814 *aLocation = aSeg.A;
815
816 if( aActual )
817 *aActual = 0;
818
819 return true;
820 }
821
822 SEG::ecoord closest_dist_sq = VECTOR2I::ECOORD_MAX;
823 SEG::ecoord clearance_sq = SEG::Square( aClearance );
824 VECTOR2I nearest;
825
826 for( size_t i = 0; i < GetSegmentCount(); i++ )
827 {
828 const SEG& s = GetSegment( i );
829 SEG::ecoord dist_sq = s.SquaredDistance( aSeg );
830
831 if( dist_sq < closest_dist_sq )
832 {
833 if( aLocation )
834 nearest = s.NearestPoint( aSeg );
835
836 closest_dist_sq = dist_sq;
837
838 if( closest_dist_sq == 0)
839 break;
840
841 // If we're not looking for aActual then any collision will do
842 if( closest_dist_sq < clearance_sq && !aActual )
843 break;
844 }
845 }
846
847 if( closest_dist_sq == 0 || closest_dist_sq < clearance_sq )
848 {
849 if( aLocation )
850 *aLocation = nearest;
851
852 if( aActual )
853 *aActual = sqrt( closest_dist_sq );
854
855 return true;
856 }
857
858 return false;
859}
860
861
862bool SHAPE_LINE_CHAIN::Collide( const SEG& aSeg, int aClearance, int* aActual,
863 VECTOR2I* aLocation ) const
864{
865 if( IsClosed() && PointInside( aSeg.A ) )
866 {
867 if( aLocation )
868 *aLocation = aSeg.A;
869
870 if( aActual )
871 *aActual = 0;
872
873 return true;
874 }
875
876 SEG::ecoord closest_dist_sq = VECTOR2I::ECOORD_MAX;
877 SEG::ecoord clearance_sq = SEG::Square( aClearance );
878 VECTOR2I nearest;
879
880 // Collide line segments
881 for( size_t i = 0; i < GetSegmentCount(); i++ )
882 {
883 if( IsArcSegment( i ) )
884 continue;
885
886 const SEG& s = GetSegment( i );
887 SEG::ecoord dist_sq = s.SquaredDistance( aSeg );
888
889 if( dist_sq < closest_dist_sq )
890 {
891 if( aLocation )
892 nearest = s.NearestPoint( aSeg );
893
894 closest_dist_sq = dist_sq;
895
896 if( closest_dist_sq == 0 )
897 break;
898
899 // If we're not looking for aActual then any collision will do
900 if( closest_dist_sq < clearance_sq && !aActual )
901 break;
902 }
903 }
904
905 if( closest_dist_sq == 0 || closest_dist_sq < clearance_sq )
906 {
907 if( aLocation )
908 *aLocation = nearest;
909
910 if( aActual )
911 *aActual = sqrt( closest_dist_sq );
912
913 return true;
914 }
915
916 int dist = std::numeric_limits<int>::max();
917 SEG::ecoord closest_dist = sqrt( closest_dist_sq );
918
919 // Collide arc segments
920 for( size_t i = 0; i < ArcCount(); i++ )
921 {
922 const SHAPE_ARC& arc = Arc( i );
923 VECTOR2I pos;
924
925 // The arcs in the chain should have zero width
926 wxASSERT_MSG( arc.GetWidth() == 0, wxT( "Invalid arc width - should be zero" ) );
927
928 if( arc.Collide( aSeg, aClearance, aActual || aLocation ? &dist : nullptr,
929 aLocation ? &pos : nullptr ) )
930 {
931 if( !aActual )
932 return true;
933
934 if( dist < closest_dist )
935 {
936 closest_dist = dist;
937 nearest = pos;
938 }
939 }
940 }
941
942 if( closest_dist == 0 || closest_dist < aClearance )
943 {
944 if( aLocation )
945 *aLocation = nearest;
946
947 if( aActual )
948 *aActual = closest_dist;
949
950 return true;
951 }
952
953 return false;
954}
955
956
958{
959 SHAPE_LINE_CHAIN a( *this );
960
961 reverse( a.m_points.begin(), a.m_points.end() );
962 reverse( a.m_shapes.begin(), a.m_shapes.end() );
963 reverse( a.m_arcs.begin(), a.m_arcs.end() );
964
965 for( auto& sh : a.m_shapes )
966 {
967 if( sh != SHAPES_ARE_PT )
968 {
970 [&]( ssize_t& aShapeIndex )
971 {
972 if( aShapeIndex != SHAPE_IS_PT )
973 aShapeIndex = a.m_arcs.size() - aShapeIndex - 1;
974 } );
975
976 if( sh.second != SHAPE_IS_PT )
977 {
978 // If the second element is populated, the first one should be too!
979 assert( sh.first != SHAPE_IS_PT );
980
981 // Switch round first and second in shared points, as part of reversing the chain
982 std::swap( sh.first, sh.second );
983 }
984 }
985 }
986
987 for( SHAPE_ARC& arc : a.m_arcs )
988 arc.Reverse();
989
990 a.m_closed = m_closed;
991
992 return a;
993}
994
995
997{
998 for( ssize_t arcIndex = m_arcs.size() - 1; arcIndex >= 0; --arcIndex )
999 convertArc( arcIndex );
1000}
1001
1002
1003long long int SHAPE_LINE_CHAIN::Length() const
1004{
1005 long long int l = 0;
1006
1007 for( int i = 0; i < SegmentCount(); i++ )
1008 {
1009 // Only include segments that aren't part of arc shapes
1010 if( !IsArcSegment(i) )
1011 l += CSegment( i ).Length();
1012 }
1013
1014 for( size_t i = 0; i < ArcCount(); i++ )
1015 l += CArcs()[i].GetLength();
1016
1017 return l;
1018}
1019
1020
1021void SHAPE_LINE_CHAIN::Mirror( const VECTOR2I& aRef, FLIP_DIRECTION aFlipDirection )
1022{
1023 for( auto& pt : m_points )
1024 {
1025 if( aFlipDirection == FLIP_DIRECTION::LEFT_RIGHT )
1026 pt.x = -pt.x + 2 * aRef.x;
1027 else
1028 pt.y = -pt.y + 2 * aRef.y;
1029 }
1030
1031 for( auto& arc : m_arcs )
1032 arc.Mirror( aRef, aFlipDirection );
1033}
1034
1035
1037{
1038 for( auto& pt : m_points )
1039 pt = axis.ReflectPoint( pt );
1040
1041 for( auto& arc : m_arcs )
1042 arc.Mirror( axis );
1043}
1044
1045
1046void SHAPE_LINE_CHAIN::Replace( int aStartIndex, int aEndIndex, const VECTOR2I& aP )
1047{
1048 Remove( aStartIndex, aEndIndex );
1049 Insert( aStartIndex, aP );
1050 assert( m_shapes.size() == m_points.size() );
1051}
1052
1053
1054void SHAPE_LINE_CHAIN::Replace( int aStartIndex, int aEndIndex, const SHAPE_LINE_CHAIN& aLine )
1055{
1056 if( aEndIndex < 0 )
1057 aEndIndex += PointCount();
1058
1059 if( aStartIndex < 0 )
1060 aStartIndex += PointCount();
1061
1062 // We only process lines in order in this house
1063 wxASSERT( aStartIndex <= aEndIndex );
1064 wxASSERT( aEndIndex < static_cast<int>( m_points.size() ) );
1065
1066 SHAPE_LINE_CHAIN newLine = aLine;
1067
1068 // Zero points to add?
1069 if( newLine.PointCount() == 0 )
1070 {
1071 Remove( aStartIndex, aEndIndex );
1072 return;
1073 }
1074
1075 // Remove coincident points in the new line
1076 if( newLine.m_points.front() == m_points[aStartIndex] )
1077 {
1078 aStartIndex++;
1079 newLine.Remove( 0 );
1080
1081 // Zero points to add?
1082 if( newLine.PointCount() == 0 )
1083 {
1084 Remove( aStartIndex, aEndIndex );
1085 return;
1086 }
1087 }
1088
1089 if( newLine.m_points.back() == m_points[aEndIndex] && aEndIndex > 0 )
1090 {
1091 aEndIndex--;
1092 newLine.Remove( -1 );
1093 }
1094
1095 Remove( aStartIndex, aEndIndex );
1096
1097 // Zero points to add?
1098 if( newLine.PointCount() == 0 )
1099 return;
1100
1101 // The total new arcs index is added to the new arc indices
1102 size_t prev_arc_count = m_arcs.size();
1103 std::vector<std::pair<ssize_t, ssize_t>> new_shapes = newLine.m_shapes;
1104
1105 for( std::pair<ssize_t, ssize_t>& shape_pair : new_shapes )
1106 {
1107 alg::run_on_pair( shape_pair,
1108 [&]( ssize_t& aShape )
1109 {
1110 if( aShape != SHAPE_IS_PT )
1111 aShape += prev_arc_count;
1112 } );
1113 }
1114
1115 m_shapes.insert( m_shapes.begin() + aStartIndex, new_shapes.begin(), new_shapes.end() );
1116 m_points.insert( m_points.begin() + aStartIndex, newLine.m_points.begin(),
1117 newLine.m_points.end() );
1118 m_arcs.insert( m_arcs.end(), newLine.m_arcs.begin(), newLine.m_arcs.end() );
1119
1120 assert( m_shapes.size() == m_points.size() );
1121}
1122
1123
1124void SHAPE_LINE_CHAIN::Remove( int aStartIndex, int aEndIndex )
1125{
1126 wxCHECK( m_shapes.size() == m_points.size(), /*void*/ );
1127
1128 // Unwrap the chain first (correctly handling removing arc at
1129 // end of chain coincident with start)
1130 bool closedState = IsClosed();
1131 SetClosed( false );
1132
1133 if( aEndIndex < 0 )
1134 aEndIndex += PointCount();
1135
1136 if( aStartIndex < 0 )
1137 aStartIndex += PointCount();
1138
1139 if( aStartIndex >= PointCount() || aEndIndex >= PointCount() || aStartIndex > aEndIndex)
1140 {
1141 SetClosed( closedState );
1142 return;
1143 }
1144
1145
1146 // Split arcs, making arcs coincident
1147 if( !IsArcStart( aStartIndex ) && IsPtOnArc( aStartIndex ) )
1148 splitArc( aStartIndex, false );
1149
1150 if( IsSharedPt( aStartIndex ) ) // Don't delete the shared point
1151 aStartIndex += 1;
1152
1153 if( !IsArcEnd( aEndIndex ) && IsPtOnArc( aEndIndex ) && aEndIndex < PointCount() - 1 )
1154 splitArc( aEndIndex + 1, true );
1155
1156 if( IsSharedPt( aEndIndex ) ) // Don't delete the shared point
1157 aEndIndex -= 1;
1158
1159 if( aStartIndex > aEndIndex )
1160 {
1161 SetClosed( closedState );
1162 return;
1163 }
1164
1165 std::set<size_t> extra_arcs;
1166 auto logArcIdxRemoval = [&]( ssize_t& aShapeIndex )
1167 {
1168 if( aShapeIndex != SHAPE_IS_PT )
1169 extra_arcs.insert( aShapeIndex );
1170 };
1171
1172 // Remove any overlapping arcs in the point range
1173 for( int i = aStartIndex; i <= aEndIndex; i++ )
1174 {
1175 if( IsSharedPt( i ) )
1176 {
1177 if( i == aStartIndex )
1178 {
1179 logArcIdxRemoval( m_shapes[i].second ); // Only remove the arc on the second index
1180
1181 // Ensure that m_shapes has been built correctly.
1182 assert( i < aEndIndex || m_shapes[i + 1].first == m_shapes[i].second );
1183
1184 continue;
1185 }
1186 else if( i == aEndIndex )
1187 {
1188 logArcIdxRemoval( m_shapes[i].first ); // Only remove the arc on the first index
1189
1190 // Ensure that m_shapes has been built correctly.
1191 assert( i > aStartIndex || ( IsSharedPt( i - 1 )
1192 ? m_shapes[i - 1].second == m_shapes[i].first
1193 : m_shapes[i - 1].first == m_shapes[i].first ) );
1194 continue;
1195 }
1196 }
1197 else
1198 {
1199 alg::run_on_pair( m_shapes[i], logArcIdxRemoval );
1200 }
1201 }
1202
1203 for( auto arc : extra_arcs )
1204 convertArc( arc );
1205
1206 m_shapes.erase( m_shapes.begin() + aStartIndex, m_shapes.begin() + aEndIndex + 1 );
1207 m_points.erase( m_points.begin() + aStartIndex, m_points.begin() + aEndIndex + 1 );
1208 assert( m_shapes.size() == m_points.size() );
1209
1210 SetClosed( closedState );
1211}
1212
1213
1215{
1217
1218 if( IsClosed() && PointInside( aP ) && !aOutlineOnly )
1219 return 0;
1220
1221 for( size_t s = 0; s < GetSegmentCount(); s++ )
1222 d = std::min( d, GetSegment( s ).SquaredDistance( aP ) );
1223
1224 return d;
1225}
1226
1227
1228int SHAPE_LINE_CHAIN::Split( const VECTOR2I& aP, bool aExact )
1229{
1230 int ii = -1;
1231 int min_dist = 2;
1232
1233 int found_index = Find( aP );
1234
1235 if( found_index >= 0 && aExact )
1236 return found_index;
1237
1238 for( int s = 0; s < SegmentCount(); s++ )
1239 {
1240 const SEG seg = CSegment( s );
1241 int dist = seg.Distance( aP );
1242
1243 // make sure we are not producing a 'slightly concave' primitive. This might happen
1244 // if aP lies very close to one of already existing points.
1245 if( dist < min_dist && seg.A != aP && seg.B != aP )
1246 {
1247 min_dist = dist;
1248 if( found_index < 0 )
1249 ii = s;
1250 else if( s < found_index )
1251 ii = s;
1252 }
1253 }
1254
1255 if( ii < 0 )
1256 ii = found_index;
1257
1258 if( ii >= 0 )
1259 {
1260 // Don't create duplicate points
1261 if( GetPoint( ii ) == aP )
1262 return ii;
1263
1264 size_t newIndex = static_cast<size_t>( ii ) + 1;
1265
1266 if( IsArcSegment( ii ) )
1267 {
1268 m_points.insert( m_points.begin() + newIndex, aP );
1269 m_shapes.insert( m_shapes.begin() + newIndex, { ArcIndex( ii ), SHAPE_IS_PT } );
1270 splitArc( newIndex, true ); // Make the inserted point a shared point
1271 }
1272 else
1273 {
1274 Insert( newIndex, aP );
1275 }
1276
1277 return newIndex;
1278 }
1279
1280 return -1;
1281}
1282
1283
1284int SHAPE_LINE_CHAIN::Find( const VECTOR2I& aP, int aThreshold ) const
1285{
1286 for( int s = 0; s < PointCount(); s++ )
1287 {
1288 if( aThreshold == 0 )
1289 {
1290 if( CPoint( s ) == aP )
1291 return s;
1292 }
1293 else
1294 {
1295 if( (CPoint( s ) - aP).EuclideanNorm() <= aThreshold )
1296 return s;
1297 }
1298 }
1299
1300 return -1;
1301}
1302
1303
1304int SHAPE_LINE_CHAIN::FindSegment( const VECTOR2I& aP, int aThreshold ) const
1305{
1306 for( int s = 0; s < SegmentCount(); s++ )
1307 {
1308 if( CSegment( s ).Distance( aP ) <= aThreshold )
1309 return s;
1310 }
1311
1312 return -1;
1313}
1314
1315
1317{
1318 wxCHECK2_MSG( m_points.size() == m_shapes.size(), return 0, "Invalid chain!" );
1319
1320 if( m_points.size() < 2 )
1321 return 0;
1322
1323 int numShapes = 1;
1324
1325 for( int i = NextShape( 0 ); i != -1; i = NextShape( i ) )
1326 numShapes++;
1327
1328 return numShapes;
1329}
1330
1331
1333{
1334 if( aIndex < 0 )
1335 aIndex += SegmentCount();
1336
1337 wxCHECK( aIndex < SegmentCount() && aIndex >= 0,
1338 m_points.size() > 0 ? SEG( m_points.back(), m_points.back() ) : SEG( 0, 0, 0, 0 ) );
1339
1340 if( aIndex == (int) ( m_points.size() - 1 ) && m_closed )
1341 return SEG( m_points[aIndex], m_points[0], aIndex );
1342 else
1343 return SEG( m_points[aIndex], m_points[aIndex + 1], aIndex );
1344}
1345
1346
1347int SHAPE_LINE_CHAIN::NextShape( int aPointIndex ) const
1348{
1349 if( aPointIndex < 0 )
1350 aPointIndex += PointCount();
1351
1352 if( aPointIndex < 0 )
1353 return -1;
1354
1355 int lastIndex = PointCount() - 1;
1356
1357 // Last point?
1358 if( aPointIndex >= lastIndex )
1359 return -1; // we don't want to wrap around
1360
1361 if( m_shapes[aPointIndex] == SHAPES_ARE_PT )
1362 {
1363 if( aPointIndex == lastIndex - 1 )
1364 {
1365 if( m_closed )
1366 return lastIndex;
1367 else
1368 return -1;
1369 }
1370 else
1371 {
1372 return aPointIndex + 1;
1373 }
1374 }
1375
1376 int arcStart = aPointIndex;
1377
1378 // The second element should only get populated when the point is shared between two shapes.
1379 // If not a shared point, then the index should always go on the first element.
1380 wxCHECK2_MSG( m_shapes[aPointIndex].first != SHAPE_IS_PT, return -1, "malformed chain!" );
1381
1382 ssize_t currentArcIdx = ArcIndex( aPointIndex );
1383
1384 // Now skip the rest of the arc
1385 while( aPointIndex < lastIndex && ArcIndex( aPointIndex ) == currentArcIdx )
1386 aPointIndex += 1;
1387
1388 bool indexStillOnArc = alg::pair_contains( m_shapes[aPointIndex], currentArcIdx );
1389
1390 // We want the last vertex of the arc if the initial point was the start of one
1391 // Well-formed arcs should generate more than one point to travel above
1392 if( aPointIndex - arcStart > 1 && !indexStillOnArc )
1393 aPointIndex -= 1;
1394
1395 if( aPointIndex == lastIndex )
1396 {
1397 if( !m_closed || IsArcSegment( aPointIndex ) )
1398 return -1; //no shape
1399 else
1400 return lastIndex; // Segment between last point and the start of the chain
1401 }
1402
1403 return aPointIndex;
1404}
1405
1406
1407void SHAPE_LINE_CHAIN::SetPoint( int aIndex, const VECTOR2I& aPos )
1408{
1409 if( aIndex < 0 )
1410 aIndex += PointCount();
1411 else if( aIndex >= PointCount() )
1412 aIndex -= PointCount();
1413
1414 m_points[aIndex] = aPos;
1415
1416 alg::run_on_pair( m_shapes[aIndex],
1417 [&]( ssize_t& aIdx )
1418 {
1419 if( aIdx != SHAPE_IS_PT )
1420 convertArc( aIdx );
1421 } );
1422}
1423
1424
1425void SHAPE_LINE_CHAIN::RemoveShape( int aPointIndex )
1426{
1427 if( aPointIndex < 0 )
1428 aPointIndex += PointCount();
1429
1430 if( aPointIndex >= PointCount() || aPointIndex < 0 )
1431 return; // Invalid index, fail gracefully
1432
1433 if( m_shapes[aPointIndex] == SHAPES_ARE_PT )
1434 {
1435 Remove( aPointIndex );
1436 return;
1437 }
1438
1439 int start = aPointIndex;
1440 int end = aPointIndex;
1441 int arcIdx = ArcIndex( aPointIndex );
1442
1443 if( !IsArcStart( start ) )
1444 {
1445 // aPointIndex is not a shared point, so iterate backwards to find the start of the arc
1446 while( start > 0 && ArcIndex( static_cast<ssize_t>( start ) - 1 ) == arcIdx )
1447 start--;
1448 }
1449
1450 if( !IsArcEnd( end ) || start == end )
1451 end = NextShape( end ); // can be -1 to indicate end of chain
1452
1453 Remove( start, end );
1454}
1455
1456
1457const SHAPE_LINE_CHAIN SHAPE_LINE_CHAIN::Slice( int aStartIndex, int aEndIndex ) const
1458{
1460
1461 if( aEndIndex < 0 )
1462 aEndIndex += PointCount();
1463
1464 if( aStartIndex < 0 )
1465 aStartIndex += PointCount();
1466
1467 // Bad programmer checks
1468 wxCHECK( aStartIndex >= 0, SHAPE_LINE_CHAIN() );
1469 wxCHECK( aEndIndex >= 0, SHAPE_LINE_CHAIN() );
1470 wxCHECK( aStartIndex < PointCount(), SHAPE_LINE_CHAIN() );
1471 wxCHECK( aEndIndex < PointCount(), SHAPE_LINE_CHAIN() );
1472 wxCHECK( aEndIndex >= aStartIndex, SHAPE_LINE_CHAIN() );
1473
1474 int numPoints = static_cast<int>( m_points.size() );
1475
1476 if( IsArcSegment( aStartIndex ) && !IsArcStart( aStartIndex ) )
1477 {
1478 // Cutting in middle of an arc, lets split it
1479 ssize_t arcToSplitIndex = ArcIndex( aStartIndex );
1480 const SHAPE_ARC& arcToSplit = Arc( arcToSplitIndex );
1481
1482 // Copy the points as arc points
1483 for( size_t i = aStartIndex; i < m_points.size() && arcToSplitIndex == ArcIndex( i ); i++ )
1484 {
1485 rv.m_points.push_back( m_points[i] );
1486 rv.m_shapes.push_back( { rv.m_arcs.size(), SHAPE_IS_PT } );
1487 rv.m_bbox.Merge( m_points[i] );
1488 }
1489
1490 // Create a new arc from the existing one, with different start point.
1491 SHAPE_ARC newArc;
1492
1493 VECTOR2I newArcStart = m_points[aStartIndex];
1494
1495 newArc.ConstructFromStartEndCenter( newArcStart, arcToSplit.GetP1(),
1496 arcToSplit.GetCenter(),
1497 arcToSplit.IsClockwise() );
1498
1499
1500 rv.m_arcs.push_back( newArc );
1501
1502 aStartIndex += rv.PointCount();
1503 }
1504
1505 for( int i = aStartIndex; i <= aEndIndex && i < numPoints; i = NextShape( i ) )
1506 {
1507 if( i == -1 )
1508 return rv; // NextShape reached the end
1509
1510 int nextShape = NextShape( i );
1511 bool isLastShape = nextShape < 0;
1512
1513 if( IsArcStart( i ) )
1514 {
1515 if( ( isLastShape && aEndIndex != ( numPoints - 1 ) )
1516 || ( nextShape > aEndIndex ) )
1517 {
1518 if( i == aEndIndex )
1519 {
1520 // Single point on an arc, just append the single point
1521 rv.Append( m_points[i] );
1522 return rv;
1523 }
1524
1525 // Cutting in middle of an arc, lets split it
1526 ssize_t arcIndex = ArcIndex( i );
1527 const SHAPE_ARC& currentArc = Arc( arcIndex );
1528
1529 // Copy the points as arc points
1530 for( ; i <= aEndIndex && i < numPoints; i++ )
1531 {
1532 if( arcIndex != ArcIndex( i ) )
1533 break;
1534
1535 rv.m_points.push_back( m_points[i] );
1536 rv.m_shapes.push_back( { rv.m_arcs.size(), SHAPE_IS_PT } );
1537 rv.m_bbox.Merge( m_points[i] );
1538 }
1539
1540 // Create a new arc from the existing one, with different end point.
1541 SHAPE_ARC newArc;
1542
1543 VECTOR2I newArcEnd = m_points[aEndIndex];
1544
1545 newArc.ConstructFromStartEndCenter( currentArc.GetP0(), newArcEnd,
1546 currentArc.GetCenter(),
1547 currentArc.IsClockwise() );
1548
1549
1550 rv.m_arcs.push_back( newArc );
1551
1552 return rv;
1553 }
1554 else
1555 {
1556 // append the whole arc
1557 const SHAPE_ARC& currentArc = Arc( ArcIndex( i ) );
1558 rv.Append( currentArc );
1559 }
1560
1561 if( isLastShape )
1562 return rv;
1563 }
1564 else
1565 {
1566 wxASSERT_MSG( !IsArcSegment( i ),
1567 wxT( "Still on an arc segment, we missed something..." ) );
1568
1569 if( i == aStartIndex )
1570 rv.Append( m_points[i] );
1571
1572 bool nextPointIsArc = isLastShape ? false : IsArcSegment( nextShape );
1573
1574 if( !nextPointIsArc && i < SegmentCount() && i < aEndIndex )
1575 rv.Append( GetSegment( i ).B );
1576 }
1577
1578 }
1579
1580 wxASSERT( rv.m_points.size() == rv.m_shapes.size() );
1581
1582 return rv;
1583}
1584
1585
1587{
1588 assert( m_shapes.size() == m_points.size() );
1589
1590 if( aOtherLine.PointCount() == 0 )
1591 {
1592 return;
1593 }
1594
1595 size_t num_arcs = m_arcs.size();
1596 m_arcs.insert( m_arcs.end(), aOtherLine.m_arcs.begin(), aOtherLine.m_arcs.end() );
1597
1598 auto fixShapeIndices =
1599 [&]( const std::pair<ssize_t, ssize_t>& aShapeIndices ) -> std::pair<ssize_t, ssize_t>
1600 {
1601 std::pair<ssize_t, ssize_t> retval = aShapeIndices;
1602
1603 alg::run_on_pair( retval, [&]( ssize_t& aIndex )
1604 {
1605 if( aIndex != SHAPE_IS_PT )
1606 aIndex = aIndex + num_arcs;
1607 } );
1608
1609 return retval;
1610 };
1611
1612 if( PointCount() == 0 || aOtherLine.CPoint( 0 ) != CPoint( -1 ) )
1613 {
1614 const VECTOR2I p = aOtherLine.CPoint( 0 );
1615 m_points.push_back( p );
1616 m_shapes.push_back( fixShapeIndices( aOtherLine.CShapes()[0] ) );
1617 m_bbox.Merge( p );
1618 }
1619 else if( aOtherLine.IsArcSegment( 0 ) )
1620 {
1621 // Associate the new arc shape with the last point of this chain
1622 if( m_shapes.back() == SHAPES_ARE_PT )
1623 m_shapes.back().first = aOtherLine.CShapes()[0].first + num_arcs;
1624 else
1625 m_shapes.back().second = aOtherLine.CShapes()[0].first + num_arcs;
1626 }
1627
1628
1629 for( int i = 1; i < aOtherLine.PointCount(); i++ )
1630 {
1631 const VECTOR2I p = aOtherLine.CPoint( i );
1632 m_points.push_back( p );
1633
1634 ssize_t arcIndex = aOtherLine.ArcIndex( i );
1635
1636 if( arcIndex != ssize_t( SHAPE_IS_PT ) )
1637 {
1638 m_shapes.push_back( fixShapeIndices( aOtherLine.m_shapes[i] ) );
1639 }
1640 else
1641 m_shapes.push_back( SHAPES_ARE_PT );
1642
1643 m_bbox.Merge( p );
1644 }
1645
1647
1648 assert( m_shapes.size() == m_points.size() );
1649}
1650
1651
1653{
1655}
1656
1657
1658void SHAPE_LINE_CHAIN::Append( const SHAPE_ARC& aArc, double aAccuracy )
1659{
1660 SHAPE_LINE_CHAIN chain = aArc.ConvertToPolyline( aAccuracy );
1661
1662 if( chain.PointCount() > 2 )
1663 {
1664 chain.m_arcs.push_back( aArc );
1665 chain.m_arcs.back().SetWidth( 0 );
1666
1667 for( auto& sh : chain.m_shapes )
1668 sh.first = 0;
1669 }
1670
1671 Append( chain );
1672
1673 assert( m_shapes.size() == m_points.size() );
1674}
1675
1676
1677void SHAPE_LINE_CHAIN::Insert( size_t aVertex, const VECTOR2I& aP )
1678{
1679 if( aVertex == m_points.size() )
1680 {
1681 Append( aP );
1682 return;
1683 }
1684
1685 wxCHECK( aVertex < m_points.size(), /* void */ );
1686
1687 if( aVertex > 0 && IsPtOnArc( aVertex ) )
1688 splitArc( aVertex );
1689
1690 //@todo need to check we aren't creating duplicate points
1691 m_points.insert( m_points.begin() + aVertex, aP );
1692 m_shapes.insert( m_shapes.begin() + aVertex, SHAPES_ARE_PT );
1693
1694 assert( m_shapes.size() == m_points.size() );
1695}
1696
1697
1698void SHAPE_LINE_CHAIN::Insert( size_t aVertex, const SHAPE_ARC& aArc )
1699{
1700 wxCHECK( aVertex < m_points.size(), /* void */ );
1701
1702 if( aVertex > 0 && IsPtOnArc( aVertex ) )
1703 splitArc( aVertex );
1704
1706 ssize_t arc_pos = m_arcs.size();
1707
1708 for( auto arc_it = m_shapes.rbegin() ;
1709 arc_it != m_shapes.rend() + aVertex;
1710 arc_it++ )
1711 {
1712 if( *arc_it != SHAPES_ARE_PT )
1713 {
1714 arc_pos = std::max( ( *arc_it ).first, ( *arc_it ).second );
1715 arc_pos++;
1716 }
1717 }
1718
1719 //Increment all arc indices before inserting the new arc
1720 for( auto& sh : m_shapes )
1721 {
1722 alg::run_on_pair( sh,
1723 [&]( ssize_t& aIndex )
1724 {
1725 if( aIndex >= arc_pos )
1726 aIndex++;
1727 } );
1728 }
1729
1730 SHAPE_ARC arcCopy( aArc );
1731 arcCopy.SetWidth( 0 );
1732 m_arcs.insert( m_arcs.begin() + arc_pos, arcCopy );
1733
1735 //@todo need to check we aren't creating duplicate points at start or end
1736 auto& chain = aArc.ConvertToPolyline();
1737 m_points.insert( m_points.begin() + aVertex, chain.CPoints().begin(), chain.CPoints().end() );
1738
1740 //@todo need to check we aren't creating duplicate points at start or end
1741 std::vector<std::pair<ssize_t, ssize_t>> new_points( chain.PointCount(),
1742 { arc_pos, SHAPE_IS_PT } );
1743
1744 m_shapes.insert( m_shapes.begin() + aVertex, new_points.begin(), new_points.end() );
1745 assert( m_shapes.size() == m_points.size() );
1746}
1747
1748
1750{
1752 m_origin( aOrigin ) {};
1753
1755 const SHAPE_LINE_CHAIN::INTERSECTION& aB ) const
1756 {
1757 return ( m_origin - aA.p ).EuclideanNorm() < ( m_origin - aB.p ).EuclideanNorm();
1758 }
1759
1761};
1762
1763
1764int SHAPE_LINE_CHAIN::Intersect( const SEG& aSeg, INTERSECTIONS& aIp ) const
1765{
1766 for( int s = 0; s < SegmentCount(); s++ )
1767 {
1768 OPT_VECTOR2I p = CSegment( s ).Intersect( aSeg );
1769
1770 if( p )
1771 {
1772 INTERSECTION is;
1773 is.valid = true;
1774 is.index_our = s;
1775 is.index_their = -1;
1776 is.is_corner_our = is.is_corner_their = false;
1777 is.p = *p;
1778 aIp.push_back( is );
1779 }
1780 }
1781
1782 compareOriginDistance comp( aSeg.A );
1783 sort( aIp.begin(), aIp.end(), comp );
1784
1785 return aIp.size();
1786}
1787
1788
1789static inline void addIntersection( SHAPE_LINE_CHAIN::INTERSECTIONS& aIps, int aPc,
1791{
1792 if( aIps.size() == 0 )
1793 {
1794 aIps.push_back( aP );
1795 return;
1796 }
1797
1798 aIps.push_back( aP );
1799}
1800
1801
1803 bool aExcludeColinearAndTouching, BOX2I* aChainBBox ) const
1804{
1805 BOX2I bb_other = aChainBBox ? *aChainBBox : aChain.BBox();
1806
1807 for( int s1 = 0; s1 < SegmentCount(); s1++ )
1808 {
1809 const SEG& a = CSegment( s1 );
1810 const BOX2I bb_cur( a.A, a.B - a.A );
1811
1812 if( !bb_other.Intersects( bb_cur ) )
1813 continue;
1814
1815 for( int s2 = 0; s2 < aChain.SegmentCount(); s2++ )
1816 {
1817 const SEG& b = aChain.CSegment( s2 );
1818 INTERSECTION is;
1819
1820 is.index_our = s1;
1821 is.index_their = s2;
1822 is.is_corner_our = false;
1823 is.is_corner_their = false;
1824 is.valid = true;
1825
1826 OPT_VECTOR2I p = a.Intersect( b );
1827
1828 bool coll = a.Collinear( b );
1829
1830 if( coll && ! aExcludeColinearAndTouching )
1831 {
1832 if( a.Contains( b.A ) )
1833 {
1834 is.p = b.A;
1835 is.is_corner_their = true;
1836 addIntersection(aIp, PointCount(), is);
1837 }
1838
1839 if( a.Contains( b.B ) )
1840 {
1841 is.p = b.B;
1842 is.index_their++;
1843 is.is_corner_their = true;
1844 addIntersection( aIp, PointCount(), is );
1845 }
1846
1847 if( b.Contains( a.A ) )
1848 {
1849 is.p = a.A;
1850 is.is_corner_our = true;
1851 addIntersection( aIp, PointCount(), is );
1852 }
1853
1854 if( b.Contains( a.B ) )
1855 {
1856 is.p = a.B;
1857 is.index_our++;
1858 is.is_corner_our = true;
1859 addIntersection( aIp, PointCount(), is );
1860 }
1861 }
1862 else if( p )
1863 {
1864 is.p = *p;
1865 is.is_corner_our = false;
1866 is.is_corner_their = false;
1867
1868 if( p == a.A )
1869 {
1870 is.is_corner_our = true;
1871 }
1872
1873 if( p == a.B )
1874 {
1875 is.is_corner_our = true;
1876 is.index_our++;
1877 }
1878
1879 if( p == b.A )
1880 {
1881 is.is_corner_their = true;
1882 }
1883
1884 if( p == b.B )
1885 {
1886 is.is_corner_their = true;
1887 is.index_their++;
1888 }
1889
1890 addIntersection( aIp, PointCount(), is );
1891 }
1892 }
1893 }
1894
1895 return aIp.size();
1896}
1897
1898
1899int SHAPE_LINE_CHAIN::PathLength( const VECTOR2I& aP, int aIndex ) const
1900{
1901 int sum = 0;
1902
1903 for( int i = 0; i < SegmentCount(); i++ )
1904 {
1905 const SEG seg = CSegment( i );
1906 bool indexMatch = true;
1907
1908 if( aIndex >= 0 )
1909 {
1910 if( aIndex == SegmentCount() )
1911 {
1912 indexMatch = ( i == SegmentCount() - 1 );
1913 }
1914 else
1915 {
1916 indexMatch = ( i == aIndex );
1917 }
1918 }
1919
1920 if( indexMatch )
1921 {
1922 sum += ( aP - seg.A ).EuclideanNorm();
1923 return sum;
1924 }
1925 else
1926 sum += seg.Length();
1927 }
1928
1929 return -1;
1930}
1931
1932
1933bool SHAPE_LINE_CHAIN_BASE::PointInside( const VECTOR2I& aPt, int aAccuracy,
1934 bool aUseBBoxCache ) const
1935{
1936 /*
1937 * Don't check the bounding box unless it's cached. Building it is about the same speed as
1938 * the rigorous test below and so just slows things down by doing potentially two tests.
1939 */
1940 if( aUseBBoxCache && GetCachedBBox() && !GetCachedBBox()->Contains( aPt ) )
1941 return false;
1942
1943 if( !IsClosed() || GetPointCount() < 3 )
1944 return false;
1945
1946 /*
1947 * To check for interior points, we draw a line in the positive x direction from
1948 * the point. If it intersects an even number of segments, the point is outside the
1949 * line chain (it had to first enter and then exit). Otherwise, it is inside the chain.
1950 *
1951 * Note: slope might be denormal here in the case of a horizontal line but we require our
1952 * y to move from above to below the point (or vice versa)
1953 *
1954 * Note: we open-code CPoint() here so that we don't end up calculating the size of the
1955 * vector number-of-points times. This has a non-trivial impact on zone fill times.
1956 */
1957 int pointCount = GetPointCount();
1958 bool inside = false;
1959
1960 for( int i = 0; i < pointCount; )
1961 {
1962 const VECTOR2I p1 = GetPoint( i++ );
1963 const VECTOR2I p2 = GetPoint( i == pointCount ? 0 : i );
1964 const VECTOR2I diff = p2 - p1;
1965
1966 if( diff.y == 0 )
1967 continue;
1968
1969 const int d = rescale( diff.x, ( aPt.y - p1.y ), diff.y );
1970
1971 if( ( ( p1.y >= aPt.y ) != ( p2.y >= aPt.y ) ) && ( aPt.x - p1.x < d ) )
1972 inside = !inside;
1973 }
1974
1975 // If accuracy is <= 1 (nm) then we skip the accuracy test for performance. Otherwise
1976 // we use "OnEdge(accuracy)" as a proxy for "Inside(accuracy)".
1977 if( aAccuracy <= 1 )
1978 return inside;
1979 else
1980 return inside || PointOnEdge( aPt, aAccuracy );
1981}
1982
1983
1984bool SHAPE_LINE_CHAIN_BASE::PointOnEdge( const VECTOR2I& aPt, int aAccuracy ) const
1985{
1986 return EdgeContainingPoint( aPt, aAccuracy ) >= 0;
1987}
1988
1989
1990int SHAPE_LINE_CHAIN_BASE::EdgeContainingPoint( const VECTOR2I& aPt, int aAccuracy ) const
1991{
1992 if( !GetPointCount() )
1993 {
1994 return -1;
1995 }
1996 else if( GetPointCount() == 1 )
1997 {
1998 VECTOR2I dist = GetPoint(0) - aPt;
1999 return ( hypot( dist.x, dist.y ) <= aAccuracy + 1 ) ? 0 : -1;
2000 }
2001
2002 for( size_t i = 0; i < GetSegmentCount(); i++ )
2003 {
2004 const SEG s = GetSegment( i );
2005
2006 if( s.A == aPt || s.B == aPt )
2007 return i;
2008
2009 if( s.Distance( aPt ) <= aAccuracy + 1 )
2010 return i;
2011 }
2012
2013 return -1;
2014}
2015
2016
2017bool SHAPE_LINE_CHAIN::CheckClearance( const VECTOR2I& aP, const int aDist ) const
2018{
2019 if( !PointCount() )
2020 return false;
2021 else if( PointCount() == 1 )
2022 return m_points[0] == aP;
2023
2024 for( int i = 0; i < SegmentCount(); i++ )
2025 {
2026 const SEG s = CSegment( i );
2027
2028 if( s.A == aP || s.B == aP )
2029 return true;
2030
2031 if( s.Distance( aP ) <= aDist )
2032 return true;
2033 }
2034
2035 return false;
2036}
2037
2038
2039const std::optional<SHAPE_LINE_CHAIN::INTERSECTION> SHAPE_LINE_CHAIN::SelfIntersecting() const
2040{
2041 for( int s1 = 0; s1 < SegmentCount(); s1++ )
2042 {
2043 for( int s2 = s1 + 1; s2 < SegmentCount(); s2++ )
2044 {
2045 const VECTOR2I s2a = CSegment( s2 ).A, s2b = CSegment( s2 ).B;
2046
2047 if( s1 + 1 != s2 && CSegment( s1 ).Contains( s2a ) )
2048 {
2049 INTERSECTION is;
2050 is.index_our = s1;
2051 is.index_their = s2;
2052 is.p = s2a;
2053 return is;
2054 }
2055 else if( CSegment( s1 ).Contains( s2b ) &&
2056 // for closed polylines, the ending point of the
2057 // last segment == starting point of the first segment
2058 // this is a normal case, not self intersecting case
2059 !( IsClosed() && s1 == 0 && s2 == SegmentCount()-1 ) )
2060 {
2061 INTERSECTION is;
2062 is.index_our = s1;
2063 is.index_their = s2;
2064 is.p = s2b;
2065 return is;
2066 }
2067 else
2068 {
2069 OPT_VECTOR2I p = CSegment( s1 ).Intersect( CSegment( s2 ), true );
2070
2071 if( p )
2072 {
2073 INTERSECTION is;
2074 is.index_our = s1;
2075 is.index_their = s2;
2076 is.p = *p;
2077 return is;
2078 }
2079 }
2080 }
2081 }
2082
2083 return std::optional<SHAPE_LINE_CHAIN::INTERSECTION>();
2084}
2085
2086
2088{
2089 SHAPE_KEY( int aFirstIdx, int aArcIdx, const BOX2I_MINMAX& aBBox ) :
2090 m_FirstIdx( aFirstIdx ), m_ArcIdx( aArcIdx ), m_BBox( aBBox )
2091 {
2092 }
2093
2097};
2098
2099
2100const std::optional<SHAPE_LINE_CHAIN::INTERSECTION>
2102{
2103 auto pointsClose = []( const VECTOR2I& aPt1, const VECTOR2I& aPt2 ) -> bool
2104 {
2105 return ( VECTOR2D( aPt1 ) - aPt2 ).SquaredEuclideanNorm() <= 2.0;
2106 };
2107
2108 auto collideArcSeg = [&pointsClose]( const SHAPE_ARC& aArc, const SEG& aSeg, int aClearance = 0,
2109 VECTOR2I* aLocation = nullptr )
2110 {
2111 VECTOR2I center = aArc.GetCenter();
2112 CIRCLE circle( center, aArc.GetRadius() );
2113
2114 std::vector<VECTOR2I> candidatePts = circle.Intersect( aSeg );
2115
2116 for( const VECTOR2I& candidate : candidatePts )
2117 {
2118 // Skip shared points
2119 if( aArc.GetP1() == aSeg.A && pointsClose( candidate, aSeg.A ) )
2120 continue;
2121
2122 if( aSeg.B == aArc.GetP0() && pointsClose( candidate, aSeg.B ) )
2123 continue;
2124
2125 bool collides = aArc.Collide( candidate, aClearance, nullptr, aLocation );
2126
2127 if( collides )
2128 return true;
2129 }
2130
2131 return false;
2132 };
2133
2134 auto collideArcArc = [&pointsClose]( const SHAPE_ARC& aArc1, const SHAPE_ARC& aArc2,
2135 VECTOR2I* aLocation = nullptr )
2136 {
2137 std::vector<VECTOR2I> candidatePts;
2138
2139 aArc1.Intersect( aArc2, &candidatePts );
2140
2141 for( const VECTOR2I& candidate : candidatePts )
2142 {
2143 // Skip shared points
2144 if( aArc1.GetP1() == aArc2.GetP0() && pointsClose( candidate, aArc1.GetP1() ) )
2145 continue;
2146
2147 if( aArc2.GetP1() == aArc1.GetP0() && pointsClose( candidate, aArc2.GetP1() ) )
2148 continue;
2149
2150 if( aLocation )
2151 *aLocation = candidate;
2152
2153 return true;
2154 }
2155
2156 return false;
2157 };
2158
2159 auto collideSegSeg = [this]( int s1, int s2, INTERSECTION& is )
2160 {
2161 SEG seg1 = CSegment( s1 );
2162 SEG seg2 = CSegment( s2 );
2163
2164 const VECTOR2I s2a = seg2.A, s2b = seg2.B;
2165
2166 if( s1 + 1 != s2 && seg1.Contains( s2a ) )
2167 {
2168 is.index_our = s1;
2169 is.index_their = s2;
2170 is.p = s2a;
2171 return true;
2172 }
2173 else if( seg1.Contains( s2b ) &&
2174 // for closed polylines, the ending point of the
2175 // last segment == starting point of the first segment
2176 // this is a normal case, not self intersecting case
2177 !( IsClosed() && s1 == 0 && s2 == SegmentCount() - 1 ) )
2178 {
2179 is.index_our = s1;
2180 is.index_their = s2;
2181 is.p = s2b;
2182 return true;
2183 }
2184 else
2185 {
2186 OPT_VECTOR2I p = seg1.Intersect( seg2, true );
2187
2188 if( p )
2189 {
2190 is.index_our = s1;
2191 is.index_their = s2;
2192 is.p = *p;
2193 return true;
2194 }
2195 }
2196
2197 return false;
2198 };
2199
2200 INTERSECTION is;
2201
2202 std::vector<SHAPE_KEY> shapeCache;
2203 for( int si = 0; si != -1; si = NextShape( si ) )
2204 {
2205 int arci = ArcIndex( si );
2206
2207 shapeCache.emplace_back( si, arci,
2208 arci == -1 ? BOX2I_MINMAX( CSegment( si ) )
2209 : BOX2I_MINMAX( Arc( arci ) ) );
2210 }
2211
2212 for( size_t sk1 = 0; sk1 < shapeCache.size(); sk1++ )
2213 {
2214 for( size_t sk2 = sk1 + 1; sk2 < shapeCache.size(); sk2++ )
2215 {
2216 VECTOR2I loc;
2217 const SHAPE_KEY& k1 = shapeCache[sk1];
2218 const SHAPE_KEY& k2 = shapeCache[sk2];
2219
2220 if( !k1.m_BBox.Intersects( k2.m_BBox ) )
2221 continue;
2222
2223 if( k1.m_ArcIdx == -1 && k2.m_ArcIdx == -1 )
2224 {
2225 if( collideSegSeg( k1.m_FirstIdx, k2.m_FirstIdx, is ) )
2226 {
2227 return is;
2228 }
2229 }
2230 else if( k1.m_ArcIdx != -1 && k2.m_ArcIdx == -1 )
2231 {
2232 if( collideArcSeg( Arc( k1.m_ArcIdx ), CSegment( k2.m_FirstIdx ), 0, &loc ) )
2233 {
2234 is.index_our = k1.m_FirstIdx;
2235 is.index_their = k2.m_FirstIdx;
2236 is.p = loc;
2237 return is;
2238 }
2239 }
2240 else if( k1.m_ArcIdx == -1 && k2.m_ArcIdx != -1 )
2241 {
2242 if( collideArcSeg( Arc( k2.m_ArcIdx ), CSegment( k1.m_FirstIdx ), 0, &loc ) )
2243 {
2244 is.index_our = k1.m_FirstIdx;
2245 is.index_their = k2.m_FirstIdx;
2246 is.p = loc;
2247 return is;
2248 }
2249 }
2250 else if( k1.m_ArcIdx != -1 && k2.m_ArcIdx != -1 )
2251 {
2252 if( collideArcArc( Arc( k1.m_ArcIdx ), Arc( k2.m_ArcIdx ), &loc ) )
2253 {
2254 is.index_our = k1.m_FirstIdx;
2255 is.index_their = k2.m_FirstIdx;
2256 is.p = loc;
2257 return is;
2258 }
2259 }
2260 }
2261 }
2262
2263 return std::optional<SHAPE_LINE_CHAIN::INTERSECTION>();
2264}
2265
2266
2268 bool aAllowInternalShapePoints ) const
2269{
2270 if( PointCount() == 0 )
2271 {
2272 // The only right answer here is "don't crash".
2273 return { 0, 0 };
2274 }
2275
2276 int min_d = std::numeric_limits<int>::max();
2277 int nearest = 0;
2278
2279 for( int i = 0; i < SegmentCount(); i++ )
2280 {
2281 int d = CSegment( i ).Distance( aP );
2282
2283 if( d < min_d )
2284 {
2285 min_d = d;
2286 nearest = i;
2287 }
2288 }
2289
2290 if( !aAllowInternalShapePoints )
2291 {
2292 //Snap to arc end points if the closest found segment is part of an arc segment
2293 if( nearest > 0 && nearest < PointCount() && IsArcSegment( nearest ) )
2294 {
2295 VECTOR2I ptToSegStart = CSegment( nearest ).A - aP;
2296 VECTOR2I ptToSegEnd = CSegment( nearest ).B - aP;
2297
2298 if( ptToSegStart.EuclideanNorm() > ptToSegEnd.EuclideanNorm() )
2299 nearest++;
2300
2301 // Is this the start or end of an arc? If so, return it directly
2302 if( IsArcStart( nearest ) || IsArcEnd( nearest ) )
2303 {
2304 return m_points[nearest];
2305 }
2306 else
2307 {
2308 const SHAPE_ARC& nearestArc = Arc( ArcIndex( nearest ) );
2309 VECTOR2I ptToArcStart = nearestArc.GetP0() - aP;
2310 VECTOR2I ptToArcEnd = nearestArc.GetP1() - aP;
2311
2312 if( ptToArcStart.EuclideanNorm() > ptToArcEnd.EuclideanNorm() )
2313 return nearestArc.GetP1();
2314 else
2315 return nearestArc.GetP0();
2316 }
2317
2318 }
2319 }
2320
2321 return CSegment( nearest ).NearestPoint( aP );
2322}
2323
2324
2325const VECTOR2I SHAPE_LINE_CHAIN::NearestPoint( const SEG& aSeg, int& dist ) const
2326{
2327 if( PointCount() == 0 )
2328 {
2329 // The only right answer here is "don't crash".
2330 return { 0, 0 };
2331 }
2332
2333 int nearest = 0;
2334
2335 dist = std::numeric_limits<int>::max();
2336
2337 for( int i = 0; i < PointCount(); i++ )
2338 {
2339 int d = aSeg.LineDistance( CPoint( i ) );
2340
2341 if( d < dist )
2342 {
2343 dist = d;
2344 nearest = i;
2345 }
2346 }
2347
2348 return CPoint( nearest );
2349}
2350
2351
2353{
2354 int min_d = std::numeric_limits<int>::max();
2355 int nearest = 0;
2356
2357 for( int i = 0; i < SegmentCount(); i++ )
2358 {
2359 int d = CSegment( i ).Distance( aP );
2360
2361 if( d < min_d )
2362 {
2363 min_d = d;
2364 nearest = i;
2365 }
2366 }
2367
2368 return nearest;
2369}
2370
2371
2372const std::string SHAPE_LINE_CHAIN::Format( bool aCplusPlus ) const
2373{
2374 std::stringstream ss;
2375
2376 ss << "SHAPE_LINE_CHAIN( { ";
2377
2378 for( int i = 0; i < PointCount(); i++ )
2379 {
2380 ss << "VECTOR2I( " << m_points[i].x << ", " << m_points[i].y << ")";
2381
2382 if( i != PointCount() -1 )
2383 ss << ", ";
2384 }
2385
2386 ss << "}, " << ( m_closed ? "true" : "false" );
2387 ss << " );";
2388
2389 return ss.str();
2390
2391 /* fixme: arcs
2392 for( size_t i = 0; i < m_arcs.size(); i++ )
2393 ss << m_arcs[i].GetCenter().x << " " << m_arcs[i].GetCenter().y << " "
2394 << m_arcs[i].GetP0().x << " " << m_arcs[i].GetP0().y << " "
2395 << m_arcs[i].GetCentralAngle().AsDegrees();
2396
2397 return ss.str();*/
2398}
2399
2400
2402{
2403 SHAPE_LINE_CHAIN a(*this), b( aOther );
2404 a.Simplify();
2405 b.Simplify();
2406
2407 if( a.m_points.size() != b.m_points.size() )
2408 return false;
2409
2410 for( int i = 0; i < a.PointCount(); i++ )
2411 {
2412 if( a.CPoint( i ) != b.CPoint( i ) )
2413 return false;
2414 }
2415
2416 return true;
2417}
2418
2419
2421{
2423 return Intersect( aChain, dummy ) != 0;
2424}
2425
2426
2428{
2429 return new SHAPE_LINE_CHAIN( *this );
2430}
2431
2432
2433bool SHAPE_LINE_CHAIN::Parse( std::stringstream& aStream )
2434{
2435 size_t n_pts;
2436 size_t n_arcs;
2437
2438 m_points.clear();
2439 aStream >> n_pts;
2440
2441 // Rough sanity check, just make sure the loop bounds aren't absolutely outlandish
2442 if( n_pts > aStream.str().size() )
2443 return false;
2444
2445 aStream >> m_closed;
2446 aStream >> n_arcs;
2447
2448 if( n_arcs > aStream.str().size() )
2449 return false;
2450
2451 for( size_t i = 0; i < n_pts; i++ )
2452 {
2453 int x, y;
2454 ssize_t ind;
2455 aStream >> x;
2456 aStream >> y;
2457 m_points.emplace_back( x, y );
2458
2459 aStream >> ind;
2460 m_shapes.emplace_back( std::make_pair( ind, SHAPE_IS_PT ) );
2461 }
2462
2463 for( size_t i = 0; i < n_arcs; i++ )
2464 {
2465 VECTOR2I p0, pc;
2466 double angle;
2467
2468 aStream >> pc.x;
2469 aStream >> pc.y;
2470 aStream >> p0.x;
2471 aStream >> p0.y;
2472 aStream >> angle;
2473
2474 m_arcs.emplace_back( pc, p0, EDA_ANGLE( angle, DEGREES_T ) );
2475 }
2476
2477 return true;
2478}
2479
2480
2481const VECTOR2I SHAPE_LINE_CHAIN::PointAlong( int aPathLength ) const
2482{
2483 int total = 0;
2484
2485 if( aPathLength == 0 )
2486 return CPoint( 0 );
2487
2488 for( int i = 0; i < SegmentCount(); i++ )
2489 {
2490 const SEG& s = CSegment( i );
2491 int l = s.Length();
2492
2493 if( total + l >= aPathLength )
2494 {
2495 VECTOR2I d( s.B - s.A );
2496 return s.A + d.Resize( aPathLength - total );
2497 }
2498
2499 total += l;
2500 }
2501
2502 return CPoint( -1 );
2503}
2504
2505
2506double SHAPE_LINE_CHAIN::Area( bool aAbsolute ) const
2507{
2508 // see https://www.mathopenref.com/coordpolygonarea2.html
2509
2510 if( !m_closed )
2511 return 0.0;
2512
2513 double area = 0.0;
2514 int size = m_points.size();
2515
2516 for( int i = 0, j = size - 1; i < size; ++i )
2517 {
2518 area += ( (double) m_points[j].x + m_points[i].x ) *
2519 ( (double) m_points[j].y - m_points[i].y );
2520 j = i;
2521 }
2522
2523 if( aAbsolute )
2524 return std::fabs( area * 0.5 ); // The result would be negative if points are anti-clockwise
2525 else
2526 return -area * 0.5; // The result would be negative if points are anti-clockwise
2527}
2528
2529
2531{
2532 std::vector<VECTOR2I> pts_unique;
2533 std::vector<std::pair<ssize_t, ssize_t>> shapes_unique;
2534
2535 // Always try to keep at least 2 points otherwise, we're not really a line
2536 if( PointCount() < 3 )
2537 {
2538 return;
2539 }
2540 else if( PointCount() == 3 )
2541 {
2542 if( m_points[0] == m_points[1] )
2543 Remove( 1 );
2544
2545 return;
2546 }
2547
2548 int i = 0;
2549
2550 while( i < PointCount() )
2551 {
2552 int j = i + 1;
2553
2554 // We can eliminate duplicate vertices as long as they are part of the same shape, OR if
2555 // one of them is part of a shape and one is not.
2556 while( j < PointCount() && m_points[i] == m_points[j] &&
2557 ( m_shapes[i] == m_shapes[j] ||
2558 m_shapes[i] == SHAPES_ARE_PT ||
2559 m_shapes[j] == SHAPES_ARE_PT ) )
2560 {
2561 j++;
2562 }
2563
2564 std::pair<ssize_t,ssize_t> shapeToKeep = m_shapes[i];
2565
2566 if( shapeToKeep == SHAPES_ARE_PT )
2567 shapeToKeep = m_shapes[j - 1];
2568
2569 assert( shapeToKeep.first < static_cast<int>( m_arcs.size() ) );
2570 assert( shapeToKeep.second < static_cast<int>( m_arcs.size() ) );
2571
2572 pts_unique.push_back( CPoint( i ) );
2573 shapes_unique.push_back( shapeToKeep );
2574
2575 i = j;
2576 }
2577
2578 m_points.clear();
2579 m_shapes.clear();
2580
2581 for( size_t ii = 0; ii < pts_unique.size(); ++ii )
2582 {
2583 const VECTOR2I p0 = pts_unique[ii];
2584
2585 m_points.push_back( p0 );
2586 m_shapes.push_back( shapes_unique[ii] );
2587 }
2588}
2589
2590
2591
2592void SHAPE_LINE_CHAIN::Simplify( int aMaxError )
2593{
2594 if( PointCount() < 3 )
2595 return;
2596
2597 std::vector<VECTOR2I> new_points;
2598 std::vector<std::pair<ssize_t, ssize_t>> new_shapes;
2599
2600 new_points.reserve( m_points.size() );
2601 new_shapes.reserve( m_shapes.size() );
2602
2603 for( size_t start_idx = 0; start_idx < m_points.size(); )
2604 {
2605 new_points.push_back( m_points[start_idx] );
2606 new_shapes.push_back( m_shapes[start_idx] );
2607
2608 // If the line is not closed, we need at least 3 points before simplifying
2609 if( !m_closed && start_idx == m_points.size() - 2 )
2610 break;
2611
2612 // Initialize the end index to be two points ahead of start
2613 size_t end_idx = ( start_idx + 2 ) % m_points.size();
2614 bool can_simplify = true;
2615
2616 while( can_simplify && end_idx != start_idx && ( end_idx > start_idx || m_closed ) )
2617 {
2618 // Test all points between start_idx and end_idx
2619 for( size_t test_idx = ( start_idx + 1 ) % m_points.size();
2620 test_idx != end_idx;
2621 test_idx = ( test_idx + 1 ) % m_points.size() )
2622 {
2623 // Check if all points are regular points (not arcs)
2624 if( m_shapes[start_idx].first != SHAPE_IS_PT ||
2625 m_shapes[test_idx].first != SHAPE_IS_PT ||
2626 m_shapes[end_idx].first != SHAPE_IS_PT )
2627 {
2628 can_simplify = false;
2629 break;
2630 }
2631
2632 // Test if the point is within the allowed error
2633 if( !TestSegmentHit( m_points[test_idx], m_points[start_idx], m_points[end_idx], aMaxError ) )
2634 {
2635 can_simplify = false;
2636 break;
2637 }
2638 }
2639
2640 if( can_simplify )
2641 {
2642 // If we can simplify, move end_idx one further
2643 end_idx = ( end_idx + 1 ) % m_points.size();
2644 }
2645 }
2646
2647 // If we couldn't simplify at all, move to the next point
2648 if( end_idx == ( start_idx + 2 ) % m_points.size() )
2649 {
2650 ++start_idx;
2651 }
2652 else
2653 {
2654 // Otherwise, jump to the last point we could include in the simplification
2655 size_t new_start_idx = ( end_idx + m_points.size() - 1 ) % m_points.size();
2656
2657 // If we looped all the way around, we're done
2658 if( new_start_idx <= start_idx )
2659 break;
2660
2661 start_idx = new_start_idx;
2662 }
2663 }
2664
2665 // If we have only one point, we need to add a second point to make a line
2666 if( new_points.size() == 1 )
2667 {
2668 new_points.push_back( m_points.back() );
2669 new_shapes.push_back( m_shapes.back() );
2670 }
2671
2672 // If we are not closed, then the start and end points of the original line need to
2673 // be the start and end points of the new line.
2674 if( !m_closed && m_points.back() != new_points.back() )
2675 {
2676 new_points.push_back( m_points.back() );
2677 new_shapes.push_back( m_shapes.back() );
2678 }
2679
2680 m_points.clear();
2681 m_shapes.clear();
2682 m_points = new_points;
2683 m_shapes = new_shapes;
2684}
2685
2686
2687void SHAPE_LINE_CHAIN::Split( const VECTOR2I& aStart, const VECTOR2I& aEnd, SHAPE_LINE_CHAIN& aPre,
2688 SHAPE_LINE_CHAIN& aMid, SHAPE_LINE_CHAIN& aPost ) const
2689{
2690 VECTOR2I cp( aEnd );
2691
2692 VECTOR2I n = NearestPoint( cp, false );
2693 VECTOR2I m = NearestPoint( aStart, false );
2694
2695 SHAPE_LINE_CHAIN l( *this );
2696 l.Split( n, true );
2697 l.Split( m, true );
2698
2699 int i_start = l.Find( m );
2700 int i_end = l.Find( n );
2701
2702 if( i_start > i_end )
2703 {
2704 l = l.Reverse();
2705 i_start = l.Find( m );
2706 i_end = l.Find( n );
2707 }
2708
2709 aPre = l.Slice( 0, i_start );
2710 aPost = l.Slice( i_end, -1 );
2711 aMid = l.Slice( i_start, i_end );
2712}
2713
2714
2715
2717{
2718 std::vector<VECTOR2I> pts_unique;
2719 std::vector<std::pair<ssize_t, ssize_t>> shapes_unique;
2720
2721 // Always try to keep at least 2 points otherwise, we're not really a line
2722 if( PointCount() < 3 )
2723 {
2724 return *this;
2725 }
2726 else if( PointCount() == 3 )
2727 {
2728 if( m_points[0] == m_points[1] )
2729 Remove( 1 );
2730
2731 return *this;
2732 }
2733
2734 int i = 0;
2735 int np = PointCount();
2736
2737 // stage 1: eliminate duplicate vertices
2738 while( i < np )
2739 {
2740 int j = i + 1;
2741
2742 // We can eliminate duplicate vertices as long as they are part of the same shape, OR if
2743 // one of them is part of a shape and one is not.
2744 while( j < np && m_points[i] == m_points[j] &&
2745 ( m_shapes[i] == m_shapes[j] ||
2746 m_shapes[i] == SHAPES_ARE_PT ||
2747 m_shapes[j] == SHAPES_ARE_PT ) )
2748 {
2749 j++;
2750 }
2751
2752 std::pair<ssize_t,ssize_t> shapeToKeep = m_shapes[i];
2753
2754 if( shapeToKeep == SHAPES_ARE_PT )
2755 shapeToKeep = m_shapes[j - 1];
2756
2757 assert( shapeToKeep.first < static_cast<int>( m_arcs.size() ) );
2758 assert( shapeToKeep.second < static_cast<int>( m_arcs.size() ) );
2759
2760 pts_unique.push_back( CPoint( i ) );
2761 shapes_unique.push_back( shapeToKeep );
2762
2763 i = j;
2764 }
2765
2766 m_points.clear();
2767 m_shapes.clear();
2768 np = pts_unique.size();
2769
2770 i = 0;
2771
2772 // stage 2: eliminate colinear segments
2773 while( i < np - 2 )
2774 {
2775 const VECTOR2I p0 = pts_unique[i];
2776 int n = i;
2777
2778 if( aRemoveColinear && shapes_unique[i] == SHAPES_ARE_PT
2779 && shapes_unique[i + 1] == SHAPES_ARE_PT )
2780 {
2781 while( n < np - 2
2782 && ( SEG( p0, pts_unique[n + 2] ).LineDistance( pts_unique[n + 1] ) <= 1
2783 || SEG( p0, pts_unique[n + 2] ).Collinear( SEG( p0, pts_unique[n + 1] ) ) ) )
2784 n++;
2785 }
2786
2787 m_points.push_back( p0 );
2788 m_shapes.push_back( shapes_unique[i] );
2789
2790 if( n > i )
2791 i = n;
2792
2793 if( n == np - 2 )
2794 {
2795 m_points.push_back( pts_unique[np - 1] );
2796 m_shapes.push_back( shapes_unique[np - 1] );
2797 return *this;
2798 }
2799
2800 i++;
2801 }
2802
2803 if( np > 1 )
2804 {
2805 m_points.push_back( pts_unique[np - 2] );
2806 m_shapes.push_back( shapes_unique[np - 2] );
2807 }
2808
2809 m_points.push_back( pts_unique[np - 1] );
2810 m_shapes.push_back( shapes_unique[np - 1] );
2811
2812 assert( m_points.size() == m_shapes.size() );
2813
2814 return *this;
2815}
2816
2817bool SHAPE_LINE_CHAIN::OffsetLine( int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError,
2818 SHAPE_LINE_CHAIN& aLeft, SHAPE_LINE_CHAIN& aRight,
2819 bool aSimplify ) const
2820{
2821 if( PointCount() < 2 )
2822 return false;
2823
2824 SHAPE_POLY_SET poly;
2825 poly.OffsetLineChain( *this, aAmount, aCornerStrategy, aMaxError, aSimplify );
2826
2827 if( poly.OutlineCount() != 1 )
2828 return false;
2829
2830 if( poly.COutline( 0 ).PointCount() < 3 )
2831 return false;
2832
2833 if( poly.HasHoles() )
2834 return false;
2835
2836 SHAPE_LINE_CHAIN outline = poly.COutline( 0 );
2837
2838 wxASSERT( outline.IsClosed() );
2839
2840 const VECTOR2I& start = CPoint( 0 );
2841 const VECTOR2I& end = CPoint( -1 );
2842
2843 outline.Split( start, true );
2844 outline.Split( end, true );
2845
2846 const int idA = outline.Find( start );
2847 const int idB = outline.Find( end );
2848
2849 if( idA == -1 || idB == -1 )
2850 return false;
2851
2852 aLeft.Clear();
2853 aRight.Clear();
2854
2855 for( int i = idA;; )
2856 {
2857 aLeft.Append( outline.CPoint( i ) );
2858
2859 i = ( i + 1 ) % outline.PointCount();
2860
2861 if( i == idB )
2862 {
2863 aLeft.Append( outline.CPoint( i ) );
2864 break;
2865 }
2866
2867 if( i == idA )
2868 return false;
2869 }
2870
2871 if( aLeft.PointCount() < 2 )
2872 return false;
2873
2874 for( int i = idB;; )
2875 {
2876 aRight.Append( outline.CPoint( i ) );
2877
2878 i = ( i + 1 ) % outline.PointCount();
2879
2880 if( i == idA )
2881 {
2882 aRight.Append( outline.CPoint( i ) );
2883 break;
2884 }
2885
2886 if( i == idB )
2887 return false;
2888 }
2889
2890 if( aRight.PointCount() < 2 )
2891 return false;
2892
2893 if( aLeft.CPoint( 0 ) != start )
2894 {
2895 aLeft = aLeft.Reverse();
2896 wxASSERT( aLeft.CPoint( 0 ) == start );
2897 }
2898
2899 if( aRight.CPoint( 0 ) != start )
2900 {
2901 aRight = aRight.Reverse();
2902 wxASSERT( aRight.CPoint( 0 ) == start );
2903 }
2904
2905 SEG base( CPoint( 0 ), CPoint( 1 ) );
2906 int sideLeft = base.Side( aLeft.CPoint( 1 ) );
2907 int sideRight = base.Side( aRight.CPoint( 1 ) );
2908
2909 if( sideLeft == 0 || sideRight == 0 )
2910 return false;
2911
2912 if( sideLeft == sideRight )
2913 return false;
2914
2915 if( sideLeft > 0 && sideRight < 0 )
2916 std::swap( aLeft, aRight );
2917
2918 if( aLeft.PointCount() < 4 )
2919 return false;
2920
2921 if( aRight.PointCount() < 4 )
2922 return false;
2923
2924 aLeft.Remove( 0 );
2925 aLeft.Remove( aLeft.PointCount() - 1 );
2926
2927 aRight.Remove( 0 );
2928 aRight.Remove( aRight.PointCount() - 1 );
2929
2930 return true;
2931}
2932
2933
2935 ERROR_LOC aErrorLoc ) const
2936{
2937 aBuffer.AddOutline( *this );
2938}
2939
2940
2942 m_point( aPoint ),
2943 m_finished( false ),
2944 m_state( 0 ),
2945 m_count( 0 )
2946{
2947}
2948
2949
2951 const VECTOR2I& ip, const VECTOR2I& ipNext )
2952{
2953 if( ipNext.y == m_point.y )
2954 {
2955 if( ( ipNext.x == m_point.x )
2956 || ( ip.y == m_point.y && ( ( ipNext.x > m_point.x ) == ( ip.x < m_point.x ) ) ) )
2957 {
2958 m_finished = true;
2959 m_state = -1;
2960 return false;
2961 }
2962 }
2963
2964 if( ( ip.y < m_point.y ) != ( ipNext.y < m_point.y ) )
2965 {
2966 if( ip.x >= m_point.x )
2967 {
2968 if( ipNext.x > m_point.x )
2969 {
2970 m_state = 1 - m_state;
2971 }
2972 else
2973 {
2974 double d = (double) ( ip.x - m_point.x ) * ( ipNext.y - m_point.y )
2975 - (double) ( ipNext.x - m_point.x ) * ( ip.y - m_point.y );
2976
2977 if( !d )
2978 {
2979 m_finished = true;
2980 m_state = -1;
2981 return false;
2982 }
2983
2984 if( ( d > 0 ) == ( ipNext.y > ip.y ) )
2985 m_state = 1 - m_state;
2986 }
2987 }
2988 else
2989 {
2990 if( ipNext.x > m_point.x )
2991 {
2992 double d = (double) ( ip.x - m_point.x ) * ( ipNext.y - m_point.y )
2993 - (double) ( ipNext.x - m_point.x ) * ( ip.y - m_point.y );
2994
2995 if( !d )
2996 {
2997 m_finished = true;
2998 m_state = -1;
2999 return false;
3000 }
3001
3002 if( ( d > 0 ) == ( ipNext.y > ip.y ) )
3003 m_state = 1 - m_state;
3004 }
3005 }
3006 }
3007
3008 return true;
3009}
3010
3011
3013{
3014 if( !m_count )
3015 {
3016 m_lastPoint = aPolyline.CPoint( 0 );
3017 m_firstPoint = aPolyline.CPoint( 0 );
3018 }
3019
3020 m_count += aPolyline.PointCount();
3021
3022 for( int i = 1; i < aPolyline.PointCount(); i++ )
3023 {
3024 auto p = aPolyline.CPoint( i );
3025
3026 if( !processVertex( m_lastPoint, p ) )
3027 return;
3028
3029 m_lastPoint = p;
3030 }
3031
3032}
3033
3034
3036{
3037 processVertex( m_lastPoint, m_firstPoint );
3038 return m_state > 0;
3039}
3040
3041
3042bool SHAPE_LINE_CHAIN::IsSharedPt( size_t aIndex ) const
3043{
3044 return aIndex < m_shapes.size()
3045 && m_shapes[aIndex].first != SHAPE_IS_PT
3046 && m_shapes[aIndex].second != SHAPE_IS_PT;
3047}
3048
3049
3050bool SHAPE_LINE_CHAIN::IsPtOnArc( size_t aPtIndex ) const
3051{
3052 return aPtIndex < m_shapes.size() && m_shapes[aPtIndex] != SHAPES_ARE_PT;
3053}
3054
3055
3056bool SHAPE_LINE_CHAIN::IsArcSegment( size_t aSegment ) const
3057{
3058 /*
3059 * A segment is part of an arc except in the special case of two arcs next to each other
3060 * but without a shared vertex. Here there is a segment between the end of the first arc
3061 * and the start of the second arc.
3062 */
3063 size_t nextIdx = aSegment + 1;
3064
3065 if( nextIdx > m_shapes.size() - 1 )
3066 {
3067 if( nextIdx == m_shapes.size() && m_closed && IsSharedPt( 0 ) )
3068 nextIdx = 0; // segment between end point and first point
3069 else
3070 return false;
3071 }
3072
3073 return ( IsPtOnArc( aSegment )
3074 && ( ArcIndex( aSegment ) == m_shapes[nextIdx].first ) );
3075}
3076
3077
3078bool SHAPE_LINE_CHAIN::IsArcStart( size_t aIndex ) const
3079{
3080 if( !IsArcSegment( aIndex ) ) // also does bound checking
3081 return false;
3082
3083 if( IsSharedPt( aIndex ) )
3084 return true;
3085
3086 const SHAPE_ARC& arc = Arc( ArcIndex( aIndex ) );
3087
3088 return arc.GetP0() == m_points[aIndex];
3089}
3090
3091
3092bool SHAPE_LINE_CHAIN::IsArcEnd( size_t aIndex ) const
3093{
3094 size_t prevIndex = aIndex - 1;
3095
3096 if( aIndex == 0 )
3097 prevIndex = m_points.size() - 1;
3098 else if( aIndex > m_points.size() -1 )
3099 return false; // invalid index requested
3100
3101 if( !IsArcSegment( prevIndex ) )
3102 return false;
3103
3104 if( IsSharedPt( aIndex ) )
3105 return true;
3106
3107 const SHAPE_ARC& arc = Arc( ArcIndex( aIndex ) );
3108
3109 return arc.GetP1() == m_points[aIndex];
3110}
ERROR_LOC
When approximating an arc or circle, should the error be placed on the outside or inside of the curve...
Definition: approximation.h:32
constexpr BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:658
constexpr bool Intersects(const BOX2< Vec > &aRect) const
Definition: box2.h:311
Represent basic circle geometry with utility geometry functions.
Definition: circle.h:33
std::vector< VECTOR2I > Intersect(const CIRCLE &aCircle) const
Compute the intersection points between this circle and aCircle.
Definition: circle.cpp:221
Definition: seg.h:42
const VECTOR2I ReflectPoint(const VECTOR2I &aP) const
Reflect a point using this segment as axis.
Definition: seg.cpp:350
VECTOR2I A
Definition: seg.h:49
int LineDistance(const VECTOR2I &aP, bool aDetermineSide=false) const
Return the closest Euclidean distance between point aP and the line defined by the ends of segment (t...
Definition: seg.cpp:428
ecoord SquaredDistance(const SEG &aSeg) const
Definition: seg.cpp:75
VECTOR2I::extended_type ecoord
Definition: seg.h:44
VECTOR2I B
Definition: seg.h:50
const VECTOR2I NearestPoint(const VECTOR2I &aP) const
Compute a point on the segment (this) that is closest to point aP.
Definition: seg.cpp:327
int Length() const
Return the length (this).
Definition: seg.h:333
OPT_VECTOR2I Intersect(const SEG &aSeg, bool aIgnoreEndpoints=false, bool aLines=false) const
Compute intersection point of segment (this) with segment aSeg.
Definition: seg.cpp:254
static SEG::ecoord Square(int a)
Definition: seg.h:123
bool Collinear(const SEG &aSeg) const
Check if segment aSeg lies on the same line as (this).
Definition: seg.h:276
bool NearestPoints(const SEG &aSeg, VECTOR2I &aPtA, VECTOR2I &aPtB, int64_t &aDistSq) const
Compute closest points between this segment and aSeg.
Definition: seg.cpp:150
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition: seg.cpp:388
bool Contains(const SEG &aSeg) const
Definition: seg.h:314
int Side(const VECTOR2I &aP) const
Determine on which side of directed line passing via segment ends point aP lies.
Definition: seg.h:143
bool IsClockwise() const
Definition: shape_arc.h:273
int Intersect(const CIRCLE &aArc, std::vector< VECTOR2I > *aIpsBuffer) const
Find intersection points between this arc and a CIRCLE.
Definition: shape_arc.cpp:316
int GetWidth() const
Definition: shape_arc.h:168
void SetWidth(int aWidth)
Definition: shape_arc.h:163
SHAPE_ARC & ConstructFromStartEndCenter(const VECTOR2I &aStart, const VECTOR2I &aEnd, const VECTOR2I &aCenter, bool aClockwise=false, double aWidth=0)
Constructs this arc from the given start, end and center.
Definition: shape_arc.cpp:212
const VECTOR2I & GetP1() const
Definition: shape_arc.h:115
bool Collide(const SEG &aSeg, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the segment aSeg than aClearance,...
Definition: shape_arc.cpp:244
const SHAPE_LINE_CHAIN ConvertToPolyline(double aAccuracy=DefaultAccuracyForPCB(), double *aEffectiveAccuracy=nullptr) const
Construct a SHAPE_LINE_CHAIN of segments from a given arc.
Definition: shape_arc.cpp:560
double GetRadius() const
Definition: shape_arc.cpp:554
static double DefaultAccuracyForPCB()
Definition: shape_arc.h:232
void Reverse()
Definition: shape_arc.cpp:674
const VECTOR2I & GetP0() const
Definition: shape_arc.h:114
const VECTOR2I & GetCenter() const
Definition: shape_arc.cpp:523
bool processVertex(const VECTOR2I &ip, const VECTOR2I &ipNext)
void AddPolyline(const SHAPE_LINE_CHAIN &aPolyline)
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if point aP lies closer to us than aClearance.
bool PointOnEdge(const VECTOR2I &aP, int aAccuracy=0) const
Check if point aP lies on an edge or vertex of the line chain.
int EdgeContainingPoint(const VECTOR2I &aP, int aAccuracy=0) const
Check if point aP lies on an edge or vertex of the line chain.
virtual size_t GetPointCount() const =0
virtual size_t GetSegmentCount() const =0
virtual const VECTOR2I GetPoint(int aIndex) const =0
virtual BOX2I * GetCachedBBox() const
Definition: shape.h:351
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
virtual bool IsClosed() const =0
virtual const SEG GetSegment(int aIndex) const =0
SEG::ecoord SquaredDistance(const VECTOR2I &aP, bool aOutlineOnly=false) const override
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
std::vector< std::pair< ssize_t, ssize_t > > m_shapes
Array of indices that refer to the index of the shape if the point is part of a larger shape,...
const SHAPE_LINE_CHAIN Reverse() const
Reverse point order in the line chain.
bool IsPtOnArc(size_t aPtIndex) const
const SHAPE_ARC & Arc(size_t aArc) const
void splitArc(ssize_t aPtIndex, bool aCoincident=false)
Splits an arc into two arcs at aPtIndex.
const std::optional< INTERSECTION > SelfIntersecting() const
Check if the line chain is self-intersecting.
bool Parse(std::stringstream &aStream) override
bool CheckClearance(const VECTOR2I &aP, const int aDist) const
Check if point aP is closer to (or on) an edge or vertex of the line chain.
std::vector< SHAPE_ARC > m_arcs
bool IsClosed() const override
virtual const VECTOR2I GetPoint(int aIndex) const override
ClipperLib::Path convertToClipper(bool aRequiredOrientation, std::vector< CLIPPER_Z_VALUE > &aZValueBuffer, std::vector< SHAPE_ARC > &aArcBuffer) const
Create a new Clipper path from the SHAPE_LINE_CHAIN in a given orientation.
void SetPoint(int aIndex, const VECTOR2I &aPos)
Move a point to a specific location.
const VECTOR2I PointAlong(int aPathLength) const
int Split(const VECTOR2I &aP, bool aExact=false)
Insert the point aP belonging to one of the our segments, splitting the adjacent segment in two.
void fixIndicesRotation()
Fix indices of this chain to ensure arcs are not split between the end and start indices.
std::vector< VECTOR2I > m_points
array of vertices
int FindSegment(const VECTOR2I &aP, int aThreshold=1) const
Search for segment containing point aP.
int ShapeCount() const
Return the number of shapes (line segments or arcs) in this line chain.
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int Intersect(const SEG &aSeg, INTERSECTIONS &aIp) const
Find all intersection points between our line chain and the segment aSeg.
bool Intersects(const SHAPE_LINE_CHAIN &aChain) const
void Simplify(int aMaxError=0)
Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
SHAPE_LINE_CHAIN()
Initialize an empty line chain.
int PointCount() const
Return the number of points (vertices) in this line chain.
bool IsArcEnd(size_t aIndex) const
void Replace(int aStartIndex, int aEndIndex, const VECTOR2I &aP)
Replace points with indices in range [start_index, end_index] with a single point aP.
void TransformToPolygon(SHAPE_POLY_SET &aBuffer, int aError, ERROR_LOC aErrorLoc) const override
Fills a SHAPE_POLY_SET with a polygon representation of this shape.
virtual bool Collide(const VECTOR2I &aP, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if point aP lies closer to us than aClearance.
void ClearArcs()
Remove all arc references in the line chain, resulting in a chain formed only of straight segments.
void mergeFirstLastPointIfNeeded()
Merge the first and last point if they are the same and this chain is closed.
bool CompareGeometry(const SHAPE_LINE_CHAIN &aOther) const
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
void Clear()
Remove all points from the line chain.
SEG Segment(int aIndex) const
Return a copy of the aIndex-th segment in the line chain.
BOX2I m_bbox
cached bounding box
const SHAPE_LINE_CHAIN Slice(int aStartIndex, int aEndIndex=-1) const
Return a subset of this line chain containing the [start_index, end_index] range of points.
bool m_closed
is the line chain closed?
const std::vector< SHAPE_ARC > & CArcs() const
const std::optional< INTERSECTION > SelfIntersectingWithArcs() const
Check if the line chain is self-intersecting.
int NearestSegment(const VECTOR2I &aP) const
Find the segment nearest the given point.
int NextShape(int aPointIndex) const
Return the vertex index of the next shape in the chain, or -1 if aPointIndex is the last shape.
double Area(bool aAbsolute=true) const
Return the area of this chain.
SHAPE_LINE_CHAIN & Simplify2(bool aRemoveColinear=true)
void amendArc(size_t aArcIndex, const VECTOR2I &aNewStart, const VECTOR2I &aNewEnd)
bool ClosestPoints(const SHAPE_LINE_CHAIN &aOther, VECTOR2I &aPt0, VECTOR2I &aPt1) const
Finds closest points between this and the other line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
virtual const SEG GetSegment(int aIndex) const override
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
const std::vector< std::pair< ssize_t, ssize_t > > & CShapes() const
const VECTOR2I NearestPoint(const VECTOR2I &aP, bool aAllowInternalShapePoints=true) const
Find a point on the line chain that is closest to point aP.
int SegmentCount() const
Return the number of segments in this line chain.
int PathLength(const VECTOR2I &aP, int aIndex=-1) const
Compute the walk path length from the beginning of the line chain and the point aP belonging to our l...
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the line points about y or x (or both).
bool ClosestSegmentsFast(const SHAPE_LINE_CHAIN &aOther, VECTOR2I &aPt0, VECTOR2I &aPt1) const
Finds closest points between segments of this and the other line chain.
const std::string Format(bool aCplusPlus=true) const override
static bool ClosestSegments(const VECTOR2I &aMyPrevPt, const point_citer &aMyStart, const point_citer &aMyEnd, const VECTOR2I &aOtherPrevPt, const point_citer &aOtherStart, const point_citer &aOtherEnd, VECTOR2I &aPt0, VECTOR2I &aPt1, int64_t &aDistSq)
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.
static const std::pair< ssize_t, ssize_t > SHAPES_ARE_PT
void convertArc(ssize_t aArcIndex)
Convert an arc to only a point chain by removing the arc and references.
virtual size_t GetSegmentCount() const override
void Remove(int aStartIndex, int aEndIndex)
Remove the range of points [start_index, end_index] from the line chain.
void RemoveDuplicatePoints()
Remove the duplicate points from the line chain.
size_t ArcCount() const
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
bool IsArcSegment(size_t aSegment) const
void RemoveShape(int aPointIndex)
Remove the shape at the given index from the line chain.
void Insert(size_t aVertex, const VECTOR2I &aP)
bool IsArcStart(size_t aIndex) const
bool IsSharedPt(size_t aIndex) const
Test if a point is shared between multiple shapes.
std::vector< INTERSECTION > INTERSECTIONS
long long int Length() const
Return length of the line chain in Euclidean metric.
int Find(const VECTOR2I &aP, int aThreshold=0) const
Search for point aP.
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
std::vector< VECTOR2I >::const_iterator point_citer
SHAPE * Clone() const override
Return a dynamically allocated copy of the shape.
static const ssize_t SHAPE_IS_PT
bool OffsetLine(int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, SHAPE_LINE_CHAIN &aLeft, SHAPE_LINE_CHAIN &aRight, bool aSimplify=false) const
Creates line chains aLeft and aRight offset to this line chain.
Represent a set of closed polygons.
bool HasHoles() const
Return true if the polygon set has any holes.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
void OffsetLineChain(const SHAPE_LINE_CHAIN &aLine, int aAmount, CORNER_STRATEGY aCornerStrategy, int aMaxError, bool aSimplify)
Perform offsetting of a line chain.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
An abstract shape on 2D plane.
Definition: shape.h:126
VECTOR2I::extended_type ecoord
Definition: shape.h:284
static constexpr extended_type ECOORD_MAX
Definition: vector2d.h:76
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition: vector2d.h:283
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition: vector2d.h:385
CORNER_STRATEGY
define how inflate transform build inflated polygon
@ DEGREES_T
Definition: eda_angle.h:31
FLIP_DIRECTION
Definition: mirror.h:27
bool pair_contains(const std::pair< _Type, _Type > __pair, _Value __value)
Returns true if either of the elements in an std::pair contains the given value.
Definition: kicad_algo.h:113
void run_on_pair(std::pair< _Type, _Type > &__pair, _Function __f)
Apply a function to the first and second element of a std::pair.
Definition: kicad_algo.h:45
std::optional< VECTOR2I > OPT_VECTOR2I
Definition: seg.h:39
@ SH_LINE_CHAIN
line chain (polyline)
Definition: shape.h:49
static void addIntersection(SHAPE_LINE_CHAIN::INTERSECTIONS &aIps, int aPc, const SHAPE_LINE_CHAIN::INTERSECTION &aP)
std::vector< FAB_LAYER_COLOR > dummy
A min-max version of BOX2 for fast intersection checking.
Definition: box2_minmax.h:37
bool Intersects(const BOX2I_MINMAX &aOther) const
Definition: box2_minmax.h:73
Holds information on each point of a SHAPE_LINE_CHAIN that is retrievable after an operation with Cli...
SHAPE_KEY(int aFirstIdx, int aArcIdx, const BOX2I_MINMAX &aBBox)
BOX2I_MINMAX m_BBox
Represent an intersection between two line segments.
bool is_corner_their
When true, the corner [index_their] of the 'their' line lies exactly on 'our' line.
bool is_corner_our
When true, the corner [index_our] of the 'our' line lies exactly on 'their' line.
int index_our
Index of the intersecting corner/segment in the 'our' (== this) line.
VECTOR2I p
Point of intersection between our and their.
bool valid
Auxiliary flag to avoid copying intersection info to intersection refining code, used by the refining...
int index_their
index of the intersecting corner/segment in the 'their' (Intersect() method parameter) line.
bool operator()(const SHAPE_LINE_CHAIN::INTERSECTION &aA, const SHAPE_LINE_CHAIN::INTERSECTION &aB) const
compareOriginDistance(const VECTOR2I &aOrigin)
bool TestSegmentHit(const VECTOR2I &aRefPoint, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aDist)
Test if aRefPoint is with aDistance on the line defined by aStart and aEnd.
Definition: trigo.cpp:175
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition: trigo.cpp:229
T rescale(T aNumerator, T aValue, T aDenominator)
Scale a number (value) by rational (numerator/denominator).
Definition: util.h:153
VECTOR2< double > VECTOR2D
Definition: vector2d.h:690