valence_server/
chunk_view.rs

1use valence_generated::chunk_view::{CHUNK_VIEW_LUT, EXTRA_VIEW_RADIUS, MAX_VIEW_DIST};
2use valence_protocol::ChunkPos;
3
4/// Represents the set of all chunk positions that a client can see, defined by
5/// a center chunk position `pos` and view distance `dist`.
6#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
7pub struct ChunkView {
8    /// The center position of this chunk view.
9    pub pos: ChunkPos,
10    dist: u8,
11}
12
13impl ChunkView {
14    /// Creates a new chunk view. `dist` is clamped to the range
15    /// 0..[`MAX_VIEW_DIST`].
16    pub const fn new(pos: ChunkPos, dist: u8) -> Self {
17        Self {
18            pos,
19            dist: if dist > MAX_VIEW_DIST {
20                MAX_VIEW_DIST
21            } else {
22                dist
23            },
24        }
25    }
26
27    pub const fn with_dist(self, dist: u8) -> Self {
28        Self::new(self.pos, dist)
29    }
30
31    pub const fn dist(self) -> u8 {
32        self.dist
33    }
34
35    pub const fn contains(self, pos: ChunkPos) -> bool {
36        let true_dist = self.dist as u64 + EXTRA_VIEW_RADIUS as u64;
37        self.pos.distance_squared(pos) <= true_dist * true_dist
38    }
39
40    /// Returns an iterator over all the chunk positions in this view. Positions
41    /// are sorted by the distance to [`pos`](Self::pos) in ascending order.
42    pub fn iter(self) -> impl DoubleEndedIterator<Item = ChunkPos> + ExactSizeIterator + Clone {
43        CHUNK_VIEW_LUT[self.dist as usize]
44            .iter()
45            .map(move |&(x, z)| ChunkPos {
46                x: self.pos.x + i32::from(x),
47                z: self.pos.z + i32::from(z),
48            })
49    }
50
51    /// Returns an iterator over all the chunk positions in `self`, excluding
52    /// the positions that overlap with `other`. Positions are sorted by the
53    /// distance to [`pos`](Self::pos) in ascending order.
54    pub fn diff(self, other: Self) -> impl DoubleEndedIterator<Item = ChunkPos> + Clone {
55        self.iter().filter(move |&p| !other.contains(p))
56    }
57
58    /// Returns a `(min, max)` tuple describing the tight axis-aligned bounding
59    /// box for this view. All chunk positions in the view are contained in the
60    /// bounding box.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use valence_server::{ChunkPos, ChunkView};
66    ///
67    /// let view = ChunkView::new(ChunkPos::new(5, -4), 16);
68    /// let (min, max) = view.bounding_box();
69    ///
70    /// for pos in view.iter() {
71    ///     assert!(pos.x >= min.x && pos.x <= max.x && pos.z >= min.z && pos.z <= max.z);
72    /// }
73    /// ```
74    pub fn bounding_box(self) -> (ChunkPos, ChunkPos) {
75        let r = i32::from(self.dist) + EXTRA_VIEW_RADIUS;
76
77        (
78            ChunkPos::new(self.pos.x - r, self.pos.z - r),
79            ChunkPos::new(self.pos.x + r, self.pos.z + r),
80        )
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use std::collections::BTreeSet;
87
88    use super::*;
89
90    #[test]
91    fn chunk_view_contains() {
92        let view = ChunkView::new(ChunkPos::new(0, 0), 32);
93        let positions = view.iter().collect::<BTreeSet<_>>();
94
95        for z in -64..64 {
96            for x in -64..64 {
97                let p = ChunkPos::new(x, z);
98                assert_eq!(view.contains(p), positions.contains(&p), "{p:?}");
99            }
100        }
101    }
102}