use crate::{CoreError, ScalarFieldSemantics};
use molgfx_math::{Aabb, Mat4, Quat, Vec3};
use std::sync::Arc;
#[cfg(test)]
#[path = "density_tests.rs"]
mod tests;
#[derive(Clone, Debug)]
pub struct ScalarVolume {
dimensions: [u32; 3],
empty_space_dimensions: [u32; 3],
voxel_to_world: Mat4,
values: Arc<[f32]>,
empty_space_bounds: Arc<[f32]>,
range: [f32; 2],
semantics: ScalarFieldSemantics,
}
impl ScalarVolume {
pub const EMPTY_SPACE_BRICK_SIZE: u32 = 8;
pub fn new(
dimensions: [u32; 3],
voxel_to_world: Mat4,
values: Arc<[f32]>,
) -> Result<Self, CoreError> {
if dimensions
.iter()
.any(|&dimension| !(2..=u32::from(u16::MAX)).contains(&dimension))
{
return Err(invalid("dimensions must be between 2 and 65535"));
}
let Some(voxels) = dimensions.iter().try_fold(1u64, |product, &dimension| {
product.checked_mul(u64::from(dimension))
}) else {
return Err(invalid("dimension product overflows"));
};
if usize::try_from(voxels).ok() != Some(values.len()) {
return Err(invalid("value count does not match dimensions"));
}
if !voxel_to_world
.to_cols_array()
.iter()
.all(|component| component.is_finite())
|| !voxel_to_world.determinant().is_finite()
|| voxel_to_world.determinant().abs() <= 1e-8
{
return Err(invalid("voxel transform must be finite and invertible"));
}
let mut range = [f32::INFINITY, f32::NEG_INFINITY];
for &value in values.iter() {
if !value.is_finite() {
return Err(invalid("density values must be finite"));
}
range[0] = range[0].min(value);
range[1] = range[1].max(value);
}
let empty_space_dimensions = dimensions.map(|dimension| {
dimension.saturating_add(Self::EMPTY_SPACE_BRICK_SIZE - 1)
/ Self::EMPTY_SPACE_BRICK_SIZE
});
let empty_space_bounds = Arc::from(build_empty_space_bounds(
dimensions,
empty_space_dimensions,
&values,
));
Ok(Self {
dimensions,
empty_space_dimensions,
voxel_to_world,
values,
empty_space_bounds,
range,
semantics: ScalarFieldSemantics::default(),
})
}
pub fn from_spacing(
dimensions: [u32; 3],
origin: Vec3,
spacing: Vec3,
values: Arc<[f32]>,
) -> Result<Self, CoreError> {
if !origin.is_finite() || !spacing.is_finite() || spacing.min_element() <= 0.0 {
return Err(invalid("origin and positive spacing must be finite"));
}
Self::new(
dimensions,
Mat4::from_scale_rotation_translation(spacing, Quat::default(), origin),
values,
)
}
#[must_use]
pub const fn dimensions(&self) -> [u32; 3] {
self.dimensions
}
#[must_use]
pub const fn voxel_to_world(&self) -> Mat4 {
self.voxel_to_world
}
#[must_use]
pub fn values(&self) -> &[f32] {
&self.values
}
#[must_use]
pub const fn empty_space_dimensions(&self) -> [u32; 3] {
self.empty_space_dimensions
}
#[must_use]
pub fn empty_space_bounds(&self) -> &[f32] {
&self.empty_space_bounds
}
#[must_use]
pub const fn range(&self) -> [f32; 2] {
self.range
}
#[must_use]
pub fn with_semantics(mut self, semantics: ScalarFieldSemantics) -> Self {
self.semantics = semantics;
self
}
#[must_use]
pub const fn semantics(&self) -> &ScalarFieldSemantics {
&self.semantics
}
#[must_use]
pub fn world_aabb(&self) -> Aabb {
let maximum = Vec3::new(
dimension_f32(self.dimensions[0] - 1),
dimension_f32(self.dimensions[1] - 1),
dimension_f32(self.dimensions[2] - 1),
);
Aabb::new(Vec3::ZERO, maximum).transform(&self.voxel_to_world)
}
}
fn dimension_f32(value: u32) -> f32 {
match u16::try_from(value) {
Ok(value) => f32::from(value),
Err(_) => f32::from(u16::MAX),
}
}
fn build_empty_space_bounds(
dimensions: [u32; 3],
macro_dimensions: [u32; 3],
values: &[f32],
) -> Vec<f32> {
let macro_count = macro_dimensions
.into_iter()
.fold(1usize, |count, dimension| {
count.saturating_mul(dimension as usize)
});
let mut bounds = Vec::with_capacity(macro_count.saturating_mul(2));
for macro_z in 0..macro_dimensions[2] {
for macro_y in 0..macro_dimensions[1] {
for macro_x in 0..macro_dimensions[0] {
let lower = [
macro_x * ScalarVolume::EMPTY_SPACE_BRICK_SIZE,
macro_y * ScalarVolume::EMPTY_SPACE_BRICK_SIZE,
macro_z * ScalarVolume::EMPTY_SPACE_BRICK_SIZE,
];
let upper = [
(lower[0] + ScalarVolume::EMPTY_SPACE_BRICK_SIZE + 1).min(dimensions[0]),
(lower[1] + ScalarVolume::EMPTY_SPACE_BRICK_SIZE + 1).min(dimensions[1]),
(lower[2] + ScalarVolume::EMPTY_SPACE_BRICK_SIZE + 1).min(dimensions[2]),
];
let mut minimum = f32::INFINITY;
let mut maximum = f32::NEG_INFINITY;
for z in lower[2]..upper[2] {
for y in lower[1]..upper[1] {
let row = ((z * dimensions[1] + y) * dimensions[0] + lower[0]) as usize;
for x in 0..upper[0].saturating_sub(lower[0]) {
if let Some(&value) = values.get(row + x as usize) {
minimum = minimum.min(value);
maximum = maximum.max(value);
}
}
}
}
bounds.extend([minimum, maximum]);
}
}
}
bounds
}
const fn invalid(reason: &'static str) -> CoreError {
CoreError::InvalidVolume { reason }
}