use bevy::{ecs::entity::Entity, log, math::UVec3, platform::collections::HashMap};
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, path_line_trace},
};
#[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(node_path) = node_path {
let start_path = start_paths
.get(&(start_node.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_node.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 !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
}
#[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 goal = *path.path.back().unwrap();
let mut refined_path = Vec::with_capacity(path.len());
let mut i = 0;
refined_path.push(path.path[i]);
while i < path.len() {
if let Some(direct_path) = path_line_trace(grid, path.path[i], goal) {
refined_path.extend(direct_path.into_iter().skip(1));
break;
}
let mut shortcut_taken = false;
for farthest in (i + 1..path.len()).rev() {
if !filtered {
if let Some(shortcut) = bresenham_path(
grid,
path.path[i],
path.path[farthest],
neighborhood.is_ordinal(),
) {
refined_path.extend(shortcut.into_iter().skip(1));
i = farthest;
shortcut_taken = true;
break;
}
} else if let Some(shortcut) = bresenham_path_filtered(
grid,
path.path[i],
path.path[farthest],
neighborhood.is_ordinal(),
) {
refined_path.extend(shortcut.into_iter().skip(1));
i = farthest;
shortcut_taken = true;
break;
}
}
if !shortcut_taken {
i += 1;
if i < path.len() {
refined_path.push(path.path[i]);
}
}
}
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.clone());
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
}