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};
#[derive(Debug, Clone, Default)]
pub struct PolyVertices {
points: Vec<[Scalar; 2]>,
values: Vec<(f64, f64)>,
}
impl PartialEq for PolyVertices {
fn eq(&self, o: &Self) -> bool {
self.points == o.points
}
}
impl PolyVertices {
pub fn new() -> Self {
Self::default()
}
pub fn from_scalars(points: impl IntoIterator<Item = [Scalar; 2]>) -> Self {
let points: Vec<[Scalar; 2]> = points.into_iter().collect();
let values = points.iter().map(|[a, b]| (a.value(), b.value())).collect();
PolyVertices { points, values }
}
pub fn points(&self) -> &[[Scalar; 2]] {
&self.points
}
pub fn values(&self) -> &[(f64, f64)] {
&self.values
}
pub fn push(&mut self, u: impl Into<Scalar>, v: impl Into<Scalar>) {
let p = [u.into(), v.into()];
self.values.push((p[0].value(), p[1].value()));
self.points.push(p);
}
pub fn set(&mut self, i: usize, u: impl Into<Scalar>, v: impl Into<Scalar>) {
let p = [u.into(), v.into()];
self.values[i] = (p[0].value(), p[1].value());
self.points[i] = p;
}
pub fn clear(&mut self) {
self.points.clear();
self.values.clear();
}
pub fn update(&mut self, params: &ParameterSet) -> Result<()> {
for (p, v) in self.points.iter_mut().zip(&mut self.values) {
p[0].update(params)?;
p[1].update(params)?;
*v = (p[0].value(), p[1].value());
}
Ok(())
}
}
impl core::ops::Deref for PolyVertices {
type Target = [(f64, f64)];
fn deref(&self) -> &[(f64, f64)] {
&self.values
}
}
impl<'a> IntoIterator for &'a PolyVertices {
type Item = &'a (f64, f64);
type IntoIter = core::slice::Iter<'a, (f64, f64)>;
fn into_iter(self) -> Self::IntoIter {
self.values.iter()
}
}
impl From<Vec<(f64, f64)>> for PolyVertices {
fn from(v: Vec<(f64, f64)>) -> Self {
let points = v
.iter()
.map(|&(a, b)| [Scalar::new(a), Scalar::new(b)])
.collect();
PolyVertices { points, values: v }
}
}
impl FromIterator<[Scalar; 2]> for PolyVertices {
fn from_iter<I: IntoIterator<Item = [Scalar; 2]>>(it: I) -> Self {
PolyVertices::from_scalars(it)
}
}
impl FromIterator<(f64, f64)> for PolyVertices {
fn from_iter<I: IntoIterator<Item = (f64, f64)>>(it: I) -> Self {
PolyVertices::from(it.into_iter().collect::<Vec<_>>())
}
}
#[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 {
if n > 2 {
return BoundingBox::EMPTY;
}
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: PolyVertices,
}
impl Polygon {
pub fn new(
norm_dir: usize,
elevation: impl Into<Scalar>,
vertices: impl Into<PolyVertices>,
) -> Self {
Polygon {
norm_dir,
elevation: elevation.into(),
vertices: vertices.into(),
}
}
pub(crate) fn inside(&self, p: Vec3) -> bool {
let n = self.norm_dir;
if n > 2 {
return false;
}
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.vertices.update(p)?;
self.elevation.update(p)
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct LinPoly {
pub norm_dir: usize,
pub elevation: Scalar,
pub length: Scalar,
pub vertices: PolyVertices,
}
impl LinPoly {
pub fn new(
norm_dir: usize,
elevation: impl Into<Scalar>,
length: impl Into<Scalar>,
vertices: impl Into<PolyVertices>,
) -> Self {
LinPoly {
norm_dir,
elevation: elevation.into(),
length: length.into(),
vertices: vertices.into(),
}
}
pub(crate) fn inside(&self, p: Vec3) -> bool {
let n = self.norm_dir;
if n > 2 {
return false;
}
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.vertices.update(p)?;
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: PolyVertices,
}
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: PolyVertices::new(),
}
}
}
impl RotPoly {
pub fn new(norm_dir: usize, rot_axis_dir: usize, vertices: impl Into<PolyVertices>) -> Self {
RotPoly {
norm_dir,
rot_axis_dir,
vertices: vertices.into(),
..Default::default()
}
}
fn axes_valid(&self) -> bool {
self.norm_dir < 3 && self.rot_axis_dir < 3 && self.norm_dir != self.rot_axis_dir
}
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 {
if !self.axes_valid() {
return false;
}
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 {
if !self.axes_valid() {
return BoundingBox::EMPTY;
}
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.vertices.update(p)?;
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)));
}
#[test]
fn parametric_vertices() {
let mut v = PolyVertices::new();
v.push(0.0, 0.0);
v.push("w", 0.0);
v.push("w", "w/2");
v.push(0.0, "2*3");
assert_eq!(v[3], (0.0, 6.0));
assert!(v[1].0.is_nan());
let mut p = Polygon::new(2, 0.0, v);
let mut ps = ParameterSet::new();
ps.set("w", 2.0);
p.update(&ps).unwrap();
assert_eq!(p.vertices.values()[2], (2.0, 1.0));
assert!(p.inside(Vec3::new(1.5, 0.5, 0.0)));
assert!(!p.inside(Vec3::new(3.0, 0.5, 0.0)));
ps.set("w", 4.0);
p.update(&ps).unwrap();
assert!(p.inside(Vec3::new(3.0, 0.5, 0.0)));
assert_eq!(p.vertices.points()[1][0].expression(), Some("w"));
let q = Polygon::new(2, 0.0, p.vertices.clone());
assert_eq!(p, q);
p.vertices.set(0, -1.0, -1.0);
assert_eq!(p.vertices[0], (-1.0, -1.0));
assert!(p.update(&ParameterSet::new()).is_err());
}
}