1use axiolid_core::{Point2, Vec2};
26use axiolid_exact::{certify, Arith, Dyadic, SignExpr};
27use axiolid_guarantees::Sign;
28
29#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct OrientedRectangle {
33 pub centre: Point2,
35 pub axes: [Vec2; 2],
38 pub half_extents: [f64; 2],
41}
42
43impl OrientedRectangle {
44 #[must_use]
46 pub fn area(&self) -> f64 {
47 4.0 * self.half_extents[0] * self.half_extents[1]
48 }
49
50 #[must_use]
52 pub fn corners(&self) -> [Point2; 4] {
53 let u = self.axes[0] * self.half_extents[0];
54 let v = self.axes[1] * self.half_extents[1];
55 let c = self.centre;
56 [c - u - v, c + u - v, c + u + v, c - u + v]
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq)]
62#[non_exhaustive]
63pub struct RectangleEvidence {
64 pub hull_vertices: usize,
66 pub minimal_orientations: usize,
70 pub error: f64,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq)]
81#[non_exhaustive]
82pub struct MinimumRectangle {
83 pub rectangle: OrientedRectangle,
85 pub evidence: RectangleEvidence,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91#[non_exhaustive]
92pub enum RectangleError {
93 Empty,
95 NonFinite,
97}
98
99struct Along {
101 a: Point2,
102 b: Point2,
103 d: Vec2,
104 across: bool,
105}
106
107impl SignExpr for Along {
108 fn sign_in<T: Arith>(&self) -> Option<Sign> {
109 let f = T::from_f64;
110 let (dx, dy) = if self.across {
111 (f(self.d.y).neg(), f(self.d.x))
112 } else {
113 (f(self.d.x), f(self.d.y))
114 };
115 let ex = f(self.b.x).sub(&f(self.a.x));
116 let ey = f(self.b.y).sub(&f(self.a.y));
117 ex.mul(&dx).add(&ey.mul(&dy)).sign()
118 }
119}
120
121struct Orient {
123 a: Point2,
124 b: Point2,
125 c: Point2,
126}
127
128impl SignExpr for Orient {
129 fn sign_in<T: Arith>(&self) -> Option<Sign> {
130 let f = T::from_f64;
131 let (ux, uy) = (f(self.b.x).sub(&f(self.a.x)), f(self.b.y).sub(&f(self.a.y)));
132 let (vx, vy) = (f(self.c.x).sub(&f(self.a.x)), f(self.c.y).sub(&f(self.a.y)));
133 ux.mul(&vy).sub(&uy.mul(&vx)).sign()
134 }
135}
136
137fn sign<E: SignExpr>(e: &E) -> Sign {
139 certify(e).unwrap_or(Sign::Zero)
140}
141
142fn hull(points: &[Point2]) -> Vec<Point2> {
145 let mut p = points.to_vec();
146 p.sort_by(|a, b| a.x.total_cmp(&b.x).then(a.y.total_cmp(&b.y)));
147 p.dedup();
148 if p.len() < 3 {
149 return p;
150 }
151 let chain = |order: &mut dyn Iterator<Item = Point2>| {
152 let mut out: Vec<Point2> = Vec::new();
153 for c in order {
154 while let [.., a, b] = out[..] {
155 if sign(&Orient { a, b, c }) == Sign::Positive {
156 break;
157 }
158 out.pop();
159 }
160 out.push(c);
161 }
162 out.pop();
163 out
164 };
165 let mut lower = chain(&mut p.iter().copied());
166 lower.extend(chain(&mut p.iter().rev().copied()));
167 lower
168}
169
170fn exact(x: f64) -> Dyadic {
171 Dyadic::from_f64(x)
172}
173
174fn canonical(mut d: Vec2) -> Vec2 {
176 while !(d.x > 0.0 && d.y >= 0.0) {
177 d = Vec2::new(d.y, -d.x);
178 }
179 d
180}
181
182fn clockwise(a: Vec2, b: Vec2) -> bool {
184 exact(a.x)
185 .mul(&exact(b.y))
186 .sub(&exact(a.y).mul(&exact(b.x)))
187 .sign()
188 == Some(Sign::Negative)
189}
190
191#[derive(Debug, Clone, Copy)]
193struct Candidate {
194 d: Vec2,
195 lo: usize,
198 hi: usize,
199 base: usize,
200 top: usize,
201}
202
203impl Candidate {
204 fn area_parts(&self, h: &[Point2]) -> (Dyadic, Dyadic) {
206 let (dx, dy) = (exact(self.d.x), exact(self.d.y));
207 let span = |a: Point2, b: Point2, across: bool| {
208 let (ex, ey) = (exact(b.x).sub(&exact(a.x)), exact(b.y).sub(&exact(a.y)));
209 if across {
210 ey.mul(&dx).sub(&ex.mul(&dy))
211 } else {
212 ex.mul(&dx).add(&ey.mul(&dy))
213 }
214 };
215 let w = span(h[self.lo], h[self.hi], false);
216 let t = span(h[self.base], h[self.top], true);
217 (w.mul(&t), dx.mul(&dx).add(&dy.mul(&dy)))
218 }
219}
220
221pub fn minimum_area_rectangle(points: &[Point2]) -> Result<MinimumRectangle, RectangleError> {
228 if points.is_empty() {
229 return Err(RectangleError::Empty);
230 }
231 if !points.iter().all(|p| p.is_finite()) {
232 return Err(RectangleError::NonFinite);
233 }
234 let h = hull(points);
235 let size = h
236 .iter()
237 .fold(0.0f64, |m, p| m.max(p.x.abs()).max(p.y.abs()));
238 let evidence = |minimal, error| RectangleEvidence {
239 hull_vertices: h.len(),
240 minimal_orientations: minimal,
241 error,
242 };
243 let (rectangle, minimal) = match h.len() {
244 1 => {
245 let rectangle = OrientedRectangle {
246 centre: h[0],
247 axes: [Vec2::X, Vec2::Y],
248 half_extents: [0.0, 0.0],
249 };
250 return Ok(MinimumRectangle {
251 rectangle,
252 evidence: evidence(1, 0.0),
253 });
254 }
255 2 => {
256 let d = h[1] - h[0];
259 let axis = canonical(d);
260 let mut rectangle = fit(&h, axis);
261 rectangle.half_extents[usize::from(axis == d || axis == -d)] = 0.0;
262 rectangle.centre = Point2::new(0.5 * (h[0].x + h[1].x), 0.5 * (h[0].y + h[1].y));
263 (rectangle, 1)
264 }
265 _ => {
266 let (best, minimal) = calipers(&h);
267 (fit(&h, canonical(best.d)), minimal)
268 }
269 };
270 Ok(MinimumRectangle {
271 rectangle,
272 evidence: evidence(
273 minimal,
274 measured_error(&h, &rectangle).unwrap_or(64.0 * f64::EPSILON * size),
275 ),
276 })
277}
278
279fn measured_error(h: &[Point2], r: &OrientedRectangle) -> Option<f64> {
285 if r.axes != [Vec2::X, Vec2::Y] {
286 return None;
287 }
288 let low = |f: fn(&Point2) -> f64| h.iter().map(f).fold(f64::INFINITY, f64::min);
289 let high = |f: fn(&Point2) -> f64| h.iter().map(f).fold(f64::NEG_INFINITY, f64::max);
290 let (x0, x1, y0, y1) = (low(|p| p.x), high(|p| p.x), low(|p| p.y), high(|p| p.y));
291 let half = exact(0.5);
292 let mid = |a: f64, b: f64| exact(a).add(&exact(b)).mul(&half);
293 let span = |a: f64, b: f64| exact(b).sub(&exact(a)).mul(&half);
294 let [c0, c1, c2, c3] = r.corners();
295 let pairs = [
296 (r.centre.x, mid(x0, x1)),
297 (r.centre.y, mid(y0, y1)),
298 (r.half_extents[0], span(x0, x1)),
299 (r.half_extents[1], span(y0, y1)),
300 (c0.x, exact(x0)),
301 (c0.y, exact(y0)),
302 (c1.x, exact(x1)),
303 (c1.y, exact(y0)),
304 (c2.x, exact(x1)),
305 (c2.y, exact(y1)),
306 (c3.x, exact(x0)),
307 (c3.y, exact(y1)),
308 ];
309 let mut worst = 0.0f64;
310 for (rounded, true_value) in pairs {
311 let gap = exact(rounded).sub(&true_value);
312 let gap = if gap.sign() == Some(Sign::Negative) {
313 gap.neg()
314 } else {
315 gap
316 };
317 let mut bound = gap.to_f64();
319 if exact(bound).sub(&gap).sign() == Some(Sign::Negative) {
320 bound = bound.next_up();
321 }
322 worst = worst.max(bound);
323 }
324 Some(worst)
325}
326
327fn calipers(h: &[Point2]) -> (Candidate, usize) {
330 let n = h.len();
331 let step = |at: usize, d: Vec2, across: bool, larger: bool| {
334 let s = sign(&Along {
335 a: h[at],
336 b: h[(at + 1) % n],
337 d,
338 across,
339 });
340 if larger {
341 s != Sign::Negative
342 } else {
343 s != Sign::Positive
344 }
345 };
346 let advance = |mut at: usize, d: Vec2, across: bool, larger: bool| {
347 for _ in 0..n {
350 if !step(at, d, across, larger) {
351 break;
352 }
353 at = (at + 1) % n;
354 }
355 at
356 };
357 let mut candidates = Vec::with_capacity(n);
358 let (mut hi, mut top, mut lo) = (0, 0, 0);
359 for base in 0..n {
360 let d = h[(base + 1) % n] - h[base];
361 if base == 0 {
362 hi = advance(0, d, false, true);
365 top = advance(hi, d, true, true);
366 lo = advance(top, d, false, false);
367 } else {
368 hi = advance(hi, d, false, true);
369 top = advance(top, d, true, true);
370 lo = advance(lo, d, false, false);
371 }
372 candidates.push(Candidate {
373 d,
374 lo,
375 hi,
376 base,
377 top,
378 });
379 }
380 let mut best = candidates[0];
381 let mut best_parts = best.area_parts(h);
382 let mut ties = vec![canonical(best.d)];
383 for c in &candidates[1..] {
384 let parts = c.area_parts(h);
385 let order = parts
386 .0
387 .mul(&best_parts.1)
388 .sub(&best_parts.0.mul(&parts.1))
389 .sign();
390 match order {
391 Some(Sign::Negative) => {
392 best = *c;
393 best_parts = parts;
394 ties = vec![canonical(c.d)];
395 }
396 Some(Sign::Zero) => {
397 let axis = canonical(c.d);
398 if clockwise(canonical(best.d), axis) {
399 best = *c;
400 best_parts = parts;
401 }
402 if !ties
403 .iter()
404 .any(|t| !clockwise(*t, axis) && !clockwise(axis, *t))
405 {
406 ties.push(axis);
407 }
408 }
409 _ => {}
410 }
411 }
412 (best, ties.len())
413}
414
415fn fit(h: &[Point2], d: Vec2) -> OrientedRectangle {
418 let l = d.x.hypot(d.y);
419 let u = Vec2::new(d.x / l, d.y / l);
420 let v = Vec2::new(-u.y, u.x);
421 let span = |axis: Vec2| {
422 h.iter()
423 .fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), p| {
424 let t = p.x * axis.x + p.y * axis.y;
425 (lo.min(t), hi.max(t))
426 })
427 };
428 let ((u0, u1), (v0, v1)) = (span(u), span(v));
429 let (mu, mv) = (0.5 * (u0 + u1), 0.5 * (v0 + v1));
430 OrientedRectangle {
431 centre: Point2::new(u.x * mu + v.x * mv, u.y * mu + v.y * mv),
432 axes: [u, v],
433 half_extents: [0.5 * (u1 - u0), 0.5 * (v1 - v0)],
434 }
435}