1use rand::Rng;
12
13pub fn nearby_walkable(
27 pos: (usize, usize),
28 radius: usize,
29 walkmap: &[bool],
30 width: usize,
31 height: usize,
32 periodic: bool,
33) -> Vec<(usize, usize)> {
34 let r = radius as i32;
35 let (cx, cy) = (pos.0 as i32, pos.1 as i32);
36 let mut result = Vec::new();
37
38 for dy in -r..=r {
39 for dx in -r..=r {
40 if dx == 0 && dy == 0 {
41 continue;
42 }
43 let nx = cx + dx;
44 let ny = cy + dy;
45
46 let cell = if periodic {
47 let px = ((nx % width as i32) + width as i32) % width as i32;
48 let py = ((ny % height as i32) + height as i32) % height as i32;
49 Some((px as usize, py as usize))
50 } else if nx >= 0 && ny >= 0 && (nx as usize) < width && (ny as usize) < height {
51 Some((nx as usize, ny as usize))
52 } else {
53 None
54 };
55
56 if let Some((x, y)) = cell {
57 if walkmap[y * width + x] {
58 result.push((x, y));
59 }
60 }
61 }
62 }
63
64 result
65}
66
67pub fn random_walkable<R: Rng>(
84 walkmap: &[bool],
85 width: usize,
86 _height: usize,
87 rng: &mut R,
88) -> Option<(usize, usize)> {
89 let walkable_cells: Vec<usize> = walkmap
90 .iter()
91 .enumerate()
92 .filter(|(_, &w)| w)
93 .map(|(i, _)| i)
94 .collect();
95
96 if walkable_cells.is_empty() {
97 return None;
98 }
99
100 let idx = walkable_cells[rng.gen_range(0..walkable_cells.len())];
101 let x = idx % width;
102 let y = idx / width;
103 Some((x, y))
104}
105
106pub fn random_walkable_nearby<R: Rng>(
123 pos: (usize, usize),
124 radius: usize,
125 walkmap: &[bool],
126 width: usize,
127 height: usize,
128 periodic: bool,
129 rng: &mut R,
130) -> Option<(usize, usize)> {
131 let candidates = nearby_walkable(pos, radius, walkmap, width, height, periodic);
132 if candidates.is_empty() {
133 return None;
134 }
135 Some(candidates[rng.gen_range(0..candidates.len())])
136}
137
138pub fn random_walkable_continuous<R: Rng>(
145 walkmap: &[bool],
146 grid_w: usize,
147 grid_h: usize,
148 extent_x: f64,
149 extent_y: f64,
150 rng: &mut R,
151) -> Option<(f64, f64)> {
152 let cell = random_walkable(walkmap, grid_w, grid_h, rng)?;
153 let cell_w = extent_x / grid_w as f64;
154 let cell_h = extent_y / grid_h as f64;
155 let x = cell.0 as f64 * cell_w + rng.gen_range(0.0..cell_w);
156 let y = cell.1 as f64 * cell_h + rng.gen_range(0.0..cell_h);
157 Some((x, y))
158}
159
160#[allow(clippy::too_many_arguments)]
168pub fn random_walkable_continuous_nearby<R: Rng>(
169 pos: (f64, f64),
170 radius: f64,
171 walkmap: &[bool],
172 grid_w: usize,
173 grid_h: usize,
174 extent_x: f64,
175 extent_y: f64,
176 periodic: bool,
177 rng: &mut R,
178) -> Option<(f64, f64)> {
179 let cell_w = extent_x / grid_w as f64;
180 let cell_h = extent_y / grid_h as f64;
181
182 let cx = ((pos.0 / extent_x * grid_w as f64).floor() as usize).min(grid_w - 1);
184 let cy = ((pos.1 / extent_y * grid_h as f64).floor() as usize).min(grid_h - 1);
185
186 let grid_r = ((radius / cell_w.min(cell_h)).ceil() as usize).max(1);
188
189 let candidates = nearby_walkable((cx, cy), grid_r, walkmap, grid_w, grid_h, periodic);
190
191 if candidates.is_empty() {
192 return None;
193 }
194
195 for _ in 0..100 {
197 let cell = candidates[rng.gen_range(0..candidates.len())];
198 let x = cell.0 as f64 * cell_w + rng.gen_range(0.0..cell_w);
199 let y = cell.1 as f64 * cell_h + rng.gen_range(0.0..cell_h);
200
201 let mut dx = (x - pos.0).abs();
202 let mut dy = (y - pos.1).abs();
203 if periodic {
204 if dx > extent_x * 0.5 {
205 dx = extent_x - dx;
206 }
207 if dy > extent_y * 0.5 {
208 dy = extent_y - dy;
209 }
210 }
211 let dist = (dx * dx + dy * dy).sqrt();
212 if dist <= radius {
213 return Some((x, y));
214 }
215 }
216
217 None
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use rand::rngs::StdRng;
224 use rand::SeedableRng;
225
226 #[test]
227 fn nearby_walkable_all_open() {
228 let walkmap = vec![true; 25]; let result = nearby_walkable((2, 2), 1, &walkmap, 5, 5, false);
230 assert_eq!(result.len(), 8); }
232
233 #[test]
234 fn nearby_walkable_with_walls() {
235 let mut walkmap = vec![true; 25];
236 walkmap[5 + 2] = false; walkmap[3 * 5 + 2] = false; let result = nearby_walkable((2, 2), 1, &walkmap, 5, 5, false);
239 assert_eq!(result.len(), 6);
240 }
241
242 #[test]
243 fn nearby_walkable_corner_periodic() {
244 let walkmap = vec![true; 25]; let result = nearby_walkable((0, 0), 1, &walkmap, 5, 5, true);
246 assert_eq!(result.len(), 8); }
248
249 #[test]
250 fn nearby_walkable_corner_bounded() {
251 let walkmap = vec![true; 25]; let result = nearby_walkable((0, 0), 1, &walkmap, 5, 5, false);
253 assert_eq!(result.len(), 3); }
255
256 #[test]
257 fn random_walkable_basic() {
258 let walkmap = vec![true; 25];
259 let mut rng = StdRng::seed_from_u64(42);
260 let pos = random_walkable(&walkmap, 5, 5, &mut rng);
261 assert!(pos.is_some());
262 let (x, y) = pos.unwrap();
263 assert!(x < 5 && y < 5);
264 }
265
266 #[test]
267 fn random_walkable_none() {
268 let walkmap = vec![false; 25];
269 let mut rng = StdRng::seed_from_u64(42);
270 let pos = random_walkable(&walkmap, 5, 5, &mut rng);
271 assert!(pos.is_none());
272 }
273
274 #[test]
275 fn random_walkable_continuous_basic() {
276 let walkmap = vec![true; 100];
277 let mut rng = StdRng::seed_from_u64(42);
278 let pos = random_walkable_continuous(&walkmap, 10, 10, 100.0, 100.0, &mut rng);
279 assert!(pos.is_some());
280 let (x, y) = pos.unwrap();
281 assert!((0.0..100.0).contains(&x));
282 assert!((0.0..100.0).contains(&y));
283 }
284}