use core::f64::consts::{PI, TAU};
use crate::error::{Error, Result};
use crate::math::{BoundingBox, Vec3};
use crate::param::{ParameterSet, Scalar};
use super::geom::{angle_ok, in_range, point_in_polygon, sector_bbox};
#[inline]
pub fn plane_axes(n: usize) -> (usize, usize) {
((n + 1) % 3, (n + 2) % 3)
}
fn check_dir(d: usize, what: &str) -> Result<()> {
if d < 3 {
Ok(())
} else {
Err(Error::Invalid(format!("{what} {d} is not an axis (0..=2)")))
}
}
fn verts_bbox(n: usize, verts: &[(f64, f64)], lo: f64, hi: f64) -> BoundingBox {
let (u, v) = plane_axes(n);
let mut b = BoundingBox::EMPTY;
for &(a, c) in verts {
for h in [lo, hi] {
let mut p = Vec3::ZERO;
p[u] = a;
p[v] = c;
p[n] = h;
b.include(p);
}
}
b
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Polygon {
pub norm_dir: usize,
pub elevation: Scalar,
pub vertices: Vec<(f64, f64)>,
}
impl Polygon {
pub fn new(norm_dir: usize, elevation: impl Into<Scalar>, vertices: Vec<(f64, f64)>) -> Self {
Polygon {
norm_dir,
elevation: elevation.into(),
vertices,
}
}
pub(crate) fn inside(&self, p: Vec3) -> bool {
let n = self.norm_dir;
let (u, v) = plane_axes(n);
let e = self.elevation.value();
in_range(p[n], e, e) && point_in_polygon((p[u], p[v]), &self.vertices)
}
fn native(&self) -> BoundingBox {
let e = self.elevation.value();
verts_bbox(self.norm_dir, &self.vertices, e, e)
}
pub(crate) fn bbox(&self) -> BoundingBox {
self.native()
}
pub(crate) fn dimension(&self) -> usize {
if self.vertices.len() >= 3 {
2
} else {
self.native().dimension()
}
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
check_dir(self.norm_dir, "normal direction")?;
self.elevation.update(p)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct LinPoly {
pub norm_dir: usize,
pub elevation: Scalar,
pub length: Scalar,
pub vertices: Vec<(f64, f64)>,
}
impl LinPoly {
pub fn new(
norm_dir: usize,
elevation: impl Into<Scalar>,
length: impl Into<Scalar>,
vertices: Vec<(f64, f64)>,
) -> Self {
LinPoly {
norm_dir,
elevation: elevation.into(),
length: length.into(),
vertices,
}
}
pub(crate) fn inside(&self, p: Vec3) -> bool {
let n = self.norm_dir;
let (u, v) = plane_axes(n);
let e = self.elevation.value();
let l = self.length.value();
in_range(p[n], e, e + l) && point_in_polygon((p[u], p[v]), &self.vertices)
}
fn native(&self) -> BoundingBox {
let e = self.elevation.value();
verts_bbox(self.norm_dir, &self.vertices, e, e + self.length.value())
}
pub(crate) fn bbox(&self) -> BoundingBox {
self.native()
}
pub(crate) fn dimension(&self) -> usize {
if self.length.value() != 0.0 { 3 } else { 2 }
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
check_dir(self.norm_dir, "normal direction")?;
self.elevation.update(p)?;
self.length.update(p)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RotPoly {
pub norm_dir: usize,
pub rot_axis_dir: usize,
pub elevation: Scalar,
pub start_angle: Scalar,
pub stop_angle: Scalar,
pub vertices: Vec<(f64, f64)>,
}
impl Default for RotPoly {
fn default() -> Self {
RotPoly {
norm_dir: 0,
rot_axis_dir: 2,
elevation: Scalar::new(0.0),
start_angle: Scalar::new(0.0),
stop_angle: Scalar::new(TAU),
vertices: Vec::new(),
}
}
}
impl RotPoly {
pub fn new(norm_dir: usize, rot_axis_dir: usize, vertices: Vec<(f64, f64)>) -> Self {
RotPoly {
norm_dir,
rot_axis_dir,
vertices,
..Default::default()
}
}
fn frame(&self) -> (usize, Vec3) {
let a = self.rot_axis_dir;
let q = 3 - a - self.norm_dir;
(q, Vec3::unit(a).cross(Vec3::unit(q)))
}
fn to_poly(&self, radial: f64, axial: f64, q: usize) -> (f64, f64) {
let (u, _) = plane_axes(self.norm_dir);
if u == q {
(radial, axial)
} else {
(axial, radial)
}
}
pub(crate) fn inside(&self, p: Vec3) -> bool {
let a = self.rot_axis_dir;
let (q, w) = self.frame();
let h = p[a];
let x = p[q];
let y = p.dot(w);
let r = x.hypot(y);
let phi = y.atan2(x);
let (s, e) = (self.start_angle.value(), self.stop_angle.value());
if r == 0.0 {
return point_in_polygon(self.to_poly(0.0, h, q), &self.vertices);
}
(angle_ok(phi, s, e) && point_in_polygon(self.to_poly(r, h, q), &self.vertices))
|| (angle_ok(phi + PI, s, e)
&& point_in_polygon(self.to_poly(-r, h, q), &self.vertices))
}
pub(crate) fn bbox(&self) -> BoundingBox {
let a = self.rot_axis_dir;
let (q, w) = self.frame();
let (u, _) = plane_axes(self.norm_dir);
let (mut rpos, mut rneg) = (f64::NEG_INFINITY, f64::NEG_INFINITY);
let (mut hmin, mut hmax) = (f64::INFINITY, f64::NEG_INFINITY);
for &(c0, c1) in &self.vertices {
let (r, h) = if u == q { (c0, c1) } else { (c1, c0) };
rpos = rpos.max(r);
rneg = rneg.max(-r);
hmin = hmin.min(h);
hmax = hmax.max(h);
}
if self.vertices.is_empty() {
return BoundingBox::EMPTY;
}
let (s, e) = (self.start_angle.value(), self.stop_angle.value());
let mut local = BoundingBox::EMPTY;
if rpos > 0.0 {
local = local.union(§or_bbox((0.0, rpos), (s, e), (hmin, hmax)));
}
if rneg > 0.0 {
local = local.union(§or_bbox((0.0, rneg), (s + PI, e + PI), (hmin, hmax)));
}
if local.is_empty() {
local = BoundingBox::from_corners(Vec3::new(0.0, 0.0, hmin), Vec3::new(0.0, 0.0, hmax));
}
BoundingBox::from_points(
local
.corners()
.map(|c| Vec3::unit(q) * c[0] + w * c[1] + Vec3::unit(a) * c[2]),
)
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
check_dir(self.norm_dir, "normal direction")?;
check_dir(self.rot_axis_dir, "rotation axis")?;
if self.norm_dir == self.rot_axis_dir {
return Err(Error::Invalid(
"rotation axis must lie in the polygon plane".into(),
));
}
self.elevation.update(p)?;
self.start_angle.update(p)?;
self.stop_angle.update(p)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn square() -> Vec<(f64, f64)> {
vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
}
#[test]
fn polygon_axes() {
let p = Polygon::new(2, 0.5, vec![(0.0, 0.0), (2.0, 0.0), (2.0, 1.0)]);
assert!(p.inside(Vec3::new(1.5, 0.5, 0.5)));
assert!(!p.inside(Vec3::new(0.5, 1.5, 0.5)));
assert!(!p.inside(Vec3::new(1.5, 0.5, 0.6)));
let p = Polygon::new(0, 0.0, vec![(0.0, 0.0), (2.0, 0.0), (2.0, 1.0)]);
assert!(p.inside(Vec3::new(0.0, 1.5, 0.5)));
assert_eq!(p.dimension(), 2);
}
#[test]
fn linpoly() {
let l = LinPoly::new(1, 1.0, -2.0, square());
assert!(l.inside(Vec3::new(0.25, 0.0, 0.75)));
assert!(!l.inside(Vec3::new(0.25, 1.5, 0.75)));
let b = l.bbox();
assert_eq!(b.min, Vec3::new(0.0, -1.0, 0.0));
assert_eq!(b.max, Vec3::new(1.0, 1.0, 1.0));
}
#[test]
fn rotpoly_torus_like() {
let r = RotPoly::new(1, 2, vec![(0.0, 1.0), (1.0, 1.0), (1.0, 2.0), (0.0, 2.0)]);
for ang in [0.0f64, 1.0, 2.5, -2.0] {
let p = Vec3::new(1.5 * ang.cos(), 1.5 * ang.sin(), 0.5);
assert!(r.inside(p), "{ang}");
}
assert!(!r.inside(Vec3::new(0.5, 0.0, 0.5)));
let b = r.bbox();
assert!((b.min - Vec3::new(-2.0, -2.0, 0.0)).norm() < 1e-12);
assert!((b.max - Vec3::new(2.0, 2.0, 1.0)).norm() < 1e-12);
let mut half = r.clone();
half.stop_angle = Scalar::new(PI / 2.0);
assert!(half.inside(Vec3::new(1.0, 1.0, 0.5)));
assert!(!half.inside(Vec3::new(-1.0, 1.0, 0.5)));
}
}