Fix SHAPE_ARC::Collide and add Unit Tests

This commit is contained in:
Roberto Fernandez Bautista 2021-03-14 20:26:35 +00:00 committed by Jon Evans
parent 1d879e36cc
commit e4b99b9bb5
2 changed files with 114 additions and 9 deletions

View File

@ -316,22 +316,39 @@ bool SHAPE_ARC::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
int minDist = aClearance + m_width / 2; int minDist = aClearance + m_width / 2;
auto bbox = BBox( minDist ); auto bbox = BBox( minDist );
// Fast check using bounding box:
if( !bbox.Contains( aP ) ) if( !bbox.Contains( aP ) )
return false; return false;
ecoord min_dist_sq = SEG::Square( minDist ); VECTOR2I center = GetCenter();
ecoord r_sq = SEG::Square( GetRadius() ); VECTOR2I vec = aP - center;
ecoord dist_sq = ( aP - GetCenter() ).SquaredEuclideanNorm(); int dist = abs( vec.EuclideanNorm() - GetRadius() );
ecoord dist_to_edge_sq = abs( dist_sq - r_sq );
if( dist_to_edge_sq == 0 || dist_to_edge_sq < min_dist_sq ) // If not a 360 degree arc, need to use arc angles to decide if point collides
if( m_start != m_end )
{
bool ccw = GetCentralAngle() > 0.0;
double rotatedVecAngle = NormalizeAngleDegreesPos( NormalizeAngleDegreesPos( RAD2DEG( vec.Angle() ) )
- GetStartAngle() );
double rotatedEndAngle = NormalizeAngleDegreesPos( GetEndAngle() - GetStartAngle() );
if( ( ccw && rotatedVecAngle > rotatedEndAngle )
|| ( !ccw && rotatedVecAngle < rotatedEndAngle ) )
{
int distStartpt = ( aP - m_start ).EuclideanNorm();
int distEndpt = ( aP - m_end ).EuclideanNorm();
dist = std::min( distStartpt, distEndpt );
}
}
if( dist <= minDist )
{ {
if( aLocation ) if( aLocation )
*aLocation = ( aP + GetCenter() ) / 2; *aLocation = ( aP + GetCenter() ) / 2;
if( aActual ) if( aActual )
*aActual = std::max( 0, (int) sqrt( dist_to_edge_sq ) - m_width / 2 ); *aActual = std::max( 0, dist - m_width / 2 );
return true; return true;
} }

View File

@ -441,7 +441,7 @@ BOOST_AUTO_TEST_CASE( BasicTTRGeom )
{ {
//Swap input segments. //Swap input segments.
seg1 = c.m_geom.m_segment_2; seg1 = c.m_geom.m_segment_2;
seg2 = c.m_geom.m_segment_1; seg2 = c.m_geom.m_segment_1;
//The result should swap start and end points and invert the angles: //The result should swap start and end points and invert the angles:
props.m_end_point = c.m_properties.m_start_point; props.m_end_point = c.m_properties.m_start_point;
@ -450,7 +450,7 @@ BOOST_AUTO_TEST_CASE( BasicTTRGeom )
props.m_end_angle = c.m_properties.m_start_angle; props.m_end_angle = c.m_properties.m_start_angle;
props.m_center_angle = -c.m_properties.m_center_angle; props.m_center_angle = -c.m_properties.m_center_angle;
} }
//Test all combinations of start and end points for the segments //Test all combinations of start and end points for the segments
if( ( testCase % 4 ) == 1 || ( testCase % 4 ) == 3 ) if( ( testCase % 4 ) == 1 || ( testCase % 4 ) == 3 )
{ {
@ -459,7 +459,7 @@ BOOST_AUTO_TEST_CASE( BasicTTRGeom )
seg1.A = seg1.B; seg1.A = seg1.B;
seg1.B = temp; seg1.B = temp;
} }
if( ( testCase % 4 ) == 2 || ( testCase % 4 ) == 3 ) if( ( testCase % 4 ) == 2 || ( testCase % 4 ) == 3 )
{ {
//Swap start and end points for seg2 //Swap start and end points for seg2
@ -479,6 +479,94 @@ BOOST_AUTO_TEST_CASE( BasicTTRGeom )
} }
struct ARC_PT_COLLIDE_CASE
{
std::string m_ctx_name;
ARC_CENTRE_PT_ANGLE m_geom;
int m_arc_clearance;
VECTOR2I m_point;
bool m_exp_result;
int m_exp_distance;
};
static const std::vector<ARC_PT_COLLIDE_CASE> arc_pt_collide_cases = {
{ " 270deg, 0 cl, 0 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 100, 0 }, true, 0 },
{ " 270deg, 0 cl, 90 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 0, 100 }, true, 0 },
{ " 270deg, 0 cl, 180 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { -100, 0 }, true, 0 },
{ " 270deg, 0 cl, 270 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 0, -100 }, true, 0 },
{ " 270deg, 0 cl, 45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 71, 71 }, true, 0 },
{ " 270deg, 0 cl, -45 deg ", { { 0, 0 }, { 100, 0 }, 270.0 }, 0, { 71, -71 }, false, -1 },
{ "-270deg, 0 cl, 0 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 100, 0 }, true, 0 },
{ "-270deg, 0 cl, 90 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 0, 100 }, true, 0 },
{ "-270deg, 0 cl, 180 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { -100, 0 }, true, 0 },
{ "-270deg, 0 cl, 270 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 0, -100 }, true, 0 },
{ "-270deg, 0 cl, 45 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 71, 71 }, false, -1 },
{ "-270deg, 0 cl, -45 deg ", { { 0, 0 }, { 100, 0 }, -270.0 }, 0, { 71, -71 }, true, 0 },
{ " 270deg, 5 cl, 0 deg, 5 pos X", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 105, 0 }, true, 5 },
{ " 270deg, 5 cl, 0 deg, 5 pos Y", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 100, -5 }, true, 5 },
{ " 270deg, 5 cl, 90 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, 105 }, true, 5 },
{ " 270deg, 5 cl, 180 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { -105, 0 }, true, 5 },
{ " 270deg, 5 cl, 270 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, -105 }, true, 5 },
{ " 270deg, 5 cl, 0 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 105, 0 }, true, 5 },
{ " 270deg, 5 cl, 90 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, 105 }, true, 5 },
{ " 270deg, 5 cl, 180 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { -105, 0 }, true, 5 },
{ " 270deg, 5 cl, 270 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 0, -105 }, true, 5 },
{ " 270deg, 5 cl, 45 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 75, 74 }, true, 5 },
{ " 270deg, 5 cl, -45 deg, 5 pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 75, -74 }, false, -1 },
{ " 270deg, 5 cl, 45 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 67, 68 }, true, 5 },
{ " 270deg, 5 cl, -45 deg, 5 neg", { { 0, 0 }, { 100, 0 }, 270.0 }, 5, { 67, -68 }, false, -1 },
{ " 270deg, 4 cl, 0 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 105, 0 }, false, -1 },
{ " 270deg, 4 cl, 90 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 0, 105 }, false, -1 },
{ " 270deg, 4 cl, 180 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { -105, 0 }, false, -1 },
{ " 270deg, 4 cl, 270 deg pos", { { 0, 0 }, { 100, 0 }, 270.0 }, 4, { 0, -105 }, false, -1 },
{ " 90deg, 0 cl, 0 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 71, -71 }, true, 0 },
{ " 90deg, 0 cl, 45 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 100, 0 }, true, 0 },
{ " 90deg, 0 cl, 90 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 71, 71 }, true, 0 },
{ " 90deg, 0 cl, 135 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 0, -100 }, false, -1 },
{ " 90deg, 0 cl, -45 deg ", { { 0, 0 }, { 71, -71 }, 90.0 }, 0, { 0, 100 }, false, -1 },
{ " -90deg, 0 cl, 0 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 71, -71 }, true, 0 },
{ " -90deg, 0 cl, 45 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 100, 0 }, true, 0 },
{ " -90deg, 0 cl, 90 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 71, 71 }, true, 0 },
{ " -90deg, 0 cl, 135 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 0, -100 }, false, -1 },
{ " -90deg, 0 cl, -45 deg ", { { 0, 0 }, { 71, 71 }, -90.0 }, 0, { 0, 100 }, false, -1 },
};
BOOST_AUTO_TEST_CASE( CollidePt )
{
for( const auto& c : arc_pt_collide_cases )
{
BOOST_TEST_CONTEXT( c.m_ctx_name )
{
SHAPE_ARC arc( c.m_geom.m_center_point, c.m_geom.m_start_point,
c.m_geom.m_center_angle );
// Test a zero width arc (distance should equal the clearance)
BOOST_TEST_CONTEXT( "Test Clearance" )
{
int dist = -1;
BOOST_CHECK_EQUAL( arc.Collide( c.m_point, c.m_arc_clearance, &dist ),
c.m_exp_result );
BOOST_CHECK_EQUAL( dist, c.m_exp_distance );
}
// Test by changing the width of the arc (distance should equal zero)
BOOST_TEST_CONTEXT( "Test Width" )
{
int dist = -1;
arc.SetWidth( c.m_arc_clearance * 2 );
BOOST_CHECK_EQUAL( arc.Collide( c.m_point, 0, &dist ), c.m_exp_result );
if( c.m_exp_result )
BOOST_CHECK_EQUAL( dist, 0 );
else
BOOST_CHECK_EQUAL( dist, -1 );
}
}
}
}
struct ARC_TO_POLYLINE_CASE struct ARC_TO_POLYLINE_CASE
{ {