use crate::CoreError;
use molgfx_math::{Aabb, Mat4, Quat, Vec3};
#[cfg(test)]
#[path = "occupancy_tests.rs"]
mod tests;
const fn invalid(reason: &'static str) -> CoreError {
CoreError::InvalidVolume { reason }
}
#[derive(Clone, Debug)]
pub struct OccupancyStream {
dimensions: [u32; 3],
voxel_to_model: Mat4,
decay: f32,
deposit: f32,
maximum: f32,
}
impl OccupancyStream {
pub fn new(
dimensions: [u32; 3],
origin: Vec3,
spacing: Vec3,
decay: f32,
deposit: f32,
maximum: f32,
) -> Result<Self, CoreError> {
if dimensions
.iter()
.any(|&dimension| !(2..=u32::from(u16::MAX)).contains(&dimension))
{
return Err(invalid("occupancy dimensions must be between 2 and 65535"));
}
if !origin.is_finite() || !spacing.is_finite() || spacing.min_element() <= 0.0 {
return Err(invalid(
"occupancy origin and positive spacing must be finite",
));
}
if !decay.is_finite() || !(0.0..=1.0).contains(&decay) {
return Err(invalid("occupancy decay must be finite in [0, 1]"));
}
if !deposit.is_finite()
|| deposit <= 0.0
|| !maximum.is_finite()
|| maximum <= 0.0
|| deposit > maximum
|| maximum > 1_000_000.0
{
return Err(invalid(
"occupancy deposit must not exceed a positive maximum of 1,000,000",
));
}
dimensions
.iter()
.try_fold(1u64, |product, &dimension| {
product.checked_mul(u64::from(dimension))
})
.ok_or_else(|| invalid("occupancy dimension product overflows"))?;
Ok(Self {
dimensions,
voxel_to_model: Mat4::from_scale_rotation_translation(spacing, Quat::IDENTITY, origin),
decay,
deposit,
maximum,
})
}
#[must_use]
pub const fn dimensions(&self) -> [u32; 3] {
self.dimensions
}
#[must_use]
pub const fn voxel_to_model(&self) -> Mat4 {
self.voxel_to_model
}
#[must_use]
pub const fn decay(&self) -> f32 {
self.decay
}
#[must_use]
pub const fn deposit(&self) -> f32 {
self.deposit
}
#[must_use]
pub const fn maximum(&self) -> f32 {
self.maximum
}
#[must_use]
pub fn model_aabb(&self) -> Aabb {
let maximum = Vec3::new(
f32::from(bounded_u16(self.dimensions[0] - 1)),
f32::from(bounded_u16(self.dimensions[1] - 1)),
f32::from(bounded_u16(self.dimensions[2] - 1)),
);
Aabb::new(Vec3::ZERO, maximum).transform(&self.voxel_to_model)
}
}
fn bounded_u16(value: u32) -> u16 {
u16::try_from(value)
.into_iter()
.fold(u16::MAX, |_, converted| converted)
}