mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Better track routing for civsim
This commit is contained in:
parent
fe4418bc0d
commit
348003fc1a
@ -52,7 +52,8 @@ pub struct Astar<S: Clone + Eq + Hash> {
|
|||||||
cheapest_scores: HashMap<S, f32>,
|
cheapest_scores: HashMap<S, f32>,
|
||||||
final_scores: HashMap<S, f32>,
|
final_scores: HashMap<S, f32>,
|
||||||
visited: HashSet<S>,
|
visited: HashSet<S>,
|
||||||
lowest_cost: Option<S>,
|
cheapest_node: Option<S>,
|
||||||
|
cheapest_cost: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Clone + Eq + Hash> Astar<S> {
|
impl<S: Clone + Eq + Hash> Astar<S> {
|
||||||
@ -69,7 +70,8 @@ impl<S: Clone + Eq + Hash> Astar<S> {
|
|||||||
cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(),
|
cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(),
|
||||||
final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(),
|
final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(),
|
||||||
visited: std::iter::once(start).collect(),
|
visited: std::iter::once(start).collect(),
|
||||||
lowest_cost: None,
|
cheapest_node: None,
|
||||||
|
cheapest_cost: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,11 +88,12 @@ impl<S: Clone + Eq + Hash> Astar<S> {
|
|||||||
{
|
{
|
||||||
let iter_limit = self.max_iters.min(self.iter + iters);
|
let iter_limit = self.max_iters.min(self.iter + iters);
|
||||||
while self.iter < iter_limit {
|
while self.iter < iter_limit {
|
||||||
if let Some(PathEntry { node, .. }) = self.potential_nodes.pop() {
|
if let Some(PathEntry { node, cost }) = self.potential_nodes.pop() {
|
||||||
|
self.cheapest_cost = Some(cost);
|
||||||
if satisfied(&node) {
|
if satisfied(&node) {
|
||||||
return PathResult::Path(self.reconstruct_path_to(node));
|
return PathResult::Path(self.reconstruct_path_to(node));
|
||||||
} else {
|
} else {
|
||||||
self.lowest_cost = Some(node.clone());
|
self.cheapest_node = Some(node.clone());
|
||||||
for neighbor in neighbors(&node) {
|
for neighbor in neighbors(&node) {
|
||||||
let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX);
|
let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX);
|
||||||
let neighbor_cheapest =
|
let neighbor_cheapest =
|
||||||
@ -114,7 +117,7 @@ impl<S: Clone + Eq + Hash> Astar<S> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return PathResult::None(
|
return PathResult::None(
|
||||||
self.lowest_cost
|
self.cheapest_node
|
||||||
.clone()
|
.clone()
|
||||||
.map(|lc| self.reconstruct_path_to(lc))
|
.map(|lc| self.reconstruct_path_to(lc))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@ -126,7 +129,7 @@ impl<S: Clone + Eq + Hash> Astar<S> {
|
|||||||
|
|
||||||
if self.iter >= self.max_iters {
|
if self.iter >= self.max_iters {
|
||||||
PathResult::Exhausted(
|
PathResult::Exhausted(
|
||||||
self.lowest_cost
|
self.cheapest_node
|
||||||
.clone()
|
.clone()
|
||||||
.map(|lc| self.reconstruct_path_to(lc))
|
.map(|lc| self.reconstruct_path_to(lc))
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@ -136,6 +139,10 @@ impl<S: Clone + Eq + Hash> Astar<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_cheapest_cost(&self) -> Option<f32> {
|
||||||
|
self.cheapest_cost
|
||||||
|
}
|
||||||
|
|
||||||
fn reconstruct_path_to(&mut self, end: S) -> Path<S> {
|
fn reconstruct_path_to(&mut self, end: S) -> Path<S> {
|
||||||
let mut path = vec![end.clone()];
|
let mut path = vec![end.clone()];
|
||||||
let mut cnode = &end;
|
let mut cnode = &end;
|
||||||
|
@ -39,7 +39,8 @@ const INITIAL_CIV_COUNT: usize = 20;
|
|||||||
pub struct Civs {
|
pub struct Civs {
|
||||||
civs: Store<Civ>,
|
civs: Store<Civ>,
|
||||||
places: Store<Place>,
|
places: Store<Place>,
|
||||||
routes: HashMap<(Id<Place>, Id<Place>), Route>,
|
tracks: Store<Track>,
|
||||||
|
track_map: HashMap<Id<Place>, HashMap<Id<Place>, Id<Track>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GenCtx<'a, R: Rng> {
|
struct GenCtx<'a, R: Rng> {
|
||||||
@ -62,8 +63,8 @@ impl Civs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Temporary!
|
// Temporary!
|
||||||
for route in this.routes.values() {
|
for track in this.tracks.iter() {
|
||||||
for loc in route.path.iter() {
|
for loc in track.path.iter() {
|
||||||
sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland);
|
sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +72,35 @@ impl Civs {
|
|||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the direct track between two places
|
||||||
|
fn track_between(&self, a: Id<Place>, b: Id<Place>) -> Option<Id<Track>> {
|
||||||
|
self.track_map
|
||||||
|
.get(&a)
|
||||||
|
.and_then(|dests| dests.get(&b))
|
||||||
|
.or_else(|| self.track_map
|
||||||
|
.get(&b)
|
||||||
|
.and_then(|dests| dests.get(&a)))
|
||||||
|
.copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the cheapest route between two places
|
||||||
|
fn route_between(&self, a: Id<Place>, b: Id<Place>) -> Option<(Path<Id<Place>>, f32)> {
|
||||||
|
let heuristic = move |p: &Id<Place>| (self.places.get(*p).center.distance_squared(self.places.get(b).center) as f32).sqrt();
|
||||||
|
let neighbors = |p: &Id<Place>| {
|
||||||
|
let p = *p;
|
||||||
|
let to = self.track_map.get(&p).map(|dests| dests.keys()).into_iter().flatten();
|
||||||
|
let fro = self.track_map.iter().filter(move |(_, dests)| dests.contains_key(&p)).map(|(p, _)| p);
|
||||||
|
to.chain(fro).filter(|p| **p != a).copied()
|
||||||
|
};
|
||||||
|
let transition = |a: &Id<Place>, b: &Id<Place>| self.tracks.get(self.track_between(*a, *b).unwrap()).cost;
|
||||||
|
let satisfied = |p: &Id<Place>| *p == b;
|
||||||
|
let mut astar = Astar::new(100, a, heuristic);
|
||||||
|
astar
|
||||||
|
.poll(100, heuristic, neighbors, transition, satisfied)
|
||||||
|
.into_path()
|
||||||
|
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
|
||||||
|
}
|
||||||
|
|
||||||
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
|
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
|
||||||
const CIV_BIRTHPLACE_AREA: Range<usize> = 64..256;
|
const CIV_BIRTHPLACE_AREA: Range<usize> = 64..256;
|
||||||
let place = attempt(5, || {
|
let place = attempt(5, || {
|
||||||
@ -112,34 +142,39 @@ impl Civs {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let place = self.places.insert(Place {
|
||||||
|
center: loc,
|
||||||
|
});
|
||||||
|
|
||||||
// Find neighbors
|
// Find neighbors
|
||||||
const MAX_NEIGHBOR_DISTANCE: f32 = 100.0;
|
const MAX_NEIGHBOR_DISTANCE: f32 = 250.0;
|
||||||
let mut nearby = self.places
|
let mut nearby = self.places
|
||||||
.iter_ids()
|
.iter_ids()
|
||||||
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
|
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
|
||||||
.filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
|
.filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
nearby.sort_by_key(|(_, dist)| -*dist as i32);
|
nearby.sort_by_key(|(_, dist)| *dist as i32);
|
||||||
let route_count = ctx.rng.gen_range(1, 3);
|
|
||||||
let neighbors = nearby
|
|
||||||
.into_iter()
|
|
||||||
.map(|(p, _)| p)
|
|
||||||
.filter_map(|p| if let Some(path) = find_path(ctx, loc, self.places.get(p).center) {
|
|
||||||
Some((p, path))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.take(route_count)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let place = self.places.insert(Place {
|
for (nearby, _) in nearby.into_iter().take(ctx.rng.gen_range(3, 5)) {
|
||||||
center: loc,
|
// Find a novel path
|
||||||
neighbors: neighbors.iter().map(|(p, _)| *p).collect(),
|
if let Some((path, cost)) = find_path(ctx, loc, self.places.get(nearby).center) {
|
||||||
});
|
// Find a path using existing paths
|
||||||
|
if self
|
||||||
// Insert routes to neighbours into route list
|
.route_between(place, nearby)
|
||||||
for (p, path) in neighbors {
|
// If the novel path isn't efficient compared to existing routes, don't use it
|
||||||
self.routes.insert((place, p), Route { path });
|
.filter(|(_, route_cost)| *route_cost < cost * 3.0)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
let track = self.tracks.insert(Track {
|
||||||
|
cost,
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
self.track_map
|
||||||
|
.entry(place)
|
||||||
|
.or_default()
|
||||||
|
.insert(nearby, track);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write place to map
|
// Write place to map
|
||||||
@ -154,7 +189,7 @@ impl Civs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to find a path between two locations
|
/// Attempt to find a path between two locations
|
||||||
fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<Path<Vec2<i32>>> {
|
fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<(Path<Vec2<i32>>, f32)> {
|
||||||
let sim = &ctx.sim;
|
let sim = &ctx.sim;
|
||||||
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
|
let heuristic = move |l: &Vec2<i32>| (l.distance_squared(b) as f32).sqrt();
|
||||||
let neighbors = |l: &Vec2<i32>| {
|
let neighbors = |l: &Vec2<i32>| {
|
||||||
@ -163,9 +198,11 @@ fn find_path(ctx: &mut GenCtx<impl Rng>, a: Vec2<i32>, b: Vec2<i32>) -> Option<P
|
|||||||
};
|
};
|
||||||
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
|
let transition = |a: &Vec2<i32>, b: &Vec2<i32>| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0);
|
||||||
let satisfied = |l: &Vec2<i32>| *l == b;
|
let satisfied = |l: &Vec2<i32>| *l == b;
|
||||||
Astar::new(5000, a, heuristic)
|
let mut astar = Astar::new(20000, a, heuristic);
|
||||||
.poll(5000, heuristic, neighbors, transition, satisfied)
|
astar
|
||||||
|
.poll(20000, heuristic, neighbors, transition, satisfied)
|
||||||
.into_path()
|
.into_path()
|
||||||
|
.and_then(|path| astar.get_cheapest_cost().map(|cost| (path, cost)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if travel between a location and a chunk next to it is permitted (TODO: by whom?)
|
/// Return true if travel between a location and a chunk next to it is permitted (TODO: by whom?)
|
||||||
@ -175,7 +212,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2<i32>, dir: Vec2<i32>) -> Option<f32> {
|
|||||||
{
|
{
|
||||||
let a_alt = sim.get(a)?.alt;
|
let a_alt = sim.get(a)?.alt;
|
||||||
let b_alt = sim.get(a + dir)?.alt;
|
let b_alt = sim.get(a + dir)?.alt;
|
||||||
Some((b_alt - a_alt).max(0.0).powf(2.0).abs() / 50.0)
|
Some(0.5 + (b_alt - a_alt).max(-0.5).abs() / 5.0)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -240,9 +277,11 @@ pub struct Civ {
|
|||||||
|
|
||||||
pub struct Place {
|
pub struct Place {
|
||||||
center: Vec2<i32>,
|
center: Vec2<i32>,
|
||||||
neighbors: Vec<Id<Place>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Route {
|
pub struct Track {
|
||||||
|
/// Cost of using this track relative to other paths. This cost is an arbitrary unit and
|
||||||
|
/// doesn't make sense unless compared to other track costs.
|
||||||
|
cost: f32,
|
||||||
path: Path<Vec2<i32>>,
|
path: Path<Vec2<i32>>,
|
||||||
}
|
}
|
||||||
|
@ -65,8 +65,8 @@ use vek::*;
|
|||||||
// don't think we actually cast a chunk id to float, just coordinates... could
|
// don't think we actually cast a chunk id to float, just coordinates... could
|
||||||
// be wrong though!
|
// be wrong though!
|
||||||
pub const WORLD_SIZE: Vec2<usize> = Vec2 {
|
pub const WORLD_SIZE: Vec2<usize> = Vec2 {
|
||||||
x: 256 * 1,
|
x: 512 * 1,
|
||||||
y: 256 * 1,
|
y: 512 * 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A structure that holds cached noise values and cumulative distribution
|
/// A structure that holds cached noise values and cumulative distribution
|
||||||
|
Loading…
Reference in New Issue
Block a user