KiCad PCB EDA Suite
eda_shape.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) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
6 * Copyright (C) 2011 Wayne Stambaugh <[email protected]>
7 * Copyright (C) 1992-2022 KiCad Developers, see AUTHORS.txt for contributors.
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 <bezier_curves.h>
28#include <base_units.h>
30#include <eda_draw_frame.h>
34#include <macros.h>
35#include <math/util.h> // for KiROUND
36#include <eda_shape.h>
37#include <plotters/plotter.h>
38
39
40EDA_SHAPE::EDA_SHAPE( SHAPE_T aType, int aLineWidth, FILL_T aFill ) :
41 m_endsSwapped( false ),
42 m_shape( aType ),
43 m_stroke( aLineWidth, PLOT_DASH_TYPE::DEFAULT, COLOR4D::UNSPECIFIED ),
44 m_fill( aFill ),
45 m_fillColor( COLOR4D::UNSPECIFIED ),
46 m_editState( 0 ),
47 m_annotationProxy( false )
48{
49}
50
51
53{
54}
55
56
57wxString EDA_SHAPE::ShowShape() const
58{
59 if( IsAnnotationProxy() )
60 return _( "Number Box" );
61
62 switch( m_shape )
63 {
64 case SHAPE_T::SEGMENT: return _( "Line" );
65 case SHAPE_T::RECT: return _( "Rect" );
66 case SHAPE_T::ARC: return _( "Arc" );
67 case SHAPE_T::CIRCLE: return _( "Circle" );
68 case SHAPE_T::BEZIER: return _( "Bezier Curve" );
69 case SHAPE_T::POLY: return _( "Polygon" );
70 default: return wxT( "??" );
71 }
72}
73
74
76{
77 switch( m_shape )
78 {
79 case SHAPE_T::SEGMENT: return wxS( "S_SEGMENT" );
80 case SHAPE_T::RECT: return wxS( "S_RECT" );
81 case SHAPE_T::ARC: return wxS( "S_ARC" );
82 case SHAPE_T::CIRCLE: return wxS( "S_CIRCLE" );
83 case SHAPE_T::POLY: return wxS( "S_POLYGON" );
84 case SHAPE_T::BEZIER: return wxS( "S_CURVE" );
85 case SHAPE_T::LAST: return wxS( "!S_LAST!" ); // Synthetic value, but if we come across it then
86 // we're going to want to know.
87 }
88
89 return wxEmptyString; // Just to quiet GCC.
90}
91
92
94{
95 move( aPos - getPosition() );
96}
97
98
100{
101 if( m_shape == SHAPE_T::ARC )
102 return getCenter();
103 else if( m_shape == SHAPE_T::POLY )
104 return m_poly.CVertex( 0 );
105 else
106 return m_start;
107}
108
109
111{
112 double length = 0.0;
113
114 switch( m_shape )
115 {
116 case SHAPE_T::BEZIER:
117 for( size_t ii = 1; ii < m_bezierPoints.size(); ++ii )
118 length += GetLineLength( m_bezierPoints[ ii - 1], m_bezierPoints[ii] );
119
120 return length;
121
122 case SHAPE_T::SEGMENT:
123 return GetLineLength( GetStart(), GetEnd() );
124
125 case SHAPE_T::POLY:
126 for( int ii = 0; ii < m_poly.COutline( 0 ).SegmentCount(); ii++ )
127 length += m_poly.COutline( 0 ).CSegment( ii ).Length();
128
129 return length;
130
131 case SHAPE_T::ARC:
132 return GetRadius() * GetArcAngle().AsRadians();
133
134 default:
136 return 0.0;
137 }
138}
139
140
142{
143 switch( m_shape )
144 {
145 case SHAPE_T::CIRCLE:
146 case SHAPE_T::RECT:
147 return true;
148
149 case SHAPE_T::ARC:
150 case SHAPE_T::SEGMENT:
151 return false;
152
153 case SHAPE_T::POLY:
154 if( m_poly.IsEmpty() )
155 return false;
156 else
157 return m_poly.Outline( 0 ).IsClosed();
158
159 case SHAPE_T::BEZIER:
160 if( m_bezierPoints.size() < 3 )
161 return false;
162 else
163 return m_bezierPoints[0] == m_bezierPoints[ m_bezierPoints.size() - 1 ];
164
165 default:
167 return false;
168 }
169}
170
171
172
173void EDA_SHAPE::move( const VECTOR2I& aMoveVector )
174{
175 switch ( m_shape )
176 {
177 case SHAPE_T::ARC:
178 case SHAPE_T::SEGMENT:
179 case SHAPE_T::RECT:
180 case SHAPE_T::CIRCLE:
181 m_start += aMoveVector;
182 m_end += aMoveVector;
183 m_arcCenter += aMoveVector;
184 break;
185
186 case SHAPE_T::POLY:
187 m_poly.Move( VECTOR2I( aMoveVector ) );
188 break;
189
190 case SHAPE_T::BEZIER:
191 m_start += aMoveVector;
192 m_end += aMoveVector;
193 m_bezierC1 += aMoveVector;
194 m_bezierC2 += aMoveVector;
195
196 for( VECTOR2I& pt : m_bezierPoints )
197 pt += aMoveVector;
198
199 break;
200
201 default:
203 break;
204 }
205}
206
207
208void EDA_SHAPE::scale( double aScale )
209{
210 auto scalePt = [&]( VECTOR2I& pt )
211 {
212 pt.x = KiROUND( pt.x * aScale );
213 pt.y = KiROUND( pt.y * aScale );
214 };
215
216 switch( m_shape )
217 {
218 case SHAPE_T::ARC:
219 case SHAPE_T::SEGMENT:
220 case SHAPE_T::RECT:
221 scalePt( m_start );
222 scalePt( m_end );
223 scalePt( m_arcCenter );
224 break;
225
226 case SHAPE_T::CIRCLE: // ring or circle
227 scalePt( m_start );
228 m_end.x = m_start.x + KiROUND( GetRadius() * aScale );
229 m_end.y = m_start.y;
230 break;
231
232 case SHAPE_T::POLY: // polygon
233 {
234 std::vector<VECTOR2I> pts;
235
236 for( int ii = 0; ii < m_poly.OutlineCount(); ++ ii )
237 {
238 for( const VECTOR2I& pt : m_poly.Outline( ii ).CPoints() )
239 {
240 pts.emplace_back( pt );
241 scalePt( pts.back() );
242 }
243 }
244
245 SetPolyPoints( pts );
246 }
247 break;
248
249 case SHAPE_T::BEZIER:
250 scalePt( m_start );
251 scalePt( m_end );
252 scalePt( m_bezierC1 );
253 scalePt( m_bezierC2 );
254 break;
255
256 default:
258 break;
259 }
260}
261
262
263void EDA_SHAPE::rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
264{
265 switch( m_shape )
266 {
267 case SHAPE_T::SEGMENT:
268 case SHAPE_T::CIRCLE:
269 RotatePoint( m_start, aRotCentre, aAngle );
270 RotatePoint( m_end, aRotCentre, aAngle );
271 break;
272
273 case SHAPE_T::ARC:
274 RotatePoint( m_start, aRotCentre, aAngle );
275 RotatePoint( m_end, aRotCentre, aAngle );
276 RotatePoint( m_arcCenter, aRotCentre, aAngle );
277 break;
278
279 case SHAPE_T::RECT:
280 if( aAngle.IsCardinal() )
281 {
282 RotatePoint( m_start, aRotCentre, aAngle );
283 RotatePoint( m_end, aRotCentre, aAngle );
284 break;
285 }
286
287 // Convert non-cardinally-rotated rect to a diamond
295
297
298 case SHAPE_T::POLY:
299 m_poly.Rotate( aAngle, aRotCentre );
300 break;
301
302 case SHAPE_T::BEZIER:
303 RotatePoint( m_start, aRotCentre, aAngle );
304 RotatePoint( m_end, aRotCentre, aAngle );
305 RotatePoint( m_bezierC1, aRotCentre, aAngle );
306 RotatePoint( m_bezierC2, aRotCentre, aAngle );
307
308 for( VECTOR2I& pt : m_bezierPoints )
309 RotatePoint( pt, aRotCentre, aAngle);
310
311 break;
312
313 default:
315 break;
316 }
317}
318
319
320void EDA_SHAPE::flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
321{
322 switch ( m_shape )
323 {
324 case SHAPE_T::SEGMENT:
325 case SHAPE_T::RECT:
326 if( aFlipLeftRight )
327 {
328 m_start.x = aCentre.x - ( m_start.x - aCentre.x );
329 m_end.x = aCentre.x - ( m_end.x - aCentre.x );
330 }
331 else
332 {
333 m_start.y = aCentre.y - ( m_start.y - aCentre.y );
334 m_end.y = aCentre.y - ( m_end.y - aCentre.y );
335 }
336
337 std::swap( m_start, m_end );
338 break;
339
340 case SHAPE_T::CIRCLE:
341 if( aFlipLeftRight )
342 {
343 m_start.x = aCentre.x - ( m_start.x - aCentre.x );
344 m_end.x = aCentre.x - ( m_end.x - aCentre.x );
345 }
346 else
347 {
348 m_start.y = aCentre.y - ( m_start.y - aCentre.y );
349 m_end.y = aCentre.y - ( m_end.y - aCentre.y );
350 }
351 break;
352
353 case SHAPE_T::ARC:
354 if( aFlipLeftRight )
355 {
356 m_start.x = aCentre.x - ( m_start.x - aCentre.x );
357 m_end.x = aCentre.x - ( m_end.x - aCentre.x );
358 m_arcCenter.x = aCentre.x - ( m_arcCenter.x - aCentre.x );
359 }
360 else
361 {
362 m_start.y = aCentre.y - ( m_start.y - aCentre.y );
363 m_end.y = aCentre.y - ( m_end.y - aCentre.y );
364 m_arcCenter.y = aCentre.y - ( m_arcCenter.y - aCentre.y );
365 }
366
367 std::swap( m_start, m_end );
368 break;
369
370 case SHAPE_T::POLY:
371 m_poly.Mirror( aFlipLeftRight, !aFlipLeftRight, aCentre );
372 break;
373
374 case SHAPE_T::BEZIER:
375 if( aFlipLeftRight )
376 {
377 m_start.x = aCentre.x - ( m_start.x - aCentre.x );
378 m_end.x = aCentre.x - ( m_end.x - aCentre.x );
379 m_bezierC1.x = aCentre.x - ( m_bezierC1.x - aCentre.x );
380 m_bezierC2.x = aCentre.x - ( m_bezierC2.x - aCentre.x );
381 }
382 else
383 {
384 m_start.y = aCentre.y - ( m_start.y - aCentre.y );
385 m_end.y = aCentre.y - ( m_end.y - aCentre.y );
386 m_bezierC1.y = aCentre.y - ( m_bezierC1.y - aCentre.y );
387 m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y );
388 }
389
390 // Rebuild the poly points shape
391 {
392 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
393 BEZIER_POLY converter( ctrlPoints );
394 converter.GetPoly( m_bezierPoints, m_stroke.GetWidth() );
395 }
396 break;
397
398 default:
400 break;
401 }
402}
403
404
406{
407 // Has meaning only for SHAPE_T::BEZIER
408 if( m_shape != SHAPE_T::BEZIER )
409 {
410 m_bezierPoints.clear();
411 return;
412 }
413
414 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
416
417 // Ensure last point respects aMinSegLen parameter
418 if( m_bezierPoints.size() > 2 )
419 {
420 int idx = m_bezierPoints.size()-1;
421
422 if( VECTOR2I( m_bezierPoints[idx] - m_bezierPoints[idx]-1 ).EuclideanNorm() < aMinSegLen )
423 {
424 m_bezierPoints[idx]-1 = m_bezierPoints[idx];
425 m_bezierPoints.pop_back();
426 }
427 }
428}
429
430
431const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMinSegLen ) const
432{
433 std::vector<VECTOR2I> bezierPoints;
434
435 // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
436 std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
437 BEZIER_POLY converter( ctrlPoints );
438 converter.GetPoly( bezierPoints, aMinSegLen );
439
440 return bezierPoints;
441}
442
443
445{
446 switch( m_shape )
447 {
448 case SHAPE_T::ARC:
449 return m_arcCenter;
450
451 case SHAPE_T::CIRCLE:
452 return m_start;
453
454 case SHAPE_T::SEGMENT:
455 // Midpoint of the line
456 return ( m_start + m_end ) / 2;
457
458 case SHAPE_T::POLY:
459 case SHAPE_T::RECT:
460 case SHAPE_T::BEZIER:
461 return getBoundingBox().Centre();
462
463 default:
465 return VECTOR2I();
466 }
467}
468
469
470void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
471{
472 switch( m_shape )
473 {
474 case SHAPE_T::ARC:
475 m_arcCenter = aCenter;
476 break;
477
478 case SHAPE_T::CIRCLE:
479 m_start = aCenter;
480 break;
481
482 default:
484 }
485}
486
487
489{
490 // If none of the input data have changed since we loaded the arc,
491 // keep the original mid point data to minimize churn
494 return m_arcMidData.mid;
495
496 VECTOR2I mid = m_start;
497 RotatePoint( mid, m_arcCenter, -GetArcAngle() / 2.0 );
498 return mid;
499}
500
501
502void EDA_SHAPE::CalcArcAngles( EDA_ANGLE& aStartAngle, EDA_ANGLE& aEndAngle ) const
503{
504 VECTOR2D startRadial( GetStart() - getCenter() );
505 VECTOR2D endRadial( GetEnd() - getCenter() );
506
507 aStartAngle = EDA_ANGLE( startRadial );
508 aEndAngle = EDA_ANGLE( endRadial );
509
510 if( aEndAngle == aStartAngle )
511 aEndAngle = aStartAngle + ANGLE_360; // ring, not null
512
513 if( aStartAngle > aEndAngle )
514 {
515 if( aEndAngle < ANGLE_0 )
516 aEndAngle.Normalize();
517 else
518 aStartAngle = aStartAngle.Normalize() - ANGLE_360;
519 }
520}
521
522
524{
525 double radius = 0.0;
526
527 switch( m_shape )
528 {
529 case SHAPE_T::ARC:
530 radius = GetLineLength( m_arcCenter, m_start );
531 break;
532
533 case SHAPE_T::CIRCLE:
534 radius = GetLineLength( m_start, m_end );
535 break;
536
537 default:
539 }
540
541 // don't allow degenerate circles/arcs
542 return std::max( 1, KiROUND( radius ) );
543}
544
545
546void EDA_SHAPE::SetCachedArcData( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd, const VECTOR2I& aCenter )
547{
548 m_arcMidData.start = aStart;
549 m_arcMidData.end = aEnd;
550 m_arcMidData.center = aCenter;
551 m_arcMidData.mid = aMid;
552}
553
554
555void EDA_SHAPE::SetArcGeometry( const VECTOR2I& aStart, const VECTOR2I& aMid, const VECTOR2I& aEnd )
556{
557 m_arcMidData = {};
558 m_start = aStart;
559 m_end = aEnd;
560 m_arcCenter = CalcArcCenter( aStart, aMid, aEnd );
561 VECTOR2I new_mid = GetArcMid();
562
563 m_endsSwapped = false;
564
565 // Watch the ordering here. GetArcMid above needs to be called prior to initializing the
566 // m_arcMidData structure in order to ensure we get the calculated variant, not the cached
567 SetCachedArcData( aStart, aMid, aEnd, m_arcCenter );
568
569 /*
570 * If the input winding doesn't match our internal winding, the calculated midpoint will end
571 * up on the other side of the arc. In this case, we need to flip the start/end points and
572 * flag this change for the system.
573 */
574 VECTOR2D dist( new_mid - aMid );
575 VECTOR2D dist2( new_mid - m_arcCenter );
576
577 if( dist.SquaredEuclideanNorm() > dist2.SquaredEuclideanNorm() )
578 {
579 std::swap( m_start, m_end );
580 m_endsSwapped = true;
581 }
582}
583
584
586{
587 EDA_ANGLE startAngle;
588 EDA_ANGLE endAngle;
589
590 CalcArcAngles( startAngle, endAngle );
591
592 return endAngle - startAngle;
593}
594
595
596void EDA_SHAPE::SetArcAngleAndEnd( const EDA_ANGLE& aAngle, bool aCheckNegativeAngle )
597{
598 EDA_ANGLE angle( aAngle );
599
600 m_end = m_start;
601 RotatePoint( m_end, m_arcCenter, -angle.Normalize720() );
602
603 if( aCheckNegativeAngle && aAngle < ANGLE_0 )
604 {
605 std::swap( m_start, m_end );
606 m_endsSwapped = true;
607 }
608}
609
610
612{
613 switch( m_shape )
614 {
615 case SHAPE_T::CIRCLE: return _( "Circle" );
616 case SHAPE_T::ARC: return _( "Arc" );
617 case SHAPE_T::BEZIER: return _( "Curve" );
618 case SHAPE_T::POLY: return _( "Polygon" );
619 case SHAPE_T::RECT: return IsAnnotationProxy() ? _( "Pad Number Box" ) : _( "Rectangle" );
620 case SHAPE_T::SEGMENT: return _( "Segment" );
621 default: return _( "Unrecognized" );
622 }
623}
624
625
626void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
627{
628 ORIGIN_TRANSFORMS originTransforms = aFrame->GetOriginTransforms();
629 wxString msg;
630
631 wxString shape = _( "Shape" );
632 aList.emplace_back( shape, GetFriendlyName() );
633
634 switch( m_shape )
635 {
636 case SHAPE_T::CIRCLE:
637 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
638 break;
639
640 case SHAPE_T::ARC:
642 aList.emplace_back( _( "Angle" ), msg );
643
644 aList.emplace_back( _( "Radius" ), aFrame->MessageTextFromValue( GetRadius() ) );
645 break;
646
647 case SHAPE_T::BEZIER:
648 aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetLength() ) );
649 break;
650
651 case SHAPE_T::POLY:
652 msg.Printf( wxS( "%d" ), GetPolyShape().Outline(0).PointCount() );
653 aList.emplace_back( _( "Points" ), msg );
654 break;
655
656 case SHAPE_T::RECT:
657 aList.emplace_back( _( "Width" ),
658 aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
659
660 aList.emplace_back( _( "Height" ),
661 aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
662 break;
663
664 case SHAPE_T::SEGMENT:
665 {
666 aList.emplace_back( _( "Length" ),
668
669 // angle counter-clockwise from 3'o-clock
670 EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ),
671 (double)( GetEnd().x - GetStart().x ) ), RADIANS_T );
672 aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
673 break;
674 }
675
676 default:
677 break;
678 }
679
680 m_stroke.GetMsgPanelInfo( aFrame, aList );
681}
682
683
685{
686 BOX2I bbox;
687
688 switch( m_shape )
689 {
690 case SHAPE_T::RECT:
691 for( VECTOR2I& pt : GetRectCorners() )
692 bbox.Merge( pt );
693
694 break;
695
696 case SHAPE_T::SEGMENT:
697 bbox.SetOrigin( GetStart() );
698 bbox.SetEnd( GetEnd() );
699 break;
700
701 case SHAPE_T::CIRCLE:
702 bbox.SetOrigin( GetStart() );
703 bbox.Inflate( GetRadius() );
704 break;
705
706 case SHAPE_T::ARC:
707 computeArcBBox( bbox );
708 break;
709
710 case SHAPE_T::POLY:
711 if( m_poly.IsEmpty() )
712 break;
713
714 for( auto iter = m_poly.CIterate(); iter; iter++ )
715 {
716 VECTOR2I pt( iter->x, iter->y );
717
719 pt += getParentPosition();
720
721 bbox.Merge( pt );
722 }
723
724 break;
725
726 case SHAPE_T::BEZIER:
727 bbox.SetOrigin( GetStart() );
728 bbox.Merge( GetBezierC1() );
729 bbox.Merge( GetBezierC2() );
730 bbox.Merge( GetEnd() );
731 break;
732
733 default:
735 break;
736 }
737
738 bbox.Inflate( std::max( 0, GetWidth() ) / 2 );
739 bbox.Normalize();
740
741 return bbox;
742}
743
744
745bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
746{
747 int maxdist = aAccuracy;
748
749 if( GetWidth() > 0 )
750 maxdist += GetWidth() / 2;
751
752 switch( m_shape )
753 {
754 case SHAPE_T::CIRCLE:
755 {
756 int radius = GetRadius();
757
758 VECTOR2I::extended_type dist = KiROUND<double, VECTOR2I::extended_type>(
759 EuclideanNorm( aPosition - getCenter() ) );
760
761 if( IsFilled() )
762 return dist <= radius + maxdist; // Filled circle hit-test
763 else
764 return abs( radius - dist ) <= maxdist; // Ring hit-test
765 }
766
767 case SHAPE_T::ARC:
768 {
769 if( EuclideanNorm( aPosition - m_start ) <= maxdist )
770 return true;
771
772 if( EuclideanNorm( aPosition - m_end ) <= maxdist )
773 return true;
774
775 VECTOR2I relPos = aPosition - getCenter();
776 int radius = GetRadius();
777
779 KiROUND<double, VECTOR2I::extended_type>( EuclideanNorm( relPos ) );
780
781 if( IsFilled() )
782 {
783 // Check distance from arc center
784 if( dist > radius + maxdist )
785 return false;
786 }
787 else
788 {
789 // Check distance from arc circumference
790 if( abs( radius - dist ) > maxdist )
791 return false;
792 }
793
794 // Finally, check to see if it's within arc's swept angle.
795 EDA_ANGLE startAngle;
796 EDA_ANGLE endAngle;
797 CalcArcAngles( startAngle, endAngle );
798
799 EDA_ANGLE relPosAngle( relPos );
800
801 startAngle.Normalize();
802 endAngle.Normalize();
803 relPosAngle.Normalize();
804
805 if( endAngle > startAngle )
806 return relPosAngle >= startAngle && relPosAngle <= endAngle;
807 else
808 return relPosAngle >= startAngle || relPosAngle <= endAngle;
809 }
810
811 case SHAPE_T::BEZIER:
812 const_cast<EDA_SHAPE*>( this )->RebuildBezierToSegmentsPointsList( GetWidth() );
813
814 for( unsigned int i= 1; i < m_bezierPoints.size(); i++)
815 {
816 if( TestSegmentHit( aPosition, m_bezierPoints[ i - 1], m_bezierPoints[i], maxdist ) )
817 return true;
818 }
819
820 return false;
821
822 case SHAPE_T::SEGMENT:
823 return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
824
825 case SHAPE_T::RECT:
826 if( IsAnnotationProxy() || IsFilled() ) // Filled rect hit-test
827 {
828 SHAPE_POLY_SET poly;
829 poly.NewOutline();
830
831 for( const VECTOR2I& pt : GetRectCorners() )
832 poly.Append( pt );
833
834 return poly.Collide( aPosition, maxdist );
835 }
836 else // Open rect hit-test
837 {
838 std::vector<VECTOR2I> pts = GetRectCorners();
839
840 return TestSegmentHit( aPosition, pts[0], pts[1], maxdist )
841 || TestSegmentHit( aPosition, pts[1], pts[2], maxdist )
842 || TestSegmentHit( aPosition, pts[2], pts[3], maxdist )
843 || TestSegmentHit( aPosition, pts[3], pts[0], maxdist );
844 }
845
846 case SHAPE_T::POLY:
847 if( IsFilled() )
848 return m_poly.Collide( aPosition, maxdist );
849 else
850 return m_poly.CollideEdge( aPosition, nullptr, maxdist );
851
852 default:
854 return false;
855 }
856}
857
858
859bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
860{
861 BOX2I arect = aRect;
862 arect.Normalize();
863 arect.Inflate( aAccuracy );
864
865 BOX2I bbox = getBoundingBox();
866
867 switch( m_shape )
868 {
869 case SHAPE_T::CIRCLE:
870 // Test if area intersects or contains the circle:
871 if( aContained )
872 {
873 return arect.Contains( bbox );
874 }
875 else
876 {
877 // If the rectangle does not intersect the bounding box, this is a much quicker test
878 if( !arect.Intersects( bbox ) )
879 return false;
880 else
881 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
882 }
883
884 case SHAPE_T::ARC:
885 // Test for full containment of this arc in the rect
886 if( aContained )
887 {
888 return arect.Contains( bbox );
889 }
890 // Test if the rect crosses the arc
891 else
892 {
893 if( !arect.Intersects( bbox ) )
894 return false;
895
896 if( IsFilled() )
897 {
898 return ( arect.Intersects( getCenter(), GetStart() )
899 || arect.Intersects( getCenter(), GetEnd() )
900 || arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() ) );
901 }
902 else
903 {
904 return arect.IntersectsCircleEdge( getCenter(), GetRadius(), GetWidth() );
905 }
906 }
907
908 case SHAPE_T::RECT:
909 if( aContained )
910 {
911 return arect.Contains( bbox );
912 }
913 else
914 {
915 std::vector<VECTOR2I> pts = GetRectCorners();
916
917 // Account for the width of the lines
918 arect.Inflate( GetWidth() / 2 );
919 return ( arect.Intersects( pts[0], pts[1] )
920 || arect.Intersects( pts[1], pts[2] )
921 || arect.Intersects( pts[2], pts[3] )
922 || arect.Intersects( pts[3], pts[0] ) );
923 }
924
925 case SHAPE_T::SEGMENT:
926 if( aContained )
927 {
928 return arect.Contains( GetStart() ) && aRect.Contains( GetEnd() );
929 }
930 else
931 {
932 // Account for the width of the line
933 arect.Inflate( GetWidth() / 2 );
934 return arect.Intersects( GetStart(), GetEnd() );
935 }
936
937 case SHAPE_T::POLY:
938 if( aContained )
939 {
940 return arect.Contains( bbox );
941 }
942 else
943 {
944 // Fast test: if aRect is outside the polygon bounding box,
945 // rectangles cannot intersect
946 if( !arect.Intersects( bbox ) )
947 return false;
948
949 // Account for the width of the line
950 arect.Inflate( GetWidth() / 2 );
951
952 // Polygons in footprints use coordinates relative to the footprint.
953 // Therefore, instead of using m_poly, we make a copy which is translated
954 // to the actual location in the board.
955 VECTOR2I offset = getParentPosition();
956
957 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
958 {
959 SHAPE_LINE_CHAIN poly = m_poly.Outline( ii );
961 poly.Move( offset );
962
963 int count = poly.GetPointCount();
964
965 for( int jj = 0; jj < count; jj++ )
966 {
967 VECTOR2I vertex = poly.GetPoint( jj );
968
969 // Test if the point is within aRect
970 if( arect.Contains( vertex ) )
971 return true;
972
973 if( jj + 1 < count )
974 {
975 VECTOR2I vertexNext = poly.GetPoint( jj + 1 );
976
977 // Test if this edge intersects aRect
978 if( arect.Intersects( vertex, vertexNext ) )
979 return true;
980 }
981 else if( poly.IsClosed() )
982 {
983 VECTOR2I vertexNext = poly.GetPoint( 0 );
984
985 // Test if this edge intersects aRect
986 if( arect.Intersects( vertex, vertexNext ) )
987 return true;
988 }
989 }
990 }
991
992 return false;
993 }
994
995 case SHAPE_T::BEZIER:
996 if( aContained )
997 {
998 return arect.Contains( bbox );
999 }
1000 else
1001 {
1002 // Fast test: if aRect is outside the polygon bounding box,
1003 // rectangles cannot intersect
1004 if( !arect.Intersects( bbox ) )
1005 return false;
1006
1007 // Account for the width of the line
1008 arect.Inflate( GetWidth() / 2 );
1009 unsigned count = m_bezierPoints.size();
1010
1011 for( unsigned ii = 1; ii < count; ii++ )
1012 {
1013 VECTOR2I vertex = m_bezierPoints[ii - 1];
1014 VECTOR2I vertexNext = m_bezierPoints[ii];
1015
1016 // Test if the point is within aRect
1017 if( arect.Contains( vertex ) )
1018 return true;
1019
1020 // Test if this edge intersects aRect
1021 if( arect.Intersects( vertex, vertexNext ) )
1022 return true;
1023 }
1024
1025 return false;
1026 }
1027
1028 default:
1030 return false;
1031 }
1032}
1033
1034
1035std::vector<VECTOR2I> EDA_SHAPE::GetRectCorners() const
1036{
1037 std::vector<VECTOR2I> pts;
1038 VECTOR2I topLeft = GetStart();
1039 VECTOR2I botRight = GetEnd();
1040
1041 // Un-rotate rect topLeft and botRight
1042 if( !getParentOrientation().IsCardinal() )
1043 {
1044 topLeft -= getParentPosition();
1045 RotatePoint( topLeft, -getParentOrientation() );
1046
1047 botRight -= getParentPosition();
1048 RotatePoint( botRight, -getParentOrientation() );
1049 }
1050
1051 // Set up the un-rotated 4 corners
1052 pts.emplace_back( topLeft );
1053 pts.emplace_back( botRight.x, topLeft.y );
1054 pts.emplace_back( botRight );
1055 pts.emplace_back( topLeft.x, botRight.y );
1056
1057 // Now re-rotate the 4 corners to get a diamond
1058 if( !getParentOrientation().IsCardinal() )
1059 {
1060 for( VECTOR2I& pt : pts )
1061 {
1063 pt += getParentPosition();
1064 }
1065 }
1066
1067 return pts;
1068}
1069
1070
1072{
1073 // Start, end, and each inflection point the arc crosses will enclose the entire arc.
1074 // Only include the center when filled; it's not necessarily inside the BB of an unfilled
1075 // arc with a small included angle.
1076 aBBox.SetOrigin( m_start );
1077 aBBox.Merge( m_end );
1078
1079 if( IsFilled() )
1080 aBBox.Merge( m_arcCenter );
1081
1082 int radius = GetRadius();
1083 EDA_ANGLE t1, t2;
1084
1085 CalcArcAngles( t1, t2 );
1086
1087 t1.Normalize();
1088 t2.Normalize();
1089
1090 if( t2 > t1 )
1091 {
1092 if( t1 < ANGLE_0 && t2 > ANGLE_0 )
1093 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1094
1095 if( t1 < ANGLE_90 && t2 > ANGLE_90 )
1096 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1097
1098 if( t1 < ANGLE_180 && t2 > ANGLE_180 )
1099 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1100
1101 if( t1 < ANGLE_270 && t2 > ANGLE_270 )
1102 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1103 }
1104 else
1105 {
1106 if( t1 < ANGLE_0 || t2 > ANGLE_0 )
1107 aBBox.Merge( VECTOR2I( m_arcCenter.x + radius, m_arcCenter.y ) ); // right
1108
1109 if( t1 < ANGLE_90 || t2 > ANGLE_90 )
1110 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y + radius ) ); // down
1111
1112 if( t1 < ANGLE_180 || t2 > ANGLE_180 )
1113 aBBox.Merge( VECTOR2I( m_arcCenter.x - radius, m_arcCenter.y ) ); // left
1114
1115 if( t1 < ANGLE_270 || t2 > ANGLE_270 )
1116 aBBox.Merge( VECTOR2I( m_arcCenter.x, m_arcCenter.y - radius ) ); // up
1117 }
1118}
1119
1120
1121void EDA_SHAPE::SetPolyPoints( const std::vector<VECTOR2I>& aPoints )
1122{
1125
1126 for( const VECTOR2I& p : aPoints )
1127 m_poly.Append( p.x, p.y );
1128}
1129
1130
1131std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineChainOnly ) const
1132{
1133 std::vector<SHAPE*> effectiveShapes;
1134 int width = GetEffectiveWidth();
1135
1136 switch( m_shape )
1137 {
1138 case SHAPE_T::ARC:
1139 effectiveShapes.emplace_back( new SHAPE_ARC( m_arcCenter, m_start, GetArcAngle(), width ) );
1140 break;
1141
1142 case SHAPE_T::SEGMENT:
1143 effectiveShapes.emplace_back( new SHAPE_SEGMENT( m_start, m_end, width ) );
1144 break;
1145
1146 case SHAPE_T::RECT:
1147 {
1148 std::vector<VECTOR2I> pts = GetRectCorners();
1149
1150 if( ( IsFilled() || IsAnnotationProxy() ) && !aEdgeOnly )
1151 effectiveShapes.emplace_back( new SHAPE_SIMPLE( pts ) );
1152
1153 if( width > 0 || !IsFilled() || aEdgeOnly )
1154 {
1155 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[0], pts[1], width ) );
1156 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[1], pts[2], width ) );
1157 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[2], pts[3], width ) );
1158 effectiveShapes.emplace_back( new SHAPE_SEGMENT( pts[3], pts[0], width ) );
1159 }
1160 }
1161 break;
1162
1163 case SHAPE_T::CIRCLE:
1164 {
1165 if( IsFilled() && !aEdgeOnly )
1166 effectiveShapes.emplace_back( new SHAPE_CIRCLE( getCenter(), GetRadius() ) );
1167
1168 if( width > 0 || !IsFilled() || aEdgeOnly )
1169 effectiveShapes.emplace_back( new SHAPE_ARC( getCenter(), GetEnd(), ANGLE_360, width ) );
1170
1171 break;
1172 }
1173
1174 case SHAPE_T::BEZIER:
1175 {
1176 std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( width );
1177 VECTOR2I start_pt = bezierPoints[0];
1178
1179 for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
1180 {
1181 VECTOR2I end_pt = bezierPoints[jj];
1182 effectiveShapes.emplace_back( new SHAPE_SEGMENT( start_pt, end_pt, width ) );
1183 start_pt = end_pt;
1184 }
1185
1186 break;
1187 }
1188
1189 case SHAPE_T::POLY:
1190 {
1191 if( GetPolyShape().OutlineCount() == 0 ) // malformed/empty polygon
1192 break;
1193
1194 for( int ii = 0; ii < GetPolyShape().OutlineCount(); ++ii )
1195 {
1197
1198 if( aLineChainOnly )
1199 l.SetClosed( false );
1200
1202 l.Move( getParentPosition() );
1203
1204 if( IsFilled() && !aEdgeOnly )
1205 effectiveShapes.emplace_back( new SHAPE_SIMPLE( l ) );
1206
1207 if( width > 0 || !IsFilled() || aEdgeOnly )
1208 {
1209 for( int jj = 0; jj < l.SegmentCount(); jj++ )
1210 effectiveShapes.emplace_back( new SHAPE_SEGMENT( l.Segment( jj ), width ) );
1211 }
1212 }
1213 }
1214 break;
1215
1216 default:
1218 break;
1219 }
1220
1221 return effectiveShapes;
1222}
1223
1224
1225void EDA_SHAPE::DupPolyPointsList( std::vector<VECTOR2I>& aBuffer ) const
1226{
1227 for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
1228 {
1229 int pointCount = m_poly.COutline( ii ).PointCount();
1230
1231 if( pointCount )
1232 {
1233 aBuffer.reserve( pointCount );
1234
1235 for ( auto iter = m_poly.CIterate(); iter; iter++ )
1236 aBuffer.emplace_back( iter->x, iter->y );
1237 }
1238 }
1239}
1240
1241
1243{
1244 // return true if the polygonal shape is valid (has more than 2 points)
1245 if( GetPolyShape().OutlineCount() == 0 )
1246 return false;
1247
1248 const SHAPE_LINE_CHAIN& outline = static_cast<const SHAPE_POLY_SET&>( GetPolyShape() ).Outline( 0 );
1249
1250 return outline.PointCount() > 2;
1251}
1252
1253
1255{
1256 // return the number of corners of the polygonal shape
1257 // this shape is expected to be only one polygon without hole
1258 if( GetPolyShape().OutlineCount() )
1259 return GetPolyShape().VertexCount( 0 );
1260
1261 return 0;
1262}
1263
1264
1265void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
1266{
1267 switch( GetShape() )
1268 {
1269 case SHAPE_T::SEGMENT:
1270 case SHAPE_T::CIRCLE:
1271 case SHAPE_T::RECT:
1272 SetStart( aPosition );
1273 SetEnd( aPosition );
1274 break;
1275
1276 case SHAPE_T::ARC:
1277 SetArcGeometry( aPosition, aPosition, aPosition );
1278 m_editState = 1;
1279 break;
1280
1281 case SHAPE_T::POLY:
1283 m_poly.Outline( 0 ).SetClosed( false );
1284
1285 // Start and end of the first segment (co-located for now)
1286 m_poly.Outline( 0 ).Append( aPosition );
1287 m_poly.Outline( 0 ).Append( aPosition, true );
1288 break;
1289
1290 default:
1292 }
1293}
1294
1295
1296bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
1297{
1298 switch( GetShape() )
1299 {
1300 case SHAPE_T::ARC:
1301 case SHAPE_T::SEGMENT:
1302 case SHAPE_T::CIRCLE:
1303 case SHAPE_T::RECT:
1304 return false;
1305
1306 case SHAPE_T::POLY:
1307 {
1308 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
1309
1310 // do not add zero-length segments
1311 if( poly.CPoint( poly.GetPointCount() - 2 ) != poly.CLastPoint() )
1312 poly.Append( aPosition, true );
1313 }
1314 return true;
1315
1316 default:
1318 return false;
1319 }
1320}
1321
1322
1323void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
1324{
1325#define sq( x ) pow( x, 2 )
1326
1327 switch( GetShape() )
1328 {
1329 case SHAPE_T::SEGMENT:
1330 case SHAPE_T::CIRCLE:
1331 case SHAPE_T::RECT:
1332 SetEnd( aPosition );
1333 break;
1334
1335 case SHAPE_T::ARC:
1336 {
1337 int radius = GetRadius();
1338
1339 // Edit state 0: drawing: place start
1340 // Edit state 1: drawing: place end (center calculated for 90-degree subtended angle)
1341 // Edit state 2: point edit: move start (center calculated for invariant subtended angle)
1342 // Edit state 3: point edit: move end (center calculated for invariant subtended angle)
1343 // Edit state 4: point edit: move center
1344 // Edit state 5: point edit: move arc-mid-point
1345
1346 switch( m_editState )
1347 {
1348 case 0:
1349 SetArcGeometry( aPosition, aPosition, aPosition );
1350 return;
1351
1352 case 1:
1353 m_end = aPosition;
1354 radius = KiROUND( sqrt( sq( GetLineLength( m_start, m_end ) ) / 2.0 ) );
1355 break;
1356
1357 case 2:
1358 case 3:
1359 {
1360 VECTOR2I v = m_start - m_end;
1361 double chordBefore = sq( v.x ) + sq( v.y );
1362
1363 if( m_editState == 2 )
1364 m_start = aPosition;
1365 else
1366 m_end = aPosition;
1367
1368 v = m_start - m_end;
1369 double chordAfter = sq( v.x ) + sq( v.y );
1370 double ratio = chordAfter / chordBefore;
1371
1372 if( ratio != 0 )
1373 {
1374 radius = std::max( int( sqrt( sq( radius ) * ratio ) ) + 1,
1375 int( sqrt( chordAfter ) / 2 ) + 1 );
1376 }
1377 }
1378 break;
1379
1380 case 4:
1381 {
1382 double radialA = GetLineLength( m_start, aPosition );
1383 double radialB = GetLineLength( m_end, aPosition );
1384 radius = int( ( radialA + radialB ) / 2.0 ) + 1;
1385 }
1386 break;
1387
1388 case 5:
1389 SetArcGeometry( GetStart(), aPosition, GetEnd() );
1390 return;
1391 }
1392
1393 // Calculate center based on start, end, and radius
1394 //
1395 // Let 'l' be the length of the chord and 'm' the middle point of the chord
1396 double l = GetLineLength( m_start, m_end );
1397 VECTOR2I m = ( m_start + m_end ) / 2;
1398
1399 // Calculate 'd', the vector from the chord midpoint to the center
1400 VECTOR2I d;
1401 d.x = KiROUND( sqrt( sq( radius ) - sq( l/2 ) ) * ( m_start.y - m_end.y ) / l );
1402 d.y = KiROUND( sqrt( sq( radius ) - sq( l/2 ) ) * ( m_end.x - m_start.x ) / l );
1403
1404 VECTOR2I c1 = m + d;
1405 VECTOR2I c2 = m - d;
1406
1407 // Solution gives us 2 centers; we need to pick one:
1408 switch( m_editState )
1409 {
1410 case 1:
1411 // Keep arc clockwise while drawing i.e. arc angle = 90 deg.
1412 // it can be 90 or 270 deg depending on the arc center choice (c1 or c2)
1413 m_arcCenter = c1; // first trial
1414
1415 if( GetArcAngle() > ANGLE_180 )
1416 m_arcCenter = c2;
1417
1418 break;
1419
1420 case 2:
1421 case 3:
1422 // Pick the one of c1, c2 to keep arc <= 180 deg
1423 m_arcCenter = c1; // first trial
1424
1425 if( GetArcAngle() > ANGLE_180 )
1426 m_arcCenter = c2;
1427
1428 break;
1429
1430 case 4:
1431 // Pick the one closer to the mouse position
1432 m_arcCenter = GetLineLength( c1, aPosition ) < GetLineLength( c2, aPosition ) ? c1 : c2;
1433
1434 // keep arc angle <= 180 deg
1435 if( GetArcAngle() > ANGLE_180 )
1436 std::swap( m_start, m_end );
1437
1438 break;
1439 }
1440 }
1441 break;
1442
1443 case SHAPE_T::POLY:
1444 m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
1445 break;
1446
1447 default:
1449 }
1450}
1451
1452
1453void EDA_SHAPE::endEdit( bool aClosed )
1454{
1455 switch( GetShape() )
1456 {
1457 case SHAPE_T::ARC:
1458 case SHAPE_T::SEGMENT:
1459 case SHAPE_T::CIRCLE:
1460 case SHAPE_T::RECT:
1461 break;
1462
1463 case SHAPE_T::POLY:
1464 {
1465 SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
1466
1467 // do not include last point twice
1468 if( poly.GetPointCount() > 2 )
1469 {
1470 if( poly.CPoint( poly.GetPointCount() - 2 ) == poly.CLastPoint() )
1471 {
1472 poly.SetClosed( aClosed );
1473 poly.Remove( poly.GetPointCount() - 1 );
1474 }
1475 }
1476 }
1477 break;
1478
1479 default:
1481 }
1482}
1483
1484
1486{
1487 EDA_SHAPE* image = dynamic_cast<EDA_SHAPE*>( aImage );
1488 assert( image );
1489
1490 #define SWAPITEM( x ) std::swap( x, image->x )
1491 SWAPITEM( m_stroke );
1492 SWAPITEM( m_start );
1493 SWAPITEM( m_end );
1495 SWAPITEM( m_shape );
1499 SWAPITEM( m_poly );
1500 SWAPITEM( m_fill );
1504 #undef SWAPITEM
1505}
1506
1507
1508int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
1509{
1510#define EPSILON 2 // Should be enough for rounding errors on calculated items
1511
1512#define TEST( a, b ) { if( a != b ) return a - b; }
1513#define TEST_E( a, b ) { if( abs( a - b ) > EPSILON ) return a - b; }
1514#define TEST_PT( a, b ) { TEST_E( a.x, b.x ); TEST_E( a.y, b.y ); }
1515
1516 TEST_PT( m_start, aOther->m_start );
1517 TEST_PT( m_end, aOther->m_end );
1518
1519 TEST( (int) m_shape, (int) aOther->m_shape );
1520
1521 if( m_shape == SHAPE_T::ARC )
1522 {
1523 TEST_PT( m_arcCenter, aOther->m_arcCenter );
1524 }
1525 else if( m_shape == SHAPE_T::BEZIER )
1526 {
1527 TEST_PT( m_bezierC1, aOther->m_bezierC1 );
1528 TEST_PT( m_bezierC2, aOther->m_bezierC2 );
1529 }
1530 else if( m_shape == SHAPE_T::POLY )
1531 {
1533 }
1534
1535 for( size_t ii = 0; ii < m_bezierPoints.size(); ++ii )
1536 TEST_PT( m_bezierPoints[ii], aOther->m_bezierPoints[ii] );
1537
1538 for( int ii = 0; ii < m_poly.TotalVertices(); ++ii )
1539 TEST_PT( m_poly.CVertex( ii ), aOther->m_poly.CVertex( ii ) );
1540
1541 TEST_E( m_stroke.GetWidth(), aOther->m_stroke.GetWidth() );
1542 TEST( (int) m_stroke.GetPlotStyle(), (int) aOther->m_stroke.GetPlotStyle() );
1543 TEST( (int) m_fill, (int) aOther->m_fill );
1544
1545 return 0;
1546}
1547
1548
1549void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
1550 ERROR_LOC aErrorLoc, bool ignoreLineWidth ) const
1551{
1552 int width = ignoreLineWidth ? 0 : GetWidth();
1553
1554 width += 2 * aClearance;
1555
1556 switch( m_shape )
1557 {
1558 case SHAPE_T::CIRCLE:
1559 {
1560 int r = GetRadius();
1561
1562 if( IsFilled() )
1563 TransformCircleToPolygon( aBuffer, getCenter(), r + width / 2, aError, aErrorLoc );
1564 else
1565 TransformRingToPolygon( aBuffer, getCenter(), r, width, aError, aErrorLoc );
1566
1567 break;
1568 }
1569
1570 case SHAPE_T::RECT:
1571 {
1572 std::vector<VECTOR2I> pts = GetRectCorners();
1573
1574 if( IsFilled() || IsAnnotationProxy() )
1575 {
1576 aBuffer.NewOutline();
1577
1578 for( const VECTOR2I& pt : pts )
1579 aBuffer.Append( pt );
1580 }
1581
1582 if( width > 0 || !IsFilled() )
1583 {
1584 // Add in segments
1585 TransformOvalToPolygon( aBuffer, pts[0], pts[1], width, aError, aErrorLoc );
1586 TransformOvalToPolygon( aBuffer, pts[1], pts[2], width, aError, aErrorLoc );
1587 TransformOvalToPolygon( aBuffer, pts[2], pts[3], width, aError, aErrorLoc );
1588 TransformOvalToPolygon( aBuffer, pts[3], pts[0], width, aError, aErrorLoc );
1589 }
1590
1591 break;
1592 }
1593
1594 case SHAPE_T::ARC:
1595 TransformArcToPolygon( aBuffer, GetStart(), GetArcMid(), GetEnd(), width, aError, aErrorLoc );
1596 break;
1597
1598 case SHAPE_T::SEGMENT:
1599 TransformOvalToPolygon( aBuffer, GetStart(), GetEnd(), width, aError, aErrorLoc );
1600 break;
1601
1602 case SHAPE_T::POLY:
1603 {
1604 if( !IsPolyShapeValid() )
1605 break;
1606
1607 // The polygon is expected to be a simple polygon; not self intersecting, no hole.
1608 EDA_ANGLE orientation = getParentOrientation();
1609 VECTOR2I offset = getParentPosition();
1610
1611 // Build the polygon with the actual position and orientation:
1612 std::vector<VECTOR2I> poly;
1613 DupPolyPointsList( poly );
1614
1615 for( VECTOR2I& point : poly )
1616 {
1617 RotatePoint( point, orientation );
1618 point += offset;
1619 }
1620
1621 if( IsFilled() )
1622 {
1623 aBuffer.NewOutline();
1624
1625 for( const VECTOR2I& point : poly )
1626 aBuffer.Append( point.x, point.y );
1627 }
1628
1629 if( width > 0 || !IsFilled() )
1630 {
1631 VECTOR2I pt1( poly[poly.size() - 1] );
1632
1633 for( const VECTOR2I& pt2 : poly )
1634 {
1635 if( pt2 != pt1 )
1636 TransformOvalToPolygon( aBuffer, pt1, pt2, width, aError, aErrorLoc );
1637
1638 pt1 = pt2;
1639 }
1640 }
1641
1642 break;
1643 }
1644
1645 case SHAPE_T::BEZIER:
1646 {
1647 std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
1648 BEZIER_POLY converter( ctrlPts );
1649 std::vector<VECTOR2I> poly;
1650 converter.GetPoly( poly, GetWidth() );
1651
1652 for( unsigned ii = 1; ii < poly.size(); ii++ )
1653 TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
1654
1655 break;
1656 }
1657
1658 default:
1660 break;
1661 }
1662}
1663
1664
1667
1668
1669static struct EDA_SHAPE_DESC
1670{
1672 {
1674 .Map( SHAPE_T::SEGMENT, _HKI( "Segment" ) )
1675 .Map( SHAPE_T::RECT, _HKI( "Rectangle" ) )
1676 .Map( SHAPE_T::ARC, _HKI( "Arc" ) )
1677 .Map( SHAPE_T::CIRCLE, _HKI( "Circle" ) )
1678 .Map( SHAPE_T::POLY, _HKI( "Polygon" ) )
1679 .Map( SHAPE_T::BEZIER, _HKI( "Bezier" ) );
1681 .Map( PLOT_DASH_TYPE::DEFAULT, _HKI( "Default" ) )
1682 .Map( PLOT_DASH_TYPE::SOLID, _HKI( "Solid" ) )
1683 .Map( PLOT_DASH_TYPE::DASH, _HKI( "Dashed" ) )
1684 .Map( PLOT_DASH_TYPE::DOT, _HKI( "Dotted" ) )
1685 .Map( PLOT_DASH_TYPE::DASHDOT, _HKI( "Dash-Dot" ) )
1686 .Map( PLOT_DASH_TYPE::DASHDOTDOT, _HKI( "Dash-Dot-Dot" ) );
1687
1690 auto shape = new PROPERTY_ENUM<EDA_SHAPE, SHAPE_T>( _HKI( "Shape" ),
1692 propMgr.AddProperty( shape );
1693 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start X" ),
1696 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Start Y" ),
1699 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End X" ),
1702 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "End Y" ),
1705 // TODO: m_arcCenter, m_bezierC1, m_bezierC2, m_poly
1706 propMgr.AddProperty( new PROPERTY<EDA_SHAPE, int>( _HKI( "Line Width" ),
1708
1709 auto angle = new PROPERTY<EDA_SHAPE, EDA_ANGLE>( _HKI( "Angle" ),
1712 angle->SetAvailableFunc(
1713 [=]( INSPECTABLE* aItem ) -> bool
1714 {
1715 return aItem->Get<SHAPE_T>( shape ) == SHAPE_T::ARC;
1716 } );
1717 propMgr.AddProperty( angle );
1718
1719 auto filled = new PROPERTY<EDA_SHAPE, bool>( _HKI( "Filled" ),
1721 filled->SetAvailableFunc(
1722 [=]( INSPECTABLE* aItem ) -> bool
1723 {
1724 SHAPE_T itemShape = aItem->Get<SHAPE_T>( shape );
1725
1726 switch( itemShape )
1727 {
1728 case SHAPE_T::POLY:
1729 case SHAPE_T::RECT:
1730 case SHAPE_T::CIRCLE:
1731 return true;
1732
1733 default:
1734 return false;
1735 }
1736 } );
1737
1738 propMgr.AddProperty( filled );
1739 }
Bezier curves to polygon converter.
Definition: bezier_curves.h:38
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMinSegLen=0, int aMaxSegCount=32)
Convert a Bezier curve to a polygon.
void SetOrigin(const Vec &pos)
Definition: box2.h:202
BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition: box2.h:119
bool Intersects(const BOX2< Vec > &aRect) const
Definition: box2.h:269
bool IntersectsCircleEdge(const Vec &aCenter, const int aRadius, const int aWidth) const
Definition: box2.h:471
bool Contains(const Vec &aPoint) const
Definition: box2.h:141
BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition: box2.h:506
Vec Centre() const
Definition: box2.h:70
void SetEnd(coord_type x, coord_type y)
Definition: box2.h:255
BOX2< Vec > & Merge(const BOX2< Vec > &aRect)
Modify the position and size of the rectangle in order to contain aRect.
Definition: box2.h:588
EDA_ANGLE Normalize()
Definition: eda_angle.h:249
bool IsCardinal() const
Definition: eda_angle.cpp:49
double AsRadians() const
Definition: eda_angle.h:153
ORIGIN_TRANSFORMS & GetOriginTransforms() override
Return a reference to the default ORIGIN_TRANSFORMS object.
The base class for create windows for drawing purpose.
EDA_ANGLE GetArcAngle() const
Definition: eda_shape.cpp:585
SHAPE_T m_shape
Definition: eda_shape.h:364
virtual VECTOR2I getParentPosition() const =0
wxString GetFriendlyName() const
Definition: eda_shape.cpp:611
void SetStartX(int x)
Definition: eda_shape.h:136
bool m_endsSwapped
Definition: eda_shape.h:363
const VECTOR2I & GetBezierC2() const
Definition: eda_shape.h:179
virtual EDA_ANGLE getParentOrientation() const =0
void move(const VECTOR2I &aMoveVector)
Definition: eda_shape.cpp:173
void SetCenter(const VECTOR2I &aCenter)
Definition: eda_shape.cpp:470
VECTOR2I getCenter() const
Definition: eda_shape.cpp:444
int GetStartY() const
Definition: eda_shape.h:121
int m_editState
Definition: eda_shape.h:381
void rotate(const VECTOR2I &aRotCentre, const EDA_ANGLE &aAngle)
Definition: eda_shape.cpp:263
void flip(const VECTOR2I &aCentre, bool aFlipLeftRight)
Definition: eda_shape.cpp:320
virtual ~EDA_SHAPE()
Definition: eda_shape.cpp:52
void SetEndY(int y)
Definition: eda_shape.h:155
void RebuildBezierToSegmentsPointsList(int aMinSegLen)
Rebuild the m_bezierPoints vertex list that approximate the Bezier curve by a list of segments.
Definition: eda_shape.cpp:405
virtual int GetEffectiveWidth() const
Definition: eda_shape.h:110
int GetEndX() const
Definition: eda_shape.h:147
const std::vector< VECTOR2I > buildBezierToSegmentsPointsList(int aMinSegLen) const
Definition: eda_shape.cpp:431
void calcEdit(const VECTOR2I &aPosition)
Definition: eda_shape.cpp:1323
void SetStartY(int y)
Definition: eda_shape.h:130
SHAPE_POLY_SET & GetPolyShape()
Definition: eda_shape.h:247
bool IsFilled() const
Definition: eda_shape.h:90
void CalcArcAngles(EDA_ANGLE &aStartAngle, EDA_ANGLE &aEndAngle) const
Calc arc start and end angles such that aStartAngle < aEndAngle.
Definition: eda_shape.cpp:502
void ShapeGetMsgPanelInfo(EDA_DRAW_FRAME *aFrame, std::vector< MSG_PANEL_ITEM > &aList)
Definition: eda_shape.cpp:626
void SetFilled(bool aFlag)
Definition: eda_shape.h:95
int GetRadius() const
Definition: eda_shape.cpp:523
SHAPE_T GetShape() const
Definition: eda_shape.h:113
VECTOR2I m_arcCenter
Definition: eda_shape.h:372
bool continueEdit(const VECTOR2I &aPosition)
Definition: eda_shape.cpp:1296
wxString ShowShape() const
Definition: eda_shape.cpp:57
ARC_MID m_arcMidData
Definition: eda_shape.h:373
int GetEndY() const
Definition: eda_shape.h:146
std::vector< SHAPE * > makeEffectiveShapes(bool aEdgeOnly, bool aLineChainOnly=false) const
Make a set of SHAPE objects representing the EDA_SHAPE.
Definition: eda_shape.cpp:1131
bool hitTest(const VECTOR2I &aPosition, int aAccuracy=0) const
Definition: eda_shape.cpp:745
void SetCachedArcData(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, const VECTOR2I &aCenter)
Set the data used for mid point caching.
Definition: eda_shape.cpp:546
EDA_SHAPE(SHAPE_T aType, int aLineWidth, FILL_T aFill)
Definition: eda_shape.cpp:40
void beginEdit(const VECTOR2I &aStartPoint)
Definition: eda_shape.cpp:1265
void SetEndX(int x)
Definition: eda_shape.h:161
VECTOR2I m_start
Definition: eda_shape.h:369
int GetPointCount() const
Definition: eda_shape.cpp:1254
const VECTOR2I & GetEnd() const
Return the ending point of the graphic.
Definition: eda_shape.h:145
bool IsClosed() const
Definition: eda_shape.cpp:141
void SetStart(const VECTOR2I &aStart)
Definition: eda_shape.h:124
void DupPolyPointsList(std::vector< VECTOR2I > &aBuffer) const
Duplicate the list of corners in a std::vector<VECTOR2I>
Definition: eda_shape.cpp:1225
void endEdit(bool aClosed=true)
Finishes editing the shape.
Definition: eda_shape.cpp:1453
const VECTOR2I & GetStart() const
Return the starting point of the graphic.
Definition: eda_shape.h:120
void SwapShape(EDA_SHAPE *aImage)
Definition: eda_shape.cpp:1485
std::vector< VECTOR2I > GetRectCorners() const
Definition: eda_shape.cpp:1035
std::vector< VECTOR2I > m_bezierPoints
Definition: eda_shape.h:378
int GetWidth() const
Definition: eda_shape.h:109
void setPosition(const VECTOR2I &aPos)
Definition: eda_shape.cpp:93
void computeArcBBox(BOX2I &aBBox) const
Definition: eda_shape.cpp:1071
void SetEnd(const VECTOR2I &aEnd)
Definition: eda_shape.h:149
void SetArcGeometry(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Set the three controlling points for an arc.
Definition: eda_shape.cpp:555
double GetLength() const
Return the length of the track using the hypotenuse calculation.
Definition: eda_shape.cpp:110
wxString SHAPE_T_asString() const
Definition: eda_shape.cpp:75
void scale(double aScale)
Definition: eda_shape.cpp:208
int GetStartX() const
Definition: eda_shape.h:122
const VECTOR2I & GetBezierC1() const
Definition: eda_shape.h:176
VECTOR2I m_end
Definition: eda_shape.h:370
const BOX2I getBoundingBox() const
Definition: eda_shape.cpp:684
void SetArcAngleAndEnd(const EDA_ANGLE &aAngle, bool aCheckNegativeAngle=false)
Set the end point from the angle center and start.
Definition: eda_shape.cpp:596
SHAPE_POLY_SET m_poly
Definition: eda_shape.h:379
VECTOR2I getPosition() const
Definition: eda_shape.cpp:99
STROKE_PARAMS m_stroke
Definition: eda_shape.h:365
bool IsAnnotationProxy() const
Definition: eda_shape.h:87
void SetPolyPoints(const std::vector< VECTOR2I > &aPoints)
Definition: eda_shape.cpp:1121
void TransformShapeToPolygon(SHAPE_POLY_SET &aBuffer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool ignoreLineWidth=false) const
Convert the shape to a closed polygon.
Definition: eda_shape.cpp:1549
VECTOR2I m_bezierC1
Definition: eda_shape.h:375
FILL_T m_fill
Definition: eda_shape.h:366
COLOR4D m_fillColor
Definition: eda_shape.h:367
void SetWidth(int aWidth)
Definition: eda_shape.h:108
VECTOR2I m_bezierC2
Definition: eda_shape.h:376
bool IsPolyShapeValid() const
Definition: eda_shape.cpp:1242
int Compare(const EDA_SHAPE *aOther) const
Definition: eda_shape.cpp:1508
VECTOR2I GetArcMid() const
Definition: eda_shape.cpp:488
static ENUM_MAP< T > & Instance()
Definition: property.h:573
Class that other classes need to inherit from, in order to be inspectable.
Definition: inspectable.h:36
wxAny Get(PROPERTY_BASE *aProperty) const
Definition: inspectable.h:84
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
A class to perform either relative or absolute display origin transforms for a single axis of a point...
Provide class metadata.Helper macro to map type hashes to names.
Definition: property_mgr.h:74
static PROPERTY_MANAGER & Instance()
Definition: property_mgr.h:76
void AddProperty(PROPERTY_BASE *aProperty, const wxString &aGroup=wxEmptyString)
Register a property.
int Length() const
Return the length (this).
Definition: seg.h:326
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
void Move(const VECTOR2I &aVector) override
bool IsClosed() const override
virtual const VECTOR2I GetPoint(int aIndex) const override
void SetPoint(int aIndex, const VECTOR2I &aPos)
Move a point to a specific location.
void SetClosed(bool aClosed)
Mark the line chain as closed (i.e.
int PointCount() const
Return the number of points (vertices) in this line chain.
virtual size_t GetPointCount() const override
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.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
int SegmentCount() const
Return the number of segments in this line chain.
const VECTOR2I & CLastPoint() const
Return the last point in the line chain.
void Remove(int aStartIndex, int aEndIndex)
Remove the range of points [start_index, end_index] from the line chain.
const SEG CSegment(int aIndex) const
Return a constant copy of the aIndex segment in the line chain.
const std::vector< VECTOR2I > & CPoints() const
SEG Segment(int aIndex)
Return a copy of the aIndex-th segment in the line chain.
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
bool CollideEdge(const VECTOR2I &aPoint, VERTEX_INDEX *aClosestVertex=nullptr, int aClearance=0) const
Check whether aPoint collides with any edge of any of the contours of the polygon.
int VertexCount(int aOutline=-1, int aHole=-1) const
Return the number of points in the shape poly set.
bool IsEmpty() const
CONST_ITERATOR CIterate(int aFirst, int aLast, bool aIterateHoles=false) const
bool Collide(const SHAPE *aShape, int aClearance=0, int *aActual=nullptr, VECTOR2I *aLocation=nullptr) const override
Check if the boundary of shape (this) lies closer to the shape aShape than aClearance,...
int TotalVertices() const
Delete aIdx-th polygon from the set.
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Add a new vertex to the contour indexed by aOutline and aHole (defaults to the outline of the last po...
void Mirror(bool aX=true, bool aY=false, const VECTOR2I &aRef={ 0, 0 })
Mirror the line points about y or x (or both)
SHAPE_LINE_CHAIN & Outline(int aIndex)
int NewOutline()
Creates a new hole in a given outline.
const VECTOR2I & CVertex(int aIndex, int aOutline, int aHole) const
Return the aGlobalIndex-th vertex in the poly set.
int OutlineCount() const
Return the number of vertices in a given outline/hole.
void Move(const VECTOR2I &aVector) override
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Represent a simple polygon consisting of a zero-thickness closed chain of connected line segments.
Definition: shape_simple.h:42
int GetWidth() const
Definition: stroke_params.h:98
void GetMsgPanelInfo(UNITS_PROVIDER *aUnitsProvider, std::vector< MSG_PANEL_ITEM > &aList, bool aIncludeStyle=true, bool aIncludeWidth=true)
PLOT_DASH_TYPE GetPlotStyle() const
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
A lower-precision version of StringFromValue().
extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition: vector2d.h:300
VECTOR2_TRAITS< int >::extended_type extended_type
Definition: vector2d.h:76
void TransformRingToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aCentre, int aRadius, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arcs to multiple straight segments.
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a circle to a polygon, using multiple straight lines.
void TransformArcToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount=0)
Convert a oblong shape to a polygon, using multiple segments.
#define _HKI(x)
#define _(s)
static constexpr EDA_ANGLE & ANGLE_180
Definition: eda_angle.h:427
@ RADIANS_T
Definition: eda_angle.h:32
static constexpr EDA_ANGLE & ANGLE_360
Definition: eda_angle.h:429
static constexpr EDA_ANGLE & ANGLE_90
Definition: eda_angle.h:425
static constexpr EDA_ANGLE & ANGLE_0
Definition: eda_angle.h:423
static constexpr EDA_ANGLE & ANGLE_270
Definition: eda_angle.h:428
#define TEST_PT(a, b)
#define TEST(a, b)
#define TEST_E(a, b)
static struct EDA_SHAPE_DESC _EDA_SHAPE_DESC
#define SWAPITEM(x)
#define sq(x)
SHAPE_T
Definition: eda_shape.h:41
@ LAST
marker for list end
FILL_T
Definition: eda_shape.h:54
ERROR_LOC
When approximating an arc or circle, should the error be placed on the outside or inside of the curve...
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition: macros.h:83
#define UNIMPLEMENTED_FOR(type)
Definition: macros.h:120
wxString MessageTextFromValue(const EDA_IU_SCALE &aIuScale, EDA_UNITS aUnits, double aValue, bool aAddUnitsText=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE)
A helper to convert the double length aValue to a string in inches, millimeters, or unscaled units.
Definition: eda_units.cpp:326
static DIRECTION_45::AngleType angle(const VECTOR2I &a, const VECTOR2I &b)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition: eda_angle.h:412
Plot settings, and plotting engines (PostScript, Gerber, HPGL and DXF)
#define NO_SETTER(owner, type)
Definition: property.h:684
#define ENUM_TO_WXANY(type)
Macro to define read-only fields (no setter method available)
Definition: property.h:675
@ PT_COORD
Coordinate expressed in distance units (mm/inch)
Definition: property.h:56
@ PT_DECIDEGREE
Angle expressed in decidegrees.
Definition: property.h:58
@ PT_SIZE
Size expressed in distance units (mm/inch)
Definition: property.h:55
#define REGISTER_TYPE(x)
Definition: property_mgr.h:328
PLOT_DASH_TYPE
Dashed line types.
Definition: stroke_params.h:48
VECTOR2I end
Definition: eda_shape.h:67
VECTOR2I center
Definition: eda_shape.h:68
VECTOR2I start
Definition: eda_shape.h:66
VECTOR2I mid
Definition: eda_shape.h:65
bool TestSegmentHit(const VECTOR2I &aRefPoint, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aDist)
Test if aRefPoint is with aDistance on the line defined by aStart and aEnd.
Definition: trigo.cpp:129
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Definition: trigo.cpp:183
const VECTOR2I CalcArcCenter(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Determine the center of an arc or circle given three points on its circumference.
Definition: trigo.cpp:472
double GetLineLength(const VECTOR2I &aPointA, const VECTOR2I &aPointB)
Return the length of a line segment defined by aPointA and aPointB.
Definition: trigo.h:188
double EuclideanNorm(const VECTOR2I &vector)
Definition: trigo.h:129
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:85
VECTOR2< int > VECTOR2I
Definition: vector2d.h:618