start grouping uber-turns together, to make editing clusters of signals sane. very basic first steps.

This commit is contained in:
Dustin Carlino 2020-05-20 14:01:19 -07:00
parent e0d33e22f6
commit d3c4b0b26a
8 changed files with 267 additions and 7 deletions

View File

@ -0,0 +1,101 @@
use crate::app::{App, ShowEverything};
use crate::game::{DrawBaselayer, State, Transition};
use crate::render::{DrawOptions, DrawUberTurnGroup, BIG_ARROW_THICKNESS};
use ezgui::{
hotkey, Btn, Composite, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Outcome,
VerticalAlignment, Widget,
};
use geom::ArrowCap;
use map_model::{IntersectionCluster, IntersectionID};
use std::collections::BTreeSet;
pub struct ClusterTrafficSignalEditor {
composite: Composite,
members: BTreeSet<IntersectionID>,
groups: Vec<DrawUberTurnGroup>,
group_selected: Option<usize>,
}
impl ClusterTrafficSignalEditor {
pub fn new(ctx: &mut EventCtx, app: &mut App, ic: &IntersectionCluster) -> Box<dyn State> {
app.primary.current_selection = None;
Box::new(ClusterTrafficSignalEditor {
composite: Composite::new(
Widget::row(vec![
Btn::text_fg("Finish").build_def(ctx, hotkey(Key::Escape))
])
.bg(app.cs.panel_bg),
)
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx),
groups: DrawUberTurnGroup::new(ic, &app.primary.map),
group_selected: None,
members: ic.members.clone(),
})
}
}
impl State for ClusterTrafficSignalEditor {
fn event(&mut self, ctx: &mut EventCtx, _: &mut App) -> Transition {
match self.composite.event(ctx) {
Some(Outcome::Clicked(x)) => match x.as_ref() {
"Finish" => {
return Transition::Pop;
}
_ => unreachable!(),
},
None => {}
}
ctx.canvas_movement();
if ctx.redo_mouseover() {
self.group_selected = None;
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
for (idx, g) in self.groups.iter().enumerate() {
if g.block.contains_pt(pt) {
self.group_selected = Some(idx);
break;
}
}
}
}
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.members.clone());
app.draw(g, opts, &app.primary.sim, &ShowEverything::new());
}
let mut batch = GeomBatch::new();
for (idx, g) in self.groups.iter().enumerate() {
if Some(idx) == self.group_selected {
batch.push(app.cs.selected, g.block.clone());
// Overwrite the original thing
batch.push(
app.cs.selected,
g.group
.geom
.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle)
.unwrap(),
);
} else {
batch.push(app.cs.signal_turn_block_bg, g.block.clone());
}
let arrow_color = app.cs.signal_protected_turn;
batch.push(arrow_color, g.arrow.clone());
}
batch.draw(g);
self.composite.draw(g);
}
}

View File

@ -1,8 +1,10 @@
mod bulk;
mod cluster_traffic_signals;
mod lanes;
mod stop_signs;
mod traffic_signals;
pub use self::cluster_traffic_signals::ClusterTrafficSignalEditor;
pub use self::lanes::LaneEditor;
pub use self::stop_signs::StopSignEditor;
pub use self::traffic_signals::TrafficSignalEditor;

View File

@ -22,7 +22,7 @@ pub use crate::render::map::{AgentCache, AgentColorScheme, DrawMap};
pub use crate::render::pedestrian::{DrawPedCrowd, DrawPedestrian};
pub use crate::render::road::DrawRoad;
pub use crate::render::traffic_signal::{draw_signal_phase, make_signal_diagram};
pub use crate::render::turn::DrawTurnGroup;
pub use crate::render::turn::{DrawTurnGroup, DrawUberTurnGroup};
use ezgui::{GfxCtx, Prerender};
use geom::{Distance, Polygon, Pt2D};
use map_model::{IntersectionID, Map};

View File

@ -1,5 +1,5 @@
use geom::{ArrowCap, Distance, PolyLine, Polygon};
use map_model::{IntersectionID, LaneID, Map, TurnGroupID};
use map_model::{IntersectionCluster, IntersectionID, LaneID, Map, TurnGroupID, UberTurnGroup};
use std::collections::{HashMap, HashSet};
const TURN_ICON_ARROW_LENGTH: Distance = Distance::const_meters(1.5);
@ -61,3 +61,62 @@ impl DrawTurnGroup {
draw
}
}
// TODO Share some code with DrawTurnGroup?
pub struct DrawUberTurnGroup {
pub group: UberTurnGroup,
pub block: Polygon,
pub arrow: Polygon,
}
impl DrawUberTurnGroup {
pub fn new(ic: &IntersectionCluster, map: &Map) -> Vec<DrawUberTurnGroup> {
let mut offset_per_lane: HashMap<LaneID, usize> = HashMap::new();
let mut draw = Vec::new();
for group in ic.uber_turn_groups(map) {
// TODO Right now they have one lane, but probably changing this soon
let offset = group
.members
.iter()
.map(|ut| *offset_per_lane.entry(ut.entry()).or_insert(0))
.max()
.unwrap() as f64;
let (pl, width) = group.src_center_and_width(map);
let slice = if pl.length() >= (offset + 1.0) * TURN_ICON_ARROW_LENGTH {
pl.exact_slice(
offset * TURN_ICON_ARROW_LENGTH,
(offset + 1.0) * TURN_ICON_ARROW_LENGTH,
)
} else {
pl
};
let block = slice.make_polygons(width);
// TODO need to shrink the entire pl
let arrow = {
let center = slice.middle();
PolyLine::new(vec![
center.project_away(TURN_ICON_ARROW_LENGTH / 2.0, geom::Angle::new_degs(90.0)),
center.project_away(TURN_ICON_ARROW_LENGTH / 2.0, geom::Angle::new_degs(270.0)),
])
.make_arrow(Distance::meters(0.5), ArrowCap::Triangle)
.unwrap()
};
let mut seen_lanes = HashSet::new();
for ut in &group.members {
if !seen_lanes.contains(&ut.entry()) {
*offset_per_lane.get_mut(&ut.entry()).unwrap() += 1;
seen_lanes.insert(ut.entry());
}
}
draw.push(DrawUberTurnGroup {
group,
block,
arrow,
});
}
draw
}
}

View File

@ -1,5 +1,6 @@
use crate::app::{App, ShowEverything};
use crate::common::CommonState;
use crate::edit::ClusterTrafficSignalEditor;
use crate::game::{msg, DrawBaselayer, State, Transition};
use crate::helpers::ID;
use crate::render::{DrawOptions, BIG_ARROW_THICKNESS};
@ -176,6 +177,7 @@ impl UberTurnViewer {
Btn::text_fg("X").build_def(ctx, hotkey(Key::Escape)),
]),
Checkbox::text(ctx, "legal / illegal movements", None, legal_turns),
Btn::text_fg("Edit").build_def(ctx, None),
])
.padding(10)
.bg(app.cs.panel_bg),
@ -198,6 +200,11 @@ impl State for UberTurnViewer {
"X" => {
return Transition::Pop;
}
"Edit" => {
return Transition::Replace(ClusterTrafficSignalEditor::new(
ctx, app, &self.ic,
));
}
"previous uber-turn" => {
return Transition::Replace(UberTurnViewer::new(
ctx,

View File

@ -24,7 +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::uber_turns::{IntersectionCluster, UberTurn, UberTurnGroup};
pub use crate::pathfind::{Path, PathConstraints, PathRequest, PathStep};
pub use crate::road::{DirectedRoadID, Road, RoadID};
pub use crate::stop_signs::{ControlStopSign, RoadWithStopSign};

View File

@ -144,7 +144,7 @@ fn make_input_graph(
.iter()
.all(|t| constraints.can_use(map.get_l(t.dst), map))
{
uber_turn_entrances.insert(ut.path[0].src, idx);
uber_turn_entrances.insert(ut.entry(), idx);
} else {
// Similar to the hack below for unused lanes
if idx == uber_turns.len() - 1 {
@ -185,7 +185,7 @@ fn make_input_graph(
input_graph.add_edge(from, nodes.get(Node::UberTurn(*idx)), sum_cost.max(1));
input_graph.add_edge(
nodes.get(Node::UberTurn(*idx)),
nodes.get(Node::Lane(ut.path.last().unwrap().dst)),
nodes.get(Node::Lane(ut.exit())),
// The cost is already captured for entering the uber-turn
1,
);

View File

@ -1,5 +1,6 @@
use crate::{IntersectionID, Map, TurnID};
use geom::PolyLine;
use crate::{IntersectionID, LaneID, Map, TurnID};
use abstutil::MultiMap;
use geom::{Distance, PolyLine, Pt2D};
use petgraph::graphmap::UnGraphMap;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
@ -154,6 +155,13 @@ fn trace_back(end: TurnID, preds: &BTreeMap<TurnID, TurnID>) -> Vec<TurnID> {
}
impl UberTurn {
pub fn entry(&self) -> LaneID {
self.path[0].src
}
pub fn exit(&self) -> LaneID {
self.path.last().unwrap().dst
}
pub fn geom(&self, map: &Map) -> PolyLine {
let mut pl = map.get_t(self.path[0]).geom.clone();
let mut first = true;
@ -168,3 +176,86 @@ impl UberTurn {
pl
}
}
pub struct UberTurnGroup {
pub members: Vec<UberTurn>,
pub geom: PolyLine,
}
impl IntersectionCluster {
pub fn uber_turn_groups(&self, map: &Map) -> Vec<UberTurnGroup> {
let mut groups: MultiMap<(LaneID, LaneID), usize> = MultiMap::new();
for (idx, ut) in self.uber_turns.iter().enumerate() {
groups.insert((ut.entry(), ut.exit()), idx);
}
let mut result = Vec::new();
for (_, member_indices) in groups.consume() {
let mut members = Vec::new();
let mut polylines = Vec::new();
for idx in member_indices {
polylines.push(self.uber_turns[idx].geom(map));
members.push(self.uber_turns[idx].clone());
}
result.push(UberTurnGroup {
members,
geom: group_geom(polylines),
});
}
result
}
}
impl UberTurnGroup {
// TODO Share code with TurnGroup
// Polyline points FROM intersection
pub fn src_center_and_width(&self, map: &Map) -> (PolyLine, Distance) {
let sample_entry = self.members[0].entry();
let r = map.get_parent(sample_entry);
let dir = r.is_forwards(sample_entry);
// Points towards the intersection
let pl = if dir {
r.get_current_center(map)
} else {
r.get_current_center(map).reversed()
};
// TODO Poorly expressed. We just want the first leftmost value, and the last rightmost.
let mut leftmost = Distance::meters(99999.0);
let mut rightmost = Distance::ZERO;
let mut left = Distance::ZERO;
let mut right = Distance::ZERO;
for l in r.lanes_on_side(dir) {
right += map.get_l(l).width;
if self.members.iter().any(|ut| ut.entry() == l) {
leftmost = leftmost.min(left);
rightmost = rightmost.max(right);
}
left += map.get_l(l).width;
}
let pl = map.right_shift(pl, (leftmost + rightmost) / 2.0).unwrap();
// Flip direction, so we point away from the intersection
(pl.reversed(), rightmost - leftmost)
}
}
fn group_geom(mut polylines: Vec<PolyLine>) -> PolyLine {
let num_pts = polylines[0].points().len();
for pl in &polylines {
if num_pts != pl.points().len() {
return polylines.remove(0);
}
}
let mut pts = Vec::new();
for idx in 0..num_pts {
pts.push(Pt2D::center(
&polylines.iter().map(|pl| pl.points()[idx]).collect(),
));
}
PolyLine::new(pts)
}