/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2014-2021 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ /************************************/ /* routines to handle bezier curves */ /************************************/ #include #include #include #include // for VECTOR2D, operator*, VECTOR2 #include // for wxASSERT BEZIER_POLY::BEZIER_POLY( const VECTOR2I& aStart, const VECTOR2I& aCtrl1, const VECTOR2I& aCtrl2, const VECTOR2I& aEnd ) { m_ctrlPts.emplace_back( VECTOR2D( aStart ) ); m_ctrlPts.emplace_back( VECTOR2D( aCtrl1 ) ); m_ctrlPts.emplace_back( VECTOR2D( aCtrl2 ) ); m_ctrlPts.emplace_back( VECTOR2D( aEnd ) ); m_minSegLen = 0.0; } BEZIER_POLY::BEZIER_POLY( const std::vector& aControlPoints ) { for( unsigned ii = 0; ii < aControlPoints.size(); ++ii ) m_ctrlPts.emplace_back( VECTOR2I( aControlPoints[ii] ) ); m_minSegLen = 0.0; } void BEZIER_POLY::GetPoly( std::vector& aOutput, int aMinSegLen, int aMaxSegCount ) { aOutput.clear(); std::vector buffer; GetPoly( buffer, double( aMinSegLen ), aMaxSegCount ); for( unsigned ii = 0; ii < buffer.size(); ++ii ) aOutput.emplace_back( VECTOR2I( int( buffer[ii].x ), int( buffer[ii].y ) ) ); } void BEZIER_POLY::GetPoly( std::vector& aOutput, double aMinSegLen, int aMaxSegCount ) { wxASSERT( m_ctrlPts.size() == 4 ); // FIXME Brute force method, use a better (recursive?) algorithm // with a max error value. // to optimize the number of segments double dt = 1.0 / aMaxSegCount; aOutput.clear(); aOutput.push_back( m_ctrlPts[0] ); // If the Bezier curve is degenerated (straight line), skip intermediate points: bool degenerated = m_ctrlPts[0] == m_ctrlPts[1] && m_ctrlPts[2] == m_ctrlPts[3]; if( !degenerated ) { for( int ii = 1; ii < aMaxSegCount; ii++ ) { double t = dt * ii; double omt = 1.0 - t; double omt2 = omt * omt; double omt3 = omt * omt2; double t2 = t * t; double t3 = t * t2; VECTOR2D vertex = omt3 * m_ctrlPts[0] + 3.0 * t * omt2 * m_ctrlPts[1] + 3.0 * t2 * omt * m_ctrlPts[2] + t3 * m_ctrlPts[3]; // a minimal filter on the length of the segment being created: // The offset from last point: VECTOR2D delta = vertex - aOutput.back(); double dist = delta.EuclideanNorm(); if( dist > aMinSegLen ) aOutput.push_back( vertex ); } } if( aOutput.back() != m_ctrlPts[3] ) aOutput.push_back( m_ctrlPts[3] ); } template void TransformEllipseToBeziers( const ELLIPSE& aEllipse, std::vector>& aBeziers ) { EDA_ANGLE arcAngle = -( aEllipse.EndAngle - aEllipse.StartAngle ); if( arcAngle >= ANGLE_0 ) arcAngle -= ANGLE_360; /* * KiCad does not natively support ellipses or elliptical arcs. So, we convert them to Bezier * splines as these are the nearest thing we have that represents them in a way that is both * editable and preserves their curvature accurately (enough). * * Credit to Kliment for developing and documenting this method. */ /// Minimum number of Beziers to use for a full circle to keep error manageable. const int minBeziersPerCircle = 4; /// The number of Beziers needed for the given arc const int numBeziers = std::ceil( std::abs( arcAngle.AsRadians() / ( 2 * M_PI / minBeziersPerCircle ) ) ); /// Angle occupied by each Bezier const double angleIncrement = arcAngle.AsRadians() / numBeziers; /* * Now, let's assume a circle of radius 1, centered on origin, with angle startangle * x-axis-aligned. We'll move, scale, and rotate it later. We're creating Bezier curves that hug * this circle as closely as possible, with the angles that will be used on the final ellipse * too. * * Thanks to the beautiful and excellent https://pomax.github.io/bezierinfo we know how to * define a curve that hugs a circle as closely as possible. * * We need the value k, which is the optimal distance from the endpoint to the control point to * make the curve match the circle for a given circle arc angle. * * k = 4/3 * tan(θ/4), where θ is the angle of the arc. In our case, θ=angleIncrement */ double theta = angleIncrement; double k = ( 4. / 3. ) * std::tan( theta / 4 ); /* * Define our Bezier: * - Start point is on the circle at the x-axis * - First control point just uses k as the y-value * - Second control point is offset from the end point * - End point is defined by the angle of the arc segment * Note that we use double here no matter what the template param is; round at the end only. */ BEZIER first = { { 1, 0 }, { 1, k }, { std::cos( theta ) + k * std::sin( theta ), std::sin( theta ) - k * std::cos( theta ) }, { std::cos( theta ), std::sin( theta ) } }; /* * Now construct the actual segments by transforming/rotating the first one */ auto transformPoint = [&]( VECTOR2D aPoint, const double aAngle ) -> VECTOR2D { // Bring to the actual starting angle RotatePoint( aPoint, -EDA_ANGLE( aAngle - aEllipse.StartAngle.AsRadians(), RADIANS_T ) ); // Then scale to the major and minor radiuses of the ellipse aPoint *= VECTOR2D( aEllipse.MajorRadius, aEllipse.MinorRadius ); // Now rotate to the ellipse coordinate system RotatePoint( aPoint, -aEllipse.Rotation ); // And finally offset to the center location of the ellipse aPoint += aEllipse.Center; return aPoint; }; for( int i = 0; i < numBeziers; i++ ) { aBeziers.emplace_back( BEZIER( { transformPoint( first.Start, i * angleIncrement ), transformPoint( first.C1, i * angleIncrement ), transformPoint( first.C2, i * angleIncrement ), transformPoint( first.End, i * angleIncrement ) } ) ); } } template void TransformEllipseToBeziers( const ELLIPSE& aEllipse, std::vector>& aBeziers ); template void TransformEllipseToBeziers( const ELLIPSE& aEllipse, std::vector>& aBeziers );