KiCad PCB EDA Suite
Loading...
Searching...
No Matches
shape_ellipse.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 The KiCad Developers, see AUTHORS.txt for contributors.
5*
6* This program is free software; you can redistribute it and/or
7* modify it under the terms of the GNU General Public License
8* as published by the Free Software Foundation; either version 2
9* of the License, or (at your option) any later version.
10*
11* This program is distributed in the hope that it will be useful,
12* but WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14* GNU General Public License for more details.
15*
16* You should have received a copy of the GNU General Public License
17* along with this program. If not, see <https://www.gnu.org/licenses/>.
18*/
19
23#include <geometry/shape_arc.h>
24#include <geometry/circle.h>
25
26#include <algorithm>
27#include <cmath>
28#include <sstream>
29
30#include <trigo.h>
31
32
33namespace
34{
35
45template <typename F>
46double adaptiveSimpson( F f, double a, double b, double tol, int maxDepth );
47
48template <typename F>
49double adaptiveSimpsonRec( F f, double a, double b, double tol, double whole, double fa, double fb, double fm,
50 int depth )
51{
52 const double m = 0.5 * ( a + b );
53 const double lm = 0.5 * ( a + m );
54 const double rm = 0.5 * ( m + b );
55 const double flm = f( lm );
56 const double frm = f( rm );
57
58 const double left = ( m - a ) * ( fa + 4.0 * flm + fm ) / 6.0;
59 const double right = ( b - m ) * ( fm + 4.0 * frm + fb ) / 6.0;
60 const double diff = left + right - whole;
61
62 if( depth <= 0 || std::abs( diff ) < 15.0 * tol )
63 return left + right + diff / 15.0;
64
65 return adaptiveSimpsonRec( f, a, m, 0.5 * tol, left, fa, fm, flm, depth - 1 )
66 + adaptiveSimpsonRec( f, m, b, 0.5 * tol, right, fm, fb, frm, depth - 1 );
67}
68
69template <typename F>
70double adaptiveSimpson( F f, double a, double b, double tol, int maxDepth )
71{
72 const double fa = f( a );
73 const double fb = f( b );
74 const double fm = f( 0.5 * ( a + b ) );
75 const double whole = ( b - a ) * ( fa + 4.0 * fm + fb ) / 6.0;
76 return adaptiveSimpsonRec( f, a, b, tol, whole, fa, fb, fm, maxDepth );
77}
78
79
86template <typename Eval>
87void subdivideEllipseArc( double t0, const VECTOR2I& p0, double t1, const VECTOR2I& p1, double aMaxErrSq, int aDepth,
88 Eval aEval, SHAPE_LINE_CHAIN& aOut )
89{
90 if( aDepth <= 0 )
91 {
92 aOut.Append( p1 );
93 return;
94 }
95
96 const double tm = 0.5 * ( t0 + t1 );
97 const VECTOR2I pm = aEval( tm );
98
99 const double mx = 0.5 * ( static_cast<double>( p0.x ) + p1.x );
100 const double my = 0.5 * ( static_cast<double>( p0.y ) + p1.y );
101 const double ex = pm.x - mx;
102 const double ey = pm.y - my;
103
104 if( ex * ex + ey * ey <= aMaxErrSq )
105 {
106 aOut.Append( p1 );
107 return;
108 }
109
110 subdivideEllipseArc( t0, p0, tm, pm, aMaxErrSq, aDepth - 1, aEval, aOut );
111 subdivideEllipseArc( tm, pm, t1, p1, aMaxErrSq, aDepth - 1, aEval, aOut );
112}
113
114
115constexpr double ROOT_EPSILON = 1e-12;
116
117
123std::vector<double> quadraticRoots( double aA, double aB, double aC )
124{
125 std::vector<double> roots;
126
127 if( std::abs( aA ) < ROOT_EPSILON )
128 {
129 if( std::abs( aB ) >= ROOT_EPSILON )
130 roots.push_back( -aC / aB );
131
132 return roots;
133 }
134
135 const double disc = aB * aB - 4.0 * aA * aC;
136
137 if( disc < 0.0 )
138 return roots;
139
140 const double sq = std::sqrt( disc );
141 const double q = -0.5 * ( aB + ( aB >= 0.0 ? sq : -sq ) );
142
143 roots.push_back( q / aA );
144
145 if( std::abs( q ) >= ROOT_EPSILON )
146 roots.push_back( aC / q );
147
148 return roots;
149}
150
151
156std::vector<double> cubicRoots( double aA, double aB, double aC, double aD )
157{
158 if( std::abs( aA ) < ROOT_EPSILON )
159 return quadraticRoots( aB, aC, aD );
160
161 const double b = aB / aA;
162 const double c = aC / aA;
163 const double d = aD / aA;
164
165 // Depress to y^3 + p y + q, where x is y minus the shift
166 const double shift = b / 3.0;
167 const double p = c - b * b / 3.0;
168 const double q = 2.0 * b * b * b / 27.0 - b * c / 3.0 + d;
169
170 std::vector<double> roots;
171 const double disc = q * q / 4.0 + p * p * p / 27.0;
172
173 if( std::abs( p ) < ROOT_EPSILON && std::abs( q ) < ROOT_EPSILON )
174 {
175 roots.push_back( -shift );
176 }
177 else if( disc > 0.0 )
178 {
179 const double sq = std::sqrt( disc );
180 roots.push_back( std::cbrt( -q / 2.0 + sq ) + std::cbrt( -q / 2.0 - sq ) - shift );
181 }
182 else
183 {
184 const double r = 2.0 * std::sqrt( -p / 3.0 );
185 const double arg = std::clamp( 3.0 * q / ( p * r ), -1.0, 1.0 );
186 const double phi = std::acos( arg ) / 3.0;
187
188 for( int k = 0; k < 3; ++k )
189 roots.push_back( r * std::cos( phi - 2.0 * M_PI * k / 3.0 ) - shift );
190 }
191
192 return roots;
193}
194
195
200std::vector<double> quarticRoots( double aA, double aB, double aC, double aD, double aE )
201{
202 if( std::abs( aA ) < ROOT_EPSILON )
203 return cubicRoots( aB, aC, aD, aE );
204
205 const double b = aB / aA;
206 const double c = aC / aA;
207 const double d = aD / aA;
208 const double e = aE / aA;
209
210 // Depress to y^4 + p y^2 + q y + r, where x is y minus the shift
211 const double shift = b / 4.0;
212 const double p = c - 3.0 * b * b / 8.0;
213 const double q = d - b * c / 2.0 + b * b * b / 8.0;
214 const double r = e - b * d / 4.0 + b * b * c / 16.0 - 3.0 * b * b * b * b / 256.0;
215
216 std::vector<double> roots;
217
218 if( std::abs( q ) < ROOT_EPSILON )
219 {
220 for( double ySq : quadraticRoots( 1.0, p, r ) )
221 {
222 if( ySq >= 0.0 )
223 {
224 const double y = std::sqrt( ySq );
225 roots.push_back( y - shift );
226 roots.push_back( -y - shift );
227 }
228 }
229
230 return roots;
231 }
232
233 double alphaSq = 0.0;
234
235 for( double z : cubicRoots( 1.0, 2.0 * p, p * p - 4.0 * r, -q * q ) )
236 {
237 if( z > alphaSq )
238 alphaSq = z;
239 }
240
241 if( alphaSq <= 0.0 )
242 return roots;
243
244 const double alpha = std::sqrt( alphaSq );
245 const double beta = ( p + alphaSq - q / alpha ) / 2.0;
246 const double gamma = ( p + alphaSq + q / alpha ) / 2.0;
247
248 for( double y : quadraticRoots( 1.0, alpha, beta ) )
249 roots.push_back( y - shift );
250
251 for( double y : quadraticRoots( 1.0, -alpha, gamma ) )
252 roots.push_back( y - shift );
253
254 return roots;
255}
256
257
258void dedupePoints( std::vector<VECTOR2I>& aPoints )
259{
260 std::sort( aPoints.begin(), aPoints.end(),
261 []( const VECTOR2I& aLeft, const VECTOR2I& aRight )
262 {
263 return aLeft.x != aRight.x ? aLeft.x < aRight.x : aLeft.y < aRight.y;
264 } );
265
266 aPoints.erase( std::unique( aPoints.begin(), aPoints.end() ), aPoints.end() );
267}
268
269} // namespace
270
271
273 SHAPE( SH_ELLIPSE ),
274 m_ellipse(),
275 m_isArc( false ),
276 m_sinRot( 0.0 ),
277 m_cosRot( 1.0 ),
278 m_invMajorRSq( 0.0 ),
279 m_invMinorRSq( 0.0 )
280{
281}
282
283
284SHAPE_ELLIPSE::SHAPE_ELLIPSE( const VECTOR2I& aCenter, int aMajorRadius, int aMinorRadius,
285 const EDA_ANGLE& aRotation ) :
286 SHAPE( SH_ELLIPSE ),
287 m_ellipse( aCenter, aMajorRadius, aMinorRadius, aRotation ),
288 m_isArc( false )
289{
290 normalize();
291}
292
293
294SHAPE_ELLIPSE::SHAPE_ELLIPSE( const VECTOR2I& aCenter, int aMajorRadius, int aMinorRadius, const EDA_ANGLE& aRotation,
295 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aEndAngle ) :
296 SHAPE( SH_ELLIPSE ),
297 m_ellipse( aCenter, aMajorRadius, aMinorRadius, aRotation, aStartAngle, aEndAngle ),
298 m_isArc( true )
299{
300 normalize();
301}
302
303
304SHAPE_ELLIPSE::SHAPE_ELLIPSE( const VECTOR2I& aCenter, const VECTOR2I& aMajorEndpoint, double aRatio ) :
305 SHAPE( SH_ELLIPSE ),
306 m_ellipse( aCenter, aMajorEndpoint, aRatio ),
307 m_isArc( false )
308{
309 normalize();
310}
311
312
313SHAPE_ELLIPSE::SHAPE_ELLIPSE( const VECTOR2I& aCenter, const VECTOR2I& aMajorEndpoint, double aRatio,
314 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aEndAngle ) :
315 SHAPE( SH_ELLIPSE ),
316 m_ellipse( aCenter, aMajorEndpoint, aRatio, aStartAngle, aEndAngle ),
317 m_isArc( true )
318{
319 normalize();
320}
321
322
324{
325 m_ellipse.MajorRadius = std::max( 1, m_ellipse.MajorRadius );
326 m_ellipse.MinorRadius = std::max( 1, m_ellipse.MinorRadius );
327
328 if( m_ellipse.MajorRadius < m_ellipse.MinorRadius )
329 {
330 std::swap( m_ellipse.MajorRadius, m_ellipse.MinorRadius );
331 m_ellipse.Rotation += ANGLE_90;
332
333 if( m_isArc )
334 {
335 m_ellipse.StartAngle -= ANGLE_90;
336 m_ellipse.EndAngle -= ANGLE_90;
337 }
338 }
339
340 updateCache();
341}
342
343
344void SHAPE_ELLIPSE::SetCenter( const VECTOR2I& aCenter )
345{
346 m_ellipse.Center = aCenter;
347}
348
349
351{
352 m_ellipse.MajorRadius = aRadius;
353 normalize();
354}
355
356
358{
359 m_ellipse.MinorRadius = aRadius;
360 normalize();
361}
362
363
365{
366 m_ellipse.Rotation = aAngle;
367 updateCache();
368}
369
370
372{
373 m_ellipse.StartAngle = aAngle;
374}
375
376
378{
379 m_ellipse.EndAngle = aAngle;
380}
381
382
383const BOX2I SHAPE_ELLIPSE::BBox( int aClearance ) const
384{
385 const double a = static_cast<double>( m_ellipse.MajorRadius );
386 const double b = static_cast<double>( m_ellipse.MinorRadius );
387
388 if( !m_isArc )
389 {
390 const double cos2 = m_cosRot * m_cosRot;
391 const double sin2 = m_sinRot * m_sinRot;
392
393 const double dx = std::sqrt( a * a * cos2 + b * b * sin2 );
394 const double dy = std::sqrt( a * a * sin2 + b * b * cos2 );
395
396 const int idx = static_cast<int>( std::ceil( dx ) ) + aClearance;
397 const int idy = static_cast<int>( std::ceil( dy ) ) + aClearance;
398
399 return BOX2I( VECTOR2I( m_ellipse.Center.x - idx, m_ellipse.Center.y - idy ), VECTOR2I( 2 * idx, 2 * idy ) );
400 }
401
402 auto eval = [&]( double theta ) -> VECTOR2D
403 {
404 const double ct = std::cos( theta );
405 const double st = std::sin( theta );
406 return VECTOR2D( a * ct * m_cosRot - b * st * m_sinRot, a * ct * m_sinRot + b * st * m_cosRot );
407 };
408
409 const VECTOR2D p0 = eval( m_ellipse.StartAngle.AsRadians() );
410 const VECTOR2D p1 = eval( m_ellipse.EndAngle.AsRadians() );
411
412 double minX = std::min( p0.x, p1.x );
413 double maxX = std::max( p0.x, p1.x );
414 double minY = std::min( p0.y, p1.y );
415 double maxY = std::max( p0.y, p1.y );
416
417 const double thetaX = std::atan2( -b * m_sinRot, a * m_cosRot );
418 const double thetaY = std::atan2( b * m_cosRot, a * m_sinRot );
419
420 const double candidates[4] = { thetaX, thetaX + M_PI, thetaY, thetaY + M_PI };
421
422 for( double c : candidates )
423 {
424 if( !isAngleInSweep( c ) )
425 continue;
426
427 const VECTOR2D p = eval( c );
428 minX = std::min( minX, p.x );
429 maxX = std::max( maxX, p.x );
430 minY = std::min( minY, p.y );
431 maxY = std::max( maxY, p.y );
432 }
433
434 const int iMinX = static_cast<int>( std::floor( minX ) ) - aClearance;
435 const int iMaxX = static_cast<int>( std::ceil( maxX ) ) + aClearance;
436 const int iMinY = static_cast<int>( std::floor( minY ) ) - aClearance;
437 const int iMaxY = static_cast<int>( std::ceil( maxY ) ) + aClearance;
438
439 return BOX2I( VECTOR2I( m_ellipse.Center.x + iMinX, m_ellipse.Center.y + iMinY ),
440 VECTOR2I( iMaxX - iMinX, iMaxY - iMinY ) );
441}
442
443
445{
446 const double a = static_cast<double>( m_ellipse.MajorRadius );
447 const double b = static_cast<double>( m_ellipse.MinorRadius );
448
449 if( !m_isArc )
450 {
451 // Ramanujan's second approximation
452 // See https://en.wikipedia.org/wiki/Perimeter_of_an_ellipse
453 const double h = ( a - b ) / ( a + b );
454 const double h2 = h * h;
455 return M_PI * ( a + b ) * ( 1.0 + 3.0 * h2 / ( 10.0 + std::sqrt( 4.0 - 3.0 * h2 ) ) );
456 }
457
458 auto integrand = [a, b]( double theta ) -> double
459 {
460 const double s = std::sin( theta );
461 const double c = std::cos( theta );
462 return std::sqrt( a * a * s * s + b * b * c * c );
463 };
464
465 double t0, t1;
466 sweepRange( t0, t1 );
467
468 return adaptiveSimpson( integrand, t0, t1, 1e-9, 20 );
469}
470
471
472bool SHAPE_ELLIPSE::Collide( const SEG& aSeg, int aClearance, int* aActual, VECTOR2I* aLocation ) const
473{
474 if( aSeg.A == aSeg.B )
475 {
476 const SEG::ecoord dSq = SquaredDistance( aSeg.A, false );
477 const SEG::ecoord clearSq = static_cast<SEG::ecoord>( aClearance ) * static_cast<SEG::ecoord>( aClearance );
478
479 if( dSq == 0 || dSq < clearSq )
480 {
481 if( aActual )
482 *aActual = static_cast<int>( std::round( std::sqrt( static_cast<double>( dSq ) ) ) );
483 if( aLocation )
484 *aLocation = aSeg.A;
485 return true;
486 }
487
488 return false;
489 }
490
491 const VECTOR2D Aloc = toLocal( aSeg.A );
492 const VECTOR2D Bloc = toLocal( aSeg.B );
493 const VECTOR2D D( Bloc.x - Aloc.x, Bloc.y - Aloc.y );
494
495 const double a = static_cast<double>( m_ellipse.MajorRadius );
496 const double b = static_cast<double>( m_ellipse.MinorRadius );
497 const double aSq = a * a;
498 const double bSq = b * b;
499
500 const double alpha = D.x * D.x / aSq + D.y * D.y / bSq;
501 const double beta = 2.0 * ( Aloc.x * D.x / aSq + Aloc.y * D.y / bSq );
502 const double gamma = Aloc.x * Aloc.x / aSq + Aloc.y * Aloc.y / bSq - 1.0;
503
504 // A is inside the closed ellipse if gamma < 0, B is inside if alpha + beta + gamma < 0
505 const double valB = alpha + beta + gamma;
506
507 if( !m_isArc )
508 {
509 if( gamma <= 0.0 )
510 {
511 if( aActual )
512 *aActual = 0;
513 if( aLocation )
514 *aLocation = aSeg.A;
515 return true;
516 }
517 if( valB <= 0.0 )
518 {
519 if( aActual )
520 *aActual = 0;
521 if( aLocation )
522 *aLocation = aSeg.B;
523 return true;
524 }
525 }
526
527 // disc < 0 means the line misses the ellipse
528 const double disc = beta * beta - 4.0 * alpha * gamma;
529
530 if( disc >= 0.0 && alpha > 0.0 )
531 {
532 const double sqrtDisc = std::sqrt( disc );
533 const double twoAlpha = 2.0 * alpha;
534 const double t0 = ( -beta - sqrtDisc ) / twoAlpha;
535 const double t1 = ( -beta + sqrtDisc ) / twoAlpha;
536 const double roots[2] = { t0, t1 };
537
538 for( double t : roots )
539 {
540 if( t < 0.0 || t > 1.0 )
541 continue;
542
543 const VECTOR2D hit( Aloc.x + t * D.x, Aloc.y + t * D.y );
544
545 // For arcs, the intersection must be within the angular sweep.
546 if( m_isArc )
547 {
548 const double angle = std::atan2( hit.y / b, hit.x / a );
549 if( !isAngleInSweep( angle ) )
550 continue;
551 }
552
553 if( aActual )
554 *aActual = 0;
555 if( aLocation )
556 *aLocation = toWorld( hit );
557 return true;
558 }
559 }
560
561 double minDistSq = std::numeric_limits<double>::max();
562 VECTOR2D bestOnSegment( 0.0, 0.0 );
563
564 {
565 const double dA = static_cast<double>( SquaredDistance( aSeg.A, true ) );
566 const double dB = static_cast<double>( SquaredDistance( aSeg.B, true ) );
567
568 if( dA < minDistSq )
569 {
570 minDistSq = dA;
571 bestOnSegment = Aloc;
572 }
573 if( dB < minDistSq )
574 {
575 minDistSq = dB;
576 bestOnSegment = Bloc;
577 }
578 }
579
580 const double dDotD = D.x * D.x + D.y * D.y;
581
582 // Check where ellipse outline runs parallel to the segment
583 if( dDotD > 0.0 )
584 {
585 const double theta0 = std::atan2( -b * D.x, a * D.y );
586 const double thetas[2] = { theta0, theta0 + M_PI };
587
588 for( double theta : thetas )
589 {
590 if( m_isArc && !isAngleInSweep( theta ) )
591 continue;
592
593 const double ex = a * std::cos( theta );
594 const double ey = b * std::sin( theta );
595
596 // Orthogonal projection of (ex, ey) onto the segment
597 const double pDotD = ( ex - Aloc.x ) * D.x + ( ey - Aloc.y ) * D.y;
598 const double t = std::clamp( pDotD / dDotD, 0.0, 1.0 );
599 const double qx = Aloc.x + t * D.x;
600 const double qy = Aloc.y + t * D.y;
601
602 const double distSq = ( ex - qx ) * ( ex - qx ) + ( ey - qy ) * ( ey - qy );
603
604 if( distSq < minDistSq )
605 {
606 minDistSq = distSq;
607 bestOnSegment = VECTOR2D( qx, qy );
608 }
609 }
610 }
611
612 // Arc endpoints projected onto segment.
613 if( m_isArc && dDotD > 0.0 )
614 {
615 const EDA_ANGLE endAngles[2] = { m_ellipse.StartAngle, m_ellipse.EndAngle };
616
617 for( const EDA_ANGLE& endAngle : endAngles )
618 {
619 const double angleRad = endAngle.AsRadians();
620 const double ex = a * std::cos( angleRad );
621 const double ey = b * std::sin( angleRad );
622
623 const double pDotD = ( ex - Aloc.x ) * D.x + ( ey - Aloc.y ) * D.y;
624 const double t = std::clamp( pDotD / dDotD, 0.0, 1.0 );
625 const double qx = Aloc.x + t * D.x;
626 const double qy = Aloc.y + t * D.y;
627
628 const double distSq = ( ex - qx ) * ( ex - qx ) + ( ey - qy ) * ( ey - qy );
629
630 if( distSq < minDistSq )
631 {
632 minDistSq = distSq;
633 bestOnSegment = VECTOR2D( qx, qy );
634 }
635 }
636 }
637
638 // Clearance comparison
639 const double thresholdSq = static_cast<double>( aClearance ) * static_cast<double>( aClearance );
640
641 if( minDistSq > 0.0 && minDistSq >= thresholdSq )
642 return false;
643
644 if( aActual )
645 *aActual = static_cast<int>( std::round( std::sqrt( minDistSq ) ) );
646 if( aLocation )
647 *aLocation = toWorld( bestOnSegment );
648 return true;
649}
650
651
652// ERROR_LOC is unused, tessellation points sit on the true ellipse curve, not offset inward or outward.
653void SHAPE_ELLIPSE::TransformToPolygon( SHAPE_POLY_SET& aBuffer, int aError, ERROR_LOC /*aErrorLoc*/ ) const
654{
655 if( m_isArc )
656 return;
657
659 chain.SetClosed( true );
660 aBuffer.AddOutline( chain );
661}
662
663
664void SHAPE_ELLIPSE::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
665{
666 RotatePoint( m_ellipse.Center, aCenter, aAngle );
667 m_ellipse.Rotation -= aAngle;
668 updateCache();
669}
670
671
672void SHAPE_ELLIPSE::Mirror( const VECTOR2I& aRef, FLIP_DIRECTION aFlipDirection )
673{
674 m_ellipse.Mirror( aRef, aFlipDirection );
675 updateCache();
676}
677
678
679const std::string SHAPE_ELLIPSE::Format( bool aCplusPlus ) const
680{
681 std::stringstream ss;
682
683 if( aCplusPlus )
684 {
685 ss << "SHAPE_ELLIPSE( VECTOR2I( " << m_ellipse.Center.x << ", " << m_ellipse.Center.y << " ), "
686 << m_ellipse.MajorRadius << ", " << m_ellipse.MinorRadius << ", EDA_ANGLE( "
687 << m_ellipse.Rotation.AsDegrees() << ", DEGREES_T )";
688
689 if( m_isArc )
690 {
691 ss << ", EDA_ANGLE( " << m_ellipse.StartAngle.AsDegrees() << ", DEGREES_T )"
692 << ", EDA_ANGLE( " << m_ellipse.EndAngle.AsDegrees() << ", DEGREES_T )";
693 }
694
695 ss << " );";
696 }
697 else
698 {
699 ss << SHAPE::Format( aCplusPlus ) << " " << m_ellipse.Center.x << " " << m_ellipse.Center.y << " "
700 << m_ellipse.MajorRadius << " " << m_ellipse.MinorRadius << " " << m_ellipse.Rotation.AsDegrees() << " "
701 << ( m_isArc ? 1 : 0 );
702
703 if( m_isArc )
704 {
705 ss << " " << m_ellipse.StartAngle.AsDegrees() << " " << m_ellipse.EndAngle.AsDegrees();
706 }
707 }
708
709 return ss.str();
710}
711
712
713void SHAPE_ELLIPSE::Move( const VECTOR2I& aVector )
714{
715 m_ellipse.Center += aVector;
716}
717
718
720{
721 const double rotRad = m_ellipse.Rotation.AsRadians();
722 m_sinRot = std::sin( rotRad );
723 m_cosRot = std::cos( rotRad );
724
725 const double a = static_cast<double>( m_ellipse.MajorRadius );
726 const double b = static_cast<double>( m_ellipse.MinorRadius );
727 m_invMajorRSq = 1.0 / ( a * a );
728 m_invMinorRSq = 1.0 / ( b * b );
729}
730
731
732bool SHAPE_ELLIPSE::PointInside( const VECTOR2I& aPt, int aAccuracy, bool /*aUseBBoxCache*/ ) const
733{
734 // No interior for elliptical arcs. Open curve
735 if( m_isArc )
736 return false;
737
738 const double dx = aPt.x - m_ellipse.Center.x;
739 const double dy = aPt.y - m_ellipse.Center.y;
740 const double lx = dx * m_cosRot + dy * m_sinRot;
741 const double ly = -dx * m_sinRot + dy * m_cosRot;
742
743 if( aAccuracy > 0 )
744 {
745 // Increase both radii by aAccuracy.
746 const double a = static_cast<double>( m_ellipse.MajorRadius ) + aAccuracy;
747 const double b = static_cast<double>( m_ellipse.MinorRadius ) + aAccuracy;
748 return ( lx * lx ) / ( a * a ) + ( ly * ly ) / ( b * b ) < 1.0;
749 }
750
751 return lx * lx * m_invMajorRSq + ly * ly * m_invMinorRSq < 1.0;
752}
753
754
756{
757 const double dx = aP.x - m_ellipse.Center.x;
758 const double dy = aP.y - m_ellipse.Center.y;
759
760 return VECTOR2D( dx * m_cosRot + dy * m_sinRot, -dx * m_sinRot + dy * m_cosRot );
761}
762
763
765{
766 const double wx = aP.x * m_cosRot - aP.y * m_sinRot;
767 const double wy = aP.x * m_sinRot + aP.y * m_cosRot;
768
769 return VECTOR2I( static_cast<int>( std::round( m_ellipse.Center.x + wx ) ),
770 static_cast<int>( std::round( m_ellipse.Center.y + wy ) ) );
771}
772
773
775{
776 return VECTOR2D( m_ellipse.MajorRadius * std::cos( aTheta ), m_ellipse.MinorRadius * std::sin( aTheta ) );
777}
778
779
781{
782 const double lx = aLocal.x;
783 const double ly = aLocal.y;
784
785 const double a = static_cast<double>( m_ellipse.MajorRadius );
786 const double b = static_cast<double>( m_ellipse.MinorRadius );
787
788 // Closest point on ellipse via Eberly's bisection.
789 // Reference: "Distance from a Point to an Ellipse, an Ellipsoid, or a
790 // Hyperellipsoid", David Eberly, Geometric Tools.
791 // https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf
792
793 const double y0 = std::abs( lx );
794 const double y1 = std::abs( ly );
795
796 double x0Local = 0.0;
797 double x1Local = 0.0;
798
799 if( y1 > 0.0 )
800 {
801 if( y0 > 0.0 )
802 {
803 const double z0 = y0 / a;
804 const double z1 = y1 / b;
805 const double g = z0 * z0 + z1 * z1 - 1.0;
806
807 if( g != 0.0 )
808 {
809 const double r0 = ( a / b ) * ( a / b );
810 const double n0 = r0 * z0;
811
812 double s0 = z1 - 1.0;
813 double s1 = ( g < 0.0 ) ? 0.0 : std::sqrt( n0 * n0 + z1 * z1 ) - 1.0;
814 double s = 0.0;
815
816 for( int iter = 0; iter < 64; ++iter )
817 {
818 s = 0.5 * ( s0 + s1 );
819
820 if( s == s0 || s == s1 )
821 break;
822
823 const double ratio0 = n0 / ( s + r0 );
824 const double ratio1 = z1 / ( s + 1.0 );
825 const double gs = ratio0 * ratio0 + ratio1 * ratio1 - 1.0;
826
827 if( gs > 0.0 )
828 s0 = s;
829 else if( gs < 0.0 )
830 s1 = s;
831 else
832 break;
833 }
834
835 x0Local = r0 * y0 / ( s + r0 );
836 x1Local = y1 / ( s + 1.0 );
837 }
838 else
839 {
840 // Point is on the ellipse.
841 x0Local = y0;
842 x1Local = y1;
843 }
844 }
845 else
846 {
847 // y0 == 0 point lies on the minor axis.
848 x0Local = 0.0;
849 x1Local = b;
850 }
851 }
852 else
853 {
854 // y1 == 0 point lies on the major axis.
855 const double numer0 = a * y0;
856 const double denom0 = a * a - b * b;
857
858 if( numer0 < denom0 )
859 {
860 const double xde0 = numer0 / denom0;
861 x0Local = a * xde0;
862 x1Local = b * std::sqrt( std::max( 0.0, 1.0 - xde0 * xde0 ) );
863 }
864 else
865 {
866 x0Local = a;
867 x1Local = 0.0;
868 }
869 }
870
871 const VECTOR2D closest( ( lx < 0.0 ) ? -x0Local : x0Local, ( ly < 0.0 ) ? -x1Local : x1Local );
872
873 // An arc that does not reach round to the closest point is nearest at one of its ends
874 if( m_isArc )
875 {
876 const double closestTheta = std::atan2( closest.y / b, closest.x / a );
877
878 if( !isAngleInSweep( closestTheta ) )
879 {
880 const VECTOR2D start = pointAtParam( m_ellipse.StartAngle.AsRadians() );
881 const VECTOR2D end = pointAtParam( m_ellipse.EndAngle.AsRadians() );
882
883 return ( aLocal - start ).SquaredEuclideanNorm() <= ( aLocal - end ).SquaredEuclideanNorm() ? start : end;
884 }
885 }
886
887 return closest;
888}
889
890
891SEG::ecoord SHAPE_ELLIPSE::SquaredDistance( const VECTOR2I& aP, bool aOutlineOnly ) const
892{
893 const VECTOR2D local = toLocal( aP );
894
895 // Interior of a closed ellipse if val < 1
896 if( !m_isArc && !aOutlineOnly )
897 {
898 const double val = local.x * local.x * m_invMajorRSq + local.y * local.y * m_invMinorRSq;
899
900 if( val <= 1.0 )
901 return 0;
902 }
903
904 const VECTOR2D closest = closestLocalPoint( local );
905 const double dxE = closest.x - local.x;
906 const double dyE = closest.y - local.y;
907
908 return static_cast<SEG::ecoord>( dxE * dxE + dyE * dyE );
909}
910
911
913{
914 return toWorld( closestLocalPoint( toLocal( aP ) ) );
915}
916
917
918SHAPE_ELLIPSE::CONIC SHAPE_ELLIPSE::conicOf( const VECTOR2I& aCenter, double aMajorR, double aMinorR,
919 const EDA_ANGLE& aRotation ) const
920{
921 // Take a point of this local frame across to the other curve's local frame, then
922 // square out the other curve's own equation to get the coefficients.
923 const double delta = m_ellipse.Rotation.AsRadians() - aRotation.AsRadians();
924 const double cd = std::cos( delta );
925 const double sd = std::sin( delta );
926
927 const double cr = std::cos( aRotation.AsRadians() );
928 const double sr = std::sin( aRotation.AsRadians() );
929
930 const double wx = static_cast<double>( m_ellipse.Center.x - aCenter.x );
931 const double wy = static_cast<double>( m_ellipse.Center.y - aCenter.y );
932
933 const double dx = wx * cr + wy * sr;
934 const double dy = -wx * sr + wy * cr;
935
936 const double p = 1.0 / ( aMajorR * aMajorR );
937 const double q = 1.0 / ( aMinorR * aMinorR );
938
939 CONIC conic;
940 conic.Axx = p * cd * cd + q * sd * sd;
941 conic.Axy = 2.0 * sd * cd * ( q - p );
942 conic.Ayy = p * sd * sd + q * cd * cd;
943 conic.Bx = 2.0 * ( p * dx * cd + q * dy * sd );
944 conic.By = 2.0 * ( q * dy * cd - p * dx * sd );
945 conic.C = p * dx * dx + q * dy * dy - 1.0;
946
947 return conic;
948}
949
950
951std::vector<double> SHAPE_ELLIPSE::conicRoots( const CONIC& aConic ) const
952{
953 const double a = static_cast<double>( m_ellipse.MajorRadius );
954 const double b = static_cast<double>( m_ellipse.MinorRadius );
955
956 // The conic seen along this ellipse, as a function of the parameter angle
957 const double cA = aConic.Axx * a * a;
958 const double cB = aConic.Axy * a * b;
959 const double cC = aConic.Ayy * b * b;
960 const double cD = aConic.Bx * a;
961 const double cE = aConic.By * b;
962 const double cF = aConic.C;
963
964 const auto value = [&]( double aTheta )
965 {
966 const double ct = std::cos( aTheta );
967 const double st = std::sin( aTheta );
968 return cA * ct * ct + cB * ct * st + cC * st * st + cD * ct + cE * st + cF;
969 };
970
971 const auto slope = [&]( double aTheta )
972 {
973 const double ct = std::cos( aTheta );
974 const double st = std::sin( aTheta );
975 return 2.0 * ( cC - cA ) * ct * st + cB * ( ct * ct - st * st ) - cD * st + cE * ct;
976 };
977
978 // The tangent of the half angle turns that into a quartic, at the cost of never
979 // reaching pi. That one angle is offered below as a candidate like any other.
980 const double k4 = cA - cD + cF;
981 const double k3 = 2.0 * ( cE - cB );
982 const double k2 = 2.0 * ( cF - cA ) + 4.0 * cC;
983 const double k1 = 2.0 * ( cB + cE );
984 const double k0 = cA + cD + cF;
985
986 const double scale = std::max( { std::abs( k4 ), std::abs( k3 ), std::abs( k2 ), std::abs( k1 ), std::abs( k0 ) } );
987
988 // The curves lie on top of each other, so there is no crossing to report
989 if( scale == 0.0 )
990 return {};
991
992 std::vector<double> params;
993
994 for( double u : quarticRoots( k4 / scale, k3 / scale, k2 / scale, k1 / scale, k0 / scale ) )
995 params.push_back( 2.0 * std::atan( u ) );
996
997 params.push_back( M_PI );
998
999 std::vector<double> result;
1000
1001 for( double theta : params )
1002 {
1003 // The quartic loses digits when its roots run large, so finish on the exact equation
1004 for( int iter = 0; iter < 8; ++iter )
1005 {
1006 const double f = value( theta );
1007 const double df = slope( theta );
1008
1009 if( df == 0.0 )
1010 break;
1011
1012 const double step = f / df;
1013 theta -= step;
1014
1015 if( std::abs( step ) < 1e-15 )
1016 break;
1017 }
1018
1019 if( std::abs( value( theta ) ) > scale * 1e-9 )
1020 continue;
1021
1022 if( m_isArc && !isAngleInSweep( theta ) )
1023 continue;
1024
1025 result.push_back( theta );
1026 }
1027
1028 return result;
1029}
1030
1031
1032std::vector<VECTOR2I> SHAPE_ELLIPSE::Intersect( const SHAPE_ELLIPSE& aOther ) const
1033{
1034 const CONIC conic = conicOf( aOther.m_ellipse.Center, aOther.m_ellipse.MajorRadius, aOther.m_ellipse.MinorRadius,
1035 aOther.m_ellipse.Rotation );
1036
1037 std::vector<VECTOR2I> points;
1038
1039 for( double theta : conicRoots( conic ) )
1040 {
1041 const VECTOR2I world = m_ellipse.GetPointAtAngle( EDA_ANGLE( theta, RADIANS_T ) );
1042
1043 if( aOther.m_isArc && !aOther.isAngleInSweep( aOther.m_ellipse.GetAngleAtPoint( world ).AsRadians() ) )
1044 {
1045 continue;
1046 }
1047
1048 points.push_back( world );
1049 }
1050
1051 dedupePoints( points );
1052 return points;
1053}
1054
1055
1056std::vector<VECTOR2I> SHAPE_ELLIPSE::intersectCircle( const VECTOR2I& aCenter, double aRadius ) const
1057{
1058 std::vector<VECTOR2I> points;
1059
1060 if( aRadius <= 0.0 )
1061 return points;
1062
1063 for( double theta : conicRoots( conicOf( aCenter, aRadius, aRadius, ANGLE_0 ) ) )
1064 points.push_back( m_ellipse.GetPointAtAngle( EDA_ANGLE( theta, RADIANS_T ) ) );
1065
1066 dedupePoints( points );
1067 return points;
1068}
1069
1070
1071std::vector<VECTOR2I> SHAPE_ELLIPSE::Intersect( const CIRCLE& aCircle ) const
1072{
1073 return intersectCircle( aCircle.Center, aCircle.Radius );
1074}
1075
1076
1077std::vector<VECTOR2I> SHAPE_ELLIPSE::Intersect( const SHAPE_ARC& aArc ) const
1078{
1079 // The radius is taken as a double, so the arc is not rounded to a CIRCLE first
1080 std::vector<VECTOR2I> points = intersectCircle( aArc.GetCenter(), aArc.GetRadius() );
1081
1082 // Crossings of the full circle that miss the drawn part are pulled back to an end
1083 // of the arc, so they fail this test
1084 std::erase_if( points,
1085 [&]( const VECTOR2I& aPoint )
1086 {
1087 return aArc.NearestPoint( aPoint ).Distance( aPoint ) > SHAPE::MIN_PRECISION_IU;
1088 } );
1089
1090 return points;
1091}
1092
1093
1094std::vector<VECTOR2I> SHAPE_ELLIPSE::Intersect( const SEG& aSeg, bool aTreatAsLine ) const
1095{
1096 const VECTOR2D start = toLocal( aSeg.A );
1097 const VECTOR2D dir = toLocal( aSeg.B ) - start;
1098
1099 if( dir.x == 0.0 && dir.y == 0.0 )
1100 return {};
1101
1102 // A straight line is a conic with no squared terms, so the quartic falls away by itself
1103 CONIC conic;
1104 conic.Axx = 0.0;
1105 conic.Axy = 0.0;
1106 conic.Ayy = 0.0;
1107 conic.Bx = dir.y;
1108 conic.By = -dir.x;
1109 conic.C = start.y * dir.x - start.x * dir.y;
1110
1111 std::vector<VECTOR2I> points;
1112
1113 for( double theta : conicRoots( conic ) )
1114 {
1115 const VECTOR2I world = m_ellipse.GetPointAtAngle( EDA_ANGLE( theta, RADIANS_T ) );
1116
1117 if( aTreatAsLine || aSeg.Contains( world ) )
1118 points.push_back( world );
1119 }
1120
1121 dedupePoints( points );
1122 return points;
1123}
1124
1125
1127{
1128 if( aMaxError < 1 )
1129 aMaxError = 1;
1130
1131 const double a = static_cast<double>( m_ellipse.MajorRadius );
1132 const double b = static_cast<double>( m_ellipse.MinorRadius );
1133 const double cx = m_ellipse.Center.x;
1134 const double cy = m_ellipse.Center.y;
1135 const double sinRot = m_sinRot;
1136 const double cosRot = m_cosRot;
1137
1138 auto eval = [=]( double theta ) -> VECTOR2I
1139 {
1140 const double ct = std::cos( theta );
1141 const double st = std::sin( theta );
1142 const double lx = a * ct;
1143 const double ly = b * st;
1144 const double wx = lx * cosRot - ly * sinRot;
1145 const double wy = lx * sinRot + ly * cosRot;
1146 return VECTOR2I( static_cast<int>( std::round( cx + wx ) ), static_cast<int>( std::round( cy + wy ) ) );
1147 };
1148
1149 double tStart, tEnd;
1150 sweepRange( tStart, tEnd );
1151
1152 const double maxErrSq = static_cast<double>( aMaxError ) * aMaxError;
1153
1154 SHAPE_LINE_CHAIN out;
1155 const VECTOR2I pStart = eval( tStart );
1156 const VECTOR2I pEnd = eval( tEnd );
1157
1158 out.Append( pStart );
1159 subdivideEllipseArc( tStart, pStart, tEnd, pEnd, maxErrSq, 20, eval, out );
1160
1161 if( !m_isArc )
1162 {
1163 if( out.PointCount() > 1 && out.CPoint( 0 ) == out.CPoint( -1 ) )
1164 out.Remove( out.PointCount() - 1 );
1165
1166 out.SetClosed( true );
1167 }
1168
1169 return out;
1170}
1171
1172
1173void SHAPE_ELLIPSE::sweepRange( double& aStart, double& aEnd ) const
1174{
1175 const double twoPi = 2.0 * M_PI;
1176
1177 if( !m_isArc )
1178 {
1179 aStart = 0.0;
1180 aEnd = twoPi;
1181 return;
1182 }
1183
1184 aStart = m_ellipse.StartAngle.AsRadians();
1185 aEnd = m_ellipse.EndAngle.AsRadians();
1186
1187 const double sweep = aEnd - aStart;
1188
1189 if( sweep >= twoPi || sweep <= -twoPi )
1190 aEnd = aStart + twoPi;
1191 else if( aEnd < aStart )
1192 aEnd += twoPi;
1193}
1194
1195
1196bool SHAPE_ELLIPSE::isAngleInSweep( double aAngleRad ) const
1197{
1198 const double twoPi = 2.0 * M_PI;
1199 double tStart, tEnd;
1200 sweepRange( tStart, tEnd );
1201
1202 // Reduce aAngleRad into [tStart, tStart + 2*pi).
1203 double t = aAngleRad;
1204 while( t < tStart )
1205 t += twoPi;
1206 while( t >= tStart + twoPi )
1207 t -= twoPi;
1208
1209 return t <= tEnd;
1210}
ERROR_LOC
When approximating an arc or circle, should the error be placed on the outside or inside of the curve...
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
Represent basic circle geometry with utility geometry functions.
Definition circle.h:33
VECTOR2I Center
Public to make access simpler.
Definition circle.h:150
int Radius
Public to make access simpler.
Definition circle.h:149
double AsRadians() const
Definition eda_angle.h:120
NumericType MinorRadius
Definition ellipse.h:103
EDA_ANGLE Rotation
Definition ellipse.h:104
NumericType MajorRadius
Definition ellipse.h:102
EDA_ANGLE GetAngleAtPoint(const VECTOR2< NumericType > &aPt) const
Get the parametric angle of a point on the ellipse.
Definition ellipse.cpp:109
VECTOR2< NumericType > Center
Definition ellipse.h:101
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
bool Contains(const SEG &aSeg) const
Definition seg.h:320
VECTOR2I NearestPoint(const VECTOR2I &aP) const
double GetRadius() const
const VECTOR2I & GetCenter() const
void SetRotation(const EDA_ANGLE &aAngle)
void updateCache()
Recompute cached sin/cos and inverse-radius-squared values.
SHAPE_LINE_CHAIN ConvertToPolyline(int aMaxError) const
Build a polyline approximation of the ellipse or arc.
void SetMajorRadius(int aRadius)
ELLIPSE< int > m_ellipse
Wrapped geometric data (from geometry/ellipse.h)
bool isAngleInSweep(double aAngleRad) const
Return true if aAngleRad falls between StartAngle and EndAngle (counter-clockwise sweep).
SEG::ecoord SquaredDistance(const VECTOR2I &aP, bool aOutlineOnly=false) const override
double m_invMinorRSq
1 / MinorRadius ^ 2
void SetStartAngle(const EDA_ANGLE &aAngle)
VECTOR2D toLocal(const VECTOR2I &aP) const
CONIC conicOf(const VECTOR2I &aCenter, double aMajorR, double aMinorR, const EDA_ANGLE &aRotation) const
Write an ellipse with the given world placement as a conic in this local frame.
double m_cosRot
cos(Rotation)
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.
bool PointInside(const VECTOR2I &aPt, int aAccuracy=0, bool aUseBBoxCache=false) const override
Check if point aP lies inside a closed shape.
const std::string Format(bool aCplusPlus=true) const override
Serialize the ellipse.
double GetLength() const
VECTOR2I toWorld(const VECTOR2D &aP) const
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
const BOX2I BBox(int aClearance=0) const override
Compute a bounding box of the shape, with a margin of aClearance a collision.
std::vector< double > conicRoots(const CONIC &aConic) const
Parameter angles of this curve where aConic is zero, already limited to its sweep.
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the ellipse across a horizontal or vertical axis passing through aRef.
VECTOR2D pointAtParam(double aTheta) const
Point on the full ellipse at parameter angle aTheta, in the local frame.
void sweepRange(double &aStart, double &aEnd) const
Canonical CCW sweep in radians; aEnd >= aStart. Used by all sweep-aware paths.
void normalize()
If major < minor, swap them and add 90 degrees to rotation.
VECTOR2I NearestPoint(const VECTOR2I &aP) const
Find the point on the curve closest to aP.
std::vector< VECTOR2I > intersectCircle(const VECTOR2I &aCenter, double aRadius) const
Points where this curve crosses a full circle, before any sweep of that circle applies.
void SetCenter(const VECTOR2I &aCenter)
std::vector< VECTOR2I > Intersect(const SHAPE_ELLIPSE &aOther) const
Find the points where this curve crosses another one.
void SetMinorRadius(int aRadius)
double m_invMajorRSq
1 / MajorRadius ^ 2
double m_sinRot
sin(Rotation)
VECTOR2D closestLocalPoint(const VECTOR2D &aLocal) const
Point of the curve closest to aLocal, both in the local frame.
void Move(const VECTOR2I &aVector) override
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,...
bool m_isArc
true if open elliptical arc, false if closed ellipse
void SetEndAngle(const EDA_ANGLE &aAngle)
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
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.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
void Remove(int aStartIndex, int aEndIndex)
Remove the range of points [start_index, end_index] from the line chain.
Represent a set of closed polygons.
int AddOutline(const SHAPE_LINE_CHAIN &aOutline)
Adds a new outline to the set and returns its index.
SHAPE(SHAPE_TYPE aType)
Create an empty shape of type aType.
Definition shape.h:134
virtual const std::string Format(bool aCplusPlus=true) const
Definition shape.cpp:43
static const int MIN_PRECISION_IU
This is the minimum precision for all the points in a shape.
Definition shape.h:129
double Distance(const VECTOR2< extended_type > &aVector) const
Compute the distance between two vectors.
Definition vector2d.h:549
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:422
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:424
@ RADIANS_T
Definition eda_angle.h:32
#define sq(x)
#define F(x, y, z)
Definition md5_hash.cpp:15
FLIP_DIRECTION
Definition mirror.h:23
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:411
#define D(x)
Definition ptree.cpp:37
@ SH_ELLIPSE
ellipse or elliptical arc
Definition shape.h:53
const int scale
A conic curve Axx x^2 + Axy xy + Ayy y^2 + Bx x + By y + C = 0, written in this ellipse's local frame...
const SHAPE_LINE_CHAIN chain
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.
int delta
#define M_PI
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:225
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682