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
464 // A vanishing deviation yields no valid split parameter, so subdivide recursively rather
465 // than dividing by zero
466 if( d == 0.0 )
467 {
468 c->recursiveSegmentation( aOutput, aMaxError );
469 break;
470 }
471
472 double t = 2 * std::sqrt( aMaxError / ( 3.0 * d ) ); // Forumla 2 in Hain et al.
473
474 wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: split point t = %f", t );
475
476 if( t > 1.0 )
477 {
478 wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: Invalid t value detected" );
479 // Case where the t value calculated is invalid, so use recursive subdivision
480 c->recursiveSegmentation( aOutput, aMaxError );
481 break;
482 }
483
484 // Valid t value to subdivide at that calculated value
485 c->subdivide( t, *b1, *b2 );
486
487 // First subsegment should have its deviation equal to flatness
488 if( b1->isFlat( aMaxError ) )
489 {
490 wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: Flatness detected, adding %f %f", b1->m_ctrlPts[3].x, b1->m_ctrlPts[3].y );
491 aOutput.push_back( b1->m_ctrlPts[3] );
492 }
493 else
494 {
495 // if not then use segment to handle any mathematical errors
496 b1->recursiveSegmentation( aOutput, aMaxError );
497 }
498
499 // Repeat the process for the left over subsegment
500 c = b2;
501
502 if( b1 == &stack.front() )
503 {
504 b1 = &stack[2];
505 b2 = &stack[3];
506 }
507 else
508 {
509 b1 = &stack[0];
510 b2 = &stack[1];
511 }
512 }
513}
514
515
516void BEZIER_POLY::getCubicPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
517{
518 aOutput.push_back( m_ctrlPts[0] );
519
520 if( numberOfInflectionPoints() == 0 )
521 {
522 wxLogTrace( BEZIER_DBG, "getCubicPoly Short circuit to PA" );
523 // If no inflection points then apply PA on the full Bezier segment.
524 cubicParabolicApprox( aOutput, aMaxError );
525 return;
526 }
527
528 // If one or more inflection points then we will have to subdivide the curve
529 double t1, t2;
530 int numOfIfP = findInflectionPoints( t1, t2 );
531
532 if( numOfIfP == 2 )
533 {
534 wxLogTrace( BEZIER_DBG, "getCubicPoly: 2 inflection points" );
535 // Case when 2 inflection points then divide at the smallest one first
536 BEZIER_POLY sub1( std::vector<VECTOR2D>( 4 ) );
537 BEZIER_POLY tmp1( std::vector<VECTOR2D>( 4 ) );
538 BEZIER_POLY sub2( std::vector<VECTOR2D>( 4 ) );
539 BEZIER_POLY sub3( std::vector<VECTOR2D>( 4 ) );
540
541 subdivide( t1, sub1, tmp1 );
542
543 // Use PA for first subsegment
544 sub1.cubicParabolicApprox( aOutput, aMaxError );
545
546 // Now find the second inflection point in the second curve and subdivide
547 numOfIfP = tmp1.findInflectionPoints( t1, t2 );
548
549 if( numOfIfP == 2 )
550 tmp1.subdivide( t1, sub2, sub3 );
551 else if( numOfIfP == 1 )
552 tmp1.subdivide( t1, sub2, sub3 );
553 else
554 {
555 wxLogTrace( BEZIER_DBG, "getCubicPoly: 2nd inflection point not found" );
556 aOutput.push_back( tmp1.m_ctrlPts[3] );
557 return;
558 }
559
560 // Use Segment for the second (middle) subsegment
561 sub2.recursiveSegmentation( aOutput, aMaxError );
562
563 // Use PA for the third curve
564 sub3.cubicParabolicApprox( aOutput, aMaxError );
565 }
566 else if( numOfIfP == 1 )
567 {
568 wxLogTrace( BEZIER_DBG, "getCubicPoly: 1 inflection point" );
569 // Case where there is one inflection point, subdivide once and use PA on both subsegments
570 BEZIER_POLY sub1( std::vector<VECTOR2D>( 4 ) );
571 BEZIER_POLY sub2( std::vector<VECTOR2D>( 4 ) );
572 subdivide( t1, sub1, sub2 );
573 sub1.cubicParabolicApprox( aOutput, aMaxError );
574 sub2.cubicParabolicApprox( aOutput, aMaxError );
575 }
576 else
577 {
578 wxLogTrace( BEZIER_DBG, "getCubicPoly: Unknown inflection points" );
579 // Case where there is no inflection, use PA directly
580 cubicParabolicApprox( aOutput, aMaxError );
581 }
582}
583
584
585void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
586{
587 if( aMaxError <= 0.0 )
588 aMaxError = 10.0;
589
590 if( m_ctrlPts.size() == 3 )
591 {
592 getQuadPoly( aOutput, aMaxError );
593 }
594 else if( m_ctrlPts.size() == 4 )
595 {
596 getCubicPoly( aOutput, aMaxError );
597 }
598 else
599 {
600 wxASSERT( false );
601 }
602
603 wxLogTrace( BEZIER_DBG, "GetPoly generated %zu points", aOutput.size() );
604}
605
606
607template<typename T>
608void TransformEllipseToBeziers( const ELLIPSE<T>& aEllipse, std::vector<BEZIER<T>>& aBeziers )
609{
610 EDA_ANGLE arcAngle = -( aEllipse.EndAngle - aEllipse.StartAngle );
611
612 if( arcAngle >= ANGLE_0 )
613 arcAngle -= ANGLE_360;
614
615 /*
616 * KiCad does not natively support ellipses or elliptical arcs. So, we convert them to Bezier
617 * splines as these are the nearest thing we have that represents them in a way that is both
618 * editable and preserves their curvature accurately (enough).
619 *
620 * Credit to Kliment for developing and documenting this method.
621 */
623 const int minBeziersPerCircle = 4;
624
626 const int numBeziers = static_cast<int>(
627 std::ceil( std::abs( arcAngle.AsRadians() / ( 2 * M_PI / minBeziersPerCircle ) ) ) );
628
630 const double angleIncrement = arcAngle.AsRadians() / numBeziers;
631
632 /*
633 * Now, let's assume a circle of radius 1, centered on origin, with angle startangle
634 * x-axis-aligned. We'll move, scale, and rotate it later. We're creating Bezier curves that hug
635 * this circle as closely as possible, with the angles that will be used on the final ellipse
636 * too.
637 *
638 * Thanks to the beautiful and excellent https://pomax.github.io/bezierinfo we know how to
639 * define a curve that hugs a circle as closely as possible.
640 *
641 * We need the value k, which is the optimal distance from the endpoint to the control point to
642 * make the curve match the circle for a given circle arc angle.
643 *
644 * k = 4/3 * tan(θ/4), where θ is the angle of the arc. In our case, θ=angleIncrement
645 */
646 double theta = angleIncrement;
647 double k = ( 4. / 3. ) * std::tan( theta / 4 );
648
649 /*
650 * Define our Bezier:
651 * - Start point is on the circle at the x-axis
652 * - First control point just uses k as the y-value
653 * - Second control point is offset from the end point
654 * - End point is defined by the angle of the arc segment
655 * Note that we use double here no matter what the template param is; round at the end only.
656 */
657 BEZIER<double> first = { { 1, 0 },
658 { 1, k },
659 { std::cos( theta ) + k * std::sin( theta ),
660 std::sin( theta ) - k * std::cos( theta ) },
661 { std::cos( theta ), std::sin( theta ) } };
662
663 /*
664 * Now construct the actual segments by transforming/rotating the first one
665 */
666 auto transformPoint =
667 [&]( VECTOR2D aPoint, const double aAngle ) -> VECTOR2D
668 {
669 // Bring to the actual starting angle
670 RotatePoint( aPoint,
671 -EDA_ANGLE( aAngle - aEllipse.StartAngle.AsRadians(), RADIANS_T ) );
672
673 // Then scale to the major and minor radiuses of the ellipse
674 aPoint *= VECTOR2D( aEllipse.MajorRadius, aEllipse.MinorRadius );
675
676 // Now rotate to the ellipse coordinate system
677 RotatePoint( aPoint, -aEllipse.Rotation );
678
679 // And finally offset to the center location of the ellipse
680 aPoint += aEllipse.Center;
681
682 return aPoint;
683 };
684
685 for( int i = 0; i < numBeziers; i++ )
686 {
687 aBeziers.emplace_back( BEZIER<T>( {
688 transformPoint( first.Start, i * angleIncrement ),
689 transformPoint( first.C1, i * angleIncrement ),
690 transformPoint( first.C2, i * angleIncrement ),
691 transformPoint( first.End, i * angleIncrement )
692 } ) );
693 }
694}
695
696
697template void TransformEllipseToBeziers( const ELLIPSE<double>& aEllipse,
698 std::vector<BEZIER<double>>& aBeziers );
699template void TransformEllipseToBeziers( const ELLIPSE<int>& aEllipse,
700 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:103
EDA_ANGLE Rotation
Definition ellipse.h:104
EDA_ANGLE StartAngle
Definition ellipse.h:105
NumericType MajorRadius
Definition ellipse.h:102
EDA_ANGLE EndAngle
Definition ellipse.h:106
VECTOR2< NumericType > Center
Definition ellipse.h:101
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