Skip to main content

rill_core/
interpolate.rs

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