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