use rand::Rng;
pub fn nearby_walkable(
pos: (usize, usize),
radius: usize,
walkmap: &[bool],
width: usize,
height: usize,
periodic: bool,
) -> Vec<(usize, usize)> {
let r = radius as i32;
let (cx, cy) = (pos.0 as i32, pos.1 as i32);
let mut result = Vec::new();
for dy in -r..=r {
for dx in -r..=r {
if dx == 0 && dy == 0 {
continue;
}
let nx = cx + dx;
let ny = cy + dy;
let cell = if periodic {
let px = ((nx % width as i32) + width as i32) % width as i32;
let py = ((ny % height as i32) + height as i32) % height as i32;
Some((px as usize, py as usize))
} else if nx >= 0 && ny >= 0 && (nx as usize) < width && (ny as usize) < height {
Some((nx as usize, ny as usize))
} else {
None
};
if let Some((x, y)) = cell {
if walkmap[y * width + x] {
result.push((x, y));
}
}
}
}
result
}
pub fn random_walkable<R: Rng>(
walkmap: &[bool],
width: usize,
_height: usize,
rng: &mut R,
) -> Option<(usize, usize)> {
let walkable_cells: Vec<usize> = walkmap
.iter()
.enumerate()
.filter(|(_, &w)| w)
.map(|(i, _)| i)
.collect();
if walkable_cells.is_empty() {
return None;
}
let idx = walkable_cells[rng.gen_range(0..walkable_cells.len())];
let x = idx % width;
let y = idx / width;
Some((x, y))
}
pub fn random_walkable_nearby<R: Rng>(
pos: (usize, usize),
radius: usize,
walkmap: &[bool],
width: usize,
height: usize,
periodic: bool,
rng: &mut R,
) -> Option<(usize, usize)> {
let candidates = nearby_walkable(pos, radius, walkmap, width, height, periodic);
if candidates.is_empty() {
return None;
}
Some(candidates[rng.gen_range(0..candidates.len())])
}
pub fn random_walkable_continuous<R: Rng>(
walkmap: &[bool],
grid_w: usize,
grid_h: usize,
extent_x: f64,
extent_y: f64,
rng: &mut R,
) -> Option<(f64, f64)> {
let cell = random_walkable(walkmap, grid_w, grid_h, rng)?;
let cell_w = extent_x / grid_w as f64;
let cell_h = extent_y / grid_h as f64;
let x = cell.0 as f64 * cell_w + rng.gen_range(0.0..cell_w);
let y = cell.1 as f64 * cell_h + rng.gen_range(0.0..cell_h);
Some((x, y))
}
#[allow(clippy::too_many_arguments)]
pub fn random_walkable_continuous_nearby<R: Rng>(
pos: (f64, f64),
radius: f64,
walkmap: &[bool],
grid_w: usize,
grid_h: usize,
extent_x: f64,
extent_y: f64,
periodic: bool,
rng: &mut R,
) -> Option<(f64, f64)> {
let cell_w = extent_x / grid_w as f64;
let cell_h = extent_y / grid_h as f64;
let cx = ((pos.0 / extent_x * grid_w as f64).floor() as usize).min(grid_w - 1);
let cy = ((pos.1 / extent_y * grid_h as f64).floor() as usize).min(grid_h - 1);
let grid_r = ((radius / cell_w.min(cell_h)).ceil() as usize).max(1);
let candidates = nearby_walkable((cx, cy), grid_r, walkmap, grid_w, grid_h, periodic);
if candidates.is_empty() {
return None;
}
for _ in 0..100 {
let cell = candidates[rng.gen_range(0..candidates.len())];
let x = cell.0 as f64 * cell_w + rng.gen_range(0.0..cell_w);
let y = cell.1 as f64 * cell_h + rng.gen_range(0.0..cell_h);
let mut dx = (x - pos.0).abs();
let mut dy = (y - pos.1).abs();
if periodic {
if dx > extent_x * 0.5 {
dx = extent_x - dx;
}
if dy > extent_y * 0.5 {
dy = extent_y - dy;
}
}
let dist = (dx * dx + dy * dy).sqrt();
if dist <= radius {
return Some((x, y));
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use rand::rngs::StdRng;
use rand::SeedableRng;
#[test]
fn nearby_walkable_all_open() {
let walkmap = vec![true; 25]; let result = nearby_walkable((2, 2), 1, &walkmap, 5, 5, false);
assert_eq!(result.len(), 8); }
#[test]
fn nearby_walkable_with_walls() {
let mut walkmap = vec![true; 25];
walkmap[5 + 2] = false; walkmap[3 * 5 + 2] = false; let result = nearby_walkable((2, 2), 1, &walkmap, 5, 5, false);
assert_eq!(result.len(), 6);
}
#[test]
fn nearby_walkable_corner_periodic() {
let walkmap = vec![true; 25]; let result = nearby_walkable((0, 0), 1, &walkmap, 5, 5, true);
assert_eq!(result.len(), 8); }
#[test]
fn nearby_walkable_corner_bounded() {
let walkmap = vec![true; 25]; let result = nearby_walkable((0, 0), 1, &walkmap, 5, 5, false);
assert_eq!(result.len(), 3); }
#[test]
fn random_walkable_basic() {
let walkmap = vec![true; 25];
let mut rng = StdRng::seed_from_u64(42);
let pos = random_walkable(&walkmap, 5, 5, &mut rng);
assert!(pos.is_some());
let (x, y) = pos.unwrap();
assert!(x < 5 && y < 5);
}
#[test]
fn random_walkable_none() {
let walkmap = vec![false; 25];
let mut rng = StdRng::seed_from_u64(42);
let pos = random_walkable(&walkmap, 5, 5, &mut rng);
assert!(pos.is_none());
}
#[test]
fn random_walkable_continuous_basic() {
let walkmap = vec![true; 100];
let mut rng = StdRng::seed_from_u64(42);
let pos = random_walkable_continuous(&walkmap, 10, 10, 100.0, 100.0, &mut rng);
assert!(pos.is_some());
let (x, y) = pos.unwrap();
assert!((0.0..100.0).contains(&x));
assert!((0.0..100.0).contains(&y));
}
}