FIX: Bezier curves: replace previous algorithm to convert the curve to segments. In some cases it was not working well. Opengl was using an other algo (using the curve properties to create segments) Now only the OpenGL algo is used, with optimization to reduce the number of segments.

This commit is contained in:
jean-pierre charras 2019-11-09 11:14:20 +01:00
parent c4023637df
commit 943858fd67
5 changed files with 71 additions and 454 deletions

View File

@ -1,8 +1,7 @@
/* /*
* This program source code file is part of KiCad, a free EDA CAD application. * 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-2019 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2014-2017 KiCad Developers, see CHANGELOG.TXT for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -26,362 +25,69 @@
/* routines to handle bezier curves */ /* routines to handle bezier curves */
/************************************/ /************************************/
#include <fctsys.h>
#include <bezier_curves.h> #include <bezier_curves.h>
BEZIER_POLY::BEZIER_POLY( const std::vector<wxPoint>& aControlPoints )
static inline double calc_sq_distance( int x1, int y1, int x2, int y2 )
{ {
int dx = x2 - x1; for( unsigned ii = 0; ii < aControlPoints.size(); ++ii )
int dy = y2 - y1; m_ctrlPts.emplace_back( VECTOR2D( aControlPoints[ii] ) );
return (double)dx * dx + (double)dy * dy; m_minSegLen = 0.0;
}
static inline double sqrt_len( int dx, int dy )
{
return ((double)dx * dx) + ((double)dy * dy);
} }
void BEZIER_POLY::GetPoly( std::vector<wxPoint>& aOutput, int aMinSegLen ) void BEZIER_POLY::GetPoly( std::vector<wxPoint>& aOutput, int aMinSegLen )
{ {
wxCHECK( !m_ctrlPts.empty(), /* void */ ); aOutput.clear();
m_minSegLen = std::max( 1, aMinSegLen ); std::vector<VECTOR2D> buffer;
m_output = &aOutput; GetPoly( buffer, double( aMinSegLen ) );
m_output->clear();
m_output->push_back( wxPoint( m_ctrlPts.front() ) );
// Only quadratic and cubic Bezier curves are handled for( unsigned ii = 0; ii < buffer.size(); ++ii )
if( m_ctrlPts.size() == 3 ) aOutput.emplace_back( wxPoint( int( buffer[ii].x ), int( buffer[ii].y ) ) );
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() ) );
} }
void BEZIER_POLY::recursiveBezier( int x1, int y1, int x2, int y2, void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen )
int x3, int y3, unsigned int level )
{ {
if( level > recursion_limit ) wxASSERT( m_ctrlPts.size() == 4 );
return; // 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 aOutput.clear();
//---------------------- aOutput.push_back( m_ctrlPts[0] );
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;
int dx = x3 - x1; // If the Bezier curve is degenerated (straight line), skip that:
int dy = y3 - y1; bool degenerated = m_ctrlPts[0] == m_ctrlPts[1] && m_ctrlPts[2] == m_ctrlPts[3];
double d = fabs( ((double) (x2 - x3) * dy) - ((double) (y2 - y3) * dx ) );
double da;
if( d > curve_collinearity_epsilon ) if( !degenerated )
{ {
// Regular case for( int ii = 1; ii < CURVE_POINTS; ii++ )
//-----------------
if( d * d <= distance_tolerance_square * (dx * dx + dy * dy) )
{ {
// If the curvature doesn't exceed the distance_tolerance value double t = dt * ii;
// we tend to finish subdivisions. double omt = 1.0 - t;
//---------------------- double omt2 = omt * omt;
if( angle_tolerance < curve_angle_tolerance_epsilon ) double omt3 = omt * omt2;
{ double t2 = t * t;
addSegment( wxPoint( x123, y123 ) ); double t3 = t * t2;
return;
}
// Angle & Cusp Condition VECTOR2D vertex = omt3 * m_ctrlPts[0]
//---------------------- + 3.0 * t * omt2 * m_ctrlPts[1]
da = fabs( atan2( (double) ( y3 - y2 ), (double) ( x3 - x2 ) ) - + 3.0 * t2 * omt * m_ctrlPts[2]
atan2( (double) ( y2 - y1 ), (double) ( x2 - x1 ) ) ); + t3 * m_ctrlPts[3];
if( da >=M_PI )
da = 2 * M_PI - da;
if( da < angle_tolerance ) // a minimal filter on the lenght of the segment being created:
{ // The offset from last point:
// Finally we can stop the recursion VECTOR2D delta = vertex - aOutput.back();
//---------------------- double dist = delta.EuclideanNorm();
addSegment( wxPoint( x123, y123 ) );
return; if( dist > aMinSegLen )
} aOutput.push_back( vertex );
}
}
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;
} }
} }
// Continue subdivision if( aOutput.back() != m_ctrlPts[3] )
//---------------------- aOutput.push_back( m_ctrlPts[3] );
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 );
} }

View File

@ -32,6 +32,7 @@
#include <gl_context_mgr.h> #include <gl_context_mgr.h>
#include <geometry/shape_poly_set.h> #include <geometry/shape_poly_set.h>
#include <bitmap_base.h> #include <bitmap_base.h>
#include <bezier_curves.h>
#include <macros.h> #include <macros.h>
@ -1035,32 +1036,18 @@ void OPENGL_GAL::DrawPolygon( const SHAPE_LINE_CHAIN& aPolygon )
void OPENGL_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA, void OPENGL_GAL::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint ) const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint )
{ {
// FIXME The drawing quality needs to be improved std::vector<VECTOR2D> output;
// FIXME Perhaps choose a quad/triangle strip instead? std::vector<VECTOR2D> pointCtrl;
// FIXME Brute force method, use a better (recursive?) algorithm
std::deque<VECTOR2D> pointList; pointCtrl.push_back( aStartPoint );
pointCtrl.push_back( aControlPointA );
pointCtrl.push_back( aControlPointB );
pointCtrl.push_back( aEndPoint );
double t = 0.0; BEZIER_POLY converter( pointCtrl );
double dt = 1.0 / (double) CURVE_POINTS; converter.GetPoly( output, GetLineWidth() );
for( int i = 0; i <= CURVE_POINTS; i++ ) DrawPolyline( &output[0], output.size() );
{
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 );
} }

View File

@ -993,45 +993,16 @@ void ClipAndDrawPoly( EDA_RECT* aClipBox, wxDC* aDC, wxPoint aPoints[], int n )
} }
void GRBezier( EDA_RECT* ClipBox, void GRBezier( EDA_RECT* aClipBox, wxDC* aDC,
wxDC* DC, std::vector<wxPoint>& aPoint,
int x1, int aWidth, COLOR4D aColor )
int y1,
int x2,
int y2,
int x3,
int y3,
int width,
COLOR4D Color )
{ {
std::vector<wxPoint> points; std::vector<wxPoint> output;
BEZIER_POLY converter( x1, y1, x2, y2, x3, y3 ); BEZIER_POLY converter( aPoint );
converter.GetPoly( points ); converter.GetPoly( output, aWidth );
GRPoly( ClipBox, DC, points.size(), &points[0], false, width, Color, Color ); GRPoly( aClipBox, aDC, output.size(), &output[0], false, aWidth, aColor, aColor );
}
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<wxPoint> 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 );
} }

View File

@ -1,8 +1,7 @@
/* /*
* This program source code file is part of KiCad, a free EDA CAD application. * 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) 2014-2019 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 1992-2017 KiCad Developers, see CHANGELOG.TXT for contributors.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@ -27,6 +26,7 @@
#include <vector> #include <vector>
#include <wx/gdicmn.h> #include <wx/gdicmn.h>
#include <math/vector2d.h>
/** /**
* Bezier curves to polygon converter. * Bezier curves to polygon converter.
@ -35,32 +35,12 @@
class BEZIER_POLY class BEZIER_POLY
{ {
public: public:
/** cubic Bezier curve */ BEZIER_POLY( const std::vector<wxPoint>& aControlPoints );
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;
}
/** Quadratic and cubic Bezier curve */ BEZIER_POLY( const std::vector<VECTOR2D>& aControlPoints )
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<wxPoint>& aControlPoints )
: m_ctrlPts( aControlPoints ) : m_ctrlPts( aControlPoints )
{ {
m_output = nullptr; m_minSegLen = 0.0;
m_minSegLen = 0;
} }
/** /**
@ -71,40 +51,13 @@ public:
* (the last point is always generated) * (the last point is always generated)
*/ */
void GetPoly( std::vector<wxPoint>& aOutput, int aMinSegLen = 0 ); void GetPoly( std::vector<wxPoint>& aOutput, int aMinSegLen = 0 );
void GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen = 0.0 );
private: private:
int m_minSegLen; double m_minSegLen;
///> Control points ///> Control points
std::vector<wxPoint> m_ctrlPts; std::vector<VECTOR2D> m_ctrlPts;
///> Pointer to the output vector
std::vector<wxPoint>* 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;
}; };
#endif // BEZIER_CURVES_H #endif // BEZIER_CURVES_H

View File

@ -114,10 +114,10 @@ void GRLineTo( EDA_RECT* ClipBox, wxDC* DC,
void GRPoly( EDA_RECT* ClipBox, wxDC* DC, int n, wxPoint Points[], bool Fill, void GRPoly( EDA_RECT* ClipBox, wxDC* DC, int n, wxPoint Points[], bool Fill,
int width, COLOR4D Color, COLOR4D BgColor ); int width, COLOR4D Color, COLOR4D BgColor );
void GRBezier( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2, /** Draw cubic (4 points: start control1, control2, end) bezier curve
int x3, int y3, int width, COLOR4D Color ); */
void GRBezier( EDA_RECT* ClipBox, wxDC* DC, int x1, int y1, int x2, int y2, void GRBezier( EDA_RECT* aClipBox, wxDC* aDC, std::vector<wxPoint>& aPoints,
int x3, int y3, int x4, int y4, int width, COLOR4D Color ); int aWidth, COLOR4D aColor );
/** /**
* Function GRClosedPoly * Function GRClosedPoly