use crate::coords::CoordSystem;
use crate::error::{Error, Result};
use crate::math::{BoundingBox, Vec3};
use crate::param::{ParameterSet, Scalar, ScalarPoint};
use super::geom::{angle_ok, in_range, sector_bbox, tol};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct BoxShape {
pub start: ScalarPoint,
pub stop: ScalarPoint,
}
impl BoxShape {
pub fn new(start: impl Into<ScalarPoint>, stop: impl Into<ScalarPoint>) -> Self {
BoxShape {
start: start.into(),
stop: stop.into(),
}
}
pub(crate) fn inside(&self, p: Vec3, cs: CoordSystem) -> bool {
inside_box(self.start.value(), self.stop.value(), p, cs)
}
pub(crate) fn inside_native(&self, c: Vec3, cs: CoordSystem) -> bool {
inside_box_native(self.start.value(), self.stop.value(), c, cs)
}
pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
box_bbox(self.start.value(), self.stop.value(), cs)
}
pub(crate) fn native(&self) -> BoundingBox {
BoundingBox::from_corners(self.start.value(), self.stop.value())
}
pub(crate) fn dimension(&self) -> usize {
self.native().dimension()
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
self.start.update(p)?;
self.stop.update(p)
}
}
pub(crate) fn inside_box(a: Vec3, b: Vec3, p: Vec3, cs: CoordSystem) -> bool {
inside_box_native(a, b, cs.from_cartesian(p), cs)
}
pub(crate) fn inside_box_native(a: Vec3, b: Vec3, c: Vec3, cs: CoordSystem) -> bool {
match cs {
CoordSystem::Cartesian => (0..3).all(|i| in_range(c[i], a[i], b[i])),
CoordSystem::Cylindrical => {
if !in_range(c[2], a[2], b[2]) || !in_range(c[0], a[0], b[0]) {
return false;
}
c[0] <= tol(a[0], b[0]) || angle_ok(c[1], a[1], b[1])
}
}
}
pub(crate) fn box_bbox(a: Vec3, b: Vec3, cs: CoordSystem) -> BoundingBox {
match cs {
CoordSystem::Cartesian => BoundingBox::from_corners(a, b),
CoordSystem::Cylindrical => sector_bbox((a[0], b[0]), (a[1], b[1]), (a[2], b[2])),
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct MultiBox {
pub boxes: Vec<(ScalarPoint, ScalarPoint)>,
}
impl MultiBox {
pub fn new<A: Into<ScalarPoint>, B: Into<ScalarPoint>>(
boxes: impl IntoIterator<Item = (A, B)>,
) -> Self {
MultiBox {
boxes: boxes
.into_iter()
.map(|(a, b)| (a.into(), b.into()))
.collect(),
}
}
pub fn push(&mut self, start: impl Into<ScalarPoint>, stop: impl Into<ScalarPoint>) {
self.boxes.push((start.into(), stop.into()));
}
pub(crate) fn inside(&self, p: Vec3, cs: CoordSystem) -> bool {
self.inside_native(cs.from_cartesian(p))
}
pub(crate) fn inside_native(&self, c: Vec3) -> bool {
self.boxes.iter().any(|(a, b)| {
let (a, b) = (a.value(), b.value());
(0..3).all(|i| in_range(c[i], a[i], b[i]))
})
}
pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
self.boxes.iter().fold(BoundingBox::EMPTY, |acc, (a, b)| {
acc.union(&box_bbox(a.value(), b.value(), cs))
})
}
pub(crate) fn native(&self) -> BoundingBox {
self.boxes.iter().fold(BoundingBox::EMPTY, |acc, (a, b)| {
acc.union(&BoundingBox::from_corners(a.value(), b.value()))
})
}
pub(crate) fn dimension(&self) -> usize {
self.boxes
.iter()
.map(|(a, b)| BoundingBox::from_corners(a.value(), b.value()).dimension())
.max()
.unwrap_or(0)
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
for (a, b) in &mut self.boxes {
a.update(p)?;
b.update(p)?;
}
Ok(())
}
}
fn check_radius(r: &Scalar, what: &str) -> Result<()> {
if r.value() < 0.0 {
Err(Error::Invalid(format!("negative {what} {}", r.value())))
} else {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Sphere {
pub center: ScalarPoint,
pub radius: Scalar,
}
impl Sphere {
pub fn new(center: impl Into<ScalarPoint>, radius: impl Into<Scalar>) -> Self {
Sphere {
center: center.into(),
radius: radius.into(),
}
}
pub(crate) fn inside(&self, p: Vec3, cs: CoordSystem) -> bool {
let c = cs.to_cartesian(self.center.value());
let r = self.radius.value();
(p - c).norm() <= r + tol(r, 0.0)
}
pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
let c = cs.to_cartesian(self.center.value());
BoundingBox::from_corners(c, c).expanded(self.radius.value())
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
self.center.update(p)?;
self.radius.update(p)?;
check_radius(&self.radius, "radius")
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct SphericalShell {
pub center: ScalarPoint,
pub radius: Scalar,
pub shell_width: Scalar,
}
impl SphericalShell {
pub fn new(
center: impl Into<ScalarPoint>,
radius: impl Into<Scalar>,
shell_width: impl Into<Scalar>,
) -> Self {
SphericalShell {
center: center.into(),
radius: radius.into(),
shell_width: shell_width.into(),
}
}
pub(crate) fn inside(&self, p: Vec3, cs: CoordSystem) -> bool {
let c = cs.to_cartesian(self.center.value());
let (r, w) = (self.radius.value(), self.shell_width.value().abs() / 2.0);
((p - c).norm() - r).abs() <= w + tol(r, w)
}
pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
let c = cs.to_cartesian(self.center.value());
BoundingBox::from_corners(c, c)
.expanded(self.radius.value() + self.shell_width.value().abs() / 2.0)
}
pub(crate) fn dimension(&self) -> usize {
if self.shell_width.value() != 0.0 {
3
} else {
2
}
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
self.center.update(p)?;
self.radius.update(p)?;
self.shell_width.update(p)?;
check_radius(&self.radius, "radius")
}
}
fn axial(a: Vec3, b: Vec3, p: Vec3) -> Option<(f64, f64, f64)> {
let d = b - a;
let l2 = d.norm_sq();
if l2 == 0.0 {
return None;
}
let t = (p - a).dot(d) / l2;
let r = (p - (a + d * t)).norm();
Some((t, r, l2.sqrt()))
}
fn cylinder_bbox(a: Vec3, b: Vec3, r: f64) -> BoundingBox {
let d = (b - a).normalized();
let e = Vec3::new(
r * (1.0 - d[0] * d[0]).max(0.0).sqrt(),
r * (1.0 - d[1] * d[1]).max(0.0).sqrt(),
r * (1.0 - d[2] * d[2]).max(0.0).sqrt(),
);
let bb = BoundingBox::from_corners(a, b);
BoundingBox {
min: bb.min - e,
max: bb.max + e,
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Cylinder {
pub start: ScalarPoint,
pub stop: ScalarPoint,
pub radius: Scalar,
}
impl Cylinder {
pub fn new(
start: impl Into<ScalarPoint>,
stop: impl Into<ScalarPoint>,
radius: impl Into<Scalar>,
) -> Self {
Cylinder {
start: start.into(),
stop: stop.into(),
radius: radius.into(),
}
}
pub(crate) fn inside(&self, p: Vec3, cs: CoordSystem) -> bool {
let (a, b) = (
cs.to_cartesian(self.start.value()),
cs.to_cartesian(self.stop.value()),
);
let r = self.radius.value();
match axial(a, b, p) {
Some((t, d, len)) => {
let tt = tol(len, 0.0) / len;
t >= -tt && t <= 1.0 + tt && d <= r + tol(r, 0.0)
}
None => false,
}
}
pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
let (a, b) = (
cs.to_cartesian(self.start.value()),
cs.to_cartesian(self.stop.value()),
);
cylinder_bbox(a, b, self.radius.value())
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
self.start.update(p)?;
self.stop.update(p)?;
self.radius.update(p)?;
check_radius(&self.radius, "radius")
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct CylindricalShell {
pub start: ScalarPoint,
pub stop: ScalarPoint,
pub radius: Scalar,
pub shell_width: Scalar,
}
impl CylindricalShell {
pub fn new(
start: impl Into<ScalarPoint>,
stop: impl Into<ScalarPoint>,
radius: impl Into<Scalar>,
shell_width: impl Into<Scalar>,
) -> Self {
CylindricalShell {
start: start.into(),
stop: stop.into(),
radius: radius.into(),
shell_width: shell_width.into(),
}
}
pub(crate) fn inside(&self, p: Vec3, cs: CoordSystem) -> bool {
let (a, b) = (
cs.to_cartesian(self.start.value()),
cs.to_cartesian(self.stop.value()),
);
let (r, w) = (self.radius.value(), self.shell_width.value().abs() / 2.0);
match axial(a, b, p) {
Some((t, d, len)) => {
let tt = tol(len, 0.0) / len;
t >= -tt && t <= 1.0 + tt && (d - r).abs() <= w + tol(r, w)
}
None => false,
}
}
pub(crate) fn bbox(&self, cs: CoordSystem) -> BoundingBox {
let (a, b) = (
cs.to_cartesian(self.start.value()),
cs.to_cartesian(self.stop.value()),
);
cylinder_bbox(
a,
b,
self.radius.value() + self.shell_width.value().abs() / 2.0,
)
}
pub(crate) fn dimension(&self) -> usize {
if self.shell_width.value() != 0.0 {
3
} else {
2
}
}
pub(crate) fn update(&mut self, p: &ParameterSet) -> Result<()> {
self.start.update(p)?;
self.stop.update(p)?;
self.radius.update(p)?;
self.shell_width.update(p)?;
check_radius(&self.radius, "radius")
}
}