blob: c670efa8a13528833386f9d2e76eb9cfca9928bb [file] [log] [blame]
#include "delaunator.h"
#include <algorithm>
#include <cstdio>
#include <limits>
#include <tuple>
#include <exception>
#include <cmath>
#include "prettyprint.hpp"
#include <iostream>
using namespace std;
namespace {
const double max_double = numeric_limits<double>::max();
double dist(
const double &ax,
const double &ay,
const double &bx,
const double &by
) {
const double dx = ax - bx;
const double dy = ay - by;
return dx * dx + dy * dy;
}
double circumradius(
const double &ax,
const double &ay,
const double &bx,
const double &by,
const double &cx,
const double &cy
) {
const double dx = bx - ax;
const double dy = by - ay;
const double ex = cx - ax;
const double ey = cy - ay;
const double bl = dx * dx + dy * dy;
const double cl = ex * ex + ey * ey;
const double d = dx * ey - dy * ex;
const double x = (ey * bl - dy * cl) * 0.5 / d;
const double y = (dx * cl - ex * bl) * 0.5 / d;
if (bl && cl && d) {
return x * x + y * y;
} else {
return max_double;
}
}
double area(
const double px,
const double py,
const double qx,
const double qy,
const double rx,
const double ry
) {
return (qy - py) * (rx - qx) - (qx - px) * (ry - qy);
}
tuple<double, double> circumcenter(
const double &ax,
const double &ay,
const double &bx,
const double &by,
const double &cx,
const double &cy
) {
const double dx = bx - ax;
const double dy = by - ay;
const double ex = cx - ax;
const double ey = cy - ay;
const double bl = dx * dx + dy * dy;
const double cl = ex * ex + ey * ey;
const double d = dx * ey - dy * ex;
const double x = ax + (ey * bl - dy * cl) * 0.5 / d;
const double y = ay + (dx * cl - ex * bl) * 0.5 / d;
return make_tuple(x, y);
}
double compare(
const vector<double> &coords,
unsigned long int i,
unsigned long int j,
double cx,
double cy
) {
const double d1 = dist(coords[2 * i], coords[2 * i + 1], cx, cy);
const double d2 = dist(coords[2 * j], coords[2 * j + 1], cx, cy);
const double diff1 = d1 - d2;
const double diff2 = coords[2 * i] - coords[2 * j];
const double diff3 = coords[2 * i + 1] - coords[2 * j + 1];
if (diff1) {
return diff1;
} else if(diff2) {
return diff2;
} else {
return diff3;
}
}
struct sort_to_center {
double cx;
double cy;
vector<double> &coords;
bool operator() (long int i, long int j) {
return compare(coords, i, j, cx, cy) < 0;
}
};
bool in_circle(
double ax, double ay,
double bx, double by,
double cx, double cy,
double px, double py
) {
const double dx = ax - px;
const double dy = ay - py;
const double ex = bx - px;
const double ey = by - py;
const double fx = cx - px;
const double fy = cy - py;
const double ap = dx * dx + dy * dy;
const double bp = ex * ex + ey * ey;
const double cp = fx * fx + fy * fy;
return (dx * (ey * cp - bp * fy) -
dy * (ex * cp - bp * fx) +
ap * (ex * fy - ey * fx)) < 0;
}
}
Delaunator::Delaunator(const vector<double> &in_coords) {
coords = move(in_coords);
const long int n = coords.size() >> 1;
double max_x = -1 * max_double;
double max_y = -1 * max_double;
double min_x = max_double;
double min_y = max_double;
vector<long int> ids;
ids.reserve(n);
for (long int i = 0; i < n; i++) {
const double x = coords[2 * i];
const double y = coords[2 * i + 1];
// printf("%f %f", x, y);
if (x < min_x) min_x = x;
if (y < min_y) min_y = y;
if (x > max_x) max_x = x;
if (y > max_y) max_y = y;
// ids[i] = i;
ids.push_back(i);
}
const double cx = (min_x + max_x) / 2;
const double cy = (min_y + max_y) / 2;
double min_dist = max_double;
unsigned long int i0;
unsigned long int i1;
unsigned long int i2;
// pick a seed point close to the centroid
for (unsigned long int i = 0; i < n; i++) {
const double d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]);
if (d < min_dist) {
i0 = i;
min_dist = d;
}
}
min_dist = max_double;
// find the point closest to the seed
for (unsigned long int i = 0; i < n; i++) {
if (i == i0) continue;
const double d = dist(coords[2 * i0], coords[2 * i0 + 1], coords[2 * i], coords[2 * i + 1]);
if (d < min_dist && d > 0)
{
i1 = i;
min_dist = d;
}
}
double min_radius = max_double;
// find the third point which forms the smallest circumcircle with the first two
for (unsigned long int i = 0; i < n; i++)
{
if (i == i0 || i == i1) continue;
const double r = circumradius(
coords[2 * i0], coords[2 * i0 + 1],
coords[2 * i1], coords[2 * i1 + 1],
coords[2 * i], coords[2 * i + 1]);
if (r < min_radius)
{
i2 = i;
min_radius = r;
}
}
if (min_radius == max_double) {
throw runtime_error("not triangulation");;
}
if (
area(
coords[2 * i0], coords[2 * i0 + 1],
coords[2 * i1], coords[2 * i1 + 1],
coords[2 * i2], coords[2 * i2 + 1]
) < 0
) {
const double tmp = i1;
i1 = i2;
i2 = tmp;
}
const double i0x = coords[2 * i0];
const double i0y = coords[2 * i0 + 1];
const double i1x = coords[2 * i1];
const double i1y = coords[2 * i1 + 1];
const double i2x = coords[2 * i2];
const double i2y = coords[2 * i2 + 1];
tie(m_center_x, m_center_y) = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
// sort the points by distance from the seed triangle circumcenter
// cerr << ids << endl;
const sort_to_center s = {
.cx = m_center_x,
.cy = m_center_y,
.coords = coords
};
sort(ids.begin(), ids.end(), s);
// quicksort(ids, coords, 0, n - 1, m_center_x, m_center_y);
// cerr << ids << endl;
m_hash_size = ceil(sqrt(n));
m_hash.reserve(m_hash_size);
for (int i = 0; i < m_hash_size; i++) m_hash.push_back(-1);
m_hull.reserve(coords.size());
long int e = insert_node(i0);
hash_edge(e);
m_hull[e].t = 0;
e = insert_node(i1, e);
hash_edge(e);
m_hull[e].t = 1;
e = insert_node(i2, e);
hash_edge(e);
m_hull[e].t = 2;
const long int max_triangles = 2 * n - 5;
triangles.reserve(max_triangles * 3);
halfedges.reserve(max_triangles * 3);
add_triangle(i0, i1, i2, -1, -1, -1);
double xp = NAN;
double yp = NAN;
for (long int k = 0; k < n; k++) {
const long int i = ids[k];
const double x = coords[2 * i];
const double y = coords[2 * i + 1];
if (x == xp && y == yp) continue;
xp = x;
yp = y;
if (
(x == i0x && y == i0y) ||
(x == i1x && y == i1y) ||
(x == i2x && y == i2y)
) continue;
const long int start_key = hash_key(x, y);
long int key = start_key;
long int start = -1;
do {
start = m_hash[key];
key = (key + 1) % m_hash_size;
} while(
(start < 0 || m_hull[start].removed) &&
(key != start_key)
);
e = start;
while(
area(
x, y,
m_hull[e].x, m_hull[e].y,
m_hull[m_hull[e].next].x, m_hull[m_hull[e].next].y
) >= 0
) {
e = m_hull[e].next;
if (e == start) {
throw runtime_error("Something is wrong with the input points.");
}
}
const bool walk_back = e == start;
// add the first triangle from the point
long int t = add_triangle(
m_hull[e].i,
i,
m_hull[m_hull[e].next].i,
-1, -1, m_hull[e].t
);
m_hull[e].t = t; // keep track of boundary triangles on the hull
e = insert_node(i, e);
// recursively flip triangles from the point until they satisfy the Delaunay condition
m_hull[e].t = legalize(t + 2);
if (m_hull[m_hull[m_hull[e].prev].prev].t == halfedges[t + 1]) {
m_hull[m_hull[m_hull[e].prev].prev].t = t + 2;
}
// walk forward through the hull, adding more triangles and flipping recursively
long int q = m_hull[e].next;
while(
area(
x, y,
m_hull[q].x, m_hull[q].y,
m_hull[m_hull[q].next].x, m_hull[m_hull[q].next].y
) < 0
) {
t = add_triangle(
m_hull[q].i, i,
m_hull[m_hull[q].next].i, m_hull[m_hull[q].prev].t,
-1, m_hull[q].t
);
m_hull[m_hull[q].prev].t = legalize(t + 2);
remove_node(q);
q = m_hull[q].next;
}
if (walk_back) {
// walk backward from the other side, adding more triangles and flipping
q = m_hull[e].prev;
while(
area(
x, y,
m_hull[m_hull[q].prev].x, m_hull[m_hull[q].prev].y,
m_hull[q].x, m_hull[q].y
) < 0
) {
t = add_triangle(
m_hull[m_hull[q].prev].i, i,
m_hull[q].i, -1,
m_hull[q].t, m_hull[m_hull[q].prev].t
);
legalize(t + 2);
m_hull[m_hull[q].prev].t = t;
remove_node(q);
q = m_hull[q].prev;
}
}
hash_edge(e);
hash_edge(m_hull[e].prev);
}
};
long int Delaunator::remove_node(long int node) {
m_hull[m_hull[node].prev].next = m_hull[node].next;
m_hull[m_hull[node].next].prev = m_hull[node].prev;
m_hull[node].removed = true;
return m_hull[node].prev;
}
long int Delaunator::legalize(long int a) {
long int halfedges_size = halfedges.size();
const long int b = halfedges[a];
const long int a0 = a - a % 3;
const long int b0 = b - b % 3;
const long int al = a0 + (a + 1) % 3;
const long int ar = a0 + (a + 2) % 3;
const long int bl = b0 + (b + 2) % 3;
const long int p0 = triangles[ar];
const long int pr = triangles[a];
const long int pl = triangles[al];
const long int p1 = triangles[bl];
const bool illegal = in_circle(
coords[2 * p0], coords[2 * p0 + 1],
coords[2 * pr], coords[2 * pr + 1],
coords[2 * pl], coords[2 * pl + 1],
coords[2 * p1], coords[2 * p1 + 1]
);
if (illegal) {
triangles[a] = p1;
triangles[b] = p0;
link(a, halfedges[bl]);
link(b, halfedges[ar]);
link(ar, bl);
const long int br = b0 + (b + 1) % 3;
legalize(a);
return legalize(br);
}
return ar;
}
long int Delaunator::insert_node(long int i, long int prev) {
const long int node = insert_node(i);
m_hull[node].next = m_hull[prev].next;
m_hull[node].prev = prev;
m_hull[m_hull[node].next].prev = node;
m_hull[prev].next = node;
return node;
};
long int Delaunator::insert_node(long int i) {
long int node = m_hull.size();
DelaunatorPoint p = {
.i = i,
.x = coords[2 * i],
.y = coords[2 * i + 1],
.prev = node,
.next = node,
.removed = false
};
m_hull.push_back(move(p));
return node;
}
long int Delaunator::hash_key(double x, double y) {
const double dx = x - m_center_x;
const double dy = y - m_center_y;
// use pseudo-angle: a measure that monotonically increases
// with real angle, but doesn't require expensive trigonometry
const double p = 1 - dx / (abs(dx) + abs(dy));
return floor((2 + (dy < 0 ? -p : p)) / 4 * m_hash_size);
}
void Delaunator::hash_edge(long int e){
m_hash[hash_key(m_hull[e].x, m_hull[e].y)] = e;
}
long int Delaunator::add_triangle(
long int i0, long int i1, long int i2,
long int a, long int b, long int c
) {
const long int t = triangles.size();
triangles.push_back(i0);
triangles.push_back(i1);
triangles.push_back(i2);
link(t, a);
link(t + 1, b);
link(t + 2, c);
return t;
}
void Delaunator::link(long int a, long int b) {
long int s = halfedges.size();
if (a == s) {
halfedges.push_back(b);
} else if (a < s) {
halfedges[a] = b;
} else {
throw runtime_error("Cannot link edge");
}
if (b != -1) {
long int s = halfedges.size();
if (b == s) {
halfedges.push_back(a);
} else if (b < s) {
halfedges[b] = a;
} else {
throw runtime_error("Cannot link edge");
}
}
};