abstreet/game/src/debug/floodfill.rs

133 lines
4.1 KiB
Rust
Raw Normal View History

2020-03-02 20:51:20 +03:00
use crate::app::App;
use crate::common::Colorer;
use crate::game::{State, Transition};
use crate::helpers::ID;
use crate::managed::WrappedComposite;
use ezgui::{Color, Composite, EventCtx, GfxCtx, Key, Line, Outcome, Text};
use map_model::{connectivity, LaneID, Map, PathConstraints};
use std::collections::HashSet;
pub struct Floodfiller {
composite: Composite,
2020-01-03 19:30:02 +03:00
colorer: Colorer,
}
impl Floodfiller {
2020-03-02 20:51:20 +03:00
pub fn new(ctx: &mut EventCtx, app: &App) -> Option<Box<dyn State>> {
let map = &app.primary.map;
let (reachable_lanes, unreachable_lanes, title) =
2020-03-02 20:51:20 +03:00
if let Some(ID::Lane(l)) = app.primary.current_selection {
let lt = map.get_l(l).lane_type;
if !lt.supports_any_movement() {
return None;
}
2020-03-02 20:51:20 +03:00
if app.per_obj.action(ctx, Key::F, "floodfill from this lane") {
find_reachable_from(l, map)
2020-03-02 20:51:20 +03:00
} else if app
.per_obj
.action(ctx, Key::S, "show strongly-connected components")
{
let constraints = PathConstraints::from_lt(lt);
let (good, bad) = connectivity::find_scc(map, constraints);
(
good,
bad,
format!("strongly-connected components for {:?}", constraints),
)
} else {
return None;
}
2019-08-22 00:27:28 +03:00
} else {
return None;
};
2020-03-02 20:51:20 +03:00
let reachable_color = app.cs.get_def("reachable lane", Color::GREEN);
let unreachable_color = app.cs.get_def("unreachable lane", Color::RED);
2019-08-22 00:27:28 +03:00
let mut colorer = Colorer::new(
2019-12-17 01:50:21 +03:00
Text::from(Line("lane connectivity")),
vec![
("unreachable", unreachable_color),
("reachable", reachable_color),
],
);
for l in reachable_lanes {
2020-01-03 19:30:02 +03:00
colorer.add_l(l, reachable_color, map);
}
let num_unreachable = unreachable_lanes.len();
for l in unreachable_lanes {
2020-01-03 19:30:02 +03:00
colorer.add_l(l, unreachable_color, map);
println!("{} is unreachable", l);
}
2019-08-22 00:27:28 +03:00
Some(Box::new(Floodfiller {
composite: WrappedComposite::quick_menu(
ctx,
title,
vec![format!("{} unreachable lanes", num_unreachable)],
vec![],
),
2020-03-02 20:51:20 +03:00
colorer: colorer.build(ctx, app),
2019-08-22 00:27:28 +03:00
}))
}
}
impl State for Floodfiller {
2020-03-02 20:51:20 +03:00
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
if ctx.redo_mouseover() {
2020-03-02 20:51:20 +03:00
app.recalculate_current_selection(ctx);
}
ctx.canvas_movement();
match self.composite.event(ctx) {
Some(Outcome::Clicked(x)) => match x.as_ref() {
"X" => {
return Transition::Pop;
}
_ => unreachable!(),
},
None => {}
}
Transition::Keep
}
2020-03-02 20:51:20 +03:00
fn draw(&self, g: &mut GfxCtx, _: &App) {
self.colorer.draw(g);
self.composite.draw(g);
}
}
// (reachable, unreachable, a title)
fn find_reachable_from(start: LaneID, map: &Map) -> (HashSet<LaneID>, HashSet<LaneID>, String) {
let constraints = PathConstraints::from_lt(map.get_l(start).lane_type);
let mut visited = HashSet::new();
let mut queue = vec![start];
while !queue.is_empty() {
let current = queue.pop().unwrap();
if visited.contains(&current) {
continue;
}
visited.insert(current);
for turn in map.get_turns_for(current, constraints) {
if !visited.contains(&turn.id.dst) {
queue.push(turn.id.dst);
}
}
}
let mut unreached = HashSet::new();
for l in map.all_lanes() {
if constraints.can_use(l, map) && !visited.contains(&l.id) {
unreached.insert(l.id);
}
}
(
visited,
unreached,
format!("Floodfiller for {:?} from {}", constraints, start),
)
}