mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 15:02:59 +03:00
Browse all neighborhoods for the LTN tool [rebuild] [release]
This commit is contained in:
parent
7fccf09f45
commit
ceed02b7d4
@ -420,7 +420,7 @@ fn finish_app_setup(
|
||||
let layers = ungap::Layers::new(ctx, app);
|
||||
vec![ungap::ExploreMap::new_state(ctx, app, layers)]
|
||||
} else if setup.ltn {
|
||||
vec![ltn::Viewer::start_anywhere(ctx, app)]
|
||||
vec![ltn::BrowseNeighborhoods::new_state(ctx, app)]
|
||||
} else {
|
||||
// Not attempting to keep the primary and secondary simulations synchronized at the same
|
||||
// time yet. Just handle this one startup case, so we can switch maps without constantly
|
||||
|
@ -45,16 +45,29 @@ impl Neighborhood {
|
||||
}
|
||||
}
|
||||
|
||||
let mut n = Neighborhood {
|
||||
Neighborhood {
|
||||
interior,
|
||||
perimeter,
|
||||
borders,
|
||||
|
||||
modal_filters: BTreeSet::new(),
|
||||
rat_runs: Vec::new(),
|
||||
};
|
||||
n.rat_runs = n.find_rat_runs(map);
|
||||
n
|
||||
}
|
||||
}
|
||||
|
||||
// Just finds a sampling of rat runs, not necessarily all of them
|
||||
pub fn calculate_rat_runs(&mut self, map: &Map) {
|
||||
// Just flood from each border and see if we can reach another border.
|
||||
//
|
||||
// We might be able to do this in one pass, seeding the queue with all borders. But I think
|
||||
// the "visited" bit would get tangled up between different possibilities...
|
||||
self.rat_runs = self
|
||||
.borders
|
||||
.iter()
|
||||
.flat_map(|i| self.rat_run_from(map, *i))
|
||||
.collect();
|
||||
self.rat_runs
|
||||
.sort_by(|a, b| a.length_ratio.partial_cmp(&b.length_ratio).unwrap());
|
||||
}
|
||||
|
||||
pub fn toggle_modal_filter(&mut self, map: &Map, r: RoadID) {
|
||||
@ -63,7 +76,7 @@ impl Neighborhood {
|
||||
} else {
|
||||
self.modal_filters.insert(r);
|
||||
}
|
||||
self.rat_runs = self.find_rat_runs(map);
|
||||
self.calculate_rat_runs(map);
|
||||
}
|
||||
|
||||
pub fn is_interior_road(r: RoadID, map: &Map) -> bool {
|
||||
@ -75,21 +88,6 @@ impl Neighborhood {
|
||||
.any(|l| PathConstraints::Car.can_use(l, map))
|
||||
}
|
||||
|
||||
// Just returns a sampling of rat runs, not necessarily all of them
|
||||
fn find_rat_runs(&self, map: &Map) -> Vec<RatRun> {
|
||||
// Just flood from each border and see if we can reach another border.
|
||||
//
|
||||
// We might be able to do this in one pass, seeding the queue with all borders. But I think
|
||||
// the "visited" bit would get tangled up between different possibilities...
|
||||
let mut runs: Vec<RatRun> = self
|
||||
.borders
|
||||
.iter()
|
||||
.flat_map(|i| self.rat_run_from(map, *i))
|
||||
.collect();
|
||||
runs.sort_by(|a, b| a.length_ratio.partial_cmp(&b.length_ratio).unwrap());
|
||||
runs
|
||||
}
|
||||
|
||||
fn rat_run_from(&self, map: &Map, start: IntersectionID) -> Option<RatRun> {
|
||||
// We don't need a priority queue
|
||||
let mut back_refs = HashMap::new();
|
||||
|
125
game/src/ltn/browse.rs
Normal file
125
game/src/ltn/browse.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use map_gui::tools::{nice_map_name, CityPicker};
|
||||
use map_gui::ID;
|
||||
use widgetry::{
|
||||
lctrl, Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Outcome,
|
||||
Panel, State, TextExt, VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use super::{Neighborhood, Viewer};
|
||||
use crate::app::{App, Transition};
|
||||
use crate::common::intersections_from_roads;
|
||||
|
||||
pub struct BrowseNeighborhoods {
|
||||
panel: Panel,
|
||||
draw_neighborhoods: Drawable,
|
||||
}
|
||||
|
||||
impl BrowseNeighborhoods {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
|
||||
let panel = Panel::new_builder(Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Line("LTN tool").small_heading().into_widget(ctx),
|
||||
ctx.style()
|
||||
.btn_popup_icon_text(
|
||||
"system/assets/tools/map.svg",
|
||||
nice_map_name(app.primary.map.get_name()),
|
||||
)
|
||||
.hotkey(lctrl(Key::L))
|
||||
.build_widget(ctx, "change map")
|
||||
.centered_vert()
|
||||
.align_right(),
|
||||
]),
|
||||
"Click a neighborhood".text_widget(ctx),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Left, VerticalAlignment::Top)
|
||||
.build(ctx);
|
||||
let draw_neighborhoods = calculate_neighborhoods(app).upload(ctx);
|
||||
Box::new(BrowseNeighborhoods {
|
||||
panel,
|
||||
draw_neighborhoods,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State<App> for BrowseNeighborhoods {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
ctx.canvas_movement();
|
||||
if ctx.redo_mouseover() {
|
||||
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,
|
||||
};
|
||||
}
|
||||
if let Some(ID::Road(r)) = app.primary.current_selection {
|
||||
if Neighborhood::is_interior_road(r, &app.primary.map) && ctx.normal_left_click() {
|
||||
return Transition::Replace(Viewer::start_from_road(ctx, app, r));
|
||||
}
|
||||
}
|
||||
|
||||
if let Outcome::Clicked(x) = self.panel.event(ctx) {
|
||||
match x.as_ref() {
|
||||
"change map" => {
|
||||
return Transition::Push(CityPicker::new_state(
|
||||
ctx,
|
||||
app,
|
||||
Box::new(|ctx, app| {
|
||||
Transition::Replace(BrowseNeighborhoods::new_state(ctx, app))
|
||||
}),
|
||||
));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
g.redraw(&self.draw_neighborhoods);
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_neighborhoods(app: &App) -> GeomBatch {
|
||||
let map = &app.primary.map;
|
||||
let mut unvisited = BTreeSet::new();
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
let colors = [
|
||||
Color::BLUE,
|
||||
Color::ORANGE,
|
||||
Color::PURPLE,
|
||||
Color::RED,
|
||||
Color::GREEN,
|
||||
Color::CYAN,
|
||||
];
|
||||
let mut num_neighborhoods = 0;
|
||||
|
||||
for r in map.all_roads() {
|
||||
if Neighborhood::is_interior_road(r.id, map) {
|
||||
unvisited.insert(r.id);
|
||||
}
|
||||
}
|
||||
|
||||
while !unvisited.is_empty() {
|
||||
let start = *unvisited.iter().next().unwrap();
|
||||
let neighborhood = Neighborhood::from_road(map, start);
|
||||
|
||||
// TODO Either use that 4-color theorem and actually guarantee no adjacent same-color ones,
|
||||
// or change the style to have a clear outline around each
|
||||
let color = colors[num_neighborhoods % colors.len()];
|
||||
num_neighborhoods += 1;
|
||||
for i in intersections_from_roads(&neighborhood.interior, map) {
|
||||
batch.push(color, map.get_i(i).polygon.clone());
|
||||
}
|
||||
for r in neighborhood.interior {
|
||||
batch.push(color, map.get_r(r).get_thick_polygon());
|
||||
unvisited.remove(&r);
|
||||
}
|
||||
}
|
||||
|
||||
batch
|
||||
}
|
@ -11,8 +11,10 @@ use widgetry::{
|
||||
|
||||
use crate::app::{App, Transition};
|
||||
use crate::common::intersections_from_roads;
|
||||
pub use browse::BrowseNeighborhoods;
|
||||
|
||||
mod algorithms;
|
||||
mod browse;
|
||||
|
||||
pub struct Viewer {
|
||||
panel: Panel,
|
||||
@ -42,20 +44,9 @@ struct RatRun {
|
||||
}
|
||||
|
||||
impl Viewer {
|
||||
pub fn start_anywhere(ctx: &mut EventCtx, app: &App) -> Box<dyn State<App>> {
|
||||
// Find some residential road to start on
|
||||
let r = app
|
||||
.primary
|
||||
.map
|
||||
.all_roads()
|
||||
.iter()
|
||||
.find(|r| Neighborhood::is_interior_road(r.id, &app.primary.map))
|
||||
.unwrap();
|
||||
Viewer::start_from_road(ctx, app, r.id)
|
||||
}
|
||||
|
||||
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 mut neighborhood = Neighborhood::from_road(&app.primary.map, start);
|
||||
neighborhood.calculate_rat_runs(&app.primary.map);
|
||||
let (draw_neighborhood, legend) = neighborhood.render(ctx, app);
|
||||
let panel = Panel::new_builder(Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
@ -70,6 +61,11 @@ impl Viewer {
|
||||
.centered_vert()
|
||||
.align_right(),
|
||||
]),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("Browse neighborhoods")
|
||||
.hotkey(Key::B)
|
||||
.build_def(ctx),
|
||||
legend,
|
||||
Text::new().into_widget(ctx).named("rat runs"),
|
||||
]))
|
||||
@ -195,7 +191,9 @@ impl State<App> for Viewer {
|
||||
return Transition::Push(CityPicker::new_state(
|
||||
ctx,
|
||||
app,
|
||||
Box::new(|ctx, app| Transition::Replace(Viewer::start_anywhere(ctx, app))),
|
||||
Box::new(|ctx, app| {
|
||||
Transition::Replace(BrowseNeighborhoods::new_state(ctx, app))
|
||||
}),
|
||||
));
|
||||
}
|
||||
"previous rat run" => {
|
||||
@ -206,6 +204,9 @@ impl State<App> for Viewer {
|
||||
self.current_rat_run_idx += 1;
|
||||
self.recalculate(ctx, app);
|
||||
}
|
||||
"Browse neighborhoods" => {
|
||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -263,8 +264,9 @@ impl Neighborhood {
|
||||
|
||||
impl RatRun {
|
||||
fn roads<'a>(&'a self, map: &'a Map) -> impl Iterator<Item = &'a Road> {
|
||||
// TODO Find the neighborhoods that aren't being defined right, instead of flat_map here
|
||||
self.path
|
||||
.windows(2)
|
||||
.map(move |pair| map.get_i(pair[0]).find_road_between(pair[1], map).unwrap())
|
||||
.flat_map(move |pair| map.get_i(pair[0]).find_road_between(pair[1], map))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user