volren_core/transfer_function/
opacity.rs1#[derive(Debug, Clone)]
11pub struct OpacityTransferFunction {
12 points: Vec<(f64, f64)>,
13}
14
15impl OpacityTransferFunction {
16 #[must_use]
18 pub fn new() -> Self {
19 Self { points: Vec::new() }
20 }
21
22 #[must_use]
24 pub fn linear_ramp(scalar_min: f64, scalar_max: f64) -> Self {
25 let mut otf = Self::new();
26 otf.add_point(scalar_min, 0.0);
27 otf.add_point(scalar_max, 1.0);
28 otf
29 }
30
31 pub fn add_point(&mut self, scalar: f64, opacity: f64) {
35 let opacity = opacity.clamp(0.0, 1.0);
36 match self
37 .points
38 .binary_search_by(|(s, _)| s.partial_cmp(&scalar).unwrap())
39 {
40 Ok(pos) => self.points[pos] = (scalar, opacity),
41 Err(pos) => self.points.insert(pos, (scalar, opacity)),
42 }
43 }
44
45 pub fn remove_point(&mut self, scalar: f64, epsilon: f64) {
47 if let Some(pos) = self
48 .points
49 .iter()
50 .position(|(s, _)| (s - scalar).abs() < epsilon)
51 {
52 self.points.remove(pos);
53 }
54 }
55
56 #[must_use]
58 pub fn evaluate(&self, scalar: f64) -> f64 {
59 if self.points.is_empty() {
60 return 0.0;
61 }
62 if scalar <= self.points.first().unwrap().0 {
63 return self.points.first().unwrap().1;
64 }
65 if scalar >= self.points.last().unwrap().0 {
66 return self.points.last().unwrap().1;
67 }
68 let pos = self
69 .points
70 .partition_point(|(s, _)| *s <= scalar)
71 .saturating_sub(1);
72 let (s0, a0) = self.points[pos];
73 let (s1, a1) = self.points[pos + 1];
74 let t = (scalar - s0) / (s1 - s0);
75 a0 + (a1 - a0) * t
76 }
77
78 #[must_use]
80 pub fn len(&self) -> usize {
81 self.points.len()
82 }
83
84 #[must_use]
86 pub fn is_empty(&self) -> bool {
87 self.points.is_empty()
88 }
89}
90
91impl Default for OpacityTransferFunction {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97#[cfg(test)]
100mod tests {
101 use super::*;
102 use approx::assert_abs_diff_eq;
103
104 #[test]
105 fn linear_ramp_midpoint() {
106 let otf = OpacityTransferFunction::linear_ramp(0.0, 1.0);
107 assert_abs_diff_eq!(otf.evaluate(0.5), 0.5, epsilon = 1e-10);
108 }
109
110 #[test]
111 fn clamp_opacity_to_one() {
112 let mut otf = OpacityTransferFunction::new();
113 otf.add_point(0.0, 1.5);
114 assert_abs_diff_eq!(otf.evaluate(0.0), 1.0, epsilon = 1e-10);
115 }
116
117 #[test]
118 fn empty_returns_zero() {
119 assert_abs_diff_eq!(
120 OpacityTransferFunction::new().evaluate(0.5),
121 0.0,
122 epsilon = 1e-10
123 );
124 }
125
126 #[test]
127 fn monotone_ramp_is_monotone() {
128 let otf = OpacityTransferFunction::linear_ramp(-1000.0, 1000.0);
129 let mut prev = otf.evaluate(-1000.0);
130 for i in -999..=1000 {
131 let v = otf.evaluate(i as f64);
132 assert!(v >= prev - 1e-12, "monotonicity violated at {i}");
133 prev = v;
134 }
135 }
136}