use super::Shape;
#[derive(Clone)]
pub struct PolygonPrism {
verts: Vec<(f64, f64)>,
y_min: i32,
y_max: i32,
min_x: i32,
min_z: i32,
max_x: i32,
max_z: i32,
}
impl PolygonPrism {
pub fn new(verts: Vec<(f64, f64)>, y0: i32, y1: i32) -> Self {
let (y_min, y_max) = (y0.min(y1), y0.max(y1));
let (mut lo_x, mut lo_z, mut hi_x, mut hi_z) =
(f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
for &(x, z) in &verts {
lo_x = lo_x.min(x);
lo_z = lo_z.min(z);
hi_x = hi_x.max(x);
hi_z = hi_z.max(z);
}
if !lo_x.is_finite() {
lo_x = 0.0;
lo_z = 0.0;
hi_x = -1.0;
hi_z = -1.0;
}
Self {
verts,
y_min,
y_max,
min_x: lo_x.floor() as i32,
min_z: lo_z.floor() as i32,
max_x: hi_x.ceil() as i32,
max_z: hi_z.ceil() as i32,
}
}
fn footprint_contains(&self, px: f64, pz: f64) -> bool {
let v = &self.verts;
let n = v.len();
if n < 3 {
return false;
}
let mut inside = false;
let mut j = n - 1;
for i in 0..n {
let (xi, zi) = v[i];
let (xj, zj) = v[j];
if (zi > pz) != (zj > pz) {
let xint = (xj - xi) * (pz - zi) / (zj - zi) + xi;
if px < xint {
inside = !inside;
}
}
j = i;
}
inside
}
}
impl Shape for PolygonPrism {
fn contains(&self, x: i32, y: i32, z: i32) -> bool {
y >= self.y_min
&& y <= self.y_max
&& self.footprint_contains(x as f64 + 0.5, z as f64 + 0.5)
}
fn points(&self) -> Vec<(i32, i32, i32)> {
let mut points = Vec::new();
self.for_each_point(|x, y, z| points.push((x, y, z)));
points
}
fn normal_at(&self, _x: i32, _y: i32, _z: i32) -> (f64, f64, f64) {
(0.0, 1.0, 0.0)
}
fn bounds(&self) -> (i32, i32, i32, i32, i32, i32) {
(self.min_x, self.y_min, self.min_z, self.max_x, self.y_max, self.max_z)
}
fn for_each_point<F>(&self, mut f: F)
where
F: FnMut(i32, i32, i32),
{
for x in self.min_x..=self.max_x {
for z in self.min_z..=self.max_z {
if self.footprint_contains(x as f64 + 0.5, z as f64 + 0.5) {
for y in self.y_min..=self.y_max {
f(x, y, z);
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn square() -> PolygonPrism {
PolygonPrism::new(
vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)],
0,
4,
)
}
#[test]
fn fills_footprint_and_height() {
let p = square();
assert!(p.contains(5, 2, 5), "center of the prism is solid");
assert!(p.contains(1, 0, 1), "corner interior is solid");
assert!(!p.contains(5, 5, 5), "above the top is empty");
assert!(!p.contains(5, -1, 5), "below the base is empty");
assert!(!p.contains(11, 2, 5), "outside the footprint is empty");
assert_eq!(p.bounds(), (0, 0, 0, 10, 4, 10));
}
#[test]
fn concave_polygon_respects_the_notch() {
let l = PolygonPrism::new(
vec![
(0.0, 0.0),
(10.0, 0.0),
(10.0, 4.0),
(4.0, 4.0),
(4.0, 10.0),
(0.0, 10.0),
],
0,
2,
);
assert!(l.contains(2, 1, 2), "inside the L arm");
assert!(l.contains(8, 1, 2), "inside the L foot");
assert!(!l.contains(8, 1, 8), "the notch is empty");
}
}