1 /**
2 * @file convert_basic_shapes_to_polygon.cpp
3 */
4 /*
5 * This program source code file is part of KiCad, a free EDA CAD application.
6 *
7 * Copyright (C) 2018 Jean-Pierre Charras, jp.charras at wanadoo.fr
8 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, you may find one here:
22 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
23 * or you may search the http://www.gnu.org website for the version 2 license,
24 * or you may write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
26 */
27
28 #include <algorithm> // for max, min
29 #include <bitset> // for bitset::count
30 #include <math.h> // for atan2
31 #include <type_traits> // for swap
32
33 #include <convert_basic_shapes_to_polygon.h>
34 #include <geometry/geometry_utils.h>
35 #include <geometry/shape_line_chain.h> // for SHAPE_LINE_CHAIN
36 #include <geometry/shape_poly_set.h> // for SHAPE_POLY_SET, SHAPE_POLY_SE...
37 #include <math/util.h>
38 #include <math/vector2d.h> // for VECTOR2I
39 #include <trigo.h>
40
41
TransformCircleToPolygon(SHAPE_LINE_CHAIN & aCornerBuffer,const wxPoint & aCenter,int aRadius,int aError,ERROR_LOC aErrorLoc,int aMinSegCount)42 void TransformCircleToPolygon( SHAPE_LINE_CHAIN& aCornerBuffer, const wxPoint& aCenter, int aRadius,
43 int aError, ERROR_LOC aErrorLoc, int aMinSegCount )
44 {
45 wxPoint corner_position;
46 int numSegs = GetArcToSegmentCount( aRadius, aError, 360.0 );
47 numSegs = std::max( aMinSegCount, numSegs );
48
49 // The shape will be built with a even number of segs. Reason: the horizontal
50 // diameter begins and ends to points on the actual circle, or circle
51 // expanded by aError if aErrorLoc == ERROR_OUTSIDE.
52 // This is used by Arc to Polygon shape convert.
53 if( numSegs & 1 )
54 numSegs++;
55
56 int delta = 3600 / numSegs; // rotate angle in 0.1 degree
57 int radius = aRadius;
58
59 if( aErrorLoc == ERROR_OUTSIDE )
60 {
61 // The outer radius should be radius+aError
62 // Recalculate the actual approx error, as it can be smaller than aError
63 // because numSegs is clamped to a minimal value
64 int actual_delta_radius = CircleToEndSegmentDeltaRadius( radius, numSegs );
65 radius += GetCircleToPolyCorrection( actual_delta_radius );
66 }
67
68 for( int angle = 0; angle < 3600; angle += delta )
69 {
70 corner_position.x = radius;
71 corner_position.y = 0;
72 RotatePoint( &corner_position, angle );
73 corner_position += aCenter;
74 aCornerBuffer.Append( corner_position.x, corner_position.y );
75 }
76
77 aCornerBuffer.SetClosed( true );
78 }
79
80
TransformCircleToPolygon(SHAPE_POLY_SET & aCornerBuffer,const wxPoint & aCenter,int aRadius,int aError,ERROR_LOC aErrorLoc,int aMinSegCount)81 void TransformCircleToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aCenter, int aRadius,
82 int aError, ERROR_LOC aErrorLoc, int aMinSegCount )
83 {
84 wxPoint corner_position;
85 int numSegs = GetArcToSegmentCount( aRadius, aError, 360.0 );
86 numSegs = std::max( aMinSegCount, numSegs);
87
88 // The shape will be built with a even number of segs. Reason: the horizontal
89 // diameter begins and ends to points on the actual circle, or circle
90 // expanded by aError if aErrorLoc == ERROR_OUTSIDE.
91 // This is used by Arc to Polygon shape convert.
92 if( numSegs & 1 )
93 numSegs++;
94
95 int delta = 3600 / numSegs; // rotate angle in 0.1 degree
96 int radius = aRadius;
97
98 if( aErrorLoc == ERROR_OUTSIDE )
99 {
100 // The outer radius should be radius+aError
101 // Recalculate the actual approx error, as it can be smaller than aError
102 // because numSegs is clamped to a minimal value
103 int actual_delta_radius = CircleToEndSegmentDeltaRadius( radius, numSegs );
104 radius += GetCircleToPolyCorrection( actual_delta_radius );
105 }
106
107 aCornerBuffer.NewOutline();
108
109 for( int angle = 0; angle < 3600; angle += delta )
110 {
111 corner_position.x = radius;
112 corner_position.y = 0;
113 RotatePoint( &corner_position, angle );
114 corner_position += aCenter;
115 aCornerBuffer.Append( corner_position.x, corner_position.y );
116 }
117
118 // Finish circle
119 corner_position.x = radius;
120 corner_position.y = 0;
121 corner_position += aCenter;
122 aCornerBuffer.Append( corner_position.x, corner_position.y );
123 }
124
125
TransformOvalToPolygon(SHAPE_POLY_SET & aCornerBuffer,const wxPoint & aStart,const wxPoint & aEnd,int aWidth,int aError,ERROR_LOC aErrorLoc,int aMinSegCount)126 void TransformOvalToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aStart,
127 const wxPoint& aEnd, int aWidth, int aError, ERROR_LOC aErrorLoc,
128 int aMinSegCount )
129 {
130 // To build the polygonal shape outside the actual shape, we use a bigger
131 // radius to build rounded ends.
132 // However, the width of the segment is too big.
133 // so, later, we will clamp the polygonal shape with the bounding box
134 // of the segment.
135 int radius = aWidth / 2;
136 int numSegs = GetArcToSegmentCount( radius, aError, 360.0 );
137 numSegs = std::max( aMinSegCount, numSegs );
138
139 int delta = 3600 / numSegs; // rotate angle in 0.1 degree
140
141 if( aErrorLoc == ERROR_OUTSIDE )
142 {
143 // The outer radius should be radius+aError
144 // Recalculate the actual approx error, as it can be smaller than aError
145 // because numSegs is clamped to a minimal value
146 int actual_delta_radius = CircleToEndSegmentDeltaRadius( radius, numSegs );
147 int correction = GetCircleToPolyCorrection( actual_delta_radius );
148 radius += correction;
149 }
150
151 // end point is the coordinate relative to aStart
152 wxPoint endp = aEnd - aStart;
153 wxPoint startp = aStart;
154 wxPoint corner;
155 SHAPE_POLY_SET polyshape;
156
157 polyshape.NewOutline();
158
159 // normalize the position in order to have endp.x >= 0
160 // it makes calculations more easy to understand
161 if( endp.x < 0 )
162 {
163 endp = aStart - aEnd;
164 startp = aEnd;
165 }
166
167 // delta_angle is in radian
168 double delta_angle = atan2( (double)endp.y, (double)endp.x );
169 int seg_len = KiROUND( EuclideanNorm( endp ) );
170
171 // Compute the outlines of the segment, and creates a polygon
172 // Note: the polygonal shape is built from the equivalent horizontal
173 // segment starting at {0,0}, and ending at {seg_len,0}
174
175 // add right rounded end:
176
177 for( int angle = 0; angle < 1800; angle += delta )
178 {
179 corner = wxPoint( 0, radius );
180 RotatePoint( &corner, angle );
181 corner.x += seg_len;
182 polyshape.Append( corner.x, corner.y );
183 }
184
185 // Finish arc:
186 corner = wxPoint( seg_len, -radius );
187 polyshape.Append( corner.x, corner.y );
188
189 // add left rounded end:
190 for( int angle = 0; angle < 1800; angle += delta )
191 {
192 corner = wxPoint( 0, -radius );
193 RotatePoint( &corner, angle );
194 polyshape.Append( corner.x, corner.y );
195 }
196
197 // Finish arc:
198 corner = wxPoint( 0, radius );
199 polyshape.Append( corner.x, corner.y );
200
201 // Now trim the edges of the polygonal shape which will be slightly outside the
202 // track width.
203 SHAPE_POLY_SET bbox;
204 bbox.NewOutline();
205 // Build the bbox (a horizontal rectangle).
206 int halfwidth = aWidth / 2; // Use the exact segment width for the bbox height
207 corner.x = -radius - 2; // use a bbox width slightly bigger to avoid
208 // creating useless corner at segment ends
209 corner.y = halfwidth;
210 bbox.Append( corner.x, corner.y );
211 corner.y = -halfwidth;
212 bbox.Append( corner.x, corner.y );
213 corner.x = radius + seg_len + 2;
214 bbox.Append( corner.x, corner.y );
215 corner.y = halfwidth;
216 bbox.Append( corner.x, corner.y );
217
218 // Now, clamp the shape
219 polyshape.BooleanIntersection( bbox, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
220 // Note the final polygon is a simple, convex polygon with no hole
221 // due to the shape of initial polygons
222
223 // Rotate and move the polygon to its right location
224 polyshape.Rotate( delta_angle, VECTOR2I( 0, 0 ) );
225 polyshape.Move( startp );
226
227 aCornerBuffer.Append( polyshape);
228 }
229
230
231 struct ROUNDED_CORNER
232 {
233 VECTOR2I m_position;
234 int m_radius;
ROUNDED_CORNERROUNDED_CORNER235 ROUNDED_CORNER( int x, int y ) : m_position( VECTOR2I( x, y ) ), m_radius( 0 ) {}
ROUNDED_CORNERROUNDED_CORNER236 ROUNDED_CORNER( int x, int y, int radius ) : m_position( VECTOR2I( x, y ) ), m_radius( radius ) {}
237 };
238
239
240 // Corner List requirements: no concave shape, corners in clockwise order, no duplicate corners
CornerListToPolygon(SHAPE_POLY_SET & outline,std::vector<ROUNDED_CORNER> & aCorners,int aInflate,int aError,ERROR_LOC aErrorLoc)241 void CornerListToPolygon( SHAPE_POLY_SET& outline, std::vector<ROUNDED_CORNER>& aCorners,
242 int aInflate, int aError, ERROR_LOC aErrorLoc )
243 {
244 assert( aInflate >= 0 );
245 outline.NewOutline();
246 VECTOR2I incoming = aCorners[0].m_position - aCorners.back().m_position;
247
248 for( int n = 0, count = aCorners.size(); n < count; n++ )
249 {
250 ROUNDED_CORNER& cur = aCorners[n];
251 ROUNDED_CORNER& next = aCorners[( n + 1 ) % count];
252 VECTOR2I outgoing = next.m_position - cur.m_position;
253
254 if( !( aInflate || cur.m_radius ) )
255 outline.Append( cur.m_position );
256 else
257 {
258 VECTOR2I cornerPosition = cur.m_position;
259 int endAngle, radius = cur.m_radius;
260 double tanAngle2;
261
262 if( ( incoming.x == 0 && outgoing.y == 0 ) || ( incoming.y == 0 && outgoing.x == 0 ) )
263 {
264 endAngle = 900;
265 tanAngle2 = 1.0;
266 }
267 else
268 {
269 double cosNum = (double) incoming.x * outgoing.x + (double) incoming.y * outgoing.y;
270 double cosDen = (double) incoming.EuclideanNorm() * outgoing.EuclideanNorm();
271 double angle = acos( cosNum / cosDen );
272 tanAngle2 = tan( ( M_PI - angle ) / 2 );
273 endAngle = RAD2DECIDEG( angle );
274 }
275
276 if( aInflate )
277 {
278 radius += aInflate;
279 cornerPosition += incoming.Resize( aInflate / tanAngle2 )
280 + incoming.Perpendicular().Resize( -aInflate );
281 }
282
283 // Ensure 16+ segments per 360deg and ensure first & last segment are the same size
284 int numSegs = std::max( 16, GetArcToSegmentCount( radius, aError, 360.0 ) );
285 int angDelta = 3600 / numSegs;
286 int lastSegLen = endAngle % angDelta; // or 0 if last seg length is angDelta
287 int angPos = lastSegLen ? ( angDelta + lastSegLen ) / 2 : angDelta;
288
289 double arcTransitionDistance = radius / tanAngle2;
290 VECTOR2I arcStart = cornerPosition - incoming.Resize( arcTransitionDistance );
291 VECTOR2I arcCenter = arcStart + incoming.Perpendicular().Resize( radius );
292 VECTOR2I arcEnd, arcStartOrigin;
293
294 if( aErrorLoc == ERROR_INSIDE )
295 {
296 arcEnd = SEG( cornerPosition, arcCenter ).ReflectPoint( arcStart );
297 arcStartOrigin = arcStart - arcCenter;
298 outline.Append( arcStart );
299 }
300 else
301 {
302 // The outer radius should be radius+aError, recalculate because numSegs is clamped
303 int actualDeltaRadius = CircleToEndSegmentDeltaRadius( radius, numSegs );
304 int radiusExtend = GetCircleToPolyCorrection( actualDeltaRadius );
305 arcStart += incoming.Perpendicular().Resize( -radiusExtend );
306 arcStartOrigin = arcStart - arcCenter;
307
308 // To avoid "ears", we only add segments crossing/within the non-rounded outline
309 // Note: outlineIn is short and must be treated as defining an infinite line
310 SEG outlineIn( cornerPosition - incoming, cornerPosition );
311 VECTOR2I prevPt = arcStart;
312 arcEnd = cornerPosition; // default if no points within the outline are found
313
314 while( angPos < endAngle )
315 {
316 VECTOR2I pt = arcStartOrigin;
317 RotatePoint( pt, -angPos );
318 pt += arcCenter;
319 angPos += angDelta;
320
321 if( outlineIn.Side( pt ) > 0 )
322 {
323 VECTOR2I intersect = outlineIn.IntersectLines( SEG( prevPt, pt ) ).get();
324 outline.Append( intersect );
325 outline.Append( pt );
326 arcEnd = SEG( cornerPosition, arcCenter ).ReflectPoint( intersect );
327 break;
328 }
329
330 endAngle -= angDelta; // if skipping first, also skip last
331 prevPt = pt;
332 }
333 }
334
335 for( ; angPos < endAngle; angPos += angDelta )
336 {
337 VECTOR2I pt = arcStartOrigin;
338 RotatePoint( pt, -angPos );
339 outline.Append( pt + arcCenter );
340 }
341
342 outline.Append( arcEnd );
343 }
344
345 incoming = outgoing;
346 }
347 }
348
349
CornerListRemoveDuplicates(std::vector<ROUNDED_CORNER> & aCorners)350 void CornerListRemoveDuplicates( std::vector<ROUNDED_CORNER>& aCorners )
351 {
352 VECTOR2I prev = aCorners[0].m_position;
353
354 for( int pos = aCorners.size() - 1; pos >= 0; pos-- )
355 {
356 if( aCorners[pos].m_position == prev )
357 aCorners.erase( aCorners.begin() + pos );
358 else
359 prev = aCorners[pos].m_position;
360 }
361 }
362
363
TransformTrapezoidToPolygon(SHAPE_POLY_SET & aCornerBuffer,const wxPoint & aPosition,const wxSize & aSize,double aRotation,int aDeltaX,int aDeltaY,int aInflate,int aError,ERROR_LOC aErrorLoc)364 void TransformTrapezoidToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aPosition,
365 const wxSize& aSize, double aRotation, int aDeltaX, int aDeltaY,
366 int aInflate, int aError, ERROR_LOC aErrorLoc )
367 {
368 SHAPE_POLY_SET outline;
369 wxSize size( aSize / 2 );
370 std::vector<ROUNDED_CORNER> corners;
371
372 if( aInflate < 0 )
373 {
374 if( !aDeltaX && !aDeltaY ) // rectangle
375 {
376 size.x = std::max( 1, size.x + aInflate );
377 size.y = std::max( 1, size.y + aInflate );
378 }
379 else if( aDeltaX ) // horizontal trapezoid
380 {
381 double slope = (double) aDeltaX / size.x;
382 int yShrink = KiROUND( ( std::hypot( size.x, aDeltaX ) * aInflate ) / size.x );
383 size.y = std::max( 1, size.y + yShrink );
384 size.x = std::max( 1, size.x + aInflate );
385 aDeltaX = KiROUND( size.x * slope );
386
387 if( aDeltaX > size.y ) // shrinking turned the trapezoid into a triangle
388 {
389 corners.reserve( 3 );
390 corners.push_back( ROUNDED_CORNER( -size.x, -size.y - aDeltaX ) );
391 corners.push_back( ROUNDED_CORNER( KiROUND( size.y / slope ), 0 ) );
392 corners.push_back( ROUNDED_CORNER( -size.x, size.y + aDeltaX ) );
393 }
394 }
395 else // vertical trapezoid
396 {
397 double slope = (double) aDeltaY / size.y;
398 int xShrink = KiROUND( ( std::hypot( size.y, aDeltaY ) * aInflate ) / size.y );
399 size.x = std::max( 1, size.x + xShrink );
400 size.y = std::max( 1, size.y + aInflate );
401 aDeltaY = KiROUND( size.y * slope );
402
403 if( aDeltaY > size.x )
404 {
405 corners.reserve( 3 );
406 corners.push_back( ROUNDED_CORNER( 0, -KiROUND( size.x / slope ) ) );
407 corners.push_back( ROUNDED_CORNER( size.x + aDeltaY, size.y ) );
408 corners.push_back( ROUNDED_CORNER( -size.x - aDeltaY, size.y ) );
409 }
410 }
411
412 aInflate = 0;
413 }
414
415 if( corners.empty() )
416 {
417 corners.reserve( 4 );
418 corners.push_back( ROUNDED_CORNER( -size.x + aDeltaY, -size.y - aDeltaX ) );
419 corners.push_back( ROUNDED_CORNER( size.x - aDeltaY, -size.y + aDeltaX ) );
420 corners.push_back( ROUNDED_CORNER( size.x + aDeltaY, size.y - aDeltaX ) );
421 corners.push_back( ROUNDED_CORNER( -size.x - aDeltaY, size.y + aDeltaX ) );
422
423 if( aDeltaY == size.x || aDeltaX == size.y )
424 CornerListRemoveDuplicates( corners );
425 }
426
427 CornerListToPolygon( outline, corners, aInflate, aError, aErrorLoc );
428
429 if( aRotation != 0.0 )
430 outline.Rotate( DECIDEG2RAD( -aRotation ), VECTOR2I( 0, 0 ) );
431
432 outline.Move( VECTOR2I( aPosition ) );
433 aCornerBuffer.Append( outline );
434 }
435
436
TransformRoundChamferedRectToPolygon(SHAPE_POLY_SET & aCornerBuffer,const wxPoint & aPosition,const wxSize & aSize,double aRotation,int aCornerRadius,double aChamferRatio,int aChamferCorners,int aInflate,int aError,ERROR_LOC aErrorLoc)437 void TransformRoundChamferedRectToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aPosition,
438 const wxSize& aSize, double aRotation, int aCornerRadius,
439 double aChamferRatio, int aChamferCorners, int aInflate,
440 int aError, ERROR_LOC aErrorLoc )
441 {
442 SHAPE_POLY_SET outline;
443 wxSize size( aSize / 2 );
444 int chamferCnt = std::bitset<8>( aChamferCorners ).count();
445 double chamferDeduct = 0;
446
447 if( aInflate < 0 )
448 {
449 size.x = std::max( 1, size.x + aInflate );
450 size.y = std::max( 1, size.y + aInflate );
451 chamferDeduct = aInflate * ( 2.0 - M_SQRT2 );
452 aCornerRadius = std::max( 0, aCornerRadius + aInflate );
453 aInflate = 0;
454 }
455
456 std::vector<ROUNDED_CORNER> corners;
457 corners.reserve( 4 + chamferCnt );
458 corners.push_back( ROUNDED_CORNER( -size.x, -size.y, aCornerRadius ) );
459 corners.push_back( ROUNDED_CORNER( size.x, -size.y, aCornerRadius ) );
460 corners.push_back( ROUNDED_CORNER( size.x, size.y, aCornerRadius ) );
461 corners.push_back( ROUNDED_CORNER( -size.x, size.y, aCornerRadius ) );
462
463 if( aChamferCorners )
464 {
465 int shorterSide = std::min( aSize.x, aSize.y );
466 int chamfer = std::max( 0, KiROUND( aChamferRatio * shorterSide + chamferDeduct ) );
467 int chamId[4] = { RECT_CHAMFER_TOP_LEFT, RECT_CHAMFER_TOP_RIGHT,
468 RECT_CHAMFER_BOTTOM_RIGHT, RECT_CHAMFER_BOTTOM_LEFT };
469 int sign[8] = { 0, 1, -1, 0, 0, -1, 1, 0 };
470
471 for( int cc = 0, pos = 0; cc < 4; cc++, pos++ )
472 {
473 if( !( aChamferCorners & chamId[cc] ) )
474 continue;
475
476 corners[pos].m_radius = 0;
477
478 if( chamfer == 0 )
479 continue;
480
481 corners.insert( corners.begin() + pos + 1, corners[pos] );
482 corners[pos].m_position.x += sign[( 2 * cc ) & 7] * chamfer;
483 corners[pos].m_position.y += sign[( 2 * cc - 2 ) & 7] * chamfer;
484 corners[pos + 1].m_position.x += sign[( 2 * cc + 1 ) & 7] * chamfer;
485 corners[pos + 1].m_position.y += sign[( 2 * cc - 1 ) & 7] * chamfer;
486 pos++;
487 }
488
489 if( chamferCnt > 1 && 2 * chamfer >= shorterSide )
490 CornerListRemoveDuplicates( corners );
491 }
492
493 CornerListToPolygon( outline, corners, aInflate, aError, aErrorLoc );
494
495 if( aRotation != 0.0 )
496 outline.Rotate( DECIDEG2RAD( -aRotation ), VECTOR2I( 0, 0 ) );
497
498 outline.Move( VECTOR2I( aPosition ) );
499 aCornerBuffer.Append( outline );
500 }
501
502
ConvertArcToPolyline(SHAPE_LINE_CHAIN & aPolyline,VECTOR2I aCenter,int aRadius,double aStartAngleDeg,double aArcAngleDeg,double aAccuracy,ERROR_LOC aErrorLoc)503 int ConvertArcToPolyline( SHAPE_LINE_CHAIN& aPolyline, VECTOR2I aCenter, int aRadius,
504 double aStartAngleDeg, double aArcAngleDeg, double aAccuracy,
505 ERROR_LOC aErrorLoc )
506 {
507 double endAngle = aStartAngleDeg + aArcAngleDeg;
508 int n = 2;
509
510 if( aRadius >= aAccuracy )
511 n = GetArcToSegmentCount( aRadius, aAccuracy, aArcAngleDeg )+1; // n >= 3
512
513 if( aErrorLoc == ERROR_OUTSIDE )
514 {
515 int seg360 = std::abs( KiROUND( n * 360.0 / aArcAngleDeg ) );
516 int actual_delta_radius = CircleToEndSegmentDeltaRadius( aRadius, seg360 );
517 aRadius += actual_delta_radius;
518 }
519
520 for( int i = 0; i <= n ; i++ )
521 {
522 double rot = aStartAngleDeg;
523 rot += ( aArcAngleDeg * i ) / n;
524
525 double x = aCenter.x + aRadius * cos( rot * M_PI / 180.0 );
526 double y = aCenter.y + aRadius * sin( rot * M_PI / 180.0 );
527
528 aPolyline.Append( KiROUND( x ), KiROUND( y ) );
529 }
530
531 return n;
532 }
533
534
TransformArcToPolygon(SHAPE_POLY_SET & aCornerBuffer,const wxPoint & aStart,const wxPoint & aMid,const wxPoint & aEnd,int aWidth,int aError,ERROR_LOC aErrorLoc)535 void TransformArcToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aStart,
536 const wxPoint& aMid, const wxPoint& aEnd, int aWidth,
537 int aError, ERROR_LOC aErrorLoc )
538 {
539 SHAPE_ARC arc( aStart, aMid, aEnd, aWidth );
540 // Currentlye have currently 2 algos:
541 // the first approximates the thick arc from its outlines
542 // the second approximates the thick arc from segments given by SHAPE_ARC
543 // using SHAPE_ARC::ConvertToPolyline
544 // The actual approximation errors are similar but not exactly the same.
545 //
546 // For now, both algorithms are kept, the second is the initial algo used in Kicad.
547
548 #if 1
549 // This appproximation convert the 2 ends to polygons, arc outer to polyline
550 // and arc inner to polyline and merge shapes.
551 int radial_offset = ( aWidth + 1 ) / 2;
552
553 SHAPE_POLY_SET polyshape;
554 std::vector<VECTOR2I> outside_pts;
555
556 /// We start by making rounded ends on the arc
557 TransformCircleToPolygon( polyshape, aStart, radial_offset, aError, aErrorLoc );
558 TransformCircleToPolygon( polyshape, aEnd, radial_offset, aError, aErrorLoc );
559
560 // The circle polygon is built with a even number of segments, so the
561 // horizontal diameter has 2 corners on the biggest diameter
562 // Rotate these 2 corners to match the start and ens points of inner and outer
563 // end points of the arc appoximation outlines, build below.
564 // The final shape is much better.
565 double arc_angle_start_deg = arc.GetStartAngle();
566 double arc_angle = arc.GetCentralAngle();
567 double arc_angle_end_deg = arc_angle_start_deg + arc_angle;
568
569 if( arc_angle_start_deg != 0 && arc_angle_start_deg != 180.0 )
570 polyshape.Outline(0).Rotate( arc_angle_start_deg * M_PI/180.0, aStart );
571
572 if( arc_angle_end_deg != 0 && arc_angle_end_deg != 180.0 )
573 polyshape.Outline(1).Rotate( arc_angle_end_deg * M_PI/180.0, aEnd );
574
575 VECTOR2I center = arc.GetCenter();
576 int radius = arc.GetRadius();
577
578 int arc_outer_radius = radius + radial_offset;
579 int arc_inner_radius = radius - radial_offset;
580 ERROR_LOC errorLocInner = ERROR_OUTSIDE;
581 ERROR_LOC errorLocOuter = ERROR_INSIDE;
582
583 if( aErrorLoc == ERROR_OUTSIDE )
584 {
585 errorLocInner = ERROR_INSIDE;
586 errorLocOuter = ERROR_OUTSIDE;
587 }
588
589 polyshape.NewOutline();
590
591 ConvertArcToPolyline( polyshape.Outline(2), center, arc_outer_radius,
592 arc_angle_start_deg, arc_angle, aError, errorLocOuter );
593
594 if( arc_inner_radius > 0 )
595 ConvertArcToPolyline( polyshape.Outline(2), center, arc_inner_radius,
596 arc_angle_end_deg, -arc_angle, aError, errorLocInner );
597 else
598 polyshape.Append( center );
599 #else
600 // This appproximation use SHAPE_ARC to convert the 2 ends to polygons,
601 // approximate arc to polyline, convert the polyline corners to outer and inner
602 // corners of outer and inner utliners and merge shapes.
603 double defaultErr;
604 SHAPE_LINE_CHAIN arcSpine = arc.ConvertToPolyline( SHAPE_ARC::DefaultAccuracyForPCB(),
605 &defaultErr);
606 int radius = arc.GetRadius();
607 int radial_offset = ( aWidth + 1 ) / 2;
608 SHAPE_POLY_SET polyshape;
609 std::vector<VECTOR2I> outside_pts;
610
611 // delta is the effective error approximation to build a polyline from an arc
612 int segCnt360 = arcSpine.GetSegmentCount()*360.0/arc.GetCentralAngle();;
613 int delta = CircleToEndSegmentDeltaRadius( radius+radial_offset, std::abs(segCnt360) );
614
615 /// We start by making rounded ends on the arc
616 TransformCircleToPolygon( polyshape, aStart, radial_offset, aError, aErrorLoc );
617 TransformCircleToPolygon( polyshape, aEnd, radial_offset, aError, aErrorLoc );
618
619 // The circle polygon is built with a even number of segments, so the
620 // horizontal diameter has 2 corners on the biggest diameter
621 // Rotate these 2 corners to match the start and ens points of inner and outer
622 // end points of the arc appoximation outlines, build below.
623 // The final shape is much better.
624 double arc_angle_end_deg = arc.GetStartAngle();
625
626 if( arc_angle_end_deg != 0 && arc_angle_end_deg != 180.0 )
627 polyshape.Outline(0).Rotate( arc_angle_end_deg * M_PI/180.0, arcSpine.GetPoint( 0 ) );
628
629 arc_angle_end_deg = arc.GetEndAngle();
630
631 if( arc_angle_end_deg != 0 && arc_angle_end_deg != 180.0 )
632 polyshape.Outline(1).Rotate( arc_angle_end_deg * M_PI/180.0, arcSpine.GetPoint( -1 ) );
633
634 if( aErrorLoc == ERROR_OUTSIDE )
635 radial_offset += delta + defaultErr/2;
636 else
637 radial_offset -= defaultErr/2;
638
639 if( radial_offset < 0 )
640 radial_offset = 0;
641
642 polyshape.NewOutline();
643
644 VECTOR2I center = arc.GetCenter();
645 int last_index = arcSpine.GetPointCount() -1;
646
647 for( std::size_t ii = 0; ii <= last_index; ++ii )
648 {
649 VECTOR2I offset = arcSpine.GetPoint( ii ) - center;
650 int curr_rd = radius;
651
652 polyshape.Append( offset.Resize( curr_rd - radial_offset ) + center );
653 outside_pts.emplace_back( offset.Resize( curr_rd + radial_offset ) + center );
654 }
655
656 for( auto it = outside_pts.rbegin(); it != outside_pts.rend(); ++it )
657 polyshape.Append( *it );
658 #endif
659
660 // Can be removed, but usefull to display the outline:
661 polyshape.Simplify( SHAPE_POLY_SET::PM_FAST );
662
663 aCornerBuffer.Append( polyshape );
664
665 }
666
667
TransformRingToPolygon(SHAPE_POLY_SET & aCornerBuffer,const wxPoint & aCentre,int aRadius,int aWidth,int aError,ERROR_LOC aErrorLoc)668 void TransformRingToPolygon( SHAPE_POLY_SET& aCornerBuffer, const wxPoint& aCentre, int aRadius,
669 int aWidth, int aError, ERROR_LOC aErrorLoc )
670 {
671 int inner_radius = aRadius - ( aWidth / 2 );
672 int outer_radius = inner_radius + aWidth;
673
674 if( inner_radius <= 0 )
675 { //In this case, the ring is just a circle (no hole inside)
676 TransformCircleToPolygon( aCornerBuffer, aCentre, aRadius + ( aWidth / 2 ), aError,
677 aErrorLoc );
678 return;
679 }
680
681 SHAPE_POLY_SET buffer;
682
683 TransformCircleToPolygon( buffer, aCentre, outer_radius, aError, aErrorLoc );
684
685 // Build the hole:
686 buffer.NewHole();
687 // The circle is the hole, so the approximation error location is the opposite of aErrorLoc
688 ERROR_LOC inner_err_loc = aErrorLoc == ERROR_OUTSIDE ? ERROR_INSIDE : ERROR_OUTSIDE;
689 TransformCircleToPolygon( buffer.Hole( 0, 0 ), aCentre, inner_radius,
690 aError, inner_err_loc );
691
692 buffer.Fracture( SHAPE_POLY_SET::PM_FAST );
693 aCornerBuffer.Append( buffer );
694 }
695