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