mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-29 12:43:38 +03:00
tool to visualize uber-turns
This commit is contained in:
parent
ac459fd8e8
commit
e2e4ddab2f
@ -173,17 +173,13 @@ impl State for TurnExplorer {
|
|||||||
return Transition::Pop;
|
return Transition::Pop;
|
||||||
}
|
}
|
||||||
"previous turn" => {
|
"previous turn" => {
|
||||||
if self.idx != 0 {
|
|
||||||
self.idx -= 1;
|
self.idx -= 1;
|
||||||
self.composite = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
|
self.composite = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
"next turn" => {
|
"next turn" => {
|
||||||
if self.idx != app.primary.map.get_turns_from_lane(self.l).len() {
|
|
||||||
self.idx += 1;
|
self.idx += 1;
|
||||||
self.composite = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
|
self.composite = TurnExplorer::make_panel(ctx, app, self.l, self.idx);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
None => {}
|
None => {}
|
||||||
|
@ -2,6 +2,7 @@ mod dashboards;
|
|||||||
pub mod gameplay;
|
pub mod gameplay;
|
||||||
mod misc_tools;
|
mod misc_tools;
|
||||||
mod speed;
|
mod speed;
|
||||||
|
mod uber_turns;
|
||||||
|
|
||||||
use self::misc_tools::{RoutePreview, ShowTrafficSignal, TurnExplorer};
|
use self::misc_tools::{RoutePreview, ShowTrafficSignal, TurnExplorer};
|
||||||
use crate::app::App;
|
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::F, "explore traffic signal details".to_string()));
|
||||||
actions.push((Key::C, "show current demand".to_string()));
|
actions.push((Key::C, "show current demand".to_string()));
|
||||||
actions.push((Key::E, "edit traffic signal".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() {
|
if app.primary.map.get_i(i).is_stop_sign() {
|
||||||
actions.push((Key::E, "edit stop sign".to_string()));
|
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(EditMode::new(ctx, app, self.gameplay.clone())),
|
||||||
Box::new(StopSignEditor::new(i, ctx, app)),
|
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") => {
|
(ID::Lane(l), "explore turns from this lane") => {
|
||||||
Transition::Push(TurnExplorer::new(ctx, app, l))
|
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::lane::{Lane, LaneID, LaneType, PARKING_SPOT_LENGTH};
|
||||||
pub use crate::make::RoadSpec;
|
pub use crate::make::RoadSpec;
|
||||||
pub use crate::map::Map;
|
pub use crate::map::Map;
|
||||||
|
pub use crate::pathfind::uber_turns::{IntersectionCluster, UberTurn};
|
||||||
pub use crate::pathfind::{Path, PathConstraints, PathRequest, PathStep};
|
pub use crate::pathfind::{Path, PathConstraints, PathRequest, PathStep};
|
||||||
pub use crate::road::{DirectedRoadID, Road, RoadID};
|
pub use crate::road::{DirectedRoadID, Road, RoadID};
|
||||||
pub use crate::stop_signs::{ControlStopSign, RoadWithStopSign};
|
pub use crate::stop_signs::{ControlStopSign, RoadWithStopSign};
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
use crate::{IntersectionID, Map, TurnID};
|
use crate::{IntersectionID, Map, TurnID};
|
||||||
|
use geom::PolyLine;
|
||||||
use petgraph::graphmap::UnGraphMap;
|
use petgraph::graphmap::UnGraphMap;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
// This only applies to VehiclePathfinder; walking through these intersections is nothing special.
|
// 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.
|
// TODO I haven't seen any cases yet with "interior" intersections. Some stuff might break.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct IntersectionCluster {
|
pub struct IntersectionCluster {
|
||||||
pub members: HashSet<IntersectionID>,
|
pub members: HashSet<IntersectionID>,
|
||||||
pub uber_turns: Vec<UberTurn>,
|
pub uber_turns: Vec<UberTurn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct UberTurn {
|
pub struct UberTurn {
|
||||||
pub path: Vec<TurnID>,
|
pub path: Vec<TurnID>,
|
||||||
}
|
}
|
||||||
@ -27,11 +30,43 @@ pub fn find(map: &Map) -> Vec<IntersectionCluster> {
|
|||||||
}
|
}
|
||||||
for intersections in petgraph::algo::kosaraju_scc(&graph) {
|
for intersections in petgraph::algo::kosaraju_scc(&graph) {
|
||||||
let members: HashSet<IntersectionID> = intersections.iter().cloned().collect();
|
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
|
// Find all entrances and exits through this group of intersections
|
||||||
let mut entrances = Vec::new();
|
let mut entrances = Vec::new();
|
||||||
let mut exits = HashSet::new();
|
let mut exits = HashSet::new();
|
||||||
for i in &intersections {
|
for i in &members {
|
||||||
for turn in map.get_turns_in_intersection(*i) {
|
for turn in map.get_turns_in_intersection(*i) {
|
||||||
if turn.between_sidewalks() {
|
if turn.between_sidewalks() {
|
||||||
continue;
|
continue;
|
||||||
@ -50,30 +85,6 @@ pub fn find(map: &Map) -> Vec<IntersectionCluster> {
|
|||||||
for entrance in entrances {
|
for entrance in entrances {
|
||||||
uber_turns.extend(flood(entrance, map, &exits));
|
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 {
|
/*for ut in &uber_turns {
|
||||||
print!("- {}", ut.path[0].src);
|
print!("- {}", ut.path[0].src);
|
||||||
for t in &ut.path {
|
for t in &ut.path {
|
||||||
@ -82,13 +93,11 @@ pub fn find(map: &Map) -> Vec<IntersectionCluster> {
|
|||||||
println!("");
|
println!("");
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
clusters.push(IntersectionCluster {
|
IntersectionCluster {
|
||||||
members,
|
members,
|
||||||
uber_turns,
|
uber_turns,
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
clusters
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flood(start: TurnID, map: &Map, exits: &HashSet<TurnID>) -> Vec<UberTurn> {
|
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