2024-12-02 22:09:35 -08:00
|
|
|
|
#include "RDP.hpp"
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
// Implementation
|
|
|
|
|
|
/******************************************************************************/
|
|
|
|
|
|
|
|
|
|
|
|
// We'll typically end up with a ton of points to draw for the spectrum,
|
|
|
|
|
|
// and some simplification is worthwhile; use the Ramer–Douglas–Peucker
|
|
|
|
|
|
// algorithm to reduce to a smaller number of points.
|
|
|
|
|
|
//
|
|
|
|
|
|
// We'll modify the inbound polygon in place, such that anything we want
|
|
|
|
|
|
// to keep is at the start of the polygon and anything we want to omit
|
|
|
|
|
|
// is at the end, returning an iterator to the new end, i.e., the point
|
|
|
|
|
|
// one past the last point we want to keep.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Our goal here is to avoid reallocations. Since we're at worst going to
|
|
|
|
|
|
// be leaving this the same size, we should be able to work with what we
|
|
|
|
|
|
// have already.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Note that this is a functor; it's serially reusable, but not reentrant.
|
|
|
|
|
|
// Call it from one thread only. In practical use, that's not expected to
|
|
|
|
|
|
// be a problem, and it allows us to reuse allocated memory in a serial
|
|
|
|
|
|
// manner, rather than requesting it and freeing it constantly.
|
|
|
|
|
|
|
|
|
|
|
|
QPolygonF::iterator
|
|
|
|
|
|
RDP::operator()(QPolygonF & polygon,
|
|
|
|
|
|
qreal const epsilon)
|
|
|
|
|
|
{
|
|
|
|
|
|
// There's no point in proceeding with less than 3 points.
|
|
|
|
|
|
|
|
|
|
|
|
if (polygon.size() < 3) return polygon.end();
|
|
|
|
|
|
|
2024-12-03 07:23:55 -08:00
|
|
|
|
// We're always going to keep the first and last points; all others are
|
|
|
|
|
|
// initially in play. Prime the stack with the full span; run the stack
|
|
|
|
|
|
// machine until it empties.
|
|
|
|
|
|
|
|
|
|
|
|
array.clear();
|
|
|
|
|
|
array.resize(polygon.size());
|
|
|
|
|
|
array.setBit(0);
|
|
|
|
|
|
array.setBit(polygon.size() - 1);
|
2024-12-02 22:09:35 -08:00
|
|
|
|
stack.push({0, polygon.size() - 1});
|
|
|
|
|
|
|
|
|
|
|
|
while (!stack.isEmpty())
|
|
|
|
|
|
{
|
|
|
|
|
|
auto const [
|
|
|
|
|
|
index1,
|
|
|
|
|
|
index2
|
|
|
|
|
|
] = stack.pop();
|
|
|
|
|
|
|
|
|
|
|
|
// Create a theoretical line between the first and last points
|
|
|
|
|
|
// in the span we're presently considering; compute the vector
|
|
|
|
|
|
// components and the line length.
|
|
|
|
|
|
|
|
|
|
|
|
auto const & p1 = polygon[index1];
|
|
|
|
|
|
auto const & p2 = polygon[index2];
|
|
|
|
|
|
auto const dx = p2.x() - p1.x();
|
|
|
|
|
|
auto const dy = p2.y() - p1.y();
|
|
|
|
|
|
auto const ll = std::hypot(dx, dy);
|
|
|
|
|
|
|
|
|
|
|
|
// Find the point within the span at the largest perpendicular
|
2024-12-03 16:03:26 -08:00
|
|
|
|
// distance from the line greater than epsilon, if any.
|
2024-12-02 22:09:35 -08:00
|
|
|
|
|
2024-12-03 16:38:23 -08:00
|
|
|
|
qreal limit = epsilon;
|
2024-12-03 16:03:26 -08:00
|
|
|
|
qsizetype index = 0;
|
2024-12-02 22:09:35 -08:00
|
|
|
|
|
|
|
|
|
|
for (auto i = index1 + 1;
|
|
|
|
|
|
i < index2;
|
|
|
|
|
|
++i)
|
|
|
|
|
|
{
|
2024-12-03 18:32:22 -08:00
|
|
|
|
auto const & pi = polygon[i];
|
|
|
|
|
|
auto const pd = std::abs(dy * (pi.x() - p1.x()) -
|
|
|
|
|
|
dx * (pi.y() - p1.y())) / ll;
|
|
|
|
|
|
if (pd > limit)
|
2024-12-03 07:23:55 -08:00
|
|
|
|
{
|
2024-12-03 18:32:22 -08:00
|
|
|
|
limit = pd;
|
2024-12-03 07:23:55 -08:00
|
|
|
|
index = i;
|
2024-12-02 22:09:35 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-03 16:03:26 -08:00
|
|
|
|
// If index is non-zero, that's our point. Keep it and break the
|
|
|
|
|
|
// span into two spans at it, then continue working the problem.
|
2024-12-02 22:09:35 -08:00
|
|
|
|
|
2024-12-03 16:03:26 -08:00
|
|
|
|
if (index)
|
2024-12-02 22:09:35 -08:00
|
|
|
|
{
|
2024-12-03 07:23:55 -08:00
|
|
|
|
array.setBit(index);
|
2024-12-02 22:09:35 -08:00
|
|
|
|
stack.push({index1, index});
|
|
|
|
|
|
stack.push({index, index2});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Our array now contains bits set to true for every point that
|
2024-12-03 07:23:55 -08:00
|
|
|
|
// should be kept, false for those that should be removed. Move
|
2024-12-02 22:09:35 -08:00
|
|
|
|
// everything we want to keep to the front and return the first
|
|
|
|
|
|
// element to remove.
|
|
|
|
|
|
|
2024-12-03 04:47:42 -08:00
|
|
|
|
auto first = polygon.begin();
|
|
|
|
|
|
|
|
|
|
|
|
for (qsizetype i = 0; i < polygon.size(); ++i)
|
2024-12-02 22:09:35 -08:00
|
|
|
|
{
|
2024-12-03 07:23:55 -08:00
|
|
|
|
if (array.at(i)) *first++ = std::move(polygon[i]);
|
2024-12-03 04:47:42 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return first;
|
2024-12-02 22:09:35 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************************/
|