use crate::math::Aabb;
use crate::transfer_function::{ColorTransferFunction, OpacityTransferFunction};
use crate::window_level::WindowLevel;
use glam::DVec4;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[non_exhaustive]
pub enum BlendMode {
#[default]
Composite,
MaximumIntensity,
MinimumIntensity,
AverageIntensity,
Additive,
Isosurface {
iso_value: f64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Interpolation {
Nearest,
#[default]
Linear,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ShadingParams {
pub ambient: f32,
pub diffuse: f32,
pub specular: f32,
pub specular_power: f32,
}
impl Default for ShadingParams {
fn default() -> Self {
Self {
ambient: 0.1,
diffuse: 0.7,
specular: 0.2,
specular_power: 10.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ClipPlane {
pub equation: DVec4,
}
impl ClipPlane {
#[must_use]
pub fn from_point_and_normal(point: glam::DVec3, normal: glam::DVec3) -> Self {
let n = normal.normalize();
let d = -n.dot(point);
Self {
equation: DVec4::new(n.x, n.y, n.z, d),
}
}
#[must_use]
pub fn signed_distance(&self, pos: glam::DVec3) -> f64 {
self.equation.x * pos.x
+ self.equation.y * pos.y
+ self.equation.z * pos.z
+ self.equation.w
}
}
#[derive(Debug, Clone)]
pub struct VolumeRenderParams {
pub color_tf: ColorTransferFunction,
pub opacity_tf: OpacityTransferFunction,
pub gradient_opacity_tf: Option<OpacityTransferFunction>,
pub window_level: Option<WindowLevel>,
pub blend_mode: BlendMode,
pub interpolation: Interpolation,
pub shading: Option<ShadingParams>,
pub step_size_factor: f32,
pub clip_planes: Vec<ClipPlane>,
pub cropping_bounds: Option<Aabb>,
pub background: [f32; 4],
}
impl Default for VolumeRenderParams {
fn default() -> Self {
Self {
color_tf: ColorTransferFunction::greyscale(0.0, 1.0),
opacity_tf: OpacityTransferFunction::linear_ramp(0.0, 1.0),
gradient_opacity_tf: None,
window_level: None,
blend_mode: BlendMode::default(),
interpolation: Interpolation::default(),
shading: Some(ShadingParams::default()),
step_size_factor: 0.5,
clip_planes: Vec::new(),
cropping_bounds: None,
background: [0.0, 0.0, 0.0, 1.0],
}
}
}
impl VolumeRenderParams {
#[must_use]
pub fn builder() -> VolumeRenderParamsBuilder {
VolumeRenderParamsBuilder::default()
}
}
#[derive(Debug, Default)]
pub struct VolumeRenderParamsBuilder {
params: VolumeRenderParams,
}
impl VolumeRenderParamsBuilder {
#[must_use]
pub fn blend_mode(mut self, blend_mode: BlendMode) -> Self {
self.params.blend_mode = blend_mode;
self
}
#[must_use]
pub fn interpolation(mut self, interpolation: Interpolation) -> Self {
self.params.interpolation = interpolation;
self
}
#[must_use]
pub fn shading(mut self, params: ShadingParams) -> Self {
self.params.shading = Some(params);
self
}
#[must_use]
pub fn no_shading(mut self) -> Self {
self.params.shading = None;
self
}
#[must_use]
pub fn step_size_factor(mut self, step: f32) -> Self {
self.params.step_size_factor = step;
self
}
#[must_use]
pub fn color_tf(mut self, tf: ColorTransferFunction) -> Self {
self.params.color_tf = tf;
self
}
#[must_use]
pub fn opacity_tf(mut self, tf: OpacityTransferFunction) -> Self {
self.params.opacity_tf = tf;
self
}
#[must_use]
pub fn gradient_opacity_tf(mut self, tf: OpacityTransferFunction) -> Self {
self.params.gradient_opacity_tf = Some(tf);
self
}
#[must_use]
pub fn window_level(mut self, wl: WindowLevel) -> Self {
self.params.window_level = Some(wl);
self
}
#[must_use]
pub fn cropping_bounds(mut self, bounds: Aabb) -> Self {
self.params.cropping_bounds = Some(bounds);
self
}
#[must_use]
pub fn clip_plane(mut self, plane: ClipPlane) -> Self {
self.params.clip_planes.push(plane);
self
}
#[must_use]
pub fn background(mut self, rgba: [f32; 4]) -> Self {
self.params.background = rgba;
self
}
#[must_use]
pub fn build(self) -> VolumeRenderParams {
self.params
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use glam::DVec3;
#[test]
fn builder_overrides_defaults() {
let params = VolumeRenderParams::builder()
.blend_mode(BlendMode::MaximumIntensity)
.no_shading()
.step_size_factor(0.25)
.build();
assert_eq!(params.blend_mode, BlendMode::MaximumIntensity);
assert!(params.shading.is_none());
assert_abs_diff_eq!(params.step_size_factor as f64, 0.25, epsilon = 1e-6);
}
#[test]
fn clip_plane_from_point_normal() {
let plane = ClipPlane::from_point_and_normal(DVec3::ZERO, DVec3::Y);
let d = plane.signed_distance(DVec3::new(0.0, 1.0, 0.0));
assert!(d > 0.0, "expected positive distance, got {d}");
let d2 = plane.signed_distance(DVec3::new(0.0, -1.0, 0.0));
assert!(d2 < 0.0);
}
#[test]
fn clip_plane_at_point_is_zero() {
let plane = ClipPlane::from_point_and_normal(DVec3::new(0.0, 5.0, 0.0), DVec3::Y);
let d = plane.signed_distance(DVec3::new(3.0, 5.0, 7.0));
assert_abs_diff_eq!(d, 0.0, epsilon = 1e-10);
}
}