pub type P = (f64, f64);
fn cross(o: P, a: P, b: P) -> f64 {
(a.0 - o.0) * (b.1 - o.1) - (a.1 - o.1) * (b.0 - o.0)
}
pub fn convex_hull(points: &[P]) -> Vec<P> {
let mut pts = points.to_vec();
pts.sort_by(|a, b| a.partial_cmp(b).expect("坐标不能是 NaN"));
pts.dedup();
if pts.len() < 3 {
return pts;
}
let mut hull: Vec<P> = Vec::with_capacity(2 * pts.len());
for pass in 0..2 {
let base = hull.len();
let iter: Box<dyn Iterator<Item = &P>> = if pass == 0 {
Box::new(pts.iter())
} else {
Box::new(pts.iter().rev())
};
for &p in iter {
while hull.len() >= base + 2
&& cross(hull[hull.len() - 2], hull[hull.len() - 1], p) <= 0.0
{
hull.pop();
}
hull.push(p);
}
hull.pop(); }
if hull.len() < 2 {
return vec![pts[0], pts[pts.len() - 1]];
}
hull
}
pub fn min_area_rect(points: &[P]) -> ([P; 4], f64, f64) {
let hull = convex_hull(points);
match hull.len() {
0 => return ([(0.0, 0.0); 4], 0.0, 0.0),
1 => return ([hull[0]; 4], 0.0, 0.0),
_ => {}
}
let n = hull.len();
let mut best: Option<(f64, [P; 4], f64, f64)> = None;
for i in 0..n {
let (a, b) = (hull[i], hull[(i + 1) % n]);
let len = ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt();
if len == 0.0 {
continue;
}
let u = ((b.0 - a.0) / len, (b.1 - a.1) / len);
let v = (-u.1, u.0);
let (mut u0, mut u1, mut v0, mut v1) = (f64::MAX, f64::MIN, f64::MAX, f64::MIN);
for p in &hull {
let pu = p.0 * u.0 + p.1 * u.1;
let pv = p.0 * v.0 + p.1 * v.1;
u0 = u0.min(pu);
u1 = u1.max(pu);
v0 = v0.min(pv);
v1 = v1.max(pv);
}
let area = (u1 - u0) * (v1 - v0);
if best.as_ref().is_none_or(|b| area < b.0 - 1e-9) {
let at = |s: f64, t: f64| (s * u.0 + t * v.0, s * u.1 + t * v.1);
best = Some((
area,
[at(u0, v0), at(u1, v0), at(u1, v1), at(u0, v1)],
u1 - u0,
v1 - v0,
));
}
}
let (_, corners, w, h) = best.expect("凸包至少有一条非零长的边");
(corners, w, h)
}
pub fn order_box(corners: [P; 4]) -> [P; 4] {
let mut p = corners;
p.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("坐标不能是 NaN"));
let (tl, bl) = if p[1].1 > p[0].1 {
(p[0], p[1])
} else {
(p[1], p[0])
};
let (tr, br) = if p[3].1 > p[2].1 {
(p[2], p[3])
} else {
(p[3], p[2])
};
[tl, tr, br, bl]
}
pub fn mini_box(points: &[P]) -> ([P; 4], f64) {
let (c, w, h) = min_area_rect(points);
(order_box(c), w.min(h))
}
pub fn area(poly: &[P]) -> f64 {
signed_area2(poly).abs() / 2.0
}
fn signed_area2(poly: &[P]) -> f64 {
let n = poly.len();
(0..n)
.map(|i| {
let (a, b) = (poly[i], poly[(i + 1) % n]);
a.0 * b.1 - a.1 * b.0
})
.sum()
}
pub fn perimeter(poly: &[P]) -> f64 {
let n = poly.len();
(0..n)
.map(|i| {
let (a, b) = (poly[i], poly[(i + 1) % n]);
((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt()
})
.sum()
}
pub fn unclip(poly: &[P], ratio: f64) -> Option<Vec<P>> {
let n = poly.len();
let per = perimeter(poly);
if n < 3 || per <= 0.0 {
return None;
}
let d = area(poly) * ratio / per;
let ccw = signed_area2(poly) > 0.0;
let dirs: Vec<P> = (0..n)
.map(|i| {
let (a, b) = (poly[i], poly[(i + 1) % n]);
let (dx, dy) = (b.0 - a.0, b.1 - a.1);
let l = (dx * dx + dy * dy).sqrt().max(1e-6);
(dx / l, dy / l)
})
.collect();
let normals: Vec<P> = dirs
.iter()
.map(|&(x, y)| if ccw { (y, -x) } else { (-y, x) })
.collect();
let shifted: Vec<P> = (0..n)
.map(|i| (poly[i].0 + d * normals[i].0, poly[i].1 + d * normals[i].1))
.collect();
let out = (0..n)
.map(|i| {
let j = (i + n - 1) % n; let c = dirs[j].0 * dirs[i].1 - dirs[j].1 * dirs[i].0;
if c.abs() < 1e-6 {
return (
poly[i].0 + 0.5 * d * (normals[j].0 + normals[i].0),
poly[i].1 + 0.5 * d * (normals[j].1 + normals[i].1),
);
}
let v = (shifted[i].0 - shifted[j].0, shifted[i].1 - shifted[j].1);
let t = (v.0 * dirs[i].1 - v.1 * dirs[i].0) / c;
(shifted[j].0 + t * dirs[j].0, shifted[j].1 + t * dirs[j].1)
})
.collect();
Some(out)
}
pub fn fill_convex_rows(poly: &[(i32, i32)], w: usize, h: usize) -> Vec<(usize, usize, usize)> {
if poly.is_empty() || w == 0 || h == 0 {
return Vec::new();
}
let ymin = poly.iter().map(|p| p.1).min().unwrap().max(0);
let ymax = poly.iter().map(|p| p.1).max().unwrap().min(h as i32 - 1);
let n = poly.len();
let mut rows = Vec::new();
for y in ymin..=ymax {
let (mut lo, mut hi) = (f64::MAX, f64::MIN);
let yf = y as f64;
for i in 0..n {
let (a, b) = (poly[i], poly[(i + 1) % n]);
let (y0, y1) = (a.1.min(b.1), a.1.max(b.1));
if y < y0 || y > y1 {
continue;
}
if a.1 == b.1 {
lo = lo.min(a.0.min(b.0) as f64);
hi = hi.max(a.0.max(b.0) as f64);
} else {
let x = a.0 as f64 + (yf - a.1 as f64) * (b.0 - a.0) as f64 / (b.1 - a.1) as f64;
lo = lo.min(x);
hi = hi.max(x);
}
}
if lo > hi {
continue;
}
let x0 = (lo - 1e-9).ceil().max(0.0);
let x1 = (hi + 1e-9).floor().min(w as f64 - 1.0);
if x0 <= x1 {
rows.push((y as usize, x0 as usize, x1 as usize));
}
}
rows
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: P, b: P) -> bool {
(a.0 - b.0).abs() < 1e-6 && (a.1 - b.1).abs() < 1e-6
}
#[test]
fn 凸包去掉内点和共线点() {
let pts = [
(0.0, 0.0),
(2.0, 0.0),
(1.0, 0.0),
(2.0, 2.0),
(0.0, 2.0),
(1.0, 1.0),
];
let h = convex_hull(&pts);
assert_eq!(h.len(), 4, "{h:?}");
assert_eq!(convex_hull(&[(0.0, 0.0), (1.0, 1.0), (3.0, 3.0)]).len(), 2);
}
#[test]
fn 轴对齐矩形() {
let pts = [(1.0, 1.0), (1.0, 3.0), (4.0, 3.0), (4.0, 1.0), (2.0, 2.0)];
let (b, short) = mini_box(&pts);
assert!((short - 2.0).abs() < 1e-9);
let want = [(1.0, 1.0), (4.0, 1.0), (4.0, 3.0), (1.0, 3.0)];
for (a, w) in b.iter().zip(want) {
assert!(close(*a, w), "{b:?}");
}
}
#[test]
fn 旋转矩形() {
let (s, c) = (30f64.to_radians().sin(), 30f64.to_radians().cos());
let rot = |x: f64, y: f64| (x * c - y * s + 50.0, x * s + y * c + 50.0);
let mut pts = vec![rot(0.0, 0.0), rot(10.0, 0.0), rot(10.0, 2.0), rot(0.0, 2.0)];
pts.push(rot(5.0, 1.0));
pts.push(rot(3.0, 0.5));
let (corners, w, h) = min_area_rect(&pts);
assert!((w * h - 20.0).abs() < 1e-6, "{w} × {h}");
assert!((w.min(h) - 2.0).abs() < 1e-6);
let (b, _) = mini_box(&pts);
assert!(
close(b[0], rot(0.0, 0.0)) || close(b[0], rot(0.0, 2.0)),
"{b:?}"
);
assert_eq!(corners.len(), 4);
}
#[test]
fn 退化情形() {
let (_, s) = mini_box(&[(3.0, 3.0)]);
assert_eq!(s, 0.0);
let (_, w, h) = min_area_rect(&[(0.0, 5.0), (4.0, 5.0), (2.0, 5.0)]);
assert!((w.max(h) - 4.0).abs() < 1e-9 && w.min(h).abs() < 1e-9);
}
#[test]
fn 矩形外扩() {
let r = [(0.0, 0.0), (10.0, 0.0), (10.0, 4.0), (0.0, 4.0)];
let d = 40.0 * 1.5 / 28.0;
let out = unclip(&r, 1.5).unwrap();
let want = [(-d, -d), (10.0 + d, -d), (10.0 + d, 4.0 + d), (-d, 4.0 + d)];
for (a, w) in out.iter().zip(want) {
assert!(close(*a, w), "{out:?}");
}
let rev: Vec<P> = r.iter().rev().copied().collect();
let out2 = unclip(&rev, 1.5).unwrap();
assert!((area(&out2) - (10.0 + 2.0 * d) * (4.0 + 2.0 * d)).abs() < 1e-9);
assert!(unclip(&[(1.0, 1.0); 4], 1.5).is_none());
}
#[test]
fn 扫描线填充含边界() {
let rows = fill_convex_rows(&[(1, 1), (3, 1), (3, 2), (1, 2)], 10, 10);
assert_eq!(rows, vec![(1, 1, 3), (2, 1, 3)]);
let rows = fill_convex_rows(&[(2, 0), (4, 2), (2, 4), (0, 2)], 10, 10);
assert_eq!(rows[0], (0, 2, 2));
assert_eq!(rows[2], (2, 0, 4));
let rows = fill_convex_rows(&[(-3, -3), (5, -3), (5, 1), (-3, 1)], 4, 4);
assert_eq!(rows, vec![(0, 0, 3), (1, 0, 3)]);
}
}