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