diff --git a/qa/common/geometry/geom_test_utils.h b/qa/common/geometry/geom_test_utils.h index 5f79edc688..e658bb8ac8 100644 --- a/qa/common/geometry/geom_test_utils.h +++ b/qa/common/geometry/geom_test_utils.h @@ -31,6 +31,7 @@ #include #include +#include /** * @brief Utility functions for testing geometry functions. @@ -81,7 +82,7 @@ bool IsInQuadrant( const VECTOR2& aPoint, QUADRANT aQuadrant ) /* * @Brief Check if both ends of a segment are in Quadrant 1 */ -bool SegmentCompletelyInQuadrant( const SEG& aSeg, QUADRANT aQuadrant ) +inline bool SegmentCompletelyInQuadrant( const SEG& aSeg, QUADRANT aQuadrant ) { return IsInQuadrant( aSeg.A, aQuadrant) && IsInQuadrant( aSeg.B, aQuadrant ); @@ -90,7 +91,7 @@ bool SegmentCompletelyInQuadrant( const SEG& aSeg, QUADRANT aQuadrant ) /* * @brief Check if at least one end of the segment is in Quadrant 1 */ -bool SegmentEndsInQuadrant( const SEG& aSeg, QUADRANT aQuadrant ) +inline bool SegmentEndsInQuadrant( const SEG& aSeg, QUADRANT aQuadrant ) { return IsInQuadrant( aSeg.A, aQuadrant ) || IsInQuadrant( aSeg.B, aQuadrant ); @@ -99,14 +100,66 @@ bool SegmentEndsInQuadrant( const SEG& aSeg, QUADRANT aQuadrant ) /* * @brief Check if a segment is entirely within a certain radius of a point. */ -bool SegmentCompletelyWithinRadius( const SEG& aSeg, const VECTOR2I& aPt, - const int aRadius ) +inline bool SegmentCompletelyWithinRadius( const SEG& aSeg, const VECTOR2I& aPt, const int aRadius ) { // This is true iff both ends of the segment are within the radius return ( ( aSeg.A - aPt ).EuclideanNorm() < aRadius ) && ( ( aSeg.B - aPt ).EuclideanNorm() < aRadius ); } +/** + * Check that two points are the given distance apart, within the given tolerance. + * + * @tparam T the dimension type + * @param aPtA the first point + * @param aPtB the second point + * @param aExpDist the expected distance + * @param aTol the permitted tolerance + */ +template +bool IsPointAtDistance( const VECTOR2& aPtA, const VECTOR2& aPtB, T aExpDist, T aTol ) +{ + const int dist = ( aPtB - aPtA ).EuclideanNorm(); + const bool ok = KI_TEST::IsWithin( dist, aExpDist, aTol ); + + if( !ok ) + { + BOOST_TEST_INFO( "Points not at expected distance: distance is " << dist << ", expected " + << aExpDist ); + } + + return ok; +} + +/** + * Predicate for checking a set of points is within a certain tolerance of + * a circle + * @param aPoints the points to check + * @param aCentre the circle centre + * @param aRad the circle radius + * @param aTolEnds the tolerance for the endpoint-centre distance + * @return true if predicate met + */ +template +bool ArePointsNearCircle( + const std::vector>& aPoints, const VECTOR2& aCentre, T aRad, T aTol ) +{ + bool ok = true; + + for( unsigned i = 0; i < aPoints.size(); ++i ) + { + if( !IsPointAtDistance( aPoints[i], aCentre, aRad, aTol ) ) + { + BOOST_TEST_INFO( "Point " << i << " " << aPoints[i] << " is not within tolerance (" + << aTol << ") of radius (" << aRad << ") from centre point " + << aCentre ); + ok = false; + } + } + + return ok; +} + /* * @brief Check if two vectors are perpendicular * @@ -135,7 +188,7 @@ bool ArePerpendicular( const VECTOR2& a, const VECTOR2& b, double aToleran * @param aSize: the side width (must be divisible by 2 if want to avoid rounding) * @param aCentre: the centre of the square */ -SHAPE_LINE_CHAIN MakeSquarePolyLine( int aSize, const VECTOR2I& aCentre ) +inline SHAPE_LINE_CHAIN MakeSquarePolyLine( int aSize, const VECTOR2I& aCentre ) { SHAPE_LINE_CHAIN polyLine; @@ -154,8 +207,7 @@ SHAPE_LINE_CHAIN MakeSquarePolyLine( int aSize, const VECTOR2I& aCentre ) /* * @brief Fillet every polygon in a set and return a new set */ -SHAPE_POLY_SET FilletPolySet( SHAPE_POLY_SET& aPolySet, int aRadius, - int aError ) +inline SHAPE_POLY_SET FilletPolySet( SHAPE_POLY_SET& aPolySet, int aRadius, int aError ) { SHAPE_POLY_SET filletedPolySet; @@ -169,6 +221,27 @@ SHAPE_POLY_SET FilletPolySet( SHAPE_POLY_SET& aPolySet, int aRadius, return filletedPolySet; } +} // namespace GEOM_TEST + +BOOST_TEST_PRINT_NAMESPACE_OPEN +{ +template <> +struct print_log_value +{ + inline void operator()( std::ostream& os, const SHAPE_LINE_CHAIN& c ) + { + os << "SHAPE_LINE_CHAIN: " << c.PointCount() << " points: [\n"; + + for( int i = 0; i < c.PointCount(); ++i ) + { + os << " " << i << ": " << c.CPoint( i ) << "\n"; + } + + os << "]"; + } +}; } +BOOST_TEST_PRINT_NAMESPACE_CLOSE + #endif // GEOM_TEST_UTILS_H \ No newline at end of file diff --git a/qa/common/geometry/test_shape_arc.cpp b/qa/common/geometry/test_shape_arc.cpp index 9bcd5bd795..9e3f6342d5 100644 --- a/qa/common/geometry/test_shape_arc.cpp +++ b/qa/common/geometry/test_shape_arc.cpp @@ -23,10 +23,13 @@ #include +#include + #include #include #include +#include "geom_test_utils.h" BOOST_AUTO_TEST_SUITE( ShapeArc ) @@ -281,4 +284,96 @@ BOOST_AUTO_TEST_CASE( BasicCPAGeom ) } } + +struct ARC_TO_POLYLINE_CASE +{ + std::string m_ctx_name; + ARC_CENTRE_PT_ANGLE m_geom; +}; + + +/** + * Predicate for checking a polyline has all the points on (near) a circle of + * given centre and radius + * @param aPolyline the polyline to check + * @param aCentre the circle centre + * @param aRad the circle radius + * @param aTolEnds the tolerance for the endpoint-centre distance + * @return true if predicate met + */ +bool ArePolylinePointsNearCircle( + const SHAPE_LINE_CHAIN& aPolyline, const VECTOR2I& aCentre, int aRad, int aTolEnds ) +{ + std::vector points; + + for( int i = 0; i < aPolyline.PointCount(); ++i ) + { + points.push_back( aPolyline.CPoint( i ) ); + } + + return GEOM_TEST::ArePointsNearCircle( points, aCentre, aRad, aTolEnds ); +} + +#ifdef HAVE_EXPECTED_FAILURES + +// Failure in zero-radius case +BOOST_AUTO_TEST_CASE( ArcToPolyline, *boost::unit_test::expected_failures( 1 ) ) +{ + const std::vector cases = { + { + "Zero rad", + { + { 0, 0 }, + { 0, 0 }, + 180, + }, + }, + { + "Semicircle", + { + { 0, 0 }, + { -10, 0 }, + 180, + }, + }, + { + "Larger semicircle", + { + { 0, 0 }, + { -1000, 0 }, + 180, + }, + }, + }; + + const int width = 0; + const double accuracy = 1.0; + + for( const auto& c : cases ) + { + BOOST_TEST_CONTEXT( c.m_ctx_name ) + { + const SHAPE_ARC this_arc{ c.m_geom.m_center_point, c.m_geom.m_start_point, + c.m_geom.m_center_angle, width }; + + const SHAPE_LINE_CHAIN chain = this_arc.ConvertToPolyline( accuracy ); + + BOOST_TEST_MESSAGE( "Polyline has " << chain.PointCount() << " points" ); + + BOOST_CHECK_EQUAL( chain.CPoint( 0 ), c.m_geom.m_start_point ); + + const int radius = ( c.m_geom.m_center_point - c.m_geom.m_start_point ).EuclideanNorm(); + const int tol = 2; + + BOOST_CHECK_PREDICATE( ArePolylinePointsNearCircle, + ( chain )( c.m_geom.m_center_point )( radius )( tol ) ); + + // TODO: check midpoints are near circle too + } + } +} + +#endif // HAVE_EXPECTED_FAILURES + + BOOST_AUTO_TEST_SUITE_END()