Skip to main content

rill_core/
interpolate.rs

1use crate::math::Transcendental;
2
3/// Fractional-index reading with interpolation.
4pub trait Interpolate {
5    type Output;
6
7    /// Linear interpolation at fractional index.
8    /// `index` in [0, len-1]; clamps to valid range.
9    fn interpolate_linear(&self, index: f64) -> Self::Output;
10
11    /// Cubic Hermite interpolation at fractional index.
12    /// Requires index in [1, len-2] for 4-point stencil; clamps.
13    fn interpolate_cubic(&self, index: f64) -> Self::Output;
14
15    /// Nearest-neighbor (round to nearest integer index).
16    fn interpolate_nearest(&self, index: f64) -> Self::Output;
17}
18
19impl<T: Transcendental + Copy> Interpolate for [T] {
20    type Output = T;
21
22    fn interpolate_linear(&self, index: f64) -> T {
23        let len = self.len();
24        if len == 0 {
25            return T::ZERO;
26        }
27        let idx = index.clamp(0.0, (len - 1) as f64);
28        let i0 = idx.floor() as usize;
29        let i1 = (i0 + 1).min(len - 1);
30        let frac = T::from_f64(idx.fract());
31        let a = self[i0];
32        let b = self[i1];
33        a + (b - a) * frac
34    }
35
36    fn interpolate_cubic(&self, index: f64) -> T {
37        let len = self.len();
38        if len < 4 {
39            return self.interpolate_linear(index);
40        }
41        let idx = index.clamp(1.0, (len - 3) as f64);
42        let i = idx.floor() as usize;
43        let i0 = i - 1;
44        let i1 = i;
45        let i2 = i + 1;
46        let i3 = i + 2;
47        let frac = T::from_f64(idx.fract());
48
49        let c0 = self[i1];
50        let c1 = (self[i2] - self[i0]) * T::from_f32(0.5);
51        let c2 = self[i0] * T::from_f32(-1.5)
52            + self[i1] * T::from_f32(2.0)
53            + self[i2] * T::from_f32(-0.5);
54        let c3 = self[i0] * T::from_f32(-0.5)
55            + self[i1] * T::from_f32(1.5)
56            + self[i2] * T::from_f32(-1.5)
57            + self[i3] * T::from_f32(0.5);
58
59        let f2 = frac * frac;
60        let f3 = f2 * frac;
61        c0 + c1 * frac + c2 * f2 + c3 * f3
62    }
63
64    fn interpolate_nearest(&self, index: f64) -> T {
65        let len = self.len();
66        if len == 0 {
67            return T::ZERO;
68        }
69        let idx = (index.round() as usize).min(len - 1);
70        self[idx]
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn test_linear_simple() {
80        let buf: [f64; 5] = [0.0, 1.0, 2.0, 3.0, 4.0];
81        assert_eq!(buf.interpolate_linear(0.0), 0.0);
82        assert_eq!(buf.interpolate_linear(4.0), 4.0);
83        assert!((buf.interpolate_linear(0.5) - 0.5).abs() < 1e-10);
84        assert!((buf.interpolate_linear(2.5) - 2.5).abs() < 1e-10);
85    }
86
87    #[test]
88    fn test_linear_clamp() {
89        let buf: [f64; 3] = [10.0, 20.0, 30.0];
90        assert_eq!(buf.interpolate_linear(-1.0), 10.0);
91        assert_eq!(buf.interpolate_linear(100.0), 30.0);
92    }
93
94    #[test]
95    fn test_linear_empty() {
96        let buf: [f64; 0] = [];
97        assert_eq!(buf.interpolate_linear(0.0), 0.0);
98    }
99
100    #[test]
101    fn test_cubic_exact_at_knots() {
102        let buf: [f64; 6] = [0.0, 0.5, 1.0, 0.8, 0.3, 0.0];
103        for i in 1..=3 {
104            let v = buf.interpolate_cubic(i as f64);
105            assert!((v - buf[i]).abs() < 1e-10,
106                "cubic should pass through knot {}: got {}, expected {}", i, v, buf[i]);
107        }
108    }
109
110    #[test]
111    fn test_cubic_interior() {
112        let buf: [f64; 4] = [0.0, 0.3, 0.7, 1.0];
113        for i in 0..=10 {
114            let t = 1.0 + i as f64 / 10.0;
115            let v = buf.interpolate_cubic(t);
116            assert!(v >= -0.1 && v <= 1.1,
117                "cubic range violated at t={}: got {}", t, v);
118        }
119    }
120
121    #[test]
122    fn test_cubic_short_fallback() {
123        let buf: [f64; 2] = [0.0, 1.0];
124        assert_eq!(buf.interpolate_cubic(0.5), buf.interpolate_linear(0.5));
125    }
126
127    #[test]
128    fn test_nearest() {
129        let buf: [f64; 3] = [10.0, 20.0, 30.0];
130        assert_eq!(buf.interpolate_nearest(0.0), 10.0);
131        assert_eq!(buf.interpolate_nearest(0.4), 10.0);
132        assert_eq!(buf.interpolate_nearest(0.6), 20.0);
133        assert_eq!(buf.interpolate_nearest(2.0), 30.0);
134    }
135
136    #[test]
137    fn test_on_vec() {
138        let buf: Vec<f64> = vec![0.0, 1.0, 2.0];
139        assert_eq!(buf.interpolate_linear(1.5), 1.5);
140    }
141
142    #[test]
143    fn test_on_boxed_slice() {
144        let buf: Box<[f64]> = vec![0.0, 1.0, 2.0].into_boxed_slice();
145        assert_eq!(buf.interpolate_linear(1.5), 1.5);
146    }
147}