mipmap_1d/
mipmap.rs

1use num_traits::{FromPrimitive, Num, ToPrimitive};
2
3/// Creates several downsampled versions of given vector.
4/// This data structure takes 2x space of original data.
5/// Example:
6/// ```rust
7/// use mipmap_1d::MipMap1D;
8///
9/// let data = vec![2, 4, 6, 8, 9];
10/// let mipmap = MipMap1D::new(data);
11/// assert_eq!(mipmap.num_levels(), 4);
12/// assert_eq!(*mipmap.get_level(0).unwrap(), [2, 4, 6, 8, 9]);
13/// assert_eq!(*mipmap.get_level(1).unwrap(), [3, 7, 9]);
14/// assert_eq!(*mipmap.get_level(2).unwrap(), [5, 9]);
15/// assert_eq!(*mipmap.get_level(3).unwrap(), [7]);
16/// assert_eq!(mipmap.get_level(4), None);
17/// ```
18#[derive(Debug, Default, PartialEq, Eq)]
19pub struct MipMap1D<T: Num + ToPrimitive + FromPrimitive> {
20    data: Vec<Vec<T>>,
21}
22
23impl<T: Num + ToPrimitive + FromPrimitive + Copy> MipMap1D<T> {
24    pub fn new(source: Vec<T>) -> Self {
25        let mut data = vec![source.clone()];
26        let mut current = source;
27
28        while current.len() > 1 {
29            let mipmap = Self::downsample(&current);
30            current.clone_from(&mipmap);
31            data.push(mipmap);
32        }
33
34        Self { data }
35    }
36
37    /// Returns the total number of downsampled levels.
38    /// Equal to `ceil(log2(source.len())`
39    pub fn num_levels(&self) -> usize {
40        self.data.len()
41    }
42
43    /// Returns the data on given level.
44    /// Level `0` returns the source data; the higher the level, the higher the compression (i.e. smaller vectors are returned).
45    /// If the level is out of bounds, returns None
46    pub fn get_level(&self, level: usize) -> Option<&Vec<T>> {
47        if level >= self.num_levels() {
48            return None;
49        }
50
51        Some(&self.data[level])
52    }
53
54    /// Downsamples a vector to `ceil(len / 2)`` elements.
55    /// Currently, downsampling is done by averaging the pair of elements
56    fn downsample(source: &[T]) -> Vec<T> {
57        source
58            .chunks(2)
59            .map(|pair| match pair.len() {
60                1 => pair[0],
61                2 => T::from_f64((pair[0] + pair[1]).to_f64().unwrap() / 2.0).unwrap(),
62                _ => panic!("Unsound condition"),
63            })
64            .collect()
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn test_correct_downsample_ints() {
74        let data = vec![2, 4, 6, 8];
75        assert_eq!(MipMap1D::downsample(&data), vec![3, 7]);
76    }
77
78    #[test]
79    fn test_uneven_downsample() {
80        let data = vec![2, 4, 6, 8, 9];
81        assert_eq!(MipMap1D::downsample(&data), vec![3, 7, 9]);
82    }
83
84    #[test]
85    fn test_uneven_mipmap() {
86        let data = vec![2, 4, 6, 8, 9];
87        let target = vec![vec![2, 4, 6, 8, 9], vec![3, 7, 9], vec![5, 9], vec![7]];
88        let mipmap = MipMap1D::new(data);
89        assert_eq!(mipmap.data, target);
90    }
91
92    #[test]
93    fn test_mipmap_levels() {
94        let data = vec![2, 4, 6, 8, 9];
95        let target = [vec![2, 4, 6, 8, 9], vec![3, 7, 9], vec![5, 9], vec![7]];
96        let mipmap = MipMap1D::new(data);
97
98        assert_eq!(mipmap.num_levels(), target.len());
99        for (level, target_item) in target.iter().enumerate() {
100            let res = mipmap.get_level(level);
101            assert!(res.is_some());
102            let res = res.unwrap();
103
104            assert_eq!(*res, *target_item)
105        }
106    }
107
108    #[test]
109    fn test_fails_on_nonexistent_level() {
110        let data = vec![2, 4, 6, 8, 9];
111        let mipmap = MipMap1D::new(data);
112
113        assert_eq!(mipmap.get_level(mipmap.num_levels()), None);
114    }
115}