Skip to main content

oxiphysics_geometry/computational_geometry/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::types::{
6    ArtGalleryResult, ConvexFace3D, ConvexHull3D, DelaunayTri, Line2D, Point2, VoronoiCell2D,
7};
8use std::ops::{Add, Sub};
9
10/// A point in 3D space represented as a plain array.
11pub type Point3 = [f64; 3];
12/// Subtract two 3D points.
13pub fn sub3(a: Point3, b: Point3) -> Point3 {
14    [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
15}
16/// Add two 3D vectors.
17pub fn add3(a: Point3, b: Point3) -> Point3 {
18    [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
19}
20/// Scale a 3D vector.
21pub fn scale3(a: Point3, t: f64) -> Point3 {
22    [a[0] * t, a[1] * t, a[2] * t]
23}
24/// Dot product of two 3D vectors.
25pub fn dot3(a: Point3, b: Point3) -> f64 {
26    a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
27}
28/// Cross product of two 3D vectors.
29pub fn cross3(a: Point3, b: Point3) -> Point3 {
30    [
31        a[1] * b[2] - a[2] * b[1],
32        a[2] * b[0] - a[0] * b[2],
33        a[0] * b[1] - a[1] * b[0],
34    ]
35}
36/// Squared magnitude of a 3D vector.
37pub fn mag3_sq(a: Point3) -> f64 {
38    dot3(a, a)
39}
40/// Magnitude of a 3D vector.
41pub fn mag3(a: Point3) -> f64 {
42    mag3_sq(a).sqrt()
43}
44/// Normalize a 3D vector; returns the zero vector if the magnitude is too small.
45pub fn normalize3(a: Point3) -> Point3 {
46    let m = mag3(a);
47    if m < 1e-15 {
48        [0.0; 3]
49    } else {
50        scale3(a, 1.0 / m)
51    }
52}
53/// Index of a half-edge in a `HalfEdgeMesh`.
54pub type HalfEdgeId = usize;
55/// Index of a vertex in a `HalfEdgeMesh`.
56pub type VertexId = usize;
57/// Index of a face in a `HalfEdgeMesh`.
58pub type FaceId = usize;
59/// Check if a point `p` is above (in the direction of the outward normal) a face defined
60/// by vertices `a`, `b`, `c` (counter-clockwise order from outside).
61pub(super) fn above_face(a: Point3, b: Point3, c: Point3, p: Point3) -> bool {
62    let normal = cross3(sub3(b, a), sub3(c, a));
63    dot3(normal, sub3(p, a)) > 1e-12
64}
65/// Build the initial tetrahedron from the first four non-coplanar points.
66pub(super) fn initial_tetrahedron(pts: &[Point3]) -> Option<ConvexHull3D> {
67    if pts.len() < 4 {
68        return None;
69    }
70    let p0 = pts[0];
71    let p1_idx = pts
72        .iter()
73        .enumerate()
74        .skip(1)
75        .find(|(_, p)| mag3(sub3(**p, p0)) > 1e-10)
76        .map(|(i, _)| i)?;
77    let p1 = pts[p1_idx];
78    let p2_idx = pts
79        .iter()
80        .enumerate()
81        .skip(p1_idx + 1)
82        .find(|(_, p)| mag3(cross3(sub3(p1, p0), sub3(**p, p0))) > 1e-10)
83        .map(|(i, _)| i)?;
84    let p2 = pts[p2_idx];
85    let mut p3_idx = None;
86    let normal = cross3(sub3(p1, p0), sub3(p2, p0));
87    for (i, &pt) in pts.iter().enumerate().skip(p2_idx + 1) {
88        if dot3(normal, sub3(pt, p0)).abs() > 1e-10 {
89            p3_idx = Some(i);
90            break;
91        }
92    }
93    let p3_idx = p3_idx?;
94    let p3 = pts[p3_idx];
95    let verts = vec![p0, p1, p2, p3];
96    let cx = (p0[0] + p1[0] + p2[0] + p3[0]) / 4.0;
97    let cy = (p0[1] + p1[1] + p2[1] + p3[1]) / 4.0;
98    let cz = (p0[2] + p1[2] + p2[2] + p3[2]) / 4.0;
99    let center: Point3 = [cx, cy, cz];
100    let face_triples: [[usize; 3]; 4] = [[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]];
101    let mut faces = Vec::new();
102    for tri in &face_triples {
103        let a = verts[tri[0]];
104        let b = verts[tri[1]];
105        let c = verts[tri[2]];
106        let mut normal = normalize3(cross3(sub3(b, a), sub3(c, a)));
107        if dot3(normal, sub3(a, center)) < 0.0 {
108            normal = scale3(normal, -1.0);
109            faces.push(ConvexFace3D {
110                verts: [tri[0], tri[2], tri[1]],
111                normal,
112            });
113        } else {
114            faces.push(ConvexFace3D {
115                verts: *tri,
116                normal,
117            });
118        }
119    }
120    Some(ConvexHull3D {
121        vertices: verts,
122        faces,
123    })
124}
125/// Compute the 3D convex hull of a set of points using the incremental algorithm.
126///
127/// Returns `None` if fewer than 4 non-coplanar points are provided.
128///
129/// # Algorithm
130/// 1. Build an initial tetrahedron from 4 non-coplanar seed points.
131/// 2. For each remaining point, find all faces visible from it.
132/// 3. Remove visible faces, identify the horizon edge loop, and connect horizon
133///    edges to the new point to form new faces.
134///
135/// Time complexity: O(n²) worst case; O(n log n) expected for random points.
136pub fn convex_hull_3d(points: &[Point3]) -> Option<ConvexHull3D> {
137    if points.len() < 4 {
138        return None;
139    }
140    let mut hull = initial_tetrahedron(points)?;
141    let skip_eps = 1e-10;
142    let mut remaining: Vec<Point3> = Vec::new();
143    'outer: for &p in points.iter() {
144        for v in &hull.vertices {
145            if mag3(sub3(p, *v)) < skip_eps {
146                continue 'outer;
147            }
148        }
149        remaining.push(p);
150    }
151    for &pt in &remaining {
152        let visible: Vec<bool> = hull
153            .faces
154            .iter()
155            .map(|f| {
156                let a = hull.vertices[f.verts[0]];
157                above_face(a, hull.vertices[f.verts[1]], hull.vertices[f.verts[2]], pt)
158            })
159            .collect();
160        let any_visible = visible.iter().any(|&v| v);
161        if !any_visible {
162            continue;
163        }
164        let mut horizon: Vec<(usize, usize)> = Vec::new();
165        for (fi, face) in hull.faces.iter().enumerate() {
166            if !visible[fi] {
167                continue;
168            }
169            let tri = face.verts;
170            let edges = [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])];
171            for (ea, eb) in edges {
172                let on_horizon = hull.faces.iter().enumerate().any(|(fj, g)| {
173                    if visible[fj] {
174                        return false;
175                    }
176                    let t = g.verts;
177                    [(t[0], t[1]), (t[1], t[2]), (t[2], t[0])]
178                        .iter()
179                        .any(|&(ga, gb)| ga == eb && gb == ea)
180                });
181                if on_horizon {
182                    horizon.push((ea, eb));
183                }
184            }
185        }
186        let new_vid = hull.vertices.len();
187        hull.vertices.push(pt);
188        let n_v = hull.vertices.len() as f64;
189        let cx: f64 = hull.vertices.iter().map(|v| v[0]).sum::<f64>() / n_v;
190        let cy: f64 = hull.vertices.iter().map(|v| v[1]).sum::<f64>() / n_v;
191        let cz: f64 = hull.vertices.iter().map(|v| v[2]).sum::<f64>() / n_v;
192        let center = [cx, cy, cz];
193        let new_faces: Vec<ConvexFace3D> = hull
194            .faces
195            .iter()
196            .enumerate()
197            .filter(|(fi, _)| !visible[*fi])
198            .map(|(_, f)| f.clone())
199            .collect();
200        hull.faces = new_faces;
201        for (ha, hb) in horizon {
202            let a = hull.vertices[ha];
203            let b = hull.vertices[hb];
204            let mut normal = normalize3(cross3(sub3(b, a), sub3(pt, a)));
205            let verts = if dot3(normal, sub3(a, center)) < 0.0 {
206                normal = scale3(normal, -1.0);
207                [ha, new_vid, hb]
208            } else {
209                [ha, hb, new_vid]
210            };
211            hull.faces.push(ConvexFace3D { verts, normal });
212        }
213    }
214    Some(hull)
215}
216/// Clip a polygon against an infinite half-plane defined by the directed edge from
217/// `edge_a` to `edge_b` (the inside is to the left).
218///
219/// Returns the clipped polygon vertices.
220pub fn clip_polygon_by_edge(polygon: &[Point2], edge_a: Point2, edge_b: Point2) -> Vec<Point2> {
221    if polygon.is_empty() {
222        return vec![];
223    }
224    let n = polygon.len();
225    let mut output = Vec::with_capacity(n + 1);
226    for i in 0..n {
227        let cur = polygon[i];
228        let next = polygon[(i + 1) % n];
229        let d_cur = Point2::cross2(edge_a, edge_b, cur);
230        let d_next = Point2::cross2(edge_a, edge_b, next);
231        if d_cur >= 0.0 {
232            output.push(cur);
233            if d_next < 0.0 {
234                output.push(edge_intersect(cur, next, edge_a, edge_b));
235            }
236        } else {
237            if d_next >= 0.0 {
238                output.push(edge_intersect(cur, next, edge_a, edge_b));
239            }
240        }
241    }
242    output
243}
244/// Compute the intersection of segment (p, q) with the line through (a, b).
245pub(super) fn edge_intersect(p: Point2, q: Point2, a: Point2, b: Point2) -> Point2 {
246    let pq = q.sub(p);
247    let ab = b.sub(a);
248    let ap = a.sub(p);
249    let denom = pq.cross(ab);
250    if denom.abs() < 1e-15 {
251        return p;
252    }
253    let t = ap.cross(ab) / denom;
254    p.add(pq.scale(t))
255}
256/// Clip a subject polygon against a convex clipping polygon using the
257/// Sutherland-Hodgman algorithm.
258///
259/// Both polygons must be given as counter-clockwise vertex lists.
260/// Returns the vertices of the clipped (intersection) polygon, which is
261/// convex if the clipping polygon is convex.
262pub fn sutherland_hodgman(subject: &[Point2], clip: &[Point2]) -> Vec<Point2> {
263    if subject.is_empty() || clip.is_empty() {
264        return vec![];
265    }
266    let mut output = subject.to_vec();
267    let n = clip.len();
268    for i in 0..n {
269        if output.is_empty() {
270            break;
271        }
272        let a = clip[i];
273        let b = clip[(i + 1) % n];
274        output = clip_polygon_by_edge(&output, a, b);
275    }
276    output
277}
278/// Compute the area of a simple polygon (positive for CCW).
279pub fn polygon_area(poly: &[Point2]) -> f64 {
280    let n = poly.len();
281    if n < 3 {
282        return 0.0;
283    }
284    let mut area = 0.0f64;
285    for i in 0..n {
286        let j = (i + 1) % n;
287        area += poly[i].x * poly[j].y;
288        area -= poly[j].x * poly[i].y;
289    }
290    area / 2.0
291}
292/// Compute the Minkowski sum of two convex polygons P and Q.
293///
294/// Both polygons must be given as vertices in counter-clockwise order.
295/// The result is the convex polygon P ⊕ Q.
296///
297/// # Algorithm
298/// Rotating calipers / edge-angle merge:
299/// 1. Rotate both polygons so the bottom-most (then left-most) vertex is first.
300/// 2. Merge the edge direction sequences of P and Q in angular order.
301/// 3. Walk the merged sequence to build the sum polygon.
302pub fn minkowski_sum(p: &[Point2], q: &[Point2]) -> Vec<Point2> {
303    if p.is_empty() || q.is_empty() {
304        return vec![];
305    }
306    let start_p = bottom_vertex(p);
307    let start_q = bottom_vertex(q);
308    let n = p.len();
309    let m = q.len();
310    let p_rot: Vec<Point2> = (0..n).map(|i| p[(start_p + i) % n]).collect();
311    let q_rot: Vec<Point2> = (0..m).map(|i| q[(start_q + i) % m]).collect();
312    let edges_p: Vec<Point2> = (0..n).map(|i| p_rot[(i + 1) % n].sub(p_rot[i])).collect();
313    let edges_q: Vec<Point2> = (0..m).map(|i| q_rot[(i + 1) % m].sub(q_rot[i])).collect();
314    let mut result = Vec::with_capacity(n + m);
315    let mut cur = p_rot[0].add(q_rot[0]);
316    result.push(cur);
317    let mut i = 0;
318    let mut j = 0;
319    while i < n || j < m {
320        let ep = if i < n {
321            edges_p[i]
322        } else {
323            Point2::new(1e18, 1e18)
324        };
325        let eq = if j < m {
326            edges_q[j]
327        } else {
328            Point2::new(1e18, 1e18)
329        };
330        let cross = ep.cross(eq);
331        let next_edge = if cross > 0.0 || j >= m {
332            i += 1;
333            ep
334        } else if cross < 0.0 || i >= n {
335            j += 1;
336            eq
337        } else {
338            i += 1;
339            j += 1;
340            ep.add(eq)
341        };
342        if i <= n || j <= m {
343            cur = cur.add(next_edge);
344            result.push(cur);
345        }
346    }
347    if result.len() > 1 {
348        let last = *result.last().expect("collection should not be empty");
349        let first = result[0];
350        if last.dist_sq(first) < 1e-20 {
351            result.pop();
352        }
353    }
354    result
355}
356/// Find the index of the bottom-most (then left-most) vertex.
357pub(super) fn bottom_vertex(poly: &[Point2]) -> usize {
358    let mut best = 0;
359    for i in 1..poly.len() {
360        let b = poly[best];
361        let c = poly[i];
362        if c.y < b.y || (c.y == b.y && c.x < b.x) {
363            best = i;
364        }
365    }
366    best
367}
368/// Intersection point of two lines (if they are not parallel).
369pub fn line_intersect_2d(l1: &Line2D, l2: &Line2D) -> Option<Point2> {
370    let det = l1.a * l2.b - l2.a * l1.b;
371    if det.abs() < 1e-15 {
372        return None;
373    }
374    let x = (l1.c * l2.b - l2.c * l1.b) / det;
375    let y = (l1.a * l2.c - l2.a * l1.c) / det;
376    Some(Point2::new(x, y))
377}
378/// Test whether the open segment (p, q) is visible (does not cross any blocking edge).
379///
380/// Endpoint coincidences are permitted (two vertices of the same polygon can see
381/// each other if they are adjacent, handled by the caller).
382pub(super) fn segment_visible(p: Point2, q: Point2, blocking: &[(Point2, Point2)]) -> bool {
383    for &(a, b) in blocking {
384        if segments_properly_intersect(p, q, a, b) {
385            return false;
386        }
387    }
388    true
389}
390/// Test whether two open segments properly intersect (not at shared endpoints).
391pub(super) fn segments_properly_intersect(p: Point2, q: Point2, a: Point2, b: Point2) -> bool {
392    let eps = 1e-12;
393    if p.dist_sq(a) < eps || p.dist_sq(b) < eps || q.dist_sq(a) < eps || q.dist_sq(b) < eps {
394        return false;
395    }
396    let d1 = Point2::cross2(a, b, p);
397    let d2 = Point2::cross2(a, b, q);
398    let d3 = Point2::cross2(p, q, a);
399    let d4 = Point2::cross2(p, q, b);
400    if d1 * d2 < 0.0 && d3 * d4 < 0.0 {
401        return true;
402    }
403    false
404}
405/// Greedy vertex-guard approximation for the art gallery problem.
406///
407/// Given a simple polygon (vertices in CCW order), iteratively places a guard
408/// at the vertex that covers the maximum number of currently uncovered vertices,
409/// until all vertices are covered or `max_guards` is reached.
410///
411/// This is a set-cover greedy approximation with ratio O(log n).
412///
413/// Returns a [`ArtGalleryResult`] describing the guards and coverage.
414pub fn art_gallery_greedy(poly: &[Point2], max_guards: usize) -> ArtGalleryResult {
415    let n = poly.len();
416    if n == 0 {
417        return ArtGalleryResult {
418            guards: vec![],
419            covered: vec![],
420        };
421    }
422    let edges: Vec<(Point2, Point2)> = (0..n).map(|i| (poly[i], poly[(i + 1) % n])).collect();
423    let vis: Vec<Vec<bool>> = (0..n)
424        .map(|i| {
425            (0..n)
426                .map(|j| {
427                    if i == j {
428                        true
429                    } else {
430                        segment_visible(poly[i], poly[j], &edges)
431                    }
432                })
433                .collect()
434        })
435        .collect();
436    let mut covered = vec![false; n];
437    let mut guards = Vec::new();
438    for _ in 0..max_guards {
439        if covered.iter().all(|&c| c) {
440            break;
441        }
442        let best = (0..n)
443            .max_by_key(|&i| (0..n).filter(|&j| !covered[j] && vis[i][j]).count())
444            .expect("operation should succeed");
445        guards.push(best);
446        for j in 0..n {
447            if vis[best][j] {
448                covered[j] = true;
449            }
450        }
451    }
452    ArtGalleryResult { guards, covered }
453}
454/// Check if every vertex of a polygon is covered by the given set of guards.
455///
456/// Returns `true` if full coverage is achieved.
457pub fn check_full_coverage(poly: &[Point2], guards: &[usize]) -> bool {
458    let n = poly.len();
459    let edges: Vec<(Point2, Point2)> = (0..n).map(|i| (poly[i], poly[(i + 1) % n])).collect();
460    for j in 0..n {
461        let seen = guards
462            .iter()
463            .any(|&g| g == j || segment_visible(poly[g], poly[j], &edges));
464        if !seen {
465            return false;
466        }
467    }
468    true
469}
470/// Circumcircle of three 2D points. Returns (center, radius²) or None if degenerate.
471pub fn circumcircle_2d(pa: Point2, pb: Point2, pc: Point2) -> Option<(Point2, f64)> {
472    let ax = pb.x - pa.x;
473    let ay = pb.y - pa.y;
474    let bx = pc.x - pa.x;
475    let by = pc.y - pa.y;
476    let d = 2.0 * (ax * by - ay * bx);
477    if d.abs() < 1e-12 {
478        return None;
479    }
480    let ux = (by * (ax * ax + ay * ay) - ay * (bx * bx + by * by)) / d;
481    let uy = (ax * (bx * bx + by * by) - bx * (ax * ax + ay * ay)) / d;
482    let cx = pa.x + ux;
483    let cy = pa.y + uy;
484    let center = Point2::new(cx, cy);
485    let r2 = center.dist_sq(pa);
486    Some((center, r2))
487}
488/// Test if point `p` lies inside the circumcircle of triangle (a, b, c).
489pub fn in_circumcircle_2d(pa: Point2, pb: Point2, pc: Point2, p: Point2) -> bool {
490    match circumcircle_2d(pa, pb, pc) {
491        Some((center, r2)) => center.dist_sq(p) < r2 - 1e-12,
492        None => false,
493    }
494}
495/// Bowyer-Watson incremental Delaunay triangulation for 2D points.
496///
497/// Returns the list of [`DelaunayTri`] triangles (indices into `points`).
498/// Points are assumed to be in general position (no four co-circular).
499pub fn delaunay_2d(points: &[Point2]) -> Vec<DelaunayTri> {
500    let n = points.len();
501    if n < 3 {
502        return vec![];
503    }
504    let min_x = points.iter().map(|p| p.x).fold(f64::INFINITY, f64::min);
505    let max_x = points.iter().map(|p| p.x).fold(f64::NEG_INFINITY, f64::max);
506    let min_y = points.iter().map(|p| p.y).fold(f64::INFINITY, f64::min);
507    let max_y = points.iter().map(|p| p.y).fold(f64::NEG_INFINITY, f64::max);
508    let dx = max_x - min_x;
509    let dy = max_y - min_y;
510    let delta = dx.max(dy) * 3.0 + 1.0;
511    let mx = (min_x + max_x) / 2.0;
512    let my = (min_y + max_y) / 2.0;
513    let mut all_pts = points.to_vec();
514    all_pts.push(Point2::new(mx - 20.0 * delta, my - delta));
515    all_pts.push(Point2::new(mx, my + 20.0 * delta));
516    all_pts.push(Point2::new(mx + 20.0 * delta, my - delta));
517    let s0 = n;
518    let s1 = n + 1;
519    let s2 = n + 2;
520    let mut triangles: Vec<DelaunayTri> = vec![DelaunayTri {
521        a: s0,
522        b: s1,
523        c: s2,
524    }];
525    for i in 0..n {
526        let pt = all_pts[i];
527        let (bad, good): (Vec<_>, Vec<_>) = triangles
528            .into_iter()
529            .partition(|t| in_circumcircle_2d(all_pts[t.a], all_pts[t.b], all_pts[t.c], pt));
530        let mut boundary: Vec<(usize, usize)> = Vec::new();
531        for t in &bad {
532            let edges = [(t.a, t.b), (t.b, t.c), (t.c, t.a)];
533            for (ea, eb) in edges {
534                let shared = bad.iter().any(|u| {
535                    u != t
536                        && ([(u.a, u.b), (u.b, u.c), (u.c, u.a)]
537                            .iter()
538                            .any(|&(ua, ub)| ua == eb && ub == ea))
539                });
540                if !shared {
541                    boundary.push((ea, eb));
542                }
543            }
544        }
545        triangles = good;
546        for (ea, eb) in boundary {
547            triangles.push(DelaunayTri { a: ea, b: eb, c: i });
548        }
549    }
550    triangles.retain(|t| t.a < n && t.b < n && t.c < n);
551    triangles
552}
553/// Compute the dual Voronoi diagram from a Delaunay triangulation.
554///
555/// For each site point, collects all Delaunay triangles incident to that site
556/// and returns their circumcenters as the Voronoi polygon vertices.
557pub fn voronoi_from_delaunay(points: &[Point2], tris: &[DelaunayTri]) -> Vec<VoronoiCell2D> {
558    let n = points.len();
559    let mut cells: Vec<VoronoiCell2D> = (0..n)
560        .map(|i| VoronoiCell2D {
561            site: i,
562            circumcenters: Vec::new(),
563        })
564        .collect();
565    for t in tris {
566        if let Some((center, _)) = circumcircle_2d(points[t.a], points[t.b], points[t.c]) {
567            cells[t.a].circumcenters.push(center);
568            cells[t.b].circumcenters.push(center);
569            cells[t.c].circumcenters.push(center);
570        }
571    }
572    for cell in &mut cells {
573        let site = points[cell.site];
574        cell.circumcenters.sort_by(|a, b| {
575            let ta = (a.y - site.y).atan2(a.x - site.x);
576            let tb = (b.y - site.y).atan2(b.x - site.x);
577            ta.partial_cmp(&tb).unwrap_or(std::cmp::Ordering::Equal)
578        });
579    }
580    cells
581}
582/// Test if three 2D points are in counter-clockwise orientation.
583pub fn ccw(a: Point2, b: Point2, c: Point2) -> bool {
584    Point2::cross2(a, b, c) > 0.0
585}
586/// Test if three 2D points are collinear (within tolerance).
587pub fn collinear(a: Point2, b: Point2, c: Point2) -> bool {
588    Point2::cross2(a, b, c).abs() < 1e-10
589}
590/// Test if point `p` lies inside the triangle (a, b, c) using barycentric coordinates.
591///
592/// Returns `true` if strictly inside, `false` on boundary or outside.
593pub fn point_in_triangle(p: Point2, a: Point2, b: Point2, c: Point2) -> bool {
594    let d1 = Point2::cross2(p, a, b);
595    let d2 = Point2::cross2(p, b, c);
596    let d3 = Point2::cross2(p, c, a);
597    let has_neg = d1 < 0.0 || d2 < 0.0 || d3 < 0.0;
598    let has_pos = d1 > 0.0 || d2 > 0.0 || d3 > 0.0;
599    !(has_neg && has_pos)
600}
601/// Test if point `p` lies inside a convex polygon (given in CCW order).
602pub fn point_in_convex_polygon(p: Point2, poly: &[Point2]) -> bool {
603    let n = poly.len();
604    if n < 3 {
605        return false;
606    }
607    for i in 0..n {
608        let a = poly[i];
609        let b = poly[(i + 1) % n];
610        if Point2::cross2(a, b, p) < 0.0 {
611            return false;
612        }
613    }
614    true
615}
616/// Compute the 2D convex hull of a point set (Graham scan).
617///
618/// Returns vertices in CCW order.
619pub fn convex_hull_2d(points: &[Point2]) -> Vec<Point2> {
620    let mut pts = points.to_vec();
621    if pts.len() < 3 {
622        return pts;
623    }
624    pts.sort_by(|a, b| {
625        a.x.partial_cmp(&b.x)
626            .expect("operation should succeed")
627            .then(a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
628    });
629    pts.dedup_by(|a, b| a.dist_sq(*b) < 1e-20);
630    let n = pts.len();
631    let mut hull: Vec<Point2> = Vec::with_capacity(2 * n);
632    for &p in &pts {
633        while hull.len() >= 2 {
634            let a = hull[hull.len() - 2];
635            let b = hull[hull.len() - 1];
636            if Point2::cross2(a, b, p) <= 0.0 {
637                hull.pop();
638            } else {
639                break;
640            }
641        }
642        hull.push(p);
643    }
644    let lower_len = hull.len();
645    for &p in pts.iter().rev() {
646        while hull.len() > lower_len {
647            let a = hull[hull.len() - 2];
648            let b = hull[hull.len() - 1];
649            if Point2::cross2(a, b, p) <= 0.0 {
650                hull.pop();
651            } else {
652                break;
653            }
654        }
655        hull.push(p);
656    }
657    hull.pop();
658    hull
659}