diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b4d4d2af2..8d59283fc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -988,7 +988,10 @@ add_subdirectory( plugins ) # 3D plugins must be built before kicad add_subdirectory( kicad ) # should follow pcbnew, eeschema add_subdirectory( tools ) add_subdirectory( utils ) -add_subdirectory( qa ) + +if( KICAD_BUILD_QA_TESTS ) + add_subdirectory( qa ) +endif() # Resources if ( KICAD_INSTALL_DEMOS ) diff --git a/common/bezier_curves.cpp b/common/bezier_curves.cpp index 8d7f588478..381495511d 100644 --- a/common/bezier_curves.cpp +++ b/common/bezier_curves.cpp @@ -1,8 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2014 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 2014-2017 KiCad Developers, see CHANGELOG.TXT for contributors. + * Copyright (C) 2014-2019 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 @@ -26,362 +25,69 @@ /* routines to handle bezier curves */ /************************************/ -#include #include - -static inline double calc_sq_distance( int x1, int y1, int x2, int y2 ) +BEZIER_POLY::BEZIER_POLY( const std::vector& aControlPoints ) { - int dx = x2 - x1; - int dy = y2 - y1; + for( unsigned ii = 0; ii < aControlPoints.size(); ++ii ) + m_ctrlPts.emplace_back( VECTOR2D( aControlPoints[ii] ) ); - return (double)dx * dx + (double)dy * dy; -} - - -static inline double sqrt_len( int dx, int dy ) -{ - return ((double)dx * dx) + ((double)dy * dy); + m_minSegLen = 0.0; } void BEZIER_POLY::GetPoly( std::vector& aOutput, int aMinSegLen ) { - wxCHECK( !m_ctrlPts.empty(), /* void */ ); - m_minSegLen = std::max( 1, aMinSegLen ); - m_output = &aOutput; - m_output->clear(); - m_output->push_back( wxPoint( m_ctrlPts.front() ) ); + aOutput.clear(); + std::vector buffer; + GetPoly( buffer, double( aMinSegLen ) ); - // Only quadratic and cubic Bezier curves are handled - if( m_ctrlPts.size() == 3 ) - recursiveBezier( m_ctrlPts[0].x, m_ctrlPts[0].y, - m_ctrlPts[1].x, m_ctrlPts[1].y, - m_ctrlPts[2].x, m_ctrlPts[2].y, 0 ); - - else if( m_ctrlPts.size() == 4 ) - recursiveBezier( m_ctrlPts[0].x, m_ctrlPts[0].y, - m_ctrlPts[1].x, m_ctrlPts[1].y, - m_ctrlPts[2].x, m_ctrlPts[2].y, - m_ctrlPts[3].x, m_ctrlPts[3].y, 0 ); - - m_output->push_back( wxPoint( m_ctrlPts.back() ) ); + for( unsigned ii = 0; ii < buffer.size(); ++ii ) + aOutput.emplace_back( wxPoint( int( buffer[ii].x ), int( buffer[ii].y ) ) ); } -void BEZIER_POLY::recursiveBezier( int x1, int y1, int x2, int y2, - int x3, int y3, unsigned int level ) +void BEZIER_POLY::GetPoly( std::vector& aOutput, double aMinSegLen ) { - if( level > recursion_limit ) - return; + 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 + #define CURVE_POINTS 32 + double dt = 1.0 / CURVE_POINTS; - // Calculate all the mid-points of the line segments - //---------------------- - int x12 = (x1 + x2) / 2; - int y12 = (y1 + y2) / 2; - int x23 = (x2 + x3) / 2; - int y23 = (y2 + y3) / 2; - int x123 = (x12 + x23) / 2; - int y123 = (y12 + y23) / 2; + aOutput.clear(); + aOutput.push_back( m_ctrlPts[0] ); - int dx = x3 - x1; - int dy = y3 - y1; - double d = fabs( ((double) (x2 - x3) * dy) - ((double) (y2 - y3) * dx ) ); - double da; + // If the Bezier curve is degenerated (straight line), skip that: + bool degenerated = m_ctrlPts[0] == m_ctrlPts[1] && m_ctrlPts[2] == m_ctrlPts[3]; - if( d > curve_collinearity_epsilon ) + if( !degenerated ) { - // Regular case - //----------------- - if( d * d <= distance_tolerance_square * (dx * dx + dy * dy) ) + for( int ii = 1; ii < CURVE_POINTS; ii++ ) { - // If the curvature doesn't exceed the distance_tolerance value - // we tend to finish subdivisions. - //---------------------- - if( angle_tolerance < curve_angle_tolerance_epsilon ) - { - addSegment( wxPoint( x123, y123 ) ); - return; - } + 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; - // Angle & Cusp Condition - //---------------------- - da = fabs( atan2( (double) ( y3 - y2 ), (double) ( x3 - x2 ) ) - - atan2( (double) ( y2 - y1 ), (double) ( x2 - x1 ) ) ); - if( da >=M_PI ) - da = 2 * M_PI - da; + VECTOR2D vertex = omt3 * m_ctrlPts[0] + + 3.0 * t * omt2 * m_ctrlPts[1] + + 3.0 * t2 * omt * m_ctrlPts[2] + + t3 * m_ctrlPts[3]; - if( da < angle_tolerance ) - { - // Finally we can stop the recursion - //---------------------- - addSegment( wxPoint( x123, y123 ) ); - return; - } - } - } - else - { - // Collinear case - //------------------ - da = sqrt_len(dx, dy); - if( da == 0 ) - { - d = calc_sq_distance( x1, y1, x2, y2 ); - } - else - { - d = ( (double)(x2 - x1) * dx + (double)(y2 - y1) * dy ) / da; - if( d > 0 && d < 1 ) - { - // Simple collinear case, 1---2---3 - // We can leave just two endpoints - return; - } - if( d <= 0 ) - d = calc_sq_distance( x2, y2, x1, y1 ); - else if( d >= 1 ) - d = calc_sq_distance( x2, y2, x3, y3 ); - else - d = calc_sq_distance( x2, y2, x1 + (int) d * dx, - y1 + (int) d * dy ); - } - if( d < distance_tolerance_square ) - { - addSegment( wxPoint( x2, y2 ) ); - return; + // a minimal filter on the lenght 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 ); } } - // Continue subdivision - //---------------------- - recursiveBezier( x1, y1, x12, y12, x123, y123, level + 1 ); - recursiveBezier( x123, y123, x23, y23, x3, y3, level + 1 ); -} - - -void BEZIER_POLY::recursiveBezier( int x1, int y1, int x2, int y2, - int x3, int y3, int x4, int y4, unsigned int level ) -{ - if( level > recursion_limit ) - return; - - // Calculate all the mid-points of the line segments - //---------------------- - int x12 = (x1 + x2) / 2; - int y12 = (y1 + y2) / 2; - int x23 = (x2 + x3) / 2; - int y23 = (y2 + y3) / 2; - int x34 = (x3 + x4) / 2; - int y34 = (y3 + y4) / 2; - int x123 = (x12 + x23) / 2; - int y123 = (y12 + y23) / 2; - int x234 = (x23 + x34) / 2; - int y234 = (y23 + y34) / 2; - int x1234 = (x123 + x234) / 2; - int y1234 = (y123 + y234) / 2; - - - // Try to approximate the full cubic curve by a single straight line - //------------------ - int dx = x4 - x1; - int dy = y4 - y1; - - double d2 = fabs( (double) ( (x2 - x4) * dy - (y2 - y4) * dx ) ); - double d3 = fabs( (double) ( (x3 - x4) * dy - (y3 - y4) * dx ) ); - double da1, da2, k; - - switch( (int(d2 > curve_collinearity_epsilon) << 1) + - int(d3 > curve_collinearity_epsilon) ) - { - case 0: - - // All collinear OR p1==p4 - //---------------------- - k = dx * dx + dy * dy; - if( k == 0 ) - { - d2 = calc_sq_distance( x1, y1, x2, y2 ); - d3 = calc_sq_distance( x4, y4, x3, y3 ); - } - else - { - k = 1 / k; - da1 = x2 - x1; - da2 = y2 - y1; - d2 = k * (da1 * dx + da2 * dy); - da1 = x3 - x1; - da2 = y3 - y1; - d3 = k * (da1 * dx + da2 * dy); - if( d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 ) - { - // Simple collinear case, 1---2---3---4 - // We can leave just two endpoints - return; - } - if( d2 <= 0 ) - d2 = calc_sq_distance( x2, y2, x1, y1 ); - else if( d2 >= 1 ) - d2 = calc_sq_distance( x2, y2, x4, y4 ); - else - d2 = calc_sq_distance( x2, y2, x1 + (int) d2 * dx, - y1 + (int) d2 * dy ); - - if( d3 <= 0 ) - d3 = calc_sq_distance( x3, y3, x1, y1 ); - else if( d3 >= 1 ) - d3 = calc_sq_distance( x3, y3, x4, y4 ); - else - d3 = calc_sq_distance( x3, y3, x1 + (int) d3 * dx, - y1 + (int) d3 * dy ); - } - if( d2 > d3 ) - { - if( d2 < distance_tolerance_square ) - { - addSegment( wxPoint( x2, y2 ) ); - return; - } - } - else - { - if( d3 < distance_tolerance_square ) - { - addSegment( wxPoint( x3, y3 ) ); - return; - } - } - break; - - case 1: - - // p1,p2,p4 are collinear, p3 is significant - //---------------------- - if( d3 * d3 <= distance_tolerance_square * sqrt_len(dx, dy) ) - { - if( angle_tolerance < curve_angle_tolerance_epsilon ) - { - addSegment( wxPoint( x23, y23 ) ); - return; - } - - // Angle Condition - //---------------------- - da1 = fabs( atan2( (double) ( y4 - y3 ), (double) ( x4 - x3 ) ) - - atan2( (double) ( y3 - y2 ), (double) ( x3 - x2 ) ) ); - if( da1 >= M_PI ) - da1 = 2 * M_PI - da1; - - if( da1 < angle_tolerance ) - { - addSegment( wxPoint( x2, y2 ) ); - addSegment( wxPoint( x3, y3 ) ); - return; - } - - if( cusp_limit != 0.0 ) - { - if( da1 > cusp_limit ) - { - addSegment( wxPoint( x3, y3 ) ); - return; - } - } - } - break; - - case 2: - - // p1,p3,p4 are collinear, p2 is significant - //---------------------- - if( d2 * d2 <= distance_tolerance_square * sqrt_len(dx, dy) ) - { - if( angle_tolerance < curve_angle_tolerance_epsilon ) - { - addSegment( wxPoint( x23, y23 ) ); - return; - } - - // Angle Condition - //---------------------- - da1 = fabs( atan2( (double) ( y3 - y2 ), (double) ( x3 - x2 ) ) - - atan2( (double) ( y2 - y1 ), (double) ( x2 - x1 ) ) ); - if( da1 >= M_PI ) - da1 = 2 * M_PI - da1; - - if( da1 < angle_tolerance ) - { - addSegment( wxPoint( x2, y2 ) ); - addSegment( wxPoint( x3, y3 ) ); - return; - } - - if( cusp_limit != 0.0 ) - { - if( da1 > cusp_limit ) - { - addSegment( wxPoint( x2, y2 ) ); - return; - } - } - } - break; - - case 3: - - // Regular case - //----------------- - if( (d2 + d3) * (d2 + d3) <= distance_tolerance_square * sqrt_len(dx, dy) ) - { - // If the curvature doesn't exceed the distance_tolerance value - // we tend to finish subdivisions. - //---------------------- - if( angle_tolerance < curve_angle_tolerance_epsilon ) - { - addSegment( wxPoint( x23, y23 ) ); - return; - } - - // Angle & Cusp Condition - //---------------------- - k = atan2( (double) ( y3 - y2 ), (double) ( x3 - x2 ) ); - da1 = fabs( k - atan2( (double) ( y2 - y1 ), - (double) ( x2 - x1 ) ) ); - da2 = fabs( atan2( (double) ( y4 - y3 ), - (double) ( x4 - x3 ) ) - k ); - if( da1 >= M_PI ) - da1 = 2 * M_PI - da1; - if( da2 >= M_PI ) - da2 = 2 * M_PI - da2; - - if( da1 + da2 < angle_tolerance ) - { - // Finally we can stop the recursion - //---------------------- - addSegment( wxPoint( x23, y23 ) ); - return; - } - - if( cusp_limit != 0.0 ) - { - if( da1 > cusp_limit ) - { - addSegment( wxPoint( x2, y2 ) ); - return; - } - - if( da2 > cusp_limit ) - { - addSegment( wxPoint( x3, y3 ) ); - return; - } - } - } - break; - } - - // Continue subdivision - //---------------------- - recursiveBezier( x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1 ); - recursiveBezier( x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1 ); + if( aOutput.back() != m_ctrlPts[3] ) + aOutput.push_back( m_ctrlPts[3] ); } diff --git a/common/gal/opengl/opengl_gal.cpp b/common/gal/opengl/opengl_gal.cpp index a9297deb9c..f9998786e3 100644 --- a/common/gal/opengl/opengl_gal.cpp +++ b/common/gal/opengl/opengl_gal.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -1036,32 +1037,18 @@ void OPENGL_GAL::DrawPolygon( const SHAPE_LINE_CHAIN& aPolygon ) void OPENGL_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA, const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint ) { - // FIXME The drawing quality needs to be improved - // FIXME Perhaps choose a quad/triangle strip instead? - // FIXME Brute force method, use a better (recursive?) algorithm + std::vector output; + std::vector pointCtrl; - std::deque pointList; + pointCtrl.push_back( aStartPoint ); + pointCtrl.push_back( aControlPointA ); + pointCtrl.push_back( aControlPointB ); + pointCtrl.push_back( aEndPoint ); - double t = 0.0; - double dt = 1.0 / (double) CURVE_POINTS; + BEZIER_POLY converter( pointCtrl ); + converter.GetPoly( output, GetLineWidth() ); - for( int i = 0; i <= CURVE_POINTS; i++ ) - { - double omt = 1.0 - t; - double omt2 = omt * omt; - double omt3 = omt * omt2; - double t2 = t * t; - double t3 = t * t2; - - VECTOR2D vertex = omt3 * aStartPoint + 3.0 * t * omt2 * aControlPointA - + 3.0 * t2 * omt * aControlPointB + t3 * aEndPoint; - - pointList.push_back( vertex ); - - t += dt; - } - - DrawPolyline( pointList ); + DrawPolyline( &output[0], output.size() ); } diff --git a/common/gr_basic.cpp b/common/gr_basic.cpp index 92503fe54e..6375b0c890 100644 --- a/common/gr_basic.cpp +++ b/common/gr_basic.cpp @@ -1148,45 +1148,16 @@ void ClipAndDrawPoly( EDA_RECT* aClipBox, wxDC* aDC, wxPoint aPoints[], int n ) } -void GRBezier( EDA_RECT* ClipBox, - wxDC* DC, - int x1, - int y1, - int x2, - int y2, - int x3, - int y3, - int width, - COLOR4D Color ) +void GRBezier( EDA_RECT* aClipBox, wxDC* aDC, + std::vector& aPoint, + int aWidth, COLOR4D aColor ) { - std::vector points; + std::vector output; - BEZIER_POLY converter( x1, y1, x2, y2, x3, y3 ); - converter.GetPoly( points ); + BEZIER_POLY converter( aPoint ); + converter.GetPoly( output, aWidth ); - GRPoly( ClipBox, DC, points.size(), &points[0], false, width, Color, Color ); -} - - -void GRBezier( EDA_RECT* ClipBox, - wxDC* DC, - int x1, - int y1, - int x2, - int y2, - int x3, - int y3, - int x4, - int y4, - int width, - COLOR4D Color ) -{ - std::vector points; - - BEZIER_POLY converter( x1, y1, x2, y2, x3, y3, x4, y4 ); - converter.GetPoly( points ); - - GRPoly( ClipBox, DC, points.size(), &points[0], false, width, Color, Color ); + GRPoly( aClipBox, aDC, output.size(), &output[0], false, aWidth, aColor, aColor ); } diff --git a/include/bezier_curves.h b/include/bezier_curves.h index 7fb63a139d..bf6aee3d49 100644 --- a/include/bezier_curves.h +++ b/include/bezier_curves.h @@ -1,8 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2009-2014 Jean-Pierre Charras, jp.charras at wanadoo.fr - * Copyright (C) 1992-2017 KiCad Developers, see CHANGELOG.TXT for contributors. + * Copyright (C) 2014-2019 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 @@ -27,6 +26,7 @@ #include #include +#include /** * Bezier curves to polygon converter. @@ -35,32 +35,12 @@ class BEZIER_POLY { public: - /** cubic Bezier curve */ - BEZIER_POLY( int x1, int y1, int x2, int y2, int x3, int y3 ) - { - m_ctrlPts.emplace_back( x1, y1 ); - m_ctrlPts.emplace_back( x2, y2 ); - m_ctrlPts.emplace_back( x3, y3 ); - m_output = nullptr; - m_minSegLen = 0; - } + BEZIER_POLY( const std::vector& aControlPoints ); - /** Quadratic and cubic Bezier curve */ - BEZIER_POLY( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ) - { - m_ctrlPts.emplace_back( x1, y1 ); - m_ctrlPts.emplace_back( x2, y2 ); - m_ctrlPts.emplace_back( x3, y3 ); - m_ctrlPts.emplace_back( x4, y4 ); - m_output = nullptr; - m_minSegLen = 0; - } - - BEZIER_POLY( const std::vector& aControlPoints ) + BEZIER_POLY( const std::vector& aControlPoints ) : m_ctrlPts( aControlPoints ) { - m_output = nullptr; - m_minSegLen = 0; + m_minSegLen = 0.0; } /** @@ -71,40 +51,13 @@ public: * (the last point is always generated) */ void GetPoly( std::vector& aOutput, int aMinSegLen = 0 ); + void GetPoly( std::vector& aOutput, double aMinSegLen = 0.0 ); private: - int m_minSegLen; + double m_minSegLen; ///> Control points - std::vector m_ctrlPts; - - ///> Pointer to the output vector - std::vector* m_output; - - void addSegment( const wxPoint& aSegment ) - { - int seglen = std::abs( m_output->back().x - aSegment.x ) - + std::abs( m_output->back().y - aSegment.y ); - - // m_minSegLen is always > 0, so never store a 0 len segment - if( seglen >= m_minSegLen ) - m_output->push_back( aSegment ); - } - - void recursiveBezier( int x1, int y1, int x2, int y2, int x3, int y3, unsigned int level ); - void recursiveBezier( int x1, int y1, int x2, int y2, - int x3, int y3, int x4, int y4, unsigned int level ); - - - // Conversion parameters - constexpr static double angle_tolerance = 0.0; - constexpr static double cusp_limit = 0.0; - constexpr static unsigned int recursion_limit = 12; - constexpr static double approximation_scale = 0.5; // 1 - constexpr static double distance_tolerance_square = ( 0.5 / approximation_scale ) * ( 0.5 / approximation_scale ); - - constexpr static double curve_collinearity_epsilon = 1e-30; - constexpr static double curve_angle_tolerance_epsilon = 0.0001; + std::vector m_ctrlPts; }; #endif // BEZIER_CURVES_H diff --git a/include/gr_basic.h b/include/gr_basic.h index 71f1d67923..69985b4835 100644 --- a/include/gr_basic.h +++ b/include/gr_basic.h @@ -124,10 +124,10 @@ void GRLineTo( EDA_RECT* ClipBox, wxDC* DC, void GRPoly( EDA_RECT* ClipBox, wxDC* DC, int n, wxPoint Points[], bool Fill, int width, COLOR4D Color, COLOR4D BgColor ); -void GRBezier( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2, - int x3, int y3, int width, COLOR4D Color ); -void GRBezier( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2, - int x3, int y3, int x4, int y4, int width, COLOR4D Color ); +/** Draw cubic (4 points: start control1, control2, end) bezier curve + */ +void GRBezier( EDA_RECT* aClipBox, wxDC* aDC, std::vector& aPoints, + int aWidth, COLOR4D aColor ); /** * Function GRClosedPoly