Tatooine
delaunator.h
Go to the documentation of this file.
1
2#ifndef TATOOINE_DELAUNATOR_H
3#define TATOOINE_DELAUNATOR_H
4//==============================================================================
5#include <tatooine/concepts.h>
6#include <tatooine/vec.h>
7
8#include <algorithm>
9#include <cmath>
10#include <exception>
11#include <iostream>
12#include <limits>
13#include <memory>
14#include <utility>
15#include <vector>
16//==============================================================================
18//==============================================================================
21inline size_t fast_mod(size_t const i, size_t const c) {
22 return i >= c ? i % c : i;
23}
24//------------------------------------------------------------------------------
26template <floating_point Real>
27Real sum(std::vector<Real> const& x) {
28 Real sum = x[0];
29 Real err = 0.0;
30
31 for (size_t i = 1; i < x.size(); i++) {
32 Real const k = x[i];
33 Real const m = sum + k;
34 err += std::fabs(sum) >= std::fabs(k) ? sum - m + k : k - m + sum;
35 sum = m;
36 }
37 return sum + err;
38}
39//------------------------------------------------------------------------------
40template <floating_point Real>
41Real dist(Real const ax, Real const ay, Real const bx, Real const by) {
42 Real const dx = ax - bx;
43 Real const dy = ay - by;
44 return dx * dx + dy * dy;
45}
46//------------------------------------------------------------------------------
47template <floating_point Real>
48Real circumradius(Real const ax, Real const ay, Real const bx, Real const by,
49 Real const cx, Real const cy) {
50 Real const dx = bx - ax;
51 Real const dy = by - ay;
52 Real const ex = cx - ax;
53 Real const ey = cy - ay;
54
55 Real const bl = dx * dx + dy * dy;
56 Real const cl = ex * ex + ey * ey;
57 Real const d = dx * ey - dy * ex;
58
59 Real const x = (ey * bl - dy * cl) * 0.5 / d;
60 Real const y = (dx * cl - ex * bl) * 0.5 / d;
61
62 if ((bl > 0.0 || bl < 0.0) && (cl > 0.0 || cl < 0.0) &&
63 (d > 0.0 || d < 0.0)) {
64 return x * x + y * y;
65 } else {
66 return std::numeric_limits<Real>::max();
67 }
68}
69//------------------------------------------------------------------------------
70template <floating_point Real>
71bool orient(Real const px, Real const py, Real const qx, Real const qy,
72 Real const rx, Real const ry) {
73 return (qy - py) * (rx - qx) - (qx - px) * (ry - qy) < 0.0;
74}
75//------------------------------------------------------------------------------
76template <floating_point Real>
77std::pair<Real, Real> circumcenter(Real const ax, Real const ay, Real const bx,
78 Real const by, Real const cx,
79 Real const cy) {
80 Real const dx = bx - ax;
81 Real const dy = by - ay;
82 Real const ex = cx - ax;
83 Real const ey = cy - ay;
84
85 Real const bl = dx * dx + dy * dy;
86 Real const cl = ex * ex + ey * ey;
87 Real const d = dx * ey - dy * ex;
88
89 Real const x = ax + (ey * bl - dy * cl) * 0.5 / d;
90 Real const y = ay + (dx * cl - ex * bl) * 0.5 / d;
91
92 return std::make_pair(x, y);
93}
94//------------------------------------------------------------------------------
95template <range Coords>
96struct compare {
97 using real_type = typename Coords::value_type::value_type;
98 Coords const& coords;
101
102 bool operator()(std::size_t i, std::size_t j) {
103 real_type const d1 = dist(coords[i](0), coords[i](1), cx, cy);
104 real_type const d2 = dist(coords[j](0), coords[j](1), cx, cy);
105 real_type const diff1 = d1 - d2;
106 real_type const diff2 = coords[i](0) - coords[j](0);
107 real_type const diff3 = coords[i](1) - coords[j](1);
108
109 if (diff1 > 0.0 || diff1 < 0.0) {
110 return diff1 < 0;
111 } else if (diff2 > 0.0 || diff2 < 0.0) {
112 return diff2 < 0;
113 } else {
114 return diff3 < 0;
115 }
116 }
117};
118//------------------------------------------------------------------------------
119template <floating_point Real>
120bool in_circle(Real const ax, Real const ay, Real const bx,
121 Real const by, Real const cx, Real const cy,
122 Real const px, Real const py) {
123 Real const dx = ax - px;
124 Real const dy = ay - py;
125 Real const ex = bx - px;
126 Real const ey = by - py;
127 Real const fx = cx - px;
128 Real const fy = cy - py;
129
130 Real const ap = dx * dx + dy * dy;
131 Real const bp = ex * ex + ey * ey;
132 Real const cp = fx * fx + fy * fy;
133
134 return (dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) +
135 ap * (ex * fy - ey * fx)) < 0.0;
136}
137//------------------------------------------------------------------------------
138template <floating_point Real>
139bool check_pts_equal(Real x1, Real y1, Real x2, Real y2) {
140 return std::fabs(x1 - x2) <= std::numeric_limits<Real>::epsilon() &&
141 std::fabs(y1 - y2) <= std::numeric_limits<Real>::epsilon();
142}
143//------------------------------------------------------------------------------
144// monotonically increases with real angle, but doesn't need expensive
145// trigonometry
146template <floating_point Real>
147Real pseudo_angle(Real const dx, Real const dy) {
148 Real const p = dx / (std::abs(dx) + std::abs(dy));
149 return (dy > 0.0 ? 3.0 - p : 1.0 + p) / 4.0; // [0..1)
150}
151//------------------------------------------------------------------------------
152template <range Coords>
154 static_assert(std::is_same_v<typename Coords::value_type, Vec2<float>> ||
155 std::is_same_v<typename Coords::value_type, Vec2<double>>);
156 using real_type = typename Coords::value_type::value_type;
157
158 public:
159 Coords const& coords;
160 std::vector<std::size_t> triangles;
161 std::vector<std::size_t> halfedges;
162 std::vector<std::size_t> hull_prev;
163 std::vector<std::size_t> hull_next;
164 std::vector<std::size_t> hull_tri;
165 std::size_t hull_start;
166
167 Delaunator(Coords const& in_coords);
168
170
171 private:
172 std::vector<std::size_t> m_hash;
175 std::size_t m_hash_size;
176 std::vector<std::size_t> m_edge_stack;
177
178 std::size_t legalize(std::size_t a);
179 std::size_t hash_key(real_type x, real_type y) const;
180 std::size_t add_triangle(std::size_t i0, std::size_t i1, std::size_t i2,
181 std::size_t a, std::size_t b, std::size_t c);
182 void link(std::size_t a, std::size_t b);
183};
184//------------------------------------------------------------------------------
185template <range Coords>
186Delaunator<Coords>::Delaunator(Coords const& in_coords) : coords{in_coords} {
187 std::size_t n = (coords.size() * 2) >> 1;
188
189 real_type max_x = std::numeric_limits<real_type>::min();
190 real_type max_y = std::numeric_limits<real_type>::min();
191 real_type min_x = std::numeric_limits<real_type>::max();
192 real_type min_y = std::numeric_limits<real_type>::max();
193 std::vector<std::size_t> ids;
194 ids.reserve(n);
195
196 for (std::size_t i = 0; i < n; i++) {
197 real_type const x = coords[i](0);
198 real_type const y = coords[i](1);
199
200 if (x < min_x) { min_x = x; }
201 if (y < min_y) { min_y = y; }
202 if (x > max_x) { max_x = x; }
203 if (y > max_y) { max_y = y; }
204
205 ids.push_back(i);
206 }
207 real_type const cx = (min_x + max_x) / 2;
208 real_type const cy = (min_y + max_y) / 2;
209 real_type min_dist = std::numeric_limits<real_type>::max();
210
211 std::size_t i0 = std::numeric_limits<std::size_t>::max();
212 std::size_t i1 = std::numeric_limits<std::size_t>::max();
213 std::size_t i2 = std::numeric_limits<std::size_t>::max();
214
215 // pick a seed point close to the centroid
216 for (std::size_t i = 0; i < n; i++) {
217 real_type const d = dist(cx, cy, coords[i](0), coords[i](1));
218 if (d < min_dist) {
219 i0 = i;
220 min_dist = d;
221 }
222 }
223
224 real_type const i0x = coords[i0](0);
225 real_type const i0y = coords[i0](1);
226
227 min_dist = std::numeric_limits<real_type>::max();
228
229 // find the point closest to the seed
230 for (std::size_t i = 0; i < n; i++) {
231 if (i == i0) continue;
232 real_type const d = dist(i0x, i0y, coords[i](0), coords[i](1));
233 if (d < min_dist && d > 0.0) {
234 i1 = i;
235 min_dist = d;
236 }
237 }
238
239 real_type i1x = coords[i1](0);
240 real_type i1y = coords[i1](1);
241
242 real_type min_radius = std::numeric_limits<real_type>::max();
243
244 // find the third point which forms the smallest circumcircle with the first
245 // two
246 for (std::size_t i = 0; i < n; i++) {
247 if (i == i0 || i == i1) continue;
248
249 real_type const r =
250 circumradius(i0x, i0y, i1x, i1y, coords[i](0), coords[i](1));
251
252 if (r < min_radius) {
253 i2 = i;
254 min_radius = r;
255 }
256 }
257
258 if (!(min_radius < std::numeric_limits<real_type>::max())) {
259 throw std::runtime_error("not triangulation");
260 }
261
262 real_type i2x = coords[i2](0);
263 real_type i2y = coords[i2](1);
264
265 if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) {
266 std::swap(i1, i2);
267 std::swap(i1x, i2x);
268 std::swap(i1y, i2y);
269 }
270
271 std::tie(m_center_x, m_center_y) = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
272
273 // sort the points by distance from the seed triangle circumcenter
274 std::sort(begin(ids), end(ids), compare<Coords>{coords, m_center_x, m_center_y});
275
276 // initialize a hash table for storing edges of the advancing convex hull
277 m_hash_size = static_cast<std::size_t>(std::llround(std::ceil(std::sqrt(n))));
278 m_hash.resize(m_hash_size);
279 std::fill(m_hash.begin(), m_hash.end(),
280 std::numeric_limits<std::size_t>::max());
281
282 // initialize arrays for tracking the edges of the advancing convex hull
283 hull_prev.resize(n);
284 hull_next.resize(n);
285 hull_tri.resize(n);
286
287 hull_start = i0;
288
289 size_t hull_size = 3;
290
291 hull_next[i0] = hull_prev[i2] = i1;
292 hull_next[i1] = hull_prev[i0] = i2;
293 hull_next[i2] = hull_prev[i1] = i0;
294
295 hull_tri[i0] = 0;
296 hull_tri[i1] = 1;
297 hull_tri[i2] = 2;
298
299 m_hash[hash_key(i0x, i0y)] = i0;
300 m_hash[hash_key(i1x, i1y)] = i1;
301 m_hash[hash_key(i2x, i2y)] = i2;
302
303 std::size_t max_triangles = n < 3 ? 1 : 2 * n - 5;
304 triangles.reserve(max_triangles * 3);
305 halfedges.reserve(max_triangles * 3);
306 add_triangle(i0, i1, i2, std::numeric_limits<std::size_t>::max(),
307 std::numeric_limits<std::size_t>::max(),
308 std::numeric_limits<std::size_t>::max());
309 real_type xp = std::numeric_limits<real_type>::quiet_NaN();
310 real_type yp = std::numeric_limits<real_type>::quiet_NaN();
311 for (std::size_t k = 0; k < n; k++) {
312 std::size_t const i = ids[k];
313 real_type const x = coords[i](0);
314 real_type const y = coords[i](1);
315
316 // skip near-duplicate points
317 if (k > 0 && check_pts_equal(x, y, xp, yp)) continue;
318 xp = x;
319 yp = y;
320
321 // skip seed triangle points
322 if (check_pts_equal(x, y, i0x, i0y) || check_pts_equal(x, y, i1x, i1y) ||
323 check_pts_equal(x, y, i2x, i2y))
324 continue;
325
326 // find a visible edge on the convex hull using edge hash
327 std::size_t start = 0;
328
329 size_t key = hash_key(x, y);
330 for (size_t j = 0; j < m_hash_size; j++) {
331 start = m_hash[fast_mod(key + j, m_hash_size)];
332 if (start != std::numeric_limits<std::size_t>::max() &&
333 start != hull_next[start])
334 break;
335 }
336
337 start = hull_prev[start];
338 size_t e = start;
339 size_t q;
340
341 while (
342 q = hull_next[e],
343 !orient(x, y, coords[e](0), coords[e](1), coords[q](0),
344 coords[q](1))) { // TODO: does it works in a same way as in JS
345 e = q;
346 if (e == start) {
347 e = std::numeric_limits<std::size_t>::max();
348 break;
349 }
350 }
351
352 if (e == std::numeric_limits<std::size_t>::max()) {
353 continue; // likely a near-duplicate point; skip it
354 }
355
356 // add the first triangle from the point
357 auto t = add_triangle(
358 e, i, hull_next[e], std::numeric_limits<std::size_t>::max(),
359 std::numeric_limits<std::size_t>::max(), hull_tri[e]);
360
361 hull_tri[i] = legalize(t + 2);
362 hull_tri[e] = t;
363 hull_size++;
364
365 // walk forward through the hull, adding more triangles and flipping
366 // recursively
367 std::size_t next = hull_next[e];
368 while (q = hull_next[next],
369 orient(x, y, coords[next](0), coords[next](1), coords[q](0),
370 coords[q](1))) {
371 t = add_triangle(next, i, q, hull_tri[i],
372 std::numeric_limits<std::size_t>::max(), hull_tri[next]);
373 hull_tri[i] = legalize(t + 2);
374 hull_next[next] = next; // mark as removed
375 hull_size--;
376 next = q;
377 }
378
379 // walk backward from the other side, adding more triangles and flipping
380 if (e == start) {
381 while (q = hull_prev[e], orient(x, y, coords[q](0), coords[q](1),
382 coords[e](0), coords[e](1))) {
383 t = add_triangle(q, i, e, std::numeric_limits<std::size_t>::max(),
384 hull_tri[e], hull_tri[q]);
385 legalize(t + 2);
386 hull_tri[q] = t;
387 hull_next[e] = e; // mark as removed
388 hull_size--;
389 e = q;
390 }
391 }
392
393 // update the hull indices
394 hull_prev[i] = e;
395 hull_start = e;
396 hull_prev[next] = i;
397 hull_next[e] = i;
398 hull_next[i] = next;
399
400 m_hash[hash_key(x, y)] = i;
401 m_hash[hash_key(coords[e](0), coords[e](1))] = e;
402 }
403}
404//------------------------------------------------------------------------------
405template <range Coords>
407 std::vector<real_type> hull_area;
408 size_t e = hull_start;
409 do {
410 hull_area.push_back((coords[e](0) - coords[hull_prev[e]](0)) *
411 (coords[e](1) + coords[hull_prev[e]](1)));
412 e = hull_next[e];
413 } while (e != hull_start);
414 return sum(hull_area);
415}
416//------------------------------------------------------------------------------
417template <range Coords>
418std::size_t Delaunator<Coords>::legalize(std::size_t a) {
419 std::size_t i = 0;
420 std::size_t ar = 0;
421 m_edge_stack.clear();
422
423 // recursion eliminated with a fixed-size stack
424 while (true) {
425 const size_t b = halfedges[a];
426
427 /* if the pair of triangles doesn't satisfy the Delaunay condition
428 * (p1 is inside the circumcircle of [p0, pl, pr]), flip them,
429 * then do the same check/flip recursively for the new pair of triangles
430 *
431 * pl pl
432 * /||\ / \
433 * al/ || \bl al/ \a
434 * / || \ / \
435 * / a||b \ flip /___ar___\
436 * p0\ || /p1 => p0\---bl---/p1
437 * \ || / \ /
438 * ar\ || /br b\ /br
439 * \||/ \ /
440 * pr pr
441 */
442 const size_t a0 = 3 * (a / 3);
443 ar = a0 + (a + 2) % 3;
444
445 if (b == std::numeric_limits<std::size_t>::max()) {
446 if (i > 0) {
447 i--;
448 a = m_edge_stack[i];
449 continue;
450 } else {
451 // i = std::numeric_limits<std::size_t>::max();
452 break;
453 }
454 }
455
456 const size_t b0 = 3 * (b / 3);
457 const size_t al = a0 + (a + 1) % 3;
458 const size_t bl = b0 + (b + 2) % 3;
459
460 std::size_t const p0 = triangles[ar];
461 std::size_t const pr = triangles[a];
462 std::size_t const pl = triangles[al];
463 std::size_t const p1 = triangles[bl];
464
465 const bool illegal =
466 in_circle(coords[p0](0), coords[p0](1), coords[pr](0), coords[pr](1),
467 coords[pl](0), coords[pl](1), coords[p1](0), coords[p1](1));
468
469 if (illegal) {
470 triangles[a] = p1;
471 triangles[b] = p0;
472
473 auto hbl = halfedges[bl];
474
475 // edge swapped on the other side of the hull (rare); fix the halfedge
476 // reference
477 if (hbl == std::numeric_limits<std::size_t>::max()) {
478 std::size_t e = hull_start;
479 do {
480 if (hull_tri[e] == bl) {
481 hull_tri[e] = a;
482 break;
483 }
484 e = hull_next[e];
485 } while (e != hull_start);
486 }
487 link(a, hbl);
488 link(b, halfedges[ar]);
489 link(ar, bl);
490 std::size_t br = b0 + (b + 1) % 3;
491
492 if (i < m_edge_stack.size()) {
493 m_edge_stack[i] = br;
494 } else {
495 m_edge_stack.push_back(br);
496 }
497 i++;
498
499 } else {
500 if (i > 0) {
501 i--;
502 a = m_edge_stack[i];
503 continue;
504 } else {
505 break;
506 }
507 }
508 }
509 return ar;
510}
511//------------------------------------------------------------------------------
512template <range Coords>
514 real_type const y) const {
515 real_type const dx = x - m_center_x;
516 real_type const dy = y - m_center_y;
517 return fast_mod(static_cast<std::size_t>(std::llround(std::floor(
518 pseudo_angle(dx, dy) * static_cast<real_type>(m_hash_size)))),
519 m_hash_size);
520}
521//------------------------------------------------------------------------------
522template <range Coords>
523std::size_t Delaunator<Coords>::add_triangle(std::size_t i0, std::size_t i1,
524 std::size_t i2, std::size_t a,
525 std::size_t b, std::size_t c) {
526 std::size_t t = triangles.size();
527 triangles.push_back(i0);
528 triangles.push_back(i1);
529 triangles.push_back(i2);
530 link(t, a);
531 link(t + 1, b);
532 link(t + 2, c);
533 return t;
534}
535//------------------------------------------------------------------------------
536template <range Coords>
537void Delaunator<Coords>::link(std::size_t const a, std::size_t const b) {
538 std::size_t s = halfedges.size();
539 if (a == s) {
540 halfedges.push_back(b);
541 } else if (a < s) {
542 halfedges[a] = b;
543 } else {
544 throw std::runtime_error("Cannot link edge");
545 }
546 if (b != std::numeric_limits<std::size_t>::max()) {
547 std::size_t s2 = halfedges.size();
548 if (b == s2) {
549 halfedges.push_back(a);
550 } else if (b < s2) {
551 halfedges[b] = a;
552 } else {
553 throw std::runtime_error("Cannot link edge");
554 }
555 }
556}
557//==============================================================================
558} // namespace tatooine::delaunator
559//==============================================================================
560#endif
Definition: delaunator.h:153
std::size_t add_triangle(std::size_t i0, std::size_t i1, std::size_t i2, std::size_t a, std::size_t b, std::size_t c)
Definition: delaunator.h:523
std::vector< std::size_t > hull_tri
Definition: delaunator.h:164
std::size_t m_hash_size
Definition: delaunator.h:175
std::size_t legalize(std::size_t a)
Definition: delaunator.h:418
std::vector< std::size_t > hull_prev
Definition: delaunator.h:162
std::size_t hull_start
Definition: delaunator.h:165
std::size_t hash_key(real_type x, real_type y) const
Definition: delaunator.h:513
std::vector< std::size_t > m_edge_stack
Definition: delaunator.h:176
real_type m_center_y
Definition: delaunator.h:174
real_type get_hull_area()
Definition: delaunator.h:406
std::vector< std::size_t > m_hash
Definition: delaunator.h:172
real_type m_center_x
Definition: delaunator.h:173
typename Coords::value_type::value_type real_type
Definition: delaunator.h:156
std::vector< std::size_t > hull_next
Definition: delaunator.h:163
std::vector< std::size_t > triangles
Definition: delaunator.h:160
Coords const & coords
Definition: delaunator.h:159
void link(std::size_t a, std::size_t b)
Definition: delaunator.h:537
std::vector< std::size_t > halfedges
Definition: delaunator.h:161
Definition: delaunator.h:17
bool in_circle(Real const ax, Real const ay, Real const bx, Real const by, Real const cx, Real const cy, Real const px, Real const py)
Definition: delaunator.h:120
bool orient(Real const px, Real const py, Real const qx, Real const qy, Real const rx, Real const ry)
Definition: delaunator.h:71
Real sum(std::vector< Real > const &x)
Kahan and Babuska summation, Neumaier variant; accumulates less FP error.
Definition: delaunator.h:27
Real dist(Real const ax, Real const ay, Real const bx, Real const by)
Definition: delaunator.h:41
size_t fast_mod(size_t const i, size_t const c)
Definition: delaunator.h:21
std::pair< Real, Real > circumcenter(Real const ax, Real const ay, Real const bx, Real const by, Real const cx, Real const cy)
Definition: delaunator.h:77
Real pseudo_angle(Real const dx, Real const dy)
Definition: delaunator.h:147
bool check_pts_equal(Real x1, Real y1, Real x2, Real y2)
Definition: delaunator.h:139
Real circumradius(Real const ax, Real const ay, Real const bx, Real const by, Real const cx, Real const cy)
Definition: delaunator.h:48
auto begin(Range &&range)
Definition: iterator_facade.h:318
auto end(Range &&range)
Definition: iterator_facade.h:322
auto next(Iter iter)
Definition: iterator_facade.h:325
Definition: delaunator.h:96
Coords const & coords
Definition: delaunator.h:98
real_type cx
Definition: delaunator.h:99
typename Coords::value_type::value_type real_type
Definition: delaunator.h:97
bool operator()(std::size_t i, std::size_t j)
Definition: delaunator.h:102
real_type cy
Definition: delaunator.h:100