#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct VolumeShape {
pub nx: usize,
pub ny: usize,
pub nz: usize,
}
impl VolumeShape {
#[must_use]
pub const fn new(nx: usize, ny: usize, nz: usize) -> Self {
Self { nx, ny, nz }
}
pub fn from_header(header: &crate::Header) -> Result<Self, crate::Error> {
Ok(Self {
nx: usize::try_from(header.nx).map_err(|_| crate::Error::bounds_err())?,
ny: usize::try_from(header.ny).map_err(|_| crate::Error::bounds_err())?,
nz: usize::try_from(header.nz).map_err(|_| crate::Error::bounds_err())?,
})
}
pub fn total_voxels(&self) -> Option<usize> {
self.nx.checked_mul(self.ny)?.checked_mul(self.nz)
}
pub const fn is_empty(&self) -> bool {
self.nx == 0 || self.ny == 0 || self.nz == 0
}
pub fn contains_block(&self, offset: [usize; 3], shape: [usize; 3]) -> bool {
let [ox, oy, oz] = offset;
let [sx, sy, sz] = shape;
ox.checked_add(sx).is_some_and(|x| x <= self.nx)
&& oy.checked_add(sy).is_some_and(|y| y <= self.ny)
&& oz.checked_add(sz).is_some_and(|z| z <= self.nz)
}
pub fn checked_linear_index(&self, offset: [usize; 3]) -> Option<usize> {
let [ox, oy, oz] = offset;
ox.checked_add(
oy.checked_mul(self.nx)?
.checked_add(oz.checked_mul(self.nx)?.checked_mul(self.ny)?)?,
)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct VoxelBlock<T> {
pub offset: [usize; 3],
pub shape: [usize; 3],
pub data: Vec<T>,
}
impl<T> VoxelBlock<T> {
pub fn new(offset: [usize; 3], shape: [usize; 3], data: Vec<T>) -> Result<Self, crate::Error> {
Self::try_new(offset, shape, data)
}
pub fn try_new(
offset: [usize; 3],
shape: [usize; 3],
data: Vec<T>,
) -> Result<Self, crate::Error> {
let expected = shape[0]
.checked_mul(shape[1])
.and_then(|v| v.checked_mul(shape[2]))
.ok_or_else(crate::Error::bounds_err)?;
if data.len() != expected {
return Err(crate::Error::BlockShapeMismatch {
expected,
actual: data.len(),
});
}
Ok(Self {
offset,
shape,
data,
})
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn is_full_volume(&self, volume_shape: &VolumeShape) -> bool {
self.offset == [0, 0, 0]
&& self.shape == [volume_shape.nx, volume_shape.ny, volume_shape.nz]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Header;
#[test]
fn volume_shape_from_header_ok() {
let mut h = Header::new();
h.nx = 64;
h.ny = 128;
h.nz = 256;
let vs = VolumeShape::from_header(&h).unwrap();
assert_eq!(vs.nx, 64);
assert_eq!(vs.ny, 128);
assert_eq!(vs.nz, 256);
}
#[test]
fn volume_shape_from_header_negative_nx() {
let mut h = Header::new();
h.nx = -1;
assert!(VolumeShape::from_header(&h).is_err());
}
#[test]
fn volume_shape_from_header_negative_ny() {
let mut h = Header::new();
h.ny = -100;
assert!(VolumeShape::from_header(&h).is_err());
}
#[test]
fn volume_shape_from_header_negative_nz() {
let mut h = Header::new();
h.nz = -1;
assert!(VolumeShape::from_header(&h).is_err());
}
#[test]
fn volume_shape_empty() {
let vs = VolumeShape::new(0, 0, 0);
assert!(vs.is_empty());
}
#[test]
fn volume_shape_contains_block() {
let vs = VolumeShape::new(10, 10, 10);
assert!(vs.contains_block([0, 0, 0], [5, 5, 5]));
assert!(vs.contains_block([5, 5, 5], [5, 5, 5]));
assert!(!vs.contains_block([0, 0, 0], [11, 1, 1]));
assert!(!vs.contains_block([0, 0, 0], [1, 11, 1]));
assert!(!vs.contains_block([0, 0, 0], [1, 1, 11]));
}
#[test]
fn volume_shape_checked_linear_index() {
let vs = VolumeShape::new(4, 3, 2);
assert_eq!(vs.checked_linear_index([0, 0, 0]), Some(0));
assert_eq!(vs.checked_linear_index([3, 2, 1]), Some(4 * 3 * 2 - 1));
assert_eq!(vs.checked_linear_index([2, 1, 0]), Some(6));
assert_eq!(vs.checked_linear_index([0, 0, 1]), Some(12));
assert_eq!(vs.checked_linear_index([0, 0, usize::MAX]), None);
}
#[test]
fn voxel_block_shape_mismatch() {
let err = VoxelBlock::<f32>::new([0, 0, 0], [2, 2, 2], vec![0.0f32; 5]).unwrap_err();
assert!(matches!(err, crate::Error::BlockShapeMismatch { .. }));
}
#[test]
fn voxel_block_is_full_volume() {
let vs = VolumeShape::new(4, 4, 4);
let block = VoxelBlock::new([0, 0, 0], [4, 4, 4], vec![0.0f32; 64]).unwrap();
assert!(block.is_full_volume(&vs));
let offset_block = VoxelBlock::new([1, 0, 0], [3, 4, 4], vec![0.0f32; 48]).unwrap();
assert!(!offset_block.is_full_volume(&vs));
}
}