axiolid_reference/
convex_hull.rs1use crate::orient2d;
3use axiolid_contracts::{GeomError, GeomResult, Sign};
4use axiolid_core::{Point2, Rectangle2, Scalar};
5
6pub type OrientedRectangle2 = Rectangle2;
15
16#[must_use]
23pub fn side_lengths(rectangle: &Rectangle2) -> [Scalar; 2] {
24 let mut sides = [rectangle.x.length(), rectangle.y.length()];
25 sides.sort_by(|left, right| left.total_cmp(right));
26 sides
27}
28
29pub fn strict_convex_hull(points: &[Point2]) -> GeomResult<Vec<usize>> {
30 for (index, point) in points.iter().enumerate() {
31 if !point.is_finite() {
32 return Err(GeomError::InvalidInput(format!(
33 "point {index} is not finite"
34 )));
35 }
36 }
37 let mut ordered: Vec<usize> = (0..points.len()).collect();
38 ordered.sort_by(|&a, &b| {
39 points[a]
40 .x
41 .total_cmp(&points[b].x)
42 .then_with(|| points[a].y.total_cmp(&points[b].y))
43 .then(a.cmp(&b))
44 });
45 ordered.dedup_by(|a, b| points[*a] == points[*b]);
46 if ordered.len() < 3 {
47 return Err(GeomError::Degenerate("need three distinct points".into()));
48 }
49 let mut lower = Vec::new();
50 for &index in &ordered {
51 push_strict(&mut lower, index, points);
52 }
53 let mut upper = Vec::new();
54 for &index in ordered.iter().rev() {
55 push_strict(&mut upper, index, points);
56 }
57 lower.pop();
58 upper.pop();
59 lower.extend(upper);
60 if lower.len() < 3 {
61 return Err(GeomError::Degenerate("points are collinear".into()));
62 }
63 Ok(lower)
64}
65
66pub fn minimum_area_rectangle(points: &[Point2]) -> GeomResult<OrientedRectangle2> {
67 let hull = strict_convex_hull(points)?;
68 let mut best: Option<Rectangle2> = None;
69 let mut best_area: Option<Scalar> = None;
73 for i in 0..hull.len() {
74 let a = points[hull[i]];
75 let b = points[hull[(i + 1) % hull.len()]];
76 let edge = b - a;
77 let width = edge.length();
78 let u = edge / width;
79 let v = Point2::new(-u.y, u.x);
80 let (mut ulo, mut uhi, mut vlo, mut vhi) = (
81 Scalar::INFINITY,
82 Scalar::NEG_INFINITY,
83 Scalar::INFINITY,
84 Scalar::NEG_INFINITY,
85 );
86 for &index in &hull {
87 let point = points[index];
88 let pu = point.dot(u);
89 let pv = point.dot(v);
90 ulo = ulo.min(pu);
91 uhi = uhi.max(pu);
92 vlo = vlo.min(pv);
93 vhi = vhi.max(pv);
94 }
95 let sides = [uhi - ulo, vhi - vlo];
96 let area = sides[0] * sides[1];
97 let rectangle = Rectangle2::new(u * ulo + v * vlo, u * sides[0], v * sides[1]);
101 if best_area.is_none_or(|current| area < current) {
102 best_area = Some(area);
103 best = Some(rectangle);
104 }
105 }
106 Ok(best.expect("non-empty strict hull has an edge"))
107}
108
109fn push_strict(hull: &mut Vec<usize>, index: usize, points: &[Point2]) {
110 while hull.len() >= 2 {
111 let n = hull.len();
112 if sign(orient2d(
113 points[hull[n - 2]],
114 points[hull[n - 1]],
115 points[index],
116 )) == Sign::Positive
117 {
118 break;
119 }
120 hull.pop();
121 }
122 hull.push(index);
123}
124fn sign(value: axiolid_contracts::Certified) -> Sign {
125 value.sign().expect("orient2d is total")
126}