diff --git a/common/src/astar.rs b/common/src/astar.rs index 8bbb5bc7b4..3468c687ac 100644 --- a/common/src/astar.rs +++ b/common/src/astar.rs @@ -52,7 +52,8 @@ pub struct Astar { cheapest_scores: HashMap, final_scores: HashMap, visited: HashSet, - lowest_cost: Option, + cheapest_node: Option, + cheapest_cost: Option, } impl Astar { @@ -69,7 +70,8 @@ impl Astar { cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(), final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(), visited: std::iter::once(start).collect(), - lowest_cost: None, + cheapest_node: None, + cheapest_cost: None, } } @@ -86,11 +88,12 @@ impl Astar { { 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 let Some(PathEntry { node, cost }) = self.potential_nodes.pop() { + self.cheapest_cost = Some(cost); if satisfied(&node) { return PathResult::Path(self.reconstruct_path_to(node)); } else { - self.lowest_cost = Some(node.clone()); + self.cheapest_node = Some(node.clone()); for neighbor in neighbors(&node) { let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX); let neighbor_cheapest = @@ -114,7 +117,7 @@ impl Astar { } } else { return PathResult::None( - self.lowest_cost + self.cheapest_node .clone() .map(|lc| self.reconstruct_path_to(lc)) .unwrap_or_default(), @@ -126,7 +129,7 @@ impl Astar { if self.iter >= self.max_iters { PathResult::Exhausted( - self.lowest_cost + self.cheapest_node .clone() .map(|lc| self.reconstruct_path_to(lc)) .unwrap_or_default(), @@ -136,6 +139,10 @@ impl Astar { } } + pub fn get_cheapest_cost(&self) -> Option { + self.cheapest_cost + } + fn reconstruct_path_to(&mut self, end: S) -> Path { let mut path = vec![end.clone()]; let mut cnode = &end; diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 402506e31f..7b1183f9b2 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -39,7 +39,8 @@ const INITIAL_CIV_COUNT: usize = 20; pub struct Civs { civs: Store, places: Store, - routes: HashMap<(Id, Id), Route>, + tracks: Store, + track_map: HashMap, HashMap, Id>>, } struct GenCtx<'a, R: Rng> { @@ -62,8 +63,8 @@ impl Civs { } // Temporary! - for route in this.routes.values() { - for loc in route.path.iter() { + for track in this.tracks.iter() { + for loc in track.path.iter() { sim.get_mut(*loc).unwrap().place = Some(this.civs.iter().next().unwrap().homeland); } } @@ -71,6 +72,35 @@ impl Civs { this } + /// Return the direct track between two places + fn track_between(&self, a: Id, b: Id) -> Option> { + 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, b: Id) -> Option<(Path>, f32)> { + let heuristic = move |p: &Id| (self.places.get(*p).center.distance_squared(self.places.get(b).center) as f32).sqrt(); + let neighbors = |p: &Id| { + 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, b: &Id| self.tracks.get(self.track_between(*a, *b).unwrap()).cost; + let satisfied = |p: &Id| *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) -> Option> { const CIV_BIRTHPLACE_AREA: Range = 64..256; let place = attempt(5, || { @@ -112,34 +142,39 @@ impl Civs { return None; } + let place = self.places.insert(Place { + center: loc, + }); + // Find neighbors - const MAX_NEIGHBOR_DISTANCE: f32 = 100.0; + const MAX_NEIGHBOR_DISTANCE: f32 = 250.0; let mut nearby = self.places .iter_ids() .map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt())) .filter(|(p, dist)| *dist < MAX_NEIGHBOR_DISTANCE) .collect::>(); - 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::>(); + nearby.sort_by_key(|(_, dist)| *dist as i32); - let place = self.places.insert(Place { - center: loc, - neighbors: neighbors.iter().map(|(p, _)| *p).collect(), - }); - - // Insert routes to neighbours into route list - for (p, path) in neighbors { - self.routes.insert((place, p), Route { path }); + for (nearby, _) in nearby.into_iter().take(ctx.rng.gen_range(3, 5)) { + // Find a novel path + if let Some((path, cost)) = find_path(ctx, loc, self.places.get(nearby).center) { + // Find a path using existing paths + if self + .route_between(place, nearby) + // If the novel path isn't efficient compared to existing routes, don't use it + .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 @@ -154,7 +189,7 @@ impl Civs { } /// Attempt to find a path between two locations -fn find_path(ctx: &mut GenCtx, a: Vec2, b: Vec2) -> Option>> { +fn find_path(ctx: &mut GenCtx, a: Vec2, b: Vec2) -> Option<(Path>, f32)> { let sim = &ctx.sim; let heuristic = move |l: &Vec2| (l.distance_squared(b) as f32).sqrt(); let neighbors = |l: &Vec2| { @@ -163,9 +198,11 @@ fn find_path(ctx: &mut GenCtx, a: Vec2, b: Vec2) -> Option

, b: &Vec2| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0); let satisfied = |l: &Vec2| *l == b; - Astar::new(5000, a, heuristic) - .poll(5000, heuristic, neighbors, transition, satisfied) + let mut astar = Astar::new(20000, a, heuristic); + astar + .poll(20000, heuristic, neighbors, transition, satisfied) .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?) @@ -175,7 +212,7 @@ fn walk_in_dir(sim: &WorldSim, a: Vec2, dir: Vec2) -> Option { { let a_alt = sim.get(a)?.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 { None } @@ -240,9 +277,11 @@ pub struct Civ { pub struct Place { center: Vec2, - neighbors: Vec>, } -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>, } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index b12a2a07ce..22afd5ab35 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -65,8 +65,8 @@ use vek::*; // don't think we actually cast a chunk id to float, just coordinates... could // be wrong though! pub const WORLD_SIZE: Vec2 = Vec2 { - x: 256 * 1, - y: 256 * 1, + x: 512 * 1, + y: 512 * 1, }; /// A structure that holds cached noise values and cumulative distribution