use crate::coordinate::CoordinateSystem;
use crate::domain::{DataBounds, Domain};
use crate::plot_object::{PlotGeometry, PlotObject};
use crate::resolution::Resolution;
use crate::style::PlotStyle;
enum VolumeSource {
Fn {
f: Box<dyn Fn(f64, f64, f64) -> f64 + Send + Sync>,
resolution: [u32; 3],
},
Voxels {
data: Vec<f32>,
dims: [u32; 3],
},
}
pub struct DensityPlot3D {
source: VolumeSource,
style: PlotStyle,
domain_override: Option<Domain>,
}
impl DensityPlot3D {
pub fn from_fn(
f: impl Fn(f64, f64, f64) -> f64 + Send + Sync + 'static,
resolution: [u32; 3],
) -> Self {
Self {
source: VolumeSource::Fn {
f: Box::new(f),
resolution,
},
style: PlotStyle::default(),
domain_override: None,
}
}
pub fn from_voxels(data: &[f32], dims: [u32; 3]) -> Self {
Self {
source: VolumeSource::Voxels {
data: data.to_vec(),
dims,
},
style: PlotStyle::default(),
domain_override: None,
}
}
pub fn with_style(mut self, style: PlotStyle) -> Self {
self.style = style;
self
}
pub fn with_domain(mut self, domain: Domain) -> Self {
self.domain_override = Some(domain);
self
}
}
impl PlotObject for DensityPlot3D {
fn coordinate_system(&self) -> CoordinateSystem {
CoordinateSystem::Cartesian
}
fn natural_bounds(&self) -> Option<DataBounds> {
None
}
fn generate(&self, domain: &Domain, _resolution: Resolution) -> PlotGeometry {
let x0 = *domain.x.start();
let x1 = *domain.x.end();
let y0 = *domain.y.start();
let y1 = *domain.y.end();
let z0 = *domain.z.start();
let z1 = *domain.z.end();
match &self.source {
VolumeSource::Fn { f, resolution } => {
let [nx, ny, nz] = *resolution;
let nx = nx.max(2) as usize;
let ny = ny.max(2) as usize;
let nz = nz.max(2) as usize;
let dx = (x1 - x0) / (nx - 1) as f64;
let dy = (y1 - y0) / (ny - 1) as f64;
let dz = (z1 - z0) / (nz - 1) as f64;
let mut data = Vec::with_capacity(nx * ny * nz);
for iz in 0..nz {
for iy in 0..ny {
for ix in 0..nx {
let x = x0 + ix as f64 * dx;
let y = y0 + iy as f64 * dy;
let z = z0 + iz as f64 * dz;
data.push(f(x, y, z) as f32);
}
}
}
PlotGeometry::Volume {
data,
dims: [nx as u32, ny as u32, nz as u32],
origin: [x0 as f32, y0 as f32, z0 as f32],
spacing: [dx as f32, dy as f32, dz as f32],
}
}
VolumeSource::Voxels { data, dims } => {
let [nx, ny, nz] = *dims;
let nx = nx.max(1) as usize;
let ny = ny.max(1) as usize;
let nz = nz.max(1) as usize;
let dx = if nx > 1 {
(x1 - x0) / (nx - 1) as f64
} else {
1.0
};
let dy = if ny > 1 {
(y1 - y0) / (ny - 1) as f64
} else {
1.0
};
let dz = if nz > 1 {
(z1 - z0) / (nz - 1) as f64
} else {
1.0
};
PlotGeometry::Volume {
data: data.clone(),
dims: [nx as u32, ny as u32, nz as u32],
origin: [x0 as f32, y0 as f32, z0 as f32],
spacing: [dx as f32, dy as f32, dz as f32],
}
}
}
}
fn style(&self) -> &PlotStyle {
&self.style
}
fn domain_override(&self) -> Option<&Domain> {
self.domain_override.as_ref()
}
}