1use core::cmp::Ordering;
2use core::iter::FusedIterator;
3use core::ops::Range;
4
5use crate::math::{Cube, GridAab, GridCoordinate, GridPoint};
6
7#[derive(Clone, Debug)]
9pub struct GridIter {
10 x_range: Range<GridCoordinate>,
11 y_range: Range<GridCoordinate>,
12 z_range: Range<GridCoordinate>,
13 cube: GridPoint,
14}
15
16impl GridIter {
17 #[inline]
18 pub(in crate::math) fn new(bounds: GridAab) -> Self {
19 Self {
20 x_range: bounds.x_range(),
21 y_range: bounds.y_range(),
22 z_range: bounds.z_range(),
23 cube: if bounds.is_empty() {
24 bounds.upper_bounds()
27 } else {
28 bounds.lower_bounds()
29 },
30 }
31 }
32
33 #[inline]
36 pub fn bounds(&self) -> GridAab {
37 GridAab::from_ranges([
38 self.x_range.clone(),
39 self.y_range.clone(),
40 self.z_range.clone(),
41 ])
42 }
43
44 #[inline]
46 pub fn contains_cube(&self, cube: Cube) -> bool {
47 if !self.bounds().contains_cube(cube) {
48 return false;
49 }
50 match cube.x.cmp(&self.cube.x) {
51 Ordering::Greater => true, Ordering::Less => false, Ordering::Equal => {
54 match cube.y.cmp(&self.cube.y) {
55 Ordering::Greater => true, Ordering::Less => false, Ordering::Equal => {
58 cube.z >= self.cube.z
60 }
61 }
62 }
63 }
64 }
65}
66
67impl Iterator for GridIter {
68 type Item = Cube;
69
70 #[inline]
71 fn next(&mut self) -> Option<Self::Item> {
72 if self.cube.x >= self.x_range.end {
73 return None;
74 }
75 let result = self.cube;
76 self.cube.z += 1;
77 if self.cube.z >= self.z_range.end {
78 self.cube.z = self.z_range.start;
79 self.cube.y += 1;
80 if self.cube.y >= self.y_range.end {
81 self.cube.y = self.y_range.start;
82 self.cube.x += 1;
83 }
85 }
86 Some(result.into())
87 }
88
89 #[allow(clippy::missing_inline_in_public_items, reason = "unclear benefit")]
90 fn size_hint(&self) -> (usize, Option<usize>) {
91 match usize::try_from((self.x_range.end - self.cube.x) - 1) {
92 Err(_) => {
93 (0, Some(0))
95 }
96 Ok(planes_remaining) => {
97 let rows_remaining = planes_remaining * self.y_range.len()
98 + usize::try_from((self.y_range.end - self.cube.y) - 1).unwrap_or(0);
99 let cubes_remaining = rows_remaining * self.z_range.len()
100 + usize::try_from(self.z_range.end - self.cube.z).unwrap();
101
102 (cubes_remaining, Some(cubes_remaining))
103 }
104 }
105 }
106
107 #[allow(clippy::missing_inline_in_public_items, reason = "is generic already")]
109 fn fold<B, F>(mut self, init: B, mut f: F) -> B
110 where
111 F: FnMut(B, Self::Item) -> B,
112 {
113 let mut state = init;
114
115 #[cold]
118 #[inline(never)]
119 fn cold_next(i: &mut GridIter) -> Option<Cube> {
120 i.next()
121 }
122 while self.cube.y != self.y_range.start || self.cube.z != self.z_range.start {
123 let Some(cube) = cold_next(&mut self) else {
124 return state;
125 };
126 state = f(state, cube);
127 }
128
129 for x in self.cube.x..self.x_range.end {
132 for y in self.y_range.clone() {
133 for z in self.z_range.clone() {
134 state = f(state, Cube::new(x, y, z));
135 }
136 }
137 }
138
139 state
140 }
141}
142
143impl ExactSizeIterator for GridIter {}
144impl FusedIterator for GridIter {}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use alloc::vec::Vec;
150
151 #[test]
152 fn zero_items() {
153 fn assert_no_items(b: GridAab) {
154 assert_eq!(b.interior_iter().collect::<Vec<_>>(), vec![], "{b:?}");
155 }
156
157 assert_no_items(GridAab::from_lower_size([0, 0, 0], [0, 0, 0]));
158 assert_no_items(GridAab::from_lower_size([0, 0, 0], [0, 0, 1]));
159 assert_no_items(GridAab::from_lower_size([0, 0, 0], [0, 1, 0]));
160 assert_no_items(GridAab::from_lower_size([0, 0, 0], [0, 1, 1]));
161 assert_no_items(GridAab::from_lower_size([0, 0, 0], [1, 0, 0]));
162 assert_no_items(GridAab::from_lower_size([0, 0, 0], [1, 0, 1]));
163 assert_no_items(GridAab::from_lower_size([0, 0, 0], [1, 1, 0]));
164 }
165
166 #[test]
167 fn grid_iter_size_hint() {
168 let b = GridAab::from_lower_size([0, 0, 0], [12, 34, 56]);
169 let expected_size = b.volume().unwrap();
170 let mut iter = b.interior_iter();
171
172 assert_eq!(iter.size_hint(), (expected_size, Some(expected_size)));
174
175 for remaining in (1..=expected_size).rev() {
176 assert_eq!(iter.size_hint(), (remaining, Some(remaining)));
177 assert!(iter.next().is_some());
178 }
179
180 assert_eq!(iter.size_hint(), (0, Some(0)));
182 assert!(iter.next().is_none());
183 assert_eq!(iter.size_hint(), (0, Some(0)));
184 }
185
186 #[test]
187 fn next_and_fold_are_equivalent() {
188 let b = GridAab::from_lower_size([0, -1, 7], [3, 3, 3]);
189 println!("Aab = {b:?}");
190
191 for start_point in 0..=b.volume().unwrap() {
192 println!("\nSkipping {start_point}:");
193 let mut iter_to_next = b.interior_iter().skip(start_point);
194 let iter_to_fold = b.interior_iter().skip(start_point);
195 iter_to_fold.fold((), |(), fold_cube| {
196 let next_cube = iter_to_next.next();
197 println!("fold={fold_cube:?} next={next_cube:?}");
198 assert_eq!(fold_cube, next_cube.unwrap());
199 });
200 assert_eq!(iter_to_next.next(), None, "finish");
201 }
202 }
203
204 #[test]
205 fn contains_cube() {
206 let b = GridAab::from_lower_size([0, 0, 0], [3, 3, 3]);
207 let expected_sequence: Vec<Cube> = b.interior_iter().collect();
208
209 let mut iter = b.interior_iter();
210 for current in 0..expected_sequence.len() {
211 for &cube in &expected_sequence[..current] {
212 assert!(
213 !iter.contains_cube(cube),
214 "{cube:?} should be absent at {current}"
215 );
216 }
217 for &cube in &expected_sequence[current..] {
218 assert!(
219 iter.contains_cube(cube),
220 "{cube:?} should be present at {current}"
221 );
222 }
223
224 let item = iter.next();
225
226 assert_eq!(item, Some(expected_sequence[current])); }
228 }
229}