From b73481dd49f06efb0a3d0fc8780a49b64bfa2007 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Mon, 13 Jan 2025 18:29:30 -0800 Subject: [PATCH] Update SHAPE_ARC::IntersectionPoints() Force it to handle a number of edge cases from Arc-Arc collision test. Unify and simplify the Collide Arc-Arc. Add additional arc collision QA test --- libs/kimath/src/geometry/shape_arc.cpp | 112 ++++++++++++++---- libs/kimath/src/geometry/shape_collisions.cpp | 81 ++----------- .../libs/kimath/geometry/test_shape_arc.cpp | 9 +- 3 files changed, 106 insertions(+), 96 deletions(-) diff --git a/libs/kimath/src/geometry/shape_arc.cpp b/libs/kimath/src/geometry/shape_arc.cpp index 2e910dbcd8..66867e8dec 100644 --- a/libs/kimath/src/geometry/shape_arc.cpp +++ b/libs/kimath/src/geometry/shape_arc.cpp @@ -454,7 +454,7 @@ bool SHAPE_ARC::NearestPoints( const SHAPE_ARC& aArc, VECTOR2I& aPtA, VECTOR2I& VECTOR2I center1 = GetCenter(); VECTOR2I center2 = aArc.GetCenter(); - int64_t dist = center1.SquaredDistance( center2 ); + int64_t center_dist_sq = center1.SquaredDistance( center2 ); // Start by checking endpoints std::vector pts1 = { m_start, m_end }; @@ -471,44 +471,110 @@ bool SHAPE_ARC::NearestPoints( const SHAPE_ARC& aArc, VECTOR2I& aPtA, VECTOR2I& aDistSq = distSq; aPtA = pt1; aPtB = pt2; + + if( aDistSq == 0 ) + return true; } } } - // If the centers are the same, the end points are always the closest - // or at least equidistant - if( dist > 0 ) + for( const VECTOR2I& pt : pts1 ) { - CIRCLE circle1( center1, GetRadius() ); - CIRCLE circle2( center2, aArc.GetRadius() ); - - // First check for intersections on the circles - std::vector pts = circle1.Intersect( circle2 ); - - for( const VECTOR2I& pt : pts ) + if( aArc.sliceContainsPoint( pt ) ) { - if( sliceContainsPoint( pt ) && aArc.sliceContainsPoint( pt ) ) - { - aPtA = pt; - aPtB = pt; - aDistSq = 0; + CIRCLE circle( center2, aArc.GetRadius() ); + aPtA = circle.NearestPoint( pt ); + aPtB = pt; + aDistSq = aPtA.SquaredDistance( aPtB ); + + if( center_dist_sq == 0 || aDistSq == 0 ) return true; - } + } + } + + for( const VECTOR2I& pt : pts2 ) + { + if( sliceContainsPoint( pt ) ) + { + CIRCLE circle( center1, GetRadius() ); + aPtA = pt; + aPtB = circle.NearestPoint( pt ); + aDistSq = aPtA.SquaredDistance( aPtB ); + + if( center_dist_sq == 0 || aDistSq == 0 ) + return true; + } + } + + // The remaining checks are require the arcs to be on non-concentric circles + if( center_dist_sq == 0 ) + return true; + + CIRCLE circle1( center1, GetRadius() ); + CIRCLE circle2( center2, aArc.GetRadius() ); + + // First check for intersections on the circles + std::vector intersections = circle1.Intersect( circle2 ); + + for( const VECTOR2I& pt : intersections ) + { + if( sliceContainsPoint( pt ) && aArc.sliceContainsPoint( pt ) ) + { + aPtA = pt; + aPtB = pt; + aDistSq = 0; + return true; + } + } + + // Check for the closest points on the circles + VECTOR2I pt1 = circle1.NearestPoint( center2 ); + VECTOR2I pt2 = circle2.NearestPoint( center1 ); + bool pt1InSlice = sliceContainsPoint( pt1 ); + bool pt2InSlice = aArc.sliceContainsPoint( pt2 ); + + if( pt1InSlice && pt2InSlice ) + { + int64_t distSq = pt1.SquaredDistance( pt2 ); + + if( distSq < aDistSq ) + { + aDistSq = distSq; + aPtA = pt1; + aPtB = pt2; } - // Check for the closest points on the circles - VECTOR2I pt1 = circle1.NearestPoint( center2 ); - VECTOR2I pt2 = circle2.NearestPoint( center1 ); + return true; + } - if( sliceContainsPoint( pt1 ) && aArc.sliceContainsPoint( pt2 ) ) + // Check the endpoints of arc 1 against the nearest point on arc 2 + if( pt2InSlice ) + { + for( const VECTOR2I& pt : pts1 ) { - int64_t distSq = pt1.SquaredDistance( pt2 ); + int64_t distSq = pt.SquaredDistance( pt2 ); + + if( distSq < aDistSq ) + { + aDistSq = distSq; + aPtA = pt; + aPtB = pt2; + } + } + } + + // Check the endpoints of arc 2 against the nearest point on arc 1 + if( pt1InSlice ) + { + for( const VECTOR2I& pt : pts2 ) + { + int64_t distSq = pt.SquaredDistance( pt1 ); if( distSq < aDistSq ) { aDistSq = distSq; aPtA = pt1; - aPtB = pt2; + aPtB = pt; } } } diff --git a/libs/kimath/src/geometry/shape_collisions.cpp b/libs/kimath/src/geometry/shape_collisions.cpp index 1971fe6cf5..b36add4d8b 100644 --- a/libs/kimath/src/geometry/shape_collisions.cpp +++ b/libs/kimath/src/geometry/shape_collisions.cpp @@ -738,84 +738,23 @@ static inline bool Collide( const SHAPE_ARC& aA, const SHAPE_ARC& aB, int aClear aA.TypeName(), aB.TypeName() ) ); - SEG mediatrix( aA.GetCenter(), aB.GetCenter() ); + VECTOR2I ptA, ptB; + int64_t dist_sq = std::numeric_limits::max(); + aA.NearestPoints( aB, ptA, ptB, dist_sq ); + int dual_width = ( aA.GetWidth() + aB.GetWidth() ) / 2; - std::vector ips; - - // Basic case - arcs intersect - if( aA.Intersect( aB, &ips ) > 0 ) + if( dist_sq < SEG::Square( aClearance + dual_width ) ) { - if( aActual ) - *aActual = 0; - if( aLocation ) - *aLocation = ips[0]; // Pick the first intersection point + *aLocation = ( ptA + ptB ) / 2; + + if( aActual ) + *aActual = std::max( 0, KiROUND( std::sqrt( dist_sq ) - dual_width ) ); return true; } - // Arcs don't intersect, build a list of points to check - std::vector ptsA; - std::vector ptsB; - - bool cocentered = ( mediatrix.A == mediatrix.B ); - - // 1: Interior points of both arcs, which are on the line segment between the two centres - if( !cocentered ) - { - aA.IntersectLine( mediatrix, &ptsA ); - aB.IntersectLine( mediatrix, &ptsB ); - } - - // 2: Check arc end points - ptsA.push_back( aA.GetP0() ); - ptsA.push_back( aA.GetP1() ); - ptsB.push_back( aB.GetP0() ); - ptsB.push_back( aB.GetP1() ); - - // 3: Endpoint of one and "projected" point on the other, which is on the - // line segment through that endpoint and the centre of the other arc - aA.IntersectLine( SEG( aB.GetP0(), aA.GetCenter() ), &ptsA ); - aA.IntersectLine( SEG( aB.GetP1(), aA.GetCenter() ), &ptsA ); - - aB.IntersectLine( SEG( aA.GetP0(), aB.GetCenter() ), &ptsB ); - aB.IntersectLine( SEG( aA.GetP1(), aB.GetCenter() ), &ptsB ); - - double minDist = std::numeric_limits::max(); - SEG minDistSeg; - bool rv = false; - - int widths = ( aA.GetWidth() / 2 ) + ( aB.GetWidth() / 2 ); - - // @todo performance could be improved by only checking certain points (e.g only check end - // points against other end points or their corresponding "projected" points) - for( const VECTOR2I& ptA : ptsA ) - { - for( const VECTOR2I& ptB : ptsB ) - { - SEG candidateMinDist( ptA, ptB ); - int dist = candidateMinDist.Length() - widths; - - if( dist < aClearance ) - { - if( !rv || dist < minDist ) - { - minDist = dist; - minDistSeg = candidateMinDist; - } - - rv = true; - } - } - } - - if( rv && aActual ) - *aActual = std::max( 0, minDistSeg.Length() - widths ); - - if( rv && aLocation ) - *aLocation = minDistSeg.Center(); - - return rv; + return false; } diff --git a/qa/tests/libs/kimath/geometry/test_shape_arc.cpp b/qa/tests/libs/kimath/geometry/test_shape_arc.cpp index 9e0aa3d211..3e4920833c 100644 --- a/qa/tests/libs/kimath/geometry/test_shape_arc.cpp +++ b/qa/tests/libs/kimath/geometry/test_shape_arc.cpp @@ -771,8 +771,8 @@ static const std::vector arc_arc_collide_cases = { 0, false }, { "case 6: Coincident centers, colliding due to arc thickness", - { 79.279991, 80.67988, 80.3749, 81.394518, -255.5, 0.2 }, - { 79.279991, 80.67988, 80.3749, 81.694518, -255.5, 0.2 }, + { 79.279991, 80.67988, 80.3749, 81.394518, -255.5, 0.3 }, + { 79.279991, 80.67988, 80.3749, 81.694518, -255.5, 0.3 }, 0, true }, { "case 7: Single intersection", @@ -815,6 +815,11 @@ static const std::vector arc_arc_collide_cases = { { 79.87532, 93.413654, 81.86532, 93.393054, 90.0, 0.3 }, 0, true }, + { "case 15: Arcs separated by clearance", + { 303.7615, 149.9252, 303.695968, 149.925237, 90.0262, 0.065 }, + { 303.6345, 149.2637, 303.634523, 148.85619, 89.9957, 0.065 }, + 0.15, + false }, };