/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright The KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * https://www.gnu.org/licenses/gpl-3.0.html * or you may search the https://www.gnu.org website for the version 3 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include "fixtures_geometry.h" BOOST_AUTO_TEST_SUITE( SHAPE_NEAREST_POINTS_TEST ) // Circle to Circle tests BOOST_AUTO_TEST_CASE( NearestPoints_CircleToCircle_Separate ) { SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 ); SHAPE_CIRCLE circleB( VECTOR2I( 20, 0 ), 5 ); VECTOR2I ptA, ptB; bool result = circleA.NearestPoints( &circleB, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 0 ), "Expected: " << VECTOR2I( 15, 0 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_CircleToCircle_Concentric ) { SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 ); SHAPE_CIRCLE circleB( VECTOR2I( 0, 0 ), 10 ); VECTOR2I ptA, ptB; bool result = circleA.NearestPoints( &circleB, ptA, ptB ); BOOST_CHECK( result ); // For concentric circles, points should be on arbitrary direction (positive X by convention) BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_CircleToCircle_Overlapping ) { SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 ); SHAPE_CIRCLE circleB( VECTOR2I( 6, 0 ), 5 ); VECTOR2I ptA, ptB; bool result = circleA.NearestPoints( &circleB, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 1, 0 ), "Expected: " << VECTOR2I( 1, 0 ) << " Actual: " << ptB ); } // Circle to Rectangle tests BOOST_AUTO_TEST_CASE( NearestPoints_CircleToRect_Outside ) { SHAPE_CIRCLE circle( VECTOR2I( -10, 5 ), 3 ); SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 10, 10 ) ); VECTOR2I ptA, ptB; bool result = circle.NearestPoints( &rect, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( -7, 5 ), "Expected: " << VECTOR2I( -7, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, 5 ), "Expected: " << VECTOR2I( 0, 5 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_CircleToRect_Inside ) { SHAPE_CIRCLE circle( VECTOR2I( 5, 5 ), 2 ); SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 10, 10 ) ); VECTOR2I ptA, ptB; bool result = circle.NearestPoints( &rect, ptA, ptB ); BOOST_CHECK( result ); // Circle center is inside rectangle, should find nearest edge // Center at (5,5) is equidistant from left/right edges (5 units) and top/bottom edges (5 units) // Implementation should choose one consistently - likely left edge based on min comparison BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 3, 5 ), "Expected: " << VECTOR2I( 3, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, 5 ), "Expected: " << VECTOR2I( 0, 5 ) << " Actual: " << ptB ); } // Circle to Segment tests BOOST_AUTO_TEST_CASE( NearestPoints_CircleToSegment ) { SHAPE_CIRCLE circle( VECTOR2I( 0, 5 ), 2 ); SHAPE_SEGMENT segment( VECTOR2I( -5, 0 ), VECTOR2I( 5, 0 ) ); VECTOR2I ptA, ptB; bool result = circle.NearestPoints( &segment, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 0, 3 ), "Expected: " << VECTOR2I( 0, 3 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, 0 ), "Expected: " << VECTOR2I( 0, 0 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_CircleToSegment_CenterOnSegment ) { SHAPE_CIRCLE circle( VECTOR2I( 0, 0 ), 3 ); SHAPE_SEGMENT segment( VECTOR2I( -5, 0 ), VECTOR2I( 5, 0 ) ); VECTOR2I ptA, ptB; bool result = circle.NearestPoints( &segment, ptA, ptB ); BOOST_CHECK( result ); // When center is on segment, should pick perpendicular direction BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 0, 3 ), "Expected: " << VECTOR2I( 0, 3 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, 0 ), "Expected: " << VECTOR2I( 0, 0 ) << " Actual: " << ptB ); } // Rectangle to Rectangle tests BOOST_AUTO_TEST_CASE( NearestPoints_RectToRect_Separate ) { SHAPE_RECT rectA( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ) ); SHAPE_RECT rectB( VECTOR2I( 10, 0 ), VECTOR2I( 25, 25 ) ); VECTOR2I ptA, ptB; bool result = rectA.NearestPoints( &rectB, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 5 ), "Expected: " << VECTOR2I( 10, 5 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_RectToRect_Corner ) { SHAPE_RECT rectA( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ) ); SHAPE_RECT rectB( VECTOR2I( 7, 7 ), VECTOR2I( 25, 25 ) ); VECTOR2I ptA, ptB; bool result = rectA.NearestPoints( &rectB, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 7, 7 ), "Expected: " << VECTOR2I( 7, 7 ) << " Actual: " << ptB ); } // Line Chain tests BOOST_AUTO_TEST_CASE( NearestPoints_LineChainToLineChain ) { SHAPE_LINE_CHAIN chainA; chainA.Append( VECTOR2I( 0, 0 ) ); chainA.Append( VECTOR2I( 10, 0 ) ); chainA.Append( VECTOR2I( 10, 10 ) ); SHAPE_LINE_CHAIN chainB; chainB.Append( VECTOR2I( 5, 5 ) ); chainB.Append( VECTOR2I( 15, 5 ) ); VECTOR2I ptA, ptB; bool result = chainA.NearestPoints( &chainB, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 5 ), "Expected: " << VECTOR2I( 10, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 5 ), "Expected: " << VECTOR2I( 10, 5 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_LineChainWithArc ) { SHAPE_LINE_CHAIN chainA; chainA.Append( VECTOR2I( 0, 0 ) ); chainA.Append( VECTOR2I( 10, 0 ) ); SHAPE_LINE_CHAIN chainB; chainB.Append( SHAPE_ARC( VECTOR2I( 5, 10 ), VECTOR2I( 10, 5 ), VECTOR2I( 15, 10 ), 0 ) ); VECTOR2I ptA, ptB; bool result = chainA.NearestPoints( &chainB, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 10 ), "Expected: " << VECTOR2I( 10, 10 ) << " Actual: " << ptB ); } // Arc tests BOOST_AUTO_TEST_CASE( NearestPoints_ArcToArc ) { SHAPE_ARC arcA( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ), VECTOR2I( 10, 0 ), 0 ); SHAPE_ARC arcB( VECTOR2I( 15, 0 ), VECTOR2I( 20, 5 ), VECTOR2I( 25, 0 ), 0 ); VECTOR2I ptA, ptB; int64_t distSq; bool result = arcA.NearestPoints( arcB, ptA, ptB, distSq ); BOOST_CHECK( result ); // Points should be on the arcs closest to each other BOOST_CHECK( ptA.x <= 10 && ptA.x >= 0 ); BOOST_CHECK( ptB.x >= 15 && ptB.x <= 25 ); } BOOST_AUTO_TEST_CASE( NearestPoints_ArcToCircle ) { SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ), VECTOR2I( 10, 0 ), 0 ); SHAPE_CIRCLE circle( VECTOR2I( 5, 10 ), 3 ); VECTOR2I ptA, ptB; int64_t distSq; bool result = arc.NearestPoints( circle, ptA, ptB, distSq ); BOOST_CHECK( result ); // Arc point should be at or near the top of the arc BOOST_CHECK( ptA.y >= 0 ); // Circle point should be at bottom of circle BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 7 ), "Expected: " << VECTOR2I( 5, 7 ) << " Actual: " << ptB ); } // Segment tests BOOST_AUTO_TEST_CASE( NearestPoints_SegmentToSegment ) { SHAPE_SEGMENT segA( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ) ), 2 ); SHAPE_SEGMENT segB( SEG( VECTOR2I( 5, 5 ), VECTOR2I( 5, 15 ) ), 2 ); VECTOR2I ptA, ptB; bool result = segA.NearestPoints( &segB, ptA, ptB ); BOOST_CHECK( result ); // Points should account for segment width BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 1 ), "Expected: " << VECTOR2I( 5, 1 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 4 ), "Expected: " << VECTOR2I( 5, 4 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_SegmentToCircle ) { SHAPE_SEGMENT segment( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ) ), 4 ); SHAPE_CIRCLE circle( VECTOR2I( 5, 10 ), 3 ); VECTOR2I ptA, ptB; bool result = segment.NearestPoints( &circle, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 2 ), "Expected: " << VECTOR2I( 5, 2 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 7 ), "Expected: " << VECTOR2I( 5, 7 ) << " Actual: " << ptB ); } // Compound Shape tests BOOST_AUTO_TEST_CASE( NearestPoints_CompoundShapes ) { SHAPE_COMPOUND compoundA; compoundA.AddShape( new SHAPE_CIRCLE( VECTOR2I( 0, 0 ), 5 ) ); compoundA.AddShape( new SHAPE_RECT( VECTOR2I( 10, 0 ), VECTOR2I( 5, 5 ) ) ); SHAPE_COMPOUND compoundB; compoundB.AddShape( new SHAPE_CIRCLE( VECTOR2I( 20, 0 ), 3 ) ); VECTOR2I ptA, ptB; bool result = compoundA.NearestPoints( &compoundB, ptA, ptB ); BOOST_CHECK( result ); // Should find nearest points between rectangle in A and circle in B BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 17, 0 ), "Expected: " << VECTOR2I( 17, 0 ) << " Actual: " << ptB ); } // Polygon Set tests BOOST_AUTO_TEST_CASE( NearestPoints_PolySetToCircle ) { SHAPE_POLY_SET polySet; polySet.NewOutline(); polySet.Append( VECTOR2I( 0, 0 ) ); polySet.Append( VECTOR2I( 10, 0 ) ); polySet.Append( VECTOR2I( 10, 10 ) ); polySet.Append( VECTOR2I( 0, 10 ) ); SHAPE_CIRCLE circle( VECTOR2I( 15, 5 ), 3 ); VECTOR2I ptA, ptB; bool result = polySet.NearestPoints( &circle, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 12, 5 ), "Expected: " << VECTOR2I( 12, 5 ) << " Actual: " << ptB ); } // Missing Basic Shape Combinations BOOST_AUTO_TEST_CASE( NearestPoints_RectToSegment ) { SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 10, 10 ) ); SHAPE_SEGMENT segment( VECTOR2I( 15, 5 ), VECTOR2I( 25, 5 ) ); VECTOR2I ptA, ptB; bool result = rect.NearestPoints( &segment, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 5 ), "Expected: " << VECTOR2I( 10, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 5 ), "Expected: " << VECTOR2I( 15, 5 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_RectToLineChain ) { SHAPE_RECT rect( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ) ); SHAPE_LINE_CHAIN chain; chain.Append( VECTOR2I( 10, 0 ) ); chain.Append( VECTOR2I( 15, 5 ) ); chain.Append( VECTOR2I( 10, 10 ) ); VECTOR2I ptA, ptB; bool result = rect.NearestPoints( &chain, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_SegmentToLineChain ) { SHAPE_SEGMENT segment( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ) ), 2 ); SHAPE_LINE_CHAIN chain; chain.Append( VECTOR2I( 5, 10 ) ); chain.Append( VECTOR2I( 15, 10 ) ); chain.Append( VECTOR2I( 15, 5 ) ); VECTOR2I ptA, ptB; bool result = segment.NearestPoints( &chain, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 11, 1 ), "Expected: " << VECTOR2I( 11, 1 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 5 ), "Expected: " << VECTOR2I( 15, 5 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_CircleToLineChain ) { SHAPE_CIRCLE circle( VECTOR2I( 0, 0 ), 3 ); SHAPE_LINE_CHAIN chain; chain.Append( VECTOR2I( 10, -5 ) ); chain.Append( VECTOR2I( 10, 0 ) ); chain.Append( VECTOR2I( 10, 5 ) ); VECTOR2I ptA, ptB; bool result = circle.NearestPoints( &chain, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 3, 0 ), "Expected: " << VECTOR2I( 3, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_ArcToRect ) { SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ), VECTOR2I( 10, 0 ), 0 ); SHAPE_RECT rect( VECTOR2I( 150, 2 ), VECTOR2I( 50, 6 ) ); VECTOR2I ptA, ptB; int64_t distSq; bool result = arc.NearestPoints( rect, ptA, ptB, distSq ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 10, 0 ), "Expected: " << VECTOR2I( 10, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 50, 2 ), "Expected: " << VECTOR2I( 50, 2 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_ArcToSegment ) { SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 0, 10 ), VECTOR2I( 0, 20 ), 0 ); // Semicircle SEG segment( VECTOR2I( 15, 10 ), VECTOR2I( 25, 10 ) ); VECTOR2I ptA, ptB; int64_t distSq; bool result = arc.NearestPoints( segment, ptA, ptB, distSq ); BOOST_CHECK( result ); // Arc point should be on the rightmost part of the arc BOOST_CHECK( ptA.x >= 0 ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 10 ), "Expected: " << VECTOR2I( 15, 10 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_ArcToLineChain ) { SHAPE_ARC arc( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ), VECTOR2I( 10, 0 ), 0 ); SHAPE_LINE_CHAIN chain; chain.Append( VECTOR2I( 5, 15 ) ); chain.Append( VECTOR2I( 5, 10 ) ); chain.Append( VECTOR2I( 15, 10 ) ); VECTOR2I ptA, ptB; int64_t distSq; bool result = chain.NearestPoints( &arc, ptB, ptA ); BOOST_CHECK( result ); // Arc point should be near the top of the arc BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 10 ), "Expected: " << VECTOR2I( 5, 10 ) << " Actual: " << ptB ); } // Distance and Symmetry Validation Tests BOOST_AUTO_TEST_CASE( NearestPoints_DistanceValidation ) { SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 ); SHAPE_CIRCLE circleB( VECTOR2I( 20, 0 ), 3 ); VECTOR2I ptA, ptB; bool result = circleA.NearestPoints( &circleB, ptA, ptB ); BOOST_CHECK( result ); // Calculate expected distance: center distance - both radii int expectedDistance = 20 - 5 - 3; // 12 int actualDistance = (ptB - ptA).EuclideanNorm(); BOOST_CHECK_MESSAGE( actualDistance == expectedDistance, "Expected distance: " << expectedDistance << " Actual: " << actualDistance ); } BOOST_AUTO_TEST_CASE( NearestPoints_SymmetryTest ) { SHAPE_RECT rectA( VECTOR2I( 0, 0 ), VECTOR2I( 5, 5 ) ); SHAPE_CIRCLE circleB( VECTOR2I( 10, 2 ), 2 ); // Test A->B VECTOR2I ptA1, ptB1; bool result1 = rectA.NearestPoints( &circleB, ptA1, ptB1 ); // Test B->A VECTOR2I ptA2, ptB2; bool result2 = circleB.NearestPoints( &rectA, ptA2, ptB2 ); BOOST_CHECK( result1 && result2 ); // Distance should be the same both ways int dist1 = (ptB1 - ptA1).EuclideanNorm(); int dist2 = (ptB2 - ptA2).EuclideanNorm(); BOOST_CHECK_MESSAGE( dist1 == dist2, "Distance A->B: " << dist1 << " Distance B->A: " << dist2 ); // Points should be swapped (ptA1 should equal ptB2, ptB1 should equal ptA2) BOOST_CHECK_MESSAGE( ptA1 == ptB2, "Expected ptA1 == ptB2. ptA1: " << ptA1 << " ptB2: " << ptB2 ); BOOST_CHECK_MESSAGE( ptB1 == ptA2, "Expected ptB1 == ptA2. ptB1: " << ptB1 << " ptA2: " << ptA2 ); } BOOST_AUTO_TEST_CASE( NearestPoints_MinimumDistanceValidation ) { SHAPE_SEGMENT segment( SEG( VECTOR2I( 0, 0 ), VECTOR2I( 10, 0 ) ), 0 ); SHAPE_CIRCLE circle( VECTOR2I( 5, 5 ), 2 ); VECTOR2I ptA, ptB; bool result = segment.NearestPoints( &circle, ptA, ptB ); BOOST_CHECK( result ); // Verify that this is indeed the minimum distance by checking nearby points int minDist = (ptB - ptA).EuclideanNorm(); // Check a few other points on the segment for( int x = 0; x <= 10; x += 2 ) { VECTOR2I testPt( x, 0 ); VECTOR2I circleClosest = circle.GetCenter() + (testPt - circle.GetCenter()).Resize( circle.GetRadius() ); int testDist = (circleClosest - testPt).EuclideanNorm(); BOOST_CHECK_MESSAGE( minDist <= testDist, "Found shorter distance at x=" << x << ": " << testDist << " vs " << minDist ); } } // Complex Line Chain Tests BOOST_AUTO_TEST_CASE( NearestPoints_LineChainWithMixedArcs ) { SHAPE_LINE_CHAIN chainA; chainA.Append( VECTOR2I( 0, 0 ) ); chainA.Append( VECTOR2I( 10, 0 ) ); chainA.Append( SHAPE_ARC( VECTOR2I( 10, 0 ), VECTOR2I( 15, 5 ), VECTOR2I( 20, 0 ), 0 ) ); chainA.Append( VECTOR2I( 30, 0 ) ); SHAPE_CIRCLE circle( VECTOR2I( 15, 15 ), 3 ); VECTOR2I ptA, ptB; bool result = chainA.NearestPoints( &circle, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 15, 12 ), "Expected: " << VECTOR2I( 15, 12 ) << " Actual: " << ptB ); } // Degenerate Shape Tests BOOST_AUTO_TEST_CASE( NearestPoints_DegenerateShapes_ZeroWidthRect ) { SHAPE_RECT rectA( VECTOR2I( 0, 0 ), VECTOR2I( 0, 10 ) ); // Zero width SHAPE_CIRCLE circle( VECTOR2I( 5, 5 ), 2 ); VECTOR2I ptA, ptB; bool result = rectA.NearestPoints( &circle, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 0, 5 ), "Expected: " << VECTOR2I( 0, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 3, 5 ), "Expected: " << VECTOR2I( 3, 5 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_DegenerateShapes_ZeroLengthSegment ) { SHAPE_SEGMENT zeroSeg( VECTOR2I( 5, 5 ), VECTOR2I( 5, 5 ) ); // Point segment SHAPE_CIRCLE circle( VECTOR2I( 10, 5 ), 3 ); VECTOR2I ptA, ptB; bool result = circle.NearestPoints( &zeroSeg, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 7, 5 ), "Expected: " << VECTOR2I( 7, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 5 ), "Expected: " << VECTOR2I( 5, 5 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_SinglePointLineChain ) { SHAPE_LINE_CHAIN singlePoint; singlePoint.Append( VECTOR2I( 0, 0 ) ); SHAPE_CIRCLE circle( VECTOR2I( 5, 0 ), 2 ); VECTOR2I ptA, ptB; bool result = singlePoint.NearestPoints( &circle, ptA, ptB ); BOOST_CHECK( !result ); } // Negative Coordinate Tests BOOST_AUTO_TEST_CASE( NearestPoints_NegativeCoordinates ) { SHAPE_CIRCLE circleA( VECTOR2I( -10, -5 ), 3 ); SHAPE_RECT rect( VECTOR2I( 0, -2 ), VECTOR2I( 5, 4 ) ); VECTOR2I ptA, ptB; bool result = circleA.NearestPoints( &rect, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( -7, -4 ), "Expected: " << VECTOR2I( -7, -4 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 0, -2 ), "Expected: " << VECTOR2I( 0, -2 ) << " Actual: " << ptB ); } // Edge case tests BOOST_AUTO_TEST_CASE( NearestPoints_IdenticalShapes ) { SHAPE_CIRCLE circleA( VECTOR2I( 5, 5 ), 3 ); SHAPE_CIRCLE circleB( VECTOR2I( 5, 5 ), 3 ); VECTOR2I ptA, ptB; bool result = circleA.NearestPoints( &circleB, ptA, ptB ); BOOST_CHECK( result ); // Identical concentric circles - should return arbitrary points BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 8, 5 ), "Expected: " << VECTOR2I( 8, 5 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 8, 5 ), "Expected: " << VECTOR2I( 8, 5 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_ZeroSizeShapes ) { SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 0 ); SHAPE_CIRCLE circleB( VECTOR2I( 10, 0 ), 5 ); VECTOR2I ptA, ptB; bool result = circleA.NearestPoints( &circleB, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 0, 0 ), "Expected: " << VECTOR2I( 0, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_CASE( NearestPoints_VeryCloseShapes ) { SHAPE_CIRCLE circleA( VECTOR2I( 0, 0 ), 5 ); SHAPE_CIRCLE circleB( VECTOR2I( 10, 0 ), 5 ); // Just touching VECTOR2I ptA, ptB; bool result = circleA.NearestPoints( &circleB, ptA, ptB ); BOOST_CHECK( result ); BOOST_CHECK_MESSAGE( ptA == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptA ); BOOST_CHECK_MESSAGE( ptB == VECTOR2I( 5, 0 ), "Expected: " << VECTOR2I( 5, 0 ) << " Actual: " << ptB ); } BOOST_AUTO_TEST_SUITE_END()