use axiolid_core::{Point2, Vec2};
use axiolid_exact::{certify, Arith, Dyadic, SignExpr};
use axiolid_guarantees::Sign;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OrientedRectangle {
pub centre: Point2,
pub axes: [Vec2; 2],
pub half_extents: [f64; 2],
}
impl OrientedRectangle {
#[must_use]
pub fn area(&self) -> f64 {
4.0 * self.half_extents[0] * self.half_extents[1]
}
#[must_use]
pub fn corners(&self) -> [Point2; 4] {
let u = self.axes[0] * self.half_extents[0];
let v = self.axes[1] * self.half_extents[1];
let c = self.centre;
[c - u - v, c + u - v, c + u + v, c - u + v]
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct RectangleEvidence {
pub hull_vertices: usize,
pub minimal_orientations: usize,
pub error: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct MinimumRectangle {
pub rectangle: OrientedRectangle,
pub evidence: RectangleEvidence,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RectangleError {
Empty,
NonFinite,
}
struct Along {
a: Point2,
b: Point2,
d: Vec2,
across: bool,
}
impl SignExpr for Along {
fn sign_in<T: Arith>(&self) -> Option<Sign> {
let f = T::from_f64;
let (dx, dy) = if self.across {
(f(self.d.y).neg(), f(self.d.x))
} else {
(f(self.d.x), f(self.d.y))
};
let ex = f(self.b.x).sub(&f(self.a.x));
let ey = f(self.b.y).sub(&f(self.a.y));
ex.mul(&dx).add(&ey.mul(&dy)).sign()
}
}
struct Orient {
a: Point2,
b: Point2,
c: Point2,
}
impl SignExpr for Orient {
fn sign_in<T: Arith>(&self) -> Option<Sign> {
let f = T::from_f64;
let (ux, uy) = (f(self.b.x).sub(&f(self.a.x)), f(self.b.y).sub(&f(self.a.y)));
let (vx, vy) = (f(self.c.x).sub(&f(self.a.x)), f(self.c.y).sub(&f(self.a.y)));
ux.mul(&vy).sub(&uy.mul(&vx)).sign()
}
}
fn sign<E: SignExpr>(e: &E) -> Sign {
certify(e).unwrap_or(Sign::Zero)
}
fn hull(points: &[Point2]) -> Vec<Point2> {
let mut p = points.to_vec();
p.sort_by(|a, b| a.x.total_cmp(&b.x).then(a.y.total_cmp(&b.y)));
p.dedup();
if p.len() < 3 {
return p;
}
let chain = |order: &mut dyn Iterator<Item = Point2>| {
let mut out: Vec<Point2> = Vec::new();
for c in order {
while let [.., a, b] = out[..] {
if sign(&Orient { a, b, c }) == Sign::Positive {
break;
}
out.pop();
}
out.push(c);
}
out.pop();
out
};
let mut lower = chain(&mut p.iter().copied());
lower.extend(chain(&mut p.iter().rev().copied()));
lower
}
fn exact(x: f64) -> Dyadic {
Dyadic::from_f64(x)
}
fn canonical(mut d: Vec2) -> Vec2 {
while !(d.x > 0.0 && d.y >= 0.0) {
d = Vec2::new(d.y, -d.x);
}
d
}
fn clockwise(a: Vec2, b: Vec2) -> bool {
exact(a.x)
.mul(&exact(b.y))
.sub(&exact(a.y).mul(&exact(b.x)))
.sign()
== Some(Sign::Negative)
}
#[derive(Debug, Clone, Copy)]
struct Candidate {
d: Vec2,
lo: usize,
hi: usize,
base: usize,
top: usize,
}
impl Candidate {
fn area_parts(&self, h: &[Point2]) -> (Dyadic, Dyadic) {
let (dx, dy) = (exact(self.d.x), exact(self.d.y));
let span = |a: Point2, b: Point2, across: bool| {
let (ex, ey) = (exact(b.x).sub(&exact(a.x)), exact(b.y).sub(&exact(a.y)));
if across {
ey.mul(&dx).sub(&ex.mul(&dy))
} else {
ex.mul(&dx).add(&ey.mul(&dy))
}
};
let w = span(h[self.lo], h[self.hi], false);
let t = span(h[self.base], h[self.top], true);
(w.mul(&t), dx.mul(&dx).add(&dy.mul(&dy)))
}
}
pub fn minimum_area_rectangle(points: &[Point2]) -> Result<MinimumRectangle, RectangleError> {
if points.is_empty() {
return Err(RectangleError::Empty);
}
if !points.iter().all(|p| p.is_finite()) {
return Err(RectangleError::NonFinite);
}
let h = hull(points);
let size = h
.iter()
.fold(0.0f64, |m, p| m.max(p.x.abs()).max(p.y.abs()));
let evidence = |minimal, error| RectangleEvidence {
hull_vertices: h.len(),
minimal_orientations: minimal,
error,
};
let (rectangle, minimal) = match h.len() {
1 => {
let rectangle = OrientedRectangle {
centre: h[0],
axes: [Vec2::X, Vec2::Y],
half_extents: [0.0, 0.0],
};
return Ok(MinimumRectangle {
rectangle,
evidence: evidence(1, 0.0),
});
}
2 => {
let d = h[1] - h[0];
let axis = canonical(d);
let mut rectangle = fit(&h, axis);
rectangle.half_extents[usize::from(axis == d || axis == -d)] = 0.0;
rectangle.centre = Point2::new(0.5 * (h[0].x + h[1].x), 0.5 * (h[0].y + h[1].y));
(rectangle, 1)
}
_ => {
let (best, minimal) = calipers(&h);
(fit(&h, canonical(best.d)), minimal)
}
};
Ok(MinimumRectangle {
rectangle,
evidence: evidence(
minimal,
measured_error(&h, &rectangle).unwrap_or(64.0 * f64::EPSILON * size),
),
})
}
fn measured_error(h: &[Point2], r: &OrientedRectangle) -> Option<f64> {
if r.axes != [Vec2::X, Vec2::Y] {
return None;
}
let low = |f: fn(&Point2) -> f64| h.iter().map(f).fold(f64::INFINITY, f64::min);
let high = |f: fn(&Point2) -> f64| h.iter().map(f).fold(f64::NEG_INFINITY, f64::max);
let (x0, x1, y0, y1) = (low(|p| p.x), high(|p| p.x), low(|p| p.y), high(|p| p.y));
let half = exact(0.5);
let mid = |a: f64, b: f64| exact(a).add(&exact(b)).mul(&half);
let span = |a: f64, b: f64| exact(b).sub(&exact(a)).mul(&half);
let [c0, c1, c2, c3] = r.corners();
let pairs = [
(r.centre.x, mid(x0, x1)),
(r.centre.y, mid(y0, y1)),
(r.half_extents[0], span(x0, x1)),
(r.half_extents[1], span(y0, y1)),
(c0.x, exact(x0)),
(c0.y, exact(y0)),
(c1.x, exact(x1)),
(c1.y, exact(y0)),
(c2.x, exact(x1)),
(c2.y, exact(y1)),
(c3.x, exact(x0)),
(c3.y, exact(y1)),
];
let mut worst = 0.0f64;
for (rounded, true_value) in pairs {
let gap = exact(rounded).sub(&true_value);
let gap = if gap.sign() == Some(Sign::Negative) {
gap.neg()
} else {
gap
};
let mut bound = gap.to_f64();
if exact(bound).sub(&gap).sign() == Some(Sign::Negative) {
bound = bound.next_up();
}
worst = worst.max(bound);
}
Some(worst)
}
fn calipers(h: &[Point2]) -> (Candidate, usize) {
let n = h.len();
let step = |at: usize, d: Vec2, across: bool, larger: bool| {
let s = sign(&Along {
a: h[at],
b: h[(at + 1) % n],
d,
across,
});
if larger {
s != Sign::Negative
} else {
s != Sign::Positive
}
};
let advance = |mut at: usize, d: Vec2, across: bool, larger: bool| {
for _ in 0..n {
if !step(at, d, across, larger) {
break;
}
at = (at + 1) % n;
}
at
};
let mut candidates = Vec::with_capacity(n);
let (mut hi, mut top, mut lo) = (0, 0, 0);
for base in 0..n {
let d = h[(base + 1) % n] - h[base];
if base == 0 {
hi = advance(0, d, false, true);
top = advance(hi, d, true, true);
lo = advance(top, d, false, false);
} else {
hi = advance(hi, d, false, true);
top = advance(top, d, true, true);
lo = advance(lo, d, false, false);
}
candidates.push(Candidate {
d,
lo,
hi,
base,
top,
});
}
let mut best = candidates[0];
let mut best_parts = best.area_parts(h);
let mut ties = vec![canonical(best.d)];
for c in &candidates[1..] {
let parts = c.area_parts(h);
let order = parts
.0
.mul(&best_parts.1)
.sub(&best_parts.0.mul(&parts.1))
.sign();
match order {
Some(Sign::Negative) => {
best = *c;
best_parts = parts;
ties = vec![canonical(c.d)];
}
Some(Sign::Zero) => {
let axis = canonical(c.d);
if clockwise(canonical(best.d), axis) {
best = *c;
best_parts = parts;
}
if !ties
.iter()
.any(|t| !clockwise(*t, axis) && !clockwise(axis, *t))
{
ties.push(axis);
}
}
_ => {}
}
}
(best, ties.len())
}
fn fit(h: &[Point2], d: Vec2) -> OrientedRectangle {
let l = d.x.hypot(d.y);
let u = Vec2::new(d.x / l, d.y / l);
let v = Vec2::new(-u.y, u.x);
let span = |axis: Vec2| {
h.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), p| {
let t = p.x * axis.x + p.y * axis.y;
(lo.min(t), hi.max(t))
})
};
let ((u0, u1), (v0, v1)) = (span(u), span(v));
let (mu, mv) = (0.5 * (u0 + u1), 0.5 * (v0 + v1));
OrientedRectangle {
centre: Point2::new(u.x * mu + v.x * mv, u.y * mu + v.y * mv),
axes: [u, v],
half_extents: [0.5 * (u1 - u0), 0.5 * (v1 - v0)],
}
}