1use std::collections::{HashMap, HashSet};
2
3use geom::{Angle, ArrowCap, Circle, Distance, PolyLine, Polygon};
4use map_model::{IntersectionID, LaneID, Map, MovementID, TurnPriority, SIDEWALK_THICKNESS};
5use widgetry::{Color, GeomBatch, Prerender};
6
7use crate::colors::ColorScheme;
8use crate::render::{traffic_signal, BIG_ARROW_THICKNESS};
9use crate::AppLike;
10
11const TURN_ICON_ARROW_LENGTH: Distance = Distance::const_meters(1.5);
12
13pub struct DrawMovement {
14 pub id: MovementID,
15 pub hitbox: Polygon,
16}
17
18impl DrawMovement {
19 pub fn for_i(
21 prerender: &Prerender,
22 map: &Map,
23 cs: &ColorScheme,
24 i: IntersectionID,
25 idx: usize,
26 ) -> Vec<(DrawMovement, GeomBatch)> {
27 let signal = map.get_traffic_signal(i);
28 let stage = &signal.stages[idx];
29
30 let mut offset_per_lane: HashMap<LaneID, usize> = HashMap::new();
32 let mut results = Vec::new();
33 for movement in map.get_i(i).movements.values() {
34 let mut batch = GeomBatch::new();
35 let hitbox = if stage.protected_movements.contains(&movement.id) {
37 if movement.id.crosswalk {
38 batch = traffic_signal::walk_icon(movement, prerender);
39 batch.get_bounds().to_circle().to_polygon()
40 } else {
41 let arrow = movement
42 .geom
43 .make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
44 batch.push(cs.signal_protected_turn, arrow.clone());
45 batch.push(Color::BLACK, arrow.to_outline(Distance::meters(0.2)));
46 arrow
47 }
48 } else if stage.yield_movements.contains(&movement.id) {
49 let pl = &movement.geom;
50 if pl.length() >= 2.0 * SIDEWALK_THICKNESS {
53 batch.extend(
54 Color::BLACK,
55 pl.exact_slice(
56 SIDEWALK_THICKNESS - Distance::meters(0.1),
57 pl.length() - SIDEWALK_THICKNESS + Distance::meters(0.1),
58 )
59 .dashed_arrow(
60 BIG_ARROW_THICKNESS,
61 Distance::meters(1.2),
62 Distance::meters(0.3),
63 ArrowCap::Triangle,
64 ),
65 );
66 let arrow = pl
67 .exact_slice(SIDEWALK_THICKNESS, pl.length() - SIDEWALK_THICKNESS)
68 .dashed_arrow(
69 BIG_ARROW_THICKNESS / 2.0,
70 Distance::meters(1.0),
71 Distance::meters(0.5),
72 ArrowCap::Triangle,
73 );
74 batch.extend(cs.signal_protected_turn, arrow.clone());
75 } else {
76 warn!(
79 "{:?} is too short to render as a yield movement",
80 movement.id
81 );
82 batch.extend(
83 cs.signal_protected_turn,
84 pl.dashed_arrow(
85 BIG_ARROW_THICKNESS / 2.0,
86 Distance::meters(1.0),
87 Distance::meters(0.5),
88 ArrowCap::Triangle,
89 ),
90 );
91 }
92 movement
95 .geom
96 .make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle)
97 } else if movement.id.crosswalk {
98 batch = traffic_signal::dont_walk_icon(movement, prerender);
99 batch.get_bounds().to_circle().to_polygon()
100 } else {
101 let offset = movement
103 .members
104 .iter()
105 .map(|t| *offset_per_lane.entry(t.src).or_insert(0))
106 .max()
107 .unwrap();
108 let (pl, _) = movement.src_center_and_width(map);
109 let (circle, arrow) = make_circle_geom(offset as f64, pl, movement.angle);
110 let mut seen_lanes = HashSet::new();
111 for t in &movement.members {
112 if !seen_lanes.contains(&t.src) {
113 *offset_per_lane.get_mut(&t.src).unwrap() = offset + 1;
114 seen_lanes.insert(t.src);
115 }
116 }
117 batch.push(cs.signal_banned_turn.alpha(0.5), circle.clone());
118 batch.push(Color::WHITE, arrow);
119 circle
120 };
121 results.push((
122 DrawMovement {
123 id: movement.id,
124 hitbox,
125 },
126 batch,
127 ));
128 }
129 results
130 }
131
132 pub fn draw_selected_movement(
133 &self,
134 app: &dyn AppLike,
135 batch: &mut GeomBatch,
136 next_priority: Option<TurnPriority>,
137 ) {
138 let movement = &app.map().get_i(self.id.parent).movements[&self.id];
139 let pl = &movement.geom;
140
141 let green = Color::hex("#72CE36");
142 match next_priority {
143 Some(TurnPriority::Protected) => {
144 let arrow = pl.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
145 batch.push(green.alpha(0.5), arrow.clone());
146 batch.push(green, arrow.to_outline(Distance::meters(0.1)));
147 }
148 Some(TurnPriority::Yield) => {
149 batch.extend(
150 Color::BLACK.alpha(0.8),
153 pl.dashed_arrow(
154 BIG_ARROW_THICKNESS,
155 Distance::meters(1.2),
156 Distance::meters(0.3),
157 ArrowCap::Triangle,
158 ),
159 );
160 batch.extend(
161 green.alpha(0.8),
162 pl.exact_slice(Distance::meters(0.1), pl.length() - Distance::meters(0.1))
163 .dashed_arrow(
164 BIG_ARROW_THICKNESS / 2.0,
165 Distance::meters(1.0),
166 Distance::meters(0.5),
167 ArrowCap::Triangle,
168 ),
169 );
170 }
171 Some(TurnPriority::Banned) => {
172 batch.extend(
173 Color::BLACK.alpha(0.8),
174 pl.dashed_arrow(
175 BIG_ARROW_THICKNESS,
176 Distance::meters(1.2),
177 Distance::meters(0.3),
178 ArrowCap::Triangle,
179 ),
180 );
181 batch.extend(
182 app.cs().signal_banned_turn.alpha(0.8),
183 pl.exact_slice(Distance::meters(0.1), pl.length() - Distance::meters(0.1))
184 .dashed_arrow(
185 BIG_ARROW_THICKNESS / 2.0,
186 Distance::meters(1.0),
187 Distance::meters(0.5),
188 ArrowCap::Triangle,
189 ),
190 );
191 }
192 None => {}
193 }
194 }
195}
196
197fn make_circle_geom(offset: f64, pl: PolyLine, turn_angle: Angle) -> (Polygon, Polygon) {
199 let height = 2.0 * TURN_ICON_ARROW_LENGTH;
200 let extension = PolyLine::must_new(vec![
202 pl.last_pt(),
203 pl.last_pt()
204 .project_away(Distance::meters(500.0), pl.last_line().angle()),
205 ]);
206 let pl = pl.must_extend(extension);
207 let slice = pl.exact_slice(offset * height, (offset + 1.0) * height);
208 let center = slice.middle();
209 let block = Circle::new(center, TURN_ICON_ARROW_LENGTH).to_polygon();
210
211 let arrow_angle = pl.last_line().angle().opposite() + turn_angle;
212 let arrow = PolyLine::must_new(vec![
213 center.project_away(TURN_ICON_ARROW_LENGTH / 2.0, arrow_angle.opposite()),
214 center.project_away(TURN_ICON_ARROW_LENGTH / 2.0, arrow_angle),
215 ])
216 .make_arrow(Distance::meters(0.5), ArrowCap::Triangle);
217
218 (block, arrow)
219}