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}