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
24#include <algorithm>
25#include <cmath>
26#include <sstream>
27
28#include <trigo.h>
29
30
31namespace
32{
33
43template <typename F>
44double adaptiveSimpson( F f, double a, double b, double tol, int maxDepth );
45
46template <typename F>
47double adaptiveSimpsonRec( F f, double a, double b, double tol, double whole, double fa, double fb, double fm,
48 int depth )
49{
50 const double m = 0.5 * ( a + b );
51 const double lm = 0.5 * ( a + m );
52 const double rm = 0.5 * ( m + b );
53 const double flm = f( lm );
54 const double frm = f( rm );
55
56 const double left = ( m - a ) * ( fa + 4.0 * flm + fm ) / 6.0;
57 const double right = ( b - m ) * ( fm + 4.0 * frm + fb ) / 6.0;
58 const double diff = left + right - whole;
59
60 if( depth <= 0 || std::abs( diff ) < 15.0 * tol )
61 return left + right + diff / 15.0;
62
63 return adaptiveSimpsonRec( f, a, m, 0.5 * tol, left, fa, fm, flm, depth - 1 )
64 + adaptiveSimpsonRec( f, m, b, 0.5 * tol, right, fm, fb, frm, depth - 1 );
65}
66
67template <typename F>
68double adaptiveSimpson( F f, double a, double b, double tol, int maxDepth )
69{
70 const double fa = f( a );
71 const double fb = f( b );
72 const double fm = f( 0.5 * ( a + b ) );
73 const double whole = ( b - a ) * ( fa + 4.0 * fm + fb ) / 6.0;
74 return adaptiveSimpsonRec( f, a, b, tol, whole, fa, fb, fm, maxDepth );
75}
76
77
84template <typename Eval>
85void subdivideEllipseArc( double t0, const VECTOR2I& p0, double t1, const VECTOR2I& p1, double aMaxErrSq, int aDepth,
86 Eval aEval, SHAPE_LINE_CHAIN& aOut )
87{
88 if( aDepth <= 0 )
89 {
90 aOut.Append( p1 );
91 return;
92 }
93
94 const double tm = 0.5 * ( t0 + t1 );
95 const VECTOR2I pm = aEval( tm );
96
97 const double mx = 0.5 * ( static_cast<double>( p0.x ) + p1.x );
98 const double my = 0.5 * ( static_cast<double>( p0.y ) + p1.y );
99 const double ex = pm.x - mx;
100 const double ey = pm.y - my;
101
102 if( ex * ex + ey * ey <= aMaxErrSq )
103 {
104 aOut.Append( p1 );
105 return;
106 }
107
108 subdivideEllipseArc( t0, p0, tm, pm, aMaxErrSq, aDepth - 1, aEval, aOut );
109 subdivideEllipseArc( tm, pm, t1, p1, aMaxErrSq, aDepth - 1, aEval, aOut );
110}
111
112} // namespace
113
114
116 SHAPE( SH_ELLIPSE ),
117 m_ellipse(),
118 m_isArc( false ),
119 m_sinRot( 0.0 ),
120 m_cosRot( 1.0 ),
121 m_invMajorRSq( 0.0 ),
122 m_invMinorRSq( 0.0 )
123{
124}
125
126
127SHAPE_ELLIPSE::SHAPE_ELLIPSE( const VECTOR2I& aCenter, int aMajorRadius, int aMinorRadius,
128 const EDA_ANGLE& aRotation ) :
129 SHAPE( SH_ELLIPSE ),
130 m_ellipse( aCenter, aMajorRadius, aMinorRadius, aRotation ),
131 m_isArc( false )
132{
133 normalize();
134}
135
136
137SHAPE_ELLIPSE::SHAPE_ELLIPSE( const VECTOR2I& aCenter, int aMajorRadius, int aMinorRadius, const EDA_ANGLE& aRotation,
138 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aEndAngle ) :
139 SHAPE( SH_ELLIPSE ),
140 m_ellipse( aCenter, aMajorRadius, aMinorRadius, aRotation, aStartAngle, aEndAngle ),
141 m_isArc( true )
142{
143 normalize();
144}
145
146
147SHAPE_ELLIPSE::SHAPE_ELLIPSE( const VECTOR2I& aCenter, const VECTOR2I& aMajorEndpoint, double aRatio ) :
148 SHAPE( SH_ELLIPSE ),
149 m_ellipse( aCenter, aMajorEndpoint, aRatio ),
150 m_isArc( false )
151{
152 normalize();
153}
154
155
156SHAPE_ELLIPSE::SHAPE_ELLIPSE( const VECTOR2I& aCenter, const VECTOR2I& aMajorEndpoint, double aRatio,
157 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aEndAngle ) :
158 SHAPE( SH_ELLIPSE ),
159 m_ellipse( aCenter, aMajorEndpoint, aRatio, aStartAngle, aEndAngle ),
160 m_isArc( true )
161{
162 normalize();
163}
164
165
167{
168 m_ellipse.MajorRadius = std::max( 1, m_ellipse.MajorRadius );
169 m_ellipse.MinorRadius = std::max( 1, m_ellipse.MinorRadius );
170
171 if( m_ellipse.MajorRadius < m_ellipse.MinorRadius )
172 {
173 std::swap( m_ellipse.MajorRadius, m_ellipse.MinorRadius );
174 m_ellipse.Rotation += ANGLE_90;
175
176 if( m_isArc )
177 {
178 m_ellipse.StartAngle -= ANGLE_90;
179 m_ellipse.EndAngle -= ANGLE_90;
180 }
181 }
182
183 updateCache();
184}
185
186
187void SHAPE_ELLIPSE::SetCenter( const VECTOR2I& aCenter )
188{
189 m_ellipse.Center = aCenter;
190}
191
192
194{
195 m_ellipse.MajorRadius = aRadius;
196 normalize();
197}
198
199
201{
202 m_ellipse.MinorRadius = aRadius;
203 normalize();
204}
205
206
208{
209 m_ellipse.Rotation = aAngle;
210 updateCache();
211}
212
213
215{
216 m_ellipse.StartAngle = aAngle;
217}
218
219
221{
222 m_ellipse.EndAngle = aAngle;
223}
224
225
226const BOX2I SHAPE_ELLIPSE::BBox( int aClearance ) const
227{
228 const double a = static_cast<double>( m_ellipse.MajorRadius );
229 const double b = static_cast<double>( m_ellipse.MinorRadius );
230
231 if( !m_isArc )
232 {
233 const double cos2 = m_cosRot * m_cosRot;
234 const double sin2 = m_sinRot * m_sinRot;
235
236 const double dx = std::sqrt( a * a * cos2 + b * b * sin2 );
237 const double dy = std::sqrt( a * a * sin2 + b * b * cos2 );
238
239 const int idx = static_cast<int>( std::ceil( dx ) ) + aClearance;
240 const int idy = static_cast<int>( std::ceil( dy ) ) + aClearance;
241
242 return BOX2I( VECTOR2I( m_ellipse.Center.x - idx, m_ellipse.Center.y - idy ), VECTOR2I( 2 * idx, 2 * idy ) );
243 }
244
245 auto eval = [&]( double theta ) -> VECTOR2D
246 {
247 const double ct = std::cos( theta );
248 const double st = std::sin( theta );
249 return VECTOR2D( a * ct * m_cosRot - b * st * m_sinRot, a * ct * m_sinRot + b * st * m_cosRot );
250 };
251
252 const VECTOR2D p0 = eval( m_ellipse.StartAngle.AsRadians() );
253 const VECTOR2D p1 = eval( m_ellipse.EndAngle.AsRadians() );
254
255 double minX = std::min( p0.x, p1.x );
256 double maxX = std::max( p0.x, p1.x );
257 double minY = std::min( p0.y, p1.y );
258 double maxY = std::max( p0.y, p1.y );
259
260 const double thetaX = std::atan2( -b * m_sinRot, a * m_cosRot );
261 const double thetaY = std::atan2( b * m_cosRot, a * m_sinRot );
262
263 const double candidates[4] = { thetaX, thetaX + M_PI, thetaY, thetaY + M_PI };
264
265 for( double c : candidates )
266 {
267 if( !isAngleInSweep( c ) )
268 continue;
269
270 const VECTOR2D p = eval( c );
271 minX = std::min( minX, p.x );
272 maxX = std::max( maxX, p.x );
273 minY = std::min( minY, p.y );
274 maxY = std::max( maxY, p.y );
275 }
276
277 const int iMinX = static_cast<int>( std::floor( minX ) ) - aClearance;
278 const int iMaxX = static_cast<int>( std::ceil( maxX ) ) + aClearance;
279 const int iMinY = static_cast<int>( std::floor( minY ) ) - aClearance;
280 const int iMaxY = static_cast<int>( std::ceil( maxY ) ) + aClearance;
281
282 return BOX2I( VECTOR2I( m_ellipse.Center.x + iMinX, m_ellipse.Center.y + iMinY ),
283 VECTOR2I( iMaxX - iMinX, iMaxY - iMinY ) );
284}
285
286
288{
289 const double a = static_cast<double>( m_ellipse.MajorRadius );
290 const double b = static_cast<double>( m_ellipse.MinorRadius );
291
292 if( !m_isArc )
293 {
294 // Ramanujan's second approximation
295 // See https://en.wikipedia.org/wiki/Perimeter_of_an_ellipse
296 const double h = ( a - b ) / ( a + b );
297 const double h2 = h * h;
298 return M_PI * ( a + b ) * ( 1.0 + 3.0 * h2 / ( 10.0 + std::sqrt( 4.0 - 3.0 * h2 ) ) );
299 }
300
301 auto integrand = [a, b]( double theta ) -> double
302 {
303 const double s = std::sin( theta );
304 const double c = std::cos( theta );
305 return std::sqrt( a * a * s * s + b * b * c * c );
306 };
307
308 double t0, t1;
309 sweepRange( t0, t1 );
310
311 return adaptiveSimpson( integrand, t0, t1, 1e-9, 20 );
312}
313
314
315bool SHAPE_ELLIPSE::Collide( const SEG& aSeg, int aClearance, int* aActual, VECTOR2I* aLocation ) const
316{
317 if( aSeg.A == aSeg.B )
318 {
319 const SEG::ecoord dSq = SquaredDistance( aSeg.A, false );
320 const SEG::ecoord clearSq = static_cast<SEG::ecoord>( aClearance ) * static_cast<SEG::ecoord>( aClearance );
321
322 if( dSq == 0 || dSq < clearSq )
323 {
324 if( aActual )
325 *aActual = static_cast<int>( std::round( std::sqrt( static_cast<double>( dSq ) ) ) );
326 if( aLocation )
327 *aLocation = aSeg.A;
328 return true;
329 }
330
331 return false;
332 }
333
334 auto toLocal = [&]( const VECTOR2I& p ) -> VECTOR2D
335 {
336 const double dx = p.x - m_ellipse.Center.x;
337 const double dy = p.y - m_ellipse.Center.y;
338 return VECTOR2D( dx * m_cosRot + dy * m_sinRot, -dx * m_sinRot + dy * m_cosRot );
339 };
340
341 auto toWorld = [&]( const VECTOR2D& p ) -> VECTOR2I
342 {
343 const double wx = p.x * m_cosRot - p.y * m_sinRot;
344 const double wy = p.x * m_sinRot + p.y * m_cosRot;
345 return VECTOR2I( m_ellipse.Center.x + static_cast<int>( std::round( wx ) ),
346 m_ellipse.Center.y + static_cast<int>( std::round( wy ) ) );
347 };
348
349 const VECTOR2D Aloc = toLocal( aSeg.A );
350 const VECTOR2D Bloc = toLocal( aSeg.B );
351 const VECTOR2D D( Bloc.x - Aloc.x, Bloc.y - Aloc.y );
352
353 const double a = static_cast<double>( m_ellipse.MajorRadius );
354 const double b = static_cast<double>( m_ellipse.MinorRadius );
355 const double aSq = a * a;
356 const double bSq = b * b;
357
358 const double alpha = D.x * D.x / aSq + D.y * D.y / bSq;
359 const double beta = 2.0 * ( Aloc.x * D.x / aSq + Aloc.y * D.y / bSq );
360 const double gamma = Aloc.x * Aloc.x / aSq + Aloc.y * Aloc.y / bSq - 1.0;
361
362 // A is inside the closed ellipse if gamma < 0, B is inside if alpha + beta + gamma < 0
363 const double valB = alpha + beta + gamma;
364
365 if( !m_isArc )
366 {
367 if( gamma <= 0.0 )
368 {
369 if( aActual )
370 *aActual = 0;
371 if( aLocation )
372 *aLocation = aSeg.A;
373 return true;
374 }
375 if( valB <= 0.0 )
376 {
377 if( aActual )
378 *aActual = 0;
379 if( aLocation )
380 *aLocation = aSeg.B;
381 return true;
382 }
383 }
384
385 // disc < 0 means the line misses the ellipse
386 const double disc = beta * beta - 4.0 * alpha * gamma;
387
388 if( disc >= 0.0 && alpha > 0.0 )
389 {
390 const double sqrtDisc = std::sqrt( disc );
391 const double twoAlpha = 2.0 * alpha;
392 const double t0 = ( -beta - sqrtDisc ) / twoAlpha;
393 const double t1 = ( -beta + sqrtDisc ) / twoAlpha;
394 const double roots[2] = { t0, t1 };
395
396 for( double t : roots )
397 {
398 if( t < 0.0 || t > 1.0 )
399 continue;
400
401 const VECTOR2D hit( Aloc.x + t * D.x, Aloc.y + t * D.y );
402
403 // For arcs, the intersection must be within the angular sweep.
404 if( m_isArc )
405 {
406 const double angle = std::atan2( hit.y / b, hit.x / a );
407 if( !isAngleInSweep( angle ) )
408 continue;
409 }
410
411 if( aActual )
412 *aActual = 0;
413 if( aLocation )
414 *aLocation = toWorld( hit );
415 return true;
416 }
417 }
418
419 double minDistSq = std::numeric_limits<double>::max();
420 VECTOR2D bestOnSegment( 0.0, 0.0 );
421
422 {
423 const double dA = static_cast<double>( SquaredDistance( aSeg.A, true ) );
424 const double dB = static_cast<double>( SquaredDistance( aSeg.B, true ) );
425
426 if( dA < minDistSq )
427 {
428 minDistSq = dA;
429 bestOnSegment = Aloc;
430 }
431 if( dB < minDistSq )
432 {
433 minDistSq = dB;
434 bestOnSegment = Bloc;
435 }
436 }
437
438 const double dDotD = D.x * D.x + D.y * D.y;
439
440 // Check where ellipse outline runs parallel to the segment
441 if( dDotD > 0.0 )
442 {
443 const double theta0 = std::atan2( -b * D.x, a * D.y );
444 const double thetas[2] = { theta0, theta0 + M_PI };
445
446 for( double theta : thetas )
447 {
448 if( m_isArc && !isAngleInSweep( theta ) )
449 continue;
450
451 const double ex = a * std::cos( theta );
452 const double ey = b * std::sin( theta );
453
454 // Orthogonal projection of (ex, ey) onto the segment
455 const double pDotD = ( ex - Aloc.x ) * D.x + ( ey - Aloc.y ) * D.y;
456 const double t = std::clamp( pDotD / dDotD, 0.0, 1.0 );
457 const double qx = Aloc.x + t * D.x;
458 const double qy = Aloc.y + t * D.y;
459
460 const double distSq = ( ex - qx ) * ( ex - qx ) + ( ey - qy ) * ( ey - qy );
461
462 if( distSq < minDistSq )
463 {
464 minDistSq = distSq;
465 bestOnSegment = VECTOR2D( qx, qy );
466 }
467 }
468 }
469
470 // Arc endpoints projected onto segment.
471 if( m_isArc && dDotD > 0.0 )
472 {
473 const EDA_ANGLE endAngles[2] = { m_ellipse.StartAngle, m_ellipse.EndAngle };
474
475 for( const EDA_ANGLE& endAngle : endAngles )
476 {
477 const double angleRad = endAngle.AsRadians();
478 const double ex = a * std::cos( angleRad );
479 const double ey = b * std::sin( angleRad );
480
481 const double pDotD = ( ex - Aloc.x ) * D.x + ( ey - Aloc.y ) * D.y;
482 const double t = std::clamp( pDotD / dDotD, 0.0, 1.0 );
483 const double qx = Aloc.x + t * D.x;
484 const double qy = Aloc.y + t * D.y;
485
486 const double distSq = ( ex - qx ) * ( ex - qx ) + ( ey - qy ) * ( ey - qy );
487
488 if( distSq < minDistSq )
489 {
490 minDistSq = distSq;
491 bestOnSegment = VECTOR2D( qx, qy );
492 }
493 }
494 }
495
496 // Clearance comparison
497 const double thresholdSq = static_cast<double>( aClearance ) * static_cast<double>( aClearance );
498
499 if( minDistSq > 0.0 && minDistSq >= thresholdSq )
500 return false;
501
502 if( aActual )
503 *aActual = static_cast<int>( std::round( std::sqrt( minDistSq ) ) );
504 if( aLocation )
505 *aLocation = toWorld( bestOnSegment );
506 return true;
507}
508
509
510// ERROR_LOC is unused, tessellation points sit on the true ellipse curve, not offset inward or outward.
511void SHAPE_ELLIPSE::TransformToPolygon( SHAPE_POLY_SET& aBuffer, int aError, ERROR_LOC /*aErrorLoc*/ ) const
512{
513 if( m_isArc )
514 return;
515
517 chain.SetClosed( true );
518 aBuffer.AddOutline( chain );
519}
520
521
522void SHAPE_ELLIPSE::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
523{
524 RotatePoint( m_ellipse.Center, aCenter, aAngle );
525 m_ellipse.Rotation -= aAngle;
526 updateCache();
527}
528
529
530void SHAPE_ELLIPSE::Mirror( const VECTOR2I& aRef, FLIP_DIRECTION aFlipDirection )
531{
532 m_ellipse.Mirror( aRef, aFlipDirection );
533 updateCache();
534}
535
536
537const std::string SHAPE_ELLIPSE::Format( bool aCplusPlus ) const
538{
539 std::stringstream ss;
540
541 if( aCplusPlus )
542 {
543 ss << "SHAPE_ELLIPSE( VECTOR2I( " << m_ellipse.Center.x << ", " << m_ellipse.Center.y << " ), "
544 << m_ellipse.MajorRadius << ", " << m_ellipse.MinorRadius << ", EDA_ANGLE( "
545 << m_ellipse.Rotation.AsDegrees() << ", DEGREES_T )";
546
547 if( m_isArc )
548 {
549 ss << ", EDA_ANGLE( " << m_ellipse.StartAngle.AsDegrees() << ", DEGREES_T )"
550 << ", EDA_ANGLE( " << m_ellipse.EndAngle.AsDegrees() << ", DEGREES_T )";
551 }
552
553 ss << " );";
554 }
555 else
556 {
557 ss << SHAPE::Format( aCplusPlus ) << " " << m_ellipse.Center.x << " " << m_ellipse.Center.y << " "
558 << m_ellipse.MajorRadius << " " << m_ellipse.MinorRadius << " " << m_ellipse.Rotation.AsDegrees() << " "
559 << ( m_isArc ? 1 : 0 );
560
561 if( m_isArc )
562 {
563 ss << " " << m_ellipse.StartAngle.AsDegrees() << " " << m_ellipse.EndAngle.AsDegrees();
564 }
565 }
566
567 return ss.str();
568}
569
570
571void SHAPE_ELLIPSE::Move( const VECTOR2I& aVector )
572{
573 m_ellipse.Center += aVector;
574}
575
576
578{
579 const double rotRad = m_ellipse.Rotation.AsRadians();
580 m_sinRot = std::sin( rotRad );
581 m_cosRot = std::cos( rotRad );
582
583 const double a = static_cast<double>( m_ellipse.MajorRadius );
584 const double b = static_cast<double>( m_ellipse.MinorRadius );
585 m_invMajorRSq = 1.0 / ( a * a );
586 m_invMinorRSq = 1.0 / ( b * b );
587}
588
589
590bool SHAPE_ELLIPSE::PointInside( const VECTOR2I& aPt, int aAccuracy, bool /*aUseBBoxCache*/ ) const
591{
592 // No interior for elliptical arcs. Open curve
593 if( m_isArc )
594 return false;
595
596 const double dx = aPt.x - m_ellipse.Center.x;
597 const double dy = aPt.y - m_ellipse.Center.y;
598 const double lx = dx * m_cosRot + dy * m_sinRot;
599 const double ly = -dx * m_sinRot + dy * m_cosRot;
600
601 if( aAccuracy > 0 )
602 {
603 // Increase both radii by aAccuracy.
604 const double a = static_cast<double>( m_ellipse.MajorRadius ) + aAccuracy;
605 const double b = static_cast<double>( m_ellipse.MinorRadius ) + aAccuracy;
606 return ( lx * lx ) / ( a * a ) + ( ly * ly ) / ( b * b ) < 1.0;
607 }
608
609 return lx * lx * m_invMajorRSq + ly * ly * m_invMinorRSq < 1.0;
610}
611
612
613SEG::ecoord SHAPE_ELLIPSE::SquaredDistance( const VECTOR2I& aP, bool aOutlineOnly ) const
614{
615 // Transform into the ellipse's local frame.
616 const double dx = aP.x - m_ellipse.Center.x;
617 const double dy = aP.y - m_ellipse.Center.y;
618 const double lx = dx * m_cosRot + dy * m_sinRot;
619 const double ly = -dx * m_sinRot + dy * m_cosRot;
620
621 const double a = static_cast<double>( m_ellipse.MajorRadius );
622 const double b = static_cast<double>( m_ellipse.MinorRadius );
623
624 // Interior of a closed ellipse if val < 1
625 if( !m_isArc && !aOutlineOnly )
626 {
627 const double val = lx * lx * m_invMajorRSq + ly * ly * m_invMinorRSq;
628
629 if( val <= 1.0 )
630 return 0;
631 }
632
633 // Closest point on ellipse via Eberly's bisection.
634 // Reference: "Distance from a Point to an Ellipse, an Ellipsoid, or a
635 // Hyperellipsoid", David Eberly, Geometric Tools.
636 // https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf
637
638 const double y0 = std::abs( lx );
639 const double y1 = std::abs( ly );
640
641 double x0Local = 0.0;
642 double x1Local = 0.0;
643
644 if( y1 > 0.0 )
645 {
646 if( y0 > 0.0 )
647 {
648 const double z0 = y0 / a;
649 const double z1 = y1 / b;
650 const double g = z0 * z0 + z1 * z1 - 1.0;
651
652 if( g != 0.0 )
653 {
654 const double r0 = ( a / b ) * ( a / b );
655 const double n0 = r0 * z0;
656
657 double s0 = z1 - 1.0;
658 double s1 = ( g < 0.0 ) ? 0.0 : std::sqrt( n0 * n0 + z1 * z1 ) - 1.0;
659 double s = 0.0;
660
661 for( int iter = 0; iter < 64; ++iter )
662 {
663 s = 0.5 * ( s0 + s1 );
664
665 if( s == s0 || s == s1 )
666 break;
667
668 const double ratio0 = n0 / ( s + r0 );
669 const double ratio1 = z1 / ( s + 1.0 );
670 const double gs = ratio0 * ratio0 + ratio1 * ratio1 - 1.0;
671
672 if( gs > 0.0 )
673 s0 = s;
674 else if( gs < 0.0 )
675 s1 = s;
676 else
677 break;
678 }
679
680 x0Local = r0 * y0 / ( s + r0 );
681 x1Local = y1 / ( s + 1.0 );
682 }
683 else
684 {
685 // Point is on the ellipse.
686 x0Local = y0;
687 x1Local = y1;
688 }
689 }
690 else
691 {
692 // y0 == 0 point lies on the minor axis.
693 x0Local = 0.0;
694 x1Local = b;
695 }
696 }
697 else
698 {
699 // y1 == 0 point lies on the major axis.
700 const double numer0 = a * y0;
701 const double denom0 = a * a - b * b;
702
703 if( numer0 < denom0 )
704 {
705 const double xde0 = numer0 / denom0;
706 x0Local = a * xde0;
707 x1Local = b * std::sqrt( std::max( 0.0, 1.0 - xde0 * xde0 ) );
708 }
709 else
710 {
711 x0Local = a;
712 x1Local = 0.0;
713 }
714 }
715
716 const double closestX = ( lx < 0.0 ) ? -x0Local : x0Local;
717 const double closestY = ( ly < 0.0 ) ? -x1Local : x1Local;
718
719 // Pick the nearest boundary point
720 if( m_isArc )
721 {
722 const double closestTheta = std::atan2( closestY / b, closestX / a );
723
724 if( !isAngleInSweep( closestTheta ) )
725 {
726 const double s0 = m_ellipse.StartAngle.AsRadians();
727 const double e0 = m_ellipse.EndAngle.AsRadians();
728 const double ex0 = a * std::cos( s0 );
729 const double ey0 = b * std::sin( s0 );
730 const double ex1 = a * std::cos( e0 );
731 const double ey1 = b * std::sin( e0 );
732
733 const double d0 = ( lx - ex0 ) * ( lx - ex0 ) + ( ly - ey0 ) * ( ly - ey0 );
734 const double d1 = ( lx - ex1 ) * ( lx - ex1 ) + ( ly - ey1 ) * ( ly - ey1 );
735
736 return static_cast<SEG::ecoord>( std::min( d0, d1 ) );
737 }
738 }
739
740 const double dxE = closestX - lx;
741 const double dyE = closestY - ly;
742 return static_cast<SEG::ecoord>( dxE * dxE + dyE * dyE );
743}
744
745
747{
748 if( aMaxError < 1 )
749 aMaxError = 1;
750
751 const double a = static_cast<double>( m_ellipse.MajorRadius );
752 const double b = static_cast<double>( m_ellipse.MinorRadius );
753 const double cx = m_ellipse.Center.x;
754 const double cy = m_ellipse.Center.y;
755 const double sinRot = m_sinRot;
756 const double cosRot = m_cosRot;
757
758 auto eval = [=]( double theta ) -> VECTOR2I
759 {
760 const double ct = std::cos( theta );
761 const double st = std::sin( theta );
762 const double lx = a * ct;
763 const double ly = b * st;
764 const double wx = lx * cosRot - ly * sinRot;
765 const double wy = lx * sinRot + ly * cosRot;
766 return VECTOR2I( static_cast<int>( std::round( cx + wx ) ), static_cast<int>( std::round( cy + wy ) ) );
767 };
768
769 double tStart, tEnd;
770 sweepRange( tStart, tEnd );
771
772 const double maxErrSq = static_cast<double>( aMaxError ) * aMaxError;
773
775 const VECTOR2I pStart = eval( tStart );
776 const VECTOR2I pEnd = eval( tEnd );
777
778 out.Append( pStart );
779 subdivideEllipseArc( tStart, pStart, tEnd, pEnd, maxErrSq, 20, eval, out );
780
781 if( !m_isArc )
782 {
783 if( out.PointCount() > 1 && out.CPoint( 0 ) == out.CPoint( -1 ) )
784 out.Remove( out.PointCount() - 1 );
785
786 out.SetClosed( true );
787 }
788
789 return out;
790}
791
792
793void SHAPE_ELLIPSE::sweepRange( double& aStart, double& aEnd ) const
794{
795 const double twoPi = 2.0 * M_PI;
796
797 if( !m_isArc )
798 {
799 aStart = 0.0;
800 aEnd = twoPi;
801 return;
802 }
803
804 aStart = m_ellipse.StartAngle.AsRadians();
805 aEnd = m_ellipse.EndAngle.AsRadians();
806
807 const double sweep = aEnd - aStart;
808
809 if( sweep >= twoPi || sweep <= -twoPi )
810 aEnd = aStart + twoPi;
811 else if( aEnd < aStart )
812 aEnd += twoPi;
813}
814
815
816bool SHAPE_ELLIPSE::isAngleInSweep( double aAngleRad ) const
817{
818 const double twoPi = 2.0 * M_PI;
819 double tStart, tEnd;
820 sweepRange( tStart, tEnd );
821
822 // Reduce aAngleRad into [tStart, tStart + 2*pi).
823 double t = aAngleRad;
824 while( t < tStart )
825 t += twoPi;
826 while( t >= tStart + twoPi )
827 t -= twoPi;
828
829 return t <= tEnd;
830}
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:918
Definition seg.h:38
VECTOR2I A
Definition seg.h:45
VECTOR2I::extended_type ecoord
Definition seg.h:40
VECTOR2I B
Definition seg.h:46
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)
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
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.
void Mirror(const VECTOR2I &aRef, FLIP_DIRECTION aFlipDirection)
Mirror the ellipse across a horizontal or vertical axis passing through aRef.
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.
void SetCenter(const VECTOR2I &aCenter)
void SetMinorRadius(int aRadius)
double m_invMajorRSq
1 / MajorRadius ^ 2
double m_sinRot
sin(Rotation)
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 constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
#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:400
#define D(x)
Definition ptree.cpp:37
@ SH_ELLIPSE
ellipse or elliptical arc
Definition shape.h:53
const SHAPE_LINE_CHAIN chain
#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