QA: add a test for CalcArcCenter( aStart, aMid, aEnd )
This commit is contained in:
parent
df83e24eb7
commit
b7824adfb1
|
@ -31,12 +31,243 @@
|
||||||
#include <trigo.h>
|
#include <trigo.h>
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
CircleCenterFrom3Points calculate the center of a circle defined by 3 points
|
||||||
|
It is similar to CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
|
||||||
|
but it was needed to debug CalcArcCenter, so I keep it available for other issues in CalcArcCenter
|
||||||
|
|
||||||
|
The perpendicular bisector of the segment between two points is the
|
||||||
|
set of all points equidistant from both. So if you take the
|
||||||
|
perpendicular bisector of (x1,y1) and (x2,y2) and the perpendicular
|
||||||
|
bisector of the segment from (x2,y2) to (x3,y3) and find the
|
||||||
|
intersection of those lines, that point will be the center.
|
||||||
|
|
||||||
|
To find the equation of the perpendicular bisector of (x1,y1) to (x2,y2),
|
||||||
|
you know that it passes through the midpoint of the segment:
|
||||||
|
((x1+x2)/2,(y1+y2)/2), and if the slope of the line
|
||||||
|
connecting (x1,y1) to (x2,y2) is m, the slope of the perpendicular
|
||||||
|
bisector is -1/m. Work out the equations for the two lines, find
|
||||||
|
their intersection, and bingo! You've got the coordinates of the center.
|
||||||
|
|
||||||
|
An error should occur if the three points lie on a line, and you'll
|
||||||
|
need special code to check for the case where one of the slopes is zero.
|
||||||
|
|
||||||
|
see https://web.archive.org/web/20171223103555/http://mathforum.org/library/drmath/view/54323.html
|
||||||
|
*/
|
||||||
|
static bool Ref0CircleCenterFrom3Points( const VECTOR2D& p1, const VECTOR2D& p2, const VECTOR2D& p3,
|
||||||
|
VECTOR2D* aCenter )
|
||||||
|
{
|
||||||
|
// Move coordinate origin to p2, to simplify calculations
|
||||||
|
VECTOR2D b = p1 - p2;
|
||||||
|
VECTOR2D d = p3 - p2;
|
||||||
|
double bc = ( b.x * b.x + b.y * b.y ) / 2.0;
|
||||||
|
double cd = ( -d.x * d.x - d.y * d.y ) / 2.0;
|
||||||
|
double det = -b.x * d.y + d.x * b.y;
|
||||||
|
|
||||||
|
if( fabs( det ) < 1.0e-6 ) // arbitrary limit to avoid divide by 0
|
||||||
|
return false;
|
||||||
|
|
||||||
|
det = 1 / det;
|
||||||
|
aCenter->x = ( -bc * d.y - cd * b.y ) * det;
|
||||||
|
aCenter->y = ( b.x * cd + d.x * bc ) * det;
|
||||||
|
*aCenter += p2;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Based on https://paulbourke.net/geometry/circlesphere/
|
||||||
|
// Uses the Y'b equation too, depending on slope values
|
||||||
|
static const VECTOR2D Ref1CalcArcCenter( const VECTOR2D& aStart, const VECTOR2D& aMid, const VECTOR2D& aEnd )
|
||||||
|
{
|
||||||
|
VECTOR2D center;
|
||||||
|
double yDelta_21 = aMid.y - aStart.y;
|
||||||
|
double xDelta_21 = aMid.x - aStart.x;
|
||||||
|
double yDelta_32 = aEnd.y - aMid.y;
|
||||||
|
double xDelta_32 = aEnd.x - aMid.x;
|
||||||
|
|
||||||
|
// This is a special case for aMid as the half-way point when aSlope = 0 and bSlope = inf
|
||||||
|
// or the other way around. In that case, the center lies in a straight line between
|
||||||
|
// aStart and aEnd
|
||||||
|
if( ( ( xDelta_21 == 0.0 ) && ( yDelta_32 == 0.0 ) )
|
||||||
|
|| ( ( yDelta_21 == 0.0 ) && ( xDelta_32 == 0.0 ) ) )
|
||||||
|
{
|
||||||
|
center.x = ( aStart.x + aEnd.x ) / 2.0;
|
||||||
|
center.y = ( aStart.y + aEnd.y ) / 2.0;
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent div=0 errors
|
||||||
|
/*if( xDelta_21 == 0.0 )
|
||||||
|
xDelta_21 = std::numeric_limits<double>::epsilon();
|
||||||
|
|
||||||
|
if( xDelta_32 == 0.0 )
|
||||||
|
xDelta_32 = -std::numeric_limits<double>::epsilon();*/
|
||||||
|
|
||||||
|
double aSlope = yDelta_21 / xDelta_21;
|
||||||
|
double bSlope = yDelta_32 / xDelta_32;
|
||||||
|
|
||||||
|
if( aSlope == bSlope )
|
||||||
|
{
|
||||||
|
if( aStart == aEnd )
|
||||||
|
{
|
||||||
|
// This is a special case for a 360 degrees arc. In this case, the center is halfway between
|
||||||
|
// the midpoint and either end point
|
||||||
|
center.x = ( aStart.x + aMid.x ) / 2.0;
|
||||||
|
center.y = ( aStart.y + aMid.y ) / 2.0;
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If the points are colinear, the center is at infinity, so offset
|
||||||
|
// the slope by a minimal amount
|
||||||
|
// Warning: This will induce a small error in the center location
|
||||||
|
/*aSlope += std::numeric_limits<double>::epsilon();
|
||||||
|
bSlope -= std::numeric_limits<double>::epsilon();*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
center.x = ( aSlope * bSlope * ( aStart.y - aEnd.y ) + bSlope * ( aStart.x + aMid.x )
|
||||||
|
- aSlope * ( aMid.x + aEnd.x ) )
|
||||||
|
/ ( 2 * ( bSlope - aSlope ) );
|
||||||
|
|
||||||
|
if( std::abs( aSlope ) > std::abs( bSlope ) )
|
||||||
|
{
|
||||||
|
center.y = ( ( ( aStart.x + aMid.x ) / 2.0 - center.x ) / aSlope
|
||||||
|
+ ( aStart.y + aMid.y ) / 2.0 );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
center.y =
|
||||||
|
( ( ( aMid.x + aEnd.x ) / 2.0 - center.x ) / bSlope + ( aMid.y + aEnd.y ) / 2.0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct TEST_CALC_ARC_CENTER_CASE
|
||||||
|
{
|
||||||
|
VECTOR2I istart;
|
||||||
|
VECTOR2I imid;
|
||||||
|
VECTOR2I iend;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Declare the test suite
|
* Declare the test suite
|
||||||
*/
|
*/
|
||||||
BOOST_AUTO_TEST_SUITE( KiMath )
|
BOOST_AUTO_TEST_SUITE( KiMath )
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE( TestCalcArcCenter3Pts )
|
||||||
|
{
|
||||||
|
// Order: start, mid, end
|
||||||
|
std::vector<TEST_CALC_ARC_CENTER_CASE> calc_center_cases = {
|
||||||
|
//{ { 183000000, 89000000 }, { 185333332, 89000000 }, { 185333333, 91333333 } } // Fails currently
|
||||||
|
};
|
||||||
|
|
||||||
|
const double tolerance = 1.0;
|
||||||
|
|
||||||
|
// Check random points (fails currently)
|
||||||
|
/*for( int i = 0; i < 100; i++ )
|
||||||
|
{
|
||||||
|
TEST_CALC_ARC_CENTER_CASE cas;
|
||||||
|
|
||||||
|
cas.istart.x = rand();
|
||||||
|
cas.istart.y = rand();
|
||||||
|
|
||||||
|
cas.imid.x = rand();
|
||||||
|
cas.imid.y = rand();
|
||||||
|
|
||||||
|
cas.iend.x = rand();
|
||||||
|
cas.iend.y = rand();
|
||||||
|
|
||||||
|
calc_center_cases.push_back( cas );
|
||||||
|
}*/
|
||||||
|
|
||||||
|
for( const TEST_CALC_ARC_CENTER_CASE& entry : calc_center_cases )
|
||||||
|
{
|
||||||
|
wxString msg;
|
||||||
|
|
||||||
|
VECTOR2D start( entry.istart );
|
||||||
|
VECTOR2D mid( entry.imid );
|
||||||
|
VECTOR2D end( entry.iend );
|
||||||
|
|
||||||
|
VECTOR2D calcCenter = CalcArcCenter( start, mid, end );
|
||||||
|
|
||||||
|
double crs = ( calcCenter - start ).EuclideanNorm();
|
||||||
|
double crm = ( calcCenter - mid ).EuclideanNorm();
|
||||||
|
double cre = ( calcCenter - end ).EuclideanNorm();
|
||||||
|
|
||||||
|
double cavg = ( crs + crm + cre ) / 3.0;
|
||||||
|
|
||||||
|
if( std::abs( crs - cavg ) > tolerance || std::abs( crm - cavg ) > tolerance
|
||||||
|
|| std::abs( cre - cavg ) > tolerance )
|
||||||
|
{
|
||||||
|
msg << "CalcArcCenter failed.";
|
||||||
|
|
||||||
|
msg << "\nstart: " << entry.istart.Format();
|
||||||
|
msg << "\nmid: " << entry.imid.Format();
|
||||||
|
msg << "\nend: " << entry.iend.Format();
|
||||||
|
|
||||||
|
{
|
||||||
|
msg << "\nCalculated center: " << wxString::Format( "%.15f", calcCenter.x ) << ","
|
||||||
|
<< wxString::Format( "%.15f", calcCenter.y );
|
||||||
|
|
||||||
|
msg << "\n Avg radius: " << wxString::Format( "%.15f", cavg );
|
||||||
|
msg << "\nStart radius: " << wxString::Format( "%.15f", crs );
|
||||||
|
msg << "\n Mid radius: " << wxString::Format( "%.15f", crm );
|
||||||
|
msg << "\n End radius: " << wxString::Format( "%.15f", cre );
|
||||||
|
msg << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
VECTOR2D ref0Center;
|
||||||
|
Ref0CircleCenterFrom3Points( start, mid, end, &ref0Center );
|
||||||
|
|
||||||
|
double r0_rs = ( ref0Center - start ).EuclideanNorm();
|
||||||
|
double r0_rm = ( ref0Center - mid ).EuclideanNorm();
|
||||||
|
double r0_rre = ( ref0Center - end ).EuclideanNorm();
|
||||||
|
|
||||||
|
double r0_ravg = ( r0_rs + r0_rm + r0_rre ) / 3.0;
|
||||||
|
|
||||||
|
msg << "\nReference0 center: " << wxString::Format( "%.15f", ref0Center.x ) << ","
|
||||||
|
<< wxString::Format( "%.15f", ref0Center.y );
|
||||||
|
|
||||||
|
msg << "\nRef0 Avg radius: " << wxString::Format( "%.15f", r0_ravg );
|
||||||
|
msg << "\nRef0 Start radius: " << wxString::Format( "%.15f", r0_rs );
|
||||||
|
msg << "\nRef0 Mid radius: " << wxString::Format( "%.15f", r0_rm );
|
||||||
|
msg << "\nRef0 End radius: " << wxString::Format( "%.15f", r0_rre );
|
||||||
|
msg << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
VECTOR2D ref1Center = Ref1CalcArcCenter( start, mid, end );
|
||||||
|
|
||||||
|
double r1_rs = ( ref1Center - start ).EuclideanNorm();
|
||||||
|
double r1_rm = ( ref1Center - mid ).EuclideanNorm();
|
||||||
|
double r1_rre = ( ref1Center - end ).EuclideanNorm();
|
||||||
|
|
||||||
|
double r1_ravg = ( r1_rs + r1_rm + r1_rre ) / 3.0;
|
||||||
|
|
||||||
|
msg << "\nReference1 center: " << wxString::Format( "%.15f", ref1Center.x ) << ","
|
||||||
|
<< wxString::Format( "%.15f", ref1Center.y );
|
||||||
|
|
||||||
|
msg << "\nRef1 Avg radius: " << wxString::Format( "%.15f", r1_ravg );
|
||||||
|
msg << "\nRef1 Start radius: " << wxString::Format( "%.15f", r1_rs );
|
||||||
|
msg << "\nRef1 Mid radius: " << wxString::Format( "%.15f", r1_rm );
|
||||||
|
msg << "\nRef1 End radius: " << wxString::Format( "%.15f", r1_rre );
|
||||||
|
msg << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_CHECK_MESSAGE( false, msg );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE( TestInterceptsPositiveX )
|
BOOST_AUTO_TEST_CASE( TestInterceptsPositiveX )
|
||||||
{
|
{
|
||||||
BOOST_CHECK( !InterceptsPositiveX( 10.0, 20.0 ) );
|
BOOST_CHECK( !InterceptsPositiveX( 10.0, 20.0 ) );
|
||||||
|
|
Loading…
Reference in New Issue