mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-29 09:25:15 +03:00
Begin an LTN experiment. Starting from a single point, auto-discover the neighborhood and perimeter roads.
This commit is contained in:
parent
a5ed6e79fb
commit
37c1145df4
55
game/src/debug/ltn/algorithms.rs
Normal file
55
game/src/debug/ltn/algorithms.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use map_model::osm::RoadRank;
|
||||
use map_model::{Map, PathConstraints, RoadID};
|
||||
|
||||
use crate::debug::ltn::Neighborhood;
|
||||
|
||||
impl Neighborhood {
|
||||
// TODO Doesn't find the full perimeter. But do we really need that?
|
||||
pub fn from_road(map: &Map, start: RoadID) -> Neighborhood {
|
||||
// Do a simple floodfill from this road, stopping anytime we find a major road
|
||||
let mut interior = BTreeSet::new();
|
||||
let mut perimeter = BTreeSet::new();
|
||||
let mut borders = BTreeSet::new();
|
||||
|
||||
// We don't need a priority queue
|
||||
let mut visited = BTreeSet::new();
|
||||
let mut queue = vec![start];
|
||||
interior.insert(start);
|
||||
|
||||
while !queue.is_empty() {
|
||||
let current = map.get_r(queue.pop().unwrap());
|
||||
if visited.contains(¤t.id) {
|
||||
continue;
|
||||
}
|
||||
visited.insert(current.id);
|
||||
for i in [current.src_i, current.dst_i] {
|
||||
let (minor, major): (Vec<&RoadID>, Vec<&RoadID>) =
|
||||
map.get_i(i).roads.iter().partition(|r| {
|
||||
let road = map.get_r(**r);
|
||||
road.get_rank() == RoadRank::Local
|
||||
&& road
|
||||
.lanes
|
||||
.iter()
|
||||
.any(|l| PathConstraints::Car.can_use(l, map))
|
||||
});
|
||||
if major.is_empty() {
|
||||
for r in minor {
|
||||
interior.insert(*r);
|
||||
queue.push(*r);
|
||||
}
|
||||
} else {
|
||||
borders.insert(i);
|
||||
perimeter.extend(major);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Neighborhood {
|
||||
interior,
|
||||
perimeter,
|
||||
borders,
|
||||
}
|
||||
}
|
||||
}
|
101
game/src/debug/ltn/mod.rs
Normal file
101
game/src/debug/ltn/mod.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use map_gui::tools::ColorDiscrete;
|
||||
use map_gui::ID;
|
||||
use map_model::{IntersectionID, RoadID};
|
||||
use widgetry::{
|
||||
Color, Drawable, EventCtx, GfxCtx, HorizontalAlignment, Line, Panel, SimpleState, State,
|
||||
TextExt, VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use crate::app::{App, Transition};
|
||||
|
||||
mod algorithms;
|
||||
|
||||
pub struct Viewer {
|
||||
neighborhood: Neighborhood,
|
||||
draw: Drawable,
|
||||
}
|
||||
|
||||
struct Neighborhood {
|
||||
interior: BTreeSet<RoadID>,
|
||||
perimeter: BTreeSet<RoadID>,
|
||||
borders: BTreeSet<IntersectionID>,
|
||||
}
|
||||
|
||||
impl Viewer {
|
||||
pub fn start_from_road(ctx: &mut EventCtx, app: &App, start: RoadID) -> Box<dyn State<App>> {
|
||||
let neighborhood = Neighborhood::from_road(&app.primary.map, start);
|
||||
let (draw, legend) = neighborhood.render(ctx, app);
|
||||
|
||||
let panel = Panel::new_builder(Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Line("LTN tool").small_heading().into_widget(ctx),
|
||||
ctx.style().btn_close_widget(ctx),
|
||||
]),
|
||||
legend,
|
||||
"Click a road to re-center".text_widget(ctx),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.build(ctx);
|
||||
<dyn SimpleState<_>>::new_state(panel, Box::new(Viewer { neighborhood, draw }))
|
||||
}
|
||||
}
|
||||
|
||||
impl SimpleState<App> for Viewer {
|
||||
fn on_click(&mut self, _: &mut EventCtx, _: &mut App, x: &str, _: &Panel) -> Transition {
|
||||
match x {
|
||||
"close" => Transition::Pop,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouseover(&mut self, ctx: &mut EventCtx, app: &mut App) {
|
||||
app.primary.current_selection = match app.mouseover_unzoomed_roads_and_intersections(ctx) {
|
||||
x @ Some(ID::Road(_)) => x,
|
||||
Some(ID::Lane(l)) => Some(ID::Road(l.road)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
fn other_event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
ctx.canvas_movement();
|
||||
if let Some(ID::Road(r)) = app.primary.current_selection {
|
||||
if ctx.normal_left_click() {
|
||||
return Transition::Replace(Viewer::start_from_road(ctx, app, r));
|
||||
}
|
||||
}
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
g.redraw(&self.draw);
|
||||
}
|
||||
}
|
||||
|
||||
impl Neighborhood {
|
||||
// Also a legend
|
||||
fn render(&self, ctx: &mut EventCtx, app: &App) -> (Drawable, Widget) {
|
||||
let interior = Color::BLUE;
|
||||
let perimeter = Color::RED;
|
||||
let border = Color::CYAN;
|
||||
let mut colorer = ColorDiscrete::new(
|
||||
app,
|
||||
vec![
|
||||
("interior", interior),
|
||||
("perimeter", perimeter),
|
||||
("border", border),
|
||||
],
|
||||
);
|
||||
for r in &self.interior {
|
||||
colorer.add_r(*r, "interior");
|
||||
}
|
||||
for r in &self.perimeter {
|
||||
colorer.add_r(*r, "perimeter");
|
||||
}
|
||||
for i in &self.borders {
|
||||
colorer.add_i(*i, "border");
|
||||
}
|
||||
let (unzoomed, _, legend) = colorer.build(ctx);
|
||||
(unzoomed, legend)
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ pub use self::routes::PathCostDebugger;
|
||||
|
||||
mod blocked_by;
|
||||
mod floodfill;
|
||||
mod ltn;
|
||||
mod objects;
|
||||
pub mod path_counter;
|
||||
mod polygons;
|
||||
@ -610,6 +611,7 @@ impl ContextualActions for Actions {
|
||||
if cfg!(not(target_arch = "wasm32")) {
|
||||
actions.push((Key::M, "merge short segment".to_string()));
|
||||
}
|
||||
actions.push((Key::L, "LTN mode".to_string()));
|
||||
}
|
||||
ID::Intersection(i) => {
|
||||
actions.push((Key::H, "hide this".to_string()));
|
||||
@ -811,6 +813,9 @@ impl ContextualActions for Actions {
|
||||
abstio::write_json("merge_osm_ways.json".to_string(), &ways);
|
||||
Transition::Push(reimport_map(ctx, app, Some(orig_ways)))
|
||||
}
|
||||
(ID::Lane(l), "LTN mode") => {
|
||||
Transition::Push(ltn::Viewer::start_from_road(ctx, app, l.road))
|
||||
}
|
||||
(ID::Area(a), "debug area geometry") => {
|
||||
let pts = &app.primary.map.get_a(a).polygon.points();
|
||||
let center = if pts[0] == *pts.last().unwrap() {
|
||||
|
Loading…
Reference in New Issue
Block a user