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