diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index 1b5149b589..58d71e4c0e 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -104,7 +104,7 @@ static const wxChar EnableLibWithText[] = wxT( "EnableLibWithText" ); static const wxChar EnableEeschemaPrintCairo[] = wxT( "EnableEeschemaPrintCairo" ); static const wxChar DisambiguationTime[] = wxT( "DisambiguationTime" ); 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 OcePluginAngularDeflection[] = wxT( "OcePluginAngularDeflection" ); static const wxChar TriangulateSimplificationLevel[] = wxT( "TriangulateSimplificationLevel" ); @@ -257,7 +257,7 @@ ADVANCED_CFG::ADVANCED_CFG() m_PcbSelectionVisibilityRatio = 1.0; - m_MinimumSegmentLength = 50; + m_FontErrorSize = 16; m_OcePluginLinearDeflection = 0.14; m_OcePluginAngularDeflection = 30; @@ -469,9 +469,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg ) &m_PcbSelectionVisibilityRatio, m_PcbSelectionVisibilityRatio, 0.0, 1.0 ) ); - configParams.push_back( new PARAM_CFG_INT( true, AC_KEYS::MinimumSegmentLength, - &m_MinimumSegmentLength, - m_MinimumSegmentLength, 10, 1000 ) ); + configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::FontErrorSize, + &m_FontErrorSize, + m_FontErrorSize, 0.01, 100 ) ); configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::OcePluginLinearDeflection, &m_OcePluginLinearDeflection, diff --git a/common/eda_shape.cpp b/common/eda_shape.cpp index b0e7a793fd..919c4d0515 100644 --- a/common/eda_shape.cpp +++ b/common/eda_shape.cpp @@ -352,6 +352,7 @@ void EDA_SHAPE::scale( double aScale ) scalePt( m_end ); scalePt( m_bezierC1 ); scalePt( m_bezierC2 ); + RebuildBezierToSegmentsPointsList( m_stroke.GetWidth() / 2 ); break; default: @@ -488,12 +489,7 @@ void EDA_SHAPE::flip( const VECTOR2I& aCentre, bool aFlipLeftRight ) m_bezierC2.y = aCentre.y - ( m_bezierC2.y - aCentre.y ); } - // Rebuild the poly points shape - { - std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end }; - BEZIER_POLY converter( ctrlPoints ); - converter.GetPoly( m_bezierPoints, m_stroke.GetWidth() ); - } + RebuildBezierToSegmentsPointsList( m_stroke.GetWidth() / 2 ); break; 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 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 - m_bezierPoints = buildBezierToSegmentsPointsList( aMinSegLen ); - - // 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(); - } - } + m_bezierPoints = buildBezierToSegmentsPointsList( aMaxError ); } -const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMinSegLen ) const +const std::vector<VECTOR2I> EDA_SHAPE::buildBezierToSegmentsPointsList( int aMaxError ) const { std::vector<VECTOR2I> bezierPoints; // Rebuild the m_BezierPoints vertex list that approximate the Bezier curve std::vector<VECTOR2I> ctrlPoints = { m_start, m_bezierC1, m_bezierC2, m_end }; BEZIER_POLY converter( ctrlPoints ); - converter.GetPoly( bezierPoints, aMinSegLen ); + converter.GetPoly( bezierPoints, aMaxError ); return bezierPoints; } @@ -930,16 +914,25 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const } case SHAPE_T::BEZIER: - const_cast<EDA_SHAPE*>( this )->RebuildBezierToSegmentsPointsList( GetWidth() ); + { + const std::vector<VECTOR2I>* pts = &m_bezierPoints; + std::vector<VECTOR2I> updatedBezierPoints; - for( unsigned int i= 1; i < m_bezierPoints.size(); i++) + if( m_bezierPoints.empty() ) { - if( TestSegmentHit( aPosition, m_bezierPoints[ i - 1], m_bezierPoints[i], maxdist ) ) + 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 false; - + } case SHAPE_T::SEGMENT: 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 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]; - VECTOR2I vertexNext = m_bezierPoints[ii]; + BEZIER_POLY converter( m_start, m_bezierC1, m_bezierC2, m_end ); + 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 if( arect.Contains( vertex ) ) @@ -1278,7 +1279,7 @@ std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh case SHAPE_T::BEZIER: { - std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( width ); + std::vector<VECTOR2I> bezierPoints = buildBezierToSegmentsPointsList( width / 2 ); VECTOR2I start_pt = bezierPoints[0]; for( unsigned int jj = 1; jj < bezierPoints.size(); jj++ ) @@ -1381,7 +1382,7 @@ void EDA_SHAPE::beginEdit( const VECTOR2I& aPosition ) SetBezierC2( aPosition ); m_editState = 1; - RebuildBezierToSegmentsPointsList( GetWidth() ); + RebuildBezierToSegmentsPointsList( GetWidth() / 2 ); break; case SHAPE_T::POLY: @@ -1463,7 +1464,7 @@ void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition ) case 3: SetBezierC2( aPosition ); break; } - RebuildBezierToSegmentsPointsList( GetWidth() ); + RebuildBezierToSegmentsPointsList( GetWidth() / 2 ); } break; @@ -1792,7 +1793,7 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance std::vector<VECTOR2I> ctrlPts = { GetStart(), GetBezierC1(), GetBezierC2(), GetEnd() }; BEZIER_POLY converter( ctrlPts ); std::vector<VECTOR2I> poly; - converter.GetPoly( poly, GetWidth() ); + converter.GetPoly( poly, aError ); for( unsigned ii = 1; ii < poly.size(); ii++ ) TransformOvalToPolygon( aBuffer, poly[ii - 1], poly[ii], width, aError, aErrorLoc ); diff --git a/common/font/outline_decomposer.cpp b/common/font/outline_decomposer.cpp index 7dd1de483a..880eb80f57 100644 --- a/common/font/outline_decomposer.cpp +++ b/common/font/outline_decomposer.cpp @@ -111,7 +111,8 @@ int OUTLINE_DECOMPOSER::cubicTo( const FT_Vector* aFirstControlPoint, bezier.push_back( toVector2D( aEndPoint ) ); 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 ) 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 { // -1 == counterclockwise, 1 == clockwise diff --git a/common/font/outline_font.cpp b/common/font/outline_font.cpp index d11c590eba..85a8337481 100644 --- a/common/font/outline_font.cpp +++ b/common/font/outline_font.cpp @@ -95,7 +95,7 @@ FT_Error OUTLINE_FONT::loadFace( const wxString& aFontFileName, int aFaceIndex ) // m_face = handle to face object // 0 = char width in 1/64th of points ( 0 = same as char height ) // 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 ) FT_Set_Char_Size( m_face, 0, faceSize(), GLYPH_RESOLUTION, 0 ); } diff --git a/common/io/easyeda/easyeda_parser_base.cpp b/common/io/easyeda/easyeda_parser_base.cpp index 7e800d1bc1..c2af30d8d3 100644 --- a/common/io/easyeda/easyeda_parser_base.cpp +++ b/common/io/easyeda/easyeda_parser_base.cpp @@ -266,7 +266,7 @@ EASYEDA_PARSER_BASE::ParseLineChains( const wxString& data, int aArcMinSegLen, b BEZIER_POLY converter( ctrlPoints ); std::vector<VECTOR2I> bezierPoints; - converter.GetPoly( bezierPoints, aArcMinSegLen, 16 ); + converter.GetPoly( bezierPoints, aArcMinSegLen ); chain.Append( bezierPoints ); diff --git a/common/plotters/plotter.cpp b/common/plotters/plotter.cpp index 5085d1cc48..a9a50466a7 100644 --- a/common/plotters/plotter.cpp +++ b/common/plotters/plotter.cpp @@ -244,7 +244,7 @@ void PLOTTER::BezierCurve( const VECTOR2I& aStart, const VECTOR2I& aControl1, BEZIER_POLY bezier_converter( ctrlPoints ); std::vector<VECTOR2I> approxPoints; - bezier_converter.GetPoly( approxPoints, minSegLen ); + bezier_converter.GetPoly( approxPoints, aTolerance ); SetCurrentLineWidth( aLineThickness ); MoveTo( aStart ); diff --git a/eeschema/import_gfx/graphics_importer_lib_symbol.cpp b/eeschema/import_gfx/graphics_importer_lib_symbol.cpp index 54bd14cba2..adf95516bc 100644 --- a/eeschema/import_gfx/graphics_importer_lib_symbol.cpp +++ b/eeschema/import_gfx/graphics_importer_lib_symbol.cpp @@ -217,7 +217,7 @@ void GRAPHICS_IMPORTER_LIB_SYMBOL::AddSpline( const VECTOR2D& aStart, spline->SetBezierC1( MapCoordinate( aBezierControl1 ) ); spline->SetBezierC2( MapCoordinate( aBezierControl2 ) ); 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 // null (i.e. very small) length diff --git a/eeschema/import_gfx/graphics_importer_sch.cpp b/eeschema/import_gfx/graphics_importer_sch.cpp index f5fa2d8830..85c172fb36 100644 --- a/eeschema/import_gfx/graphics_importer_sch.cpp +++ b/eeschema/import_gfx/graphics_importer_sch.cpp @@ -207,7 +207,7 @@ void GRAPHICS_IMPORTER_SCH::AddSpline( const VECTOR2D& aStart, spline->SetBezierC1( MapCoordinate( aBezierControl1 ) ); spline->SetBezierC2( MapCoordinate( aBezierControl2 ) ); 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 // null (i.e. very small) length diff --git a/eeschema/sch_io/altium/sch_io_altium.cpp b/eeschema/sch_io/altium/sch_io_altium.cpp index 67b997686d..146300d55b 100644 --- a/eeschema/sch_io/altium/sch_io_altium.cpp +++ b/eeschema/sch_io/altium/sch_io_altium.cpp @@ -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->RebuildBezierToSegmentsPointsList( bezier->GetWidth() / 2 ); } } } @@ -2350,7 +2351,7 @@ void SCH_IO_ALTIUM::ParseEllipticalArc( const std::map<wxString, wxString>& aPro schbezier->SetBezierC2( bezier.C2 ); schbezier->SetEnd( bezier.End ); schbezier->SetStroke( STROKE_PARAMS( elem.LineWidth, LINE_STYLE::SOLID ) ); - schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth ); + schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth / 2 ); 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 ); - schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth ); + schbezier->RebuildBezierToSegmentsPointsList( elem.LineWidth / 2 ); } } } diff --git a/eeschema/sch_io/kicad_legacy/sch_io_kicad_legacy_lib_cache.cpp b/eeschema/sch_io/kicad_legacy/sch_io_kicad_legacy_lib_cache.cpp index b030d203fa..f76a5e2a3c 100644 --- a/eeschema/sch_io/kicad_legacy/sch_io_kicad_legacy_lib_cache.cpp +++ b/eeschema/sch_io/kicad_legacy/sch_io_kicad_legacy_lib_cache.cpp @@ -1381,7 +1381,7 @@ SCH_SHAPE* SCH_IO_KICAD_LEGACY_LIB_CACHE::loadBezier( LINE_READER& aReader ) pt.y = -schIUScale.MilsToIU( parseInt( aReader, line, &line ) ); bezier->SetEnd( pt ); - bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() ); + bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() / 2 ); if( *line != 0 ) bezier->SetFillMode( parseFillMode( aReader, line, &line ) ); diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp index bcab5f2b65..8c6e51dba3 100644 --- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp +++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp @@ -1315,7 +1315,7 @@ SCH_SHAPE* SCH_IO_KICAD_SEXPR_PARSER::parseSymbolBezier() } } - bezier->RebuildBezierToSegmentsPointsList( bezier->GetWidth() ); + bezier->RebuildBezierToSegmentsPointsList( bezier->GetPenWidth() / 2 ); return bezier.release(); } diff --git a/eeschema/tools/ee_point_editor.cpp b/eeschema/tools/ee_point_editor.cpp index 4b55474d09..b59d33eada 100644 --- a/eeschema/tools/ee_point_editor.cpp +++ b/eeschema/tools/ee_point_editor.cpp @@ -739,7 +739,7 @@ void EE_POINT_EDITOR::updateParentItem( bool aSnapToGrid ) const shape->SetBezierC2( m_editPoints->Point( BEZIER_CTRL_PT2 ).GetPosition() ); shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() ); - shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() ); + shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() / 2 ); break; default: diff --git a/include/advanced_config.h b/include/advanced_config.h index 644ea8e47b..a54009e464 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -509,14 +509,14 @@ public: double m_PcbSelectionVisibilityRatio; /** - * Length of the minimum segment for the outline decomposer. This is in IU, so - * it is nm in pcbnew and 100nm in eeschema. + * Deviation between font's bezier curve ideal and the poligonized curve. This + * is 1/16 of the font's internal units. * - * Setting name: "MinimumSegmentLength" - * Valid values: 10 to 1000 - * Default value: 50 + * Setting name: "FontErrorSize" + * Valid values: 0.01 to 100 + * Default value: 8 */ - int m_MinimumSegmentLength; + double m_FontErrorSize; /** * OCE (STEP/IGES) 3D Plugin Tesselation Linear Deflection diff --git a/include/eda_shape.h b/include/eda_shape.h index 7b866892d0..9d9f6dac4f 100644 --- a/include/eda_shape.h +++ b/include/eda_shape.h @@ -306,11 +306,9 @@ public: * * Has meaning only for BEZIER shape. * - * @param aMinSegLen is the min length of segments approximating the bezier. The shape's last - * 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. + * @param aMinSegLen is the max deviation between the polyline and the curve */ - void RebuildBezierToSegmentsPointsList( int aMinSegLen ); + void RebuildBezierToSegmentsPointsList( int aMaxError ); /** * 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 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 ); bool continueEdit( const VECTOR2I& aPosition ); diff --git a/include/font/glyph.h b/include/font/glyph.h index 7e756ecd13..caea128c3e 100644 --- a/include/font/glyph.h +++ b/include/font/glyph.h @@ -40,12 +40,6 @@ 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 { diff --git a/include/font/outline_decomposer.h b/include/font/outline_decomposer.h index 8a8aacefba..2d988f071a 100644 --- a/include/font/outline_decomposer.h +++ b/include/font/outline_decomposer.h @@ -41,6 +41,13 @@ 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 { std::vector<VECTOR2D> m_Points; @@ -70,13 +77,6 @@ private: 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, * or 0 if the winding can't be determined. diff --git a/libs/kimath/include/bezier_curves.h b/libs/kimath/include/bezier_curves.h index 6101d13423..b5e1fcbe3c 100644 --- a/libs/kimath/include/bezier_curves.h +++ b/libs/kimath/include/bezier_curves.h @@ -52,15 +52,32 @@ public: * Convert a Bezier curve to a polygon. * * @param aOutput will be used as an output vector storing polygon points. - * @param aMinSegLen is the min dist between 2 successive points. - * It can be used to reduce the number of points. - * (the last point is always generated) - * aMaxSegCount is the max number of segments created + * @param aMaxError maximum error in IU between the curve and the polygon. */ - void GetPoly( std::vector<VECTOR2I>& aOutput, int aMinSegLen = 0, int aMaxSegCount = 32 ); - void GetPoly( std::vector<VECTOR2D>& aOutput, double aMinSegLen = 0.0, int aMaxSegCount = 32 ); + void GetPoly( std::vector<VECTOR2I>& aOutput, int aMaxError = 10 ); + void GetPoly( std::vector<VECTOR2D>& aOutput, double aMaxError = 10.0 ); 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; ///< Control points diff --git a/libs/kimath/src/bezier_curves.cpp b/libs/kimath/src/bezier_curves.cpp index a52304c162..e151a8f427 100644 --- a/libs/kimath/src/bezier_curves.cpp +++ b/libs/kimath/src/bezier_curves.cpp @@ -21,16 +21,69 @@ * 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 <geometry/ellipse.h> #include <trigo.h> #include <math/vector2d.h> // for VECTOR2D, operator*, VECTOR2 #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, 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(); std::vector<VECTOR2D> buffer; - GetPoly( buffer, double( aMinSegLen ), aMaxSegCount ); + GetPoly( buffer, aMaxError ); aOutput.reserve( buffer.size() ); 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; + double omt = 1.0 - t; + double omt2 = omt * omt; - 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 ) + if( m_ctrlPts.size() == 3 ) { - for( int ii = 1; ii < aMaxSegCount; ii++ ) + 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 t2 = t * t; + 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 ); + } +} + +void BEZIER_POLY::getQuadPoly( std::vector<VECTOR2D>& aOutput, double aMaxError ) +{ + double ddx = 2 * m_ctrlPts[1].x - m_ctrlPts[0].x - m_ctrlPts[2].x; + 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 ) ); + + double a0 = approx_int( x0 ); + double a2 = approx_int( x2 ); + + int n = std::ceil( 0.5 * std::abs( a2 - a0 ) * std::sqrt( scale / aMaxError ) ); + + 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] ) { - double t = dt * ii; - double omt = 1.0 - t; - double omt2 = omt * omt; - double omt3 = omt * omt2; - double t2 = t * t; - double t3 = t * t2; - - VECTOR2D vertex = omt3 * m_ctrlPts[0] - + 3.0 * t * omt2 * m_ctrlPts[1] - + 3.0 * t2 * omt * m_ctrlPts[2] - + t3 * m_ctrlPts[3]; - - // a minimal filter on the length of the segment being created: - // The offset from last point: - VECTOR2D delta = vertex - aOutput.back(); - VECTOR2D::extended_type dist_sq = delta.SquaredEuclideanNorm(); - - if( dist_sq > minSegLen_sq ) - aOutput.push_back( vertex ); + 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] ) - aOutput.push_back( m_ctrlPts[3] ); + wxLogTrace( BEZIER_DBG, "recursiveSegmentation generated %zu points", aOutput.size() ); +} + + +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() ); } diff --git a/pcbnew/convert_shape_list_to_polygon.cpp b/pcbnew/convert_shape_list_to_polygon.cpp index 98303d26da..ea9834c17a 100644 --- a/pcbnew/convert_shape_list_to_polygon.cpp +++ b/pcbnew/convert_shape_list_to_polygon.cpp @@ -395,11 +395,7 @@ bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_ } // Ensure the approximated Bezier shape is built - // a good value is between (Bezier curve width / 2) and (Bezier curve width) - // ( 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 ); + graphic->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); if( reverse ) { diff --git a/pcbnew/dialogs/dialog_shape_properties.cpp b/pcbnew/dialogs/dialog_shape_properties.cpp index 042ea1cbc8..3f4ee3de18 100644 --- a/pcbnew/dialogs/dialog_shape_properties.cpp +++ b/pcbnew/dialogs/dialog_shape_properties.cpp @@ -513,7 +513,7 @@ bool DIALOG_SHAPE_PROPERTIES::TransferDataFromWindow() m_item->SetLayer( ToLAYER_ID( layer ) ); - m_item->RebuildBezierToSegmentsPointsList( m_item->GetWidth() ); + m_item->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); if( m_item->IsOnCopperLayer() ) m_item->SetNetCode( m_netSelector->GetSelectedNetcode() ); diff --git a/pcbnew/drc/drc_test_provider_physical_clearance.cpp b/pcbnew/drc/drc_test_provider_physical_clearance.cpp index 5a8f70e903..22997d9904 100644 --- a/pcbnew/drc/drc_test_provider_physical_clearance.cpp +++ b/pcbnew/drc/drc_test_provider_physical_clearance.cpp @@ -310,7 +310,7 @@ bool DRC_TEST_PROVIDER_PHYSICAL_CLEARANCE::Run() { SHAPE_LINE_CHAIN asPoly; - shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() ); + shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); for( const VECTOR2I& pt : shape->GetBezierPoints() ) asPoly.Append( pt ); diff --git a/pcbnew/graphics_cleaner.cpp b/pcbnew/graphics_cleaner.cpp index 5b13067c5e..a680134b1a 100644 --- a/pcbnew/graphics_cleaner.cpp +++ b/pcbnew/graphics_cleaner.cpp @@ -105,7 +105,7 @@ bool GRAPHICS_CLEANER::isNullShape( PCB_SHAPE* aShape ) return aShape->GetPointCount() == 0; 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( aShape->GetBezierPoints().size() == 2 ) diff --git a/pcbnew/import_gfx/graphics_importer_pcbnew.cpp b/pcbnew/import_gfx/graphics_importer_pcbnew.cpp index d69c5a6e33..1acee726cf 100644 --- a/pcbnew/import_gfx/graphics_importer_pcbnew.cpp +++ b/pcbnew/import_gfx/graphics_importer_pcbnew.cpp @@ -208,7 +208,7 @@ void GRAPHICS_IMPORTER_PCBNEW::AddSpline( const VECTOR2D& aStart, const VECTOR2D spline->SetBezierC1( MapCoordinate( aBezierControl1 )); spline->SetBezierC2( MapCoordinate( aBezierControl2 )); 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 // null (i.e. very small) length diff --git a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp index f68ca75388..7dd822552f 100644 --- a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp +++ b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp @@ -462,8 +462,6 @@ PCB_IO_EASYEDAPRO_PARSER::ParseContour( nlohmann::json polyData, bool aInFill, SHAPE_LINE_CHAIN result; VECTOR2D prevPt; - double bezierMinSegLen = polyData.size() < 300 ? aArcAccuracy : aArcAccuracy * 10; - for( int i = 0; i < polyData.size(); 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 ); std::vector<VECTOR2I> bezierPoints; - converter.GetPoly( bezierPoints, bezierMinSegLen, 16 ); + converter.GetPoly( bezierPoints, aArcAccuracy ); result.Append( bezierPoints ); diff --git a/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp b/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp index 3eec41b4a0..99c4a52d19 100644 --- a/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp +++ b/pcbnew/pcb_io/ipc2581/pcb_io_ipc2581.cpp @@ -1078,7 +1078,7 @@ void PCB_IO_IPC2581::addShape( wxXmlNode* aContentNode, const PCB_SHAPE& aShape aShape.GetBezierC2(), aShape.GetEnd() }; BEZIER_POLY converter( ctrlPoints ); std::vector<VECTOR2I> points; - converter.GetPoly( points, aShape.GetStroke().GetWidth() ); + converter.GetPoly( points, ARC_HIGH_DEF ); wxXmlNode* point_node = appendNode( polyline_node, "PolyBegin" ); addXY( point_node, points[0] ); diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp index a6e7fa462e..74e6644523 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp @@ -2835,6 +2835,7 @@ PCB_SHAPE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_SHAPE( BOARD_ITEM* aParent ) shape->SetBezierC1( parseXY()); shape->SetBezierC2( parseXY()); shape->SetEnd( parseXY() ); + shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); NeedRIGHT(); break; diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp index 772e353f12..db8bda5fa7 100644 --- a/pcbnew/pcb_painter.cpp +++ b/pcbnew/pcb_painter.cpp @@ -1937,22 +1937,27 @@ void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer ) pointCtrl.push_back( aShape->GetEnd() ); 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 { - m_gal->SetIsFill( aShape->IsFilled() ); - m_gal->SetIsStroke( thickness > 0 ); - m_gal->SetLineWidth( thickness ); + m_gal->SetIsFill( aShape->IsFilled() ); + m_gal->SetIsStroke( thickness > 0 ); + m_gal->SetLineWidth( thickness ); - // Use thickness as filter value to convert the curve to polyline when the curve - // is not supported - m_gal->DrawCurve( VECTOR2D( aShape->GetStart() ), - VECTOR2D( aShape->GetBezierC1() ), - VECTOR2D( aShape->GetBezierC2() ), - VECTOR2D( aShape->GetEnd() ), thickness ); + if( aShape->GetBezierPoints().size() > 2 ) + { + m_gal->DrawPolygon( aShape->GetBezierPoints() ); + } + else + { + m_gal->DrawCurve( VECTOR2D( aShape->GetStart() ), + VECTOR2D( aShape->GetBezierC1() ), + VECTOR2D( aShape->GetBezierC2() ), + VECTOR2D( aShape->GetEnd() ), m_maxError ); + } } break; diff --git a/pcbnew/pcb_shape.cpp b/pcbnew/pcb_shape.cpp index ec4a1afdbb..c1352c496c 100644 --- a/pcbnew/pcb_shape.cpp +++ b/pcbnew/pcb_shape.cpp @@ -267,6 +267,7 @@ bool PCB_SHAPE::Deserialize( const google::protobuf::Any &aContainer ) SetBezierC1( kiapi::common::UnpackVector2( msg.bezier().control1() ) ); SetBezierC2( kiapi::common::UnpackVector2( msg.bezier().control2() ) ); SetEnd( kiapi::common::UnpackVector2( msg.bezier().end() ) ); + RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); } return true; @@ -568,7 +569,7 @@ void PCB_SHAPE::Mirror( const VECTOR2I& aCentre, bool aMirrorAroundXAxis ) std::swap( m_start, m_end ); if( GetShape() == SHAPE_T::BEZIER ) - RebuildBezierToSegmentsPointsList( GetWidth() ); + RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); break; diff --git a/pcbnew/teardrop/teardrop_utils.cpp b/pcbnew/teardrop/teardrop_utils.cpp index 4f69a4bdf0..c9430d7632 100644 --- a/pcbnew/teardrop/teardrop_utils.cpp +++ b/pcbnew/teardrop/teardrop_utils.cpp @@ -250,7 +250,7 @@ void TEARDROP_MANAGER::computeCurvedForRoundShape( const TEARDROP_PARAMETERS& aP std::vector<VECTOR2I> curve_pts; 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 ) aPoly.push_back( corner ); @@ -258,7 +258,7 @@ void TEARDROP_MANAGER::computeCurvedForRoundShape( const TEARDROP_PARAMETERS& aP aPoly.push_back( pts[3] ); 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 ) aPoly.push_back( corner ); @@ -319,7 +319,7 @@ void TEARDROP_MANAGER::computeCurvedForRectShape( const TEARDROP_PARAMETERS& aPa ctrl2.x += bias.x; 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 ) aPoly.push_back( corner ); @@ -347,7 +347,7 @@ void TEARDROP_MANAGER::computeCurvedForRectShape( const TEARDROP_PARAMETERS& aPa ctrl2.x += bias.x; 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 ) aPoly.push_back( corner ); diff --git a/pcbnew/tools/pcb_point_editor.cpp b/pcbnew/tools/pcb_point_editor.cpp index 89ca90f681..9d2c2797fe 100644 --- a/pcbnew/tools/pcb_point_editor.cpp +++ b/pcbnew/tools/pcb_point_editor.cpp @@ -1407,7 +1407,7 @@ void PCB_POINT_EDITOR::updateItem( BOARD_COMMIT* aCommit ) else if( isModified( m_editPoints->Point( BEZIER_END ) ) ) shape->SetEnd( m_editPoints->Point( BEZIER_END ).GetPosition() ); - shape->RebuildBezierToSegmentsPointsList( shape->GetWidth() ); + shape->RebuildBezierToSegmentsPointsList( ARC_HIGH_DEF ); break; default: // suppress warnings diff --git a/qa/data/pcbnew/issue6039.kicad_pro b/qa/data/pcbnew/issue6039.kicad_pro index 2a30c45658..1054539c6b 100644 --- a/qa/data/pcbnew/issue6039.kicad_pro +++ b/qa/data/pcbnew/issue6039.kicad_pro @@ -3,14 +3,17 @@ "3dviewports": [], "design_settings": { "defaults": { - "board_outline_line_width": 0.049999999999999996, - "copper_line_width": 0.19999999999999998, + "apply_defaults_to_fp_fields": false, + "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_size_h": 1.5, "copper_text_size_v": 1.5, "copper_text_thickness": 0.3, "copper_text_upright": true, - "courtyard_line_width": 0.049999999999999996, + "courtyard_line_width": 0.05, "dimension_precision": 1, "dimension_units": 2, "dimensions": { @@ -21,13 +24,13 @@ "text_position": 0, "units_format": 1 }, - "fab_line_width": 0.09999999999999999, + "fab_line_width": 0.1, "fab_text_italic": false, "fab_text_size_h": 1.0, "fab_text_size_v": 1.0, "fab_text_thickness": 0.15, "fab_text_upright": true, - "other_line_width": 0.09999999999999999, + "other_line_width": 0.1, "other_text_italic": false, "other_text_size_h": 1.0, "other_text_size_v": 1.0, @@ -46,7 +49,7 @@ "silk_text_upright": true, "zones": { "45_degree_only": false, - "min_clearance": 0.19999999999999998 + "min_clearance": 0.2 } }, "diff_pair_dimensions": [ @@ -73,9 +76,12 @@ "duplicate_footprints": "warning", "extra_footprint": "warning", "footprint": "error", + "footprint_symbol_mismatch": "warning", "footprint_type_mismatch": "ignore", "hole_clearance": "error", "hole_near_hole": "error", + "hole_to_hole": "warning", + "holes_co_located": "warning", "invalid_outline": "error", "isolated_copper": "warning", "item_on_disabled_layer": "error", @@ -120,17 +126,17 @@ "min_copper_edge_clearance": 0.01, "min_hole_clearance": 0.0, "min_hole_to_hole": 0.25, - "min_microvia_diameter": 0.19999999999999998, - "min_microvia_drill": 0.09999999999999999, + "min_microvia_diameter": 0.2, + "min_microvia_drill": 0.1, "min_resolved_spokes": 2, "min_silk_clearance": 0.0, - "min_text_height": 0.7999999999999999, + "min_text_height": 0.8, "min_text_thickness": 0.08, "min_through_hole_diameter": 0.3, - "min_track_width": 0.19999999999999998, - "min_via_annular_width": 0.049999999999999996, + "min_track_width": 0.2, + "min_via_annular_width": 0.05, "min_via_annulus": 0.049999999999999996, - "min_via_diameter": 0.39999999999999997, + "min_via_diameter": 0.4, "solder_mask_to_copper_clearance": 0.0, "use_height_for_length_calcs": true }, @@ -184,6 +190,32 @@ 1.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": [ { "diameter": 0.85, @@ -193,6 +225,13 @@ "zones_allow_external_fillets": false, "zones_use_no_outline": true }, + "ipc2581": { + "dist": "", + "distpn": "", + "internal_id": "", + "mfg": "", + "mpn": "" + }, "layer_presets": [ { "activeLayer": -2,