diff --git a/map_model/src/map.rs b/map_model/src/map.rs index 1c3c5c1226..c05a19a711 100644 --- a/map_model/src/map.rs +++ b/map_model/src/map.rs @@ -55,6 +55,7 @@ impl Map { if path.starts_with(&abstutil::path_all_maps()) { match abstutil::maybe_read_binary(path.clone(), timer) { Ok(map) => { + crate::pathfind::uber_turns::find(&map); return map; } Err(err) => { diff --git a/map_model/src/pathfind/mod.rs b/map_model/src/pathfind/mod.rs index de4a3ff3e3..05a9ebcecd 100644 --- a/map_model/src/pathfind/mod.rs +++ b/map_model/src/pathfind/mod.rs @@ -1,5 +1,7 @@ mod driving; mod node_map; +// TODO tmp +pub mod uber_turns; mod walking; pub use self::driving::cost; diff --git a/map_model/src/pathfind/uber_turns.rs b/map_model/src/pathfind/uber_turns.rs new file mode 100644 index 0000000000..5774ff79e4 --- /dev/null +++ b/map_model/src/pathfind/uber_turns.rs @@ -0,0 +1,135 @@ +use crate::{IntersectionID, Map, TurnID}; +use petgraph::graphmap::UnGraphMap; +use std::collections::{HashMap, HashSet}; + +// This only applies to VehiclePathfinder; walking through these intersections is nothing special. +// TODO I haven't seen any cases yet with "interior" intersections. Some stuff might break. +pub struct IntersectionCluster { + pub members: HashSet, + pub uber_turns: Vec, +} + +pub struct UberTurn { + pub path: Vec, +} + +pub fn find(map: &Map) -> Vec { + let mut clusters = Vec::new(); + let mut graph: UnGraphMap = UnGraphMap::new(); + let mut all_restrictions = Vec::new(); + for from in map.all_roads() { + for (via, to) in &from.complicated_turn_restrictions { + // Each of these tells us 2 intersections to group together + let r = map.get_r(*via); + graph.add_edge(r.src_i, r.dst_i, ()); + all_restrictions.push((from.id, *via, *to)); + } + } + for intersections in petgraph::algo::kosaraju_scc(&graph) { + let members: HashSet = intersections.iter().cloned().collect(); + + // Find all entrances and exits through this group of intersections + let mut entrances = Vec::new(); + let mut exits = HashSet::new(); + for i in &intersections { + for turn in map.get_turns_in_intersection(*i) { + if turn.between_sidewalks() { + continue; + } + if !members.contains(&map.get_l(turn.id.src).src_i) { + entrances.push(turn.id); + } + if !members.contains(&map.get_l(turn.id.dst).dst_i) { + exits.insert(turn.id); + } + } + } + + // Find all paths between entrances and exits + let mut uber_turns = Vec::new(); + for entrance in entrances { + uber_turns.extend(flood(entrance, map, &exits)); + } + let orig_num = uber_turns.len(); + + // Filter out the restricted ones! + // TODO Could be more efficient, but eh + uber_turns.retain(|ut| { + let mut ok = true; + for pair in ut.path.windows(2) { + let r1 = map.get_l(pair[0].src).parent; + let r2 = map.get_l(pair[0].dst).parent; + let r3 = map.get_l(pair[1].dst).parent; + if all_restrictions.contains(&(r1, r2, r3)) { + ok = false; + break; + } + } + ok + }); + + println!( + "Cluster {:?} has {} uber-turns ({} filtered out):", + members, + uber_turns.len(), + orig_num - uber_turns.len() + ); + /*for ut in &uber_turns { + print!("- {}", ut.path[0].src); + for t in &ut.path { + print!("-> {}", t.dst); + } + println!(""); + }*/ + + clusters.push(IntersectionCluster { + members, + uber_turns, + }); + } + + clusters +} + +fn flood(start: TurnID, map: &Map, exits: &HashSet) -> Vec { + if exits.contains(&start) { + return vec![UberTurn { path: vec![start] }]; + } + + let mut results = Vec::new(); + let mut preds: HashMap = HashMap::new(); + let mut queue = vec![start]; + + while !queue.is_empty() { + let current = queue.pop().unwrap(); + for next in map.get_turns_from_lane(current.dst) { + if preds.contains_key(&next.id) { + continue; + } + preds.insert(next.id, current); + if exits.contains(&next.id) { + results.push(UberTurn { + path: trace_back(next.id, &preds), + }); + } else { + queue.push(next.id); + } + } + } + + results +} + +fn trace_back(end: TurnID, preds: &HashMap) -> Vec { + let mut path = vec![end]; + let mut current = end; + loop { + if let Some(prev) = preds.get(¤t) { + path.push(*prev); + current = *prev; + } else { + path.reverse(); + return path; + } + } +} diff --git a/release/build.sh b/release/build.sh index 4380417dc2..f3f86f00ff 100755 --- a/release/build.sh +++ b/release/build.sh @@ -15,7 +15,7 @@ cp docs/INSTRUCTIONS.md $output cp release/$runner $output mkdir $output/game cp $binary $output/game -cp -Rv data $output/data +cp -Rv data/system $output/data/system # TODO Github will double-zip this, but if we just pass the directory, then the # chmod +x bits get lost