Large rework of BEZIER_POLY
Add direct handling of quadratic beziers to save compute time and number of points. Update cubic interpolation to reduce number of points generated for a given smoothness Cache data on open and used cached data to avoid multiple re-calcs Remove minimum line length and number of segments and replace with standard max error level. Allows us to specify the tolerance of bezier interpolation
This commit is contained in:
parent
0d2c4c91fd
commit
bcf6b620a8
|
@ -104,7 +104,7 @@ static const wxChar EnableLibWithText[] = wxT( "EnableLibWithText" );
|
||||||
static const wxChar EnableEeschemaPrintCairo[] = wxT( "EnableEeschemaPrintCairo" );
|
static const wxChar EnableEeschemaPrintCairo[] = wxT( "EnableEeschemaPrintCairo" );
|
||||||
static const wxChar DisambiguationTime[] = wxT( "DisambiguationTime" );
|
static const wxChar DisambiguationTime[] = wxT( "DisambiguationTime" );
|
||||||
static const wxChar PcbSelectionVisibilityRatio[] = wxT( "PcbSelectionVisibilityRatio" );
|
static const wxChar PcbSelectionVisibilityRatio[] = wxT( "PcbSelectionVisibilityRatio" );
|
||||||
static const wxChar MinimumSegmentLength[] = wxT( "MinimumSegmentLength" );
|
static const wxChar FontErrorSize[] = wxT( "FontErrorSize" );
|
||||||
static const wxChar OcePluginLinearDeflection[] = wxT( "OcePluginLinearDeflection" );
|
static const wxChar OcePluginLinearDeflection[] = wxT( "OcePluginLinearDeflection" );
|
||||||
static const wxChar OcePluginAngularDeflection[] = wxT( "OcePluginAngularDeflection" );
|
static const wxChar OcePluginAngularDeflection[] = wxT( "OcePluginAngularDeflection" );
|
||||||
static const wxChar TriangulateSimplificationLevel[] = wxT( "TriangulateSimplificationLevel" );
|
static const wxChar TriangulateSimplificationLevel[] = wxT( "TriangulateSimplificationLevel" );
|
||||||
|
@ -257,7 +257,7 @@ ADVANCED_CFG::ADVANCED_CFG()
|
||||||
|
|
||||||
m_PcbSelectionVisibilityRatio = 1.0;
|
m_PcbSelectionVisibilityRatio = 1.0;
|
||||||
|
|
||||||
m_MinimumSegmentLength = 50;
|
m_FontErrorSize = 16;
|
||||||
|
|
||||||
m_OcePluginLinearDeflection = 0.14;
|
m_OcePluginLinearDeflection = 0.14;
|
||||||
m_OcePluginAngularDeflection = 30;
|
m_OcePluginAngularDeflection = 30;
|
||||||
|
@ -469,9 +469,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
|
||||||
&m_PcbSelectionVisibilityRatio,
|
&m_PcbSelectionVisibilityRatio,
|
||||||
m_PcbSelectionVisibilityRatio, 0.0, 1.0 ) );
|
m_PcbSelectionVisibilityRatio, 0.0, 1.0 ) );
|
||||||
|
|
||||||
configParams.push_back( new PARAM_CFG_INT( true, AC_KEYS::MinimumSegmentLength,
|
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::FontErrorSize,
|
||||||
&m_MinimumSegmentLength,
|
&m_FontErrorSize,
|
||||||
m_MinimumSegmentLength, 10, 1000 ) );
|
m_FontErrorSize, 0.01, 100 ) );
|
||||||
|
|
||||||
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::OcePluginLinearDeflection,
|
configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::OcePluginLinearDeflection,
|
||||||
&m_OcePluginLinearDeflection,
|
&m_OcePluginLinearDeflection,
|
||||||
|
|
|
@ -352,6 +352,7 @@ void EDA_SHAPE::scale( double aScale )
|
||||||
scalePt( m_end );
|
scalePt( m_end );
|
||||||
scalePt( m_bezierC1 );
|
scalePt( m_bezierC1 );
|
||||||
scalePt( m_bezierC2 );
|
scalePt( m_bezierC2 );
|
||||||
|
RebuildBezierToSegmentsPointsList( m_stroke.GetWidth() / 2 );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -488,12 +489,7 @@ void EDA_SHAPE::flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
|
||||||
m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y );
|
m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild the poly points shape
|
RebuildBezierToSegmentsPointsList( m_stroke.GetWidth() / 2 );
|
||||||
{
|
|
||||||
std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
|
|
||||||
BEZIER_POLY converter( ctrlPoints );
|
|
||||||
converter.GetPoly( m_bezierPoints, m_stroke.GetWidth() );
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -503,7 +499,7 @@ void EDA_SHAPE::flip( const VECTOR2I& aCentre, bool aFlipLeftRight )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMinSegLen )
|
void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMaxError )
|
||||||
{
|
{
|
||||||
// Has meaning only for SHAPE_T::BEZIER
|
// Has meaning only for SHAPE_T::BEZIER
|
||||||
if( m_shape != SHAPE_T::BEZIER )
|
if( m_shape != SHAPE_T::BEZIER )
|
||||||
|
@ -513,30 +509,18 @@ void EDA_SHAPE::RebuildBezierToSegmentsPointsList( int aMinSegLen )
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
|
// Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
|
||||||
m_bezierPoints = buildBezierToSegmentsPointsList( aMinSegLen );
|
m_bezierPoints = buildBezierToSegmentsPointsList( aMaxError );
|
||||||
|
|
||||||
// Ensure last point respects aMinSegLen parameter
|
|
||||||
if( m_bezierPoints.size() > 2 )
|
|
||||||
{
|
|
||||||
int idx = m_bezierPoints.size() - 1;
|
|
||||||
|
|
||||||
if( VECTOR2I( m_bezierPoints[idx] - m_bezierPoints[idx] - 1 ).EuclideanNorm() < aMinSegLen )
|
|
||||||
{
|
|
||||||
m_bezierPoints[idx - 1] = m_bezierPoints[idx];
|
|
||||||
m_bezierPoints.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMinSegLen ) const
|
const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const
|
||||||
{
|
{
|
||||||
std::vector<VECTOR2I> bezierPoints;
|
std::vector<VECTOR2I> bezierPoints;
|
||||||
|
|
||||||
// Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
|
// Rebuild the m_BezierPoints vertex list that approximate the Bezier curve
|
||||||
std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
|
std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end };
|
||||||
BEZIER_POLY converter( ctrlPoints );
|
BEZIER_POLY converter( ctrlPoints );
|
||||||
converter.GetPoly( bezierPoints, aMinSegLen );
|
converter.GetPoly( bezierPoints, aMaxError );
|
||||||
|
|
||||||
return bezierPoints;
|
return bezierPoints;
|
||||||
}
|
}
|
||||||
|
@ -930,16 +914,25 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
|
||||||
}
|
}
|
||||||
|
|
||||||
case SHAPE_T::BEZIER:
|
case SHAPE_T::BEZIER:
|
||||||
const_cast<EDA_SHAPE*>( this )->RebuildBezierToSegmentsPointsList( GetWidth() );
|
|
||||||
|
|
||||||
for( unsigned int i= 1; i < m_bezierPoints.size(); i++)
|
|
||||||
{
|
{
|
||||||
if( TestSegmentHit( aPosition, m_bezierPoints[ i - 1], m_bezierPoints[i], maxdist ) )
|
const std::vector<VECTOR2I>* pts = &m_bezierPoints;
|
||||||
|
std::vector<VECTOR2I> updatedBezierPoints;
|
||||||
|
|
||||||
|
if( m_bezierPoints.empty() )
|
||||||
|
{
|
||||||
|
BEZIER_POLY converter( m_start, m_bezierC1, m_bezierC2, m_end );
|
||||||
|
converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
|
||||||
|
pts = &updatedBezierPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( unsigned int i = 1; i < pts->size(); i++ )
|
||||||
|
{
|
||||||
|
if( TestSegmentHit( aPosition, ( *pts )[i - 1], ( *pts )[i], maxdist ) )
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
case SHAPE_T::SEGMENT:
|
case SHAPE_T::SEGMENT:
|
||||||
return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
|
return TestSegmentHit( aPosition, GetStart(), GetEnd(), maxdist );
|
||||||
|
|
||||||
|
@ -1132,12 +1125,20 @@ bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
|
||||||
|
|
||||||
// Account for the width of the line
|
// Account for the width of the line
|
||||||
arect.Inflate( GetWidth() / 2 );
|
arect.Inflate( GetWidth() / 2 );
|
||||||
unsigned count = m_bezierPoints.size();
|
const std::vector<VECTOR2I>* pts = &m_bezierPoints;
|
||||||
|
std::vector<VECTOR2I> updatedBezierPoints;
|
||||||
|
|
||||||
for( unsigned ii = 1; ii < count; ii++ )
|
if( m_bezierPoints.empty() )
|
||||||
{
|
{
|
||||||
VECTOR2I vertex = m_bezierPoints[ii - 1];
|
BEZIER_POLY converter( m_start, m_bezierC1, m_bezierC2, m_end );
|
||||||
VECTOR2I vertexNext = m_bezierPoints[ii];
|
converter.GetPoly( updatedBezierPoints, aAccuracy / 2 );
|
||||||
|
pts = &updatedBezierPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( unsigned ii = 1; ii < pts->size(); ii++ )
|
||||||
|
{
|
||||||
|
VECTOR2I vertex = ( *pts )[ii - 1];
|
||||||
|
VECTOR2I vertexNext = ( *pts )[ii];
|
||||||
|
|
||||||
// Test if the point is within aRect
|
// Test if the point is within aRect
|
||||||
if( arect.Contains( vertex ) )
|
if( arect.Contains( vertex ) )
|
||||||
|
@ -1278,7 +1279,7 @@ std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh
|
||||||
|
|
||||||
case SHAPE_T::BEZIER:
|
case SHAPE_T::BEZIER:
|
||||||
{
|
{
|
||||||
std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( width );
|
std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( width / 2 );
|
||||||
VECTOR2I start_pt = bezierPoints[0];
|
VECTOR2I start_pt = bezierPoints[0];
|
||||||
|
|
||||||
for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
|
for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ )
|
||||||
|
@ -1381,7 +1382,7 @@ void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition )
|
||||||
SetBezierC2( aPosition );
|
SetBezierC2( aPosition );
|
||||||
m_editState = 1;
|
m_editState = 1;
|
||||||
|
|
||||||
RebuildBezierToSegmentsPointsList( GetWidth() );
|
RebuildBezierToSegmentsPointsList( GetWidth() / 2 );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SHAPE_T::POLY:
|
case SHAPE_T::POLY:
|
||||||
|
@ -1463,7 +1464,7 @@ void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
|
||||||
case 3: SetBezierC2( aPosition ); break;
|
case 3: SetBezierC2( aPosition ); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
RebuildBezierToSegmentsPointsList( GetWidth() );
|
RebuildBezierToSegmentsPointsList( GetWidth() / 2 );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1792,7 +1793,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance
|
||||||
std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
|
std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() };
|
||||||
BEZIER_POLY converter( ctrlPts );
|
BEZIER_POLY converter( ctrlPts );
|
||||||
std::vector<VECTOR2I> poly;
|
std::vector<VECTOR2I> poly;
|
||||||
converter.GetPoly( poly, GetWidth() );
|
converter.GetPoly( poly, aError );
|
||||||
|
|
||||||
for( unsigned ii = 1; ii < poly.size(); ii++ )
|
for( unsigned ii = 1; ii < poly.size(); ii++ )
|
||||||
TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
|
TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc );
|
||||||
|
|
|
@ -111,7 +111,8 @@ int OUTLINE_DECOMPOSER::cubicTo( const FT_Vector* aFirstControlPoint,
|
||||||
bezier.push_back( toVector2D( aEndPoint ) );
|
bezier.push_back( toVector2D( aEndPoint ) );
|
||||||
|
|
||||||
std::vector<VECTOR2D> result;
|
std::vector<VECTOR2D> result;
|
||||||
decomposer->approximateBezierCurve( result, bezier );
|
BEZIER_POLY converter( bezier );
|
||||||
|
converter.GetPoly( result, ADVANCED_CFG::GetCfg().m_FontErrorSize );
|
||||||
|
|
||||||
for( const VECTOR2D& p : result )
|
for( const VECTOR2D& p : result )
|
||||||
decomposer->addContourPoint( p );
|
decomposer->addContourPoint( p );
|
||||||
|
@ -147,63 +148,6 @@ bool OUTLINE_DECOMPOSER::OutlineToSegments( std::vector<CONTOUR>* aContours )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// use converter in kimath
|
|
||||||
bool OUTLINE_DECOMPOSER::approximateQuadraticBezierCurve( std::vector<VECTOR2D>& aResult,
|
|
||||||
const std::vector<VECTOR2D>& aBezier ) const
|
|
||||||
{
|
|
||||||
wxASSERT( aBezier.size() == 3 );
|
|
||||||
|
|
||||||
// BEZIER_POLY only handles cubic Bezier curves, even though the
|
|
||||||
// comments say otherwise...
|
|
||||||
//
|
|
||||||
// Quadratic to cubic Bezier conversion:
|
|
||||||
// cpn = Cubic Bezier control points (n = 0..3, 4 in total)
|
|
||||||
// qpn = Quadratic Bezier control points (n = 0..2, 3 in total)
|
|
||||||
// cp0 = qp0, cp1 = qp0 + 2/3 * (qp1 - qp0), cp2 = qp2 + 2/3 * (qp1 - qp2), cp3 = qp2
|
|
||||||
|
|
||||||
std::vector<VECTOR2D> cubic;
|
|
||||||
cubic.reserve( 4 );
|
|
||||||
|
|
||||||
cubic.push_back( aBezier[0] ); // cp0
|
|
||||||
cubic.push_back( aBezier[0] + ( ( aBezier[1] - aBezier[0] ) * 2 / 3 ) ); // cp1
|
|
||||||
cubic.push_back( aBezier[2] + ( ( aBezier[1] - aBezier[2] ) * 2 / 3 ) ); // cp2
|
|
||||||
cubic.push_back( aBezier[2] ); // cp3
|
|
||||||
|
|
||||||
return approximateCubicBezierCurve( aResult, cubic );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool OUTLINE_DECOMPOSER::approximateCubicBezierCurve( std::vector<VECTOR2D>& aResult,
|
|
||||||
const std::vector<VECTOR2D>& aCubicBezier ) const
|
|
||||||
{
|
|
||||||
wxASSERT( aCubicBezier.size() == 4 );
|
|
||||||
|
|
||||||
static int minimumSegmentLength = ADVANCED_CFG::GetCfg().m_MinimumSegmentLength;
|
|
||||||
BEZIER_POLY converter( aCubicBezier );
|
|
||||||
converter.GetPoly( aResult, minimumSegmentLength );
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool OUTLINE_DECOMPOSER::approximateBezierCurve( std::vector<VECTOR2D>& aResult,
|
|
||||||
const std::vector<VECTOR2D>& aBezier ) const
|
|
||||||
{
|
|
||||||
switch( aBezier.size() )
|
|
||||||
{
|
|
||||||
case 4: // cubic
|
|
||||||
return approximateCubicBezierCurve( aResult, aBezier );
|
|
||||||
|
|
||||||
case 3: // quadratic
|
|
||||||
return approximateQuadraticBezierCurve( aResult, aBezier );
|
|
||||||
|
|
||||||
default:
|
|
||||||
// error, only 3 and 4 are acceptable values
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int OUTLINE_DECOMPOSER::winding( const std::vector<VECTOR2D>& aContour ) const
|
int OUTLINE_DECOMPOSER::winding( const std::vector<VECTOR2D>& aContour ) const
|
||||||
{
|
{
|
||||||
// -1 == counterclockwise, 1 == clockwise
|
// -1 == counterclockwise, 1 == clockwise
|
||||||
|
|
|
@ -95,7 +95,7 @@ FT_Error OUTLINE_FONT::loadFace( const wxString& aFontFileName, int aFaceIndex )
|
||||||
// m_face = handle to face object
|
// m_face = handle to face object
|
||||||
// 0 = char width in 1/64th of points ( 0 = same as char height )
|
// 0 = char width in 1/64th of points ( 0 = same as char height )
|
||||||
// faceSize() = char height in 1/64th of points
|
// faceSize() = char height in 1/64th of points
|
||||||
// GLYPH_RESOLUTION = horizontal device resolution (288dpi, 4x default)
|
// GLYPH_RESOLUTION = horizontal device resolution (1152dpi, 16x default)
|
||||||
// 0 = vertical device resolution ( 0 = same as horizontal )
|
// 0 = vertical device resolution ( 0 = same as horizontal )
|
||||||
FT_Set_Char_Size( m_face, 0, faceSize(), GLYPH_RESOLUTION, 0 );
|
FT_Set_Char_Size( m_face, 0, faceSize(), GLYPH_RESOLUTION, 0 );
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,7 +266,7 @@ EASYEDA_PARSER_BASE::ParseLineChains( const wxString& data, int aArcMinSegLen, b
|
||||||
BEZIER_POLY converter( ctrlPoints );
|
BEZIER_POLY converter( ctrlPoints );
|
||||||
|
|
||||||
std::vector<VECTOR2I> bezierPoints;
|
std::vector<VECTOR2I> bezierPoints;
|
||||||
converter.GetPoly( bezierPoints, aArcMinSegLen, 16 );
|
converter.GetPoly( bezierPoints, aArcMinSegLen );
|
||||||
|
|
||||||
chain.Append( bezierPoints );
|
chain.Append( bezierPoints );
|
||||||
|
|
||||||
|
|
|
@ -244,7 +244,7 @@ void PLOTTER::BezierCurve( const VECTOR2I& aStart, const VECTOR2I& aControl1,
|
||||||
BEZIER_POLY bezier_converter( ctrlPoints );
|
BEZIER_POLY bezier_converter( ctrlPoints );
|
||||||
|
|
||||||
std::vector<VECTOR2I> approxPoints;
|
std::vector<VECTOR2I> approxPoints;
|
||||||
bezier_converter.GetPoly( approxPoints, minSegLen );
|
bezier_converter.GetPoly( approxPoints, aTolerance );
|
||||||
|
|
||||||
SetCurrentLineWidth( aLineThickness );
|
SetCurrentLineWidth( aLineThickness );
|
||||||
MoveTo( aStart );
|
MoveTo( aStart );
|
||||||
|
|
|
@ -217,7 +217,7 @@ void GRAPHICS_IMPORTER_LIB_SYMBOL::AddSpline( const VECTOR2D& aStart,
|
||||||
spline->SetBezierC1( MapCoordinate( aBezierControl1 ) );
|
spline->SetBezierC1( MapCoordinate( aBezierControl1 ) );
|
||||||
spline->SetBezierC2( MapCoordinate( aBezierControl2 ) );
|
spline->SetBezierC2( MapCoordinate( aBezierControl2 ) );
|
||||||
spline->SetEnd( MapCoordinate( aEnd ) );
|
spline->SetEnd( MapCoordinate( aEnd ) );
|
||||||
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() );
|
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() / 2 );
|
||||||
|
|
||||||
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
|
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
|
||||||
// null (i.e. very small) length
|
// null (i.e. very small) length
|
||||||
|
|
|
@ -207,7 +207,7 @@ void GRAPHICS_IMPORTER_SCH::AddSpline( const VECTOR2D& aStart,
|
||||||
spline->SetBezierC1( MapCoordinate( aBezierControl1 ) );
|
spline->SetBezierC1( MapCoordinate( aBezierControl1 ) );
|
||||||
spline->SetBezierC2( MapCoordinate( aBezierControl2 ) );
|
spline->SetBezierC2( MapCoordinate( aBezierControl2 ) );
|
||||||
spline->SetEnd( MapCoordinate( aEnd ) );
|
spline->SetEnd( MapCoordinate( aEnd ) );
|
||||||
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() );
|
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() / 2 );
|
||||||
|
|
||||||
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
|
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
|
||||||
// null (i.e. very small) length
|
// null (i.e. very small) length
|
||||||
|
|
|
@ -1947,6 +1947,7 @@ void SCH_IO_ALTIUM::ParseBezier( const std::map<wxString, wxString>& aProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
bezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) );
|
bezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) );
|
||||||
|
bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() / 2 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2350,7 +2351,7 @@ void SCH_IO_ALTIUM::ParseEllipticalArc( const std::map<wxString, wxString>& aPro
|
||||||
schbezier->SetBezierC2( bezier.C2 );
|
schbezier->SetBezierC2( bezier.C2 );
|
||||||
schbezier->SetEnd( bezier.End );
|
schbezier->SetEnd( bezier.End );
|
||||||
schbezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) );
|
schbezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) );
|
||||||
schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth );
|
schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth / 2 );
|
||||||
|
|
||||||
currentScreen->Append( schbezier );
|
currentScreen->Append( schbezier );
|
||||||
}
|
}
|
||||||
|
@ -2413,7 +2414,7 @@ void SCH_IO_ALTIUM::ParseEllipticalArc( const std::map<wxString, wxString>& aPro
|
||||||
}
|
}
|
||||||
|
|
||||||
SetLibShapeLine( elem, schbezier, ALTIUM_SCH_RECORD::ELLIPTICAL_ARC );
|
SetLibShapeLine( elem, schbezier, ALTIUM_SCH_RECORD::ELLIPTICAL_ARC );
|
||||||
schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth );
|
schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth / 2 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1381,7 +1381,7 @@ SCH_SHAPE* SCH_IO_KICAD_LEGACY_LIB_CACHE::loadBezier( LINE_READER& aReader )
|
||||||
pt.y = -schIUScale.MilsToIU( parseInt( aReader, line, &line ) );
|
pt.y = -schIUScale.MilsToIU( parseInt( aReader, line, &line ) );
|
||||||
bezier->SetEnd( pt );
|
bezier->SetEnd( pt );
|
||||||
|
|
||||||
bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() );
|
bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() / 2 );
|
||||||
|
|
||||||
if( *line != 0 )
|
if( *line != 0 )
|
||||||
bezier->SetFillMode( parseFillMode( aReader, line, &line ) );
|
bezier->SetFillMode( parseFillMode( aReader, line, &line ) );
|
||||||
|
|
|
@ -1315,7 +1315,7 @@ SCH_SHAPE* SCH_IO_KICAD_SEXPR_PARSER::parseSymbolBezier()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() );
|
bezier->RebuildBezierToSegmentsPointsList( bezier->GetPenWidth() / 2 );
|
||||||
|
|
||||||
return bezier.release();
|
return bezier.release();
|
||||||
}
|
}
|
||||||
|
|
|
@ -739,7 +739,7 @@ void EE_POINT_EDITOR::updateParentItem( bool aSnapToGrid ) const
|
||||||
shape->SetBezierC2( m_editPoints->Point( BEZIER_CTRL_PT2 ).GetPosition() );
|
shape->SetBezierC2( m_editPoints->Point( BEZIER_CTRL_PT2 ).GetPosition() );
|
||||||
shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() );
|
shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() );
|
||||||
|
|
||||||
shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() );
|
shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() / 2 );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -509,14 +509,14 @@ public:
|
||||||
double m_PcbSelectionVisibilityRatio;
|
double m_PcbSelectionVisibilityRatio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of the minimum segment for the outline decomposer. This is in IU, so
|
* Deviation between font's bezier curve ideal and the poligonized curve. This
|
||||||
* it is nm in pcbnew and 100nm in eeschema.
|
* is 1/16 of the font's internal units.
|
||||||
*
|
*
|
||||||
* Setting name: "MinimumSegmentLength"
|
* Setting name: "FontErrorSize"
|
||||||
* Valid values: 10 to 1000
|
* Valid values: 0.01 to 100
|
||||||
* Default value: 50
|
* Default value: 8
|
||||||
*/
|
*/
|
||||||
int m_MinimumSegmentLength;
|
double m_FontErrorSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OCE (STEP/IGES) 3D Plugin Tesselation Linear Deflection
|
* OCE (STEP/IGES) 3D Plugin Tesselation Linear Deflection
|
||||||
|
|
|
@ -306,11 +306,9 @@ public:
|
||||||
*
|
*
|
||||||
* Has meaning only for BEZIER shape.
|
* Has meaning only for BEZIER shape.
|
||||||
*
|
*
|
||||||
* @param aMinSegLen is the min length of segments approximating the bezier. The shape's last
|
* @param aMinSegLen is the max deviation between the polyline and the curve
|
||||||
* segment can be shorter. This parameter avoids having too many very short
|
|
||||||
* segment in list. Good values are between m_width/2 and m_width.
|
|
||||||
*/
|
*/
|
||||||
void RebuildBezierToSegmentsPointsList( int aMinSegLen );
|
void RebuildBezierToSegmentsPointsList( int aMaxError );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a set of SHAPE objects representing the EDA_SHAPE. Caller owns the objects.
|
* Make a set of SHAPE objects representing the EDA_SHAPE. Caller owns the objects.
|
||||||
|
@ -387,7 +385,7 @@ protected:
|
||||||
bool hitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const;
|
bool hitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const;
|
||||||
bool hitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const;
|
bool hitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const;
|
||||||
|
|
||||||
const std::vector<VECTOR2I> buildBezierToSegmentsPointsList( int aMinSegLen ) const;
|
const std::vector<VECTOR2I> buildBezierToSegmentsPointsList( int aMaxError ) const;
|
||||||
|
|
||||||
void beginEdit( const VECTOR2I& aStartPoint );
|
void beginEdit( const VECTOR2I& aStartPoint );
|
||||||
bool continueEdit( const VECTOR2I& aPosition );
|
bool continueEdit( const VECTOR2I& aPosition );
|
||||||
|
|
|
@ -40,12 +40,6 @@
|
||||||
namespace KIFONT
|
namespace KIFONT
|
||||||
{
|
{
|
||||||
|
|
||||||
constexpr int GLYPH_DEFAULT_DPI = 72; ///< FreeType default
|
|
||||||
// The FreeType default of 72 DPI is not enough for outline decomposition;
|
|
||||||
// so we'll use something larger than that.
|
|
||||||
constexpr int GLYPH_RESOLUTION = 288;
|
|
||||||
constexpr double GLYPH_SIZE_SCALER = GLYPH_DEFAULT_DPI / (double) GLYPH_RESOLUTION;
|
|
||||||
|
|
||||||
|
|
||||||
class GAL_API GLYPH
|
class GAL_API GLYPH
|
||||||
{
|
{
|
||||||
|
|
|
@ -41,6 +41,13 @@
|
||||||
|
|
||||||
namespace KIFONT
|
namespace KIFONT
|
||||||
{
|
{
|
||||||
|
|
||||||
|
constexpr int GLYPH_DEFAULT_DPI = 72; ///< FreeType default
|
||||||
|
// The FreeType default of 72 DPI is not enough for outline decomposition;
|
||||||
|
// so we'll use something larger than that.
|
||||||
|
constexpr int GLYPH_RESOLUTION = 1152;
|
||||||
|
constexpr double GLYPH_SIZE_SCALER = GLYPH_DEFAULT_DPI / (double) GLYPH_RESOLUTION;
|
||||||
|
|
||||||
struct CONTOUR
|
struct CONTOUR
|
||||||
{
|
{
|
||||||
std::vector<VECTOR2D> m_Points;
|
std::vector<VECTOR2D> m_Points;
|
||||||
|
@ -70,13 +77,6 @@ private:
|
||||||
|
|
||||||
void addContourPoint( const VECTOR2D& p );
|
void addContourPoint( const VECTOR2D& p );
|
||||||
|
|
||||||
bool approximateBezierCurve( std::vector<VECTOR2D>& result,
|
|
||||||
const std::vector<VECTOR2D>& bezier ) const;
|
|
||||||
bool approximateQuadraticBezierCurve( std::vector<VECTOR2D>& result,
|
|
||||||
const std::vector<VECTOR2D>& bezier ) const;
|
|
||||||
bool approximateCubicBezierCurve( std::vector<VECTOR2D>& result,
|
|
||||||
const std::vector<VECTOR2D>& bezier ) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 1 if aContour is in clockwise order, -1 if it is in counterclockwise order,
|
* @return 1 if aContour is in clockwise order, -1 if it is in counterclockwise order,
|
||||||
* or 0 if the winding can't be determined.
|
* or 0 if the winding can't be determined.
|
||||||
|
|
|
@ -52,15 +52,32 @@ public:
|
||||||
* Convert a Bezier curve to a polygon.
|
* Convert a Bezier curve to a polygon.
|
||||||
*
|
*
|
||||||
* @param aOutput will be used as an output vector storing polygon points.
|
* @param aOutput will be used as an output vector storing polygon points.
|
||||||
* @param aMinSegLen is the min dist between 2 successive points.
|
* @param aMaxError maximum error in IU between the curve and the polygon.
|
||||||
* It can be used to reduce the number of points.
|
|
||||||
* (the last point is always generated)
|
|
||||||
* aMaxSegCount is the max number of segments created
|
|
||||||
*/
|
*/
|
||||||
void GetPoly( std::vector<VECTOR2I>& aOutput, int aMinSegLen = 0, int aMaxSegCount = 32 );
|
void GetPoly( std::vector<VECTOR2I>& aOutput, int aMaxError = 10 );
|
||||||
void GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen = 0.0, int aMaxSegCount = 32 );
|
void GetPoly( std::vector<VECTOR2D>& aOutput, double aMaxError = 10.0 );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
void getQuadPoly( std::vector<VECTOR2D>& aOutput, double aMaxError );
|
||||||
|
void getCubicPoly( std::vector<VECTOR2D>& aOutput, double aMaxError );
|
||||||
|
|
||||||
|
int findInflectionPoints( double& aT1, double& aT2 );
|
||||||
|
int numberOfInflectionPoints();
|
||||||
|
|
||||||
|
double thirdControlPointDeviation();
|
||||||
|
|
||||||
|
void subdivide( double aT, BEZIER_POLY& aLeft, BEZIER_POLY& aRight );
|
||||||
|
void recursiveSegmentation( std::vector<VECTOR2D>& aOutput, double aMaxError );
|
||||||
|
|
||||||
|
void cubicParabolicApprox( std::vector<VECTOR2D>& aOutput, double aMaxError );
|
||||||
|
|
||||||
|
bool isNaN() const;
|
||||||
|
|
||||||
|
bool isFlat( double aMaxError ) const;
|
||||||
|
|
||||||
|
VECTOR2D eval( double t );
|
||||||
|
|
||||||
double m_minSegLen;
|
double m_minSegLen;
|
||||||
|
|
||||||
///< Control points
|
///< Control points
|
||||||
|
|
|
@ -21,16 +21,69 @@
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/************************************/
|
/**********************************************************************************************/
|
||||||
/* routines to handle bezier curves */
|
/* routines to handle bezier curves */
|
||||||
/************************************/
|
/* Based on "Fast, Precise Flattening of Cubic Bezier segments offset Curves" by Hain, et. al.*/
|
||||||
|
/**********************************************************************************************/
|
||||||
|
|
||||||
|
// Portions of this code are based draw2d
|
||||||
|
// Copyright (c) 2010, Laurent Le Goff
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
// are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
// * Redistributions of source code must retain the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||||
|
// disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||||
|
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||||
|
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||||
|
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Portions of this code are base on BezierKit
|
||||||
|
// Copyright (c) 2017 Holmes Futrell
|
||||||
|
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
// Portions of this code are based on the spline-research project
|
||||||
|
// Copyright 2018 Raph Levien
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
#include <bezier_curves.h>
|
#include <bezier_curves.h>
|
||||||
#include <geometry/ellipse.h>
|
#include <geometry/ellipse.h>
|
||||||
#include <trigo.h>
|
#include <trigo.h>
|
||||||
#include <math/vector2d.h> // for VECTOR2D, operator*, VECTOR2
|
#include <math/vector2d.h> // for VECTOR2D, operator*, VECTOR2
|
||||||
#include <wx/debug.h> // for wxASSERT
|
#include <wx/debug.h> // for wxASSERT
|
||||||
|
#include <wx/log.h> // for wxLogTrace
|
||||||
|
|
||||||
|
#define BEZIER_DBG "bezier"
|
||||||
|
|
||||||
BEZIER_POLY::BEZIER_POLY( const VECTOR2I& aStart, const VECTOR2I& aCtrl1,
|
BEZIER_POLY::BEZIER_POLY( const VECTOR2I& aStart, const VECTOR2I& aCtrl1,
|
||||||
const VECTOR2I& aCtrl2, const VECTOR2I& aEnd )
|
const VECTOR2I& aCtrl2, const VECTOR2I& aEnd )
|
||||||
|
@ -55,62 +108,491 @@ BEZIER_POLY::BEZIER_POLY( const std::vector<VECTOR2I>& aControlPoints )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void BEZIER_POLY::GetPoly( std::vector<VECTOR2I>& aOutput, int aMinSegLen, int aMaxSegCount )
|
bool BEZIER_POLY::isNaN() const
|
||||||
|
{
|
||||||
|
for( const VECTOR2D& pt : m_ctrlPts )
|
||||||
|
{
|
||||||
|
if( std::isnan( pt.x ) || std::isnan( pt.y ) )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool BEZIER_POLY::isFlat( double aMaxError ) const
|
||||||
|
{
|
||||||
|
if( m_ctrlPts.size() == 3 )
|
||||||
|
{
|
||||||
|
VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
|
||||||
|
VECTOR2D D31 = m_ctrlPts[2] - m_ctrlPts[0];
|
||||||
|
|
||||||
|
double t = D21.Dot( D31 ) / D31.SquaredEuclideanNorm();
|
||||||
|
double u = std::min( std::max( t, 0.0 ), 1.0 );
|
||||||
|
VECTOR2D p = m_ctrlPts[0] + u * D31;
|
||||||
|
VECTOR2D delta = m_ctrlPts[1] - p;
|
||||||
|
|
||||||
|
return 0.5 * delta.EuclideanNorm() <= aMaxError;
|
||||||
|
}
|
||||||
|
else if( m_ctrlPts.size() == 4 )
|
||||||
|
{
|
||||||
|
VECTOR2D delta = m_ctrlPts[3] - m_ctrlPts[0];
|
||||||
|
|
||||||
|
VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
|
||||||
|
VECTOR2D D31 = m_ctrlPts[2] - m_ctrlPts[0];
|
||||||
|
|
||||||
|
double cross1 = delta.Cross( D21 );
|
||||||
|
double cross2 = delta.Cross( D31 );
|
||||||
|
|
||||||
|
double inv_delta_sq = 1.0 / delta.SquaredEuclideanNorm();
|
||||||
|
double d1 = ( cross1 * cross1 ) * inv_delta_sq;
|
||||||
|
double d2 = ( cross2 * cross2 ) * inv_delta_sq;
|
||||||
|
|
||||||
|
double factor = ( cross1 * cross2 > 0.0 ) ? 3.0 / 4.0 : 4.0 / 9.0;
|
||||||
|
double f2 = factor * factor;
|
||||||
|
double tol = aMaxError * aMaxError;
|
||||||
|
|
||||||
|
return d1 * f2 <= tol && d2 * f2 <= tol;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxASSERT( false );
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BEZIER_POLY::GetPoly( std::vector<VECTOR2I>& aOutput, int aMaxError )
|
||||||
{
|
{
|
||||||
aOutput.clear();
|
aOutput.clear();
|
||||||
std::vector<VECTOR2D> buffer;
|
std::vector<VECTOR2D> buffer;
|
||||||
GetPoly( buffer, double( aMinSegLen ), aMaxSegCount );
|
GetPoly( buffer, aMaxError );
|
||||||
|
|
||||||
aOutput.reserve( buffer.size() );
|
aOutput.reserve( buffer.size() );
|
||||||
|
|
||||||
for( const VECTOR2D& pt : buffer )
|
for( const VECTOR2D& pt : buffer )
|
||||||
aOutput.emplace_back( VECTOR2I( (int) pt.x, (int) pt.y ) );
|
aOutput.emplace_back( KiROUND( pt.x ), KiROUND( pt.y ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static double approx_int( double x )
|
||||||
|
{
|
||||||
|
const double d = 0.6744897501960817;
|
||||||
|
const double d4 = d * d * d * d;
|
||||||
|
return x / ( 1.0 - d + std::pow( d4 + x * x * 0.25, 0.25 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr double approx_inv_int( double x )
|
||||||
|
{
|
||||||
|
const double p = 0.39538816;
|
||||||
|
return x * ( 1.0 - p + std::sqrt( p * p + 0.25 * x * x ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen, int aMaxSegCount )
|
VECTOR2D BEZIER_POLY::eval( double t )
|
||||||
{
|
{
|
||||||
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;
|
|
||||||
VECTOR2D::extended_type minSegLen_sq = aMinSegLen * aMinSegLen;
|
|
||||||
|
|
||||||
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 omt = 1.0 - t;
|
||||||
double omt2 = omt * omt;
|
double omt2 = omt * omt;
|
||||||
|
|
||||||
|
if( m_ctrlPts.size() == 3 )
|
||||||
|
{
|
||||||
|
return omt2 * m_ctrlPts[0] + 2.0 * omt * t * m_ctrlPts[1] + t * t * m_ctrlPts[2];
|
||||||
|
}
|
||||||
|
else if( m_ctrlPts.size() == 4 )
|
||||||
|
{
|
||||||
double omt3 = omt * omt2;
|
double omt3 = omt * omt2;
|
||||||
double t2 = t * t;
|
double t2 = t * t;
|
||||||
double t3 = t * t2;
|
double t3 = t * t2;
|
||||||
|
return omt3 * m_ctrlPts[0] + 3.0 * t * omt2 * m_ctrlPts[1]
|
||||||
|
+ 3.0 * t2 * omt * m_ctrlPts[2] + t3 * m_ctrlPts[3];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxASSERT( false );
|
||||||
|
return VECTOR2D( 0, 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VECTOR2D vertex = omt3 * m_ctrlPts[0]
|
void BEZIER_POLY::getQuadPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
|
||||||
+ 3.0 * t * omt2 * m_ctrlPts[1]
|
{
|
||||||
+ 3.0 * t2 * omt * m_ctrlPts[2]
|
double ddx = 2 * m_ctrlPts[1].x - m_ctrlPts[0].x - m_ctrlPts[2].x;
|
||||||
+ t3 * m_ctrlPts[3];
|
double ddy = 2 * m_ctrlPts[1].y - m_ctrlPts[0].y - m_ctrlPts[2].y;
|
||||||
|
double u0 =
|
||||||
|
( m_ctrlPts[1].x - m_ctrlPts[0].x ) * ddx + ( m_ctrlPts[1].y - m_ctrlPts[0].y ) * ddy;
|
||||||
|
double u2 =
|
||||||
|
( m_ctrlPts[2].x - m_ctrlPts[1].x ) * ddx + ( m_ctrlPts[2].y - m_ctrlPts[1].y ) * ddy;
|
||||||
|
double cross =
|
||||||
|
( m_ctrlPts[2].x - m_ctrlPts[0].x ) * ddy - ( m_ctrlPts[2].y - m_ctrlPts[0].y ) * ddx;
|
||||||
|
double x0 = u0 / cross;
|
||||||
|
double x2 = u2 / cross;
|
||||||
|
double scale = std::abs( cross ) / ( std::hypot( ddx, ddy ) * std::abs( x2 - x0 ) );
|
||||||
|
|
||||||
// a minimal filter on the length of the segment being created:
|
double a0 = approx_int( x0 );
|
||||||
// The offset from last point:
|
double a2 = approx_int( x2 );
|
||||||
VECTOR2D delta = vertex - aOutput.back();
|
|
||||||
VECTOR2D::extended_type dist_sq = delta.SquaredEuclideanNorm();
|
|
||||||
|
|
||||||
if( dist_sq > minSegLen_sq )
|
int n = std::ceil( 0.5 * std::abs( a2 - a0 ) * std::sqrt( scale / aMaxError ) );
|
||||||
aOutput.push_back( vertex );
|
|
||||||
|
double v0 = approx_inv_int( a0 );
|
||||||
|
double v2 = approx_inv_int( a2 );
|
||||||
|
|
||||||
|
aOutput.emplace_back( m_ctrlPts[0] );
|
||||||
|
|
||||||
|
for( int ii = 0; ii < n; ++ii )
|
||||||
|
{
|
||||||
|
double u = approx_inv_int( a0 + ( a2 - a0 ) * ii / n );
|
||||||
|
double t = ( u - v0 ) / ( v2 - v0 );
|
||||||
|
aOutput.emplace_back( eval( t ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
aOutput.emplace_back( m_ctrlPts[2] );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int BEZIER_POLY::numberOfInflectionPoints()
|
||||||
|
{
|
||||||
|
VECTOR2D D21 = m_ctrlPts[1] - m_ctrlPts[0];
|
||||||
|
VECTOR2D D32 = m_ctrlPts[2] - m_ctrlPts[1];
|
||||||
|
VECTOR2D D43 = m_ctrlPts[3] - m_ctrlPts[2];
|
||||||
|
|
||||||
|
double cross1 = D21.Cross( D32 ) * D32.Cross( D43 );
|
||||||
|
double cross2 = D21.Cross( D32 ) * D21.Cross( D43 );
|
||||||
|
|
||||||
|
if( cross1 < 0.0 )
|
||||||
|
return 1;
|
||||||
|
else if( cross2 > 0.0 )
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bool b1 = D21.Dot( D32 ) > 0.0;
|
||||||
|
bool b2 = D32.Dot( D43 ) > 0.0;
|
||||||
|
|
||||||
|
if( b1 ^ b2 )
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
wxLogTrace( BEZIER_DBG, "numberOfInflectionPoints: rare case" );
|
||||||
|
// These are rare cases where there are potentially 2 or 0 inflection points.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double BEZIER_POLY::thirdControlPointDeviation()
|
||||||
|
{
|
||||||
|
VECTOR2D delta = m_ctrlPts[1] - m_ctrlPts[0];
|
||||||
|
double len_sq = delta.SquaredEuclideanNorm();
|
||||||
|
|
||||||
|
if( len_sq < 1e-6 )
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
double len = std::sqrt( len_sq );
|
||||||
|
double r = ( m_ctrlPts[1].y - m_ctrlPts[0].y ) / len;
|
||||||
|
double s = ( m_ctrlPts[0].x - m_ctrlPts[1].x ) / len;
|
||||||
|
double u = ( m_ctrlPts[1].x * m_ctrlPts[0].y - m_ctrlPts[0].x * m_ctrlPts[1].y ) / len;
|
||||||
|
|
||||||
|
return std::abs( r * m_ctrlPts[2].x + s * m_ctrlPts[2].y + u );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BEZIER_POLY::subdivide( double aT, BEZIER_POLY& aLeft, BEZIER_POLY& aRight )
|
||||||
|
{
|
||||||
|
if( m_ctrlPts.size() == 3 )
|
||||||
|
{
|
||||||
|
aLeft.m_ctrlPts[0] = m_ctrlPts[0];
|
||||||
|
aLeft.m_ctrlPts[1] = m_ctrlPts[0] + aT * ( m_ctrlPts[1] - m_ctrlPts[0] );
|
||||||
|
aLeft.m_ctrlPts[2] = eval( aT );
|
||||||
|
|
||||||
|
aRight.m_ctrlPts[2] = m_ctrlPts[2];
|
||||||
|
aRight.m_ctrlPts[1] = m_ctrlPts[1] + aT * ( m_ctrlPts[2] - m_ctrlPts[1] );
|
||||||
|
aRight.m_ctrlPts[0] = aLeft.m_ctrlPts[2];
|
||||||
|
}
|
||||||
|
else if( m_ctrlPts.size() == 4 )
|
||||||
|
{
|
||||||
|
VECTOR2D left_ctrl1 = m_ctrlPts[0] + aT * ( m_ctrlPts[1] - m_ctrlPts[0] );
|
||||||
|
VECTOR2D tmp = m_ctrlPts[1] + aT * ( m_ctrlPts[2] - m_ctrlPts[1] );
|
||||||
|
VECTOR2D left_ctrl2 = left_ctrl1 + aT * ( tmp - left_ctrl1 );
|
||||||
|
VECTOR2D right_ctrl2 = m_ctrlPts[2] + aT * ( m_ctrlPts[3] - m_ctrlPts[2] );
|
||||||
|
VECTOR2D right_ctrl1 = tmp + aT * ( right_ctrl2 - tmp );
|
||||||
|
VECTOR2D shared = left_ctrl2 + aT * ( right_ctrl1 - left_ctrl2 );
|
||||||
|
|
||||||
|
aLeft.m_ctrlPts[0] = m_ctrlPts[0];
|
||||||
|
aLeft.m_ctrlPts[1] = left_ctrl1;
|
||||||
|
aLeft.m_ctrlPts[2] = left_ctrl2;
|
||||||
|
aLeft.m_ctrlPts[3] = shared;
|
||||||
|
|
||||||
|
aRight.m_ctrlPts[3] = m_ctrlPts[3];
|
||||||
|
aRight.m_ctrlPts[2] = right_ctrl2;
|
||||||
|
aRight.m_ctrlPts[1] = right_ctrl1;
|
||||||
|
aRight.m_ctrlPts[0] = shared;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxASSERT( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BEZIER_POLY::recursiveSegmentation( std::vector<VECTOR2D>& aOutput, double aThreshhold )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "recursiveSegmentation with threshold %f", aThreshhold );
|
||||||
|
std::vector<BEZIER_POLY> stack;
|
||||||
|
|
||||||
|
BEZIER_POLY* bezier = nullptr;
|
||||||
|
BEZIER_POLY left( std::vector<VECTOR2D>(4) );
|
||||||
|
BEZIER_POLY right( std::vector<VECTOR2D>(4) );
|
||||||
|
|
||||||
|
stack.push_back( *this );
|
||||||
|
|
||||||
|
while( !stack.empty() )
|
||||||
|
{
|
||||||
|
bezier = &stack.back();
|
||||||
|
|
||||||
|
if( bezier->m_ctrlPts[3] == bezier->m_ctrlPts[0] )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "recursiveSegmentation dropping zero length segment" );
|
||||||
|
stack.pop_back();
|
||||||
|
}
|
||||||
|
else if( bezier->isFlat( aThreshhold ) )
|
||||||
|
{
|
||||||
|
aOutput.push_back( bezier->m_ctrlPts[3] );
|
||||||
|
stack.pop_back();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bezier->subdivide( 0.5, left, right );
|
||||||
|
*bezier = right;
|
||||||
|
stack.push_back( left );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( aOutput.back() != m_ctrlPts[3] )
|
wxLogTrace( BEZIER_DBG, "recursiveSegmentation generated %zu points", aOutput.size() );
|
||||||
aOutput.push_back( m_ctrlPts[3] );
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int BEZIER_POLY::findInflectionPoints( double& aT1, double& aT2 )
|
||||||
|
{
|
||||||
|
VECTOR2D A{ ( -m_ctrlPts[0].x + 3 * m_ctrlPts[1].x - 3 * m_ctrlPts[2].x + m_ctrlPts[3].x ),
|
||||||
|
( -m_ctrlPts[0].y + 3 * m_ctrlPts[1].y - 3 * m_ctrlPts[2].y + m_ctrlPts[3].y ) };
|
||||||
|
VECTOR2D B{ ( 3 * m_ctrlPts[0].x - 6 * m_ctrlPts[1].x + 3 * m_ctrlPts[2].x ),
|
||||||
|
( 3 * m_ctrlPts[0].y - 6 * m_ctrlPts[1].y + 3 * m_ctrlPts[2].y ) };
|
||||||
|
VECTOR2D C{ ( -3 * m_ctrlPts[0].x + 3 * m_ctrlPts[1].x ),
|
||||||
|
( -3 * m_ctrlPts[0].y + 3 * m_ctrlPts[1].y ) };
|
||||||
|
|
||||||
|
double a = 3 * A.Cross( B );
|
||||||
|
double b = 3 * A.Cross( C );
|
||||||
|
double c = B.Cross( C );
|
||||||
|
|
||||||
|
// Solve the quadratic equation a*t^2 + b*t + c = 0
|
||||||
|
double r2 = ( b * b - 4 * a * c );
|
||||||
|
|
||||||
|
aT1 = 0.0;
|
||||||
|
aT2 = 0.0;
|
||||||
|
|
||||||
|
if( r2 >= 0.0 && a != 0.0 )
|
||||||
|
{
|
||||||
|
double r = std::sqrt( r2 );
|
||||||
|
|
||||||
|
double t1 = ( ( -b + r ) / ( 2 * a ) );
|
||||||
|
double t2 = ( ( -b - r ) / ( 2 * a ) );
|
||||||
|
|
||||||
|
if( ( t1 > 0.0 && t1 < 1.0 ) && ( t2 > 0.0 && t2 < 1.0 ) )
|
||||||
|
{
|
||||||
|
if( t1 > t2 )
|
||||||
|
{
|
||||||
|
std::swap( t1, t2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
aT1 = t1;
|
||||||
|
aT2 = t2;
|
||||||
|
|
||||||
|
if( t2 - t1 > 0.00001 )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 2 inflection points at t1 = %f, t2 = %f", t1, t2 );
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t1 );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( t1 > 0.0 && t1 < 1.0 )
|
||||||
|
{
|
||||||
|
aT1 = t1;
|
||||||
|
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t1 );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if( t2 > 0.0 && t2 < 1.0 )
|
||||||
|
{
|
||||||
|
aT1 = t2;
|
||||||
|
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found 1 inflection point at t = %f", t2 );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found no inflection points" );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxLogTrace( BEZIER_DBG, "BEZIER_POLY Found no inflection points" );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BEZIER_POLY::cubicParabolicApprox( std::vector<VECTOR2D>& aOutput, double aMaxError )
|
||||||
|
{
|
||||||
|
std::vector<BEZIER_POLY> stack;
|
||||||
|
stack.push_back( std::vector<VECTOR2D>(4) );
|
||||||
|
stack.push_back( std::vector<VECTOR2D>(4) );
|
||||||
|
stack.push_back( std::vector<VECTOR2D>(4) );
|
||||||
|
stack.push_back( std::vector<VECTOR2D>(4) );
|
||||||
|
|
||||||
|
BEZIER_POLY* c = this;
|
||||||
|
BEZIER_POLY* b1 = &stack[0];
|
||||||
|
BEZIER_POLY* b2 = &stack[1];
|
||||||
|
|
||||||
|
for( ;; )
|
||||||
|
{
|
||||||
|
if( c->isNaN() )
|
||||||
|
{
|
||||||
|
wxLogDebug( "cubicParabolicApprox: NaN detected" );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( c->isFlat( aMaxError ) )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: General Flatness detected, adding %f %f", c->m_ctrlPts[3].x, c->m_ctrlPts[3].y );
|
||||||
|
// If the subsegment deviation satisfies the flatness criterion, store the last point and stop
|
||||||
|
aOutput.push_back( c->m_ctrlPts[3] );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the third control point deviation and the t values for subdivision
|
||||||
|
double d = c->thirdControlPointDeviation();
|
||||||
|
double t = 2 * std::sqrt( aMaxError / ( 3.0 * d ) ); // Forumla 2 in Hain et al.
|
||||||
|
|
||||||
|
wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: split point t = %f", t );
|
||||||
|
|
||||||
|
if( t > 1.0 )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: Invalid t value detected" );
|
||||||
|
// Case where the t value calculated is invalid, so use recursive subdivision
|
||||||
|
c->recursiveSegmentation( aOutput, aMaxError );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid t value to subdivide at that calculated value
|
||||||
|
c->subdivide( t, *b1, *b2 );
|
||||||
|
|
||||||
|
// First subsegment should have its deviation equal to flatness
|
||||||
|
if( b1->isFlat( aMaxError ) )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "cubicParabolicApprox: Flatness detected, adding %f %f", b1->m_ctrlPts[3].x, b1->m_ctrlPts[3].y );
|
||||||
|
aOutput.push_back( b1->m_ctrlPts[3] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// if not then use segment to handle any mathematical errors
|
||||||
|
b1->recursiveSegmentation( aOutput, aMaxError );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repeat the process for the left over subsegment
|
||||||
|
c = b2;
|
||||||
|
|
||||||
|
if( b1 == &stack.front() )
|
||||||
|
{
|
||||||
|
b1 = &stack[2];
|
||||||
|
b2 = &stack[3];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
b1 = &stack[0];
|
||||||
|
b2 = &stack[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BEZIER_POLY::getCubicPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
|
||||||
|
{
|
||||||
|
aOutput.push_back( m_ctrlPts[0] );
|
||||||
|
|
||||||
|
if( numberOfInflectionPoints() == 0 )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "getCubicPoly Short circuit to PA" );
|
||||||
|
// If no inflection points then apply PA on the full Bezier segment.
|
||||||
|
cubicParabolicApprox( aOutput, aMaxError );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If one or more inflection points then we will have to subdivide the curve
|
||||||
|
double t1, t2;
|
||||||
|
int numOfIfP = findInflectionPoints( t1, t2 );
|
||||||
|
|
||||||
|
if( numOfIfP == 2 )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "getCubicPoly: 2 inflection points" );
|
||||||
|
// Case when 2 inflection points then divide at the smallest one first
|
||||||
|
BEZIER_POLY sub1( std::vector<VECTOR2D>( 4 ) );
|
||||||
|
BEZIER_POLY tmp1( std::vector<VECTOR2D>( 4 ) );
|
||||||
|
BEZIER_POLY sub2( std::vector<VECTOR2D>( 4 ) );
|
||||||
|
BEZIER_POLY sub3( std::vector<VECTOR2D>( 4 ) );
|
||||||
|
|
||||||
|
subdivide( t1, sub1, tmp1 );
|
||||||
|
|
||||||
|
// Now find the second inflection point in the second curve and subdivide
|
||||||
|
numOfIfP = tmp1.findInflectionPoints( t1, t2 );
|
||||||
|
if( numOfIfP == 2 )
|
||||||
|
tmp1.subdivide( t1, sub2, sub3 );
|
||||||
|
else if( numOfIfP == 1 )
|
||||||
|
tmp1.subdivide( t1, sub2, sub3 );
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "getCubicPoly: 2nd inflection point not found" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use PA for first subsegment
|
||||||
|
sub1.cubicParabolicApprox( aOutput, aMaxError );
|
||||||
|
|
||||||
|
// Use Segment for the second (middle) subsegment
|
||||||
|
sub2.recursiveSegmentation( aOutput, aMaxError );
|
||||||
|
|
||||||
|
// Use PA for the third curve
|
||||||
|
sub3.cubicParabolicApprox( aOutput, aMaxError );
|
||||||
|
}
|
||||||
|
else if( numOfIfP == 1 )
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "getCubicPoly: 1 inflection point" );
|
||||||
|
// Case where there is one inflection point, subdivide once and use PA on both subsegments
|
||||||
|
BEZIER_POLY sub1( std::vector<VECTOR2D>( 4 ) );
|
||||||
|
BEZIER_POLY sub2( std::vector<VECTOR2D>( 4 ) );
|
||||||
|
subdivide( t1, sub1, sub2 );
|
||||||
|
sub1.cubicParabolicApprox( aOutput, aMaxError );
|
||||||
|
sub2.cubicParabolicApprox( aOutput, aMaxError );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxLogTrace( BEZIER_DBG, "getCubicPoly: Unknown inflection points" );
|
||||||
|
// Case where there is no inflection, use PA directly
|
||||||
|
cubicParabolicApprox( aOutput, aMaxError );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void BEZIER_POLY::GetPoly( std::vector<VECTOR2D>& aOutput, double aMaxError )
|
||||||
|
{
|
||||||
|
if( aMaxError <= 0.0 )
|
||||||
|
aMaxError = 10.0;
|
||||||
|
|
||||||
|
if( m_ctrlPts.size() == 3 )
|
||||||
|
{
|
||||||
|
getQuadPoly( aOutput, aMaxError );
|
||||||
|
}
|
||||||
|
else if( m_ctrlPts.size() == 4 )
|
||||||
|
{
|
||||||
|
getCubicPoly( aOutput, aMaxError );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxASSERT( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
wxLogTrace( BEZIER_DBG, "GetPoly generated %zu points", aOutput.size() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -395,11 +395,7 @@ bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the approximated Bezier shape is built
|
// Ensure the approximated Bezier shape is built
|
||||||
// a good value is between (Bezier curve width / 2) and (Bezier curve width)
|
graphic->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
// ( and at least 0.05 mm to avoid very small segments)
|
|
||||||
int min_segm_length = std::max( pcbIUScale.mmToIU( 0.05 ),
|
|
||||||
graphic->GetWidth() );
|
|
||||||
graphic->RebuildBezierToSegmentsPointsList( min_segm_length );
|
|
||||||
|
|
||||||
if( reverse )
|
if( reverse )
|
||||||
{
|
{
|
||||||
|
|
|
@ -513,7 +513,7 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataFromWindow()
|
||||||
|
|
||||||
m_item->SetLayer( ToLAYER_ID( layer ) );
|
m_item->SetLayer( ToLAYER_ID( layer ) );
|
||||||
|
|
||||||
m_item->RebuildBezierToSegmentsPointsList( m_item->GetWidth() );
|
m_item->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
|
|
||||||
if( m_item->IsOnCopperLayer() )
|
if( m_item->IsOnCopperLayer() )
|
||||||
m_item->SetNetCode( m_netSelector->GetSelectedNetcode() );
|
m_item->SetNetCode( m_netSelector->GetSelectedNetcode() );
|
||||||
|
|
|
@ -310,7 +310,7 @@ bool DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE::Run()
|
||||||
{
|
{
|
||||||
SHAPE_LINE_CHAIN asPoly;
|
SHAPE_LINE_CHAIN asPoly;
|
||||||
|
|
||||||
shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() );
|
shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
|
|
||||||
for( const VECTOR2I& pt : shape->GetBezierPoints() )
|
for( const VECTOR2I& pt : shape->GetBezierPoints() )
|
||||||
asPoly.Append( pt );
|
asPoly.Append( pt );
|
||||||
|
|
|
@ -105,7 +105,7 @@ bool GRAPHICS_CLEANER::isNullShape( PCB_SHAPE* aShape )
|
||||||
return aShape->GetPointCount() == 0;
|
return aShape->GetPointCount() == 0;
|
||||||
|
|
||||||
case SHAPE_T::BEZIER:
|
case SHAPE_T::BEZIER:
|
||||||
aShape->RebuildBezierToSegmentsPointsList( aShape->GetWidth() );
|
aShape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
|
|
||||||
// If the Bezier points list contains 2 points, it is equivalent to a segment
|
// If the Bezier points list contains 2 points, it is equivalent to a segment
|
||||||
if( aShape->GetBezierPoints().size() == 2 )
|
if( aShape->GetBezierPoints().size() == 2 )
|
||||||
|
|
|
@ -208,7 +208,7 @@ void GRAPHICS_IMPORTER_PCBNEW::AddSpline( const VECTOR2D& aStart, const VECTOR2D
|
||||||
spline->SetBezierC1( MapCoordinate( aBezierControl1 ));
|
spline->SetBezierC1( MapCoordinate( aBezierControl1 ));
|
||||||
spline->SetBezierC2( MapCoordinate( aBezierControl2 ));
|
spline->SetBezierC2( MapCoordinate( aBezierControl2 ));
|
||||||
spline->SetEnd( MapCoordinate( aEnd ) );
|
spline->SetEnd( MapCoordinate( aEnd ) );
|
||||||
spline->RebuildBezierToSegmentsPointsList( aStroke.GetWidth() );
|
spline->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
|
|
||||||
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
|
// If the spline is degenerated (i.e. a segment) add it as segment or discard it if
|
||||||
// null (i.e. very small) length
|
// null (i.e. very small) length
|
||||||
|
|
|
@ -462,8 +462,6 @@ PCB_IO_EASYEDAPRO_PARSER::ParseContour( nlohmann::json polyData, bool aInFill,
|
||||||
SHAPE_LINE_CHAIN result;
|
SHAPE_LINE_CHAIN result;
|
||||||
VECTOR2D prevPt;
|
VECTOR2D prevPt;
|
||||||
|
|
||||||
double bezierMinSegLen = polyData.size() < 300 ? aArcAccuracy : aArcAccuracy * 10;
|
|
||||||
|
|
||||||
for( int i = 0; i < polyData.size(); i++ )
|
for( int i = 0; i < polyData.size(); i++ )
|
||||||
{
|
{
|
||||||
nlohmann::json val = polyData.at( i );
|
nlohmann::json val = polyData.at( i );
|
||||||
|
@ -554,7 +552,7 @@ PCB_IO_EASYEDAPRO_PARSER::ParseContour( nlohmann::json polyData, bool aInFill,
|
||||||
BEZIER_POLY converter( ctrlPoints );
|
BEZIER_POLY converter( ctrlPoints );
|
||||||
|
|
||||||
std::vector<VECTOR2I> bezierPoints;
|
std::vector<VECTOR2I> bezierPoints;
|
||||||
converter.GetPoly( bezierPoints, bezierMinSegLen, 16 );
|
converter.GetPoly( bezierPoints, aArcAccuracy );
|
||||||
|
|
||||||
result.Append( bezierPoints );
|
result.Append( bezierPoints );
|
||||||
|
|
||||||
|
|
|
@ -1078,7 +1078,7 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PCB_SHAPE& aShape
|
||||||
aShape.GetBezierC2(), aShape.GetEnd() };
|
aShape.GetBezierC2(), aShape.GetEnd() };
|
||||||
BEZIER_POLY converter( ctrlPoints );
|
BEZIER_POLY converter( ctrlPoints );
|
||||||
std::vector<VECTOR2I> points;
|
std::vector<VECTOR2I> points;
|
||||||
converter.GetPoly( points, aShape.GetStroke().GetWidth() );
|
converter.GetPoly( points, ARC_HIGH_DEF );
|
||||||
|
|
||||||
wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" );
|
wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" );
|
||||||
addXY( point_node, points[0] );
|
addXY( point_node, points[0] );
|
||||||
|
|
|
@ -2835,6 +2835,7 @@ PCB_SHAPE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_SHAPE( BOARD_ITEM* aParent )
|
||||||
shape->SetBezierC1( parseXY());
|
shape->SetBezierC1( parseXY());
|
||||||
shape->SetBezierC2( parseXY());
|
shape->SetBezierC2( parseXY());
|
||||||
shape->SetEnd( parseXY() );
|
shape->SetEnd( parseXY() );
|
||||||
|
shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
NeedRIGHT();
|
NeedRIGHT();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -1937,9 +1937,9 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer )
|
||||||
pointCtrl.push_back( aShape->GetEnd() );
|
pointCtrl.push_back( aShape->GetEnd() );
|
||||||
|
|
||||||
BEZIER_POLY converter( pointCtrl );
|
BEZIER_POLY converter( pointCtrl );
|
||||||
converter.GetPoly( output, thickness );
|
converter.GetPoly( output, m_maxError );
|
||||||
|
|
||||||
m_gal->DrawSegmentChain( output, thickness );
|
m_gal->DrawSegmentChain( aShape->GetBezierPoints(), thickness );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1947,12 +1947,17 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer )
|
||||||
m_gal->SetIsStroke( thickness > 0 );
|
m_gal->SetIsStroke( thickness > 0 );
|
||||||
m_gal->SetLineWidth( thickness );
|
m_gal->SetLineWidth( thickness );
|
||||||
|
|
||||||
// Use thickness as filter value to convert the curve to polyline when the curve
|
if( aShape->GetBezierPoints().size() > 2 )
|
||||||
// is not supported
|
{
|
||||||
|
m_gal->DrawPolygon( aShape->GetBezierPoints() );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
m_gal->DrawCurve( VECTOR2D( aShape->GetStart() ),
|
m_gal->DrawCurve( VECTOR2D( aShape->GetStart() ),
|
||||||
VECTOR2D( aShape->GetBezierC1() ),
|
VECTOR2D( aShape->GetBezierC1() ),
|
||||||
VECTOR2D( aShape->GetBezierC2() ),
|
VECTOR2D( aShape->GetBezierC2() ),
|
||||||
VECTOR2D( aShape->GetEnd() ), thickness );
|
VECTOR2D( aShape->GetEnd() ), m_maxError );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -267,6 +267,7 @@ bool PCB_SHAPE::Deserialize( const google::protobuf::Any &aContainer )
|
||||||
SetBezierC1( kiapi::common::UnpackVector2( msg.bezier().control1() ) );
|
SetBezierC1( kiapi::common::UnpackVector2( msg.bezier().control1() ) );
|
||||||
SetBezierC2( kiapi::common::UnpackVector2( msg.bezier().control2() ) );
|
SetBezierC2( kiapi::common::UnpackVector2( msg.bezier().control2() ) );
|
||||||
SetEnd( kiapi::common::UnpackVector2( msg.bezier().end() ) );
|
SetEnd( kiapi::common::UnpackVector2( msg.bezier().end() ) );
|
||||||
|
RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -568,7 +569,7 @@ void PCB_SHAPE::Mirror( const VECTOR2I& aCentre, bool aMirrorAroundXAxis )
|
||||||
std::swap( m_start, m_end );
|
std::swap( m_start, m_end );
|
||||||
|
|
||||||
if( GetShape() == SHAPE_T::BEZIER )
|
if( GetShape() == SHAPE_T::BEZIER )
|
||||||
RebuildBezierToSegmentsPointsList( GetWidth() );
|
RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -250,7 +250,7 @@ void TEARDROP_MANAGER::computeCurvedForRoundShape( const TEARDROP_PARAMETERS& aP
|
||||||
|
|
||||||
std::vector<VECTOR2I> curve_pts;
|
std::vector<VECTOR2I> curve_pts;
|
||||||
curve_pts.reserve( aParams.m_CurveSegCount );
|
curve_pts.reserve( aParams.m_CurveSegCount );
|
||||||
BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount );
|
BEZIER_POLY( pts[1], tangentB, tangentC, pts[2] ).GetPoly( curve_pts, ARC_HIGH_DEF );
|
||||||
|
|
||||||
for( VECTOR2I& corner: curve_pts )
|
for( VECTOR2I& corner: curve_pts )
|
||||||
aPoly.push_back( corner );
|
aPoly.push_back( corner );
|
||||||
|
@ -258,7 +258,7 @@ void TEARDROP_MANAGER::computeCurvedForRoundShape( const TEARDROP_PARAMETERS& aP
|
||||||
aPoly.push_back( pts[3] );
|
aPoly.push_back( pts[3] );
|
||||||
|
|
||||||
curve_pts.clear();
|
curve_pts.clear();
|
||||||
BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount );
|
BEZIER_POLY( pts[4], tangentE, tangentA, pts[0] ).GetPoly( curve_pts, ARC_HIGH_DEF );
|
||||||
|
|
||||||
for( VECTOR2I& corner: curve_pts )
|
for( VECTOR2I& corner: curve_pts )
|
||||||
aPoly.push_back( corner );
|
aPoly.push_back( corner );
|
||||||
|
@ -319,7 +319,7 @@ void TEARDROP_MANAGER::computeCurvedForRectShape( const TEARDROP_PARAMETERS& aPa
|
||||||
ctrl2.x += bias.x;
|
ctrl2.x += bias.x;
|
||||||
ctrl2.y += bias.y;
|
ctrl2.y += bias.y;
|
||||||
|
|
||||||
BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount );
|
BEZIER_POLY( aPts[1], ctrl1, ctrl2, aPts[2] ).GetPoly( curve_pts, ARC_HIGH_DEF );
|
||||||
|
|
||||||
for( VECTOR2I& corner: curve_pts )
|
for( VECTOR2I& corner: curve_pts )
|
||||||
aPoly.push_back( corner );
|
aPoly.push_back( corner );
|
||||||
|
@ -347,7 +347,7 @@ void TEARDROP_MANAGER::computeCurvedForRectShape( const TEARDROP_PARAMETERS& aPa
|
||||||
ctrl2.x += bias.x;
|
ctrl2.x += bias.x;
|
||||||
ctrl2.y += bias.y;
|
ctrl2.y += bias.y;
|
||||||
|
|
||||||
BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, 0, aParams.m_CurveSegCount );
|
BEZIER_POLY( aPts[4], ctrl1, ctrl2, aPts[0] ).GetPoly( curve_pts, ARC_HIGH_DEF );
|
||||||
|
|
||||||
for( VECTOR2I& corner: curve_pts )
|
for( VECTOR2I& corner: curve_pts )
|
||||||
aPoly.push_back( corner );
|
aPoly.push_back( corner );
|
||||||
|
|
|
@ -1407,7 +1407,7 @@ void PCB_POINT_EDITOR::updateItem( BOARD_COMMIT* aCommit )
|
||||||
else if( isModified( m_editPoints->Point( BEZIER_END ) ) )
|
else if( isModified( m_editPoints->Point( BEZIER_END ) ) )
|
||||||
shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() );
|
shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() );
|
||||||
|
|
||||||
shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() );
|
shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // suppress warnings
|
default: // suppress warnings
|
||||||
|
|
|
@ -3,14 +3,17 @@
|
||||||
"3dviewports": [],
|
"3dviewports": [],
|
||||||
"design_settings": {
|
"design_settings": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"board_outline_line_width": 0.049999999999999996,
|
"apply_defaults_to_fp_fields": false,
|
||||||
"copper_line_width": 0.19999999999999998,
|
"apply_defaults_to_fp_shapes": false,
|
||||||
|
"apply_defaults_to_fp_text": false,
|
||||||
|
"board_outline_line_width": 0.05,
|
||||||
|
"copper_line_width": 0.2,
|
||||||
"copper_text_italic": false,
|
"copper_text_italic": false,
|
||||||
"copper_text_size_h": 1.5,
|
"copper_text_size_h": 1.5,
|
||||||
"copper_text_size_v": 1.5,
|
"copper_text_size_v": 1.5,
|
||||||
"copper_text_thickness": 0.3,
|
"copper_text_thickness": 0.3,
|
||||||
"copper_text_upright": true,
|
"copper_text_upright": true,
|
||||||
"courtyard_line_width": 0.049999999999999996,
|
"courtyard_line_width": 0.05,
|
||||||
"dimension_precision": 1,
|
"dimension_precision": 1,
|
||||||
"dimension_units": 2,
|
"dimension_units": 2,
|
||||||
"dimensions": {
|
"dimensions": {
|
||||||
|
@ -21,13 +24,13 @@
|
||||||
"text_position": 0,
|
"text_position": 0,
|
||||||
"units_format": 1
|
"units_format": 1
|
||||||
},
|
},
|
||||||
"fab_line_width": 0.09999999999999999,
|
"fab_line_width": 0.1,
|
||||||
"fab_text_italic": false,
|
"fab_text_italic": false,
|
||||||
"fab_text_size_h": 1.0,
|
"fab_text_size_h": 1.0,
|
||||||
"fab_text_size_v": 1.0,
|
"fab_text_size_v": 1.0,
|
||||||
"fab_text_thickness": 0.15,
|
"fab_text_thickness": 0.15,
|
||||||
"fab_text_upright": true,
|
"fab_text_upright": true,
|
||||||
"other_line_width": 0.09999999999999999,
|
"other_line_width": 0.1,
|
||||||
"other_text_italic": false,
|
"other_text_italic": false,
|
||||||
"other_text_size_h": 1.0,
|
"other_text_size_h": 1.0,
|
||||||
"other_text_size_v": 1.0,
|
"other_text_size_v": 1.0,
|
||||||
|
@ -46,7 +49,7 @@
|
||||||
"silk_text_upright": true,
|
"silk_text_upright": true,
|
||||||
"zones": {
|
"zones": {
|
||||||
"45_degree_only": false,
|
"45_degree_only": false,
|
||||||
"min_clearance": 0.19999999999999998
|
"min_clearance": 0.2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"diff_pair_dimensions": [
|
"diff_pair_dimensions": [
|
||||||
|
@ -73,9 +76,12 @@
|
||||||
"duplicate_footprints": "warning",
|
"duplicate_footprints": "warning",
|
||||||
"extra_footprint": "warning",
|
"extra_footprint": "warning",
|
||||||
"footprint": "error",
|
"footprint": "error",
|
||||||
|
"footprint_symbol_mismatch": "warning",
|
||||||
"footprint_type_mismatch": "ignore",
|
"footprint_type_mismatch": "ignore",
|
||||||
"hole_clearance": "error",
|
"hole_clearance": "error",
|
||||||
"hole_near_hole": "error",
|
"hole_near_hole": "error",
|
||||||
|
"hole_to_hole": "warning",
|
||||||
|
"holes_co_located": "warning",
|
||||||
"invalid_outline": "error",
|
"invalid_outline": "error",
|
||||||
"isolated_copper": "warning",
|
"isolated_copper": "warning",
|
||||||
"item_on_disabled_layer": "error",
|
"item_on_disabled_layer": "error",
|
||||||
|
@ -120,17 +126,17 @@
|
||||||
"min_copper_edge_clearance": 0.01,
|
"min_copper_edge_clearance": 0.01,
|
||||||
"min_hole_clearance": 0.0,
|
"min_hole_clearance": 0.0,
|
||||||
"min_hole_to_hole": 0.25,
|
"min_hole_to_hole": 0.25,
|
||||||
"min_microvia_diameter": 0.19999999999999998,
|
"min_microvia_diameter": 0.2,
|
||||||
"min_microvia_drill": 0.09999999999999999,
|
"min_microvia_drill": 0.1,
|
||||||
"min_resolved_spokes": 2,
|
"min_resolved_spokes": 2,
|
||||||
"min_silk_clearance": 0.0,
|
"min_silk_clearance": 0.0,
|
||||||
"min_text_height": 0.7999999999999999,
|
"min_text_height": 0.8,
|
||||||
"min_text_thickness": 0.08,
|
"min_text_thickness": 0.08,
|
||||||
"min_through_hole_diameter": 0.3,
|
"min_through_hole_diameter": 0.3,
|
||||||
"min_track_width": 0.19999999999999998,
|
"min_track_width": 0.2,
|
||||||
"min_via_annular_width": 0.049999999999999996,
|
"min_via_annular_width": 0.05,
|
||||||
"min_via_annulus": 0.049999999999999996,
|
"min_via_annulus": 0.049999999999999996,
|
||||||
"min_via_diameter": 0.39999999999999997,
|
"min_via_diameter": 0.4,
|
||||||
"solder_mask_to_copper_clearance": 0.0,
|
"solder_mask_to_copper_clearance": 0.0,
|
||||||
"use_height_for_length_calcs": true
|
"use_height_for_length_calcs": true
|
||||||
},
|
},
|
||||||
|
@ -184,6 +190,32 @@
|
||||||
1.0,
|
1.0,
|
||||||
2.0
|
2.0
|
||||||
],
|
],
|
||||||
|
"tuning_pattern_settings": {
|
||||||
|
"diff_pair_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 1.0
|
||||||
|
},
|
||||||
|
"diff_pair_skew_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
},
|
||||||
|
"single_track_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
}
|
||||||
|
},
|
||||||
"via_dimensions": [
|
"via_dimensions": [
|
||||||
{
|
{
|
||||||
"diameter": 0.85,
|
"diameter": 0.85,
|
||||||
|
@ -193,6 +225,13 @@
|
||||||
"zones_allow_external_fillets": false,
|
"zones_allow_external_fillets": false,
|
||||||
"zones_use_no_outline": true
|
"zones_use_no_outline": true
|
||||||
},
|
},
|
||||||
|
"ipc2581": {
|
||||||
|
"dist": "",
|
||||||
|
"distpn": "",
|
||||||
|
"internal_id": "",
|
||||||
|
"mfg": "",
|
||||||
|
"mpn": ""
|
||||||
|
},
|
||||||
"layer_presets": [
|
"layer_presets": [
|
||||||
{
|
{
|
||||||
"activeLayer": -2,
|
"activeLayer": -2,
|
||||||
|
|
Loading…
Reference in New Issue