mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-25 22:13:27 +03:00
tool to visualize uber-turns
This commit is contained in:
parent
ac459fd8e8
commit
e2e4ddab2f
@ -173,16 +173,12 @@ impl State for TurnExplorer {
|
||||
return Transition::Pop;
|
||||
}
|
||||
"previous turn" => {
|
||||
if self.idx != 0 {
|
||||
self.idx -= 1;
|
||||
self.composite = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
|
||||
}
|
||||
self.idx -= 1;
|
||||
self.composite = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
|
||||
}
|
||||
"next turn" => {
|
||||
if self.idx != app.primary.map.get_turns_from_lane(self.l).len() {
|
||||
self.idx += 1;
|
||||
self.composite = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
|
||||
}
|
||||
self.idx += 1;
|
||||
self.composite = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
@ -2,6 +2,7 @@ mod dashboards;
|
||||
pub mod gameplay;
|
||||
mod misc_tools;
|
||||
mod speed;
|
||||
mod uber_turns;
|
||||
|
||||
use self::misc_tools::{RoutePreview, ShowTrafficSignal, TurnExplorer};
|
||||
use crate::app::App;
|
||||
@ -384,6 +385,7 @@ impl ContextualActions for Actions {
|
||||
actions.push((Key::F, "explore traffic signal details".to_string()));
|
||||
actions.push((Key::C, "show current demand".to_string()));
|
||||
actions.push((Key::E, "edit traffic signal".to_string()));
|
||||
actions.push((Key::U, "explore uber-turns".to_string()));
|
||||
}
|
||||
if app.primary.map.get_i(i).is_stop_sign() {
|
||||
actions.push((Key::E, "edit stop sign".to_string()));
|
||||
@ -435,6 +437,9 @@ impl ContextualActions for Actions {
|
||||
Box::new(EditMode::new(ctx, app, self.gameplay.clone())),
|
||||
Box::new(StopSignEditor::new(i, ctx, app)),
|
||||
),
|
||||
(ID::Intersection(i), "explore uber-turns") => {
|
||||
Transition::Push(uber_turns::UberTurnPicker::new(ctx, app, i))
|
||||
}
|
||||
(ID::Lane(l), "explore turns from this lane") => {
|
||||
Transition::Push(TurnExplorer::new(ctx, app, l))
|
||||
}
|
||||
|
222
game/src/sandbox/uber_turns.rs
Normal file
222
game/src/sandbox/uber_turns.rs
Normal file
@ -0,0 +1,222 @@
|
||||
use crate::app::{App, ShowEverything};
|
||||
use crate::common::CommonState;
|
||||
use crate::game::{msg, DrawBaselayer, State, Transition};
|
||||
use crate::helpers::ID;
|
||||
use crate::render::{DrawOptions, BIG_ARROW_THICKNESS};
|
||||
use ezgui::{
|
||||
hotkey, Btn, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key,
|
||||
Line, Outcome, Text, VerticalAlignment, Widget,
|
||||
};
|
||||
use geom::{ArrowCap, Polygon};
|
||||
use map_model::{IntersectionCluster, IntersectionID};
|
||||
use sim::DontDrawAgents;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct UberTurnPicker {
|
||||
members: HashSet<IntersectionID>,
|
||||
composite: Composite,
|
||||
}
|
||||
|
||||
impl UberTurnPicker {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App, i: IntersectionID) -> Box<dyn State> {
|
||||
let mut members = HashSet::new();
|
||||
members.insert(i);
|
||||
Box::new(UberTurnPicker {
|
||||
members,
|
||||
composite: Composite::new(
|
||||
Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Line("Select multiple intersections")
|
||||
.small_heading()
|
||||
.draw(ctx),
|
||||
Btn::text_fg("X")
|
||||
.build_def(ctx, hotkey(Key::Escape))
|
||||
.align_right(),
|
||||
]),
|
||||
Btn::text_fg("View uber-turns").build_def(ctx, None),
|
||||
])
|
||||
.padding(10)
|
||||
.bg(app.cs.panel_bg),
|
||||
)
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State for UberTurnPicker {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
ctx.canvas_movement();
|
||||
if ctx.redo_mouseover() {
|
||||
app.recalculate_current_selection(ctx);
|
||||
}
|
||||
if let Some(ID::Intersection(i)) = app.primary.current_selection {
|
||||
if !self.members.contains(&i) && app.per_obj.left_click(ctx, "add this intersection") {
|
||||
self.members.insert(i);
|
||||
} else if self.members.contains(&i)
|
||||
&& app.per_obj.left_click(ctx, "remove this intersection")
|
||||
{
|
||||
self.members.remove(&i);
|
||||
}
|
||||
}
|
||||
|
||||
match self.composite.event(ctx) {
|
||||
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
||||
"X" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
"View uber-turns" => {
|
||||
if self.members.len() < 2 {
|
||||
return Transition::Push(msg(
|
||||
"Error",
|
||||
vec!["Select at least two intersections"],
|
||||
));
|
||||
}
|
||||
return Transition::Replace(UberTurnViewer::new(
|
||||
ctx,
|
||||
app,
|
||||
IntersectionCluster::new(self.members.clone(), &app.primary.map),
|
||||
0,
|
||||
));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
self.composite.draw(g);
|
||||
CommonState::draw_osd(g, app, &app.primary.current_selection);
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
for i in &self.members {
|
||||
batch.push(
|
||||
Color::RED.alpha(0.8),
|
||||
app.primary.map.get_i(*i).polygon.clone(),
|
||||
);
|
||||
}
|
||||
let draw = g.upload(batch);
|
||||
g.redraw(&draw);
|
||||
}
|
||||
}
|
||||
|
||||
struct UberTurnViewer {
|
||||
composite: Composite,
|
||||
draw: Drawable,
|
||||
ic: IntersectionCluster,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl UberTurnViewer {
|
||||
pub fn new(
|
||||
ctx: &mut EventCtx,
|
||||
app: &mut App,
|
||||
ic: IntersectionCluster,
|
||||
idx: usize,
|
||||
) -> Box<dyn State> {
|
||||
app.primary.current_selection = None;
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
for i in &ic.members {
|
||||
batch.push(
|
||||
Color::BLUE.alpha(0.5),
|
||||
app.primary.map.get_i(*i).polygon.clone(),
|
||||
);
|
||||
}
|
||||
batch.push(
|
||||
Color::RED,
|
||||
ic.uber_turns[idx]
|
||||
.geom(&app.primary.map)
|
||||
.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
Box::new(UberTurnViewer {
|
||||
draw: ctx.upload(batch),
|
||||
composite: Composite::new(
|
||||
Widget::col(vec![Widget::row(vec![
|
||||
Line("Uber-turn viewer").small_heading().draw(ctx).margin(5),
|
||||
Widget::draw_batch(
|
||||
ctx,
|
||||
GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, 50.0))]),
|
||||
)
|
||||
.margin(5),
|
||||
if idx == 0 {
|
||||
Btn::text_fg("<").inactive(ctx)
|
||||
} else {
|
||||
Btn::text_fg("<").build(ctx, "previous uber-turn", hotkey(Key::LeftArrow))
|
||||
}
|
||||
.margin(5),
|
||||
Text::from(Line(format!("{}/{}", idx, ic.uber_turns.len())).secondary())
|
||||
.draw(ctx)
|
||||
.margin(5)
|
||||
.centered_vert(),
|
||||
if idx == ic.uber_turns.len() - 1 {
|
||||
Btn::text_fg(">").inactive(ctx)
|
||||
} else {
|
||||
Btn::text_fg(">").build(ctx, "next uber-turn", hotkey(Key::RightArrow))
|
||||
}
|
||||
.margin(5),
|
||||
Btn::text_fg("X").build_def(ctx, hotkey(Key::Escape)),
|
||||
])])
|
||||
.padding(10)
|
||||
.bg(app.cs.panel_bg),
|
||||
)
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx),
|
||||
ic,
|
||||
idx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State for UberTurnViewer {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
ctx.canvas_movement();
|
||||
|
||||
match self.composite.event(ctx) {
|
||||
Some(Outcome::Clicked(x)) => match x.as_ref() {
|
||||
"X" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
"previous uber-turn" => {
|
||||
return Transition::Replace(UberTurnViewer::new(
|
||||
ctx,
|
||||
app,
|
||||
self.ic.clone(),
|
||||
self.idx - 1,
|
||||
));
|
||||
}
|
||||
"next uber-turn" => {
|
||||
return Transition::Replace(UberTurnViewer::new(
|
||||
ctx,
|
||||
app,
|
||||
self.ic.clone(),
|
||||
self.idx + 1,
|
||||
));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw_baselayer(&self) -> DrawBaselayer {
|
||||
DrawBaselayer::Custom
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
let mut opts = DrawOptions::new();
|
||||
opts.suppress_traffic_signal_details
|
||||
.extend(self.ic.members.clone());
|
||||
app.draw(g, opts, &DontDrawAgents {}, &ShowEverything::new());
|
||||
|
||||
self.composite.draw(g);
|
||||
g.redraw(&self.draw);
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ pub use crate::intersection::{Intersection, IntersectionID, IntersectionType};
|
||||
pub use crate::lane::{Lane, LaneID, LaneType, PARKING_SPOT_LENGTH};
|
||||
pub use crate::make::RoadSpec;
|
||||
pub use crate::map::Map;
|
||||
pub use crate::pathfind::uber_turns::{IntersectionCluster, UberTurn};
|
||||
pub use crate::pathfind::{Path, PathConstraints, PathRequest, PathStep};
|
||||
pub use crate::road::{DirectedRoadID, Road, RoadID};
|
||||
pub use crate::stop_signs::{ControlStopSign, RoadWithStopSign};
|
||||
|
@ -1,14 +1,17 @@
|
||||
use crate::{IntersectionID, Map, TurnID};
|
||||
use geom::PolyLine;
|
||||
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.
|
||||
#[derive(Clone)]
|
||||
pub struct IntersectionCluster {
|
||||
pub members: HashSet<IntersectionID>,
|
||||
pub uber_turns: Vec<UberTurn>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UberTurn {
|
||||
pub path: Vec<TurnID>,
|
||||
}
|
||||
@ -27,11 +30,43 @@ pub fn find(map: &Map) -> Vec<IntersectionCluster> {
|
||||
}
|
||||
for intersections in petgraph::algo::kosaraju_scc(&graph) {
|
||||
let members: HashSet<IntersectionID> = intersections.iter().cloned().collect();
|
||||
let mut ic = IntersectionCluster::new(members, map);
|
||||
|
||||
// Filter out the restricted ones!
|
||||
// TODO Could be more efficient, but eh
|
||||
let orig_num = ic.uber_turns.len();
|
||||
ic.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):",
|
||||
intersections,
|
||||
ic.uber_turns.len(),
|
||||
orig_num - ic.uber_turns.len()
|
||||
);
|
||||
|
||||
clusters.push(ic);
|
||||
}
|
||||
|
||||
clusters
|
||||
}
|
||||
|
||||
impl IntersectionCluster {
|
||||
pub fn new(members: HashSet<IntersectionID>, map: &Map) -> IntersectionCluster {
|
||||
// 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 i in &members {
|
||||
for turn in map.get_turns_in_intersection(*i) {
|
||||
if turn.between_sidewalks() {
|
||||
continue;
|
||||
@ -50,30 +85,6 @@ pub fn find(map: &Map) -> Vec<IntersectionCluster> {
|
||||
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 {
|
||||
@ -82,13 +93,11 @@ pub fn find(map: &Map) -> Vec<IntersectionCluster> {
|
||||
println!("");
|
||||
}*/
|
||||
|
||||
clusters.push(IntersectionCluster {
|
||||
IntersectionCluster {
|
||||
members,
|
||||
uber_turns,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clusters
|
||||
}
|
||||
|
||||
fn flood(start: TurnID, map: &Map, exits: &HashSet<TurnID>) -> Vec<UberTurn> {
|
||||
@ -133,3 +142,19 @@ fn trace_back(end: TurnID, preds: &HashMap<TurnID, TurnID>) -> Vec<TurnID> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UberTurn {
|
||||
pub fn geom(&self, map: &Map) -> PolyLine {
|
||||
let mut pl = map.get_t(self.path[0]).geom.clone();
|
||||
let mut first = true;
|
||||
for pair in self.path.windows(2) {
|
||||
if !first {
|
||||
pl = pl.extend(map.get_t(pair[0]).geom.clone());
|
||||
first = false;
|
||||
}
|
||||
pl = pl.extend(map.get_l(pair[0].dst).lane_center_pts.clone());
|
||||
pl = pl.extend(map.get_t(pair[1]).geom.clone());
|
||||
}
|
||||
pl
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user