openspades/Sources/Draw/CellToTriangle.h

305 lines
7.4 KiB
C++

/*
Copyright (c) 2013 yvt
Triangulates arbitary polygons represented by cells.
Algorithm's time complexity is roughly O(W*H).
Created to fix #213 ( https://github.com/yvt/openspades/issues/213 ).
See this web page for algorithm demonstration:
https://dl.dropboxusercontent.com/u/37804131/triangulate-2.html
BSD license.
*/
#include <algorithm>
#include <cassert>
#include <list>
#include <memory>
#include <vector>
namespace c2t {
struct Point {
int x, y;
Point() = default;
Point(int x, int y) : x(x), y(y) {}
};
// structures used internally
struct SpanRange {
int x1, x2;
SpanRange() = default;
SpanRange(int x1, int x2) : x1(x1), x2(x2) {}
inline static SpanRange CreateInvalid() { return SpanRange(-10, -10); }
inline bool IsValid() { return x1 != -10; }
};
struct Edge {
std::vector<Point> points;
};
struct Span {
int x1, x2;
std::vector<int> xs;
int y;
std::shared_ptr<Edge> leftEdge;
std::shared_ptr<Edge> rightEdge;
};
template <class T> class Trianglulator {
T model;
const int w, h;
SpanRange SearchSpan(int x, int y) {
while (x < w && !model(x, y))
x++;
if (x >= w)
return SpanRange::CreateInvalid();
while (x > 0 && model(x - 1, y))
x--;
auto x1 = x;
while (x < w && model(x, y))
x++;
return SpanRange(x1, x);
}
template <bool flipped>
static bool TriangleSide(const Point &p1, const Point &p2, const Point &p3) {
auto x1 = p2.x - p1.x, y1 = p2.y - p1.y;
auto x2 = p3.x - p1.x, y2 = p3.y - p1.y;
auto area = x2 * y1 - x1 * y2;
return flipped ? area < 0 : area > 0;
}
std::vector<std::shared_ptr<Edge>> lastLeftEdges;
std::vector<std::shared_ptr<Edge>> lastRightEdges;
std::vector<int> lastLeftEdgesY;
std::vector<int> lastRightEdgesY;
std::vector<int> lastProcessedY;
std::vector<Point> polys;
void Init() {
lastLeftEdges.resize(w + 1);
lastRightEdges.resize(w + 1);
lastLeftEdgesY.resize(w + 1);
lastRightEdgesY.resize(w + 1);
lastProcessedY.resize(w + 1);
}
std::vector<std::size_t> edgeStack;
template <bool flipped> void EmitEdge(Edge *edge) {
const auto &points = edge->points;
edgeStack.clear();
edgeStack.push_back(0);
edgeStack.push_back(1);
for (std::size_t i = 2; i < points.size(); i++) {
while (edgeStack.size() > 1) {
auto j = edgeStack.back();
edgeStack.pop_back();
auto k = edgeStack.back();
if (TriangleSide<flipped>(points[j], points[k], points[i])) {
if (flipped) {
polys.push_back(points[k]);
polys.push_back(points[j]);
} else {
polys.push_back(points[j]);
polys.push_back(points[k]);
}
polys.push_back(points[i]);
} else {
edgeStack.push_back(j);
break;
}
}
edgeStack.push_back(i);
}
}
public:
Trianglulator(const T &model) : model(model), w(model.GetWidth()), h(model.GetHeight()) {
Init();
}
Trianglulator(T &&model)
: model(std::move(model)), w(model.GetWidth()), h(model.GetHeight()) {
Init();
}
std::vector<Point> Triangulate() {
std::fill(lastLeftEdgesY.begin(), lastLeftEdgesY.end(), -1);
std::fill(lastRightEdgesY.begin(), lastRightEdgesY.end(), -1);
std::fill(lastProcessedY.begin(), lastProcessedY.end(), -1);
polys.clear();
std::list<Span> spans;
std::vector<std::list<Span>::iterator> removedIterators;
std::vector<int> points;
for (int y = 0; y <= h; y++) {
removedIterators.clear();
for (auto it = spans.begin(); it != spans.end(); it++) {
auto &span = *it;
bool removeSpan = false;
int x = span.x1;
for (; x < span.x2; x++) {
if (!model(x, y))
break;
}
if (x < span.x2) {
removeSpan = true;
} else if (model(span.x1 - 1, y) || model(span.x2, y)) {
removeSpan = true;
}
if (removeSpan) {
// generate polygons for span
{
const auto &startpoints = span.xs;
auto &endpoints = points;
endpoints.clear();
if (model(span.x1 - 1, y) || !model(span.x1, y))
endpoints.push_back(span.x1);
bool last = model(span.x1, y);
for (int x = span.x1 + 1; x < span.x2; x++) {
bool b = model(x, y);
if (b != last) {
endpoints.push_back(x);
last = b;
}
}
if (model(span.x2, y) || !model(span.x2 - 1, y))
endpoints.push_back(span.x2);
int y1 = span.y, y2 = y;
{
auto *leftEdge = span.leftEdge.get();
if (leftEdge)
leftEdge->points.emplace_back(endpoints.front(), y2);
}
if (endpoints.front() > span.x1) {
if (span.leftEdge == nullptr) {
span.leftEdge = std::make_shared<Edge>();
span.leftEdge->points.emplace_back(span.x1, span.y);
span.leftEdge->points.emplace_back(endpoints.front(), y2);
}
lastLeftEdges[span.x1] = span.leftEdge;
lastLeftEdgesY[span.x1] = y;
} else {
if (span.leftEdge)
EmitEdge<false>(span.leftEdge.get());
span.leftEdge.reset();
}
{
auto *rightEdge = span.rightEdge.get();
if (rightEdge)
rightEdge->points.emplace_back(endpoints.back(), y2);
}
if (endpoints.back() < span.x2) {
if (span.rightEdge == nullptr) {
span.rightEdge = std::make_shared<Edge>();
span.rightEdge->points.emplace_back(span.x2, span.y);
span.rightEdge->points.emplace_back(endpoints.back(), y2);
}
lastRightEdges[span.x2] = span.rightEdge;
lastRightEdgesY[span.x2] = y;
} else {
if (span.rightEdge)
EmitEdge<true>(span.rightEdge.get());
span.rightEdge.reset();
}
// emit polygons
for (std::size_t i = 1; i < endpoints.size(); i++) {
polys.emplace_back(startpoints.front(), y1);
polys.emplace_back(endpoints[i - 1], y2);
polys.emplace_back(endpoints[i], y2);
}
for (std::size_t i = 1; i < startpoints.size(); i++) {
polys.emplace_back(startpoints[i], y1);
polys.emplace_back(startpoints[i - 1], y1);
polys.emplace_back(endpoints.back(), y2);
}
}
removedIterators.push_back(it);
}
// -- one span done
}
for (auto &it : removedIterators) {
spans.erase(it);
}
// mark processed spans
for (auto &span : spans) {
lastProcessedY[span.x1] = y;
}
// new span discovery
for (int x = 0;;) {
auto sp = SearchSpan(x, y);
if (!sp.IsValid())
break;
if (lastProcessedY[sp.x1] < y) {
// unprocessed span found.
spans.emplace_back();
auto &span = spans.back();
if (lastLeftEdgesY[sp.x1] >= y)
span.leftEdge = lastLeftEdges[sp.x1];
if (lastRightEdgesY[sp.x2] >= y)
span.rightEdge = lastRightEdges[sp.x2];
auto &beginpoints = span.xs;
if (model(sp.x1 - 1, y - 1) || !model(sp.x1, y - 1))
beginpoints.push_back(sp.x1);
bool last = model(sp.x1, y - 1);
for (int x = sp.x1 + 1; x < sp.x2; x++) {
bool b = model(x, y - 1);
if (b != last) {
beginpoints.push_back(x);
last = b;
}
}
if (model(sp.x2, y - 1) || !model(sp.x2 - 1, y - 1))
beginpoints.push_back(sp.x2);
assert(beginpoints.size() > 0);
span.x1 = sp.x1;
span.x2 = sp.x2;
span.y = y;
}
x = sp.x2 + 1;
}
// -- one Y level done
}
return std::move(polys);
}
};
} // namespace c2t