use crate::render::ColorMap;
#[derive(Debug, Clone)]
pub struct TransferFunction {
pub color_map: ColorMap,
opacity_points: Vec<(f64, f64)>,
}
impl TransferFunction {
pub fn linear(color_map: ColorMap) -> Self {
Self {
color_map,
opacity_points: vec![(0.0, 0.0), (1.0, 1.0)],
}
}
pub fn constant_opacity(color_map: ColorMap, opacity: f64) -> Self {
Self {
color_map,
opacity_points: vec![(0.0, opacity), (1.0, opacity)],
}
}
pub fn gaussian(color_map: ColorMap, center: f64, width: f64, peak_opacity: f64) -> Self {
let mut points = Vec::new();
let n = 64;
for i in 0..=n {
let t = i as f64 / n as f64;
let d = (t - center) / width;
let opacity = peak_opacity * (-0.5 * d * d).exp();
points.push((t, opacity));
}
Self {
color_map,
opacity_points: points,
}
}
pub fn set_opacity_points(&mut self, points: Vec<(f64, f64)>) {
self.opacity_points = points;
}
pub fn sample(&self, t: f64) -> [f32; 4] {
let color = self.color_map.map(t);
let opacity = self.sample_opacity(t);
[color[0], color[1], color[2], opacity as f32]
}
fn sample_opacity(&self, t: f64) -> f64 {
let t = t.clamp(0.0, 1.0);
if self.opacity_points.is_empty() {
return 1.0;
}
if t <= self.opacity_points[0].0 {
return self.opacity_points[0].1;
}
if t >= self.opacity_points.last().unwrap().0 {
return self.opacity_points.last().unwrap().1;
}
for i in 0..self.opacity_points.len() - 1 {
let (t0, o0) = self.opacity_points[i];
let (t1, o1) = self.opacity_points[i + 1];
if t >= t0 && t <= t1 {
let frac = if (t1 - t0).abs() > 1e-10 {
(t - t0) / (t1 - t0)
} else {
0.0
};
return o0 + frac * (o1 - o0);
}
}
1.0
}
pub fn to_lut_rgba(&self) -> Vec<u8> {
let mut lut = Vec::with_capacity(256 * 4);
for i in 0..256 {
let t = i as f64 / 255.0;
let rgba = self.sample(t);
lut.push((rgba[0] * 255.0) as u8);
lut.push((rgba[1] * 255.0) as u8);
lut.push((rgba[2] * 255.0) as u8);
lut.push((rgba[3] * 255.0) as u8);
}
lut
}
}
#[derive(Debug, Clone)]
pub struct VolumeActor {
pub scalars: Vec<f64>,
pub dimensions: [usize; 3],
pub origin: [f64; 3],
pub spacing: [f64; 3],
pub scalar_range: [f64; 2],
pub transfer_function: TransferFunction,
pub num_steps: usize,
pub opacity_scale: f64,
}
impl VolumeActor {
pub fn new(
scalars: Vec<f64>,
dimensions: [usize; 3],
origin: [f64; 3],
spacing: [f64; 3],
transfer_function: TransferFunction,
) -> Self {
let (mut smin, mut smax) = (f64::INFINITY, f64::NEG_INFINITY);
for &v in &scalars {
smin = smin.min(v);
smax = smax.max(v);
}
Self {
scalars,
dimensions,
origin,
spacing,
scalar_range: [smin, smax],
transfer_function,
num_steps: 256,
opacity_scale: 1.0,
}
}
pub fn sample(&self, pos: [f64; 3]) -> Option<f64> {
let [nx, ny, nz] = self.dimensions;
let fi = (pos[0] - self.origin[0]) / self.spacing[0];
let fj = (pos[1] - self.origin[1]) / self.spacing[1];
let fk = (pos[2] - self.origin[2]) / self.spacing[2];
if fi < 0.0 || fj < 0.0 || fk < 0.0 {
return None;
}
let i0 = fi as usize;
let j0 = fj as usize;
let k0 = fk as usize;
if i0 + 1 >= nx || j0 + 1 >= ny || k0 + 1 >= nz {
return None;
}
let u = fi - i0 as f64;
let v = fj - j0 as f64;
let w = fk - k0 as f64;
let idx = |i: usize, j: usize, k: usize| k * nx * ny + j * nx + i;
let c000 = self.scalars[idx(i0, j0, k0)];
let c100 = self.scalars[idx(i0 + 1, j0, k0)];
let c010 = self.scalars[idx(i0, j0 + 1, k0)];
let c110 = self.scalars[idx(i0 + 1, j0 + 1, k0)];
let c001 = self.scalars[idx(i0, j0, k0 + 1)];
let c101 = self.scalars[idx(i0 + 1, j0, k0 + 1)];
let c011 = self.scalars[idx(i0, j0 + 1, k0 + 1)];
let c111 = self.scalars[idx(i0 + 1, j0 + 1, k0 + 1)];
let c00 = c000 * (1.0 - u) + c100 * u;
let c10 = c010 * (1.0 - u) + c110 * u;
let c01 = c001 * (1.0 - u) + c101 * u;
let c11 = c011 * (1.0 - u) + c111 * u;
let c0 = c00 * (1.0 - v) + c10 * v;
let c1 = c01 * (1.0 - v) + c11 * v;
Some(c0 * (1.0 - w) + c1 * w)
}
pub fn cast_ray(&self, origin: [f64; 3], direction: [f64; 3]) -> [f32; 4] {
let [nx, ny, nz] = self.dimensions;
let bound_max = [
self.origin[0] + (nx - 1) as f64 * self.spacing[0],
self.origin[1] + (ny - 1) as f64 * self.spacing[1],
self.origin[2] + (nz - 1) as f64 * self.spacing[2],
];
let (t_enter, t_exit) = match ray_aabb(origin, direction, self.origin, bound_max) {
Some(t) => t,
None => return [0.0, 0.0, 0.0, 0.0],
};
let step_size = (t_exit - t_enter) / self.num_steps as f64;
let srange = self.scalar_range[1] - self.scalar_range[0];
let mut color = [0.0f64; 3];
let mut alpha = 0.0f64;
for step in 0..self.num_steps {
if alpha >= 0.99 {
break;
}
let t = t_enter + (step as f64 + 0.5) * step_size;
let pos = [
origin[0] + t * direction[0],
origin[1] + t * direction[1],
origin[2] + t * direction[2],
];
if let Some(scalar) = self.sample(pos) {
let normalized = if srange.abs() > 1e-20 {
(scalar - self.scalar_range[0]) / srange
} else {
0.5
};
let rgba = self.transfer_function.sample(normalized);
let sample_alpha = rgba[3] as f64 * self.opacity_scale * step_size * 100.0;
let sample_alpha = sample_alpha.clamp(0.0, 1.0);
color[0] += (1.0 - alpha) * sample_alpha * rgba[0] as f64;
color[1] += (1.0 - alpha) * sample_alpha * rgba[1] as f64;
color[2] += (1.0 - alpha) * sample_alpha * rgba[2] as f64;
alpha += (1.0 - alpha) * sample_alpha;
}
}
[color[0] as f32, color[1] as f32, color[2] as f32, alpha as f32]
}
}
fn ray_aabb(
origin: [f64; 3],
dir: [f64; 3],
aabb_min: [f64; 3],
aabb_max: [f64; 3],
) -> Option<(f64, f64)> {
let mut tmin = f64::NEG_INFINITY;
let mut tmax = f64::INFINITY;
for i in 0..3 {
if dir[i].abs() < 1e-15 {
if origin[i] < aabb_min[i] || origin[i] > aabb_max[i] {
return None;
}
} else {
let inv = 1.0 / dir[i];
let mut t1 = (aabb_min[i] - origin[i]) * inv;
let mut t2 = (aabb_max[i] - origin[i]) * inv;
if t1 > t2 {
std::mem::swap(&mut t1, &mut t2);
}
tmin = tmin.max(t1);
tmax = tmax.min(t2);
}
}
if tmin > tmax || tmax < 0.0 {
None
} else {
Some((tmin.max(0.0), tmax))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_volume() -> VolumeActor {
let dims = [4, 4, 4];
let mut scalars = Vec::new();
for k in 0..4 {
for j in 0..4 {
for i in 0..4 {
let x = i as f64 / 3.0 - 0.5;
let y = j as f64 / 3.0 - 0.5;
let z = k as f64 / 3.0 - 0.5;
scalars.push(1.0 - (x * x + y * y + z * z).sqrt());
}
}
}
let tf = TransferFunction::linear(ColorMap::jet());
VolumeActor::new(scalars, dims, [0.0, 0.0, 0.0], [1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0], tf)
}
#[test]
fn transfer_function_linear() {
let tf = TransferFunction::linear(ColorMap::jet());
let rgba = tf.sample(0.5);
assert!(rgba[3] > 0.0); }
#[test]
fn transfer_function_constant() {
let tf = TransferFunction::constant_opacity(ColorMap::jet(), 0.3);
let rgba = tf.sample(0.5);
assert!((rgba[3] - 0.3).abs() < 0.01);
}
#[test]
fn transfer_function_lut() {
let tf = TransferFunction::linear(ColorMap::jet());
let lut = tf.to_lut_rgba();
assert_eq!(lut.len(), 256 * 4);
}
#[test]
fn volume_sample() {
let vol = make_test_volume();
let val = vol.sample([0.5, 0.5, 0.5]);
assert!(val.is_some());
}
#[test]
fn volume_sample_outside() {
let vol = make_test_volume();
assert!(vol.sample([10.0, 10.0, 10.0]).is_none());
}
#[test]
fn volume_ray_cast() {
let vol = make_test_volume();
let rgba = vol.cast_ray([0.5, 0.5, -1.0], [0.0, 0.0, 1.0]);
assert!(rgba[3] > 0.0);
}
#[test]
fn volume_ray_miss() {
let vol = make_test_volume();
let rgba = vol.cast_ray([10.0, 10.0, 0.0], [0.0, 0.0, 1.0]);
assert_eq!(rgba[3], 0.0);
}
#[test]
fn transfer_function_gaussian() {
let tf = TransferFunction::gaussian(ColorMap::jet(), 0.5, 0.1, 0.8);
let at_center = tf.sample(0.5);
let at_edge = tf.sample(0.0);
assert!(at_center[3] > at_edge[3]);
}
}