use bevy::{
ecs::entity::Entity,
log,
math::{IVec3, UVec3},
platform::collections::{HashMap, HashSet},
};
use ndarray::ArrayView3;
use crate::{
astar::{astar_graph, astar_grid},
chunk::Chunk,
dijkstra::dijkstra_grid,
grid::Grid,
nav::NavCell,
node::Node,
path::Path,
prelude::Neighborhood,
raycast::{bresenham_path, bresenham_path_filtered},
};
#[inline(always)]
pub(crate) fn pathfind_astar<N: Neighborhood>(
neighborhood: &N,
grid: &ArrayView3<NavCell>,
start: UVec3,
goal: UVec3,
blocking: &HashMap<UVec3, Entity>,
partial: bool,
) -> Option<Path> {
let shape = grid.shape();
if start.x as usize >= shape[0] || start.y as usize >= shape[1] || start.z as usize >= shape[2]
{
log::warn!("Start is out of bounds: {:?}", start);
return None;
}
if goal.x as usize >= shape[0] || goal.y as usize >= shape[1] || goal.z as usize >= shape[2] {
log::warn!("Goal is out of bounds: {:?}", goal);
return None;
}
if grid[[start.x as usize, start.y as usize, start.z as usize]].is_impassable()
|| grid[[goal.x as usize, goal.y as usize, goal.z as usize]].is_impassable() && !partial
{
return None;
}
if blocking.contains_key(&goal) && !partial {
return None;
}
let path = astar_grid(neighborhood, grid, start, goal, 1024, partial, blocking);
if let Some(mut path) = path {
path.path.pop_front();
Some(path)
} else {
None
}
}
#[inline(always)]
pub(crate) fn pathfind<N: Neighborhood>(
grid: &Grid<N>,
start: UVec3,
goal: UVec3,
blocking: &HashMap<UVec3, Entity>,
partial: bool,
refined: bool,
) -> Option<Path> {
if !grid.in_bounds(start) {
log::warn!("Start is out of bounds: {:?}", start);
return None;
}
if !grid.in_bounds(goal) {
log::warn!("Goal is out of bounds: {:?}", goal);
return None;
}
if grid.view()[[start.x as usize, start.y as usize, start.z as usize]].is_impassable()
|| grid.view()[[goal.x as usize, goal.y as usize, goal.z as usize]].is_impassable()
&& !partial
{
return None;
}
let start_chunk = grid.chunk_at_position(start)?;
let goal_chunk = grid.chunk_at_position(goal)?;
if start_chunk == goal_chunk {
let path = astar_grid(
&grid.neighborhood,
&grid.view(),
start,
goal,
100,
partial,
blocking,
);
if let Some(mut path) = path {
path.path.pop_front();
return Some(path);
} else {
return None;
}
}
let (start_nodes, start_paths) =
filter_and_rank_chunk_nodes(grid, start_chunk, start, goal, blocking)?;
let (goal_nodes, goal_paths) =
filter_and_rank_chunk_nodes(grid, goal_chunk, goal, start, blocking)?;
let mut path: Vec<UVec3> = Vec::new();
let mut cost = 0;
for start_node in &start_nodes {
for goal_node in goal_nodes.clone() {
let node_path = astar_graph(
&grid.neighborhood,
grid.graph(),
start_node.pos,
goal_node.pos,
100,
);
if let Some(mut node_path) = node_path {
let start_keys: HashSet<_> = start_paths.keys().copied().collect();
let goal_keys: HashSet<_> = goal_paths.keys().copied().collect();
trim_path(
&mut node_path,
&start_keys,
&goal_keys,
start_chunk,
goal_chunk,
);
let start_pos = node_path.path.front().unwrap();
let goal_pos = node_path.path.back().unwrap();
let start_path = start_paths.get(&(start_pos - start_chunk.min())).unwrap();
path.extend(start_path.path().iter().map(|pos| *pos + start_chunk.min()));
cost += start_path.cost();
for (node, next_node) in
node_path.path().iter().zip(node_path.path().iter().skip(1))
{
let cached_path = grid.graph().node_at(*node).unwrap().edges[next_node].clone();
path.extend(cached_path.path().iter().skip(1));
cost += cached_path.cost();
}
let end_path = goal_paths.get(&(goal_pos - goal_chunk.min())).unwrap();
path.extend(
end_path
.path()
.iter()
.rev()
.map(|pos| *pos + goal_chunk.min()),
);
cost += end_path.cost();
if path.is_empty() {
return None;
}
if path.len() >= 2 && path[path.len() - 1] == path[path.len() - 2] {
path.pop();
}
if path.len() >= 2 && path[0] == path[1] {
log::warn!("Start contains duplicate nodes: {:?}", path);
}
if !refined {
let mut path = Path::new(path, cost);
path.graph_path = node_path.path;
return Some(path);
}
let mut refined_path = optimize_path(
&grid.neighborhood,
&grid.view(),
&Path::from_slice(&path, cost),
);
refined_path.path.pop_front();
refined_path.graph_path = node_path.path;
return Some(refined_path);
}
}
}
None
}
pub(crate) fn trim_path(
path: &mut Path,
start_keys: &HashSet<UVec3>, goal_keys: &HashSet<UVec3>, start_chunk: &Chunk,
goal_chunk: &Chunk,
) {
if let Some(i) = path
.path
.iter()
.position(|pos| start_keys.contains(&(*pos - start_chunk.min())))
{
for _ in 0..i {
path.path.pop_front();
}
}
if let Some(i) = path
.path
.iter()
.rposition(|pos| goal_keys.contains(&(*pos - goal_chunk.min())))
{
let len = path.path.len();
for _ in (i + 1)..len {
path.path.pop_back();
}
}
assert!(
!path.path.is_empty(),
"BUG: trim_path() removed all nodes — this should never happen"
);
}
#[inline(always)]
pub(crate) fn optimize_path<N: Neighborhood>(
neighborhood: &N,
grid: &ArrayView3<NavCell>,
path: &Path,
) -> Path {
if path.is_empty() {
return path.clone();
}
let filtered = !neighborhood.filters().is_empty();
let mut refined_path = Vec::with_capacity(path.len());
let mut i = 0;
let mut last_dir: Option<IVec3> = None;
refined_path.push(path.path[i]);
while i < path.len() {
let mut shortcut_taken = false;
for farthest in (i + 1..path.len()).rev() {
let candidate = path.path[farthest];
let dir = (candidate.as_ivec3() - path.path[i].as_ivec3()).signum();
if let Some(prev_dir) = last_dir {
if dir != prev_dir && dir.dot(prev_dir) < 0 {
continue; }
}
let maybe_shortcut = if filtered {
bresenham_path_filtered(grid, path.path[i], candidate, neighborhood.is_ordinal())
} else {
bresenham_path(grid, path.path[i], candidate, neighborhood.is_ordinal())
};
if let Some(shortcut) = maybe_shortcut {
refined_path.extend(shortcut.into_iter().skip(1));
i = farthest;
shortcut_taken = true;
last_dir = Some(dir);
break;
}
}
if !shortcut_taken {
i += 1;
if i < path.len() {
let dir = if let Some(prev) = refined_path.last() {
(path.path[i].as_ivec3() - prev.as_ivec3()).signum()
} else {
IVec3::ZERO
};
refined_path.push(path.path[i]);
last_dir = Some(dir);
}
}
}
let cost = refined_path
.iter()
.map(|pos| grid[[pos.x as usize, pos.y as usize, pos.z as usize]].cost)
.sum();
let mut path = Path::new(refined_path.clone(), cost);
path.graph_path = refined_path.into();
path
}
#[inline(always)]
fn filter_and_rank_chunk_nodes<'a, N: Neighborhood>(
grid: &'a Grid<N>,
chunk: &Chunk,
source: UVec3,
target: UVec3,
blocking: &HashMap<UVec3, Entity>,
) -> Option<(Vec<&'a Node>, HashMap<UVec3, Path>)> {
let nodes = grid.graph().nodes_in_chunk(chunk);
let adjusted_blocking = blocking
.iter()
.map(|(pos, entity)| (chunk.to_local(pos), *entity))
.collect::<HashMap<_, _>>();
let paths = dijkstra_grid(
&grid.chunk_view(chunk),
source - chunk.min(),
&nodes
.iter()
.map(|node| node.pos - chunk.min())
.collect::<Vec<_>>(),
false,
100,
&adjusted_blocking,
);
let filtered_nodes = nodes
.iter()
.filter(|node| paths.contains_key(&(node.pos - chunk.min())))
.collect::<Vec<_>>();
if filtered_nodes.is_empty() {
return None;
}
let mut ranked_nodes = filtered_nodes
.iter()
.map(|node| {
let d_start = manhattan_distance(node.pos, source);
let d_goal = manhattan_distance(node.pos, target);
(*node, d_start + d_goal)
})
.collect::<Vec<_>>();
ranked_nodes.sort_by_key(|(_, dist)| *dist);
Some((ranked_nodes.into_iter().map(|(n, _)| *n).collect(), paths))
}
#[inline(always)]
fn manhattan_distance(a: UVec3, b: UVec3) -> i32 {
(a.x as i32 - b.x as i32).abs()
+ (a.y as i32 - b.y as i32).abs()
+ (a.z as i32 - b.z as i32).abs()
}
#[inline(always)]
pub(crate) fn reroute_path<N: Neighborhood>(
grid: &Grid<N>,
path: &Path,
start: UVec3,
goal: UVec3,
blocking: &HashMap<UVec3, Entity>,
refined: bool,
) -> Option<Path> {
if !grid.in_bounds(start) {
log::warn!("Start is out of bounds: {:?}", start);
return None;
}
if !grid.in_bounds(goal) {
log::warn!("Goal is out of bounds: {:?}", goal);
return None;
}
if path.graph_path.is_empty() {
return pathfind_astar(
&grid.neighborhood,
&grid.view(),
start,
goal,
blocking,
false,
);
}
let new_path = path.graph_path.iter().find_map(|pos| {
let new_path = pathfind_astar(
&grid.neighborhood,
&grid.view(),
start,
*pos,
blocking,
false,
);
if new_path.is_some() && !new_path.as_ref().unwrap().is_empty() {
new_path
} else {
None
}
});
if let Some(new_path) = new_path {
let mut hpa_path = Vec::new();
for pos in new_path.path() {
hpa_path.push(*pos);
}
let last_pos = *new_path.path().last().unwrap();
let hpa = pathfind(grid, last_pos, goal, blocking, false, refined);
if let Some(hpa) = hpa {
for pos in hpa.path() {
hpa_path.push(*pos);
}
let mut path = Path::new(hpa_path, new_path.cost() + hpa.cost());
path.graph_path = hpa.graph_path.clone();
return Some(path);
}
}
None
}