use crate::topology::{Axis, DirectedAxis, DirectedAxisArray, Direction};
use crate::{Aabb3d, Index, Real};
use bitflags::bitflags;
use itertools::iproduct;
use log::trace;
use nalgebra::Vector3;
use num_traits::Bounded;
use std::iter::Iterator;
use thiserror::Error as ThisError;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct PointIndex<I: Index> {
index: [I; 3],
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct CellIndex<I: Index> {
index: [I; 3],
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct EdgeIndex<I: Index> {
origin: PointIndex<I>,
axis: Axis,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Neighborhood<'a, I: Index> {
origin: &'a PointIndex<I>,
neighbors: DirectedAxisArray<Option<PointIndex<I>>>,
}
pub struct NeighborEdge<'a, 'b: 'a, I: Index> {
neighborhood: &'a Neighborhood<'b, I>,
neighbor: &'a PointIndex<I>,
connectivity: DirectedAxis,
}
#[derive(Clone, Debug)]
pub struct OwningSubdomainGrid<I: Index, R: Real> {
global_grid: UniformGrid<I, R>,
subdomain_grid: UniformGrid<I, R>,
subdomain_offset: [I; 3],
}
#[derive(Clone, Debug)]
pub struct DummySubdomain<'a, I: Index, R: Real> {
global_grid: &'a UniformGrid<I, R>,
subdomain_offset: [I; 3],
}
pub trait Subdomain<I: Index, R: Real> {
fn global_grid(&self) -> &UniformGrid<I, R>;
fn subdomain_grid(&self) -> &UniformGrid<I, R>;
fn subdomain_offset(&self) -> &[I; 3];
fn min_point(&self) -> PointIndex<I> {
self.global_grid()
.get_point(self.subdomain_offset().clone())
.expect("Invalid subdomain")
}
fn max_point(&self) -> PointIndex<I> {
let max_point = Direction::Positive
.checked_apply_step_ijk(
&self.subdomain_offset(),
self.subdomain_grid().cells_per_dim(),
)
.expect("Invalid subdomain");
self.global_grid()
.get_point(max_point)
.expect("Invalid subdomain")
}
fn map_point(&self, global_point: &PointIndex<I>) -> Option<PointIndex<I>> {
let new_point = Direction::Negative
.checked_apply_step_ijk(global_point.index(), &self.subdomain_offset())?;
self.subdomain_grid().get_point(new_point)
}
fn inv_map_point(&self, subdomain_point: &PointIndex<I>) -> Option<PointIndex<I>> {
let new_point = Direction::Positive
.checked_apply_step_ijk(subdomain_point.index(), &self.subdomain_offset())?;
self.global_grid().get_point(new_point)
}
fn map_point_to<S: Subdomain<I, R>>(
&self,
other_subdomain: &S,
subdomain_point: &PointIndex<I>,
) -> Option<PointIndex<I>> {
let global_point = self.inv_map_point(subdomain_point)?;
other_subdomain.map_point(&global_point)
}
fn map_cell(&self, global_cell: &CellIndex<I>) -> Option<CellIndex<I>> {
let new_cell = Direction::Negative
.checked_apply_step_ijk(global_cell.index(), self.subdomain_offset())?;
self.subdomain_grid().get_cell(new_cell)
}
fn inv_map_cell(&self, subdomain_cell: &CellIndex<I>) -> Option<CellIndex<I>> {
let new_cell = Direction::Positive
.checked_apply_step_ijk(subdomain_cell.index(), self.subdomain_offset())?;
self.global_grid().get_cell(new_cell)
}
fn map_cell_to<S: Subdomain<I, R>>(
&self,
other_subdomain: &S,
subdomain_cell: &CellIndex<I>,
) -> Option<CellIndex<I>> {
let global_point = self.inv_map_cell(subdomain_cell)?;
other_subdomain.map_cell(&global_point)
}
}
bitflags! {
#[derive(Copy, Clone, Debug)]
struct FaceFlags: u8 {
const X_NEG = 0b00000001;
const X_POS = 0b00000010;
const Y_NEG = 0b00000100;
const Y_POS = 0b00001000;
const Z_NEG = 0b00010000;
const Z_POS = 0b00100000;
}
}
#[derive(Copy, Clone, Debug)]
pub struct GridBoundaryFaceFlags(FaceFlags);
#[derive(Copy, Clone, Debug)]
struct CellBoundaryFaceFlags(FaceFlags);
pub type UniformGrid<I, R> = UniformCartesianCubeGrid3d<I, R>;
#[derive(Clone, PartialEq, Debug)]
pub struct UniformCartesianCubeGrid3d<I: Index, R: Real> {
aabb: Aabb3d<R>,
cell_size: R,
n_points_per_dim: [I; 3],
n_cells_per_dim: [I; 3],
}
#[rustfmt::skip]
#[derive(Copy, Clone, Eq, PartialEq, Debug, ThisError)]
pub enum GridConstructionError<I: Index, R: Real> {
#[error("invalid cell size `{0}` supplied, cell size has to be larger than zero")]
InvalidCellSize(R),
#[error("degenerate AABB supplied, every dimension of the AABB has to have non-zero extents")]
DegenerateAabb,
#[error("inconsistent AABB supplied, every dimension of the AABB has to have an extent larger than zero")]
InconsistentAabb,
#[error("index type is too small to index number of cells per dimension of the domain (max index: {})", I::max_value())]
IndexTypeTooSmallCellsPerDim,
#[error("index type is too small to index number of points per dimension of the domain (max index: {})", I::max_value())]
IndexTypeTooSmallPointsPerDim,
#[error("index type is too small to index the total number of points in the whole domain ({0}x{1}x{2}, max index: {})", I::max_value())]
IndexTypeTooSmallTotalPoints(I, I, I),
#[error("real type is too small to store the coordinates of all points in the domain (max value: {})", <R as Bounded>::max_value())]
RealTypeTooSmallDomainSize,
}
impl<I: Index, R: Real> UniformCartesianCubeGrid3d<I, R> {
pub fn from_aabb(aabb: &Aabb3d<R>, cell_size: R) -> Result<Self, GridConstructionError<I, R>> {
if !(cell_size > R::zero()) {
return Err(GridConstructionError::InvalidCellSize(cell_size));
}
if aabb.is_degenerate() {
return Err(GridConstructionError::DegenerateAabb);
}
if !aabb.is_consistent() {
return Err(GridConstructionError::InconsistentAabb);
}
let aligned_min = aabb
.min()
.unscale(cell_size)
.map(|x| x.floor())
.scale(cell_size);
let aabb = Aabb3d::new(aligned_min, aabb.max().clone());
let n_cells_real = aabb.extents() / cell_size;
let n_cells_per_dim = Self::checked_n_cells_per_dim(&n_cells_real)
.ok_or(GridConstructionError::IndexTypeTooSmallCellsPerDim)?;
Self::new(aabb.min(), &n_cells_per_dim, cell_size)
}
pub fn new(
min: &Vector3<R>,
n_cells_per_dim: &[I; 3],
cell_size: R,
) -> Result<Self, GridConstructionError<I, R>> {
let n_cells_per_dim = n_cells_per_dim.clone();
let n_points_per_dim = Self::checked_n_points_per_dim(&n_cells_per_dim)
.ok_or(GridConstructionError::IndexTypeTooSmallPointsPerDim)?;
let aabb = Self::checked_aabb(min, &n_cells_per_dim, cell_size)
.ok_or(GridConstructionError::RealTypeTooSmallDomainSize)?;
let _ = Self::checked_num_points(&n_points_per_dim).ok_or(
GridConstructionError::IndexTypeTooSmallTotalPoints(
n_points_per_dim[0],
n_points_per_dim[1],
n_points_per_dim[2],
),
)?;
Ok(Self {
aabb,
cell_size,
n_points_per_dim,
n_cells_per_dim,
})
}
pub(crate) fn new_zero() -> Self {
Self {
aabb: Aabb3d::new(Vector3::zeros(), Vector3::zeros()),
cell_size: R::zero(),
n_points_per_dim: [I::zero(); 3],
n_cells_per_dim: [I::zero(); 3],
}
}
#[inline(always)]
pub fn aabb(&self) -> &Aabb3d<R> {
&self.aabb
}
#[inline(always)]
pub fn cell_size(&self) -> R {
self.cell_size
}
#[inline(always)]
pub fn points_per_dim(&self) -> &[I; 3] {
&self.n_points_per_dim
}
#[inline(always)]
pub fn cells_per_dim(&self) -> &[I; 3] {
&self.n_cells_per_dim
}
#[inline(always)]
pub fn get_point(&self, ijk: [I; 3]) -> Option<PointIndex<I>> {
if self.point_exists(&ijk) {
Some(PointIndex::from_ijk(ijk))
} else {
None
}
}
#[inline(always)]
pub fn get_cell(&self, ijk: [I; 3]) -> Option<CellIndex<I>> {
if self.cell_exists(&ijk) {
Some(CellIndex::from_ijk(ijk))
} else {
None
}
}
pub fn get_edge(&self, origin_ijk: [I; 3], axis: Axis) -> Option<EdgeIndex<I>> {
let mut target_ijk = origin_ijk.clone();
target_ijk[axis.dim()] += I::one();
if self.point_exists(&origin_ijk) && self.point_exists(&target_ijk) {
Some(EdgeIndex {
origin: PointIndex::from_ijk(origin_ijk),
axis,
})
} else {
None
}
}
#[inline(always)]
pub fn point_exists(&self, point_ijk: &[I; 3]) -> bool {
(point_ijk[0] < self.n_points_per_dim[0]
&& point_ijk[1] < self.n_points_per_dim[1]
&& point_ijk[2] < self.n_points_per_dim[2])
&& (point_ijk[0] >= I::zero() && point_ijk[1] >= I::zero() && point_ijk[2] >= I::zero())
}
#[inline(always)]
pub fn cell_exists(&self, cell_min_point_ijk: &[I; 3]) -> bool {
(cell_min_point_ijk[0] < self.n_cells_per_dim[0]
&& cell_min_point_ijk[1] < self.n_cells_per_dim[1]
&& cell_min_point_ijk[2] < self.n_cells_per_dim[2])
&& (cell_min_point_ijk[0] >= I::zero()
&& cell_min_point_ijk[1] >= I::zero()
&& cell_min_point_ijk[2] >= I::zero())
}
pub fn is_boundary_cell(&self, cell_index: &CellIndex<I>) -> bool {
(cell_index.index[0] == I::zero()
|| cell_index.index[1] == I::zero()
|| cell_index.index[2] == I::zero())
|| (cell_index.index[0] + I::one() == self.n_cells_per_dim[0]
|| cell_index.index[1] + I::one() == self.n_cells_per_dim[1]
|| cell_index.index[2] + I::one() == self.n_cells_per_dim[2])
}
pub fn is_boundary_edge(&self, edge_index: &EdgeIndex<I>) -> bool {
let origin_index = edge_index.origin().index();
edge_index.axis.orthogonal_axes().iter().any(|orth_ax| {
origin_index[orth_ax.dim()] == I::zero()
|| origin_index[orth_ax.dim()] + I::one() == self.n_points_per_dim[orth_ax.dim()]
})
}
#[inline(always)]
pub fn flatten_point_indices(&self, i: I, j: I, k: I) -> I {
let np = &self.n_points_per_dim;
i * np[1] * np[2] + j * np[2] + k
}
#[inline(always)]
pub fn flatten_point_index_array(&self, ijk: &[I; 3]) -> I {
self.flatten_point_indices(ijk[0], ijk[1], ijk[2])
}
#[inline(always)]
pub fn flatten_point_index(&self, point: &PointIndex<I>) -> I {
self.flatten_point_index_array(point.index())
}
#[inline(always)]
pub fn flatten_cell_indices(&self, i: I, j: I, k: I) -> I {
let nc = &self.n_cells_per_dim;
i * nc[1] * nc[2] + j * nc[2] + k
}
#[inline(always)]
pub fn flatten_cell_index_array(&self, ijk: &[I; 3]) -> I {
self.flatten_cell_indices(ijk[0], ijk[1], ijk[2])
}
#[inline(always)]
pub fn flatten_cell_index(&self, cell: &CellIndex<I>) -> I {
self.flatten_cell_index_array(cell.index())
}
#[inline(always)]
fn unflatten_point_index(&self, point_index: I) -> [I; 3] {
let np = &self.n_points_per_dim;
let i = point_index / (np[1] * np[2]);
let j = (point_index - i * np[1] * np[2]) / np[2];
let k = point_index - i * np[1] * np[2] - j * np[2];
[i, j, k]
}
#[inline(always)]
pub fn try_unflatten_point_index(&self, point_index: I) -> Option<PointIndex<I>> {
let point_ijk = self.unflatten_point_index(point_index);
self.get_point(point_ijk)
}
#[inline(always)]
fn unflatten_cell_index(&self, cell_index: I) -> [I; 3] {
let nc = &self.n_cells_per_dim;
let i = cell_index / (nc[1] * nc[2]);
let j = (cell_index - i * nc[1] * nc[2]) / nc[2];
let k = cell_index - i * nc[1] * nc[2] - j * nc[2];
[i, j, k]
}
#[inline(always)]
pub fn try_unflatten_cell_index(&self, cell_index: I) -> Option<CellIndex<I>> {
let cell_ijk = self.unflatten_cell_index(cell_index);
self.get_cell(cell_ijk)
}
#[inline(always)]
pub fn point_coordinates_indices(&self, i: I, j: I, k: I) -> Vector3<R> {
self.aabb.min()
+ Vector3::new(
i.to_real_unchecked::<R>() * self.cell_size,
j.to_real_unchecked::<R>() * self.cell_size,
k.to_real_unchecked::<R>() * self.cell_size,
)
}
#[inline(always)]
pub fn point_coordinates_array(&self, ijk: &[I; 3]) -> Vector3<R> {
self.point_coordinates_indices(ijk[0], ijk[1], ijk[2])
}
#[inline(always)]
pub fn point_coordinates(&self, point: &PointIndex<I>) -> Vector3<R> {
self.point_coordinates_array(point.index())
}
#[inline(always)]
pub fn enclosing_cell(&self, coord: &Vector3<R>) -> [I; 3] {
let normalized_coord = (coord - self.aabb.min()) / self.cell_size;
[
normalized_coord[0].floor().to_index_unchecked(),
normalized_coord[1].floor().to_index_unchecked(),
normalized_coord[2].floor().to_index_unchecked(),
]
}
#[inline(always)]
pub fn cell_aabb(&self, cell: &CellIndex<I>) -> Aabb3d<R> {
let min_point_ijk = cell.index;
let max_point_ijk = [
min_point_ijk[0] + I::one(),
min_point_ijk[1] + I::one(),
min_point_ijk[2] + I::one(),
];
let min_point = self.point_coordinates_array(&min_point_ijk);
let max_point = self.point_coordinates_array(&max_point_ijk);
Aabb3d::new(min_point, max_point)
}
#[inline(always)]
pub fn get_point_neighbor(
&self,
point: &PointIndex<I>,
direction: DirectedAxis,
) -> Option<PointIndex<I>> {
let point_ijk = point.index();
let DirectedAxis { axis, direction } = &direction;
let dim = axis.dim();
if ((point_ijk[dim] == I::zero()) && direction.is_negative())
|| ((point_ijk[dim] == self.n_points_per_dim[dim] - I::one())
&& direction.is_positive())
{
return None;
}
let mut neighbor_ijk = point_ijk.clone();
neighbor_ijk[dim] = direction.apply_step(neighbor_ijk[dim], I::one());
Some(PointIndex::from_ijk(neighbor_ijk))
}
#[inline(always)]
pub fn get_point_neighbor_unchecked(
&self,
point_ijk: &[I; 3],
direction: DirectedAxis,
) -> [I; 3] {
let DirectedAxis { axis, direction } = direction;
let dim = axis.dim();
let mut neighbor_ijk = point_ijk.clone();
neighbor_ijk[dim] = direction.apply_step(neighbor_ijk[dim], I::one());
neighbor_ijk
}
pub fn get_point_neighborhood<'a>(&self, point: &'a PointIndex<I>) -> Neighborhood<'a, I> {
Neighborhood {
origin: point,
neighbors: DirectedAxisArray::new_with(|direction| {
self.get_point_neighbor(point, *direction)
}),
}
}
pub fn cells_adjacent_to_edge<'a, 'b>(
&self,
edge: &NeighborEdge<'a, 'b, I>,
) -> [Option<CellIndex<I>>; 4] {
let (edge_start_point, _) = edge.ascending_point_order();
let orthogonal_axes = edge.connectivity.axis.orthogonal_axes();
let step_dir1 = orthogonal_axes[0].with_direction(Direction::Negative);
let step_dir3 = orthogonal_axes[1].with_direction(Direction::Negative);
let p0 = Some(edge_start_point.clone());
let p1 = self.get_point_neighbor(edge_start_point, step_dir1);
let p3 = self.get_point_neighbor(edge_start_point, step_dir3);
let p2 = match (&p1, &p3) {
(Some(p1), Some(_)) => Some(PointIndex::from_ijk(
self.get_point_neighbor_unchecked(p1.index(), step_dir3),
)),
_ => None,
};
[
p0.filter(|p| self.cell_exists(p.index()))
.map(CellIndex::from_point),
p1.filter(|p| self.cell_exists(p.index()))
.map(CellIndex::from_point),
p2.filter(|p| self.cell_exists(p.index()))
.map(CellIndex::from_point),
p3.filter(|p| self.cell_exists(p.index()))
.map(CellIndex::from_point),
]
}
pub fn cells_adjacent_to_point<'a>(
&self,
neighborhood: &Neighborhood<'a, I>,
) -> [Option<CellIndex<I>>; 8] {
let cells_above = neighborhood
.get_neighbor_edge(Axis::Z.with_direction(Direction::Positive))
.map(|edge| self.cells_adjacent_to_edge(&edge));
let cells_below = neighborhood
.get_neighbor_edge(Axis::Z.with_direction(Direction::Negative))
.map(|edge| self.cells_adjacent_to_edge(&edge));
match (cells_above, cells_below) {
(Some(cells_above), Some(cells_below)) => [
cells_above[0],
cells_above[1],
cells_above[2],
cells_above[3],
cells_below[0],
cells_below[1],
cells_below[2],
cells_below[3],
],
(Some(cells_above), None) => [
cells_above[0],
cells_above[1],
cells_above[2],
cells_above[3],
None,
None,
None,
None,
],
(None, Some(cells_below)) => [
None,
None,
None,
None,
cells_below[0],
cells_below[1],
cells_below[2],
cells_below[3],
],
(None, None) => [None, None, None, None, None, None, None, None],
}
}
pub fn cells_adjacent_to_cell<'a>(
&'a self,
cell: &'a CellIndex<I>,
) -> impl Iterator<Item = CellIndex<I>> + 'a {
let index = cell.index();
static STEPS: [Option<Direction>; 3] =
[Some(Direction::Negative), None, Some(Direction::Positive)];
iproduct!(STEPS.iter(), STEPS.iter(), STEPS.iter()).filter_map(
move |(step_x, step_y, step_z)| {
if step_x.is_none() && step_y.is_none() && step_z.is_none() {
return None;
}
let neighbor_cell_ijk = [
step_x
.map(|d| d.checked_apply_step(index[0], I::one()))
.unwrap_or(Some(index[0]))?,
step_y
.map(|d| d.checked_apply_step(index[1], I::one()))
.unwrap_or(Some(index[1]))?,
step_z
.map(|d| d.checked_apply_step(index[2], I::one()))
.unwrap_or(Some(index[2]))?,
];
self.get_cell(neighbor_cell_ijk)
},
)
}
fn checked_n_cells_per_dim(n_cells_real: &Vector3<R>) -> Option<[I; 3]> {
Some([
I::one().max(n_cells_real[0].ceil().to_index()?),
I::one().max(n_cells_real[1].ceil().to_index()?),
I::one().max(n_cells_real[2].ceil().to_index()?),
])
}
fn checked_n_points_per_dim(n_cells_per_dim: &[I; 3]) -> Option<[I; 3]> {
Some([
n_cells_per_dim[0].checked_add(&I::one())?,
n_cells_per_dim[1].checked_add(&I::one())?,
n_cells_per_dim[2].checked_add(&I::one())?,
])
}
fn checked_aabb(min: &Vector3<R>, n_cells_per_dim: &[I; 3], cell_size: R) -> Option<Aabb3d<R>> {
let max = min
+ Vector3::new(
cell_size * n_cells_per_dim[0].to_real()?,
cell_size * n_cells_per_dim[1].to_real()?,
cell_size * n_cells_per_dim[2].to_real()?,
);
Some(Aabb3d::new(min.clone(), max))
}
fn checked_num_points(n_points_per_dim: &[I; 3]) -> Option<I> {
n_points_per_dim[0]
.checked_mul(&n_points_per_dim[1])?
.checked_mul(&n_points_per_dim[2])
}
pub(crate) fn log_grid_info(&self) {
trace!(
"Using a grid with {:?}x{:?}x{:?} points and {:?}x{:?}x{:?} cells of edge length {}.",
self.points_per_dim()[0],
self.points_per_dim()[1],
self.points_per_dim()[2],
self.cells_per_dim()[0],
self.cells_per_dim()[1],
self.cells_per_dim()[2],
self.cell_size()
);
trace!("The resulting domain size is: {:?}", self.aabb());
}
}
impl<I: Index, R: Real> OwningSubdomainGrid<I, R> {
pub fn new(
global_grid: UniformGrid<I, R>,
subdomain_grid: UniformGrid<I, R>,
subdomain_offset: [I; 3],
) -> Self {
OwningSubdomainGrid {
global_grid,
subdomain_grid,
subdomain_offset,
}
}
}
impl<I: Index, R: Real> Subdomain<I, R> for OwningSubdomainGrid<I, R> {
#[inline(always)]
fn global_grid(&self) -> &UniformGrid<I, R> {
&self.global_grid
}
#[inline(always)]
fn subdomain_grid(&self) -> &UniformGrid<I, R> {
&self.subdomain_grid
}
#[inline(always)]
fn subdomain_offset(&self) -> &[I; 3] {
&self.subdomain_offset
}
}
impl<'a, I: Index, R: Real> DummySubdomain<'a, I, R> {
pub fn new(global_grid: &'a UniformGrid<I, R>) -> Self {
Self {
global_grid,
subdomain_offset: [I::zero(), I::zero(), I::zero()],
}
}
}
impl<'a, I: Index, R: Real> Subdomain<I, R> for DummySubdomain<'a, I, R> {
#[inline(always)]
fn global_grid(&self) -> &UniformGrid<I, R> {
&self.global_grid
}
#[inline(always)]
fn subdomain_grid(&self) -> &UniformGrid<I, R> {
&self.global_grid
}
#[inline(always)]
fn subdomain_offset(&self) -> &[I; 3] {
&self.subdomain_offset
}
#[inline(always)]
fn min_point(&self) -> PointIndex<I> {
PointIndex::from_ijk(self.subdomain_offset)
}
#[inline(always)]
fn max_point(&self) -> PointIndex<I> {
PointIndex::from_ijk(self.global_grid.n_cells_per_dim)
}
#[inline(always)]
fn map_point(&self, global_point: &PointIndex<I>) -> Option<PointIndex<I>> {
Some(global_point.clone())
}
#[inline(always)]
fn inv_map_point(&self, subdomain_point: &PointIndex<I>) -> Option<PointIndex<I>> {
Some(subdomain_point.clone())
}
#[inline(always)]
fn map_cell(&self, global_cell: &CellIndex<I>) -> Option<CellIndex<I>> {
Some(global_cell.clone())
}
#[inline(always)]
fn inv_map_cell(&self, subdomain_cell: &CellIndex<I>) -> Option<CellIndex<I>> {
Some(subdomain_cell.clone())
}
}
impl<I: Index> PointIndex<I> {
#[inline(always)]
fn from_ijk(point_ijk: [I; 3]) -> Self {
Self { index: point_ijk }
}
#[inline(always)]
pub fn index(&self) -> &[I; 3] {
&self.index
}
}
impl<I: Index> CellIndex<I> {
#[inline(always)]
fn from_ijk(cell_ijk: [I; 3]) -> Self {
Self { index: cell_ijk }
}
#[inline(always)]
fn from_point(point_index: PointIndex<I>) -> Self {
Self {
index: point_index.index,
}
}
#[inline(always)]
pub fn index(&self) -> &[I; 3] {
&self.index
}
#[inline(always)]
pub fn local_edges_parallel_to(axis: Axis) -> &'static [usize; 4] {
&CELL_LOCAL_EDGES_BY_AXIS[axis.dim()]
}
#[inline(always)]
pub fn local_point_index_of(&self, ijk: &[I; 3]) -> Option<usize> {
let delta = [
ijk[0].checked_sub(&self.index[0])?.to_usize()?,
ijk[1].checked_sub(&self.index[1])?.to_usize()?,
ijk[2].checked_sub(&self.index[2])?.to_usize()?,
];
let flat_index = delta[2] * 4 + delta[1] * 2 + delta[0];
CELL_LOCAL_POINTS.get(flat_index).copied()
}
#[inline(always)]
pub fn local_edge_index_of<'a, 'b>(&self, edge: &NeighborEdge<'a, 'b, I>) -> Option<usize> {
let (start_point, _) = edge.ascending_point_order();
let start_point_local = self.local_point_index_of(start_point.index())?;
let edge_dim = edge.connectivity.axis.dim();
CELL_LOCAL_EDGES_FROM_LOCAL_POINT[start_point_local][edge_dim]
}
#[inline(always)]
pub fn global_point_index_of(&self, local_index: usize) -> Option<PointIndex<I>> {
let local_coords = CELL_LOCAL_POINT_COORDS.get(local_index)?;
Some(PointIndex::from_ijk([
self.index[0] + I::from_i8(local_coords[0])?,
self.index[1] + I::from_i8(local_coords[1])?,
self.index[2] + I::from_i8(local_coords[2])?,
]))
}
#[inline(always)]
pub fn global_edge_index_of(&self, local_edge_index: usize) -> Option<EdgeIndex<I>> {
let (origin_local_point, axis) = CELL_LOCAL_EDGES.get(local_edge_index).copied()?;
let origin_local_coords = CELL_LOCAL_POINT_COORDS[origin_local_point];
let origin = [
self.index[0] + I::from_i8(origin_local_coords[0])?,
self.index[1] + I::from_i8(origin_local_coords[1])?,
self.index[2] + I::from_i8(origin_local_coords[2])?,
];
Some(EdgeIndex {
origin: PointIndex::from_ijk(origin),
axis,
})
}
}
impl<I: Index> EdgeIndex<I> {
pub fn origin(&self) -> &PointIndex<I> {
&self.origin
}
pub fn target(&self) -> PointIndex<I> {
let new_index = DirectedAxis::new(self.axis, Direction::Positive)
.apply_single_step(self.origin.index())
.expect("Index type overflow");
PointIndex::from_ijk(new_index)
}
pub fn axis(&self) -> Axis {
self.axis
}
}
#[test]
fn test_cube_cell_local_point_index() {
let cube: CellIndex<i32> = CellIndex { index: [1, 1, 1] };
assert_eq!(cube.local_point_index_of(&[1, 1, 1]), Some(0));
assert_eq!(cube.local_point_index_of(&[2, 1, 1]), Some(1));
assert_eq!(cube.local_point_index_of(&[2, 2, 1]), Some(2));
assert_eq!(cube.local_point_index_of(&[1, 2, 1]), Some(3));
assert_eq!(cube.local_point_index_of(&[1, 1, 2]), Some(4));
assert_eq!(cube.local_point_index_of(&[2, 1, 2]), Some(5));
assert_eq!(cube.local_point_index_of(&[2, 2, 2]), Some(6));
assert_eq!(cube.local_point_index_of(&[1, 2, 2]), Some(7));
assert_eq!(cube.local_point_index_of(&[0, 2, 2]), None);
assert_eq!(cube.local_point_index_of(&[1, 2, 3]), None);
}
const CELL_LOCAL_POINTS: [usize; 8] = [0, 1, 3, 2, 4, 5, 7, 6];
const CELL_LOCAL_POINT_COORDS: [[i8; 3]; 8] = [
[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1], ];
#[test]
fn test_cube_local_point_coordinate_consistency() {
for (local_point, coords) in CELL_LOCAL_POINT_COORDS.iter().enumerate() {
let flattened = coords[0] + 2 * coords[1] + 4 * coords[2];
assert_eq!(CELL_LOCAL_POINTS[flattened as usize], local_point);
}
}
#[rustfmt::skip]
const CELL_LOCAL_EDGES_FROM_LOCAL_POINT: [[Option<usize>; 3]; 8] = [
[Some(0), Some(3), Some(8) ], [None , Some(1), Some(9) ], [None , None , Some(10)], [Some(2), None , Some(11)], [Some(4), Some(7), None ], [None , Some(5), None ], [None , None , None ], [Some(6), None , None ], ];
const CELL_LOCAL_EDGES: [(usize, Axis); 12] = [
(0, Axis::X), (1, Axis::Y), (3, Axis::X), (0, Axis::Y), (4, Axis::X), (5, Axis::Y), (7, Axis::X), (4, Axis::Y), (0, Axis::Z), (1, Axis::Z), (2, Axis::Z), (3, Axis::Z), ];
const LOCAL_EDGES_PARALLEL_TO_X_AXIS: [usize; 4] = [0, 2, 6, 4];
const LOCAL_EDGES_PARALLEL_TO_Y_AXIS: [usize; 4] = [3, 1, 5, 7];
const LOCAL_EDGES_PARALLEL_TO_Z_AXIS: [usize; 4] = [8, 9, 10, 11];
const CELL_LOCAL_EDGES_BY_AXIS: [[usize; 4]; 3] = [
LOCAL_EDGES_PARALLEL_TO_X_AXIS,
LOCAL_EDGES_PARALLEL_TO_Y_AXIS,
LOCAL_EDGES_PARALLEL_TO_Z_AXIS,
];
#[test]
fn test_cube_local_edge_consistency() {
for (local_edge, (local_point, axis)) in CELL_LOCAL_EDGES.iter().copied().enumerate() {
assert_eq!(
CELL_LOCAL_EDGES_FROM_LOCAL_POINT[local_point][axis.dim()],
Some(local_edge)
)
}
for (local_point, edges) in CELL_LOCAL_EDGES_FROM_LOCAL_POINT.iter().enumerate() {
for (local_edge, axis) in edges
.iter()
.copied()
.zip(Axis::all_possible().iter().copied())
{
if let Some(local_edge) = local_edge {
assert_eq!(CELL_LOCAL_EDGES[local_edge].0, local_point);
assert_eq!(CELL_LOCAL_EDGES[local_edge].1, axis);
}
}
}
}
#[test]
fn test_cube_local_edge_by_axis_consistency() {
for (i, edges_parallel_to_axis) in CELL_LOCAL_EDGES_BY_AXIS.iter().enumerate() {
for &local_edge in edges_parallel_to_axis {
assert!(CELL_LOCAL_EDGES_FROM_LOCAL_POINT
.iter()
.any(|edge| edge[i] == Some(local_edge)))
}
}
}
impl GridBoundaryFaceFlags {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[rustfmt::skip]
pub fn classify_cell<I: Index, R: Real>(
grid: &UniformGrid<I, R>,
cell_index: &CellIndex<I>,
) -> Self {
Self::classify_ijk(cell_index.index(), grid.cells_per_dim())
}
#[rustfmt::skip]
pub fn classify_point<I: Index, R: Real>(
grid: &UniformGrid<I, R>,
point_index: &PointIndex<I>,
) -> Self {
Self::classify_ijk(point_index.index(), grid.points_per_dim())
}
#[inline(always)]
fn classify_ijk<I: Index>(ijk: &[I; 3], index_count: &[I; 3]) -> Self {
let mut boundary = FaceFlags::empty();
boundary.set(FaceFlags::X_NEG, ijk[0] == I::zero());
boundary.set(FaceFlags::Y_NEG, ijk[1] == I::zero());
boundary.set(FaceFlags::Z_NEG, ijk[2] == I::zero());
boundary.set(FaceFlags::X_POS, ijk[0] + I::one() == index_count[0]);
boundary.set(FaceFlags::Y_POS, ijk[1] + I::one() == index_count[1]);
boundary.set(FaceFlags::Z_POS, ijk[2] + I::one() == index_count[2]);
Self(boundary)
}
pub fn classify_local_edge(&self, local_edge_index: usize) -> Self {
assert!(local_edge_index < 12);
Self(self.0 & CellBoundaryFaceFlags::classify_cell_local_edge(local_edge_index).0)
}
pub fn iter_individual(&self) -> impl Iterator<Item = DirectedAxis> {
let current = self.0;
FACE_FLAGS_TO_DIRECTED_AXIS
.iter()
.copied()
.filter(move |(flags, _)| current.contains(*flags))
.map(|(_, axis)| axis)
}
}
impl CellBoundaryFaceFlags {
#[inline(always)]
pub fn classify_cell_local_edge(local_edge_index: usize) -> Self {
assert!(local_edge_index < 12);
Self(CELL_LOCAL_EDGE_TO_FACE_FLAGS[local_edge_index])
}
}
#[rustfmt::skip]
const FACE_FLAGS_TO_DIRECTED_AXIS: [(FaceFlags, DirectedAxis); 6] = [
(FaceFlags::X_NEG, DirectedAxis::new(Axis::X, Direction::Negative)),
(FaceFlags::Y_NEG, DirectedAxis::new(Axis::Y, Direction::Negative)),
(FaceFlags::Z_NEG, DirectedAxis::new(Axis::Z, Direction::Negative)),
(FaceFlags::X_POS, DirectedAxis::new(Axis::X, Direction::Positive)),
(FaceFlags::Y_POS, DirectedAxis::new(Axis::Y, Direction::Positive)),
(FaceFlags::Z_POS, DirectedAxis::new(Axis::Z, Direction::Positive)),
];
const CELL_LOCAL_EDGE_TO_FACE_FLAGS: [FaceFlags; 12] = [
FaceFlags::from_bits_truncate(FaceFlags::Y_NEG.bits() | FaceFlags::Z_NEG.bits()),
FaceFlags::from_bits_truncate(FaceFlags::X_POS.bits() | FaceFlags::Z_NEG.bits()),
FaceFlags::from_bits_truncate(FaceFlags::Y_POS.bits() | FaceFlags::Z_NEG.bits()),
FaceFlags::from_bits_truncate(FaceFlags::X_NEG.bits() | FaceFlags::Z_NEG.bits()),
FaceFlags::from_bits_truncate(FaceFlags::Y_NEG.bits() | FaceFlags::Z_POS.bits()),
FaceFlags::from_bits_truncate(FaceFlags::X_POS.bits() | FaceFlags::Z_POS.bits()),
FaceFlags::from_bits_truncate(FaceFlags::Y_POS.bits() | FaceFlags::Z_POS.bits()),
FaceFlags::from_bits_truncate(FaceFlags::X_NEG.bits() | FaceFlags::Z_POS.bits()),
FaceFlags::from_bits_truncate(FaceFlags::X_NEG.bits() | FaceFlags::Y_NEG.bits()),
FaceFlags::from_bits_truncate(FaceFlags::X_POS.bits() | FaceFlags::Y_NEG.bits()),
FaceFlags::from_bits_truncate(FaceFlags::X_POS.bits() | FaceFlags::Y_POS.bits()),
FaceFlags::from_bits_truncate(FaceFlags::X_NEG.bits() | FaceFlags::Y_POS.bits()),
];
impl<'a, I: Index> Neighborhood<'a, I> {
#[inline(always)]
pub fn has_neighbor(&self, direction: DirectedAxis) -> bool {
self.neighbors.get(&direction).is_some()
}
#[inline(always)]
pub fn get_neighbor(&self, direction: DirectedAxis) -> Option<&PointIndex<I>> {
self.neighbors.get(&direction).as_ref()
}
#[inline(always)]
pub fn get_neighbor_edge<'b>(
&'b self,
direction: DirectedAxis,
) -> Option<NeighborEdge<'b, 'b, I>> {
self.neighbors
.get(&direction)
.as_ref()
.map(|neighbor| self.new_neighbor_edge(neighbor, direction))
}
pub fn neighbor_edge_iter<'b>(&'b self) -> impl Iterator<Item = NeighborEdge<'b, 'b, I>> {
self.neighbors
.iter()
.filter_map(move |(&connectivity, optional_neighbor)| {
optional_neighbor
.as_ref()
.map(|neighbor| self.new_neighbor_edge(neighbor, connectivity))
})
}
fn new_neighbor_edge<'b>(
&'b self,
neighbor: &'b PointIndex<I>,
connectivity: DirectedAxis,
) -> NeighborEdge<'b, 'b, I> {
NeighborEdge {
neighborhood: self,
neighbor,
connectivity,
}
}
}
impl<'a, 'b, I: Index> NeighborEdge<'a, 'b, I> {
#[inline(always)]
pub fn origin_index(&self) -> &PointIndex<I> {
&self.neighborhood.origin
}
#[inline(always)]
pub fn neighbor_index(&self) -> &PointIndex<I> {
&self.neighbor
}
#[inline(always)]
pub fn connectivity(&self) -> DirectedAxis {
self.connectivity
}
#[inline(always)]
pub fn ascending_point_order(&self) -> (&PointIndex<I>, &PointIndex<I>) {
if self.connectivity.direction.is_positive() {
(self.origin_index(), &self.neighbor_index())
} else {
(&self.neighbor_index(), self.origin_index())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn unit_grid<I: Index, R: Real>() -> UniformGrid<I, R> {
let origin = Vector3::new(R::zero(), R::zero(), R::zero());
let n_cubes_per_dim = [I::one(), I::one(), I::one()];
let cube_size = R::one();
UniformGrid::new(&origin, &n_cubes_per_dim, cube_size).unwrap()
}
#[test]
fn test_basic_uniform_grid_features() {
let grid = unit_grid::<i32, f64>();
assert_eq!(grid.aabb().max(), &Vector3::new(1.0, 1.0, 1.0));
assert_eq!(grid.cell_size(), 1.0);
let points = [
[0, 0, 0],
[1, 0, 0],
[1, 1, 0],
[0, 1, 0],
[0, 0, 1],
[1, 0, 1],
[1, 1, 1],
[0, 1, 1],
];
for point in points.iter() {
assert!(grid.point_exists(point));
}
assert!(grid.cell_exists(&[0, 0, 0]));
let origin = grid.get_point(points[0]);
assert!(origin.is_some());
let origin = origin.unwrap();
assert_eq!(
grid.get_point_neighbor(&origin, Axis::X.with_direction(Direction::Positive))
.unwrap()
.index(),
&[1, 0, 0]
);
assert_eq!(
grid.get_point_neighbor(&origin, Axis::Y.with_direction(Direction::Positive))
.unwrap()
.index(),
&[0, 1, 0]
);
assert_eq!(
grid.get_point_neighbor(&origin, Axis::Z.with_direction(Direction::Positive))
.unwrap()
.index(),
&[0, 0, 1]
);
assert!(grid
.get_point_neighbor(&origin, Axis::X.with_direction(Direction::Negative))
.is_none());
assert!(grid
.get_point_neighbor(&origin, Axis::Y.with_direction(Direction::Negative))
.is_none());
assert!(grid
.get_point_neighbor(&origin, Axis::Z.with_direction(Direction::Negative))
.is_none());
}
}