bezier: add intersection count test
This commit is contained in:
parent
54cb3c2153
commit
cf5a682959
104
bezier.cpp
104
bezier.cpp
@ -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 {
|
namespace util {
|
||||||
// TODO: use a more reliable method like [Xiao-Dia Chen 2010]
|
// TODO: use a more reliable method like [Xiao-Dia Chen 2010]
|
||||||
|
@ -31,6 +31,13 @@ namespace util {
|
|||||||
bezier (const util::point2f (&)[S+1]);
|
bezier (const util::point2f (&)[S+1]);
|
||||||
|
|
||||||
point2f eval (float t) const;
|
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;
|
float distance (point2f) const;
|
||||||
|
|
||||||
point2f& operator[] (size_t idx);
|
point2f& operator[] (size_t idx);
|
||||||
|
213
test/bezier.cpp
213
test/bezier.cpp
@ -4,59 +4,174 @@
|
|||||||
|
|
||||||
#include <cstdlib>
|
#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
|
int
|
||||||
main (int, char**)
|
main (int, char**)
|
||||||
{
|
{
|
||||||
// Check degree-1 beziers
|
test_eval<1> ();
|
||||||
{
|
test_eval<2> ();
|
||||||
static const util::bezier<1> b1 ({{ 0.f, 0.f},
|
test_eval<3> ();
|
||||||
{100.f, 100.f}});
|
|
||||||
|
|
||||||
auto p0 = b1.eval(0.f);
|
test_intersect<1> ();
|
||||||
auto p1 = b1.eval(1.f);
|
test_intersect<2> ();
|
||||||
|
test_intersect<3> ();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user