gistools/tools/delaunator.rs
1use crate::geometry::{incirclefast, orient2d};
2use alloc::{vec, vec::Vec};
3use core::f64;
4use libm::{ceil, fabs, floor, sqrt};
5use s2json::GetXY;
6
7/// # NO_REF means it's not pointing to a location in memory
8pub static NO_REF: usize = usize::MAX;
9
10/// # Delaunator
11///
12/// ## Description
13/// An incredibly fast and robust Typescript library for Delaunay triangulation of 2D points.
14///
15/// ## Usage
16///
17/// The methods you have access to:
18/// - [`Delaunator::new`]: Create a new Delaunator
19/// - [`Delaunator::from_points`]: Given a flattened array of x,y points. e.g. `[[x1, y1], [x2, y2], ...]`
20/// - [`Delaunator::from_vector_points`]: Create a new Delaunator from a collection of VectorPoints
21/// - [`Delaunator::update`]: Updates the triangulation if you modified delaunay.
22///
23/// The properties you have access to:
24/// - [`Delaunator::coords`]: coordinates of each point
25/// - [`Delaunator::triangles`]: indexes to each triangle. `(triangle[i * 3], triangle[(i * 3) + 1], triangle[(i * 3) + 2])`
26/// - [`Delaunator::halfedges`]: indexes to each half edge. `(halfedge[i], halfedge[(i + 1) % 3], halfedge[(i + 2) % 3])`
27/// - [`Delaunator::hull`]: indexes to each point on the convex hull
28/// - [`Delaunator::triangles_len`]: length of the triangles array
29///
30/// ```rust
31/// use gistools::tools::Delaunator;
32/// use s2json::Point;
33///
34/// let points = vec![
35/// Point(382., 302.),
36/// Point(382., 328.),
37/// Point(382., 205.),
38/// Point(623., 175.),
39/// Point(382., 188.),
40/// Point(382., 284.),
41/// Point(623., 87.),
42/// Point(623., 341.),
43/// Point(141., 227.),
44/// ];
45/// let del = Delaunator::from_points(&points);
46/// ```
47///
48/// ## Links
49/// - <https://en.wikipedia.org/wiki/Delaunay_triangulation>
50#[derive(Debug)]
51pub struct Delaunator {
52 edge_stack: Vec<usize>,
53 /// coordinates of each point
54 pub coords: Vec<f64>,
55 /// indexes to each triangle. `(triangle[i * 3], triangle[(i * 3) + 1], triangle[(i * 3) + 2])`
56 /// makes a triangle
57 pub triangles: Vec<usize>,
58 /// indexes to each half edge. `(halfedge[i], halfedge[(i + 1) % 3], halfedge[(i + 2) % 3])`
59 pub halfedges: Vec<usize>,
60 hash_size: usize,
61 hull_prev: Vec<usize>,
62 hull_next: Vec<usize>,
63 hull_tri: Vec<usize>,
64 hull_hash: Vec<usize>,
65 ids: Vec<usize>,
66 dists: Vec<f64>,
67 hull_start: usize,
68 cx: f64,
69 cy: f64,
70 /// indexes to each point on the convex hull
71 pub hull: Vec<usize>,
72 /// length of the triangles array
73 pub triangles_len: usize,
74}
75impl Delaunator {
76 /// Constructs a delaunay triangulation object given an array of point coordinates of the form:
77 /// `[x0, y0, x1, y1, ...]` (use a typed array for best performance).
78 ///
79 /// ## Parameters
80 /// - `coords`: flattened array of x,y points. e.g. `[x1, y1, x2, y2, ...]`
81 ///
82 /// ## Returns
83 /// A new [`Delaunator`] object
84 pub fn new(coords: Vec<f64>) -> Delaunator {
85 let n = coords.len() >> 1;
86 let max_triangles: usize = if n < 3 { 0 } else { usize::max(2 * n - 5, 0) };
87 let hash_size = ceil(sqrt(n as f64)) as usize;
88
89 let mut del = Delaunator {
90 coords,
91 // arrays that will store the triangulation graph
92 triangles: vec![0; max_triangles * 3],
93 halfedges: vec![0; max_triangles * 3],
94 // temporary arrays for tracking the edges of the advancing convex hull
95 hash_size,
96 hull_prev: vec![0; n], // edge to prev edge
97 hull_next: vec![0; n], // edge to next edge
98 hull_tri: vec![0; n], // edge to adjacent triangle
99 hull_hash: vec![0; hash_size], // angular edge hash
100 // temporary arrays for sorting points
101 ids: vec![0; n],
102 dists: vec![0.; n],
103 // setup the initial hull
104 edge_stack: vec![0; 512],
105 hull: vec![],
106 hull_start: 0,
107 cx: 0.0,
108 cy: 0.0,
109 triangles_len: 0,
110 };
111
112 del.update();
113
114 del
115 }
116
117 /// Given a collection of points that contain an `x` and `y` property, returns a new
118 /// [`Delaunator`] object.
119 ///
120 /// ## Returns
121 /// A Delaunator class to do Delaunay triangulation
122 pub fn from_points<P: GetXY>(points: &[P]) -> Delaunator {
123 let n = points.len();
124 let mut coords = vec![0.; n * 2];
125
126 for i in 0..n {
127 let p = &points[i];
128 coords[2 * i] = p.x();
129 coords[2 * i + 1] = p.y();
130 }
131
132 Delaunator::new(coords)
133 }
134
135 /// Updates the triangulation if you modified delaunay. Coords values in place, avoiding expensive
136 /// memory allocations. Useful for iterative relaxation algorithms such as
137 /// [Lloyd's](https://en.wikipedia.org/wiki/Lloyd%27s_algorithm).
138 pub fn update(&mut self) {
139 let n = self.coords.len() >> 1;
140 if n == 0 {
141 return;
142 }
143 let epsilon = f64::EPSILON * 2.;
144
145 // populate an array of point indices; calculate input data bbox
146 let mut min_x = f64::INFINITY;
147 let mut min_y = f64::INFINITY;
148 let mut max_x = f64::NEG_INFINITY;
149 let mut max_y = f64::NEG_INFINITY;
150
151 for i in 0..n {
152 let x = self.coords[2 * i];
153 let y = self.coords[2 * i + 1];
154 if x < min_x {
155 min_x = x;
156 }
157 if y < min_y {
158 min_y = y;
159 }
160 if x > max_x {
161 max_x = x;
162 }
163 if y > max_y {
164 max_y = y;
165 }
166 self.ids[i] = i;
167 }
168 let cx = (min_x + max_x) / 2.;
169 let cy = (min_y + max_y) / 2.;
170
171 let mut i0 = 0;
172 let mut i1 = 0;
173 let mut i2 = 0;
174
175 // pick a seed point close to the center
176 let mut min_dist = f64::INFINITY;
177 for i in 0..n {
178 let d = dist(cx, cy, self.coords[2 * i], self.coords[2 * i + 1]);
179 if d < min_dist {
180 i0 = i;
181 min_dist = d;
182 }
183 }
184 let i0x = self.coords[2 * i0];
185 let i0y = self.coords[2 * i0 + 1];
186
187 // find the point closest to the seed
188 min_dist = f64::INFINITY;
189 for i in 0..n {
190 if i == i0 {
191 continue;
192 }
193 let d = dist(i0x, i0y, self.coords[2 * i], self.coords[2 * i + 1]);
194 if d < min_dist && d > 0. {
195 i1 = i;
196 min_dist = d;
197 }
198 }
199 let mut i1x = self.coords[2 * i1];
200 let mut i1y = self.coords[2 * i1 + 1];
201
202 let mut min_radius = f64::INFINITY;
203 // find the third point which forms the smallest circumcircle with the first two
204 for i in 0..n {
205 if i == i0 || i == i1 {
206 continue;
207 }
208 let r = circumradius(i0x, i0y, i1x, i1y, self.coords[2 * i], self.coords[2 * i + 1]);
209 if r < min_radius {
210 i2 = i;
211 min_radius = r;
212 }
213 }
214 let mut i2x = self.coords[2 * i2];
215 let mut i2y = self.coords[2 * i2 + 1];
216
217 if min_radius == f64::INFINITY {
218 // order collinear points by dx (or dy if all x are identical)
219 // and return the list as a hull
220 for i in 0..n {
221 let dx = self.coords[2 * i] - self.coords[0];
222 let dy = self.coords[2 * i + 1] - self.coords[1];
223 self.dists[i] = if dx > 0. { dx } else { dy };
224 }
225 quicksort(&mut self.ids, &mut self.dists, 0, n - 1);
226 let mut hull = vec![0; n];
227 let mut j = 0;
228 let mut d0 = f64::NEG_INFINITY;
229 for i in 0..n {
230 let id = self.ids[i];
231 let d = self.dists[id];
232 if d > d0 {
233 hull[j] = id;
234 j += 1;
235 d0 = d;
236 }
237 }
238 self.hull = hull[0..j].to_vec();
239 self.triangles = vec![];
240 self.halfedges = vec![];
241 return;
242 }
243
244 // swap the order of the seed points for counter-clockwise orientation
245 if orient2d(i0x, i0y, i1x, i1y, i2x, i2y) < 0. {
246 let i = i1;
247 let x = i1x;
248 let y = i1y;
249 i1 = i2;
250 i1x = i2x;
251 i1y = i2y;
252 i2 = i;
253 i2x = x;
254 i2y = y;
255 }
256
257 let center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
258 (self.cx, self.cy) = center;
259
260 for i in 0..n {
261 self.dists[i] = dist(self.coords[2 * i], self.coords[2 * i + 1], center.0, center.1);
262 }
263
264 // sort the points by distance from the seed triangle circumcenter
265 quicksort(&mut self.ids, &mut self.dists, 0, n - 1);
266
267 // set up the seed triangle as the starting hull
268 self.hull_start = i0;
269 let mut hull_size = 3;
270
271 self.hull_prev[i2] = i1;
272 self.hull_next[i0] = i1;
273 self.hull_prev[i0] = i2;
274 self.hull_next[i1] = i2;
275 self.hull_prev[i1] = i0;
276 self.hull_next[i2] = i0;
277
278 self.hull_tri[i0] = 0;
279 self.hull_tri[i1] = 1;
280 self.hull_tri[i2] = 2;
281
282 self.hull_hash.fill(NO_REF);
283 let io_hash = self.hash_key(i0x, i0y);
284 self.hull_hash[io_hash] = i0;
285 let i1_hash = self.hash_key(i1x, i1y);
286 self.hull_hash[i1_hash] = i1;
287 let i2_hash = self.hash_key(i2x, i2y);
288 self.hull_hash[i2_hash] = i2;
289
290 self.triangles_len = 0;
291 self.add_triangle(i0, i1, i2, NO_REF, NO_REF, NO_REF);
292
293 let mut xp = 0.;
294 let mut yp = 0.;
295 for k in 0..self.ids.len() {
296 let i = self.ids[k];
297 let x = self.coords[2 * i];
298 let y = self.coords[2 * i + 1];
299
300 // skip near-duplicate points
301 if k > 0 && fabs(x - xp) <= epsilon && fabs(y - yp) <= epsilon {
302 continue;
303 }
304 xp = x;
305 yp = y;
306
307 // skip seed triangle points
308 if i == i0 || i == i1 || i == i2 {
309 continue;
310 }
311
312 // find a visible edge on the convex hull using edge hash
313 let mut start = 0;
314 let key = self.hash_key(x, y);
315 // for (let j = 0, key = self.hash_key(x, y); j < self.hash_size; j++) {
316 for j in 0..self.hash_size {
317 start = self.hull_hash[(key + j) % self.hash_size];
318 if start != NO_REF && start != self.hull_next[start] {
319 break;
320 }
321 }
322
323 start = self.hull_prev[start];
324 let mut e = start;
325 let mut q;
326 while {
327 q = self.hull_next[e];
328 orient2d(
329 x,
330 y,
331 self.coords[2 * e],
332 self.coords[2 * e + 1],
333 self.coords[2 * q],
334 self.coords[2 * q + 1],
335 ) >= 0.
336 } {
337 e = q;
338 if e == start {
339 e = NO_REF;
340 break;
341 }
342 }
343 if e == NO_REF {
344 continue;
345 } // likely a near-duplicate point; skip it
346
347 // add the first triangle from the point
348 let mut t =
349 self.add_triangle(e, i, self.hull_next[e], NO_REF, NO_REF, self.hull_tri[e]);
350
351 // recursively flip triangles from the point until they satisfy the Delaunay condition
352 self.hull_tri[i] = self.legalize(t + 2);
353 self.hull_tri[e] = t; // keep track of boundary triangles on the hull
354 hull_size += 1;
355
356 // walk forward through the hull, adding more triangles and flipping recursively
357 let mut n = self.hull_next[e];
358 while {
359 q = self.hull_next[n];
360 orient2d(
361 x,
362 y,
363 self.coords[2 * n],
364 self.coords[2 * n + 1],
365 self.coords[2 * q],
366 self.coords[2 * q + 1],
367 ) < 0.
368 } {
369 t = self.add_triangle(n, i, q, self.hull_tri[i], NO_REF, self.hull_tri[n]);
370 self.hull_tri[i] = self.legalize(t + 2);
371 self.hull_next[n] = n; // mark as removed
372 hull_size -= 1;
373 n = q;
374 }
375
376 // walk backward from the other side, adding more triangles and flipping
377 if e == start {
378 while {
379 q = self.hull_prev[e];
380 orient2d(
381 x,
382 y,
383 self.coords[2 * q],
384 self.coords[2 * q + 1],
385 self.coords[2 * e],
386 self.coords[2 * e + 1],
387 ) < 0.
388 } {
389 t = self.add_triangle(q, i, e, NO_REF, self.hull_tri[e], self.hull_tri[q]);
390 self.legalize(t + 2);
391 self.hull_tri[q] = t;
392 self.hull_next[e] = e; // mark as removed
393 hull_size -= 1;
394 e = q;
395 }
396 }
397
398 // update the hull indices
399 self.hull_prev[i] = e;
400 self.hull_start = e;
401 self.hull_prev[n] = i;
402 self.hull_next[e] = i;
403 self.hull_next[i] = n;
404
405 // save the two new edges in the hash table
406 let i_hash = self.hash_key(x, y);
407 self.hull_hash[i_hash] = i;
408 let e_hash = self.hash_key(self.coords[2 * e], self.coords[2 * e + 1]);
409 self.hull_hash[e_hash] = e;
410 }
411
412 self.hull = vec![0; hull_size];
413 let mut e = self.hull_start;
414 for i in 0..hull_size {
415 self.hull[i] = e;
416 e = self.hull_next[e];
417 }
418
419 // trim typed triangle mesh arrays
420 self.triangles = self.triangles[0..self.triangles_len].to_vec();
421 self.halfedges = self.halfedges[0..self.triangles_len].to_vec();
422 }
423
424 /// Check if a point is inside the circumcircle of a triangle
425 ///
426 /// ## Parameters
427 /// - `x`: x coordinate
428 /// - `y`: y coordinate
429 ///
430 /// ## Returns
431 /// A hash value corresponding to the point (x, y)
432 fn hash_key(&self, x: f64, y: f64) -> usize {
433 let hash_size = self.hash_size as f64;
434 (floor(pseudo_angle(x - self.cx, y - self.cy) * hash_size) % hash_size) as usize
435 }
436
437 /// given an index of triangle vertex
438 /// returns the index of previous triangle vertex
439 fn legalize(&mut self, mut a: usize) -> usize {
440 let mut i = 0;
441
442 // recursion eliminated with a fixed-size stack
443 loop {
444 let b = self.halfedges[a];
445
446 // if the pair of triangles doesn't satisfy the Delaunay condition
447 // (p1 is inside the circumcircle of [p0, pl, pr]), flip them,
448 // then do the same check/flip recursively for the new pair of triangles
449 //
450 // pl pl
451 // /||\ / \
452 // al/ || \bl al/ \a
453 // / || \ / \
454 // / a||b \ flip /___ar___\
455 // p0\ || /p1 => p0\---bl---/p1
456 // \ || / \ /
457 // ar\ || /br b\ /br
458 // \||/ \ /
459 // pr pr
460 let a0 = a - (a % 3);
461 let ar = a0 + ((a + 2) % 3);
462
463 if b == NO_REF {
464 // convex hull edge
465 if i == 0 {
466 return ar;
467 }
468 i -= 1;
469 a = self.edge_stack[i];
470 continue;
471 }
472
473 let b0 = b - (b % 3);
474 let al = a0 + ((a + 1) % 3);
475 let bl = b0 + ((b + 2) % 3);
476
477 let p0 = self.triangles[ar];
478 let pr = self.triangles[a];
479 let pl = self.triangles[al];
480 let p1 = self.triangles[bl];
481
482 let illegal = incirclefast(
483 self.coords[2 * p0],
484 self.coords[2 * p0 + 1],
485 self.coords[2 * pr],
486 self.coords[2 * pr + 1],
487 self.coords[2 * pl],
488 self.coords[2 * pl + 1],
489 self.coords[2 * p1],
490 self.coords[2 * p1 + 1],
491 ) < 0.;
492
493 if illegal {
494 self.triangles[a] = p1;
495 self.triangles[b] = p0;
496
497 let hbl = self.halfedges[bl];
498
499 // edge swapped on the other side of the hull (rare); fix the halfedge reference
500 if hbl == NO_REF {
501 let mut e = self.hull_start;
502 loop {
503 if self.hull_tri[e] == bl {
504 self.hull_tri[e] = a;
505 break;
506 }
507 e = self.hull_prev[e];
508 if e == self.hull_start {
509 break;
510 }
511 }
512 }
513 self.link(a, hbl);
514 self.link(b, self.halfedges[ar]);
515 self.link(ar, bl);
516
517 let br = b0 + ((b + 1) % 3);
518
519 // don't worry about hitting the cap: it can only happen on extremely degenerate input
520 if i < self.edge_stack.len() {
521 self.edge_stack[i] = br;
522 i += 1;
523 }
524 } else {
525 if i == 0 {
526 return ar;
527 }
528 i -= 1;
529 a = self.edge_stack[i];
530 }
531 }
532 }
533
534 /// Link half-edges
535 ///
536 /// ## Parameters
537 /// - `a`: index of triangle vertex
538 /// - `b`: index of next triangle vertex
539 fn link(&mut self, a: usize, b: usize) {
540 self.halfedges[a] = b;
541 if b != NO_REF {
542 self.halfedges[b] = a;
543 }
544 }
545
546 /// add a new triangle given vertex indices and adjacent half-edge ids
547 ///
548 /// ## Parameters
549 /// - `i0`: index of triangle vertex
550 /// - `i1`: index of next triangle vertex
551 /// - `i2`: index of previous triangle vertex
552 /// - `a`: adjacent half-edge id
553 /// - `b`: adjacent half-edge id
554 /// - `c`: adjacent half-edge id
555 ///
556 /// ## Returns
557 /// Index of new triangle
558 fn add_triangle(
559 &mut self,
560 i0: usize,
561 i1: usize,
562 i2: usize,
563 a: usize,
564 b: usize,
565 c: usize,
566 ) -> usize {
567 let t = self.triangles_len;
568 if t + 3 >= self.triangles.len() {
569 self.triangles.resize((t + 3) * 2, NO_REF);
570 self.halfedges.resize((t + 3) * 2, NO_REF);
571 }
572
573 self.triangles[t] = i0;
574 self.triangles[t + 1] = i1;
575 self.triangles[t + 2] = i2;
576
577 self.link(t, a);
578 self.link(t + 1, b);
579 self.link(t + 2, c);
580
581 self.triangles_len += 3;
582
583 t
584 }
585}
586
587/// monotonically increases with real angle, but doesn't need expensive trigonometry
588/// returns the pseudo angle
589fn pseudo_angle(dx: f64, dy: f64) -> f64 {
590 let p = dx / (fabs(dx) + fabs(dy));
591 (if dy > 0. { 3. - p } else { 1. + p }) / 4. // [0..1]
592}
593
594/// returns the squared distance between the two points
595fn dist(ax: f64, ay: f64, bx: f64, by: f64) -> f64 {
596 let dx = ax - bx;
597 let dy = ay - by;
598 dx * dx + dy * dy
599}
600
601/// returns the squared radius of the circumscribed circle
602fn circumradius(ax: f64, ay: f64, bx: f64, by: f64, cx: f64, cy: f64) -> f64 {
603 let dx = bx - ax;
604 let dy = by - ay;
605 let ex = cx - ax;
606 let ey = cy - ay;
607
608 let bl = dx * dx + dy * dy;
609 let cl = ex * ex + ey * ey;
610 let d = 0.5 / (dx * ey - dy * ex);
611
612 let x = (ey * bl - dy * cl) * d;
613 let y = (dx * cl - ex * bl) * d;
614
615 x * x + y * y
616}
617
618/// A Voronoi diagram is built by connecting the Delaunay triangle circumcenters together using the
619/// dual of the Delaunay graph.
620/// 1. Calculate the circumcenters of each triangle
621/// 2. Construct the Voronoi edges from two circumcenters
622/// 3. Connect the edges into Voronoi cells
623fn circumcenter(ax: f64, ay: f64, bx: f64, by: f64, cx: f64, cy: f64) -> (f64, f64) {
624 let dx = bx - ax;
625 let dy = by - ay;
626 let ex = cx - ax;
627 let ey = cy - ay;
628
629 let bl = dx * dx + dy * dy;
630 let cl = ex * ex + ey * ey;
631 let d = 0.5 / (dx * ey - dy * ex);
632
633 let x = ax + (ey * bl - dy * cl) * d;
634 let y = ay + (dx * cl - ex * bl) * d;
635
636 (x, y)
637}
638
639/// sort both the points and ids at the same time
640fn quicksort(ids: &mut [usize], dists: &mut [f64], left: usize, right: usize) {
641 if right - left <= 20 {
642 for i in left + 1..=right {
643 let temp = ids[i];
644 let temp_dist = dists[temp];
645 let mut j = i - 1;
646 while j >= left && dists[ids[j]] > temp_dist {
647 ids[j + 1] = ids[j];
648 if j == 0 {
649 break;
650 }
651 j -= 1;
652 }
653 ids[j + 1] = temp;
654 }
655 } else {
656 let median = (left + right) >> 1;
657 let mut i = left + 1;
658 let mut j = right;
659 ids.swap(median, i);
660 if dists[ids[left]] > dists[ids[right]] {
661 ids.swap(left, right);
662 }
663 if dists[ids[i]] > dists[ids[right]] {
664 ids.swap(i, right);
665 }
666 if dists[ids[left]] > dists[ids[i]] {
667 ids.swap(left, i);
668 }
669
670 let temp = ids[i];
671 let temp_dist = dists[temp];
672 loop {
673 loop {
674 i += 1;
675 if dists[ids[i]] >= temp_dist {
676 break;
677 }
678 }
679 loop {
680 if j == 0 {
681 break;
682 }
683 j -= 1;
684 if dists[ids[j]] <= temp_dist {
685 break;
686 }
687 }
688 if j < i {
689 break;
690 }
691 ids.swap(i, j);
692 }
693 ids[left + 1] = ids[j];
694 ids[j] = temp;
695
696 if right - i + 1 >= j - left {
697 quicksort(ids, dists, i, right);
698 quicksort(ids, dists, left, j - 1);
699 } else {
700 quicksort(ids, dists, left, j - 1);
701 quicksort(ids, dists, i, right);
702 }
703 }
704}