KiCad PCB EDA Suite
Loading...
Searching...
No Matches
bezier_curves.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20/**********************************************************************************************/
21/* routines to handle bezier curves */
22/* Based on "Fast, Precise Flattening of Cubic Bezier segments offset Curves" by Hain, et. al.*/
23/**********************************************************************************************/
24
25// Portions of this code are based draw2d
26// Copyright (c) 2010, Laurent Le Goff
27// All rights reserved.
28
29// Redistribution and use in source and binary forms, with or without modification,
30// are permitted provided that the following conditions are met:
31
32// * Redistributions of source code must retain the above copyright notice,
33// this list of conditions and the following disclaimer.
34// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
35// disclaimer in the documentation and/or other materials provided with the distribution.
36
37// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
38// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
39// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
40// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
41// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
42// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
43// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44
45// Portions of this code are base on BezierKit
46// Copyright (c) 2017 Holmes Futrell
47
48// Permission is hereby granted, free of charge, to any person obtaining a copy
49// of this software and associated documentation files (the "Software"), to deal
50// in the Software without restriction, including without limitation the rights
51// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
52// copies of the Software, and to permit persons to whom the Software is
53// furnished to do so, subject to the following conditions:
54
55// The above copyright notice and this permission notice shall be included in all
56// copies or substantial portions of the Software.
57
58// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
59// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
60// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
61// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
62// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
63// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
64// SOFTWARE.
65
66// Portions of this code are based on the spline-research project
67// Copyright 2018 Raph Levien
68
69// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
70// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
71// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
72// option. This file may not be copied, modified, or distributed
73// except according to those terms.
74
75#include <bezier_curves.h>
76#include <geometry/ellipse.h>
77#include <trigo.h>
78#include <math/vector2d.h> // for VECTOR2D, operator*, VECTOR2
79#include <wx/debug.h> // for wxASSERT
80#include <wx/log.h> // for wxLogTrace
81
82#define BEZIER_DBG "bezier"
83
84BEZIER_POLY::BEZIER_POLY( const VECTOR2I& aStart, const VECTOR2I& aCtrl1,
85 const VECTOR2I& aCtrl2, const VECTOR2I& aEnd )
86{
87 m_ctrlPts.emplace_back( VECTOR2D( aStart ) );
88 m_ctrlPts.emplace_back( VECTOR2D( aCtrl1 ) );
89 m_ctrlPts.emplace_back( VECTOR2D( aCtrl2 ) );
90 m_ctrlPts.emplace_back( VECTOR2D( aEnd ) );
91
92 m_minSegLen = 0.0;
93}
94
95
96BEZIER_POLY::BEZIER_POLY( const std::vector<VECTOR2I>& aControlPoints )
97{
98 m_ctrlPts.reserve( aControlPoints.size() );
99
100 for( const VECTOR2I& pt : aControlPoints )
101 m_ctrlPts.emplace_back( pt );
102
103 m_minSegLen = 0.0;
104}
105
106
108{
109 for( const VECTOR2D& pt : m_ctrlPts )
110 {
111 if( std::isnan( pt.x ) || std::isnan( pt.y ) )
112 return true;
113 }
114
115 return false;
116}
117
118
119bool BEZIER_POLY::isFlat( double aMaxError ) const
120{
121 if( m_ctrlPts.size() == 3 )
122 {
123 VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
124 VECTOR2D D31 = m_ctrlPts[2] - m_ctrlPts[0];
125
126 double t = D21.Dot( D31 ) / D31.SquaredEuclideanNorm();
127 double u = std::min( std::max( t, 0.0 ), 1.0 );
128 VECTOR2D p = m_ctrlPts[0] + u * D31;
129 VECTOR2D delta = m_ctrlPts[1] - p;
130
131 return 0.5 * delta.EuclideanNorm() <= aMaxError;
132 }
133 else if( m_ctrlPts.size() == 4 )
134 {
136
137 VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
138 VECTOR2D D31 = m_ctrlPts[2] - m_ctrlPts[0];
139
140 double cross1 = delta.Cross( D21 );
141 double cross2 = delta.Cross( D31 );
142
143 double inv_delta_sq = 1.0 / delta.SquaredEuclideanNorm();
144 double d1 = ( cross1 * cross1 ) * inv_delta_sq;
145 double d2 = ( cross2 * cross2 ) * inv_delta_sq;
146
147 double factor = ( cross1 * cross2 > 0.0 ) ? 3.0 / 4.0 : 4.0 / 9.0;
148 double f2 = factor * factor;
149 double tol = aMaxError * aMaxError;
150
151 return d1 * f2 <= tol && d2 * f2 <= tol;
152 }
153
154 wxASSERT( false );
155 return false;
156
157}
158
159
160void BEZIER_POLY::GetPoly( std::vector<VECTOR2I>& aOutput, int aMaxError )
161{
162 aOutput.clear();
163 std::vector<VECTOR2D> buffer;
164 GetPoly( buffer, aMaxError );
165
166 aOutput.reserve( buffer.size() );
167
168 for( const VECTOR2D& pt : buffer )
169 aOutput.emplace_back( KiROUND( pt.x ), KiROUND( pt.y ) );
170}
171
172static double approx_int( double x )
173{
174 const double d = 0.6744897501960817;
175 const double d4 = d * d * d * d;
176 return x / ( 1.0 - d + std::pow( d4 + x * x * 0.25, 0.25 ) );
177}
178
179static double approx_inv_int( double x )
180{
181 const double p = 0.39538816;
182 return x * ( 1.0 - p + std::sqrt( p * p + 0.25 * x * x ) );
183}
184
185
187{
188 double omt = 1.0 - t;
189 double omt2 = omt * omt;
190
191 if( m_ctrlPts.size() == 3 )
192 {
193 return omt2 * m_ctrlPts[0] + 2.0 * omt * t * m_ctrlPts[1] + t * t * m_ctrlPts[2];
194 }
195 else if( m_ctrlPts.size() == 4 )
196 {
197 double omt3 = omt * omt2;
198 double t2 = t * t;
199 double t3 = t * t2;
200 return omt3 * m_ctrlPts[0] + 3.0 * t * omt2 * m_ctrlPts[1]
201 + 3.0 * t2 * omt * m_ctrlPts[2] + t3 * m_ctrlPts[3];
202 }
203 else
204 {
205 wxASSERT( false );
206 return VECTOR2D( 0, 0 );
207 }
208}
209
210void BEZIER_POLY::getQuadPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
211{
212 double ddx = 2 * m_ctrlPts[1].x - m_ctrlPts[0].x - m_ctrlPts[2].x;
213 double ddy = 2 * m_ctrlPts[1].y - m_ctrlPts[0].y - m_ctrlPts[2].y;
214 double u0 =
215 ( m_ctrlPts[1].x - m_ctrlPts[0].x ) * ddx + ( m_ctrlPts[1].y - m_ctrlPts[0].y ) * ddy;
216 double u2 =
217 ( m_ctrlPts[2].x - m_ctrlPts[1].x ) * ddx + ( m_ctrlPts[2].y - m_ctrlPts[1].y ) * ddy;
218 double cross =
219 ( m_ctrlPts[2].x - m_ctrlPts[0].x ) * ddy - ( m_ctrlPts[2].y - m_ctrlPts[0].y ) * ddx;
220 double x0 = u0 / cross;
221 double x2 = u2 / cross;
222 double scale = std::abs( cross ) / ( std::hypot( ddx, ddy ) * std::abs( x2 - x0 ) );
223
224 double a0 = approx_int( x0 );
225 double a2 = approx_int( x2 );
226
227 int n = std::ceil( 0.5 * std::abs( a2 - a0 ) * std::sqrt( scale / aMaxError ) );
228
229 double v0 = approx_inv_int( a0 );
230 double v2 = approx_inv_int( a2 );
231
232 aOutput.emplace_back( m_ctrlPts[0] );
233
234 for( int ii = 0; ii < n; ++ii )
235 {
236 double u = approx_inv_int( a0 + ( a2 - a0 ) * ii / n );
237 double t = ( u - v0 ) / ( v2 - v0 );
238 aOutput.emplace_back( eval( t ) );
239 }
240
241 aOutput.emplace_back( m_ctrlPts[2] );
242}
243
244
246{
247 VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
248 VECTOR2D D32 = m_ctrlPts[2] - m_ctrlPts[1];
249 VECTOR2D D43 = m_ctrlPts[3] - m_ctrlPts[2];
250
251 double cross1 = D21.Cross( D32 ) * D32.Cross( D43 );
252 double cross2 = D21.Cross( D32 ) * D21.Cross( D43 );
253
254 if( cross1 < 0.0 )
255 return 1;
256 else if( cross2 > 0.0 )
257 return 0;
258
259 bool b1 = D21.Dot( D32 ) > 0.0;
260 bool b2 = D32.Dot( D43 ) > 0.0;
261
262 if( b1 ^ b2 )
263 return 0;
264
265 wxLogTrace( BEZIER_DBG, "numberOfInflectionPoints: rare case" );
266 // These are rare cases where there are potentially 2 or 0 inflection points.
267 return -1;
268}
269
270
272{
274 double len_sq = delta.SquaredEuclideanNorm();
275
276 if( len_sq < 1e-6 )
277 return 0.0;
278
279 double len = std::sqrt( len_sq );
280 double r = ( m_ctrlPts[1].y - m_ctrlPts[0].y ) / len;
281 double s = ( m_ctrlPts[0].x - m_ctrlPts[1].x ) / len;
282 double u = ( m_ctrlPts[1].x * m_ctrlPts[0].y - m_ctrlPts[0].x * m_ctrlPts[1].y ) / len;
283
284 return std::abs( r * m_ctrlPts[2].x + s * m_ctrlPts[2].y + u );
285}
286
287
288void BEZIER_POLY::subdivide( double aT, BEZIER_POLY& aLeft, BEZIER_POLY& aRight )
289{
290 if( m_ctrlPts.size() == 3 )
291 {
292 aLeft.m_ctrlPts[0] = m_ctrlPts[0];
293 aLeft.m_ctrlPts[1] = m_ctrlPts[0] + aT * ( m_ctrlPts[1] - m_ctrlPts[0] );
294 aLeft.m_ctrlPts[2] = eval( aT );
295
296 aRight.m_ctrlPts[2] = m_ctrlPts[2];
297 aRight.m_ctrlPts[1] = m_ctrlPts[1] + aT * ( m_ctrlPts[2] - m_ctrlPts[1] );
298 aRight.m_ctrlPts[0] = aLeft.m_ctrlPts[2];
299 }
300 else if( m_ctrlPts.size() == 4 )
301 {
302 VECTOR2D left_ctrl1 = m_ctrlPts[0] + aT * ( m_ctrlPts[1] - m_ctrlPts[0] );
303 VECTOR2D tmp = m_ctrlPts[1] + aT * ( m_ctrlPts[2] - m_ctrlPts[1] );
304 VECTOR2D left_ctrl2 = left_ctrl1 + aT * ( tmp - left_ctrl1 );
305 VECTOR2D right_ctrl2 = m_ctrlPts[2] + aT * ( m_ctrlPts[3] - m_ctrlPts[2] );
306 VECTOR2D right_ctrl1 = tmp + aT * ( right_ctrl2 - tmp );
307 VECTOR2D shared = left_ctrl2 + aT * ( right_ctrl1 - left_ctrl2 );
308
309 aLeft.m_ctrlPts[0] = m_ctrlPts[0];
310 aLeft.m_ctrlPts[1] = left_ctrl1;
311 aLeft.m_ctrlPts[2] = left_ctrl2;
312 aLeft.m_ctrlPts[3] = shared;
313
314 aRight.m_ctrlPts[3] = m_ctrlPts[3];
315 aRight.m_ctrlPts[2] = right_ctrl2;
316 aRight.m_ctrlPts[1] = right_ctrl1;
317 aRight.m_ctrlPts[0] = shared;
318 }
319 else
320 {
321 wxASSERT( false );
322 }
323}
324
325
326void BEZIER_POLY::recursiveSegmentation( std::vector<VECTOR2D>& aOutput, double aThreshhold )
327{
328 wxLogTrace( BEZIER_DBG, "recursiveSegmentation with threshold %f", aThreshhold );
329 std::vector<BEZIER_POLY> stack;
330
331 BEZIER_POLY* bezier = nullptr;
332 BEZIER_POLY left( std::vector<VECTOR2D>(4) );
333 BEZIER_POLY right( std::vector<VECTOR2D>(4) );
334
335 stack.push_back( *this );
336
337 while( !stack.empty() )
338 {
339 bezier = &stack.back();
340
341 if( bezier->m_ctrlPts[3] == bezier->m_ctrlPts[0] )
342 {
343 wxLogTrace( BEZIER_DBG, "recursiveSegmentation dropping zero length segment" );
344 stack.pop_back();
345 }
346 else if( bezier->isFlat( aThreshhold ) )
347 {
348 aOutput.push_back( bezier->m_ctrlPts[3] );
349 stack.pop_back();
350 }
351 else
352 {
353 bezier->subdivide( 0.5, left, right );
354 *bezier = right;
355 stack.push_back( left );
356 }
357 }
358
359 wxLogTrace( BEZIER_DBG, "recursiveSegmentation generated %zu points", aOutput.size() );
360}
361
362
363int BEZIER_POLY::findInflectionPoints( double& aT1, double& aT2 )
364{
365 VECTOR2D A{ ( -m_ctrlPts[0].x + 3 * m_ctrlPts[1].x - 3 * m_ctrlPts[2].x + m_ctrlPts[3].x ),
366 ( -m_ctrlPts[0].y + 3 * m_ctrlPts[1].y - 3 * m_ctrlPts[2].y + m_ctrlPts[3].y ) };
367 VECTOR2D B{ ( 3 * m_ctrlPts[0].x - 6 * m_ctrlPts[1].x + 3 * m_ctrlPts[2].x ),
368 ( 3 * m_ctrlPts[0].y - 6 * m_ctrlPts[1].y + 3 * m_ctrlPts[2].y ) };
369 VECTOR2D C{ ( -3 * m_ctrlPts[0].x + 3 * m_ctrlPts[1].x ),
370 ( -3 * m_ctrlPts[0].y + 3 * m_ctrlPts[1].y ) };
371
372 double a = 3 * A.Cross( B );
373 double b = 3 * A.Cross( C );
374 double c = B.Cross( C );
375
376 // Solve the quadratic equation a*t^2 + b*t + c = 0
377 double r2 = ( b * b - 4 * a * c );
378
379 aT1 = 0.0;
380 aT2 = 0.0;
381
382 if( r2 >= 0.0 && a != 0.0 )
383 {
384 double r = std::sqrt( r2 );
385
386 double t1 = ( ( -b + r ) / ( 2 * a ) );
387 double t2 = ( ( -b - r ) / ( 2 * a ) );
388
389 if( ( t1 > 0.0 && t1 < 1.0 ) && ( t2 > 0.0 && t2 < 1.0 ) )
390 {
391 if( t1 > t2 )
392 {
393 std::swap( t1, t2 );
394 }
395
396 aT1 = t1;
397 aT2 = t2;
398
399 if( t2 - t1 > 0.00001 )
400 {
401 wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 2 inflection points at t1 = %f, t2 = %f", t1, t2 );
402 return 2;
403 }
404 else
405 {
406 wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t1 );
407 return 1;
408 }
409 }
410 else if( t1 > 0.0 && t1 < 1.0 )
411 {
412 aT1 = t1;
413 wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t1 );
414 return 1;
415 }
416 else if( t2 > 0.0 && t2 < 1.0 )
417 {
418 aT1 = t2;
419 wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t2 );
420 return 1;
421 }
422
423 wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found no inflection points" );
424 return 0;
425 }
426
427 wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found no inflection points" );
428 return 0;
429}
430
431
432void BEZIER_POLY::cubicParabolicApprox( std::vector<VECTOR2D>& aOutput, double aMaxError )
433{
434 std::vector<BEZIER_POLY> stack;
435 stack.push_back( std::vector<VECTOR2D>(4) );
436 stack.push_back( std::vector<VECTOR2D>(4) );
437 stack.push_back( std::vector<VECTOR2D>(4) );
438 stack.push_back( std::vector<VECTOR2D>(4) );
439
440 BEZIER_POLY* c = this;
441 BEZIER_POLY* b1 = &stack[0];
442 BEZIER_POLY* b2 = &stack[1];
443
444 for( ;; )
445 {
446 if( c->isNaN() )
447 {
448 wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: NaN detected" );
449 break;
450 }
451
452 if( c->isFlat( aMaxError ) )
453 {
454 wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: General Flatness detected, adding %f %f",
455 c->m_ctrlPts[3].x, c->m_ctrlPts[3].y );
456 // If the subsegment deviation satisfies the flatness criterion, store the last point and stop
457 aOutput.push_back( c->m_ctrlPts[3] );
458 break;
459 }
460
461 // Find the third control point deviation and the t values for subdivision
462 double d = c->thirdControlPointDeviation();
463 double t = 2 * std::sqrt( aMaxError / ( 3.0 * d ) ); // Forumla 2 in Hain et al.
464
465 wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: split point t = %f", t );
466
467 if( t > 1.0 )
468 {
469 wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: Invalid t value detected" );
470 // Case where the t value calculated is invalid, so use recursive subdivision
471 c->recursiveSegmentation( aOutput, aMaxError );
472 break;
473 }
474
475 // Valid t value to subdivide at that calculated value
476 c->subdivide( t, *b1, *b2 );
477
478 // First subsegment should have its deviation equal to flatness
479 if( b1->isFlat( aMaxError ) )
480 {
481 wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: Flatness detected, adding %f %f", b1->m_ctrlPts[3].x, b1->m_ctrlPts[3].y );
482 aOutput.push_back( b1->m_ctrlPts[3] );
483 }
484 else
485 {
486 // if not then use segment to handle any mathematical errors
487 b1->recursiveSegmentation( aOutput, aMaxError );
488 }
489
490 // Repeat the process for the left over subsegment
491 c = b2;
492
493 if( b1 == &stack.front() )
494 {
495 b1 = &stack[2];
496 b2 = &stack[3];
497 }
498 else
499 {
500 b1 = &stack[0];
501 b2 = &stack[1];
502 }
503 }
504}
505
506
507void BEZIER_POLY::getCubicPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
508{
509 aOutput.push_back( m_ctrlPts[0] );
510
511 if( numberOfInflectionPoints() == 0 )
512 {
513 wxLogTrace( BEZIER_DBG, "getCubicPoly Short circuit to PA" );
514 // If no inflection points then apply PA on the full Bezier segment.
515 cubicParabolicApprox( aOutput, aMaxError );
516 return;
517 }
518
519 // If one or more inflection points then we will have to subdivide the curve
520 double t1, t2;
521 int numOfIfP = findInflectionPoints( t1, t2 );
522
523 if( numOfIfP == 2 )
524 {
525 wxLogTrace( BEZIER_DBG, "getCubicPoly: 2 inflection points" );
526 // Case when 2 inflection points then divide at the smallest one first
527 BEZIER_POLY sub1( std::vector<VECTOR2D>( 4 ) );
528 BEZIER_POLY tmp1( std::vector<VECTOR2D>( 4 ) );
529 BEZIER_POLY sub2( std::vector<VECTOR2D>( 4 ) );
530 BEZIER_POLY sub3( std::vector<VECTOR2D>( 4 ) );
531
532 subdivide( t1, sub1, tmp1 );
533
534 // Use PA for first subsegment
535 sub1.cubicParabolicApprox( aOutput, aMaxError );
536
537 // Now find the second inflection point in the second curve and subdivide
538 numOfIfP = tmp1.findInflectionPoints( t1, t2 );
539
540 if( numOfIfP == 2 )
541 tmp1.subdivide( t1, sub2, sub3 );
542 else if( numOfIfP == 1 )
543 tmp1.subdivide( t1, sub2, sub3 );
544 else
545 {
546 wxLogTrace( BEZIER_DBG, "getCubicPoly: 2nd inflection point not found" );
547 aOutput.push_back( tmp1.m_ctrlPts[3] );
548 return;
549 }
550
551 // Use Segment for the second (middle) subsegment
552 sub2.recursiveSegmentation( aOutput, aMaxError );
553
554 // Use PA for the third curve
555 sub3.cubicParabolicApprox( aOutput, aMaxError );
556 }
557 else if( numOfIfP == 1 )
558 {
559 wxLogTrace( BEZIER_DBG, "getCubicPoly: 1 inflection point" );
560 // Case where there is one inflection point, subdivide once and use PA on both subsegments
561 BEZIER_POLY sub1( std::vector<VECTOR2D>( 4 ) );
562 BEZIER_POLY sub2( std::vector<VECTOR2D>( 4 ) );
563 subdivide( t1, sub1, sub2 );
564 sub1.cubicParabolicApprox( aOutput, aMaxError );
565 sub2.cubicParabolicApprox( aOutput, aMaxError );
566 }
567 else
568 {
569 wxLogTrace( BEZIER_DBG, "getCubicPoly: Unknown inflection points" );
570 // Case where there is no inflection, use PA directly
571 cubicParabolicApprox( aOutput, aMaxError );
572 }
573}
574
575
576void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
577{
578 if( aMaxError <= 0.0 )
579 aMaxError = 10.0;
580
581 if( m_ctrlPts.size() == 3 )
582 {
583 getQuadPoly( aOutput, aMaxError );
584 }
585 else if( m_ctrlPts.size() == 4 )
586 {
587 getCubicPoly( aOutput, aMaxError );
588 }
589 else
590 {
591 wxASSERT( false );
592 }
593
594 wxLogTrace( BEZIER_DBG, "GetPoly generated %zu points", aOutput.size() );
595}
596
597
598template<typename T>
599void TransformEllipseToBeziers( const ELLIPSE<T>& aEllipse, std::vector<BEZIER<T>>& aBeziers )
600{
601 EDA_ANGLE arcAngle = -( aEllipse.EndAngle - aEllipse.StartAngle );
602
603 if( arcAngle >= ANGLE_0 )
604 arcAngle -= ANGLE_360;
605
606 /*
607 * KiCad does not natively support ellipses or elliptical arcs. So, we convert them to Bezier
608 * splines as these are the nearest thing we have that represents them in a way that is both
609 * editable and preserves their curvature accurately (enough).
610 *
611 * Credit to Kliment for developing and documenting this method.
612 */
614 const int minBeziersPerCircle = 4;
615
617 const int numBeziers = static_cast<int>(
618 std::ceil( std::abs( arcAngle.AsRadians() / ( 2 * M_PI / minBeziersPerCircle ) ) ) );
619
621 const double angleIncrement = arcAngle.AsRadians() / numBeziers;
622
623 /*
624 * Now, let's assume a circle of radius 1, centered on origin, with angle startangle
625 * x-axis-aligned. We'll move, scale, and rotate it later. We're creating Bezier curves that hug
626 * this circle as closely as possible, with the angles that will be used on the final ellipse
627 * too.
628 *
629 * Thanks to the beautiful and excellent https://pomax.github.io/bezierinfo we know how to
630 * define a curve that hugs a circle as closely as possible.
631 *
632 * We need the value k, which is the optimal distance from the endpoint to the control point to
633 * make the curve match the circle for a given circle arc angle.
634 *
635 * k = 4/3 * tan(θ/4), where θ is the angle of the arc. In our case, θ=angleIncrement
636 */
637 double theta = angleIncrement;
638 double k = ( 4. / 3. ) * std::tan( theta / 4 );
639
640 /*
641 * Define our Bezier:
642 * - Start point is on the circle at the x-axis
643 * - First control point just uses k as the y-value
644 * - Second control point is offset from the end point
645 * - End point is defined by the angle of the arc segment
646 * Note that we use double here no matter what the template param is; round at the end only.
647 */
648 BEZIER<double> first = { { 1, 0 },
649 { 1, k },
650 { std::cos( theta ) + k * std::sin( theta ),
651 std::sin( theta ) - k * std::cos( theta ) },
652 { std::cos( theta ), std::sin( theta ) } };
653
654 /*
655 * Now construct the actual segments by transforming/rotating the first one
656 */
657 auto transformPoint =
658 [&]( VECTOR2D aPoint, const double aAngle ) -> VECTOR2D
659 {
660 // Bring to the actual starting angle
661 RotatePoint( aPoint,
662 -EDA_ANGLE( aAngle - aEllipse.StartAngle.AsRadians(), RADIANS_T ) );
663
664 // Then scale to the major and minor radiuses of the ellipse
665 aPoint *= VECTOR2D( aEllipse.MajorRadius, aEllipse.MinorRadius );
666
667 // Now rotate to the ellipse coordinate system
668 RotatePoint( aPoint, -aEllipse.Rotation );
669
670 // And finally offset to the center location of the ellipse
671 aPoint += aEllipse.Center;
672
673 return aPoint;
674 };
675
676 for( int i = 0; i < numBeziers; i++ )
677 {
678 aBeziers.emplace_back( BEZIER<T>( {
679 transformPoint( first.Start, i * angleIncrement ),
680 transformPoint( first.C1, i * angleIncrement ),
681 transformPoint( first.C2, i * angleIncrement ),
682 transformPoint( first.End, i * angleIncrement )
683 } ) );
684 }
685}
686
687
688template void TransformEllipseToBeziers( const ELLIPSE<double>& aEllipse,
689 std::vector<BEZIER<double>>& aBeziers );
690template void TransformEllipseToBeziers( const ELLIPSE<int>& aEllipse,
691 std::vector<BEZIER<int>>& aBeziers );
static double approx_int(double x)
static double approx_inv_int(double x)
#define BEZIER_DBG
void TransformEllipseToBeziers(const ELLIPSE< T > &aEllipse, std::vector< BEZIER< T > > &aBeziers)
Transforms an ellipse or elliptical arc into a set of quadratic Bezier curves that approximate it.
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
void recursiveSegmentation(std::vector< VECTOR2D > &aOutput, double aMaxError)
void getQuadPoly(std::vector< VECTOR2D > &aOutput, double aMaxError)
int numberOfInflectionPoints()
double thirdControlPointDeviation()
void cubicParabolicApprox(std::vector< VECTOR2D > &aOutput, double aMaxError)
double m_minSegLen
Control points.
void getCubicPoly(std::vector< VECTOR2D > &aOutput, double aMaxError)
BEZIER_POLY(const VECTOR2I &aStart, const VECTOR2I &aCtrl1, const VECTOR2I &aCtrl2, const VECTOR2I &aEnd)
void subdivide(double aT, BEZIER_POLY &aLeft, BEZIER_POLY &aRight)
bool isFlat(double aMaxError) const
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMaxError=10)
Convert a Bezier curve to a polygon.
int findInflectionPoints(double &aT1, double &aT2)
bool isNaN() const
VECTOR2D eval(double t)
std::vector< VECTOR2D > m_ctrlPts
Generic cubic Bezier representation.
VECTOR2< NumericType > Start
VECTOR2< NumericType > C1
VECTOR2< NumericType > C2
VECTOR2< NumericType > End
double AsRadians() const
Definition eda_angle.h:120
Plain ellipse / elliptical-arc data.
Definition ellipse.h:32
NumericType MinorRadius
Definition ellipse.h:76
EDA_ANGLE Rotation
Definition ellipse.h:77
EDA_ANGLE StartAngle
Definition ellipse.h:78
NumericType MajorRadius
Definition ellipse.h:75
EDA_ANGLE EndAngle
Definition ellipse.h:79
VECTOR2< NumericType > Center
Definition ellipse.h:74
constexpr extended_type Cross(const VECTOR2< T > &aVector) const
Compute cross product of self with aVector.
Definition vector2d.h:534
constexpr extended_type SquaredEuclideanNorm() const
Compute the squared euclidean norm of the vector, which is defined as (x ** 2 + y ** 2).
Definition vector2d.h:303
constexpr extended_type Dot(const VECTOR2< T > &aVector) const
Compute dot product of self with aVector.
Definition vector2d.h:542
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
@ RADIANS_T
Definition eda_angle.h:32
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:400
const int scale
VECTOR2I v2(1, 0)
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
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682