use crate::types::{F, F3, FNx3};
use ndarray::{Array1, Array2};
use std::sync::Arc;
pub trait Region: Send + Sync {
fn bounds(&self) -> FNx3;
fn contains(&self, points: &FNx3) -> Array1<bool>;
fn contains_point(&self, point: &[F; 3]) -> bool {
let arr = Array2::from_shape_vec((1, 3), vec![point[0], point[1], point[2]]).unwrap();
self.contains(&arr)[0]
}
}
#[derive(Debug, Clone)]
pub struct Sphere {
pub center: F3,
pub radius: F,
}
impl Sphere {
pub fn new(center: F3, radius: F) -> Self {
Self { center, radius }
}
pub fn with_radius(radius: F) -> Self {
Self {
center: Array1::zeros(3),
radius,
}
}
}
impl Region for Sphere {
fn bounds(&self) -> FNx3 {
let r = self.radius;
let mut b = Array2::zeros((3, 2));
for d in 0..3 {
b[[d, 0]] = self.center[d] - r;
b[[d, 1]] = self.center[d] + r;
}
b
}
fn contains(&self, points: &FNx3) -> Array1<bool> {
assert_eq!(points.ncols(), 3, "points must have shape (N, 3)");
let r2 = self.radius * self.radius;
let mut mask = Array1::from_elem(points.nrows(), false);
for (row, m) in points.rows().into_iter().zip(mask.iter_mut()) {
let dx = row[0] - self.center[0];
let dy = row[1] - self.center[1];
let dz = row[2] - self.center[2];
*m = (dx * dx + dy * dy + dz * dz) <= r2;
}
mask
}
fn contains_point(&self, point: &[F; 3]) -> bool {
let dx = point[0] - self.center[0];
let dy = point[1] - self.center[1];
let dz = point[2] - self.center[2];
(dx * dx + dy * dy + dz * dz) <= self.radius * self.radius
}
}
#[derive(Debug, Clone)]
pub struct Cuboid {
pub origin: F3,
pub lengths: F3,
}
impl Cuboid {
pub fn new(origin: F3, lengths: F3) -> Self {
Self { origin, lengths }
}
}
impl Region for Cuboid {
fn bounds(&self) -> FNx3 {
let mut b = Array2::zeros((3, 2));
for d in 0..3 {
b[[d, 0]] = self.origin[d];
b[[d, 1]] = self.origin[d] + self.lengths[d];
}
b
}
fn contains(&self, points: &FNx3) -> Array1<bool> {
assert_eq!(points.ncols(), 3, "points must have shape (N, 3)");
let mut mask = Array1::from_elem(points.nrows(), false);
for (row, m) in points.rows().into_iter().zip(mask.iter_mut()) {
*m = (0..3)
.all(|d| row[d] >= self.origin[d] && row[d] <= self.origin[d] + self.lengths[d]);
}
mask
}
fn contains_point(&self, point: &[F; 3]) -> bool {
(0..3).all(|d| point[d] >= self.origin[d] && point[d] <= self.origin[d] + self.lengths[d])
}
}
#[derive(Debug, Clone)]
pub struct HollowSphere {
pub center: F3,
pub outer_radius: F,
pub inner_radius: F,
}
impl HollowSphere {
pub fn new(center: F3, inner_radius: F, outer_radius: F) -> Self {
assert!(
inner_radius >= 0.0,
"inner_radius must be non-negative, got {}",
inner_radius
);
assert!(
outer_radius > inner_radius,
"outer_radius must be greater than inner_radius, got outer={}, inner={}",
outer_radius,
inner_radius
);
Self {
center,
outer_radius,
inner_radius,
}
}
pub fn with_radii(inner_radius: F, outer_radius: F) -> Self {
Self::new(Array1::zeros(3), inner_radius, outer_radius)
}
}
impl Region for HollowSphere {
fn bounds(&self) -> FNx3 {
let r = self.outer_radius;
let mut b = Array2::zeros((3, 2));
for d in 0..3 {
b[[d, 0]] = self.center[d] - r;
b[[d, 1]] = self.center[d] + r;
}
b
}
fn contains(&self, points: &FNx3) -> Array1<bool> {
assert_eq!(points.ncols(), 3, "points must have shape (N, 3)");
let outer_r2 = self.outer_radius * self.outer_radius;
let inner_r2 = self.inner_radius * self.inner_radius;
let mut mask = Array1::from_elem(points.nrows(), false);
for (row, m) in points.rows().into_iter().zip(mask.iter_mut()) {
let dx = row[0] - self.center[0];
let dy = row[1] - self.center[1];
let dz = row[2] - self.center[2];
let dist_sq = dx * dx + dy * dy + dz * dz;
*m = dist_sq > inner_r2 && dist_sq <= outer_r2;
}
mask
}
fn contains_point(&self, point: &[F; 3]) -> bool {
let dx = point[0] - self.center[0];
let dy = point[1] - self.center[1];
let dz = point[2] - self.center[2];
let dist_sq = dx * dx + dy * dy + dz * dz;
let outer_r2 = self.outer_radius * self.outer_radius;
let inner_r2 = self.inner_radius * self.inner_radius;
dist_sq > inner_r2 && dist_sq <= outer_r2
}
}
#[derive(Clone)]
pub struct AndRegion {
a: Arc<dyn Region + Send + Sync>,
b: Arc<dyn Region + Send + Sync>,
}
impl AndRegion {
pub fn new(a: Arc<dyn Region + Send + Sync>, b: Arc<dyn Region + Send + Sync>) -> Self {
Self { a, b }
}
}
impl Region for AndRegion {
fn bounds(&self) -> FNx3 {
let a_bounds = self.a.bounds();
let b_bounds = self.b.bounds();
let mut result = Array2::zeros((3, 2));
for d in 0..3 {
result[[d, 0]] = a_bounds[[d, 0]].max(b_bounds[[d, 0]]); result[[d, 1]] = a_bounds[[d, 1]].min(b_bounds[[d, 1]]); }
result
}
fn contains(&self, points: &FNx3) -> Array1<bool> {
let a_mask = self.a.contains(points);
let b_mask = self.b.contains(points);
a_mask
.iter()
.zip(b_mask.iter())
.map(|(a, b)| *a && *b)
.collect()
}
fn contains_point(&self, point: &[F; 3]) -> bool {
self.a.contains_point(point) && self.b.contains_point(point)
}
}
#[derive(Clone)]
pub struct NotRegion {
a: Arc<dyn Region + Send + Sync>,
}
impl NotRegion {
pub fn new(a: Arc<dyn Region + Send + Sync>) -> Self {
Self { a }
}
}
impl Region for NotRegion {
fn bounds(&self) -> FNx3 {
self.a.bounds()
}
fn contains(&self, points: &FNx3) -> Array1<bool> {
let a_mask = self.a.contains(points);
a_mask.iter().map(|x| !x).collect()
}
fn contains_point(&self, point: &[F; 3]) -> bool {
!self.a.contains_point(point)
}
}
#[derive(Clone)]
pub struct OrRegion {
a: Arc<dyn Region + Send + Sync>,
b: Arc<dyn Region + Send + Sync>,
}
impl OrRegion {
pub fn new(a: Arc<dyn Region + Send + Sync>, b: Arc<dyn Region + Send + Sync>) -> Self {
Self { a, b }
}
}
impl Region for OrRegion {
fn bounds(&self) -> FNx3 {
let a_bounds = self.a.bounds();
let b_bounds = self.b.bounds();
let mut result = Array2::zeros((3, 2));
for d in 0..3 {
result[[d, 0]] = a_bounds[[d, 0]].min(b_bounds[[d, 0]]); result[[d, 1]] = a_bounds[[d, 1]].max(b_bounds[[d, 1]]); }
result
}
fn contains(&self, points: &FNx3) -> Array1<bool> {
let a_mask = self.a.contains(points);
let b_mask = self.b.contains(points);
a_mask
.iter()
.zip(b_mask.iter())
.map(|(a, b)| *a || *b)
.collect()
}
fn contains_point(&self, point: &[F; 3]) -> bool {
self.a.contains_point(point) || self.b.contains_point(point)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cuboid_contains_and_bounds() {
let c = Cuboid::new(
Array1::from_vec(vec![0.0, 0.0, 0.0]),
Array1::from_vec(vec![2.0, 2.0, 2.0]),
);
let pts = Array2::from_shape_vec(
(4, 3),
vec![
1.0, 1.0, 1.0, 3.0, 1.0, 1.0, 2.0, 2.0, 2.0, -0.1, 0.0, 0.0, ],
)
.unwrap();
let mask = c.contains(&pts);
assert_eq!(mask.to_vec(), vec![true, false, true, false]);
assert!(c.contains_point(&[0.5, 1.0, 2.0]));
assert!(!c.contains_point(&[2.5, 1.0, 1.0]));
let b = c.bounds();
assert_eq!(b[[0, 0]], 0.0);
assert_eq!(b[[0, 1]], 2.0);
}
#[test]
fn sphere_bounds_are_correct() {
let s = Sphere::new(Array1::from_vec(vec![1.0, 2.0, 3.0]), 2.0);
let b = s.bounds();
assert_eq!(b[[0, 0]], -1.0);
assert_eq!(b[[1, 0]], 0.0);
assert_eq!(b[[2, 0]], 1.0);
assert_eq!(b[[0, 1]], 3.0);
assert_eq!(b[[1, 1]], 4.0);
assert_eq!(b[[2, 1]], 5.0);
}
#[test]
fn sphere_contains_points() {
let s = Sphere::with_radius(2.0);
let pts: FNx3 = Array2::from_shape_vec(
(3, 3),
vec![
0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 2.1, 0.0, 0.0, ],
)
.unwrap();
let mask = s.contains(&pts);
assert_eq!(mask.len(), 3);
assert!(mask[0]);
assert!(mask[1]);
assert!(!mask[2]);
}
#[test]
fn hollow_sphere_bounds_are_correct() {
let hs = HollowSphere::new(Array1::from_vec(vec![1.0, 2.0, 3.0]), 2.0, 5.0);
let b = hs.bounds();
assert_eq!(b[[0, 0]], -4.0); assert_eq!(b[[1, 0]], -3.0); assert_eq!(b[[2, 0]], -2.0); assert_eq!(b[[0, 1]], 6.0); assert_eq!(b[[1, 1]], 7.0); assert_eq!(b[[2, 1]], 8.0); }
#[test]
fn hollow_sphere_contains_points() {
let hs = HollowSphere::with_radii(2.0, 5.0);
let pts: FNx3 = Array2::from_shape_vec(
(5, 3),
vec![
0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 3.0, 0.0, 0.0, 5.0, 0.0, 0.0, 5.1, 0.0, 0.0, ],
)
.unwrap();
let mask = hs.contains(&pts);
assert_eq!(mask.len(), 5);
assert!(!mask[0], "center should be outside (inside inner sphere)");
assert!(!mask[1], "point inside inner sphere should be false");
assert!(mask[2], "point in shell should be true");
assert!(mask[3], "point on outer surface should be true");
assert!(!mask[4], "point outside outer sphere should be false");
}
}