mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-29 17:34:58 +03:00
start grouping uber-turns together, to make editing clusters of signals sane. very basic first steps.
This commit is contained in:
parent
e0d33e22f6
commit
d3c4b0b26a
101
game/src/edit/cluster_traffic_signals.rs
Normal file
101
game/src/edit/cluster_traffic_signals.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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};
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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};
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user