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