1use azalea_block::{BlockState, BlockStates};
2use azalea_core::position::{BlockPos, ChunkPos};
3
4use crate::{Chunk, ChunkStorage, World, iterators::ChunkIterator, palette::Palette};
5
6impl World {
7 pub fn find_block(
19 &self,
20 nearest_to: impl Into<BlockPos>,
21 block_states: &BlockStates,
22 ) -> Option<BlockPos> {
23 let nearest_to: BlockPos = nearest_to.into();
27 let start_chunk: ChunkPos = (&nearest_to).into();
28 let mut iter = ChunkIterator::new(start_chunk, 32);
29
30 let mut nearest_found_pos: Option<BlockPos> = None;
31 let mut nearest_found_distance = 0;
32
33 while let Some(chunk_pos) = iter.next() {
35 let Some(chunk) = self.chunks.get(&chunk_pos) else {
36 continue;
42 };
43
44 find_blocks_in_chunk(
45 block_states,
46 chunk_pos,
47 &chunk.read(),
48 self.chunks.min_y,
49 |this_block_pos| {
50 let this_block_distance = (nearest_to - this_block_pos).length_manhattan();
51 if nearest_found_pos.is_none() || this_block_distance < nearest_found_distance {
53 nearest_found_pos = Some(this_block_pos);
54 nearest_found_distance = this_block_distance;
55 }
56 },
57 );
58
59 if let Some(nearest_found_pos) = nearest_found_pos {
60 let required_chunk_distance = u32::max(
63 u32::max(
64 (chunk_pos.x - start_chunk.x).unsigned_abs(),
65 (chunk_pos.z - start_chunk.z).unsigned_abs(),
66 ),
67 (nearest_to.y - nearest_found_pos.y)
68 .unsigned_abs()
69 .div_ceil(16),
70 ) + 1;
71 let nearest_chunk_distance = iter.layer;
72
73 if nearest_chunk_distance > required_chunk_distance {
76 return Some(nearest_found_pos);
77 }
78 }
79 }
80
81 if nearest_found_pos.is_some() {
82 nearest_found_pos
83 } else {
84 None
85 }
86 }
87
88 pub fn find_blocks<'a>(
96 &'a self,
97 nearest_to: impl Into<BlockPos>,
98 block_states: &'a BlockStates,
99 ) -> FindBlocks<'a> {
100 FindBlocks::new(nearest_to.into(), &self.chunks, block_states)
101 }
102}
103
104pub struct FindBlocks<'a> {
105 nearest_to: BlockPos,
106 start_chunk: ChunkPos,
107 chunk_iterator: ChunkIterator,
108 chunks: &'a ChunkStorage,
109 block_states: &'a BlockStates,
110
111 queued: Vec<BlockPos>,
112}
113
114impl<'a> FindBlocks<'a> {
115 pub fn new(
116 nearest_to: BlockPos,
117 chunks: &'a ChunkStorage,
118 block_states: &'a BlockStates,
119 ) -> Self {
120 let start_chunk: ChunkPos = (&nearest_to).into();
121 Self {
122 nearest_to,
123 start_chunk,
124 chunk_iterator: ChunkIterator::new(start_chunk, 32),
125 chunks,
126 block_states,
127
128 queued: Vec::new(),
129 }
130 }
131}
132
133impl Iterator for FindBlocks<'_> {
134 type Item = BlockPos;
135
136 fn next(&mut self) -> Option<Self::Item> {
137 if let Some(queued) = self.queued.pop() {
138 return Some(queued);
139 }
140
141 let mut found = Vec::new();
142
143 let mut nearest_found_pos: Option<BlockPos> = None;
144 let mut nearest_found_distance = 0;
145
146 while let Some(chunk_pos) = self.chunk_iterator.next() {
147 let Some(chunk) = self.chunks.get(&chunk_pos) else {
148 continue;
154 };
155
156 find_blocks_in_chunk(
157 self.block_states,
158 chunk_pos,
159 &chunk.read(),
160 self.chunks.min_y,
161 |this_block_pos| {
162 let this_block_distance = (self.nearest_to - this_block_pos).length_manhattan();
163
164 found.push((this_block_pos, this_block_distance));
165
166 if nearest_found_pos.is_none() || this_block_distance < nearest_found_distance {
167 nearest_found_pos = Some(this_block_pos);
168 nearest_found_distance = this_block_distance;
169 }
170 },
171 );
172
173 if let Some(nearest_found_pos) = nearest_found_pos {
174 let required_chunk_distance = u32::max(
177 u32::max(
178 (chunk_pos.x - self.start_chunk.x).unsigned_abs(),
179 (chunk_pos.z - self.start_chunk.z).unsigned_abs(),
180 ),
181 (self.nearest_to.y - nearest_found_pos.y)
182 .unsigned_abs()
183 .div_ceil(16),
184 ) + 1;
185 let nearest_chunk_distance = self.chunk_iterator.layer;
186
187 if nearest_chunk_distance > required_chunk_distance {
190 found.sort_unstable_by_key(|(_, distance)| u32::MAX - distance);
192
193 self.queued = found.into_iter().map(|(pos, _)| pos).collect();
194 return self.queued.pop();
195 }
196 }
197 }
198
199 None
200 }
201}
202
203pub fn find_blocks_in_chunk(
209 block_states: &BlockStates,
210 chunk_pos: ChunkPos,
211 chunk: &Chunk,
212 min_y: i32,
213 mut cb: impl FnMut(BlockPos),
214) {
215 for (section_index, section) in chunk.sections.iter().enumerate() {
216 let maybe_has_block = palette_maybe_has_block(§ion.states.palette, block_states);
217 if !maybe_has_block {
218 continue;
219 }
220
221 for i in 0..4096 {
222 let block_state = section.states.get_at_index(i);
223
224 if block_states.contains(&block_state) {
225 let section_pos = section.states.pos_from_index(i);
226 let (x, y, z) = (
227 chunk_pos.x * 16 + (section_pos.x as i32),
228 min_y + (section_index * 16) as i32 + section_pos.y as i32,
229 chunk_pos.z * 16 + (section_pos.z as i32),
230 );
231 let this_block_pos = BlockPos { x, y, z };
232
233 cb(this_block_pos);
234 }
235 }
236 }
237}
238
239fn palette_maybe_has_block(palette: &Palette<BlockState>, block_states: &BlockStates) -> bool {
240 match &palette {
241 Palette::SingleValue(id) => block_states.contains(id),
242 Palette::Linear(ids) => ids.iter().any(|id| block_states.contains(id)),
243 Palette::Hashmap(ids) => ids.iter().any(|id| block_states.contains(id)),
244 Palette::Global => true,
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use azalea_registry::builtin::BlockKind;
251
252 use super::*;
253 use crate::{Chunk, PartialChunkStorage};
254
255 #[test]
256 fn find_block() {
257 let mut world = World::default();
258
259 let chunk_storage = &mut world.chunks;
260 let mut partial_chunk_storage = PartialChunkStorage::default();
261
262 partial_chunk_storage.set(
265 &ChunkPos { x: 0, z: 0 },
266 Some(Chunk::default()),
267 chunk_storage,
268 );
269 partial_chunk_storage.set(
270 &ChunkPos { x: 1, z: 0 },
271 Some(Chunk::default()),
272 chunk_storage,
273 );
274
275 chunk_storage.set_block_state(BlockPos { x: 17, y: 0, z: 0 }, BlockKind::Stone.into());
276 chunk_storage.set_block_state(BlockPos { x: 0, y: 18, z: 0 }, BlockKind::Stone.into());
277
278 let pos = world.find_block(BlockPos { x: 0, y: 0, z: 0 }, &BlockKind::Stone.into());
279 assert_eq!(pos, Some(BlockPos { x: 17, y: 0, z: 0 }));
280 }
281
282 #[test]
283 fn find_block_next_to_chunk_border() {
284 let mut world = World::default();
285
286 let chunk_storage = &mut world.chunks;
287 let mut partial_chunk_storage = PartialChunkStorage::default();
288
289 partial_chunk_storage.set(
292 &ChunkPos { x: -1, z: 0 },
293 Some(Chunk::default()),
294 chunk_storage,
295 );
296 partial_chunk_storage.set(
297 &ChunkPos { x: 0, z: 0 },
298 Some(Chunk::default()),
299 chunk_storage,
300 );
301
302 chunk_storage.set_block_state(BlockPos { x: -1, y: 0, z: 0 }, BlockKind::Stone.into());
303 chunk_storage.set_block_state(BlockPos { x: 15, y: 0, z: 0 }, BlockKind::Stone.into());
304
305 let pos = world.find_block(BlockPos { x: 0, y: 0, z: 0 }, &BlockKind::Stone.into());
306 assert_eq!(pos, Some(BlockPos { x: -1, y: 0, z: 0 }));
307 }
308}