libcruft-util/bezier2.cpp
Danny Robson fdaa5e1392 assert: split CHECK_LIMIT into INCLUSIVE and INDEX
LIMIT hid an off-by-one bug when tests used end iterators. We rename the
assertion to uncover all uses of the flawed implementation, and split it
into an identical assertion, and one intended to protect against
iterator ends.
2020-09-24 08:03:41 +10:00

180 lines
4.7 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 "polynomial.hpp"
#include "coord/iostream.hpp"
///////////////////////////////////////////////////////////////////////////////
namespace cruft {
template <>
point2f
bezier<2>::eval (float t) const
{
CHECK_INCLUSIVE(t, 0.f, 1.f);
const auto &P0 = m_coeffs[0];
const auto &P1 = m_coeffs[1];
const auto &P2 = m_coeffs[2];
return (
(1 - t) * (1 - t) * P0 +
2 * (1 - t) * t * P1 +
t * t * P2
).as<cruft::point> ();
}
}
//-----------------------------------------------------------------------------
namespace cruft {
template <>
std::array<cruft::vector2f,3>
bezier<2>::coeffs (void) const
{
auto &v = m_coeffs;
return {
+1.f * v[2] -2.f * v[1] + 1.f * v[0],
-2.f * v[2] +2.f * v[1],
+1.f * v[2]
};
}
}
///////////////////////////////////////////////////////////////////////////////
namespace cruft {
template <>
cruft::vector2f
bezier<2>::d1 (const float t) const noexcept
{
CHECK_INCLUSIVE (t, 0.f, 1.f);
const auto &P0 = m_coeffs[0];
const auto &P1 = m_coeffs[1];
const auto &P2 = m_coeffs[2];
return 2 * (1 - t) * (P1 - P0) +
2 * t * (P2 - P1);
}
}
///////////////////////////////////////////////////////////////////////////////
namespace cruft {
template <>
sdot_t
bezier<2>::sdot (point2f q) const noexcept
{
// setup inter-point vectors
const auto ab = m_points[1] - m_points[0];
const auto bc = m_points[2] - m_points[1];
const auto qa = m_points[0] - q;
const auto qb = m_points[1] - q;
const auto qc = m_points[2] - q;
// setup variables we want to minimise
float d = std::numeric_limits<float>::infinity ();
float t = std::numeric_limits<float>::quiet_NaN ();
// distance from A
const auto d_a = sign (cross (ab, qa)) * norm2 (qa);
if (abs (d_a) < abs (d)) {
d = d_a;
t = -dot (ab, qa) / norm2 (ab);
}
// distance from B
const auto d_b = sign (cross (bc, qc)) * norm2 (qc);
if (abs (d_b) < abs (d)) {
d = d_b;
t = -dot (bc, qb) / norm2 (bc);
}
// Using procedure from: http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html
//
// Parametric form: P(t) = (1-t)^2*P0 + 2t(1-t)P1 + t^2*P2
//
// Derivative: dP/dt = -2(1-t)P0 + 2(1-2t)P1 + 2tP2
// = 2(A+Bt), A=(P1-P0), B=(P2-P1-A)
//
const auto &p0 = m_points[0];
const auto &p1 = m_points[1];
const auto &p2 = m_points[2];
const auto A = p1 - p0;
const auto B = p2 - p1 - A;
// Make: dot (q, dP/dt) == 0
// dot (M - P(t), A + Bt) == 0
//
// Solve: at^3 + bt^2 + ct + d,
// a = B^2,
// b = 3A.B,
// c = 2A^2+M'.B,
// d = M'.A,
// M' = P0-M
const auto M = q;
const auto M_ = p0 - M;
const std::array<float,4>
poly = {
dot (B, B),
3 * dot (A, B),
2 * dot (A, A) + dot (M_, B),
dot (M_, A),
};
// test at polynomial minima
for (const auto r: polynomial::roots<3> (poly)) {
// bail if we have fewer roots than expected
if (std::isnan (r))
break;
// ignore if this root is off the curve
if (r < 0 || r > 1)
continue;
const auto qe = eval (r) - q;
const auto d_e = sign (cross (ab, qe)) * norm2 (qe);
if (abs (d_e) <= abs (d)) {
d = d_e;
t = r;
}
}
// calculate the angles from the point to the endpoints if needed
d = sign (d) * std::sqrt (abs (d));
if (t >= 0 && t <= 1)
return { d, 0 };
if (t < 0) {
return { d, abs (dot (normalised (ab), normalised (qa))) };
} else
return { d, abs (dot (normalised (bc), normalised (qc))) };
}
}
///////////////////////////////////////////////////////////////////////////////
namespace cruft {
template <>
float
bezier<2>::distance (cruft::point2f q) const noexcept
{
return abs (sdot (q).distance);
}
}