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. Use
81/// `ChunkIterator` to sort by x+y+z (Manhattan) distance.
82///
83/// ```
84/// # use azalea_core::position::ChunkPos;
85/// # use azalea_world::iterators::SquareChunkIterator;
86///
87/// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 4);
88/// for chunk_pos in iter {
89///   println!("{:?}", chunk_pos);
90/// }
91/// ```
92pub struct SquareChunkIterator {
93    start: ChunkPos,
94    number_of_points: u32,
95
96    dir: ChunkPos,
97
98    segment_len: u32,
99    pos: ChunkPos,
100    segment_passed: u32,
101    current_iter: u32,
102}
103impl SquareChunkIterator {
104    pub fn new(start: ChunkPos, max_distance: u32) -> Self {
105        Self {
106            start,
107            number_of_points: u32::pow(max_distance * 2 - 1, 2),
108
109            dir: ChunkPos { x: 1, z: 0 },
110
111            segment_len: 1,
112            pos: ChunkPos::default(),
113            segment_passed: 0,
114            current_iter: 0,
115        }
116    }
117
118    /// Change the distance that this iterator won't go past.
119    ///
120    /// ```
121    /// # use azalea_core::position::ChunkPos;
122    /// # use azalea_world::iterators::SquareChunkIterator;
123    ///
124    /// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 2);
125    /// while let Some(chunk_pos) = iter.next() {
126    ///   println!("{:?}", chunk_pos);
127    /// }
128    /// iter.set_max_distance(4);
129    /// while let Some(chunk_pos) = iter.next() {
130    ///   println!("{:?}", chunk_pos);
131    /// }
132    /// ```
133    pub fn set_max_distance(&mut self, max_distance: u32) {
134        self.number_of_points = u32::pow(max_distance * 2 - 1, 2);
135    }
136}
137impl Iterator for SquareChunkIterator {
138    type Item = ChunkPos;
139
140    fn next(&mut self) -> Option<Self::Item> {
141        if self.current_iter > self.number_of_points {
142            return None;
143        }
144
145        let output = self.start + self.dir;
146
147        // make a step, add the direction to the current position
148        self.pos.x += self.dir.x;
149        self.pos.z += self.dir.z;
150        self.segment_passed += 1;
151
152        if self.segment_passed == self.segment_len {
153            // done with current segment
154            self.segment_passed = 0;
155
156            // rotate directions
157            (self.dir.x, self.dir.z) = (-self.dir.z, self.dir.x);
158
159            // increase segment length if necessary
160            if self.dir.z == 0 {
161                self.segment_len += 1;
162            }
163        }
164        self.current_iter += 1;
165        Some(output)
166    }
167}
168
169/// A diagonal spiral iterator, useful for iterating over chunks in a world.
170///
171/// ```
172/// # use azalea_core::position::ChunkPos;
173/// # use azalea_world::iterators::ChunkIterator;
174///
175/// let mut iter = ChunkIterator::new(ChunkPos::default(), 4);
176/// for chunk_pos in iter {
177///   println!("{:?}", chunk_pos);
178/// }
179/// ```
180pub struct ChunkIterator {
181    pub max_distance: u32,
182    pub start: ChunkPos,
183    pub pos: ChunkPos,
184    pub layer: u32,
185    pub leg: i32,
186}
187impl ChunkIterator {
188    pub fn new(start: ChunkPos, max_distance: u32) -> Self {
189        Self {
190            max_distance,
191            start,
192            pos: ChunkPos { x: 2, z: -1 },
193            layer: 1,
194            leg: -1,
195        }
196    }
197}
198impl Iterator for ChunkIterator {
199    type Item = ChunkPos;
200
201    fn next(&mut self) -> Option<Self::Item> {
202        match self.leg {
203            -1 => {
204                self.leg = 0;
205                return Some(self.start);
206            }
207            0 => {
208                if self.max_distance == 1 {
209                    return None;
210                }
211                self.pos.x -= 1;
212                self.pos.z += 1;
213                if self.pos.x == 0 {
214                    self.leg = 1;
215                }
216            }
217            1 => {
218                self.pos.x -= 1;
219                self.pos.z -= 1;
220                if self.pos.z == 0 {
221                    self.leg = 2;
222                }
223            }
224            2 => {
225                self.pos.x += 1;
226                self.pos.z -= 1;
227                if self.pos.x == 0 {
228                    self.leg = 3;
229                }
230            }
231            3 => {
232                self.pos.x += 1;
233                self.pos.z += 1;
234                if self.pos.z == 0 {
235                    self.pos.x += 1;
236                    self.leg = 0;
237                    self.layer += 1;
238                    if self.layer == self.max_distance {
239                        return None;
240                    }
241                }
242            }
243            _ => unreachable!(),
244        }
245        Some(self.start + self.pos)
246    }
247}