libcruft-util/bezier.cpp

191 lines
4.8 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2015-2016 Danny Robson <danny@nerdcruft.net>
*/
#include "bezier.hpp"
#include "debug/assert.hpp"
#include "polynomial.hpp"
#include "stream.hpp"
#include "coord/iostream.hpp"
#include <algorithm>
#include <iterator>
///////////////////////////////////////////////////////////////////////////////
template <size_t N>
cruft::bezier<N>::bezier (const cruft::point2f (&_points)[N+1])
{
std::copy (_points, _points + N + 1, m_points);
}
//-----------------------------------------------------------------------------
// 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 N>
size_t
cruft::bezier<N>::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,N+1> bcoeff = coeffs ();
std::array<float,N+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::roots<N> (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 < N; ++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 cruft::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;
}
//-----------------------------------------------------------------------------
template <size_t N>
cruft::region2f
cruft::bezier<N>::region (void) const
{
float x0 = m_points[0].x;
float y0 = m_points[0].y;
float x1 = x0;
float y1 = y0;
for (size_t i = 1; i < N+1; ++i) {
x0 = min (x0, m_points[i].x);
y0 = min (y0, m_points[i].y);
x1 = max (x1, m_points[i].x);
y1 = max (y1, m_points[i].y);
}
cruft::point2f p0 { x0, y0 };
cruft::point2f p1 { x1, y1 };
return { p0, p1 };
}
//-----------------------------------------------------------------------------
template <size_t N>
cruft::point2f&
cruft::bezier<N>::operator[] (size_t idx)
{
CHECK_LE (idx, N);
return m_points[idx];
}
//-----------------------------------------------------------------------------
template <size_t N>
const cruft::point2f&
cruft::bezier<N>::operator[] (size_t idx) const
{
CHECK_LE (idx, N);
return m_points[idx];
}
///////////////////////////////////////////////////////////////////////////////
template <size_t N>
const cruft::point2f*
cruft::bezier<N>::begin (void) const
{
return std::cbegin (m_points);
}
//-----------------------------------------------------------------------------
template <size_t N>
const cruft::point2f*
cruft::bezier<N>::end (void) const
{
return std::cend (m_points);
}
//-----------------------------------------------------------------------------
template <size_t N>
const cruft::point2f*
cruft::bezier<N>::cbegin (void) const
{
return std::cbegin (m_points);
}
//-----------------------------------------------------------------------------
template <size_t N>
const cruft::point2f*
cruft::bezier<N>::cend (void) const
{
return std::cend (m_points);
}
///////////////////////////////////////////////////////////////////////////////
template <size_t N>
std::ostream&
cruft::operator<< (std::ostream &os, const bezier<N> &b)
{
using value_type = decltype(*b.cbegin());
os << "[";
std::transform (std::cbegin (b),
std::cend (b),
iterator::infix_iterator<value_type> (os, ", "),
[] (auto i) { return +i; });
os << "]";
return os;
}
///////////////////////////////////////////////////////////////////////////////
#define INSTANTIATE(N) \
template class cruft::bezier<N>; \
template std::ostream& cruft::operator<< (std::ostream&, const bezier<N>&);
INSTANTIATE(1)
INSTANTIATE(2)
INSTANTIATE(3)