bezier: add intersection count test

This commit is contained in:
Danny Robson 2015-01-29 15:47:40 +11:00
parent 54cb3c2153
commit cf5a682959
3 changed files with 275 additions and 49 deletions

View File

@ -138,6 +138,110 @@ namespace util {
}
//-----------------------------------------------------------------------------
namespace util {
template <>
std::array<util::vector2f,4>
bezier<3>::coeffs (void) const
{
auto &v = reinterpret_cast<const util::vector2f(&)[4]> (m_points);
return {
-1 * v[0] +3 * v[1] -3 * v[2] +1 * v[3],
3 * v[0] -6 * v[1] +3 * v[2],
-3 * v[0] +3 * v[1],
1 * v[0]
};
}
}
//-----------------------------------------------------------------------------
namespace util {
template <>
std::array<util::vector2f,3>
bezier<2>::coeffs (void) const
{
auto &v = reinterpret_cast<const util::vector2f(&)[3]> (m_points);
return {
+1 * v[2] -2 * v[1] + 1 * v[0],
-2 * v[2] +2 * v[1],
+1 * v[2]
};
}
}
//-----------------------------------------------------------------------------
namespace util {
template <>
std::array<util::vector2f,2>
bezier<1>::coeffs (void) const
{
auto &v = reinterpret_cast<const util::vector2f(&)[2]> (m_points);
return {
-1 * v[1] + 1 * v[0],
+1 * v[1],
};
}
}
//-----------------------------------------------------------------------------
// XXX: If the line is co-linear we'll have no solutions. But we return 1
// anyway as this function is used to find any point that intersects as part
// of other more comprehensive tests.
template <size_t S>
size_t
util::bezier<S>::intersections (point2f p0, point2f p1) const
{
float A = p1.y - p0.y; // A = y2 - y1
float B = p0.x - p1.x; // B = x1 - x2
float C = p0.x * (p0.y - p1.y) + // C = x1 (y1 - y2) + y1 (x2 - x1)
p0.y * (p1.x - p0.x);
// Build the intersection polynomial
const std::array<vector2f,S+1> bcoeff = coeffs ();
std::array<float,S+1> pcoeff;
for (size_t i = 0; i < pcoeff.size (); ++i)
pcoeff[i] = A * bcoeff[i].x + B * bcoeff[i].y;
pcoeff.back () += C;
const auto r = polynomial::solve<S> (pcoeff);
// The curve and line are colinear
if (std::all_of (r.begin (), r.end (), [] (auto i) { return std::isnan (i); }))
return 1;
size_t count = 0;
for (size_t i = 0; i < S; ++i) {
// Ensure the solutions are on the curve
const auto t = r[i];
if (std::isnan (t))
break;
if (t < 0.f || t > 1.f)
continue;
// Find the line's intersection point
const util::vector2f q = polynomial::eval (bcoeff, t);
const auto s = almost_equal (p0.x, p1.x) ?
(q.y-p0.y) / (p1.y-p0.y) :
(q.x-p0.x) / (p1.x-p0.x) ; // vertical
// Check if the point is on the line
if (s >= 0.f && s <= 1.f)
++count;
}
return count;
}
//-----------------------------------------------------------------------------
namespace util {
// TODO: use a more reliable method like [Xiao-Dia Chen 2010]

View File

@ -31,6 +31,13 @@ namespace util {
bezier (const util::point2f (&)[S+1]);
point2f eval (float t) const;
// Calculate the expanded polynomial coeffecients in terms of t
std::array<vector2f,S+1>
coeffs (void) const;
size_t intersections (point2f from, point2f to) const;
float distance (point2f) const;
point2f& operator[] (size_t idx);

View File

@ -4,59 +4,174 @@
#include <cstdlib>
//-----------------------------------------------------------------------------
template <size_t> void test_eval (void);
template <size_t> void test_intersect (void);
//-----------------------------------------------------------------------------
template <>
void
test_eval<1> (void)
{
static const util::bezier<1> b1 ({{ 0.f, 0.f},
{100.f, 100.f}});
auto p0 = b1.eval(0.f);
auto p1 = b1.eval(1.f);
CHECK_EQ (p0, b1[0]);
CHECK_EQ (p1, b1[1]);
auto px = b1.eval(0.5f);
auto rx = b1[0] + 0.5f * (b1[1]-b1[0]);
CHECK_EQ (px, rx);
}
//-----------------------------------------------------------------------------
template <>
void
test_eval<2> (void)
{
static const util::bezier<2> b2 ({{ 0.f, 0.f},
{ 50.f, 50.f},
{100.f, 100.f}});
auto p0 = b2.eval(0.f);
auto p2 = b2.eval(1.f);
CHECK_EQ (p0, b2[0]);
CHECK_EQ (p2, b2[2]);
auto px = b2.eval(0.5f);
auto rx = b2[0] + 0.5f * (b2[2]-b2[0]);
CHECK_EQ (px, rx);
}
//-----------------------------------------------------------------------------
template <>
void
test_eval<3> (void)
{
static const util::bezier<3> b3 ({{ 0.f, 0.f },
{ 33.f, 33.f },
{ 67.f, 67.f },
{ 100.f, 100.f }});
auto p0 = b3.eval (0.f);
auto p3 = b3.eval (1.f);
CHECK_EQ (p0, b3[0]);
CHECK_EQ (p3, b3[3]);
auto px = b3.eval (0.5f);
auto rx = b3[0] + 0.5f * (b3[3] - b3[0]);
CHECK_EQ (px, rx);
}
//-----------------------------------------------------------------------------
template <>
void
test_intersect<1> (void)
{
// A line from (0,0) to (100,100)
static const util::bezier<1> b1 ({{0.f, 0.f}, {100.f, 100.f}});
// Through the centre
CHECK_EQ (b1.intersections ({0.f, 100.f}, {100.f, 0.f}), 1);
CHECK_EQ (b1.intersections ({100.f, 0.f}, {0.f, 100.f}), 1);
// Coincident with endpoints
CHECK_EQ (b1.intersections ({0.f, 0.f}, {1.f,0.f}), 1);
CHECK_EQ (b1.intersections ({100.f, 100.f}, {100.f,0.f}), 1);
// Co-planar
CHECK_EQ (b1.intersections ({0.f, 0.f}, {1.f, 1.f}), 1);
// Underneath
CHECK_EQ (b1.intersections ({1000.f, -10.f}, {-1000.f, -10.f}), 0);
// Above
CHECK_EQ (b1.intersections ({1000.f, 110.f}, {-1000.f, 110.f}), 0);
}
//-----------------------------------------------------------------------------
template <>
void
test_intersect<2> (void)
{
// A linear curve from (0,0) to (100,100)
static const util::bezier<2> b2 ({{ 0.0f, 0.0f},
{ 50.f, 50.f},
{100.f, 100.f}});
// Through the centre
CHECK_EQ (b2.intersections ({100.f, 0.f}, {0.f, 100.f}), 1);
CHECK_EQ (b2.intersections ({0.f, 100.f}, {100.f, 0.f}), 1);
// Coincident with endpoints
CHECK_EQ (b2.intersections ({0.f, 0.f}, {0.f,100.f}), 1);
CHECK_EQ (b2.intersections ({0.f, 0.f}, {100.f,0.f}), 1);
CHECK_EQ (b2.intersections ({100.f, 100.f}, {100.f,0.f}), 1);
// Co-planar
CHECK_EQ (b2.intersections ({0.f, 0.f}, {1.f, 1.f}), 1);
// Underneath
CHECK_EQ (b2.intersections ({1000.f, -10.f}, {-1000.f, -10.f}), 0);
// Above
CHECK_EQ (b2.intersections ({1000.f, 110.f}, {-1000.f, 110.f}), 0);
}
//-----------------------------------------------------------------------------
template <>
void
test_intersect<3> (void)
{
// A linear curve from (0,0) to (100,100)
static const util::bezier<3> b3 ({{ 0.f, 0.f },
{ 33.f, 33.f },
{ 67.f, 67.f },
{ 100.f, 100.f }});
// Through the centre
CHECK_EQ (b3.intersections ({100.f, 0.f}, {0.f, 100.f}), 1);
CHECK_EQ (b3.intersections ({0.f, 100.f}, {100.f, 0.f}), 1);
// Coincident with endpoints
CHECK_EQ (b3.intersections ({0.f, 0.f}, {0.f,100.f}), 1);
CHECK_EQ (b3.intersections ({0.f, 0.f}, {100.f,0.f}), 1);
CHECK_EQ (b3.intersections ({100.f, 100.f}, {100.f,0.f}), 1);
// Co-planar
CHECK_EQ (b3.intersections ({0.f, 0.f}, {1.f, 1.f}), 1);
// Underneath
CHECK_EQ (b3.intersections ({1000.f, -10.f}, {-1000.f, -10.f}), 0);
// Above
CHECK_EQ (b3.intersections ({1000.f, 110.f}, {-1000.f, 110.f}), 0);
}
//-----------------------------------------------------------------------------
int
main (int, char**)
{
// Check degree-1 beziers
{
static const util::bezier<1> b1 ({{ 0.f, 0.f},
{100.f, 100.f}});
test_eval<1> ();
test_eval<2> ();
test_eval<3> ();
auto p0 = b1.eval(0.f);
auto p1 = b1.eval(1.f);
CHECK_EQ (p0, b1[0]);
CHECK_EQ (p1, b1[1]);
auto px = b1.eval(0.5f);
auto rx = b1[0] + 0.5f * (b1[1]-b1[0]);
CHECK_EQ (px, rx);
}
// Check degree-2 beziers
{
static const util::bezier<2> b2 ({{ 0.f, 0.f},
{ 50.f, 50.f},
{100.f, 100.f}});
auto p0 = b2.eval(0.f);
auto p2 = b2.eval(1.f);
CHECK_EQ (p0, b2[0]);
CHECK_EQ (p2, b2[2]);
auto px = b2.eval(0.5f);
auto rx = b2[0] + 0.5f * (b2[2]-b2[0]);
CHECK_EQ (px, rx);
}
// Check degree-3 beziers
{
static const util::bezier<3> b3 ({{ 0.f, 0.f },
{ 33.f, 33.f },
{ 67.f, 67.f },
{ 100.f, 100.f }});
auto p0 = b3.eval (0.f);
auto p3 = b3.eval (1.f);
CHECK_EQ (p0, b3[0]);
CHECK_EQ (p3, b3[3]);
auto px = b3.eval (0.5f);
auto rx = b3[0] + 0.5f * (b3[3] - b3[0]);
CHECK_EQ (px, rx);
}
test_intersect<1> ();
test_intersect<2> ();
test_intersect<3> ();
return EXIT_SUCCESS;
}