mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 15:33:44 +03:00
Overhaul traffic signal editor. Movements can be directly clicked now;
the turn icons (now circles) are just for currently banned turns. #331 [rebuild] Still a little work left (tuning arrow styles and using special icons for crosswalks), but this mostly seems to match Yuwen's design.
This commit is contained in:
parent
60cb96bc91
commit
69622bb86d
@ -7,7 +7,7 @@ use crate::common::CommonState;
|
|||||||
use crate::edit::{apply_map_edits, ConfirmDiscard};
|
use crate::edit::{apply_map_edits, ConfirmDiscard};
|
||||||
use crate::game::{DrawBaselayer, PopupMsg, State, Transition};
|
use crate::game::{DrawBaselayer, PopupMsg, State, Transition};
|
||||||
use crate::options::TrafficSignalStyle;
|
use crate::options::TrafficSignalStyle;
|
||||||
use crate::render::{draw_signal_stage, DrawMovement, DrawOptions};
|
use crate::render::{draw_signal_stage, draw_stage_number, DrawMovement, DrawOptions};
|
||||||
use crate::sandbox::GameplayMode;
|
use crate::sandbox::GameplayMode;
|
||||||
use abstutil::Timer;
|
use abstutil::Timer;
|
||||||
use geom::{Distance, Duration, Line, Polygon, Pt2D};
|
use geom::{Distance, Duration, Line, Polygon, Pt2D};
|
||||||
@ -77,11 +77,6 @@ impl TrafficSignalEditor {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut movements = Vec::new();
|
|
||||||
for i in &members {
|
|
||||||
movements.extend(DrawMovement::for_i(*i, &app.primary.map));
|
|
||||||
}
|
|
||||||
|
|
||||||
let original = BundleEdits::get_current(app, &members);
|
let original = BundleEdits::get_current(app, &members);
|
||||||
let synced = BundleEdits::synchronize(app, &members);
|
let synced = BundleEdits::synchronize(app, &members);
|
||||||
let warn_changed = original != synced;
|
let warn_changed = original != synced;
|
||||||
@ -93,7 +88,7 @@ impl TrafficSignalEditor {
|
|||||||
mode,
|
mode,
|
||||||
members,
|
members,
|
||||||
current_stage: 0,
|
current_stage: 0,
|
||||||
movements,
|
movements: Vec::new(),
|
||||||
movement_selected: None,
|
movement_selected: None,
|
||||||
draw_current: ctx.upload(GeomBatch::new()),
|
draw_current: ctx.upload(GeomBatch::new()),
|
||||||
command_stack: Vec::new(),
|
command_stack: Vec::new(),
|
||||||
@ -102,7 +97,7 @@ impl TrafficSignalEditor {
|
|||||||
original,
|
original,
|
||||||
fade_irrelevant: GeomBatch::from(vec![(app.cs.fade_map_dark, fade_area)]).upload(ctx),
|
fade_irrelevant: GeomBatch::from(vec![(app.cs.fade_map_dark, fade_area)]).upload(ctx),
|
||||||
};
|
};
|
||||||
editor.draw_current = editor.recalc_draw_current(ctx, app);
|
editor.recalc_draw_current(ctx, app);
|
||||||
Box::new(editor)
|
Box::new(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +117,7 @@ impl TrafficSignalEditor {
|
|||||||
.scroll_to_member(ctx, format!("stage {}", idx + 1));
|
.scroll_to_member(ctx, format!("stage {}", idx + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.draw_current = self.recalc_draw_current(ctx, app);
|
self.recalc_draw_current(ctx, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_new_edit<F: Fn(&mut ControlTrafficSignal)>(
|
fn add_new_edit<F: Fn(&mut ControlTrafficSignal)>(
|
||||||
@ -144,55 +139,45 @@ impl TrafficSignalEditor {
|
|||||||
self.change_stage(ctx, app, idx);
|
self.change_stage(ctx, app, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recalc_draw_current(&self, ctx: &mut EventCtx, app: &App) -> Drawable {
|
fn recalc_draw_current(&mut self, ctx: &mut EventCtx, app: &App) {
|
||||||
let mut batch = GeomBatch::new();
|
let mut batch = GeomBatch::new();
|
||||||
|
let mut movements = Vec::new();
|
||||||
for i in &self.members {
|
for i in &self.members {
|
||||||
let signal = app.primary.map.get_traffic_signal(*i);
|
let stage = &app.primary.map.get_traffic_signal(*i).stages[self.current_stage];
|
||||||
let mut stage = signal.stages[self.current_stage].clone();
|
for (m, draw) in DrawMovement::for_i(&app.primary.map, &app.cs, *i, self.current_stage)
|
||||||
if let Some((id, _)) = self.movement_selected {
|
{
|
||||||
if id.parent == signal.id {
|
if self
|
||||||
stage.edit_movement(&signal.movements[&id], TurnPriority::Banned);
|
.movement_selected
|
||||||
|
.map(|(x, _)| x != m.id)
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
batch.append(draw);
|
||||||
|
} else if !stage.protected_movements.contains(&m.id)
|
||||||
|
&& !stage.yield_movements.contains(&m.id)
|
||||||
|
{
|
||||||
|
// Still draw the icon, but highlight it
|
||||||
|
batch.append(draw.color(RewriteColor::Change(
|
||||||
|
Color::hex("#7C7C7C"),
|
||||||
|
Color::hex("#72CE36"),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
movements.push(m);
|
||||||
|
}
|
||||||
|
draw_stage_number(app, ctx.prerender, *i, self.current_stage, &mut batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the selected thing on top of everything else
|
||||||
|
if let Some((selected, next_priority)) = self.movement_selected {
|
||||||
|
for m in &movements {
|
||||||
|
if m.id == selected {
|
||||||
|
m.draw_selected_movement(app, &mut batch, next_priority);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
draw_signal_stage(
|
|
||||||
ctx.prerender,
|
|
||||||
&stage,
|
|
||||||
self.current_stage,
|
|
||||||
signal.id,
|
|
||||||
None,
|
|
||||||
&mut batch,
|
|
||||||
app,
|
|
||||||
app.opts.traffic_signal_style.clone(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for m in &self.movements {
|
self.draw_current = ctx.upload(batch);
|
||||||
let signal = app.primary.map.get_traffic_signal(m.id.parent);
|
self.movements = movements;
|
||||||
if self
|
|
||||||
.movement_selected
|
|
||||||
.as_ref()
|
|
||||||
.map(|(id, _)| *id == m.id)
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
m.draw_selected_movement(
|
|
||||||
app,
|
|
||||||
&mut batch,
|
|
||||||
&signal.movements[&m.id],
|
|
||||||
self.movement_selected.unwrap().1,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
batch.push(app.cs.signal_turn_block_bg, m.block.clone());
|
|
||||||
let stage = &signal.stages[self.current_stage];
|
|
||||||
let arrow_color = match stage.get_priority_of_movement(m.id) {
|
|
||||||
TurnPriority::Protected => app.cs.signal_protected_turn,
|
|
||||||
TurnPriority::Yield => app.cs.signal_permitted_turn,
|
|
||||||
TurnPriority::Banned => app.cs.signal_banned_turn,
|
|
||||||
};
|
|
||||||
batch.push(arrow_color, m.arrow.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.upload(batch)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,7 +395,7 @@ impl State for TrafficSignalEditor {
|
|||||||
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
|
if let Some(pt) = ctx.canvas.get_cursor_in_map_space() {
|
||||||
for m in &self.movements {
|
for m in &self.movements {
|
||||||
let signal = app.primary.map.get_traffic_signal(m.id.parent);
|
let signal = app.primary.map.get_traffic_signal(m.id.parent);
|
||||||
if m.block.contains_pt(pt) {
|
if m.hitbox.contains_pt(pt) {
|
||||||
let stage = &signal.stages[self.current_stage];
|
let stage = &signal.stages[self.current_stage];
|
||||||
let next_priority = match stage.get_priority_of_movement(m.id) {
|
let next_priority = match stage.get_priority_of_movement(m.id) {
|
||||||
TurnPriority::Banned => {
|
TurnPriority::Banned => {
|
||||||
@ -438,7 +423,6 @@ impl State for TrafficSignalEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.movement_selected != old {
|
if self.movement_selected != old {
|
||||||
self.draw_current = self.recalc_draw_current(ctx, app);
|
|
||||||
self.change_stage(ctx, app, self.current_stage);
|
self.change_stage(ctx, app, self.current_stage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ use crate::render::car::DrawCar;
|
|||||||
pub use crate::render::intersection::{calculate_corners, DrawIntersection};
|
pub use crate::render::intersection::{calculate_corners, DrawIntersection};
|
||||||
pub use crate::render::map::{AgentCache, DrawMap, UnzoomedAgents};
|
pub use crate::render::map::{AgentCache, DrawMap, UnzoomedAgents};
|
||||||
pub use crate::render::pedestrian::{DrawPedCrowd, DrawPedestrian};
|
pub use crate::render::pedestrian::{DrawPedCrowd, DrawPedestrian};
|
||||||
pub use crate::render::traffic_signal::draw_signal_stage;
|
pub use crate::render::traffic_signal::{draw_signal_stage, draw_stage_number};
|
||||||
pub use crate::render::turn::{DrawMovement, DrawUberTurnGroup};
|
pub use crate::render::turn::{DrawMovement, DrawUberTurnGroup};
|
||||||
use geom::{Distance, Polygon, Pt2D};
|
use geom::{Distance, Polygon, Pt2D};
|
||||||
use map_model::{IntersectionID, Map};
|
use map_model::{IntersectionID, Map};
|
||||||
|
@ -119,19 +119,7 @@ pub fn draw_signal_stage(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
draw_stage_number(app, prerender, i, idx, batch);
|
||||||
let radius = Distance::meters(1.0);
|
|
||||||
let center = app.primary.map.get_i(i).polygon.polylabel();
|
|
||||||
batch.push(
|
|
||||||
Color::hex("#5B5B5B"),
|
|
||||||
Circle::new(center, radius).to_polygon(),
|
|
||||||
);
|
|
||||||
batch.append(
|
|
||||||
Text::from(Line(format!("{}", idx + 1)))
|
|
||||||
.render_to_batch(prerender)
|
|
||||||
.scale(0.075)
|
|
||||||
.centered_on(center),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
TrafficSignalStyle::Yuwen => {
|
TrafficSignalStyle::Yuwen => {
|
||||||
for m in &stage.yield_movements {
|
for m in &stage.yield_movements {
|
||||||
@ -200,6 +188,27 @@ pub fn draw_signal_stage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_stage_number(
|
||||||
|
app: &App,
|
||||||
|
prerender: &Prerender,
|
||||||
|
i: IntersectionID,
|
||||||
|
idx: usize,
|
||||||
|
batch: &mut GeomBatch,
|
||||||
|
) {
|
||||||
|
let radius = Distance::meters(1.0);
|
||||||
|
let center = app.primary.map.get_i(i).polygon.polylabel();
|
||||||
|
batch.push(
|
||||||
|
Color::hex("#5B5B5B"),
|
||||||
|
Circle::new(center, radius).to_polygon(),
|
||||||
|
);
|
||||||
|
batch.append(
|
||||||
|
Text::from(Line(format!("{}", idx + 1)))
|
||||||
|
.render_to_batch(prerender)
|
||||||
|
.scale(0.075)
|
||||||
|
.centered_on(center),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_time_left(
|
fn draw_time_left(
|
||||||
app: &App,
|
app: &App,
|
||||||
prerender: &Prerender,
|
prerender: &Prerender,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
use crate::colors::ColorScheme;
|
||||||
use crate::render::BIG_ARROW_THICKNESS;
|
use crate::render::BIG_ARROW_THICKNESS;
|
||||||
use geom::{Angle, ArrowCap, Distance, PolyLine, Polygon};
|
use geom::{Angle, ArrowCap, Circle, Distance, PolyLine, Polygon};
|
||||||
use map_model::{
|
use map_model::{
|
||||||
IntersectionCluster, IntersectionID, LaneID, Map, Movement, MovementID, TurnPriority,
|
IntersectionCluster, IntersectionID, LaneID, Map, MovementID, TurnPriority, UberTurnGroup,
|
||||||
UberTurnGroup,
|
SIDEWALK_THICKNESS,
|
||||||
};
|
};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use widgetry::{Color, GeomBatch};
|
use widgetry::{Color, GeomBatch};
|
||||||
@ -12,66 +13,117 @@ const TURN_ICON_ARROW_LENGTH: Distance = Distance::const_meters(1.5);
|
|||||||
|
|
||||||
pub struct DrawMovement {
|
pub struct DrawMovement {
|
||||||
pub id: MovementID,
|
pub id: MovementID,
|
||||||
pub block: Polygon,
|
pub hitbox: Polygon,
|
||||||
pub arrow: Polygon,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawMovement {
|
impl DrawMovement {
|
||||||
pub fn for_i(i: IntersectionID, map: &Map) -> Vec<DrawMovement> {
|
// Also returns the stuff to draw each movement
|
||||||
|
pub fn for_i(
|
||||||
|
map: &Map,
|
||||||
|
cs: &ColorScheme,
|
||||||
|
i: IntersectionID,
|
||||||
|
idx: usize,
|
||||||
|
) -> Vec<(DrawMovement, GeomBatch)> {
|
||||||
|
let signal = map.get_traffic_signal(i);
|
||||||
|
let stage = &signal.stages[idx];
|
||||||
|
|
||||||
// TODO Sort by angle here if we want some consistency
|
// TODO Sort by angle here if we want some consistency
|
||||||
let mut offset_per_lane: HashMap<LaneID, usize> = HashMap::new();
|
let mut offset_per_lane: HashMap<LaneID, usize> = HashMap::new();
|
||||||
let mut draw = Vec::new();
|
let mut results = Vec::new();
|
||||||
for movement in map.get_traffic_signal(i).movements.values() {
|
for movement in signal.movements.values() {
|
||||||
let offset = movement
|
let mut batch = GeomBatch::new();
|
||||||
.members
|
// TODO Refactor the slice_start/slice_end stuff from draw_signal_stage.
|
||||||
.iter()
|
let hitbox = if stage.protected_movements.contains(&movement.id) {
|
||||||
.map(|t| *offset_per_lane.entry(t.src).or_insert(0))
|
let arrow = movement
|
||||||
.max()
|
.geom
|
||||||
.unwrap();
|
.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
|
||||||
let (pl, width) = movement.src_center_and_width(map);
|
batch.push(cs.signal_protected_turn, arrow.clone());
|
||||||
let (block, arrow) = make_geom(offset as f64, pl, width, movement.angle);
|
arrow
|
||||||
let mut seen_lanes = HashSet::new();
|
} else if stage.yield_movements.contains(&movement.id) {
|
||||||
for t in &movement.members {
|
let pl = &movement.geom;
|
||||||
if !seen_lanes.contains(&t.src) {
|
batch.extend(
|
||||||
*offset_per_lane.get_mut(&t.src).unwrap() = offset + 1;
|
Color::BLACK,
|
||||||
seen_lanes.insert(t.src);
|
pl.exact_slice(
|
||||||
|
SIDEWALK_THICKNESS - Distance::meters(0.1),
|
||||||
|
pl.length() - SIDEWALK_THICKNESS + Distance::meters(0.1),
|
||||||
|
)
|
||||||
|
.dashed_arrow(
|
||||||
|
BIG_ARROW_THICKNESS,
|
||||||
|
Distance::meters(1.2),
|
||||||
|
Distance::meters(0.3),
|
||||||
|
ArrowCap::Triangle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let arrow = pl
|
||||||
|
.exact_slice(SIDEWALK_THICKNESS, pl.length() - SIDEWALK_THICKNESS)
|
||||||
|
.dashed_arrow(
|
||||||
|
BIG_ARROW_THICKNESS / 2.0,
|
||||||
|
Distance::meters(1.0),
|
||||||
|
Distance::meters(0.5),
|
||||||
|
ArrowCap::Triangle,
|
||||||
|
);
|
||||||
|
batch.extend(cs.signal_protected_turn, arrow.clone());
|
||||||
|
// Bit weird, but don't use the dashed arrow as the hitbox. The gaps in between
|
||||||
|
// should still be clickable.
|
||||||
|
movement
|
||||||
|
.geom
|
||||||
|
.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle)
|
||||||
|
} else {
|
||||||
|
// Use circular icons for banned turns
|
||||||
|
let offset = movement
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.map(|t| *offset_per_lane.entry(t.src).or_insert(0))
|
||||||
|
.max()
|
||||||
|
.unwrap();
|
||||||
|
let (pl, _) = movement.src_center_and_width(map);
|
||||||
|
let (circle, arrow) = make_circle_geom(offset as f64, pl, movement.angle);
|
||||||
|
let mut seen_lanes = HashSet::new();
|
||||||
|
for t in &movement.members {
|
||||||
|
if !seen_lanes.contains(&t.src) {
|
||||||
|
*offset_per_lane.get_mut(&t.src).unwrap() = offset + 1;
|
||||||
|
seen_lanes.insert(t.src);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
batch.push(Color::hex("#7C7C7C"), circle.clone());
|
||||||
|
batch.push(Color::WHITE, arrow);
|
||||||
draw.push(DrawMovement {
|
circle
|
||||||
id: movement.id,
|
};
|
||||||
block,
|
results.push((
|
||||||
arrow,
|
DrawMovement {
|
||||||
});
|
id: movement.id,
|
||||||
|
hitbox,
|
||||||
|
},
|
||||||
|
batch,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
draw
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_selected_movement(
|
pub fn draw_selected_movement(
|
||||||
&self,
|
&self,
|
||||||
app: &App,
|
app: &App,
|
||||||
batch: &mut GeomBatch,
|
batch: &mut GeomBatch,
|
||||||
m: &Movement,
|
|
||||||
next_priority: Option<TurnPriority>,
|
next_priority: Option<TurnPriority>,
|
||||||
) {
|
) {
|
||||||
// TODO Refactor this mess. Maybe after things like "dashed with outline" can be expressed
|
let movement = &app.primary.map.get_traffic_signal(self.id.parent).movements[&self.id];
|
||||||
// more composably like SVG, using lyon.
|
let pl = &movement.geom;
|
||||||
let block_color = match next_priority {
|
|
||||||
|
match next_priority {
|
||||||
Some(TurnPriority::Protected) => {
|
Some(TurnPriority::Protected) => {
|
||||||
let green = Color::hex("#72CE36");
|
let green = Color::hex("#72CE36");
|
||||||
let arrow = m.geom.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
|
let arrow = pl.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
|
||||||
batch.push(green.alpha(0.5), arrow.clone());
|
batch.push(green.alpha(0.5), arrow.clone());
|
||||||
if let Ok(p) = arrow.to_outline(Distance::meters(0.1)) {
|
if let Ok(p) = arrow.to_outline(Distance::meters(0.1)) {
|
||||||
batch.push(green, p);
|
batch.push(green, p);
|
||||||
}
|
}
|
||||||
green
|
|
||||||
}
|
}
|
||||||
Some(TurnPriority::Yield) => {
|
Some(TurnPriority::Yield) => {
|
||||||
batch.extend(
|
batch.extend(
|
||||||
// TODO Ideally the inner part would be the lower opacity blue, but can't yet
|
// TODO Ideally the inner part would be the lower opacity blue, but can't yet
|
||||||
// express that it should cover up the thicker solid blue beneath it
|
// express that it should cover up the thicker solid blue beneath it
|
||||||
Color::BLACK.alpha(0.8),
|
Color::BLACK.alpha(0.8),
|
||||||
m.geom.dashed_arrow(
|
pl.dashed_arrow(
|
||||||
BIG_ARROW_THICKNESS,
|
BIG_ARROW_THICKNESS,
|
||||||
Distance::meters(1.2),
|
Distance::meters(1.2),
|
||||||
Distance::meters(0.3),
|
Distance::meters(0.3),
|
||||||
@ -80,11 +132,7 @@ impl DrawMovement {
|
|||||||
);
|
);
|
||||||
batch.extend(
|
batch.extend(
|
||||||
app.cs.signal_permitted_turn.alpha(0.8),
|
app.cs.signal_permitted_turn.alpha(0.8),
|
||||||
m.geom
|
pl.exact_slice(Distance::meters(0.1), pl.length() - Distance::meters(0.1))
|
||||||
.exact_slice(
|
|
||||||
Distance::meters(0.1),
|
|
||||||
m.geom.length() - Distance::meters(0.1),
|
|
||||||
)
|
|
||||||
.dashed_arrow(
|
.dashed_arrow(
|
||||||
BIG_ARROW_THICKNESS / 2.0,
|
BIG_ARROW_THICKNESS / 2.0,
|
||||||
Distance::meters(1.0),
|
Distance::meters(1.0),
|
||||||
@ -92,21 +140,17 @@ impl DrawMovement {
|
|||||||
ArrowCap::Triangle,
|
ArrowCap::Triangle,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
app.cs.signal_permitted_turn
|
|
||||||
}
|
}
|
||||||
Some(TurnPriority::Banned) => {
|
Some(TurnPriority::Banned) => {
|
||||||
let red = Color::hex("#EB3223");
|
let red = Color::hex("#EB3223");
|
||||||
let arrow = m.geom.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
|
let arrow = pl.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
|
||||||
batch.push(red.alpha(0.5), arrow.clone());
|
batch.push(red.alpha(0.5), arrow.clone());
|
||||||
if let Ok(p) = arrow.to_outline(Distance::meters(0.1)) {
|
if let Ok(p) = arrow.to_outline(Distance::meters(0.1)) {
|
||||||
batch.push(red, p);
|
batch.push(red, p);
|
||||||
}
|
}
|
||||||
red
|
|
||||||
}
|
}
|
||||||
None => app.cs.signal_turn_block_bg,
|
None => {}
|
||||||
};
|
}
|
||||||
batch.push(block_color, self.block.clone());
|
|
||||||
batch.push(Color::WHITE, self.arrow.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +172,7 @@ impl DrawUberTurnGroup {
|
|||||||
.max()
|
.max()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (pl, width) = group.src_center_and_width(map);
|
let (pl, width) = group.src_center_and_width(map);
|
||||||
let (block, arrow) = make_geom(offset as f64, pl, width, group.angle());
|
let (block, arrow) = make_block_geom(offset as f64, pl, width, group.angle());
|
||||||
let mut seen_lanes = HashSet::new();
|
let mut seen_lanes = HashSet::new();
|
||||||
for ut in &group.members {
|
for ut in &group.members {
|
||||||
if !seen_lanes.contains(&ut.entry()) {
|
if !seen_lanes.contains(&ut.entry()) {
|
||||||
@ -148,7 +192,7 @@ impl DrawUberTurnGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Produces (block, arrow)
|
// Produces (block, arrow)
|
||||||
fn make_geom(offset: f64, pl: PolyLine, width: Distance, angle: Angle) -> (Polygon, Polygon) {
|
fn make_block_geom(offset: f64, pl: PolyLine, width: Distance, angle: Angle) -> (Polygon, Polygon) {
|
||||||
let height = TURN_ICON_ARROW_LENGTH;
|
let height = TURN_ICON_ARROW_LENGTH;
|
||||||
// Always extend the pl first to handle short entry lanes
|
// Always extend the pl first to handle short entry lanes
|
||||||
let extension = PolyLine::must_new(vec![
|
let extension = PolyLine::must_new(vec![
|
||||||
@ -171,3 +215,26 @@ fn make_geom(offset: f64, pl: PolyLine, width: Distance, angle: Angle) -> (Polyg
|
|||||||
|
|
||||||
(block, arrow)
|
(block, arrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Produces (circle, arrow)
|
||||||
|
fn make_circle_geom(offset: f64, pl: PolyLine, angle: Angle) -> (Polygon, Polygon) {
|
||||||
|
let height = 2.0 * TURN_ICON_ARROW_LENGTH;
|
||||||
|
// Always extend the pl first to handle short entry lanes
|
||||||
|
let extension = PolyLine::must_new(vec![
|
||||||
|
pl.last_pt(),
|
||||||
|
pl.last_pt()
|
||||||
|
.project_away(Distance::meters(500.0), pl.last_line().angle()),
|
||||||
|
]);
|
||||||
|
let pl = pl.must_extend(extension);
|
||||||
|
let slice = pl.exact_slice(offset * height, (offset + 1.0) * height);
|
||||||
|
let center = slice.middle();
|
||||||
|
let block = Circle::new(center, TURN_ICON_ARROW_LENGTH).to_polygon();
|
||||||
|
|
||||||
|
let arrow = PolyLine::must_new(vec![
|
||||||
|
center.project_away(TURN_ICON_ARROW_LENGTH / 2.0, angle.opposite()),
|
||||||
|
center.project_away(TURN_ICON_ARROW_LENGTH / 2.0, angle),
|
||||||
|
])
|
||||||
|
.make_arrow(Distance::meters(0.5), ArrowCap::Triangle);
|
||||||
|
|
||||||
|
(block, arrow)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user