use std::collections::HashMap;
use crate::types::{
AttributeData, AttributeDomain, DecodedDataSet, PointSet, SparseGrid, StructuredVolume,
SurfaceMesh, VolumeGridGeometry, VolumeMesh,
};
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct VolumeGrid {
pub dims: [u32; 3],
pub geometry: VolumeGeometry,
pub point_data: HashMap<String, Vec<f32>>,
pub cell_data: HashMap<String, Vec<f32>>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum VolumeGeometry {
Uniform { origin: [f32; 3], spacing: [f32; 3] },
Rectilinear {
xs: Vec<f32>,
ys: Vec<f32>,
zs: Vec<f32>,
},
}
impl VolumeGrid {
pub fn bounds(&self) -> ([f32; 3], [f32; 3]) {
match &self.geometry {
VolumeGeometry::Uniform { origin, spacing } => {
let max = [
origin[0] + spacing[0] * (self.dims[0].saturating_sub(1)) as f32,
origin[1] + spacing[1] * (self.dims[1].saturating_sub(1)) as f32,
origin[2] + spacing[2] * (self.dims[2].saturating_sub(1)) as f32,
];
(*origin, max)
}
VolumeGeometry::Rectilinear { xs, ys, zs } => {
let min = [
*xs.first().unwrap_or(&0.0),
*ys.first().unwrap_or(&0.0),
*zs.first().unwrap_or(&0.0),
];
let max = [
*xs.last().unwrap_or(&0.0),
*ys.last().unwrap_or(&0.0),
*zs.last().unwrap_or(&0.0),
];
(min, max)
}
}
}
pub fn resample_scalar_uniform(
&self,
name: &str,
) -> Option<(Vec<f32>, [u32; 3], [f32; 3], [f32; 3])> {
let point_scalars = self.point_scalar_values(name)?;
match &self.geometry {
VolumeGeometry::Uniform { origin, spacing } => {
Some((point_scalars, self.dims, *origin, *spacing))
}
VolumeGeometry::Rectilinear { xs, ys, zs } => {
let dims = self.dims;
if dims[0] < 2 || dims[1] < 2 || dims[2] < 2 {
return None;
}
let origin = [
*xs.first().unwrap_or(&0.0),
*ys.first().unwrap_or(&0.0),
*zs.first().unwrap_or(&0.0),
];
let spacing = [
(*xs.last().unwrap_or(&origin[0]) - origin[0]) / (dims[0] - 1) as f32,
(*ys.last().unwrap_or(&origin[1]) - origin[1]) / (dims[1] - 1) as f32,
(*zs.last().unwrap_or(&origin[2]) - origin[2]) / (dims[2] - 1) as f32,
];
let mut out = Vec::with_capacity(point_scalars.len());
for iz in 0..dims[2] {
let z = origin[2] + spacing[2] * iz as f32;
for iy in 0..dims[1] {
let y = origin[1] + spacing[1] * iy as f32;
for ix in 0..dims[0] {
let x = origin[0] + spacing[0] * ix as f32;
out.push(sample_rectilinear_point_field(
dims,
xs,
ys,
zs,
&point_scalars,
[x, y, z],
));
}
}
}
Some((out, dims, origin, spacing))
}
}
}
pub fn scalar_values(&self, name: &str) -> Option<&[f32]> {
if let Some(v) = self.point_data.get(name) {
return Some(v);
}
self.cell_data.get(name).map(|v| v.as_slice())
}
pub fn point_scalar_values(&self, name: &str) -> Option<Vec<f32>> {
if let Some(values) = self.point_data.get(name) {
return Some(values.clone());
}
let cell_values = self.cell_data.get(name)?;
cell_to_point_structured(self.dims, cell_values)
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Dataset {
pub positions: Vec<[f32; 3]>,
pub indices: Vec<u32>,
pub normals: Vec<[f32; 3]>,
pub point_data: HashMap<String, Vec<f32>>,
pub cell_data: HashMap<String, Vec<f32>>,
pub edge_data: HashMap<String, Vec<f32>>,
#[serde(skip)]
pub sparse_volume: Option<Box<SparseGrid>>,
pub volume: Option<VolumeGrid>,
#[serde(skip)]
pub volume_mesh: Option<Box<VolumeMesh>>,
}
impl Dataset {
pub fn scalar_values(&self, name: &str) -> Option<&[f32]> {
if let Some(v) = self.point_data.get(name) {
return Some(v);
}
if let Some(v) = self.cell_data.get(name) {
return Some(v);
}
if let Some(v) = self.edge_data.get(name) {
return Some(v);
}
if let Some(vol) = &self.volume {
if let Some(v) = vol.point_data.get(name) {
return Some(v);
}
if let Some(v) = vol.cell_data.get(name) {
return Some(v);
}
}
None
}
fn to_surface_mesh(&self) -> SurfaceMesh {
let mut attributes = HashMap::new();
for (name, values) in &self.point_data {
attributes.insert(
name.clone(),
AttributeData::scalars(AttributeDomain::Point, values.clone()),
);
}
for (name, values) in &self.cell_data {
attributes.insert(
name.clone(),
AttributeData::scalars(AttributeDomain::Cell, values.clone()),
);
}
for (name, values) in &self.edge_data {
attributes.insert(
name.clone(),
AttributeData::scalars(AttributeDomain::Halfedge, values.clone()),
);
}
SurfaceMesh {
positions: self.positions.clone(),
normals: self.normals.clone(),
indices: self.indices.clone(),
uvs: None,
tangents: None,
attributes,
skin_weights: None,
}
}
pub fn into_io_dataset(self) -> DecodedDataSet {
let has_surface_mesh = !self.positions.is_empty() && !self.indices.is_empty();
let has_point_set = !self.positions.is_empty() && self.indices.is_empty();
let surface_mesh = if has_surface_mesh {
Some(self.to_surface_mesh())
} else {
None
};
let point_set = if has_point_set {
let scalar_attributes = self.point_data.clone();
let scalars = if scalar_attributes.len() == 1 {
scalar_attributes
.values()
.next()
.cloned()
.unwrap_or_default()
} else {
Vec::new()
};
Some(PointSet {
name: String::new(),
positions: self.positions.clone(),
colors: Vec::new(),
scalars,
scalar_attributes,
})
} else {
None
};
let volume = self.volume.map(|volume| StructuredVolume {
name: String::new(),
dims: volume.dims,
geometry: match volume.geometry {
VolumeGeometry::Uniform { origin, spacing } => {
VolumeGridGeometry::Uniform { origin, spacing }
}
VolumeGeometry::Rectilinear { xs, ys, zs } => {
VolumeGridGeometry::Rectilinear { xs, ys, zs }
}
},
point_fields: volume.point_data,
cell_fields: volume.cell_data,
});
DecodedDataSet {
name: String::new(),
surface_mesh,
point_set,
volume,
sparse_grid: self.sparse_volume,
volume_mesh: self.volume_mesh,
}
}
}
fn cell_to_point_structured(dims: [u32; 3], cell_values: &[f32]) -> Option<Vec<f32>> {
let [nx, ny, nz] = dims;
if nx == 0 || ny == 0 || nz == 0 {
return None;
}
let cx = nx.saturating_sub(1) as usize;
let cy = ny.saturating_sub(1) as usize;
let cz = nz.saturating_sub(1) as usize;
let expected = cx.checked_mul(cy)?.checked_mul(cz)?;
if cell_values.len() != expected {
return None;
}
let point_count = (nx as usize)
.checked_mul(ny as usize)?
.checked_mul(nz as usize)?;
let mut sums = vec![0.0f32; point_count];
let mut counts = vec![0u32; point_count];
let point_index =
|ix: usize, iy: usize, iz: usize| -> usize { ix + iy * nx as usize + iz * nx as usize * ny as usize };
let cell_index = |ix: usize, iy: usize, iz: usize| -> usize { ix + iy * cx + iz * cx * cy };
for iz in 0..cz {
for iy in 0..cy {
for ix in 0..cx {
let value = cell_values[cell_index(ix, iy, iz)];
for dz in 0..=1 {
for dy in 0..=1 {
for dx in 0..=1 {
let pi = point_index(ix + dx, iy + dy, iz + dz);
sums[pi] += value;
counts[pi] += 1;
}
}
}
}
}
}
for (sum, count) in sums.iter_mut().zip(counts.iter()) {
if *count > 0 {
*sum /= *count as f32;
}
}
Some(sums)
}
fn sample_rectilinear_point_field(
dims: [u32; 3],
xs: &[f32],
ys: &[f32],
zs: &[f32],
scalars: &[f32],
pos: [f32; 3],
) -> f32 {
let [nx, ny, nz] = dims.map(|d| d as usize);
let locate = |coords: &[f32], value: f32| -> (usize, usize, f32) {
if coords.len() < 2 {
return (0, 0, 0.0);
}
if value <= coords[0] {
return (0, 1, 0.0);
}
if value >= coords[coords.len() - 1] {
let hi = coords.len() - 1;
return (hi - 1, hi, 1.0);
}
let mut hi = 1usize;
while hi < coords.len() && coords[hi] < value {
hi += 1;
}
let lo = hi - 1;
let denom = (coords[hi] - coords[lo]).max(f32::EPSILON);
let t = (value - coords[lo]) / denom;
(lo, hi, t.clamp(0.0, 1.0))
};
let idx = |ix: usize, iy: usize, iz: usize| -> usize { ix + iy * nx + iz * nx * ny };
let (x0, x1, tx) = locate(xs, pos[0]);
let (y0, y1, ty) = locate(ys, pos[1]);
let (z0, z1, tz) = locate(zs, pos[2]);
let c000 = scalars[idx(x0, y0, z0)];
let c100 = scalars[idx(x1, y0, z0)];
let c010 = scalars[idx(x0, y1, z0)];
let c110 = scalars[idx(x1, y1, z0)];
let c001 = scalars[idx(x0, y0, z1)];
let c101 = scalars[idx(x1, y0, z1)];
let c011 = scalars[idx(x0, y1, z1)];
let c111 = scalars[idx(x1, y1, z1)];
let c00 = c000 * (1.0 - tx) + c100 * tx;
let c10 = c010 * (1.0 - tx) + c110 * tx;
let c01 = c001 * (1.0 - tx) + c101 * tx;
let c11 = c011 * (1.0 - tx) + c111 * tx;
let c0 = c00 * (1.0 - ty) + c10 * ty;
let c1 = c01 * (1.0 - ty) + c11 * ty;
let _ = nz;
c0 * (1.0 - tz) + c1 * tz
}