use alloc::vec::Vec;
use geometry_coords::CoordinateScalar;
use geometry_trait::{Point, PointMut, Polygon as PolygonTrait, Ring as RingTrait};
#[inline]
#[must_use]
pub fn point_on_surface<G, P>(polygon: &G) -> Option<P>
where
G: PolygonTrait<Point = P>,
P: PointMut + Default + Copy,
P::Scalar: CoordinateScalar,
{
let outer: Vec<P> = polygon.exterior().points().copied().collect();
if outer.len() < 3 {
return None;
}
let mut ymin = outer[0].get::<1>();
let mut ymax = ymin;
for p in &outer {
let y = p.get::<1>();
if y < ymin {
ymin = y;
}
if y > ymax {
ymax = y;
}
}
let two = P::Scalar::ONE + P::Scalar::ONE;
let sweep_y = (ymin + ymax) / two;
let mut xs: Vec<P::Scalar> = Vec::new();
collect_crossings(&outer, sweep_y, &mut xs);
for hole in polygon.interiors() {
let hpts: Vec<P> = hole.points().copied().collect();
collect_crossings(&hpts, sweep_y, &mut xs);
}
if xs.len() < 2 {
return None;
}
xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
let mut best: Option<(P::Scalar, P::Scalar)> = None; let mut i = 0;
while i + 1 < xs.len() {
let lo = xs[i];
let hi = xs[i + 1];
let width = hi - lo;
let mid = (lo + hi) / two;
match best {
Some((bw, _)) if bw >= width => {}
_ => best = Some((width, mid)),
}
i += 2;
}
let (_, mid_x) = best?;
let mut p = P::default();
p.set::<0>(mid_x);
p.set::<1>(sweep_y);
Some(p)
}
fn collect_crossings<P>(pts: &[P], sweep_y: P::Scalar, out: &mut Vec<P::Scalar>)
where
P: Point,
P::Scalar: CoordinateScalar,
{
let n = pts.len();
if n < 2 {
return;
}
for k in 0..n {
let a = &pts[k];
let b = &pts[(k + 1) % n];
let ay = a.get::<1>();
let by = b.get::<1>();
if (ay > sweep_y) != (by > sweep_y) {
let ax = a.get::<0>();
let bx = b.get::<0>();
let t = (sweep_y - ay) / (by - ay);
out.push(ax + t * (bx - ax));
}
}
}
#[cfg(test)]
mod tests {
use super::point_on_surface;
use geometry_algorithm::within;
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Polygon, polygon};
type P = Point2D<f64, Cartesian>;
#[test]
fn inside_a_square() {
let pg: Polygon<P> = polygon![[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (0.0, 0.0)]];
let p = point_on_surface(&pg).unwrap();
assert!(within(&p, &pg));
}
#[test]
fn inside_an_l_shape() {
let pg: Polygon<P> = polygon![[
(0.0, 0.0),
(6.0, 0.0),
(6.0, 2.0),
(2.0, 2.0),
(2.0, 6.0),
(0.0, 6.0),
(0.0, 0.0)
]];
let p = point_on_surface(&pg).unwrap();
assert!(within(&p, &pg));
}
#[test]
fn avoids_a_hole() {
let pg: Polygon<P> = polygon![
[
(0.0, 0.0),
(10.0, 0.0),
(10.0, 10.0),
(0.0, 10.0),
(0.0, 0.0)
],
[(3.0, 3.0), (7.0, 3.0), (7.0, 7.0), (3.0, 7.0), (3.0, 3.0)]
];
let p = point_on_surface(&pg).unwrap();
assert!(within(&p, &pg));
}
}