KiCad PCB EDA Suite
Loading...
Searching...
No Matches
convert_basic_shapes_to_polygon.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <algorithm> // for max, min
26#include <bitset> // for bitset::count
27#include <climits> // for INT_MAX
28#include <limits> // for numeric_limits
29#include <cmath> // for isfinite
30#include <cstdint> // for int64_t
31#include <math.h> // for atan2
32
36#include <geometry/shape_arc.h> // for SHAPE_ARC
37#include <geometry/shape_line_chain.h> // for SHAPE_LINE_CHAIN
38#include <geometry/shape_poly_set.h> // for SHAPE_POLY_SET, SHAPE_POLY_SE...
39#include <math/util.h>
40#include <math/vector2d.h> // for VECTOR2I
41#include <trigo.h>
42
43#include <wx/log.h>
44
45void TransformCircleToPolygon( SHAPE_LINE_CHAIN& aBuffer, const VECTOR2I& aCenter, int aRadius,
46 int aError, ERROR_LOC aErrorLoc, int aMinSegCount )
47{
48 VECTOR2I corner_position;
49 int numSegs = GetArcToSegmentCount( aRadius, aError, FULL_CIRCLE );
50 numSegs = std::max( aMinSegCount, numSegs );
51
52 // Round up to 8 to make segment approximations align properly at 45-degrees
53 numSegs = ( numSegs + 7 ) / 8 * 8;
54
55 EDA_ANGLE delta = ANGLE_360 / numSegs;
56 int radius = aRadius;
57
58 if( aErrorLoc == ERROR_OUTSIDE )
59 {
60 // The outer radius should be radius+aError
61 // Recalculate the actual approx error, as it can be smaller than aError
62 // because numSegs is clamped to a minimal value
63 int actual_delta_radius = CircleToEndSegmentDeltaRadius( radius, numSegs );
64 radius += GetCircleToPolyCorrection( actual_delta_radius );
65 }
66
67 for( EDA_ANGLE angle = delta / 2; angle < ANGLE_360; angle += delta )
68 {
69 corner_position.x = radius;
70 corner_position.y = 0;
71 RotatePoint( corner_position, angle );
72 corner_position += aCenter;
73 aBuffer.Append( corner_position.x, corner_position.y );
74 }
75
76 aBuffer.SetClosed( true );
77}
78
79
80void TransformCircleToPolygon( SHAPE_POLY_SET& aBuffer, const VECTOR2I& aCenter, int aRadius,
81 int aError, ERROR_LOC aErrorLoc, int aMinSegCount )
82{
83 VECTOR2I corner_position;
84 int numSegs = GetArcToSegmentCount( aRadius, aError, FULL_CIRCLE );
85 numSegs = std::max( aMinSegCount, numSegs );
86
87 // Round up to 8 to make segment approximations align properly at 45-degrees
88 numSegs = ( numSegs + 7 ) / 8 * 8;
89
90 EDA_ANGLE delta = ANGLE_360 / numSegs;
91 int radius = aRadius;
92
93 if( aErrorLoc == ERROR_OUTSIDE )
94 {
95 // The outer radius should be radius+aError
96 // Recalculate the actual approx error, as it can be smaller than aError
97 // because numSegs is clamped to a minimal value
98 int actual_delta_radius = CircleToEndSegmentDeltaRadius( radius, numSegs );
99 radius += GetCircleToPolyCorrection( actual_delta_radius );
100 }
101
102 aBuffer.NewOutline();
103
104 for( EDA_ANGLE angle = delta / 2; angle < ANGLE_360; angle += delta )
105 {
106 corner_position.x = radius;
107 corner_position.y = 0;
108 RotatePoint( corner_position, angle );
109 corner_position += aCenter;
110 aBuffer.Append( corner_position.x, corner_position.y );
111 }
112
113 // Finish circle
114 corner_position.x = radius;
115 corner_position.y = 0;
116 RotatePoint( corner_position, delta / 2 );
117 corner_position += aCenter;
118 aBuffer.Append( corner_position.x, corner_position.y );
119}
120
121
122void TransformOvalToPolygon( SHAPE_POLY_SET& aBuffer, const VECTOR2I& aStart, const VECTOR2I& aEnd,
123 int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount )
124{
125 // To build the polygonal shape outside the actual shape, we use a bigger
126 // radius to build rounded ends.
127 // However, the width of the segment is too big.
128 // so, later, we will clamp the polygonal shape with the bounding box
129 // of the segment.
130 int radius = aWidth / 2;
131 int numSegs = GetArcToSegmentCount( radius, aError, FULL_CIRCLE );
132 numSegs = std::max( aMinSegCount, numSegs );
133
134 // Round up to 8 to make segment approximations align properly at 45-degrees
135 numSegs = ( numSegs + 7 ) / 8 * 8;
136
137 EDA_ANGLE delta = ANGLE_360 / numSegs;
138
139 if( aErrorLoc == ERROR_OUTSIDE )
140 {
141 // The outer radius should be radius+aError
142 // Recalculate the actual approx error, as it can be smaller than aError
143 // because numSegs is clamped to a minimal value
144 int actual_delta_radius = CircleToEndSegmentDeltaRadius( radius, numSegs );
145 int correction = GetCircleToPolyCorrection( actual_delta_radius );
147 }
148
149 // end point is the coordinate relative to aStart
150 VECTOR2I endp = aEnd - aStart;
151 VECTOR2I startp = aStart;
152 VECTOR2I corner;
153 SHAPE_POLY_SET polyshape;
154
155 polyshape.NewOutline();
156
157 // normalize the position in order to have endp.x >= 0
158 // it makes calculations more easy to understand
159 if( endp.x < 0 )
160 {
161 endp = aStart - aEnd;
162 startp = aEnd;
163 }
164
165 EDA_ANGLE delta_angle( endp );
166 int seg_len = endp.EuclideanNorm();
167
168 // Compute the outlines of the segment, and creates a polygon
169 // Note: the polygonal shape is built from the equivalent horizontal
170 // segment starting at {0,0}, and ending at {seg_len,0}
171
172 // add right rounded end:
173
174 // Right arc start:
175 corner = VECTOR2I( seg_len, radius );
176 polyshape.Append( corner.x, corner.y );
177
178 for( EDA_ANGLE angle = delta / 2; angle < ANGLE_180; angle += delta )
179 {
180 corner = VECTOR2I( 0, radius );
181 RotatePoint( corner, angle );
182 corner.x += seg_len;
183 polyshape.Append( corner.x, corner.y );
184 }
185
186 // Finish right arc:
187 corner = VECTOR2I( seg_len, -radius );
188 polyshape.Append( corner.x, corner.y );
189
190 // Left arc start:
191 corner = VECTOR2I( 0, -radius );
192 polyshape.Append( corner.x, corner.y );
193
194 // add left rounded end:
195 for( EDA_ANGLE angle = delta / 2; angle < ANGLE_180; angle += delta )
196 {
197 corner = VECTOR2I( 0, -radius );
198 RotatePoint( corner, angle );
199 polyshape.Append( corner.x, corner.y );
200 }
201
202 // Finish left arc:
203 corner = VECTOR2I( 0, radius );
204 polyshape.Append( corner.x, corner.y );
205
206 // Now trim the edges of the polygonal shape which will be slightly outside the
207 // track width.
208 SHAPE_POLY_SET bbox;
209 bbox.NewOutline();
210 // Build the bbox (a horizontal rectangle).
211 int halfwidth = aWidth / 2; // Use the exact segment width for the bbox height
212 corner.x = -radius - 2; // use a bbox width slightly bigger to avoid
213 // creating useless corner at segment ends
214 corner.y = halfwidth;
215 bbox.Append( corner.x, corner.y );
216 corner.y = -halfwidth;
217 bbox.Append( corner.x, corner.y );
218 corner.x = radius + seg_len + 2;
219 bbox.Append( corner.x, corner.y );
220 corner.y = halfwidth;
221 bbox.Append( corner.x, corner.y );
222
223 // Now, clamp the shape
224 polyshape.BooleanIntersection( bbox );
225 // Note the final polygon is a simple, convex polygon with no hole
226 // due to the shape of initial polygons
227
228 // Rotate and move the polygon to its right location
229 polyshape.Rotate( -delta_angle );
230 polyshape.Move( startp );
231
232 aBuffer.Append( polyshape);
233}
234
235
237{
240 ROUNDED_CORNER( int x, int y ) : m_position( VECTOR2I( x, y ) ), m_radius( 0 ) {}
241 ROUNDED_CORNER( int x, int y, int radius ) : m_position( VECTOR2I( x, y ) ), m_radius( radius ) {}
242};
243
244
245// Corner List requirements: no concave shape, corners in clockwise order, no duplicate corners
246void CornerListToPolygon( SHAPE_POLY_SET& outline, std::vector<ROUNDED_CORNER>& aCorners,
247 int aInflate, int aError, ERROR_LOC aErrorLoc )
248{
249 assert( aInflate >= 0 );
250 outline.NewOutline();
251 VECTOR2I incoming = aCorners[0].m_position - aCorners.back().m_position;
252
253 for( int n = 0, count = aCorners.size(); n < count; n++ )
254 {
255 ROUNDED_CORNER& cur = aCorners[n];
256 ROUNDED_CORNER& next = aCorners[( n + 1 ) % count];
257 VECTOR2I outgoing = next.m_position - cur.m_position;
258
259 if( !( aInflate || cur.m_radius ) )
260 {
261 outline.Append( cur.m_position );
262 }
263 else
264 {
265 VECTOR2I cornerPosition = cur.m_position;
266 int radius = cur.m_radius;
267 EDA_ANGLE endAngle;
268 double tanAngle2;
269
270 if( ( incoming.x == 0 && outgoing.y == 0 ) || ( incoming.y == 0 && outgoing.x == 0 ) )
271 {
272 endAngle = ANGLE_90;
273 tanAngle2 = 1.0;
274 }
275 else
276 {
277 double cosNum = (double) incoming.x * outgoing.x + (double) incoming.y * outgoing.y;
278 double cosDen = (double) incoming.EuclideanNorm() * outgoing.EuclideanNorm();
279 double angle = acos( cosNum / cosDen );
280 tanAngle2 = tan( ( M_PI - angle ) / 2 );
281 endAngle = EDA_ANGLE( angle, RADIANS_T );
282 }
283
284 if( aInflate && tanAngle2 )
285 {
286 radius += aInflate;
287 cornerPosition += incoming.Resize( aInflate / tanAngle2 )
288 + incoming.Perpendicular().Resize( -aInflate );
289 }
290
291 // Ensure 16+ segments per 360deg and ensure first & last segment are the same size
292 int numSegs = std::max( 16, GetArcToSegmentCount( radius, aError, FULL_CIRCLE ) );
293 EDA_ANGLE angDelta = ANGLE_360 / numSegs;
294 EDA_ANGLE lastSeg = endAngle;
295
296 if( lastSeg > ANGLE_0 )
297 {
298 while( lastSeg > angDelta )
299 lastSeg -= angDelta;
300 }
301 else
302 {
303 while( lastSeg < -angDelta )
304 lastSeg += angDelta;
305 }
306
307 EDA_ANGLE angPos = lastSeg.IsZero() ? angDelta : ( angDelta + lastSeg ) / 2;
308
309 double arcTransitionDistance = ( tanAngle2 > 0 ) ? ( radius / tanAngle2 ) : 0;
310 VECTOR2I arcStart = cornerPosition - incoming.Resize( arcTransitionDistance );
311 VECTOR2I arcCenter = arcStart + incoming.Perpendicular().Resize( radius );
312 VECTOR2I arcEnd, arcStartOrigin;
313
314 if( aErrorLoc == ERROR_INSIDE )
315 {
316 arcEnd = SEG( cornerPosition, arcCenter ).ReflectPoint( arcStart );
317 arcStartOrigin = arcStart - arcCenter;
318 outline.Append( arcStart );
319 }
320 else
321 {
322 // The outer radius should be radius+aError, recalculate because numSegs is clamped
323 int actualDeltaRadius = CircleToEndSegmentDeltaRadius( radius, numSegs );
324 int radiusExtend = GetCircleToPolyCorrection( actualDeltaRadius );
325 arcStart += incoming.Perpendicular().Resize( -radiusExtend );
326 arcStartOrigin = arcStart - arcCenter;
327
328 // To avoid "ears", we only add segments crossing/within the non-rounded outline
329 // Note: outlineIn is short and must be treated as defining an infinite line
330 SEG outlineIn( cornerPosition - incoming, cornerPosition );
331 VECTOR2I prevPt = arcStart;
332 arcEnd = cornerPosition; // default if no points within the outline are found
333
334 while( angPos < endAngle )
335 {
336 VECTOR2I pt = arcStartOrigin;
337 RotatePoint( pt, -angPos );
338 pt += arcCenter;
339 angPos += angDelta;
340
341 if( outlineIn.Side( pt ) > 0 )
342 {
343 OPT_VECTOR2I intersect = outlineIn.IntersectLines( SEG( prevPt, pt ) );
344
345 wxCHECK_RET( intersect, wxT( "No solutions exist!" ) );
346 outline.Append( *intersect );
347 outline.Append( pt );
348 arcEnd = SEG( cornerPosition, arcCenter ).ReflectPoint( *intersect );
349 break;
350 }
351
352 endAngle -= angDelta; // if skipping first, also skip last
353 prevPt = pt;
354 }
355 }
356
357 for( ; angPos < endAngle; angPos += angDelta )
358 {
359 VECTOR2I pt = arcStartOrigin;
360 RotatePoint( pt, -angPos );
361 outline.Append( pt + arcCenter );
362 }
363
364 outline.Append( arcEnd );
365 }
366
367 incoming = outgoing;
368 }
369}
370
371
372void CornerListRemoveDuplicates( std::vector<ROUNDED_CORNER>& aCorners )
373{
374 VECTOR2I prev = aCorners[0].m_position;
375
376 for( int pos = aCorners.size() - 1; pos >= 0; pos-- )
377 {
378 if( aCorners[pos].m_position == prev )
379 aCorners.erase( aCorners.begin() + pos );
380 else
381 prev = aCorners[pos].m_position;
382 }
383}
384
385
386void TransformTrapezoidToPolygon( SHAPE_POLY_SET& aBuffer, const VECTOR2I& aPosition,
387 const VECTOR2I& aSize, const EDA_ANGLE& aRotation, int aDeltaX,
388 int aDeltaY, int aInflate, int aError, ERROR_LOC aErrorLoc )
389{
390 SHAPE_POLY_SET outline;
391 VECTOR2I size( aSize / 2 );
392 std::vector<ROUNDED_CORNER> corners;
393
394 if( aInflate < 0 )
395 {
396 if( !aDeltaX && !aDeltaY ) // rectangle
397 {
398 size.x = std::max( 1, size.x + aInflate );
399 size.y = std::max( 1, size.y + aInflate );
400 }
401 else if( aDeltaX ) // horizontal trapezoid
402 {
403 double slope = (double) aDeltaX / size.x;
404 int yShrink = KiROUND( ( std::hypot( size.x, aDeltaX ) * aInflate ) / size.x );
405 size.y = std::max( 1, size.y + yShrink );
406 size.x = std::max( 1, size.x + aInflate );
407 aDeltaX = KiROUND( size.x * slope );
408
409 if( aDeltaX > size.y ) // shrinking turned the trapezoid into a triangle
410 {
411 corners.reserve( 3 );
412 corners.emplace_back( -size.x, -size.y - aDeltaX );
413 corners.emplace_back( KiROUND( size.y / slope ), 0 );
414 corners.emplace_back( -size.x, size.y + aDeltaX );
415 }
416 }
417 else // vertical trapezoid
418 {
419 double slope = (double) aDeltaY / size.y;
420 int xShrink = KiROUND( ( std::hypot( size.y, aDeltaY ) * aInflate ) / size.y );
421 size.x = std::max( 1, size.x + xShrink );
422 size.y = std::max( 1, size.y + aInflate );
423 aDeltaY = KiROUND( size.y * slope );
424
425 if( aDeltaY > size.x )
426 {
427 corners.reserve( 3 );
428 corners.emplace_back( 0, -KiROUND( size.x / slope ) );
429 corners.emplace_back( size.x + aDeltaY, size.y );
430 corners.emplace_back( -size.x - aDeltaY, size.y );
431 }
432 }
433
434 aInflate = 0;
435 }
436
437 if( corners.empty() )
438 {
439 corners.reserve( 4 );
440 corners.emplace_back( -size.x + aDeltaY, -size.y - aDeltaX );
441 corners.emplace_back( size.x - aDeltaY, -size.y + aDeltaX );
442 corners.emplace_back( size.x + aDeltaY, size.y - aDeltaX );
443 corners.emplace_back( -size.x - aDeltaY, size.y + aDeltaX );
444
445 if( std::abs( aDeltaY ) == std::abs( size.x ) || std::abs( aDeltaX ) == std::abs( size.y ) )
447 }
448
449 CornerListToPolygon( outline, corners, aInflate, aError, aErrorLoc );
450
451 if( !aRotation.IsZero() )
452 outline.Rotate( aRotation );
453
454 outline.Move( VECTOR2I( aPosition ) );
455 aBuffer.Append( outline );
456}
457
458
460 const VECTOR2I& aSize, const EDA_ANGLE& aRotation,
461 int aCornerRadius, double aChamferRatio,
462 int aChamferCorners, int aInflate, int aError,
463 ERROR_LOC aErrorLoc )
464{
465 SHAPE_POLY_SET outline;
466 VECTOR2I size( aSize / 2 );
467 int chamferCnt = std::bitset<8>( aChamferCorners ).count();
468 double chamferDeduct = 0;
469
470 if( aInflate < 0 )
471 {
472 size.x = std::max( 1, size.x + aInflate );
473 size.y = std::max( 1, size.y + aInflate );
474 chamferDeduct = aInflate * ( 2.0 - M_SQRT2 );
475 aCornerRadius = std::max( 0, aCornerRadius + aInflate );
476 aInflate = 0;
477 }
478
479 std::vector<ROUNDED_CORNER> corners;
480 corners.reserve( 4 + chamferCnt );
481 corners.emplace_back( -size.x, -size.y, aCornerRadius );
482 corners.emplace_back( size.x, -size.y, aCornerRadius );
483 corners.emplace_back( size.x, size.y, aCornerRadius );
484 corners.emplace_back( -size.x, size.y, aCornerRadius );
485
486 if( aChamferCorners )
487 {
488 int shorterSide = std::min( aSize.x, aSize.y );
489 int chamfer = std::max( 0, KiROUND( aChamferRatio * shorterSide + chamferDeduct ) );
492 int sign[8] = { 0, 1, -1, 0, 0, -1, 1, 0 };
493
494 for( int cc = 0, pos = 0; cc < 4; cc++, pos++ )
495 {
496 if( !( aChamferCorners & chamId[cc] ) )
497 continue;
498
499 corners[pos].m_radius = 0;
500
501 if( chamfer == 0 )
502 continue;
503
504 corners.insert( corners.begin() + pos + 1, corners[pos] );
505 corners[pos].m_position.x += sign[( 2 * cc ) & 7] * chamfer;
506 corners[pos].m_position.y += sign[( 2 * cc - 2 ) & 7] * chamfer;
507 corners[pos + 1].m_position.x += sign[( 2 * cc + 1 ) & 7] * chamfer;
508 corners[pos + 1].m_position.y += sign[( 2 * cc - 1 ) & 7] * chamfer;
509 pos++;
510 }
511
512 if( chamferCnt > 1 && 2 * chamfer >= shorterSide )
514 }
515
516 CornerListToPolygon( outline, corners, aInflate, aError, aErrorLoc );
517
518 if( !aRotation.IsZero() )
519 outline.Rotate( aRotation );
520
521 outline.Move( aPosition );
522 aBuffer.Append( outline );
523}
524
525
526int ConvertArcToPolyline( SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aStart, const VECTOR2I& aMid,
527 const VECTOR2I& aEnd, double aAccuracy, ERROR_LOC aErrorLoc,
528 double aRadialOffset )
529{
530 ARC_CHORD_PARAMS params;
531
532 if( !params.Compute( aStart, aMid, aEnd ) )
533 {
534 aPolyline.Append( aStart );
535
536 if( aEnd != aStart )
537 aPolyline.Append( aEnd );
538
539 return 0;
540 }
541
542 double arc_angle = params.GetArcAngle();
543
544 if( arc_angle <= 0.0 )
545 {
546 aPolyline.Append( aStart );
547 aPolyline.Append( aEnd );
548 return 0;
549 }
550
551 constexpr double max_coord = static_cast<double>( std::numeric_limits<int>::max() );
552
553 auto append_point = [&]( double aAlpha, double aEffectiveRadius ) -> bool
554 {
555 const double u_offset = aEffectiveRadius * std::sin( aAlpha );
556 const double n_offset = params.GetCenterOffset() - aEffectiveRadius * std::cos( aAlpha );
557
558 const double x = params.GetMidX() + params.GetUx() * u_offset + params.GetNx() * n_offset;
559 const double y = params.GetMidY() + params.GetUy() * u_offset + params.GetNy() * n_offset;
560
561 if( !std::isfinite( x ) || !std::isfinite( y )
562 || std::abs( x ) > max_coord || std::abs( y ) > max_coord )
563 {
564 wxLogDebug( wxT( "Arc approximation overflow: falling back to straight segment." ) );
565 aPolyline.Clear();
566 aPolyline.Append( aStart );
567 aPolyline.Append( aEnd );
568 return false;
569 }
570
571 aPolyline.Append( KiROUND( x ), KiROUND( y ) );
572 return true;
573 };
574
575 double effective_radius = params.GetRadius() + aRadialOffset;
576
577 EDA_ANGLE arc_angle_eda( arc_angle, RADIANS_T );
578 int n = 2;
579 int radius_for_seg = KiROUND( std::min( std::abs( effective_radius ), max_coord ) );
580
581 if( radius_for_seg >= aAccuracy )
582 n = GetArcToSegmentCount( radius_for_seg, aAccuracy, arc_angle_eda ) + 1;
583
584 const double half_angle = arc_angle / 2.0;
585 const double delta = arc_angle / static_cast<double>( n );
586
587 if( aErrorLoc == ERROR_INSIDE )
588 {
589 for( int i = 0; i <= n; ++i )
590 {
591 if( !append_point( -half_angle + delta * i, effective_radius ) )
592 return 0;
593 }
594 }
595 else
596 {
597 int seg360 = std::abs( KiROUND( n * 360.0 / arc_angle_eda.AsDegrees() ) );
598
599 if( seg360 <= 0 )
600 {
601 for( int i = 0; i <= n; ++i )
602 {
603 if( !append_point( -half_angle + delta * i, effective_radius ) )
604 return 0;
605 }
606
607 return n;
608 }
609
610 int delta_radius = CircleToEndSegmentDeltaRadius( radius_for_seg, seg360 );
611 double error_radius = effective_radius + static_cast<double>( delta_radius );
612
613 if( !append_point( -half_angle, effective_radius ) )
614 return 0;
615
616 for( int i = 0; i < n; ++i )
617 {
618 if( !append_point( -half_angle + delta * ( i + 0.5 ), error_radius ) )
619 return 0;
620 }
621
622 if( !append_point( half_angle, effective_radius ) )
623 return 0;
624 }
625
626 return n;
627}
628
629
630int ConvertArcToPolyline( SHAPE_LINE_CHAIN& aPolyline, VECTOR2I aCenter, int aRadius,
631 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aArcAngle,
632 double aAccuracy, ERROR_LOC aErrorLoc )
633{
634 int n = 2;
635
636 if( aRadius >= aAccuracy )
637 n = GetArcToSegmentCount( aRadius, aAccuracy, aArcAngle ) + 1;
638
639 EDA_ANGLE delta = aArcAngle / n;
640
641 if( aErrorLoc == ERROR_INSIDE )
642 {
643 // This is the easy case: with the error on the inside the endpoints of each segment
644 // are error-free.
645
646 EDA_ANGLE rot = aStartAngle;
647
648 for( int i = 0; i <= n; i++, rot += delta )
649 {
650 double x = aCenter.x + aRadius * rot.Cos();
651 double y = aCenter.y + aRadius * rot.Sin();
652
653 aPolyline.Append( KiROUND( x ), KiROUND( y ) );
654 }
655 }
656 else
657 {
658 // This is the hard case: with the error on the outside it's the segment midpoints
659 // that are error-free. So we need to add a half-segment at each end of the arc to get
660 // them correct.
661
662 int seg360 = std::abs( KiROUND( n * 360.0 / aArcAngle.AsDegrees() ) );
663 int actual_delta_radius = CircleToEndSegmentDeltaRadius( aRadius, seg360 );
664 int errorRadius = aRadius + actual_delta_radius;
665
666 double x = aCenter.x + aRadius * aStartAngle.Cos();
667 double y = aCenter.y + aRadius * aStartAngle.Sin();
668
669 aPolyline.Append( KiROUND( x ), KiROUND( y ) );
670
671 EDA_ANGLE rot = aStartAngle + delta / 2;
672
673 for( int i = 0; i < n; i++, rot += delta )
674 {
675 x = aCenter.x + errorRadius * rot.Cos();
676 y = aCenter.y + errorRadius * rot.Sin();
677
678 aPolyline.Append( KiROUND( x ), KiROUND( y ) );
679 }
680
681 x = aCenter.x + aRadius * ( aStartAngle + aArcAngle ).Cos();
682 y = aCenter.y + aRadius * ( aStartAngle + aArcAngle ).Sin();
683
684 aPolyline.Append( KiROUND( x ), KiROUND( y ) );
685 }
686
687 return n;
688}
689
690
691void TransformArcToPolygon( SHAPE_POLY_SET& aBuffer, const VECTOR2I& aStart, const VECTOR2I& aMid,
692 const VECTOR2I& aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc )
693{
694 SEG startToEnd( aStart, aEnd );
695 int distanceToMid = startToEnd.Distance( aMid );
696
697 if( distanceToMid <= 1 )
698 {
699 // Not an arc but essentially a straight line with a small error
700 TransformOvalToPolygon( aBuffer, aStart, aEnd, aWidth + distanceToMid, aError, aErrorLoc );
701 return;
702 }
703
704 // Determine arc direction using cross product. For consistent polygon winding,
705 // ensure we always process a CCW arc by swapping endpoints if needed.
706 VECTOR2I startToMid = aMid - aStart;
707 VECTOR2I startToEndVec = aEnd - aStart;
708 int64_t cross = (int64_t) startToMid.x * startToEndVec.y
709 - (int64_t) startToMid.y * startToEndVec.x;
710
711 VECTOR2I p0 = aStart;
712 VECTOR2I p1 = aEnd;
713
714 if( cross < 0 )
715 std::swap( p0, p1 );
716
717 ARC_CHORD_PARAMS params;
718
719 if( !params.Compute( p0, aMid, p1 ) )
720 {
721 TransformOvalToPolygon( aBuffer, aStart, aEnd, aWidth, aError, aErrorLoc );
722 return;
723 }
724
725 EDA_ANGLE startAngle = params.GetStartAngle();
726 EDA_ANGLE endAngle = params.GetEndAngle();
727
728 int radial_offset = aWidth / 2;
729 double arc_inner_radius = params.GetRadius() - radial_offset;
730 ERROR_LOC errorLocInner = ( aErrorLoc == ERROR_INSIDE ) ? ERROR_OUTSIDE : ERROR_INSIDE;
731 ERROR_LOC errorLocOuter = ( aErrorLoc == ERROR_INSIDE ) ? ERROR_INSIDE : ERROR_OUTSIDE;
732
733 SHAPE_POLY_SET polyshape;
734 polyshape.NewOutline();
735
736 SHAPE_LINE_CHAIN& outline = polyshape.Outline( 0 );
737
738 // Starting end cap (semicircle at p0)
739 ConvertArcToPolyline( outline, p0, radial_offset, startAngle - ANGLE_180, ANGLE_180, aError,
740 aErrorLoc );
741
742 // Outside edge
743 ConvertArcToPolyline( outline, p0, aMid, p1, aError, errorLocOuter, radial_offset );
744
745 // Ending end cap (semicircle at p1)
746 ConvertArcToPolyline( outline, p1, radial_offset, endAngle, ANGLE_180, aError, aErrorLoc );
747
748 // Inside edge (reversed direction)
749 if( arc_inner_radius > 0 )
750 ConvertArcToPolyline( outline, p1, aMid, p0, aError, errorLocInner, -radial_offset );
751
752 aBuffer.Append( polyshape );
753}
754
755
756void TransformRingToPolygon( SHAPE_POLY_SET& aBuffer, const VECTOR2I& aCentre, int aRadius,
757 int aWidth, int aError, ERROR_LOC aErrorLoc )
758{
759 int inner_radius = aRadius - ( aWidth / 2 );
760 int outer_radius = inner_radius + aWidth;
761
762 if( inner_radius <= 0 )
763 {
764 //In this case, the ring is just a circle (no hole inside)
765 TransformCircleToPolygon( aBuffer, aCentre, aRadius + ( aWidth / 2 ), aError, aErrorLoc );
766 return;
767 }
768
769 SHAPE_POLY_SET buffer;
770
771 TransformCircleToPolygon( buffer, aCentre, outer_radius, aError, aErrorLoc );
772
773 // Build the hole:
774 buffer.NewHole();
775 // The circle is the hole, so the approximation error location is the opposite of aErrorLoc
776 ERROR_LOC inner_err_loc = aErrorLoc == ERROR_OUTSIDE ? ERROR_INSIDE : ERROR_OUTSIDE;
777 TransformCircleToPolygon( buffer.Hole( 0, 0 ), aCentre, inner_radius,
778 aError, inner_err_loc );
779
780 buffer.Fracture();
781 aBuffer.Append( buffer );
782}
ERROR_LOC
When approximating an arc or circle, should the error be placed on the outside or inside of the curve...
@ ERROR_OUTSIDE
@ ERROR_INSIDE
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
Arc geometry computed from a chord-based coordinate system.
double GetMidY() const
double GetRadius() const
Get the arc radius.
EDA_ANGLE GetEndAngle() const
Get the angle from arc center to the end point.
double GetMidX() const
bool Compute(const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd)
Compute arc geometry from three points defining the arc.
double GetNx() const
double GetUy() const
double GetCenterOffset() const
Get the distance from chord midpoint to arc center along the normal.
double GetArcAngle() const
Get the arc angle (total angle swept by the arc) in radians.
EDA_ANGLE GetStartAngle() const
Get the angle from arc center to the start point.
double GetUx() const
double GetNy() const
double Sin() const
Definition eda_angle.h:178
double AsDegrees() const
Definition eda_angle.h:116
bool IsZero() const
Definition eda_angle.h:136
double Cos() const
Definition eda_angle.h:197
Definition seg.h:42
const VECTOR2I ReflectPoint(const VECTOR2I &aP) const
Reflect a point using this segment as axis.
Definition seg.cpp:664
OPT_VECTOR2I IntersectLines(const SEG &aSeg) const
Compute the intersection point of lines passing through ends of (this) and aSeg.
Definition seg.h:220
int Distance(const SEG &aSeg) const
Compute minimum Euclidean distance to segment aSeg.
Definition seg.cpp:702
int Side(const VECTOR2I &aP) const
Determine on which side of directed line passing via segment ends point aP lies.
Definition seg.h:143
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.
void Clear()
Remove all points from the line chain.
void Append(int aX, int aY, bool aAllowDuplication=false)
Append a new point at the end of the line chain.
Represent a set of closed polygons.
void Rotate(const EDA_ANGLE &aAngle, const VECTOR2I &aCenter={ 0, 0 }) override
Rotate all vertices by a given angle.
void Fracture()
Convert a set of polygons with holes to a single outline with "slits"/"fractures" connecting the oute...
int Append(int x, int y, int aOutline=-1, int aHole=-1, bool aAllowDuplication=false)
Appends a vertex at the end of the given outline/hole (default: the last outline)
SHAPE_LINE_CHAIN & Outline(int aIndex)
Return the reference to aIndex-th outline in the set.
SHAPE_LINE_CHAIN & Hole(int aOutline, int aHole)
Return the reference to aHole-th hole in the aIndex-th outline.
int NewOutline()
Creates a new empty polygon in the set and returns its index.
void BooleanIntersection(const SHAPE_POLY_SET &b)
Perform boolean polyset intersection.
int NewHole(int aOutline=-1)
Creates a new hole in a given outline.
void Move(const VECTOR2I &aVector) override
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition vector2d.h:283
constexpr VECTOR2< T > Perpendicular() const
Compute the perpendicular vector.
Definition vector2d.h:314
VECTOR2< T > Resize(T aNewLength) const
Return a vector of the same direction, but length specified in aNewLength.
Definition vector2d.h:385
void CornerListToPolygon(SHAPE_POLY_SET &outline, std::vector< ROUNDED_CORNER > &aCorners, int aInflate, int aError, ERROR_LOC aErrorLoc)
void TransformRingToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aCentre, int aRadius, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arcs to multiple straight segments.
void TransformArcToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc)
Convert arc to multiple straight segments.
void CornerListRemoveDuplicates(std::vector< ROUNDED_CORNER > &aCorners)
void TransformOvalToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aStart, const VECTOR2I &aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc, int aMinSegCount)
Convert a oblong shape to a polygon, using multiple segments.
int ConvertArcToPolyline(SHAPE_LINE_CHAIN &aPolyline, const VECTOR2I &aStart, const VECTOR2I &aMid, const VECTOR2I &aEnd, double aAccuracy, ERROR_LOC aErrorLoc, double aRadialOffset)
Generate a polyline to approximate an arc defined by three points.
void TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aRotation, int aCornerRadius, double aChamferRatio, int aChamferCorners, int aInflate, int aError, ERROR_LOC aErrorLoc)
Convert a rectangle with rounded corners and/or chamfered corners to a polygon.
void TransformCircleToPolygon(SHAPE_LINE_CHAIN &aBuffer, const VECTOR2I &aCenter, int aRadius, int aError, ERROR_LOC aErrorLoc, int aMinSegCount)
Convert a circle to a polygon, using multiple straight lines.
void TransformTrapezoidToPolygon(SHAPE_POLY_SET &aBuffer, const VECTOR2I &aPosition, const VECTOR2I &aSize, const EDA_ANGLE &aRotation, int aDeltaX, int aDeltaY, int aInflate, int aError, ERROR_LOC aErrorLoc)
Convert a rectangle or trapezoid to a polygon.
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
static constexpr EDA_ANGLE ANGLE_90
Definition eda_angle.h:413
@ RADIANS_T
Definition eda_angle.h:32
static constexpr EDA_ANGLE FULL_CIRCLE
Definition eda_angle.h:409
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
static constexpr EDA_ANGLE ANGLE_180
Definition eda_angle.h:415
a few functions useful in geometry calculations.
int GetCircleToPolyCorrection(int aMaxError)
int CircleToEndSegmentDeltaRadius(int aInnerCircleRadius, int aSegCount)
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
static bool intersect(const SEGMENT_WITH_NORMALS &aSeg, const SFVEC2F &aStart, const SFVEC2F &aEnd)
CITER next(CITER it)
Definition ptree.cpp:124
constexpr double correction
std::optional< VECTOR2I > OPT_VECTOR2I
Definition seg.h:39
ROUNDED_CORNER(int x, int y, int radius)
int radius
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:229
constexpr int sign(T val)
Definition util.h:145
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695