1use std::collections::BTreeSet;
2
3use geom::{Angle, ArrowCap, Circle, Distance, Duration, Line, PolyLine, Pt2D};
4use map_model::{
5 Intersection, IntersectionID, Movement, Stage, StageType, TurnPriority, SIDEWALK_THICKNESS,
6};
7use widgetry::{Color, GeomBatch, Line, Prerender, RewriteColor, Text};
8
9use crate::options::TrafficSignalStyle;
10use crate::render::intersection::make_crosswalk;
11use crate::render::BIG_ARROW_THICKNESS;
12use crate::AppLike;
13
14pub fn draw_signal_stage(
15 prerender: &Prerender,
16 stage: &Stage,
17 idx: usize,
18 i: IntersectionID,
19 time_left: Option<Duration>,
20 batch: &mut GeomBatch,
21 app: &dyn AppLike,
22 signal_style: TrafficSignalStyle,
23) {
24 let i = app.map().get_i(i);
25
26 match signal_style {
27 TrafficSignalStyle::Brian => {
28 let mut dont_walk = BTreeSet::new();
29 let mut crossed_roads = BTreeSet::new();
30 for m in i.movements.keys() {
31 if m.crosswalk {
32 dont_walk.insert(m);
33 crossed_roads.insert((m.from.road, m.parent));
36 crossed_roads.insert((m.to.road, m.parent));
37 }
38 }
39
40 let (yellow_light, percent) = if let Some(t) = time_left {
41 if stage.stage_type.simple_duration() > Duration::ZERO {
42 (
43 t <= Duration::seconds(5.0),
44 (t / stage.stage_type.simple_duration()) as f32,
45 )
46 } else {
47 (true, 1.0)
48 }
49 } else {
50 (false, 1.0)
51 };
52 let arrow_body_color = if yellow_light {
53 if let StageType::Fixed(_) = stage.stage_type {
56 Color::YELLOW
57 } else {
58 Color::ORANGE
59 }
60 } else {
61 app.cs().signal_protected_turn.alpha(percent)
62 };
63
64 for m in &stage.yield_movements {
65 assert!(!m.crosswalk);
66 let pl = &i.movements[m].geom;
67 if let Ok(slice) = pl.maybe_exact_slice(
71 SIDEWALK_THICKNESS - Distance::meters(0.1),
72 pl.length() - SIDEWALK_THICKNESS + Distance::meters(0.1),
73 ) {
74 batch.extend(
75 Color::BLACK,
76 slice.dashed_arrow(
77 BIG_ARROW_THICKNESS,
78 Distance::meters(1.2),
79 Distance::meters(0.3),
80 ArrowCap::Triangle,
81 ),
82 );
83 }
84 if let Ok(slice) =
85 pl.maybe_exact_slice(SIDEWALK_THICKNESS, pl.length() - SIDEWALK_THICKNESS)
86 {
87 batch.extend(
88 arrow_body_color,
89 slice.dashed_arrow(
90 BIG_ARROW_THICKNESS / 2.0,
91 Distance::meters(1.0),
92 Distance::meters(0.5),
93 ArrowCap::Triangle,
94 ),
95 );
96 }
97 }
98
99 for m in &stage.protected_movements {
100 if !m.crosswalk {
101 let slice_start = if crossed_roads.contains(&(m.from.road, m.parent)) {
103 SIDEWALK_THICKNESS
104 } else {
105 Distance::ZERO
106 };
107 let slice_end = if crossed_roads.contains(&(m.to.road, m.parent)) {
108 SIDEWALK_THICKNESS
109 } else {
110 Distance::ZERO
111 };
112
113 let pl = &i.movements[m].geom;
114 if let Ok(pl) = pl.maybe_exact_slice(slice_start, pl.length() - slice_end) {
115 let arrow = pl.make_arrow(BIG_ARROW_THICKNESS, ArrowCap::Triangle);
116 batch.push(arrow_body_color, arrow.clone());
117 batch.push(Color::BLACK, arrow.to_outline(Distance::meters(0.2)));
118 }
119 } else {
120 batch.append(
121 walk_icon(&i.movements[m], prerender)
122 .color(RewriteColor::ChangeAlpha(percent)),
123 );
124 dont_walk.remove(m);
125 }
126 }
127
128 for m in dont_walk {
129 batch.append(dont_walk_icon(&i.movements[m], prerender));
130 }
131
132 draw_stage_number(prerender, i, idx, batch);
133 }
134 TrafficSignalStyle::Yuwen => {
135 for m in &stage.yield_movements {
136 assert!(!m.crosswalk);
137 let arrow = i.movements[m]
138 .geom
139 .make_arrow(BIG_ARROW_THICKNESS * 2.0, ArrowCap::Triangle);
140 batch.push(app.cs().signal_permitted_turn.alpha(0.3), arrow.clone());
141 batch.push(
142 app.cs().signal_permitted_turn,
143 arrow.to_outline(BIG_ARROW_THICKNESS / 2.0),
144 );
145 }
146 for m in &stage.protected_movements {
147 if m.crosswalk {
148 make_crosswalk(
151 batch,
152 app.map().get_t(i.movements[m].members[0]),
153 app.map(),
154 app.cs(),
155 );
156 } else {
157 batch.push(
158 app.cs().signal_protected_turn,
159 i.movements[m]
160 .geom
161 .make_arrow(BIG_ARROW_THICKNESS * 2.0, ArrowCap::Triangle),
162 );
163 }
164 }
165 if let Some(t) = time_left {
166 draw_time_left(app, prerender, stage, i, idx, t, batch);
167 }
168 }
169 TrafficSignalStyle::IndividualTurnArrows => {
170 for turn in &i.turns {
171 if turn.between_sidewalks() {
172 continue;
173 }
174 match stage.get_priority_of_turn(turn.id, i) {
175 TurnPriority::Protected => {
176 batch.push(
177 app.cs().signal_protected_turn,
178 turn.geom
179 .make_arrow(BIG_ARROW_THICKNESS * 2.0, ArrowCap::Triangle),
180 );
181 }
182 TurnPriority::Yield => {
183 batch.push(
184 app.cs().signal_permitted_turn,
185 turn.geom
186 .make_arrow(BIG_ARROW_THICKNESS * 2.0, ArrowCap::Triangle)
187 .to_outline(BIG_ARROW_THICKNESS / 2.0),
188 );
189 }
190 TurnPriority::Banned => {}
191 }
192 }
193 if let Some(t) = time_left {
194 draw_time_left(app, prerender, stage, i, idx, t, batch);
195 }
196 }
197 }
198}
199
200pub fn draw_stage_number(
201 prerender: &Prerender,
202 i: &Intersection,
203 idx: usize,
204 batch: &mut GeomBatch,
205) {
206 let radius = Distance::meters(1.0);
207 let center = i.polygon.polylabel();
208 batch.push(
209 Color::hex("#5B5B5B"),
210 Circle::new(center, radius).to_polygon(),
211 );
212 batch.append(
213 Text::from(Line(format!("{}", idx + 1)).fg(Color::WHITE))
214 .render_autocropped(prerender)
215 .scale(0.075)
216 .centered_on(center),
217 );
218}
219
220fn draw_time_left(
221 app: &dyn AppLike,
222 prerender: &Prerender,
223 stage: &Stage,
224 i: &Intersection,
225 idx: usize,
226 time_left: Duration,
227 batch: &mut GeomBatch,
228) {
229 let radius = Distance::meters(2.0);
230 let center = i.polygon.center();
231 let duration = stage.stage_type.simple_duration();
232 let percent = if duration > Duration::ZERO {
233 time_left / duration
234 } else {
235 1.0
236 };
237 batch.push(
238 app.cs().signal_box,
239 Circle::new(center, 1.2 * radius).to_polygon(),
240 );
241 batch.push(
242 app.cs().signal_spinner.alpha(0.3),
243 Circle::new(center, radius).to_polygon(),
244 );
245 batch.push(
246 app.cs().signal_spinner,
247 Circle::new(center, radius).to_partial_tessellation(percent),
248 );
249 batch.append(
250 Text::from(format!("{}", idx + 1))
251 .render_autocropped(prerender)
252 .scale(0.1)
253 .centered_on(center),
254 );
255}
256
257pub fn walk_icon(movement: &Movement, prerender: &Prerender) -> GeomBatch {
258 let (center, angle) = crosswalk_icon(&movement.geom);
259 GeomBatch::load_svg(prerender, "system/assets/map/walk.svg")
260 .scale(0.07)
261 .centered_on(center)
262 .rotate(angle)
263}
264pub fn dont_walk_icon(movement: &Movement, prerender: &Prerender) -> GeomBatch {
265 let (center, angle) = crosswalk_icon(&movement.geom);
266 GeomBatch::load_svg(prerender, "system/assets/map/dont_walk.svg")
267 .scale(0.07)
268 .centered_on(center)
269 .rotate(angle)
270}
271
272fn crosswalk_icon(geom: &PolyLine) -> (Pt2D, Angle) {
275 let l = Line::must_new(geom.points()[1], geom.points()[2]);
276 (
277 l.dist_along(Distance::meters(1.0))
278 .unwrap_or_else(|_| l.pt1()),
279 l.angle().shortest_rotation_towards(Angle::degrees(90.0)),
280 )
281}