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