Skip to main content

aeon_tk/geometry/
index.rs

1#![allow(clippy::needless_range_loop)]
2
3use std::array;
4
5use super::{Face, Region, Side};
6
7/// Describes an abstract index space. Allows for iteration of indices
8/// in N dimensions, and transformations between cartesian and linear
9/// indices.
10#[derive(Debug, Clone, Copy)]
11pub struct IndexSpace<const N: usize> {
12    size: [usize; N],
13}
14
15impl<const N: usize> IndexSpace<N> {
16    /// Constructs a new index space.
17    pub const fn new(size: [usize; N]) -> Self {
18        Self { size }
19    }
20
21    /// Returns the number of indices in the index space.
22    pub fn index_count(&self) -> usize {
23        let mut result = 1;
24
25        for i in 0..N {
26            result *= self.size[i]
27        }
28
29        result
30    }
31
32    /// Returns the dimensions of the index space along each axis.
33    pub fn size(self) -> [usize; N] {
34        self.size
35    }
36
37    /// Converts a linear index into a cartesian index. This
38    /// will likely be an order of magnitude slower than
39    /// `linear_from_cartesian()` due to several
40    /// modulus operations.
41    pub fn cartesian_from_linear(self, mut linear: usize) -> [usize; N] {
42        debug_assert!(linear < self.size.iter().product());
43
44        let mut result = [0; N];
45
46        for i in 0..N {
47            result[i] = linear % self.size[i];
48            linear /= self.size[i];
49        }
50
51        result
52    }
53
54    /// Converts a cartesian index into a linear index.
55    pub fn linear_from_cartesian(self, cartesian: [usize; N]) -> usize {
56        for axis in 0..N {
57            debug_assert!(cartesian[axis] < self.size[axis]);
58        }
59
60        let mut result = 0;
61        let mut stride = 1;
62
63        for i in 0..N {
64            result += stride * cartesian[i];
65            stride *= self.size[i];
66        }
67
68        result
69    }
70
71    /// Iterates all cartesian indices in the index space.
72    pub const fn iter(self) -> CartesianIter<N> {
73        CartesianIter {
74            size: self.size,
75            cursor: [0; N],
76        }
77    }
78
79    /// Returns an index window corresponding to the entire IndexSpace.
80    pub fn window(self) -> IndexWindow<N> {
81        IndexWindow {
82            origin: [0; N],
83            size: self.size,
84        }
85    }
86
87    /// Returns the window containing all points along a plane in the index space.
88    pub fn plane_window(self, axis: usize, intercept: usize) -> IndexWindow<N> {
89        debug_assert!(intercept < self.size[axis]);
90
91        let mut origin = [0; N];
92        origin[axis] = intercept;
93
94        let mut size = self.size;
95        size[axis] = 1;
96
97        IndexWindow::new(origin, size)
98    }
99
100    /// Returns the window containing all points along a face in index space.
101    pub fn face_window(self, face: Face<N>) -> IndexWindow<N> {
102        let intercept = if face.side {
103            self.size[face.axis] - 1
104        } else {
105            0
106        };
107        self.plane_window(face.axis, intercept)
108    }
109
110    /// The window of all indices that border the given region.
111    pub fn region_adjacent_window(self, region: Region<N>) -> IndexWindow<N> {
112        let origin = array::from_fn(|axis| match region.side(axis) {
113            Side::Left | Side::Middle => 0,
114            Side::Right => self.size[axis] - 1,
115        });
116
117        let size = array::from_fn(|axis| match region.side(axis) {
118            Side::Left | Side::Right => 1,
119            Side::Middle => self.size[axis],
120        });
121
122        IndexWindow { origin, size }
123    }
124}
125
126impl<const N: usize> IntoIterator for IndexSpace<N> {
127    type IntoIter = CartesianIter<N>;
128    type Item = [usize; N];
129
130    fn into_iter(self) -> Self::IntoIter {
131        self.iter()
132    }
133}
134
135/// Represents a subset of an index space, and provides utilities for iterating over this window.
136#[derive(Debug, Clone, Copy)]
137pub struct IndexWindow<const N: usize> {
138    /// Stores the origin (bottom-left corner) of the index window
139    pub origin: [usize; N],
140    /// Stores the size along each axis of the index window.
141    pub size: [usize; N],
142}
143
144impl<const N: usize> IndexWindow<N> {
145    /// Constructs a new index window.
146    pub fn new(origin: [usize; N], size: [usize; N]) -> Self {
147        Self { origin, size }
148    }
149
150    /// Iterates over indices in the index window.
151    pub fn iter(&self) -> CartesianWindowIter<N> {
152        CartesianWindowIter {
153            origin: self.origin,
154            inner: IndexSpace::new(self.size).iter(),
155        }
156    }
157}
158
159impl<const N: usize> IntoIterator for IndexWindow<N> {
160    type IntoIter = CartesianWindowIter<N>;
161    type Item = [usize; N];
162
163    fn into_iter(self) -> Self::IntoIter {
164        self.iter()
165    }
166}
167
168#[derive(Debug, Clone)]
169/// An iterator over the cartesian indices of an `IndexSpace`.
170pub struct CartesianIter<const N: usize> {
171    size: [usize; N],
172    cursor: [usize; N],
173}
174
175impl<const N: usize> Iterator for CartesianIter<N> {
176    type Item = [usize; N];
177
178    fn next(&mut self) -> Option<Self::Item> {
179        // Last index was incremented, iteration is complete
180        if self.cursor[N - 1] == self.size[N - 1] {
181            return None;
182        }
183
184        // Store current cursor value (this is what we will return)
185        let result = self.cursor;
186
187        for i in 0..N {
188            if self.size[i] == 0 {
189                // Short circuit if any of the dimensions are zero.
190                return None;
191            }
192
193            // If we need to increment this axis, we add to the cursor value
194            self.cursor[i] += 1;
195            // If the cursor is equal to size, we wrap.
196            // However, if we have reached the final axis,
197            // this indicates we are at the end of iteration,
198            // and will return None on the next call of next().
199            if self.cursor[i] == self.size[i] && i < N - 1 {
200                self.cursor[i] = 0;
201                // Continue looping over axes
202                continue;
203            }
204
205            break;
206        }
207
208        Some(result)
209    }
210}
211
212#[derive(Debug, Clone)]
213/// An iterator over the cartesian indices of an `IndexSpace`.
214pub struct CartesianWindowIter<const N: usize> {
215    origin: [usize; N],
216    inner: CartesianIter<N>,
217}
218
219impl<const N: usize> Iterator for CartesianWindowIter<N> {
220    type Item = [usize; N];
221
222    fn next(&mut self) -> Option<Self::Item> {
223        let offset = self.inner.next()?;
224        Some(array::from_fn(|i| self.origin[i] + offset[i]))
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn index_iteration() {
234        let space = IndexSpace::new([3, 2]);
235        let mut indices = space.iter();
236
237        assert_eq!(indices.next(), Some([0, 0]));
238        assert_eq!(indices.next(), Some([1, 0]));
239        assert_eq!(indices.next(), Some([2, 0]));
240        assert_eq!(indices.next(), Some([0, 1]));
241        assert_eq!(indices.next(), Some([1, 1]));
242        assert_eq!(indices.next(), Some([2, 1]));
243        assert_eq!(indices.next(), None);
244
245        let space = IndexSpace::new([0, 10]);
246        let mut indices = space.iter();
247
248        assert_eq!(indices.next(), None);
249
250        let space = IndexSpace::new([2, 3, 10]);
251        let mut plane = space.plane_window(2, 5).iter();
252
253        assert_eq!(plane.next(), Some([0, 0, 5]));
254        assert_eq!(plane.next(), Some([1, 0, 5]));
255        assert_eq!(plane.next(), Some([0, 1, 5]));
256        assert_eq!(plane.next(), Some([1, 1, 5]));
257        assert_eq!(plane.next(), Some([0, 2, 5]));
258        assert_eq!(plane.next(), Some([1, 2, 5]));
259        assert_eq!(plane.next(), None);
260
261        let space = IndexSpace::new([2, 3, 4]);
262        for (i, index) in space.iter().enumerate() {
263            assert_eq!(i, space.linear_from_cartesian(index));
264        }
265    }
266
267    #[test]
268    fn index_conversion() {
269        let space = IndexSpace::new([2, 4, 3]);
270
271        assert_eq!(space.linear_from_cartesian([1, 0, 0]), 1);
272        assert_eq!(space.linear_from_cartesian([0, 1, 0]), 2);
273        assert_eq!(space.linear_from_cartesian([0, 0, 2]), 8 * 2);
274        assert_eq!(space.linear_from_cartesian([1, 1, 2]), 8 * 2 + 2 + 1);
275
276        assert_eq!(space.cartesian_from_linear(1), [1, 0, 0]);
277        assert_eq!(space.cartesian_from_linear(2), [0, 1, 0]);
278        assert_eq!(space.cartesian_from_linear(8 * 2), [0, 0, 2]);
279        assert_eq!(space.cartesian_from_linear(8 * 2 + 2 + 1), [1, 1, 2]);
280    }
281}