use bevy::math::UVec3;
use ndarray::ArrayView3;
use crate::Point;
pub fn line_of_sight_ordinal(grid: &ArrayView3<Point>, start: UVec3, end: UVec3) -> bool {
let start = start.as_vec3();
let end = end.as_vec3();
let mut x = start.x;
let mut y = start.y;
let mut z = start.z;
let dx = end.x - start.x;
let dy = end.y - start.y;
let dz = end.z - start.z;
let steps = dx.abs().max(dy.abs()).max(dz.abs());
if steps == 0.0 {
return true;
}
let x_step = dx / steps;
let y_step = dy / steps;
let z_step = dz / steps;
for _ in 0..=steps as i32 {
let grid_x = x.round() as usize;
let grid_y = y.round() as usize;
let grid_z = z.round() as usize;
if grid[[grid_x, grid_y, grid_z]].wall {
return false; }
x += x_step;
y += y_step;
z += z_step;
}
true
}
pub fn line_of_sight_cardinal(grid: &ArrayView3<Point>, start: UVec3, end: UVec3) -> bool {
let mut pos = start.as_ivec3();
let end = end.as_ivec3();
while (pos.x, pos.y, pos.z) != (end.x, end.y, end.z) {
if grid[[pos.x as usize, pos.y as usize, pos.z as usize]].wall {
return false; }
if pos.x != end.x {
pos.x += (end.x - pos.x).signum();
} else if pos.y != end.y {
pos.y += (end.y - pos.y).signum();
} else if pos.z != end.z {
pos.z += (end.z - pos.z).signum();
}
}
true
}
pub(crate) fn generate_path_segment_ordinal(start: UVec3, end: UVec3) -> Vec<UVec3> {
let mut segment = Vec::new();
let mut current = start;
segment.push(current);
let dx = (end.x as isize - start.x as isize).abs();
let dy = (end.y as isize - start.y as isize).abs();
let dz = (end.z as isize - start.z as isize).abs();
let steps = dx.max(dy).max(dz);
if steps == 0 {
return segment; }
let step_x = (end.x as isize - start.x as isize) as f32 / steps as f32;
let step_y = (end.y as isize - start.y as isize) as f32 / steps as f32;
let step_z = (end.z as isize - start.z as isize) as f32 / steps as f32;
let mut x = start.x as f32;
let mut y = start.y as f32;
let mut z = start.z as f32;
for _ in 0..steps {
x += step_x;
y += step_y;
z += step_z;
let next = UVec3 {
x: x.round() as u32,
y: y.round() as u32,
z: z.round() as u32,
};
if next != current {
segment.push(next);
current = next;
}
}
segment
}
pub(crate) fn generate_path_segment_cardinal(start: UVec3, end: UVec3) -> Vec<UVec3> {
let mut segment = Vec::new();
segment.push(start);
let mut pos = start.as_ivec3();
let end = end.as_ivec3();
while (pos.x, pos.y, pos.z) != (end.x, end.y, end.z) {
if pos.x != end.x {
pos.x += (end.x - pos.x).signum();
} else if pos.y != end.y {
pos.y += (end.y - pos.y).signum();
} else if pos.z != end.z {
pos.z += (end.z - pos.z).signum();
}
let next = UVec3 {
x: pos.x as u32,
y: pos.y as u32,
z: pos.z as u32,
};
segment.push(next);
}
segment
}
pub fn bresenham_path(grid: &ArrayView3<Point>, start: UVec3, goal: UVec3, ordinal: bool) -> Option<Vec<UVec3>> {
let mut path = Vec::new();
let mut current = start;
let (width, height, depth) = (grid.shape()[0] as u32, grid.shape()[1] as u32, grid.shape()[2] as u32);
let dx = (goal.x as i32 - start.x as i32).abs();
let dy = (goal.y as i32 - start.y as i32).abs();
let dz = if depth > 1 {
(goal.z as i32 - start.z as i32).abs()
} else {
0 };
let sx: i32 = if start.x < goal.x { 1 } else { -1 };
let sy: i32 = if start.y < goal.y { 1 } else { -1 };
let sz: i32 = if depth > 1 && start.z < goal.z {
1
} else {
-1
};
let mut err_xy = dx - dy;
let mut err_xz = dx - dz;
while current != goal {
if current.x >= width || current.y >= height || current.z >= depth {
return None;
}
path.push(current);
if grid[[current.x as usize, current.y as usize, current.z as usize]].wall {
return None;
}
let double_err_xy = 2 * err_xy;
let double_err_xz = 2 * err_xz;
if ordinal {
if double_err_xy >= -dy && double_err_xz >= -dz {
err_xy -= dy;
err_xz -= dz;
current.x = current.x.saturating_add_signed(sx);
}
if double_err_xy < dx {
err_xy += dx;
current.y = current.y.saturating_add_signed(sy);
}
if depth > 1 && double_err_xz < dx {
err_xz += dx;
current.z = current.z.saturating_add_signed(sz);
}
} else {
if double_err_xy >= -dy && double_err_xz >= -dz {
err_xy -= dy;
err_xz -= dz;
current.x = current.x.saturating_add_signed(sx);
} else if double_err_xy < dx {
err_xy += dx;
current.y = current.y.saturating_add_signed(sy);
} else if depth > 1 && double_err_xz < dx {
err_xz += dx;
current.z = current.z.saturating_add_signed(sz);
}
}
}
path.push(goal);
Some(path)
}
#[cfg(test)]
mod tests {
use bevy::math::UVec3;
use ndarray::Array3;
use crate::{prelude::*, raycast::{bresenham_path, line_of_sight_ordinal}};
const GRID_SETTINGS: GridSettings = GridSettings {
width: 12,
height: 12,
depth: 1,
chunk_size: 4,
chunk_depth: 1,
chunk_ordinal: false,
default_cost: 1,
default_wall: false,
};
#[test]
fn test_line_of_sight() {
let mut grid = Array3::from_elem((10, 10, 1), Point::new(1, false));
grid[[5, 5, 0]] = Point::new(1, true);
let start = UVec3::new(0, 0, 0);
let end = UVec3::new(9, 9, 0);
assert_eq!(line_of_sight_ordinal(&grid.view(), start, end), false);
}
#[test]
fn test_bresenhan_path() {
let mut grid: Grid<OrdinalNeighborhood> = Grid::new(&GRID_SETTINGS);
grid.build();
let path = bresenham_path(&grid.get_view(), UVec3::new(0, 0, 0), UVec3::new(10, 10, 0), grid.neighborhood.is_ordinal());
assert!(path.is_some());
assert_eq!(path.unwrap().len(), 11);
let mut grid: Grid<CardinalNeighborhood3d> = Grid::new(&GRID_SETTINGS);
grid.build();
let path = bresenham_path(&grid.get_view(), UVec3::new(0, 0, 0), UVec3::new(10, 10, 0), grid.neighborhood.is_ordinal());
assert!(path.is_some());
assert_eq!(path.unwrap().len(), 21);
let mut grid: Grid<OrdinalNeighborhood3d> = Grid::new(&GRID_SETTINGS);
grid.set_point(UVec3::new(5, 5, 0), Point::new(1, true));
grid.build();
let path = bresenham_path(&grid.get_view(), UVec3::new(0, 0, 0), UVec3::new(10, 10, 0), grid.neighborhood.is_ordinal());
assert!(path.is_none());
}
}