use crate::ParticleLike;
use nalgebra::{Point, SVector, SimdPartialOrd};
use num_traits::{AsPrimitive, ConstOne, ConstZero, Float, NumAssignOps};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
pub type PointCloud<const N: usize> = Vec<[f64; N]>;
#[derive(Clone, Copy, Debug, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Aabb<const N: usize = 3, F: Float = f64>
where
F: std::fmt::Debug + 'static,
{
inf: Point<F, N>,
sup: Point<F, N>,
}
impl<const N: usize, F> Aabb<N, F>
where
F: Float + std::fmt::Debug + SimdPartialOrd + ConstZero,
{
pub fn from_particles<P: ParticleLike<[F; N]>>(
mut particles: impl Iterator<Item = impl Borrow<P>>,
) -> Self {
let init = particles
.next()
.map(|p| p.borrow().coords())
.unwrap_or([F::ZERO; N]);
let init = Point::from(init);
let (inf, sup) = particles
.take(i32::MAX as usize)
.fold((init, init), |(i, s), p| {
let p = Point::from(p.borrow().coords());
(i.inf(&p), s.sup(&p))
});
Self { inf, sup }
}
fn update<P: ParticleLike<[F; N]>>(&mut self, particle: impl Borrow<P>) {
let p = Point::from(particle.borrow().coords());
self.inf = p.inf(&self.inf);
self.sup = p.sup(&self.sup);
}
pub fn inf(&self) -> [F; N] {
self.inf.into()
}
pub fn sup(&self) -> [F; N] {
self.sup.into()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GridInfo<const N: usize = 3, F: Float = f64>
where
F: std::fmt::Debug + 'static,
{
pub(crate) aabb: Aabb<N, F>,
pub(crate) cutoff: F,
shape: SVector<i32, N>,
strides: SVector<i32, N>,
}
impl<const N: usize, F> GridInfo<N, F>
where
F: Float + std::fmt::Debug,
{
pub fn origin(&self) -> [F; N] {
self.aabb.inf.into()
}
pub fn shape(&self) -> [i32; N] {
self.shape.into()
}
pub fn strides(&self) -> [i32; N] {
self.strides.into()
}
pub fn bounding_box(&self) -> &Aabb<N, F> {
&self.aabb
}
pub fn flatten_index(&self, idx: impl Borrow<[i32; N]>) -> i32 {
debug_assert!(*idx.borrow() >= [-1i32; N]);
let i = SVector::from(*idx.borrow());
i.dot(&self.strides)
}
#[inline]
pub fn cutoff(&self) -> F {
self.cutoff
}
}
impl<const N: usize, F> GridInfo<N, F>
where
F: Float + NumAssignOps + AsPrimitive<i32> + std::fmt::Debug,
{
pub fn new(aabb: Aabb<N, F>, cutoff: F) -> Self {
let shape = ((aabb.sup - aabb.inf) / cutoff).map(|coord| coord.floor().as_() + 1);
let mut strides = shape;
strides.iter_mut().fold(1, |prev, curr| {
let next = prev * (*curr + 4);
*curr = prev;
next
});
Self {
aabb,
cutoff,
shape,
strides,
}
}
pub fn cell_index(&self, coordinates: impl Borrow<[F; N]>) -> [i32; N] {
self.try_cell_index(coordinates)
.expect("cell index is out of bounds")
}
pub fn try_cell_index(&self, coordinates: impl Borrow<[F; N]>) -> Option<[i32; N]> {
let p = Point::from(*coordinates.borrow());
let idx = ((p - self.aabb.inf) / self.cutoff).map(|coord| coord.floor().as_());
if SVector::from([-1; N]) <= idx && idx <= self.shape {
Some(idx.into())
} else {
None
}
}
pub fn flat_cell_index(&self, coordinates: impl Borrow<[F; N]>) -> i32 {
let p = Point::from(*coordinates.borrow());
((p - self.aabb.inf) / self.cutoff)
.map(|coord| coord.floor().as_())
.dot(&self.strides)
}
}
impl<const N: usize, F> Default for GridInfo<N, F>
where
F: Float + std::fmt::Debug + Default + NumAssignOps + AsPrimitive<i32> + ConstOne,
{
fn default() -> Self {
GridInfo::new(Aabb::default(), F::ONE)
}
}
pub fn generate_pointcloud(shape: [usize; 3], cutoff: f64, origin: [f64; 3]) -> PointCloud<3> {
let mut pointcloud = Vec::with_capacity(shape.iter().product::<usize>().div_ceil(2));
for x in 0..shape[0] {
for y in 0..shape[1] {
for z in 0..shape[2] {
if (x + y + z) % 2 == 0 {
pointcloud.push([
cutoff.mul_add(x as f64, origin[0]),
cutoff.mul_add(y as f64, origin[1]),
cutoff.mul_add(z as f64, origin[2]),
]);
pointcloud.push([
cutoff.mul_add(x as f64, cutoff.mul_add(0.5, origin[0])),
cutoff.mul_add(y as f64, cutoff.mul_add(0.5, origin[1])),
cutoff.mul_add(z as f64, cutoff.mul_add(0.5, origin[2])),
]);
}
}
}
}
pointcloud
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_pointcloud() {
let points = vec![
[0.0, 0.0, 0.0],
[0.5, 0.5, 0.5],
[0.0, 0.0, 2.0],
[0.5, 0.5, 2.5],
[0.0, 1.0, 1.0],
[0.5, 1.5, 1.5],
[0.0, 2.0, 0.0],
[0.5, 2.5, 0.5],
[0.0, 2.0, 2.0],
[0.5, 2.5, 2.5],
[1.0, 0.0, 1.0],
[1.5, 0.5, 1.5],
[1.0, 1.0, 0.0],
[1.5, 1.5, 0.5],
[1.0, 1.0, 2.0],
[1.5, 1.5, 2.5],
[1.0, 2.0, 1.0],
[1.5, 2.5, 1.5],
[2.0, 0.0, 0.0],
[2.5, 0.5, 0.5],
[2.0, 0.0, 2.0],
[2.5, 0.5, 2.5],
[2.0, 1.0, 1.0],
[2.5, 1.5, 1.5],
[2.0, 2.0, 0.0],
[2.5, 2.5, 0.5],
[2.0, 2.0, 2.0],
[2.5, 2.5, 2.5],
];
assert_eq!(points, generate_pointcloud([3, 3, 3], 1.0, [0.0, 0.0, 0.0]));
}
#[test]
fn test_utils() {
let points = generate_pointcloud([3, 3, 3], 1.0, [0.2, 0.25, 0.3]);
assert_eq!(points.len(), 28, "testing PointCloud.len()");
let aabb = Aabb::from_particles::<[_; 3]>(points.iter());
assert_eq!(
aabb,
Aabb {
inf: [0.2, 0.25, 0.3].into(),
sup: [2.7, 2.75, 2.8].into()
},
"testing Aabb::from_particles()"
);
let grid_info = GridInfo::new(aabb, 1.0);
assert_eq!(
grid_info.origin(),
[0.2, 0.25, 0.3],
"testing GridInfo.origin()"
);
assert_eq!(grid_info.shape(), [3, 3, 3], "testing GridInfo.shape");
assert_eq!(grid_info.strides(), [1, 7, 49], "testing GridInfo.strides");
assert_eq!(
grid_info.cell_index(&[2.7, 2.75, 2.3]),
[2, 2, 1],
"testing GridInfo.cell_index()"
);
assert_eq!(
grid_info.flat_cell_index(&[2.7, 2.75, 2.3]),
65,
"testing GridInfo.flat_cell_index()"
);
assert_eq!(
grid_info.cell_index(&[2.7, 2.75, 2.8]),
[2, 2, 2],
"testing GridInfo.cell_index()"
);
assert_eq!(
grid_info.flat_cell_index(&[2.7, 2.75, 2.8]),
114,
"testing GridInfo.flat_cell_index()"
);
}
}