use std::collections::BinaryHeap;
use fmc::{
bevy::math::{DVec2, DVec3},
blocks::{BlockPosition, Blocks, Friction},
prelude::*,
world::WorldMap,
};
use indexmap::{map::Entry, IndexMap};
#[derive(Component)]
pub struct PathFinder {
entity_width: usize,
entity_height: usize,
start: BlockPosition,
goal: BlockPosition,
path: Vec<DVec3>,
shortcuts: Vec<DVec3>,
}
impl PathFinder {
pub fn new(width: usize, height: usize) -> Self {
return Self {
entity_width: width,
entity_height: height,
start: BlockPosition::default(),
goal: BlockPosition::default(),
path: Vec::new(),
shortcuts: Vec::new(),
};
}
pub fn find_path(&mut self, world_map: &WorldMap, start: DVec3, goal: DVec3) {
let block_start = BlockPosition::from(start);
let block_goal = BlockPosition::from(goal);
if block_start != block_goal && self.goal != block_goal {
self.start = block_start;
self.goal = block_goal;
} else {
return;
}
self.path.clear();
self.shortcuts.clear();
let mut queue = BinaryHeap::with_capacity(16_usize.pow(3));
let mut node_map = IndexMap::new();
node_map.insert(
block_start,
PathNode {
parent_index: usize::MAX,
cost: 0.0,
},
);
let mut potential_successors = Vec::new();
queue.push(Successor {
node_index: 0,
move_cost: 0.0,
heuristic_cost: f32::MAX,
});
let mut roundabout_limit = 0;
let mut best_node_index = 0;
let mut best_node_cost = f32::MAX;
while let Some(successor) = queue.pop() {
let (node_position, path_node) = node_map.get_index(successor.node_index).unwrap();
if successor.cost() < best_node_cost {
best_node_cost = successor.cost();
best_node_index = successor.node_index;
} else {
roundabout_limit += 1;
}
if roundabout_limit > 25 {
self.set_path(best_node_index, &node_map, None);
return;
}
if successor.cost() > path_node.cost && path_node.parent_index != usize::MAX {
continue;
}
potential_successors.clear();
self.get_potential_successors(node_position, world_map, &mut potential_successors);
for (position, move_cost, heuristic_cost) in potential_successors.drain(..) {
let cost = successor.move_cost + move_cost + heuristic_cost;
let node_index;
match node_map.entry(position) {
Entry::Occupied(mut e) => {
if e.get().cost > cost {
node_index = e.index();
e.insert(PathNode {
parent_index: successor.node_index,
cost,
});
} else {
continue;
}
}
Entry::Vacant(v) => {
node_index = v.index();
v.insert(PathNode {
parent_index: successor.node_index,
cost,
});
}
}
if position == block_goal {
self.set_path(node_index, &node_map, Some(goal));
return;
}
queue.push(Successor {
node_index,
move_cost: successor.move_cost + move_cost,
heuristic_cost,
});
}
}
}
pub fn next_node(&mut self, current_postition: DVec3) -> Option<DVec3> {
while let Some(next_position) = self.path.last() {
if next_position.xz().distance_squared(current_postition.xz()) >= 0.25 {
return Some(*next_position);
}
self.path.pop();
}
None
}
pub fn next_shortcut(&mut self, current_postition: DVec3) -> Option<DVec3> {
while let Some(next_position) = self.shortcuts.last() {
if next_position.xz().distance_squared(current_postition.xz()) >= 0.25 {
return Some(*next_position);
}
self.shortcuts.pop();
}
None
}
fn get_move_cost(world_map: &WorldMap, position: BlockPosition) -> f32 {
if let Some(block_id) = world_map.get_block(position) {
let block_config = Blocks::get().get_config(&block_id);
if block_config.is_solid() {
f32::INFINITY
} else {
block_config.drag.max_element() as f32
}
} else {
f32::INFINITY
}
}
fn get_heuristic_cost(&self, position: BlockPosition) -> f32 {
position.distance_squared(*self.goal).abs() as f32
}
fn get_potential_successors(
&self,
position: &BlockPosition,
world_map: &WorldMap,
successors: &mut Vec<(BlockPosition, f32, f32)>,
) {
let above_cost = Self::get_move_cost(world_map, *position + IVec3::Y);
let max_steps = if above_cost == f32::INFINITY { 0 } else { 1 };
let get_successor = |offset: IVec3| -> (BlockPosition, f32, f32) {
let mut position = *position + offset;
let mut move_cost = Self::get_move_cost(world_map, position);
if move_cost == f32::INFINITY && max_steps == 1 {
position += IVec3::new(0, 1, 0);
move_cost = Self::get_move_cost(world_map, position);
(position, move_cost + 1.0, self.get_heuristic_cost(position))
} else {
let mut steps = 0;
loop {
if steps > 2 {
return (position, f32::INFINITY, f32::INFINITY);
}
let below_cost =
Self::get_move_cost(world_map, position - IVec3::new(0, steps + 1, 0));
if below_cost == f32::INFINITY {
move_cost += steps as f32;
position -= IVec3::new(0, steps, 0);
return (position, move_cost, self.get_heuristic_cost(position));
}
move_cost += below_cost;
steps += 1;
}
}
};
for offset in [
IVec3::X,
IVec3::NEG_X,
IVec3::Z,
IVec3::NEG_Z,
IVec3::X + IVec3::Z,
IVec3::X - IVec3::Z,
-IVec3::X + IVec3::Z,
-IVec3::X - IVec3::Z,
] {
let (node_position, move_cost, heuristic_cost) = get_successor(offset);
if move_cost != f32::INFINITY {
successors.push((node_position, move_cost, heuristic_cost));
}
}
}
fn set_path(
&mut self,
mut index: usize,
node_map: &IndexMap<BlockPosition, PathNode>,
accurate_goal: Option<DVec3>,
) {
let mut xz_offset = DVec3::splat(self.entity_width as f64 * 0.5);
xz_offset.y = 0.0;
while index != usize::MAX {
let (position, path_node) = node_map.get_index(index).unwrap();
self.path.push(position.as_dvec3() + xz_offset);
index = path_node.parent_index;
}
if let Some(accurate_goal) = accurate_goal {
self.path[0] = accurate_goal;
}
self.path.pop();
self.calc_shortcuts();
}
fn calc_shortcuts(&mut self) {
let start = self.start.as_dvec3();
let goal = self.path[0];
let mut distance = goal.distance_squared(start);
for position in self.path.iter().rev() {
let new_distance = position.distance_squared(goal);
if new_distance > distance {
self.shortcuts.push(*position);
}
distance = new_distance;
}
if self.shortcuts.len() > self.path.len() {
panic!();
}
self.shortcuts.push(goal);
self.shortcuts.reverse();
}
}
struct PathNode {
parent_index: usize,
cost: f32,
}
struct Successor {
node_index: usize,
move_cost: f32,
heuristic_cost: f32,
}
impl Successor {
fn cost(&self) -> f32 {
self.move_cost + self.heuristic_cost
}
}
impl Ord for Successor {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match other.cost().total_cmp(&self.cost()) {
std::cmp::Ordering::Equal => other.heuristic_cost.total_cmp(&self.heuristic_cost),
ordering => ordering,
}
}
}
impl PartialEq for Successor {
fn eq(&self, other: &Self) -> bool {
self.move_cost.eq(&other.move_cost) && self.heuristic_cost.eq(&other.heuristic_cost)
}
}
impl Eq for Successor {}
impl PartialOrd for Successor {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}