From ae87dc686a8ecf78a3887c4c91b79bd2fe9a8a93 Mon Sep 17 00:00:00 2001 From: Roberto Fernandez Bautista Date: Sun, 8 Aug 2021 20:56:46 +0100 Subject: [PATCH] Fix SHAPE_LINE_CHAIN::NearestPoint when aAllowInternalShapePoints=false Fixes https://gitlab.com/kicad/code/kicad/-/issues/8770 --- libs/kimath/src/geometry/shape_line_chain.cpp | 45 ++++++++++++------- .../kimath/geometry/test_shape_line_chain.cpp | 31 +++++++++++++ 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/libs/kimath/src/geometry/shape_line_chain.cpp b/libs/kimath/src/geometry/shape_line_chain.cpp index ca0bf81d43..c65e659198 100644 --- a/libs/kimath/src/geometry/shape_line_chain.cpp +++ b/libs/kimath/src/geometry/shape_line_chain.cpp @@ -1557,29 +1557,42 @@ const VECTOR2I SHAPE_LINE_CHAIN::NearestPoint( const VECTOR2I& aP, { int d = CSegment( i ).Distance( aP ); - bool isInternalShapePoint = false; - - // An internal shape point here is everything after the start of an arc and before the - // second-to-last vertex of the arc, because we are looking at segments here! - if( i > 0 && i < SegmentCount() - 1 && m_shapes[i] != SHAPES_ARE_PT - && ( ( m_shapes[i - 1] != SHAPES_ARE_PT && m_shapes[i - 1] == m_shapes[i] ) - && ( m_shapes[i + 2] != SHAPES_ARE_PT && m_shapes[i + 2] == m_shapes[i] ) ) ) - { - isInternalShapePoint = true; - } - - if( ( d < min_d ) && ( aAllowInternalShapePoints || !isInternalShapePoint ) ) + if( d < min_d ) { min_d = d; nearest = i; } } - // Is this the start or end of an arc? If so, return it directly - if( !aAllowInternalShapePoints && ( IsArcStart( nearest ) || IsArcEnd( nearest ) ) ) + if( !aAllowInternalShapePoints ) { - //@todo should we calculate the nearest point to the "true" arc? - return m_points[nearest]; + //Snap to arc end points if the closest found segment is part of an arc segment + if( nearest > 0 && nearest < PointCount() && IsArcSegment( nearest ) ) + { + VECTOR2I ptToSegStart = CSegment( nearest ).A - aP; + VECTOR2I ptToSegEnd = CSegment( nearest ).B - aP; + + if( ptToSegStart.EuclideanNorm() > ptToSegEnd.EuclideanNorm() ) + nearest++; + + // Is this the start or end of an arc? If so, return it directly + if( IsArcStart( nearest ) || IsArcEnd( nearest ) ) + { + return m_points[nearest]; + } + else + { + const SHAPE_ARC& nearestArc = Arc( ArcIndex( nearest ) ); + VECTOR2I ptToArcStart = nearestArc.GetP0() - aP; + VECTOR2I ptToArcEnd = nearestArc.GetP1() - aP; + + if( ptToArcStart.EuclideanNorm() > ptToArcEnd.EuclideanNorm() ) + return nearestArc.GetP1(); + else + return nearestArc.GetP0(); + } + + } } return CSegment( nearest ).NearestPoint( aP ); diff --git a/qa/libs/kimath/geometry/test_shape_line_chain.cpp b/qa/libs/kimath/geometry/test_shape_line_chain.cpp index dd380bd87a..7c3acdc873 100644 --- a/qa/libs/kimath/geometry/test_shape_line_chain.cpp +++ b/qa/libs/kimath/geometry/test_shape_line_chain.cpp @@ -260,4 +260,35 @@ BOOST_AUTO_TEST_CASE( Slice ) } +// Test SHAPE_LINE_CHAIN::NearestPoint( VECTOR2I ) +BOOST_AUTO_TEST_CASE( NearestPointPt ) +{ + SEG seg1( VECTOR2I( 0, 100000 ), VECTOR2I( 50000, 0 ) ); + SEG seg2( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ) ); + SHAPE_ARC arc( VECTOR2I( 200000, 0 ), VECTOR2I( 300000, 0 ), 180.0 ); + + // Start a chain with 2 points (seg1) + SHAPE_LINE_CHAIN chain( { seg1.A, seg1.B } ); + BOOST_CHECK_EQUAL( chain.PointCount(), 2 ); + // Add first arc + chain.Append( arc ); + BOOST_CHECK_EQUAL( chain.PointCount(), 9 ); + // Add two points (seg2) + chain.Append( seg2.A ); + chain.Append( seg2.B ); + BOOST_CHECK_EQUAL( chain.PointCount(), 11 ); + BOOST_CHECK( GEOM_TEST::IsOutlineValid( chain ) ); + + VECTOR2I ptOnArcCloseToStart( 297553, 31697 ); //should be index 3 in chain + VECTOR2I ptOnArcCloseToEnd( 139709, 82983 ); //should be index 6 in chain + + BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToStart, true ), ptOnArcCloseToStart ); + BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToStart, false ), arc.GetP0() ); + + BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToEnd, true ), ptOnArcCloseToEnd ); + BOOST_CHECK_EQUAL( chain.NearestPoint( ptOnArcCloseToEnd, false ), arc.GetP1() ); +} + + + BOOST_AUTO_TEST_SUITE_END()