mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Apply experimental astar2 changes to the original impl
This commit is contained in:
parent
d1ca47da41
commit
92a42ced18
@ -4,12 +4,13 @@ use core::{
|
||||
fmt,
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use hashbrown::HashMap;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PathEntry<S> {
|
||||
cost: f32,
|
||||
// cost so far + heursitic
|
||||
cost_estimate: f32,
|
||||
node: S,
|
||||
}
|
||||
|
||||
@ -23,12 +24,24 @@ impl<S: Eq> Ord for PathEntry<S> {
|
||||
// This method implements reverse ordering, so that the lowest cost
|
||||
// will be ordered first
|
||||
fn cmp(&self, other: &PathEntry<S>) -> Ordering {
|
||||
other.cost.partial_cmp(&self.cost).unwrap_or(Equal)
|
||||
other
|
||||
.cost_estimate
|
||||
.partial_cmp(&self.cost_estimate)
|
||||
.unwrap_or(Equal)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Eq> PartialOrd for PathEntry<S> {
|
||||
fn partial_cmp(&self, other: &PathEntry<S>) -> Option<Ordering> { Some(self.cmp(other)) }
|
||||
|
||||
// This is particularily hot in `BinaryHeap::pop`, so we provide this
|
||||
// implementation.
|
||||
//
|
||||
// NOTE: This probably doesn't handle edge cases like `NaNs` in a consistent
|
||||
// manner with `Ord`, but I don't think we need to care about that here(?)
|
||||
//
|
||||
// See note about reverse ordering above.
|
||||
fn le(&self, other: &PathEntry<S>) -> bool { other.cost_estimate <= self.cost_estimate }
|
||||
}
|
||||
|
||||
pub enum PathResult<T> {
|
||||
@ -56,15 +69,20 @@ impl<T> PathResult<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// If node entry exists, this was visited!
|
||||
#[derive(Clone, Debug)]
|
||||
struct NodeEntry<S> {
|
||||
// if came_from == self this is the start node!
|
||||
came_from: S,
|
||||
cheapest_score: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Astar<S, Hasher> {
|
||||
iter: usize,
|
||||
max_iters: usize,
|
||||
potential_nodes: BinaryHeap<PathEntry<S>>,
|
||||
came_from: HashMap<S, S, Hasher>,
|
||||
cheapest_scores: HashMap<S, f32, Hasher>,
|
||||
final_scores: HashMap<S, f32, Hasher>,
|
||||
visited: HashSet<S, Hasher>,
|
||||
potential_nodes: BinaryHeap<PathEntry<S>>, // cost, node pairs
|
||||
visited_nodes: HashMap<S, NodeEntry<S>, Hasher>,
|
||||
cheapest_node: Option<S>,
|
||||
cheapest_cost: Option<f32>,
|
||||
}
|
||||
@ -76,10 +94,7 @@ impl<S: Clone + Eq + Hash + fmt::Debug, H: BuildHasher> fmt::Debug for Astar<S,
|
||||
.field("iter", &self.iter)
|
||||
.field("max_iters", &self.max_iters)
|
||||
.field("potential_nodes", &self.potential_nodes)
|
||||
.field("came_from", &self.came_from)
|
||||
.field("cheapest_scores", &self.cheapest_scores)
|
||||
.field("final_scores", &self.final_scores)
|
||||
.field("visited", &self.visited)
|
||||
.field("visited_nodes", &self.visited_nodes)
|
||||
.field("cheapest_node", &self.cheapest_node)
|
||||
.field("cheapest_cost", &self.cheapest_cost)
|
||||
.finish()
|
||||
@ -92,24 +107,16 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
max_iters,
|
||||
iter: 0,
|
||||
potential_nodes: core::iter::once(PathEntry {
|
||||
cost: 0.0,
|
||||
cost_estimate: 0.0,
|
||||
node: start.clone(),
|
||||
})
|
||||
.collect(),
|
||||
came_from: HashMap::with_hasher(hasher.clone()),
|
||||
cheapest_scores: {
|
||||
let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
||||
h.extend(core::iter::once((start.clone(), 0.0)));
|
||||
h
|
||||
},
|
||||
final_scores: {
|
||||
let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
||||
h.extend(core::iter::once((start.clone(), 0.0)));
|
||||
h
|
||||
},
|
||||
visited: {
|
||||
let mut s = HashSet::with_capacity_and_hasher(1, hasher);
|
||||
s.extend(core::iter::once(start));
|
||||
visited_nodes: {
|
||||
let mut s = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
||||
s.extend(core::iter::once((start.clone(), NodeEntry {
|
||||
came_from: start,
|
||||
cheapest_score: 0.0,
|
||||
})));
|
||||
s
|
||||
},
|
||||
cheapest_node: None,
|
||||
@ -120,13 +127,16 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
pub fn poll<I>(
|
||||
&mut self,
|
||||
iters: usize,
|
||||
// Estimate how far we are from the target? but we are given two nodes...
|
||||
// (current, previous)
|
||||
mut heuristic: impl FnMut(&S, &S) -> f32,
|
||||
// get neighboring nodes
|
||||
mut neighbors: impl FnMut(&S) -> I,
|
||||
mut transition: impl FnMut(&S, &S) -> f32,
|
||||
// have we reached target?
|
||||
mut satisfied: impl FnMut(&S) -> bool,
|
||||
) -> PathResult<S>
|
||||
where
|
||||
I: Iterator<Item = S>,
|
||||
I: Iterator<Item = (S, f32)>, // (node, transition cost)
|
||||
{
|
||||
let iter_limit = self.max_iters.min(self.iter + iters);
|
||||
while self.iter < iter_limit {
|
||||
@ -134,28 +144,48 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
if satisfied(&node) {
|
||||
return PathResult::Path(self.reconstruct_path_to(node));
|
||||
} else {
|
||||
for neighbor in neighbors(&node) {
|
||||
let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX);
|
||||
let neighbor_cheapest =
|
||||
self.cheapest_scores.get(&neighbor).unwrap_or(&f32::MAX);
|
||||
let (node_cheapest, came_from) = self
|
||||
.visited_nodes
|
||||
.get(&node)
|
||||
.map(|n| (n.cheapest_score, n.came_from.clone()))
|
||||
.unwrap();
|
||||
for (neighbor, transition) in neighbors(&node) {
|
||||
if neighbor == came_from {
|
||||
continue;
|
||||
}
|
||||
let neighbor_cheapest = self
|
||||
.visited_nodes
|
||||
.get(&neighbor)
|
||||
.map_or(f32::MAX, |n| n.cheapest_score);
|
||||
|
||||
let cost = node_cheapest + transition(&node, &neighbor);
|
||||
if cost < *neighbor_cheapest {
|
||||
self.came_from.insert(neighbor.clone(), node.clone());
|
||||
self.cheapest_scores.insert(neighbor.clone(), cost);
|
||||
// compute cost to traverse to each neighbor
|
||||
let cost = node_cheapest + transition;
|
||||
|
||||
if cost < neighbor_cheapest {
|
||||
let previously_visited = self
|
||||
.visited_nodes
|
||||
.insert(neighbor.clone(), NodeEntry {
|
||||
came_from: node.clone(),
|
||||
cheapest_score: cost,
|
||||
})
|
||||
.is_some();
|
||||
let h = heuristic(&neighbor, &node);
|
||||
let neighbor_cost = cost + h;
|
||||
self.final_scores.insert(neighbor.clone(), neighbor_cost);
|
||||
// note that cheapest_scores does not include the heuristic
|
||||
// priority queue does include heuristic
|
||||
let cost_estimate = cost + h;
|
||||
|
||||
if self.cheapest_cost.map(|cc| h < cc).unwrap_or(true) {
|
||||
self.cheapest_node = Some(node.clone());
|
||||
self.cheapest_cost = Some(h);
|
||||
};
|
||||
|
||||
if self.visited.insert(neighbor.clone()) {
|
||||
// TODO: I think the if here should be removed
|
||||
// if we hadn't already visted, add this to potential nodes, what about
|
||||
// its neighbors, wouldn't they need to be revisted???
|
||||
if !previously_visited {
|
||||
self.potential_nodes.push(PathEntry {
|
||||
cost_estimate,
|
||||
node: neighbor,
|
||||
cost: neighbor_cost,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -190,7 +220,12 @@ impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
fn reconstruct_path_to(&mut self, end: S) -> Path<S> {
|
||||
let mut path = vec![end.clone()];
|
||||
let mut cnode = &end;
|
||||
while let Some(node) = self.came_from.get(cnode) {
|
||||
while let Some(node) = self
|
||||
.visited_nodes
|
||||
.get(cnode)
|
||||
.map(|n| &n.came_from)
|
||||
.filter(|n| *n != cnode)
|
||||
{
|
||||
path.push(node.clone());
|
||||
cnode = node;
|
||||
}
|
||||
|
@ -1,227 +0,0 @@
|
||||
#![allow(dead_code, unused_mut, unused_variables)]
|
||||
use crate::path::Path;
|
||||
use core::{
|
||||
cmp::Ordering::{self, Equal},
|
||||
fmt,
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use std::collections::BinaryHeap;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct PathEntry<S> {
|
||||
// cost so far + heursitic
|
||||
cost_estimate: f32,
|
||||
node: S,
|
||||
}
|
||||
|
||||
impl<S: Eq> PartialEq for PathEntry<S> {
|
||||
fn eq(&self, other: &PathEntry<S>) -> bool { self.node.eq(&other.node) }
|
||||
}
|
||||
|
||||
impl<S: Eq> Eq for PathEntry<S> {}
|
||||
|
||||
impl<S: Eq> Ord for PathEntry<S> {
|
||||
// This method implements reverse ordering, so that the lowest cost
|
||||
// will be ordered first
|
||||
fn cmp(&self, other: &PathEntry<S>) -> Ordering {
|
||||
other
|
||||
.cost_estimate
|
||||
.partial_cmp(&self.cost_estimate)
|
||||
.unwrap_or(Equal)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Eq> PartialOrd for PathEntry<S> {
|
||||
fn partial_cmp(&self, other: &PathEntry<S>) -> Option<Ordering> { Some(self.cmp(other)) }
|
||||
|
||||
// This is particularily hot in `BinaryHeap::pop`, so we provide this
|
||||
// implementation.
|
||||
//
|
||||
// NOTE: This probably doesn't handle edge cases like `NaNs` in a consistent
|
||||
// manner with `Ord`, but I don't think we need to care about that here(?)
|
||||
//
|
||||
// See note about reverse ordering above.
|
||||
fn le(&self, other: &PathEntry<S>) -> bool { other.cost_estimate <= self.cost_estimate }
|
||||
}
|
||||
|
||||
pub enum PathResult<T> {
|
||||
None(Path<T>),
|
||||
Exhausted(Path<T>),
|
||||
Path(Path<T>),
|
||||
Pending,
|
||||
}
|
||||
|
||||
impl<T> PathResult<T> {
|
||||
pub fn into_path(self) -> Option<Path<T>> {
|
||||
match self {
|
||||
PathResult::Path(path) => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map<U>(self, f: impl FnOnce(Path<T>) -> Path<U>) -> PathResult<U> {
|
||||
match self {
|
||||
PathResult::None(p) => PathResult::None(f(p)),
|
||||
PathResult::Exhausted(p) => PathResult::Exhausted(f(p)),
|
||||
PathResult::Path(p) => PathResult::Path(f(p)),
|
||||
PathResult::Pending => PathResult::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If node entry exists, this was visited!
|
||||
#[derive(Clone, Debug)]
|
||||
struct NodeEntry<S> {
|
||||
// if came_from == self this is the start node!
|
||||
came_from: S,
|
||||
cheapest_score: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Astar<S, Hasher> {
|
||||
iter: usize,
|
||||
max_iters: usize,
|
||||
potential_nodes: BinaryHeap<PathEntry<S>>, // cost, node pairs
|
||||
visited_nodes: HashMap<S, NodeEntry<S>, Hasher>,
|
||||
cheapest_node: Option<S>,
|
||||
cheapest_cost: Option<f32>,
|
||||
}
|
||||
|
||||
/// NOTE: Must manually derive since Hasher doesn't implement it.
|
||||
impl<S: Clone + Eq + Hash + fmt::Debug, H: BuildHasher> fmt::Debug for Astar<S, H> {
|
||||
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { todo!() }
|
||||
}
|
||||
|
||||
impl<S: Clone + Eq + Hash, H: BuildHasher + Clone> Astar<S, H> {
|
||||
pub fn new(max_iters: usize, start: S, hasher: H) -> Self {
|
||||
Self {
|
||||
max_iters,
|
||||
iter: 0,
|
||||
potential_nodes: core::iter::once(PathEntry {
|
||||
cost_estimate: 0.0,
|
||||
node: start.clone(),
|
||||
})
|
||||
.collect(),
|
||||
visited_nodes: {
|
||||
let mut s = HashMap::with_capacity_and_hasher(1, hasher.clone());
|
||||
s.extend(core::iter::once((start.clone(), NodeEntry {
|
||||
came_from: start,
|
||||
cheapest_score: 0.0,
|
||||
})));
|
||||
s
|
||||
},
|
||||
cheapest_node: None,
|
||||
cheapest_cost: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll<I>(
|
||||
&mut self,
|
||||
iters: usize,
|
||||
// Estimate how far we are from the target? but we are given two nodes...
|
||||
// (current, previous)
|
||||
mut heuristic: impl FnMut(&S, &S) -> f32,
|
||||
// get neighboring nodes
|
||||
mut neighbors: impl FnMut(&S) -> I,
|
||||
// have we reached target?
|
||||
mut satisfied: impl FnMut(&S) -> bool,
|
||||
) -> PathResult<S>
|
||||
where
|
||||
I: Iterator<Item = (S, f32)>, // (node, transition cost)
|
||||
{
|
||||
let iter_limit = self.max_iters.min(self.iter + iters);
|
||||
while self.iter < iter_limit {
|
||||
if let Some(PathEntry { node, .. }) = self.potential_nodes.pop() {
|
||||
if satisfied(&node) {
|
||||
return PathResult::Path(self.reconstruct_path_to(node));
|
||||
} else {
|
||||
// we have to fetch this even though it was put into the priority queue
|
||||
let (node_cheapest, came_from) = self
|
||||
.visited_nodes
|
||||
.get(&node)
|
||||
.map(|n| (n.cheapest_score, n.came_from.clone()))
|
||||
.unwrap();
|
||||
for (neighbor, transition) in neighbors(&node) {
|
||||
if neighbor == came_from {
|
||||
continue;
|
||||
}
|
||||
let neighbor_cheapest = self
|
||||
.visited_nodes
|
||||
.get(&neighbor)
|
||||
.map_or(f32::MAX, |n| n.cheapest_score);
|
||||
|
||||
// compute cost to traverse to each neighbor
|
||||
let cost = node_cheapest + transition;
|
||||
|
||||
if cost < neighbor_cheapest {
|
||||
let previously_visited = self
|
||||
.visited_nodes
|
||||
.insert(neighbor.clone(), NodeEntry {
|
||||
came_from: node.clone(),
|
||||
cheapest_score: cost,
|
||||
})
|
||||
.is_some();
|
||||
let h = heuristic(&neighbor, &node);
|
||||
// note that cheapest_scores does not include the heuristic
|
||||
// priority queue does include heuristic
|
||||
let cost_estimate = cost + h;
|
||||
|
||||
if self.cheapest_cost.map(|cc| h < cc).unwrap_or(true) {
|
||||
self.cheapest_node = Some(node.clone());
|
||||
self.cheapest_cost = Some(h);
|
||||
};
|
||||
|
||||
// TODO: I think the if here should be removed
|
||||
// if we hadn't already visted, add this to potential nodes, what about
|
||||
// its neighbors, wouldn't they need to be revisted???
|
||||
if !previously_visited {
|
||||
self.potential_nodes.push(PathEntry {
|
||||
cost_estimate,
|
||||
node: neighbor,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return PathResult::None(
|
||||
self.cheapest_node
|
||||
.clone()
|
||||
.map(|lc| self.reconstruct_path_to(lc))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
self.iter += 1
|
||||
}
|
||||
|
||||
if self.iter >= self.max_iters {
|
||||
PathResult::Exhausted(
|
||||
self.cheapest_node
|
||||
.clone()
|
||||
.map(|lc| self.reconstruct_path_to(lc))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
PathResult::Pending
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cheapest_cost(&self) -> Option<f32> { self.cheapest_cost }
|
||||
|
||||
fn reconstruct_path_to(&mut self, end: S) -> Path<S> {
|
||||
let mut path = vec![end.clone()];
|
||||
let mut cnode = &end;
|
||||
while let Some(node) = self
|
||||
.visited_nodes
|
||||
.get(cnode)
|
||||
.map(|n| &n.came_from)
|
||||
.filter(|n| *n != cnode)
|
||||
{
|
||||
path.push(node.clone());
|
||||
cnode = node;
|
||||
}
|
||||
path.into_iter().rev().collect()
|
||||
}
|
||||
}
|
@ -38,7 +38,6 @@ pub mod uid;
|
||||
// NOTE: Comment out macro to get rustfmt to re-order these as needed.
|
||||
cfg_if! { if #[cfg(not(target_arch = "wasm32"))] {
|
||||
pub mod astar;
|
||||
pub mod astar2;
|
||||
pub mod calendar;
|
||||
pub mod character;
|
||||
pub mod clock;
|
||||
|
@ -537,6 +537,17 @@ where
|
||||
};
|
||||
|
||||
let heuristic = |pos: &Vec3<i32>, _: &Vec3<i32>| (pos.distance_squared(end) as f32).sqrt();
|
||||
let transition = |a: Vec3<i32>, b: Vec3<i32>| {
|
||||
let crow_line = LineSegment2 {
|
||||
start: startf.xy(),
|
||||
end: endf.xy(),
|
||||
};
|
||||
|
||||
// Modify the heuristic a little in order to prefer paths that take us on a
|
||||
// straight line toward our target. This means we get smoother movement.
|
||||
1.0 + crow_line.distance_to_point(b.xy().map(|e| e as f32)) * 0.025
|
||||
+ (b.z - a.z - 1).max(0) as f32 * 10.0
|
||||
};
|
||||
let neighbors = |pos: &Vec3<i32>| {
|
||||
let pos = *pos;
|
||||
const DIRS: [Vec3<i32>; 17] = [
|
||||
@ -616,7 +627,10 @@ where
|
||||
.map(|b| !b.is_solid())
|
||||
.unwrap_or(true)))
|
||||
})
|
||||
.map(move |(pos, dir)| pos + dir)
|
||||
.map(|(pos, dir)| {
|
||||
let destination = pos + dir;
|
||||
(destination, transition(pos, destination))
|
||||
})
|
||||
// .chain(
|
||||
// DIAGONALS
|
||||
// .iter()
|
||||
@ -627,17 +641,6 @@ where
|
||||
// )
|
||||
};
|
||||
|
||||
let transition = |a: &Vec3<i32>, b: &Vec3<i32>| {
|
||||
let crow_line = LineSegment2 {
|
||||
start: startf.xy(),
|
||||
end: endf.xy(),
|
||||
};
|
||||
|
||||
// Modify the heuristic a little in order to prefer paths that take us on a
|
||||
// straight line toward our target. This means we get smoother movement.
|
||||
1.0 + crow_line.distance_to_point(b.xy().map(|e| e as f32)) * 0.025
|
||||
+ (b.z - a.z - 1).max(0) as f32 * 10.0
|
||||
};
|
||||
let satisfied = |pos: &Vec3<i32>| pos == &end;
|
||||
|
||||
let mut new_astar = match astar.take() {
|
||||
@ -645,7 +648,7 @@ where
|
||||
Some(astar) => astar,
|
||||
};
|
||||
|
||||
let path_result = new_astar.poll(100, heuristic, neighbors, transition, satisfied);
|
||||
let path_result = new_astar.poll(100, heuristic, neighbors, satisfied);
|
||||
|
||||
*astar = Some(new_astar);
|
||||
|
||||
|
@ -942,6 +942,12 @@ pub fn handle_manipulate_loadout(
|
||||
BuildHasherDefault::<FxHasher64>::default(),
|
||||
);
|
||||
|
||||
// Transition uses manhattan distance as the cost, with a slightly lower cost
|
||||
// for z transitions
|
||||
let transition = |a: Vec3<i32>, b: Vec3<i32>| {
|
||||
let (a, b) = (a.map(|x| x as f32), b.map(|x| x as f32));
|
||||
((a - b) * Vec3::new(1.0, 1.0, 0.9)).map(|e| e.abs()).sum()
|
||||
};
|
||||
// Neighbors are all neighboring blocks that are air
|
||||
let neighbors = |pos: &Vec3<i32>| {
|
||||
const DIRS: [Vec3<i32>; 6] = [
|
||||
@ -953,24 +959,23 @@ pub fn handle_manipulate_loadout(
|
||||
Vec3::new(0, 0, -1),
|
||||
];
|
||||
let pos = *pos;
|
||||
DIRS.iter().map(move |dir| dir + pos).filter(|pos| {
|
||||
data.terrain
|
||||
.get(*pos)
|
||||
.ok()
|
||||
.map_or(false, |block| !block.is_filled())
|
||||
})
|
||||
};
|
||||
// Transition uses manhattan distance as the cost, with a slightly lower cost
|
||||
// for z transitions
|
||||
let transition = |a: &Vec3<i32>, b: &Vec3<i32>| {
|
||||
let (a, b) = (a.map(|x| x as f32), b.map(|x| x as f32));
|
||||
((a - b) * Vec3::new(1.0, 1.0, 0.9)).map(|e| e.abs()).sum()
|
||||
DIRS.iter()
|
||||
.map(move |dir| {
|
||||
let dest = dir + pos;
|
||||
(dest, transition(pos, dest))
|
||||
})
|
||||
.filter(|(pos, _)| {
|
||||
data.terrain
|
||||
.get(*pos)
|
||||
.ok()
|
||||
.map_or(false, |block| !block.is_filled())
|
||||
})
|
||||
};
|
||||
// Pathing satisfied when it reaches the sprite position
|
||||
let satisfied = |pos: &Vec3<i32>| *pos == sprite_pos;
|
||||
|
||||
let not_blocked_by_terrain = astar
|
||||
.poll(iters, heuristic, neighbors, transition, satisfied)
|
||||
.poll(iters, heuristic, neighbors, satisfied)
|
||||
.into_path()
|
||||
.is_some();
|
||||
|
||||
|
@ -52,10 +52,10 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
||||
let heuristic = |tile: &Vec2<i32>, _: &Vec2<i32>| tile.as_::<f32>().distance(end.as_());
|
||||
let mut astar = Astar::new(1000, start, BuildHasherDefault::<FxHasher64>::default());
|
||||
|
||||
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| {
|
||||
let transition = |a: Vec2<i32>, b: Vec2<i32>| {
|
||||
let distance = a.as_::<f32>().distance(b.as_());
|
||||
let a_tile = site.tiles.get(*a);
|
||||
let b_tile = site.tiles.get(*b);
|
||||
let a_tile = site.tiles.get(a);
|
||||
let b_tile = site.tiles.get(b);
|
||||
|
||||
let terrain = match &b_tile.kind {
|
||||
TileKind::Empty => 3.0,
|
||||
@ -79,12 +79,12 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
||||
let building = if a_tile.is_building() && b_tile.is_road() {
|
||||
a_tile
|
||||
.plot
|
||||
.and_then(|plot| is_door_tile(plot, *a).then_some(1.0))
|
||||
.and_then(|plot| is_door_tile(plot, a).then_some(1.0))
|
||||
.unwrap_or(10000.0)
|
||||
} else if b_tile.is_building() && a_tile.is_road() {
|
||||
b_tile
|
||||
.plot
|
||||
.and_then(|plot| is_door_tile(plot, *b).then_some(1.0))
|
||||
.and_then(|plot| is_door_tile(plot, b).then_some(1.0))
|
||||
.unwrap_or(10000.0)
|
||||
} else if (a_tile.is_building() || b_tile.is_building()) && a_tile.plot != b_tile.plot {
|
||||
10000.0
|
||||
@ -97,10 +97,13 @@ fn path_in_site(start: Vec2<i32>, end: Vec2<i32>, site: &site2::Site) -> PathRes
|
||||
|
||||
let neighbors = |tile: &Vec2<i32>| {
|
||||
let tile = *tile;
|
||||
CARDINALS.iter().map(move |c| tile + *c)
|
||||
CARDINALS.iter().map(move |c| {
|
||||
let n = tile + *c;
|
||||
(n, transition(tile, n))
|
||||
})
|
||||
};
|
||||
|
||||
astar.poll(1000, heuristic, neighbors, transition, |tile| {
|
||||
astar.poll(1000, heuristic, neighbors, |tile| {
|
||||
*tile == end || site.tiles.get_known(*tile).is_none()
|
||||
})
|
||||
}
|
||||
@ -135,17 +138,22 @@ fn path_between_sites(
|
||||
|
||||
let mut astar = Astar::new(250, start, BuildHasherDefault::<FxHasher64>::default());
|
||||
|
||||
let neighbors = |site: &Id<civ::Site>| world.civs().neighbors(*site);
|
||||
|
||||
let transition = |a: &Id<civ::Site>, b: &Id<civ::Site>| {
|
||||
let transition = |a: Id<civ::Site>, b: Id<civ::Site>| {
|
||||
world
|
||||
.civs()
|
||||
.track_between(*a, *b)
|
||||
.track_between(a, b)
|
||||
.map(|(id, _)| world.civs().tracks.get(id).cost)
|
||||
.unwrap_or(f32::INFINITY)
|
||||
};
|
||||
let neighbors = |site: &Id<civ::Site>| {
|
||||
let site = *site;
|
||||
world
|
||||
.civs()
|
||||
.neighbors(site)
|
||||
.map(move |n| (n, transition(n, site)))
|
||||
};
|
||||
|
||||
let path = astar.poll(250, heuristic, neighbors, transition, |site| *site == end);
|
||||
let path = astar.poll(250, heuristic, neighbors, |site| *site == end);
|
||||
|
||||
path.map(|path| {
|
||||
let path = path
|
||||
|
@ -681,9 +681,12 @@ impl Civs {
|
||||
.distance_squared(self.sites.get(b).center) as f32)
|
||||
.sqrt()
|
||||
};
|
||||
let neighbors = |p: &Id<Site>| self.neighbors(*p);
|
||||
let transition = |a: &Id<Site>, b: &Id<Site>| {
|
||||
self.tracks.get(self.track_between(*a, *b).unwrap().0).cost
|
||||
let transition =
|
||||
|a: Id<Site>, b: Id<Site>| self.tracks.get(self.track_between(a, b).unwrap().0).cost;
|
||||
let neighbors = |p: &Id<Site>| {
|
||||
let p = *p;
|
||||
self.neighbors(p)
|
||||
.map(move |neighbor| (neighbor, transition(p, neighbor)))
|
||||
};
|
||||
let satisfied = |p: &Id<Site>| *p == b;
|
||||
// We use this hasher (FxHasher64) because
|
||||
@ -692,7 +695,7 @@ impl Civs {
|
||||
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||
let mut astar = Astar::new(100, a, BuildHasherDefault::<FxHasher64>::default());
|
||||
astar
|
||||
.poll(100, heuristic, neighbors, transition, satisfied)
|
||||
.poll(100, heuristic, neighbors, satisfied)
|
||||
.into_path()
|
||||
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
|
||||
}
|
||||
@ -1349,7 +1352,7 @@ fn find_path(
|
||||
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||
// (2) we care about determinism across computers (ruling out AAHash);
|
||||
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||
let mut astar = common::astar2::Astar::new(
|
||||
let mut astar = Astar::new(
|
||||
MAX_PATH_ITERS,
|
||||
a,
|
||||
BuildHasherDefault::<FxHasher64>::default(),
|
||||
|
@ -1347,15 +1347,19 @@ impl Land {
|
||||
&self,
|
||||
origin: Vec2<i32>,
|
||||
dest: Vec2<i32>,
|
||||
mut path_cost_fn: impl FnMut(Option<&Tile>, Option<&Tile>) -> f32,
|
||||
path_cost_fn: impl Fn(Option<&Tile>, Option<&Tile>) -> f32,
|
||||
) -> Option<Path<Vec2<i32>>> {
|
||||
let heuristic = |pos: &Vec2<i32>, _: &Vec2<i32>| (pos - dest).map(|e| e as f32).magnitude();
|
||||
let transition =
|
||||
|from: Vec2<i32>, to: Vec2<i32>| path_cost_fn(self.tile_at(from), self.tile_at(to));
|
||||
let neighbors = |pos: &Vec2<i32>| {
|
||||
let pos = *pos;
|
||||
CARDINALS.iter().map(move |dir| pos + *dir)
|
||||
let transition = &transition;
|
||||
CARDINALS.iter().map(move |dir| {
|
||||
let to = pos + *dir;
|
||||
(to, transition(pos, to))
|
||||
})
|
||||
};
|
||||
let transition =
|
||||
|from: &Vec2<i32>, to: &Vec2<i32>| path_cost_fn(self.tile_at(*from), self.tile_at(*to));
|
||||
let satisfied = |pos: &Vec2<i32>| *pos == dest;
|
||||
|
||||
// We use this hasher (FxHasher64) because
|
||||
@ -1363,7 +1367,7 @@ impl Land {
|
||||
// (2) we don't care about determinism across computers (we could use AAHash);
|
||||
// (3) we have 8-byte keys (for which FxHash is fastest).
|
||||
Astar::new(250, origin, BuildHasherDefault::<FxHasher64>::default())
|
||||
.poll(250, heuristic, neighbors, transition, satisfied)
|
||||
.poll(250, heuristic, neighbors, satisfied)
|
||||
.into_path()
|
||||
}
|
||||
|
||||
|
@ -166,12 +166,17 @@ impl Site {
|
||||
&heuristic,
|
||||
|(tile, _)| {
|
||||
let tile = *tile;
|
||||
CARDINALS.iter().map(move |dir| (tile + *dir, *dir))
|
||||
},
|
||||
|(a, _), (b, _)| {
|
||||
let alt_a = land.get_alt_approx(self.tile_center_wpos(*a));
|
||||
let alt_b = land.get_alt_approx(self.tile_center_wpos(*b));
|
||||
(alt_a - alt_b).abs() / TILE_SIZE as f32
|
||||
let this = &self;
|
||||
CARDINALS.iter().map(move |dir| {
|
||||
let neighbor = (tile + *dir, *dir);
|
||||
|
||||
// Transition cost
|
||||
let alt_a = land.get_alt_approx(this.tile_center_wpos(tile));
|
||||
let alt_b = land.get_alt_approx(this.tile_center_wpos(neighbor.0));
|
||||
let cost = (alt_a - alt_b).abs() / TILE_SIZE as f32;
|
||||
|
||||
(neighbor, cost)
|
||||
})
|
||||
},
|
||||
|(tile, _)| *tile == b,
|
||||
)
|
||||
|
@ -545,19 +545,22 @@ impl Floor {
|
||||
fn create_route(&mut self, _ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) {
|
||||
let heuristic =
|
||||
move |l: &Vec2<i32>, _: &Vec2<i32>| (l - b).map(|e| e.abs()).reduce_max() as f32;
|
||||
let neighbors = |l: &Vec2<i32>| {
|
||||
let l = *l;
|
||||
CARDINALS
|
||||
.iter()
|
||||
.map(move |dir| l + dir)
|
||||
.filter(|pos| self.tiles.get(*pos).is_some())
|
||||
};
|
||||
let transition = |_a: &Vec2<i32>, b: &Vec2<i32>| match self.tiles.get(*b) {
|
||||
let transition = |_a: Vec2<i32>, b: Vec2<i32>| match self.tiles.get(b) {
|
||||
Some(Tile::Room(_)) | Some(Tile::Tunnel) => 1.0,
|
||||
Some(Tile::Solid) => 25.0,
|
||||
Some(Tile::UpStair(_, _)) | Some(Tile::DownStair(_)) => 0.0,
|
||||
_ => 100000.0,
|
||||
};
|
||||
let neighbors = |l: &Vec2<i32>| {
|
||||
let l = *l;
|
||||
CARDINALS
|
||||
.iter()
|
||||
.map(move |dir| {
|
||||
let dest = l + dir;
|
||||
(dest, transition(l, dest))
|
||||
})
|
||||
.filter(|&(pos, _)| self.tiles.get(pos).is_some())
|
||||
};
|
||||
let satisfied = |l: &Vec2<i32>| *l == b;
|
||||
// We use this hasher (FxHasher64) because
|
||||
// (1) we don't care about DDOS attacks (ruling out SipHash);
|
||||
@ -569,7 +572,6 @@ impl Floor {
|
||||
FLOOR_SIZE.product() as usize + 1,
|
||||
heuristic,
|
||||
neighbors,
|
||||
transition,
|
||||
satisfied,
|
||||
)
|
||||
.into_path()
|
||||
|
Loading…
Reference in New Issue
Block a user