steel_core/chunk/
player_chunk_view.rs1use std::cmp::{max, min};
2
3use steel_utils::ChunkPos;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct PlayerChunkView {
8 pub center: ChunkPos,
10 pub view_distance: u8,
12}
13
14impl PlayerChunkView {
15 #[must_use]
17 pub const fn empty() -> Self {
18 Self {
19 center: ChunkPos::new(0, 0),
20 view_distance: 0,
21 }
22 }
23
24 #[must_use]
26 pub const fn new(center: ChunkPos, view_distance: u8) -> Self {
27 Self {
28 center,
29 view_distance,
30 }
31 }
32
33 fn min_x(&self) -> i32 {
34 self.center.0.x - i32::from(self.view_distance) - 1
35 }
36
37 fn max_x(&self) -> i32 {
38 self.center.0.x + i32::from(self.view_distance) + 1
39 }
40
41 fn min_z(&self) -> i32 {
42 self.center.0.y - i32::from(self.view_distance) - 1
43 }
44
45 fn max_z(&self) -> i32 {
46 self.center.0.y + i32::from(self.view_distance) + 1
47 }
48
49 #[must_use]
51 pub fn contains(&self, pos: ChunkPos) -> bool {
52 Self::is_within_distance(
53 self.center.0.x,
54 self.center.0.y,
55 i32::from(self.view_distance),
56 pos.0.x,
57 pos.0.y,
58 true,
59 )
60 }
61
62 #[must_use]
64 pub fn is_within_distance(
65 center_x: i32,
66 center_z: i32,
67 view_distance: i32,
68 chunk_x: i32,
69 chunk_z: i32,
70 include_neighbors: bool,
71 ) -> bool {
72 let buffer_range = if include_neighbors { 2 } else { 1 };
73 let delta_x = i64::from(max(0, (chunk_x - center_x).abs() - buffer_range));
74 let delta_z = i64::from(max(0, (chunk_z - center_z).abs() - buffer_range));
75 let distance_squared = delta_x * delta_x + delta_z * delta_z;
76 let radius_squared = i64::from(view_distance) * i64::from(view_distance);
77 distance_squared < radius_squared
78 }
79
80 pub fn for_each<F>(&self, mut f: F)
82 where
83 F: FnMut(ChunkPos),
84 {
85 for x in self.min_x()..=self.max_x() {
86 for z in self.min_z()..=self.max_z() {
87 let pos = ChunkPos::new(x, z);
88 if self.contains(pos) {
89 f(pos);
90 }
91 }
92 }
93 }
94
95 fn square_intersects(&self, other: &Self) -> bool {
96 self.min_x() <= other.max_x()
97 && self.max_x() >= other.min_x()
98 && self.min_z() <= other.max_z()
99 && self.max_z() >= other.min_z()
100 }
101
102 pub fn difference<T>(
105 old: &Self,
106 new: &Self,
107 mut on_added: impl FnMut(ChunkPos, &mut T),
108 mut on_removed: impl FnMut(ChunkPos, &mut T),
109 data: &mut T,
110 ) {
111 if old == new {
112 return;
113 }
114
115 if old.square_intersects(new) {
116 let min_x = min(old.min_x(), new.min_x());
117 let min_z = min(old.min_z(), new.min_z());
118 let max_x = max(old.max_x(), new.max_x());
119 let max_z = max(old.max_z(), new.max_z());
120
121 for x in min_x..=max_x {
122 for z in min_z..=max_z {
123 let pos = ChunkPos::new(x, z);
124 let saw = old.contains(pos);
125 let sees = new.contains(pos);
126 if saw != sees {
127 if sees {
128 on_added(pos, data);
129 } else {
130 on_removed(pos, data);
131 }
132 }
133 }
134 }
135 } else {
136 old.for_each(|pos| on_removed(pos, data));
137 new.for_each(|pos| on_added(pos, data));
138 }
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_contains() {
148 let view = PlayerChunkView::new(ChunkPos::new(0, 0), 2);
149
150 assert!(view.contains(ChunkPos::new(0, 0)));
152
153 assert!(view.contains(ChunkPos::new(3, 0))); assert!(!view.contains(ChunkPos::new(4, 0))); }
163
164 #[test]
165 fn test_difference() {
166 let mut added = Vec::new();
167 let mut removed = Vec::new();
168
169 let old_view = PlayerChunkView::new(ChunkPos::new(0, 0), 2);
170 let new_view = PlayerChunkView::new(ChunkPos::new(1, 0), 2);
171
172 PlayerChunkView::difference(
173 &old_view,
174 &new_view,
175 |p, ()| added.push(p),
176 |p, ()| removed.push(p),
177 &mut (),
178 );
179
180 assert!(!added.is_empty());
182 assert!(!removed.is_empty());
183
184 }
187
188 #[test]
189 fn test_disjoint_difference() {
190 let mut added = Vec::new();
191 let mut removed = Vec::new();
192
193 let old_view = PlayerChunkView::new(ChunkPos::new(0, 0), 2);
194 let new_view = PlayerChunkView::new(ChunkPos::new(100, 100), 2); PlayerChunkView::difference(
197 &old_view,
198 &new_view,
199 |p, ()| added.push(p),
200 |p, ()| removed.push(p),
201 &mut (),
202 );
203
204 let mut count = 0;
208 old_view.for_each(|_| count += 1);
209
210 assert_eq!(removed.len(), count);
211 assert_eq!(added.len(), count);
212 }
213}