azalea_world/
iterators.rs

1//! Iterators for iterating over Minecraft blocks and chunks, based on
2//! [prismarine-world's iterators](https://github.com/PrismarineJS/prismarine-world/blob/master/src/iterators.js).
3
4use azalea_core::position::{BlockPos, ChunkPos};
5
6/// An octahedron iterator, useful for iterating over blocks in a world.
7///
8/// ```
9/// # use azalea_core::position::BlockPos;
10/// # use azalea_world::iterators::BlockIterator;
11///
12/// let mut iter = BlockIterator::new(BlockPos::default(), 4);
13/// for block_pos in iter {
14///     println!("{:?}", block_pos);
15/// }
16/// ```
17pub struct BlockIterator {
18    start: BlockPos,
19    max_distance: u32,
20
21    pos: BlockPos,
22    apothem: u32,
23    left: i32,
24    right: i32,
25}
26impl BlockIterator {
27    pub fn new(start: BlockPos, max_distance: u32) -> Self {
28        Self {
29            start,
30            max_distance,
31
32            pos: BlockPos {
33                x: -1,
34                y: -1,
35                z: -1,
36            },
37            apothem: 1,
38            left: 1,
39            right: 2,
40        }
41    }
42}
43
44impl Iterator for BlockIterator {
45    type Item = BlockPos;
46
47    fn next(&mut self) -> Option<Self::Item> {
48        if self.apothem > self.max_distance {
49            return None;
50        }
51
52        self.right -= 1;
53        if self.right < 0 {
54            self.left -= 1;
55            if self.left < 0 {
56                self.pos.z += 2;
57                if self.pos.z > 1 {
58                    self.pos.y += 2;
59                    if self.pos.y > 1 {
60                        self.pos.x += 2;
61                        if self.pos.x > 1 {
62                            self.apothem += 1;
63                            self.pos.x = -1;
64                        }
65                        self.pos.y = -1;
66                    }
67                    self.pos.z = -1;
68                }
69                self.left = self.apothem as i32;
70            }
71            self.right = self.left;
72        }
73        let x = self.pos.x * self.right;
74        let y = self.pos.y * ((self.apothem as i32) - self.left);
75        let z = self.pos.z * ((self.apothem as i32) - (i32::abs(x) + i32::abs(y)));
76        Some(BlockPos { x, y, z } + self.start)
77    }
78}
79
80/// A spiral iterator, useful for iterating over chunks in a world.
81///
82/// You can use [`ChunkIterator`] instead to sort by x+y+z (Manhattan) distance.
83///
84/// ```
85/// # use azalea_core::position::ChunkPos;
86/// # use azalea_world::iterators::SquareChunkIterator;
87///
88/// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 4);
89/// for chunk_pos in iter {
90///     println!("{:?}", chunk_pos);
91/// }
92/// ```
93pub struct SquareChunkIterator {
94    start: ChunkPos,
95    number_of_points: u32,
96
97    dir: ChunkPos,
98
99    segment_len: u32,
100    pos: ChunkPos,
101    segment_passed: u32,
102    current_iter: u32,
103}
104impl SquareChunkIterator {
105    pub fn new(start: ChunkPos, max_distance: u32) -> Self {
106        Self {
107            start,
108            number_of_points: u32::pow(max_distance * 2 - 1, 2),
109
110            dir: ChunkPos { x: 1, z: 0 },
111
112            segment_len: 1,
113            pos: ChunkPos::default(),
114            segment_passed: 0,
115            current_iter: 0,
116        }
117    }
118
119    /// Change the distance that this iterator won't go past.
120    ///
121    /// ```
122    /// # use azalea_core::position::ChunkPos;
123    /// # use azalea_world::iterators::SquareChunkIterator;
124    ///
125    /// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 2);
126    /// while let Some(chunk_pos) = iter.next() {
127    ///     println!("{:?}", chunk_pos);
128    /// }
129    /// iter.set_max_distance(4);
130    /// while let Some(chunk_pos) = iter.next() {
131    ///     println!("{:?}", chunk_pos);
132    /// }
133    /// ```
134    pub fn set_max_distance(&mut self, max_distance: u32) {
135        self.number_of_points = u32::pow(max_distance * 2 - 1, 2);
136    }
137}
138impl Iterator for SquareChunkIterator {
139    type Item = ChunkPos;
140
141    fn next(&mut self) -> Option<Self::Item> {
142        if self.current_iter > self.number_of_points {
143            return None;
144        }
145
146        let output = self.start + self.dir;
147
148        // make a step, add the direction to the current position
149        self.pos.x += self.dir.x;
150        self.pos.z += self.dir.z;
151        self.segment_passed += 1;
152
153        if self.segment_passed == self.segment_len {
154            // done with current segment
155            self.segment_passed = 0;
156
157            // rotate directions
158            (self.dir.x, self.dir.z) = (-self.dir.z, self.dir.x);
159
160            // increase segment length if necessary
161            if self.dir.z == 0 {
162                self.segment_len += 1;
163            }
164        }
165        self.current_iter += 1;
166        Some(output)
167    }
168}
169
170/// A diagonal spiral iterator, useful for iterating over chunks in a world.
171///
172/// ```
173/// # use azalea_core::position::ChunkPos;
174/// # use azalea_world::iterators::ChunkIterator;
175///
176/// let mut iter = ChunkIterator::new(ChunkPos::default(), 4);
177/// for chunk_pos in iter {
178///     println!("{:?}", chunk_pos);
179/// }
180/// ```
181pub struct ChunkIterator {
182    pub max_distance: u32,
183    pub start: ChunkPos,
184    pub pos: ChunkPos,
185    pub layer: u32,
186    pub leg: i32,
187}
188impl ChunkIterator {
189    pub fn new(start: ChunkPos, max_distance: u32) -> Self {
190        Self {
191            max_distance,
192            start,
193            pos: ChunkPos { x: 2, z: -1 },
194            layer: 1,
195            leg: -1,
196        }
197    }
198}
199impl Iterator for ChunkIterator {
200    type Item = ChunkPos;
201
202    fn next(&mut self) -> Option<Self::Item> {
203        match self.leg {
204            -1 => {
205                self.leg = 0;
206                return Some(self.start);
207            }
208            0 => {
209                if self.max_distance == 1 {
210                    return None;
211                }
212                self.pos.x -= 1;
213                self.pos.z += 1;
214                if self.pos.x == 0 {
215                    self.leg = 1;
216                }
217            }
218            1 => {
219                self.pos.x -= 1;
220                self.pos.z -= 1;
221                if self.pos.z == 0 {
222                    self.leg = 2;
223                }
224            }
225            2 => {
226                self.pos.x += 1;
227                self.pos.z -= 1;
228                if self.pos.x == 0 {
229                    self.leg = 3;
230                }
231            }
232            3 => {
233                self.pos.x += 1;
234                self.pos.z += 1;
235                if self.pos.z == 0 {
236                    self.pos.x += 1;
237                    self.leg = 0;
238                    self.layer += 1;
239                    if self.layer == self.max_distance {
240                        return None;
241                    }
242                }
243            }
244            _ => unreachable!(),
245        }
246        Some(self.start + self.pos)
247    }
248}