#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
use bevy::ecs::query::Without;
use bevy::math::{IVec3, UVec3};
use bevy::reflect::Reflect;
use indexmap::IndexMap;
use ndarray::ArrayView3;
use rustc_hash::FxHasher;
use std::cmp::Ordering;
use std::hash::BuildHasherDefault;
use crate::nav::NavCell;
mod astar;
mod chunk;
pub mod components;
pub mod debug;
mod dijkstra;
pub mod dir;
pub mod filter;
mod flood_fill;
mod graph;
pub mod grid;
mod hpa;
mod macros;
pub mod nav;
pub mod nav_mask;
pub mod neighbor;
mod node;
pub mod path;
pub mod pathfind;
pub mod plugin;
pub mod raycast;
mod thetastar;
pub mod prelude {
pub use crate::components::*;
pub use crate::debug::{DebugTilemapType, NorthstarDebugPlugin};
pub use crate::dir::Dir;
pub use crate::filter;
pub use crate::grid::{Grid, GridSettingsBuilder};
pub use crate::nav::{Nav, Portal};
pub use crate::nav_mask::{NavCellMask, NavMask, NavMaskLayer, NavMaskResult};
pub use crate::neighbor::*;
pub use crate::path::Path;
pub use crate::pathfind::PathfindArgs;
pub use crate::plugin::{
BlockingMap, NorthstarPlugin, NorthstarPluginSettings, PathfindSettings, PathingSet, Stats,
};
pub use crate::MovementCost;
pub use crate::NavRegion;
pub use crate::SearchLimits;
pub use crate::{CardinalGrid, CardinalGrid3d, OrdinalGrid, OrdinalGrid3d};
}
pub type MovementCost = u32;
pub type CardinalGrid = grid::Grid<neighbor::CardinalNeighborhood>;
pub type CardinalGrid3d = grid::Grid<neighbor::CardinalNeighborhood3d>;
pub type OrdinalGrid = grid::Grid<neighbor::OrdinalNeighborhood>;
pub type OrdinalGrid3d = grid::Grid<neighbor::OrdinalNeighborhood3d>;
pub type WithoutPathingFailures = (
Without<components::NextPos>,
Without<components::AvoidanceFailed>,
Without<components::RerouteFailed>,
);
pub(crate) type NodeId = usize;
type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>;
pub(crate) struct SmallestCostHolder<Id> {
estimated_cost: Id,
cost: Id,
index: usize,
}
impl<Id: PartialEq> PartialEq for SmallestCostHolder<Id> {
fn eq(&self, other: &Self) -> bool {
self.estimated_cost.eq(&other.estimated_cost) && self.cost.eq(&other.cost)
}
}
impl<Id: Eq> Eq for SmallestCostHolder<Id> {}
#[derive(Clone, Copy, Debug, Default, Reflect)]
pub struct SearchLimits {
pub boundary: Option<NavRegion>,
pub distance: Option<u32>,
pub partial: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Reflect)]
pub struct NavRegion {
pub min: UVec3,
pub max: UVec3,
}
impl NavRegion {
pub fn new(min: UVec3, max: UVec3) -> Self {
assert!(
min.x <= max.x && min.y <= max.y && min.z <= max.z,
"Invalid region bounds"
);
Self { min, max }
}
pub fn from_grid(grid: &ArrayView3<NavCell>) -> Self {
let shape = grid.shape();
Self {
min: UVec3::new(0, 0, 0),
max: UVec3::new(shape[0] as u32, shape[1] as u32, shape[2] as u32),
}
}
pub fn contains(&self, pos: UVec3) -> bool {
pos.x >= self.min.x
&& pos.x <= self.max.x
&& pos.y >= self.min.y
&& pos.y <= self.max.y
&& pos.z >= self.min.z
&& pos.z <= self.max.z
}
pub fn iter(&self) -> NavRegionIter {
NavRegionIter {
region: *self,
current: self.min,
}
}
}
pub struct NavRegionIter {
region: NavRegion,
current: UVec3,
}
impl Iterator for NavRegionIter {
type Item = UVec3;
fn next(&mut self) -> Option<Self::Item> {
if self.current.z > self.region.max.z {
return None;
}
let result = self.current;
self.current.x += 1;
if self.current.x > self.region.max.x {
self.current.x = self.region.min.x;
self.current.y += 1;
if self.current.y > self.region.max.y {
self.current.y = self.region.min.y;
self.current.z += 1;
}
}
Some(result)
}
}
impl<Id: Ord + std::ops::Add<Output = Id> + Copy> PartialOrd for SmallestCostHolder<Id> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<Id: Ord + std::ops::Add<Output = Id> + Copy> Ord for SmallestCostHolder<Id> {
fn cmp(&self, other: &Self) -> Ordering {
let self_total = self.cost + self.estimated_cost;
let other_total = other.cost + other.estimated_cost;
other_total.cmp(&self_total)
}
}
#[inline(always)]
pub(crate) fn in_bounds_3d(pos: UVec3, min: UVec3, max: UVec3) -> bool {
pos.x.wrapping_sub(min.x) < (max.x - min.x)
&& pos.y.wrapping_sub(min.y) < (max.y - min.y)
&& pos.z.wrapping_sub(min.z) < (max.z - min.z)
}
#[inline(always)]
fn position_in_cubic_window(pos: UVec3, center: IVec3, radius: i32, grid_shape: IVec3) -> bool {
let min = (center - IVec3::splat(radius)).clamp(IVec3::ZERO, grid_shape - 1);
let max = (center + IVec3::splat(radius)).clamp(IVec3::ZERO, grid_shape - 1);
pos.as_ivec3().cmplt(min).any()
|| pos.as_ivec3().cmple(max).any()
|| pos.as_ivec3().cmpeq(center).all()
|| pos.as_ivec3().cmpeq(min).all()
|| pos.as_ivec3().cmpeq(max).all()
|| (pos.as_ivec3() - center).abs().max_element() <= radius
}
pub(crate) fn are_adjacent(pos1: UVec3, pos2: UVec3, ordinal: bool) -> bool {
if pos1 == pos2 {
return false; }
let diff = pos1.as_ivec3() - pos2.as_ivec3();
let abs_diff = diff.abs();
if ordinal {
abs_diff.x <= 1 && abs_diff.y <= 1 && abs_diff.z <= 1
} else {
let non_zero_count =
(abs_diff.x > 0) as u32 + (abs_diff.y > 0) as u32 + (abs_diff.z > 0) as u32;
non_zero_count == 1 && abs_diff.max_element() == 1
}
}
pub(crate) fn size_hint_grid<N: neighbor::Neighborhood>(
neighborhood: &N,
grid_shape: &[usize],
start: UVec3,
goal: UVec3,
) -> usize {
let manhattan_distance = neighborhood.heuristic(start, goal) as usize;
let grid_size = grid_shape.iter().product::<usize>();
if grid_size < 1000 || manhattan_distance < 10 {
return (manhattan_distance * 4).clamp(64, 512);
}
let search_radius = (manhattan_distance as f32 * 0.3) as usize; let estimated_nodes_explored = manhattan_distance * search_radius;
estimated_nodes_explored
.max(128)
.min(grid_size / 4)
.min(8192)
}
pub(crate) fn size_hint_graph<N: neighbor::Neighborhood>(
neighborhood: &N,
graph: &graph::Graph,
start: UVec3,
goal: UVec3,
) -> usize {
let manhattan_distance = neighborhood.heuristic(start, goal) as usize;
let graph_size = graph.nodes().len();
(manhattan_distance * 2)
.max(32)
.min(graph_size / 2)
.min(1024)
}