1use std::cell::RefCell;
2
3use geom::{
4 Angle, ArrowCap, Bounds, Distance, Line, PolyLine, Polygon, Pt2D, Ring, Tessellation, Time,
5 EPSILON_DIST,
6};
7use map_model::{
8 ControlTrafficSignal, Direction, DrivingSide, Intersection, IntersectionControl,
9 IntersectionID, LaneType, Map, Road, RoadWithStopSign, Turn, TurnType, SIDEWALK_THICKNESS,
10};
11use widgetry::{Color, Drawable, GeomBatch, GfxCtx, Prerender, RewriteColor, Text};
12
13use crate::colors::ColorScheme;
14use crate::render::{traffic_signal, DrawOptions, Renderable, OUTLINE_THICKNESS};
15use crate::{AppLike, ID};
16
17pub struct DrawIntersection {
18 pub id: IntersectionID,
19 zorder: isize,
20
21 draw_default: RefCell<Option<Drawable>>,
22 pub draw_traffic_signal: RefCell<Option<(Time, Drawable)>>,
23}
24
25impl DrawIntersection {
26 pub fn new(i: &Intersection, map: &Map) -> DrawIntersection {
27 DrawIntersection {
28 id: i.id,
29 zorder: i.get_zorder(map),
30 draw_default: RefCell::new(None),
31 draw_traffic_signal: RefCell::new(None),
32 }
33 }
34
35 pub fn render<P: AsRef<Prerender>>(&self, prerender: &P, app: &dyn AppLike) -> GeomBatch {
36 let map = app.map();
37 let i = map.get_i(self.id);
38
39 let mut default_geom = GeomBatch::new();
41 let rank = i.get_rank(map);
42 default_geom.push(
43 if i.is_footway(map) {
44 app.cs().zoomed_road_surface(LaneType::Footway, rank)
46 } else if i.is_cycleway(map) {
47 app.cs().zoomed_road_surface(LaneType::Biking, rank)
48 } else {
49 app.cs().zoomed_intersection_surface(rank)
50 },
51 i.polygon.clone(),
52 );
53 default_geom.extend(
54 app.cs().zoomed_road_surface(LaneType::Sidewalk, rank),
55 calculate_corners(i, map),
56 );
57 if app.cs().road_outlines {
58 default_geom.extend(app.cs().curb(rank), calculate_corner_curbs(i, map));
59 }
60 if i.is_footway(map) {
61 for pl in Self::get_unzoomed_outline(i, map) {
62 default_geom.extend(
63 Color::BLACK,
64 pl.exact_dashed_polygons(
65 Distance::meters(0.25),
66 Distance::meters(1.0),
67 Distance::meters(1.5),
68 ),
69 );
70 }
71 }
72
73 for turn in &i.turns {
74 if !app.opts().show_crosswalks {
75 break;
76 }
77 if turn.turn_type.pedestrian_crossing() {
78 make_crosswalk(&mut default_geom, turn, map, app.cs());
79 }
80 }
81
82 if i.is_private(map) {
83 if let Some(color) = app.cs().private_road {
84 default_geom.push(color.alpha(0.5), i.polygon.clone());
85 }
86 }
87
88 if i.is_border() {
89 let r = map.get_r(*i.roads.iter().next().unwrap());
90 default_geom.extend(
91 app.cs().road_center_line(map),
92 calculate_border_arrows(i, r, map),
93 );
94 } else {
95 match i.control {
96 IntersectionControl::Signed | IntersectionControl::Uncontrolled => {
97 for ss in map.get_stop_sign(i.id).roads.values() {
98 if !app.opts().show_stop_signs {
99 break;
100 }
101 if ss.must_stop {
102 if let Some((octagon, pole, angle)) =
103 DrawIntersection::stop_sign_geom(ss, map)
104 {
105 let center = octagon.center();
106 default_geom.push(app.cs().stop_sign, octagon);
107 default_geom.push(app.cs().stop_sign_pole, pole);
108
109 default_geom.append(
113 Text::from(
114 widgetry::Line("STOP").small_heading().fg(Color::WHITE),
115 )
116 .render_autocropped(prerender.as_ref())
117 .scale(0.02)
118 .centered_on(center)
119 .rotate(angle.opposite().rotate_degs(-90.0)),
120 );
121 }
122 }
123 }
124 }
125 IntersectionControl::Construction => {
126 default_geom.append(
128 GeomBatch::load_svg(prerender, "system/assets/map/under_construction.svg")
129 .scale(0.08)
130 .centered_on(i.polygon.center()),
131 );
132 }
133 IntersectionControl::Signalled => {}
134 }
135 }
136
137 let zorder = i.get_zorder(map);
138 if zorder < 0 {
139 default_geom = default_geom.color(RewriteColor::ChangeAlpha(0.5));
140 }
141
142 default_geom
143 }
144
145 pub fn stop_sign_geom(ss: &RoadWithStopSign, map: &Map) -> Option<(Polygon, Polygon, Angle)> {
147 let trim_back = Distance::meters(0.1);
148 let edge_lane = map.get_l(ss.lane_closest_to_edge);
149 if edge_lane.length() - trim_back <= EPSILON_DIST {
151 return None;
153 }
154 let last_line = edge_lane
155 .lane_center_pts
156 .exact_slice(Distance::ZERO, edge_lane.length() - trim_back)
157 .last_line();
158 let last_line = if map.get_config().driving_side == DrivingSide::Right {
159 last_line.shift_right(edge_lane.width)
160 } else {
161 last_line.shift_left(edge_lane.width)
162 };
163
164 let octagon = make_octagon(last_line.pt2(), Distance::meters(1.0), last_line.angle());
165 let pole = Line::must_new(
166 last_line
167 .pt2()
168 .project_away(Distance::meters(1.5), last_line.angle().opposite()),
169 last_line
171 .pt2()
172 .project_away(Distance::meters(0.9), last_line.angle().opposite()),
173 )
174 .make_polygons(Distance::meters(0.3));
175 Some((octagon, pole, last_line.angle()))
176 }
177
178 pub fn clear_rendering(&mut self) {
179 *self.draw_default.borrow_mut() = None;
180 }
181
182 pub fn get_unzoomed_outline(i: &Intersection, map: &Map) -> Vec<PolyLine> {
185 let road_pairs = i
188 .roads
189 .iter()
190 .map(|r| {
191 let road = map.get_r(*r);
192 let half_width = road.get_half_width();
193 let left = road.center_pts.must_shift_left(half_width);
194 let right = road.center_pts.must_shift_right(half_width);
195 if road.src_i == i.id {
196 (left.first_pt(), right.first_pt())
197 } else {
198 (left.last_pt(), right.last_pt())
199 }
200 })
201 .collect::<Vec<_>>();
202
203 i.polygon
206 .get_outer_ring()
207 .points()
208 .windows(2)
209 .filter(|window| {
210 !road_pairs
211 .iter()
212 .any(|road_pair| approx_eq(window, &road_pair))
213 })
214 .filter_map(|pair| PolyLine::new(vec![pair[0], pair[1]]).ok())
215 .collect::<Vec<_>>()
216
217 }
219
220 fn redraw_default(&self, g: &mut GfxCtx, app: &dyn AppLike) {
221 let mut draw = self.draw_default.borrow_mut();
224 if draw.is_none() {
225 *draw = Some(g.upload(self.render(g, app)));
226 }
227 g.redraw(draw.as_ref().unwrap());
228 }
229
230 fn draw_traffic_signal(
231 &self,
232 g: &mut GfxCtx,
233 app: &dyn AppLike,
234 opts: &DrawOptions,
235 signal: &ControlTrafficSignal,
236 ) {
237 if opts.suppress_traffic_signal_details.contains(&self.id) {
238 return;
239 }
240
241 let mut maybe_redraw = self.draw_traffic_signal.borrow_mut();
242 if app.opts().show_traffic_signal_icon {
243 let recalc = maybe_redraw.is_none();
245 if recalc {
246 let batch = GeomBatch::load_svg(g, "system/assets/map/traffic_signal.svg")
247 .scale(0.3)
248 .centered_on(app.map().get_i(self.id).polygon.polylabel());
249 *maybe_redraw = Some((Time::START_OF_DAY, g.prerender.upload(batch)));
250 }
251 } else {
252 let recalc = maybe_redraw
253 .as_ref()
254 .map(|(t, _)| *t != app.sim_time())
255 .unwrap_or(true);
256 if recalc {
257 let (idx, remaining) = app.current_stage_and_remaining_time(self.id);
258 let mut batch = GeomBatch::new();
259 traffic_signal::draw_signal_stage(
260 g.prerender,
261 &signal.stages[idx],
262 idx,
263 self.id,
264 Some(remaining),
265 &mut batch,
266 app,
267 app.opts().traffic_signal_style.clone(),
268 );
269 *maybe_redraw = Some((app.sim_time(), g.prerender.upload(batch)));
270 }
271 }
272
273 let (_, batch) = maybe_redraw.as_ref().unwrap();
274 g.redraw(batch);
275 }
276}
277
278fn approx_eq(pair1: &[Pt2D], pair2: &(Pt2D, Pt2D)) -> bool {
279 let epsilon = Distance::meters(0.1);
280 (pair1[0].approx_eq(pair2.0, epsilon) && pair1[1].approx_eq(pair2.1, epsilon))
281 || (pair1[0].approx_eq(pair2.1, epsilon) && pair1[1].approx_eq(pair2.0, epsilon))
282}
283
284impl Renderable for DrawIntersection {
285 fn get_id(&self) -> ID {
286 ID::Intersection(self.id)
287 }
288
289 fn draw(&self, g: &mut GfxCtx, app: &dyn AppLike, opts: &DrawOptions) {
290 self.redraw_default(g, app);
291 if let Some(signal) = app.map().maybe_get_traffic_signal(self.id) {
292 self.draw_traffic_signal(g, app, opts, signal);
293 }
294 }
295
296 fn get_outline(&self, map: &Map) -> Tessellation {
297 map.get_i(self.id).polygon.to_outline(OUTLINE_THICKNESS)
298 }
299
300 fn get_bounds(&self, map: &Map) -> Bounds {
301 map.get_i(self.id).polygon.get_bounds()
302 }
303
304 fn contains_pt(&self, pt: Pt2D, map: &Map) -> bool {
305 map.get_i(self.id).polygon.contains_pt(pt)
306 }
307
308 fn get_zorder(&self) -> isize {
309 self.zorder
310 }
311}
312
313pub fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
315 if i.roads.iter().any(|r| map.get_r(*r).is_footway()) {
317 return Vec::new();
318 }
319
320 let mut corners = Vec::new();
321
322 for turn in &i.turns {
323 if turn.turn_type == TurnType::SharedSidewalkCorner {
324 let l1 = map.get_l(turn.id.src);
325 let l2 = map.get_l(turn.id.dst);
326
327 if i.roads.len() == 1 {
329 corners.push(turn.geom.make_polygons(l1.width.min(l2.width)));
330 continue;
331 }
332
333 let dir = if i
335 .polygon
336 .center()
337 .angle_to(turn.geom.first_pt())
338 .simple_shortest_rotation_towards(i.polygon.center().angle_to(turn.geom.last_pt()))
339 > 0.0
340 {
341 1.0
342 } else {
343 -1.0
344 };
345
346 if l1.width == l2.width {
347 let shift = dir * l1.width / 2.0;
350 if let Some(poly) = (|| {
351 let mut pts = turn.geom.shift_either_direction(-shift).ok()?.into_points();
352 pts.push(l2.end_line(i.id).shift_either_direction(shift).pt2());
353 pts.push(l2.end_line(i.id).shift_either_direction(-shift).pt2());
354 pts.extend(
355 turn.geom
356 .shift_either_direction(shift)
357 .ok()?
358 .reversed()
359 .into_points(),
360 );
361 pts.push(l1.end_line(i.id).shift_either_direction(shift).pt2());
362 pts.push(l1.end_line(i.id).shift_either_direction(-shift).pt2());
363 pts.push(pts[0]);
364 Some(Ring::deduping_new(pts).ok()?.into_polygon())
365 })() {
366 corners.push(poly);
367 }
368 } else {
369 let mut pts = vec![
371 l2.end_line(i.id)
372 .shift_either_direction(dir * l2.width / 2.0)
373 .pt2(),
374 l2.end_line(i.id)
375 .shift_either_direction(-dir * l2.width / 2.0)
376 .pt2(),
377 l1.end_line(i.id)
378 .shift_either_direction(-dir * l1.width / 2.0)
379 .pt2(),
380 l1.end_line(i.id)
381 .shift_either_direction(dir * l1.width / 2.0)
382 .pt2(),
383 ];
384 pts.push(pts[0]);
385 if let Ok(ring) = Ring::new(pts) {
386 corners.push(ring.into_polygon());
387 }
388 }
389 }
390 }
391
392 corners
393}
394
395fn calculate_corner_curbs(i: &Intersection, map: &Map) -> Vec<Polygon> {
396 if i.roads.iter().any(|r| map.get_r(*r).is_footway()) {
398 return Vec::new();
399 }
400
401 let mut curbs = Vec::new();
402
403 let thickness = Distance::meters(0.2);
404 let shift = |width| (width - thickness) / 2.0;
405
406 for turn in &i.turns {
407 if turn.turn_type == TurnType::SharedSidewalkCorner {
408 let dir = if turn
409 .geom
410 .first_pt()
411 .angle_to(i.polygon.center())
412 .simple_shortest_rotation_towards(
413 turn.geom.first_pt().angle_to(turn.geom.last_pt()),
414 )
415 > 0.0
416 {
417 1.0
418 } else {
419 -1.0
420 } * if i.is_deadend_for_everyone() {
422 -1.0
423 } else {
424 1.0
425 };
426 let l1 = map.get_l(turn.id.src);
427 let l2 = map.get_l(turn.id.dst);
428
429 if l1.width == l2.width {
430 let width = dir * shift(l1.width);
433
434 if let Some(pl) = (|| {
435 let mut pts = turn.geom.shift_either_direction(width).ok()?.into_points();
436 let first_line = l2.end_line(i.id).shift_either_direction(-width);
442 if !pts.last().unwrap().approx_eq(first_line.pt2(), thickness) {
443 pts.push(first_line.pt2());
444 pts.push(first_line.unbounded_dist_along(first_line.length() - thickness));
445 }
446 let last_line = l1.end_line(i.id).shift_either_direction(width);
447 if !pts[0].approx_eq(last_line.pt2(), thickness) {
448 pts.insert(0, last_line.pt2());
449 pts.insert(
450 0,
451 last_line.unbounded_dist_along(last_line.length() - thickness),
452 );
453 }
454 PolyLine::deduping_new(pts).ok()
455 })() {
456 curbs.push(pl.make_polygons(thickness));
457 }
458 } else {
459 let l1_line = l1
461 .end_line(i.id)
462 .shift_either_direction(dir * shift(l1.width));
463 let l2_line = l2
464 .end_line(i.id)
465 .shift_either_direction(-dir * shift(l2.width));
466 if let Ok(pl) = PolyLine::deduping_new(vec![
467 l1_line.unbounded_dist_along(l1_line.length() - thickness),
468 l1_line.pt2(),
469 l2_line.pt2(),
470 l2_line.unbounded_dist_along(l2_line.length() - thickness),
471 ]) {
472 curbs.push(pl.make_polygons(thickness));
473 }
474 }
475 }
476 }
477
478 curbs
479}
480
481fn calculate_border_arrows(i: &Intersection, r: &Road, map: &Map) -> Vec<Polygon> {
484 let mut result = Vec::new();
485
486 let mut width_fwd = Distance::ZERO;
487 let mut width_back = Distance::ZERO;
488 for l in &r.lanes {
489 if l.dir == Direction::Fwd {
490 width_fwd += l.width;
491 } else {
492 width_back += l.width;
493 }
494 }
495 let center = r.get_dir_change_pl(map);
496
497 if !i.outgoing_lanes.is_empty() {
499 let (line, width) = if r.dst_i == i.id {
500 (
501 center.last_line().shift_left(width_back / 2.0).reversed(),
502 width_back,
503 )
504 } else {
505 (center.first_line().shift_right(width_fwd / 2.0), width_fwd)
506 };
507 result.push(
508 PolyLine::must_new(vec![
510 line.unbounded_dist_along(Distance::meters(-9.5)),
511 line.unbounded_dist_along(Distance::meters(-0.5)),
512 ])
513 .make_arrow(width / 3.0, ArrowCap::Triangle),
514 );
515 }
516
517 if !i.incoming_lanes.is_empty() {
519 let (line, width) = if r.dst_i == i.id {
520 (
521 center.last_line().shift_right(width_fwd / 2.0).reversed(),
522 width_fwd,
523 )
524 } else {
525 (center.first_line().shift_left(width_back / 2.0), width_back)
526 };
527 result.push(
528 PolyLine::must_new(vec![
529 line.unbounded_dist_along(Distance::meters(-0.5)),
530 line.unbounded_dist_along(Distance::meters(-9.5)),
531 ])
532 .make_arrow(width / 3.0, ArrowCap::Triangle),
533 );
534 }
535
536 result
537}
538
539fn make_octagon(center: Pt2D, radius: Distance, facing: Angle) -> Polygon {
541 Ring::must_new(
542 (0..=8)
543 .map(|i| center.project_away(radius, facing.rotate_degs(22.5 + f64::from(i * 360 / 8))))
544 .collect(),
545 )
546 .into_polygon()
547}
548
549pub fn make_crosswalk(batch: &mut GeomBatch, turn: &Turn, map: &Map, cs: &ColorScheme) {
551 if turn.turn_type == TurnType::UnmarkedCrossing {
552 make_unmarked_crossing(batch, turn, map, cs);
553 return;
554 }
555
556 if make_rainbow_crosswalk(batch, turn, map) {
557 return;
558 }
559
560 let width = SIDEWALK_THICKNESS;
562 let boundary = width;
565 let tile_every = width * 0.6;
566 let line = if let Some(l) = turn.crosswalk_line() {
567 l
568 } else {
569 return;
570 };
571
572 const CROSSWALK_LINE_THICKNESS: Distance = Distance::const_meters(0.15);
573
574 let available_length = line.length() - (boundary * 2.0);
575 if available_length > Distance::ZERO {
576 let num_markings = (available_length / tile_every).floor() as usize;
577 let mut dist_along =
578 boundary + (available_length - tile_every * (num_markings as f64)) / 2.0;
579 let err = format!("make_crosswalk for {} broke", turn.id);
581 for _ in 0..=num_markings {
582 let pt1 = line.dist_along(dist_along).expect(&err);
583 let pt2 = pt1.project_away(Distance::meters(1.0), line.angle());
585 batch.push(
586 cs.general_road_marking,
587 perp_line(Line::must_new(pt1, pt2), width).make_polygons(CROSSWALK_LINE_THICKNESS),
588 );
589
590 let pt3 = line
592 .dist_along(dist_along + 2.0 * CROSSWALK_LINE_THICKNESS)
593 .expect(&err);
594 let pt4 = pt3.project_away(Distance::meters(1.0), line.angle());
595 batch.push(
596 cs.general_road_marking,
597 perp_line(Line::must_new(pt3, pt4), width).make_polygons(CROSSWALK_LINE_THICKNESS),
598 );
599
600 dist_along += tile_every;
601 }
602 }
603}
604
605fn make_rainbow_crosswalk(batch: &mut GeomBatch, turn: &Turn, map: &Map) -> bool {
606 let node = map.get_i(turn.id.parent).orig_id.0;
608 let way = map.get_parent(turn.id.src).orig_id.osm_way_id.0;
609 match (node, way) {
610 (53073255, 428246441) |
612 (53073255, 332601014) |
613 (53073254, 6447455) |
615 (53073254, 607690679) |
616 (53168934, 6456052) |
618 (53200834, 6456052) |
620 (53068795, 607691081) |
622 (53068795, 65588105) |
623 (53068794, 65588105) => {}
625 _ => { return false; }
626 }
627
628 let total_width = map.get_l(turn.id.src).width;
629 let colors = vec![
630 Color::WHITE,
631 Color::RED,
632 Color::ORANGE,
633 Color::YELLOW,
634 Color::GREEN,
635 Color::BLUE,
636 Color::hex("#8B00FF"),
637 Color::WHITE,
638 ];
639 let band_width = total_width / (colors.len() as f64);
640 let total_width = map.get_l(turn.id.src).width;
641 let slice = turn
642 .geom
643 .exact_slice(total_width, turn.geom.length() - total_width)
644 .must_shift_left(total_width / 2.0 - band_width / 2.0);
645 for (idx, color) in colors.into_iter().enumerate() {
646 batch.push(
647 color,
648 slice
649 .must_shift_right(band_width * (idx as f64))
650 .make_polygons(band_width),
651 );
652 }
653 true
654}
655
656fn make_unmarked_crossing(batch: &mut GeomBatch, turn: &Turn, map: &Map, cs: &ColorScheme) {
657 let color = cs.general_road_marking.alpha(0.5);
658 let band_width = Distance::meters(0.1);
659 let total_width = map.get_l(turn.id.src).width;
660 if let Some(line) = turn.crosswalk_line() {
661 if let Ok(slice) = line.slice(total_width, line.length() - total_width) {
662 batch.push(
663 color,
664 slice
665 .shift_left(total_width / 2.0 - band_width / 2.0)
666 .make_polygons(band_width),
667 );
668 batch.push(
669 color,
670 slice
671 .shift_right(total_width / 2.0 - band_width / 2.0)
672 .make_polygons(band_width),
673 );
674 }
675 }
676}
677
678fn perp_line(l: Line, length: Distance) -> Line {
680 let pt1 = l.shift_right(length / 2.0).pt1();
681 let pt2 = l.shift_left(length / 2.0).pt1();
682 Line::must_new(pt1, pt2)
683}