map_gui/render/
lane.rs

1use std::cell::RefCell;
2
3use lyon::geom::{CubicBezierSegment, Point, QuadraticBezierSegment};
4
5use geom::{
6    Angle, ArrowCap, Bounds, Circle, Distance, InfiniteLine, Line, PolyLine, Polygon, Pt2D,
7    Tessellation,
8};
9use map_model::{BufferType, Direction, DrivingSide, Lane, LaneID, LaneType, Map, Road, TurnID};
10use widgetry::{Color, Drawable, GeomBatch, GfxCtx, Prerender, RewriteColor};
11
12use crate::render::{DrawOptions, Renderable, OUTLINE_THICKNESS};
13use crate::{AppLike, ID};
14
15pub struct DrawLane {
16    pub id: LaneID,
17    pub polygon: Polygon,
18    zorder: isize,
19
20    draw_default: RefCell<Option<Drawable>>,
21}
22
23impl DrawLane {
24    pub fn new(lane: &Lane, road: &Road) -> DrawLane {
25        DrawLane {
26            id: lane.id,
27            polygon: lane.get_thick_polygon(),
28            zorder: road.zorder,
29            draw_default: RefCell::new(None),
30        }
31    }
32
33    pub fn render<P: AsRef<Prerender>>(&self, prerender: &P, app: &dyn AppLike) -> GeomBatch {
34        let map = app.map();
35        let lane = map.get_l(self.id);
36        let road = map.get_r(lane.id.road);
37        let rank = road.get_rank();
38        let mut batch = GeomBatch::new();
39
40        if !lane.is_light_rail() {
41            batch.push(
42                app.cs().zoomed_road_surface(lane.lane_type, rank),
43                self.polygon.clone(),
44            );
45        }
46        let general_road_marking = app.cs().general_road_marking;
47
48        match lane.lane_type {
49            LaneType::Sidewalk | LaneType::Shoulder => {
50                // Don't draw these for shoulders
51                if lane.is_sidewalk() {
52                    batch.extend(app.cs().sidewalk_lines, calculate_sidewalk_lines(lane));
53                }
54                if app.cs().road_outlines {
55                    // Create a sense of depth at the curb
56                    let width = Distance::meters(0.2);
57                    let mut shift = (lane.width - width) / 2.0;
58                    if map.get_config().driving_side == DrivingSide::Right {
59                        shift *= -1.0;
60                    }
61                    if let Ok(pl) = lane.lane_center_pts.shift_either_direction(shift) {
62                        batch.push(app.cs().curb(rank), pl.make_polygons(width));
63                    }
64                }
65            }
66            LaneType::Parking => {
67                batch.extend(general_road_marking, calculate_parking_lines(lane, map));
68            }
69            LaneType::Driving => {
70                batch.extend(general_road_marking, calculate_driving_lines(lane, road));
71                batch.extend(general_road_marking, calculate_turn_markings(map, lane));
72                batch.extend(general_road_marking, calculate_one_way_markings(lane, road));
73            }
74            LaneType::Bus => {
75                batch.extend(general_road_marking, calculate_driving_lines(lane, road));
76                batch.extend(general_road_marking, calculate_turn_markings(map, lane));
77                batch.extend(general_road_marking, calculate_one_way_markings(lane, road));
78                for (pt, angle) in lane
79                    .lane_center_pts
80                    .step_along(Distance::meters(30.0), Distance::meters(5.0))
81                {
82                    batch.append(
83                        GeomBatch::load_svg(prerender, "system/assets/map/bus_only.svg")
84                            .scale(0.06)
85                            .centered_on(pt)
86                            .rotate(angle.shortest_rotation_towards(Angle::degrees(-90.0))),
87                    );
88                }
89            }
90            LaneType::Biking => {
91                for (pt, angle) in lane
92                    .lane_center_pts
93                    .step_along(Distance::meters(30.0), Distance::meters(5.0))
94                {
95                    batch.append(
96                        GeomBatch::load_svg(prerender, "system/assets/meters/bike.svg")
97                            .scale(0.06)
98                            .centered_on(pt)
99                            .rotate(angle.shortest_rotation_towards(Angle::degrees(-90.0))),
100                    );
101                }
102            }
103            LaneType::SharedLeftTurn => {
104                let thickness = Distance::meters(0.25);
105                let center_line = app.cs().road_center_line(map);
106                batch.push(
107                    center_line,
108                    lane.lane_center_pts
109                        .must_shift_right((lane.width - thickness) / 2.0)
110                        .make_polygons(thickness),
111                );
112                batch.push(
113                    center_line,
114                    lane.lane_center_pts
115                        .must_shift_left((lane.width - thickness) / 2.0)
116                        .make_polygons(thickness),
117                );
118                for (pt, angle) in lane
119                    .lane_center_pts
120                    .step_along(Distance::meters(30.0), Distance::meters(5.0))
121                {
122                    batch.append(
123                        GeomBatch::load_svg(prerender, "system/assets/map/shared_left_turn.svg")
124                            .autocrop()
125                            .scale(0.003)
126                            .centered_on(pt)
127                            .rotate(angle.shortest_rotation_towards(Angle::degrees(-90.0))),
128                    );
129                }
130            }
131            LaneType::Construction => {
132                for (pt, angle) in lane
133                    .lane_center_pts
134                    .step_along(Distance::meters(30.0), Distance::meters(5.0))
135                {
136                    // TODO Still not quite centered right, but close enough
137                    batch.append(
138                        GeomBatch::load_svg(prerender, "system/assets/map/under_construction.svg")
139                            .scale(0.05)
140                            .rotate_around_batch_center(
141                                angle.shortest_rotation_towards(Angle::degrees(-90.0)),
142                            )
143                            .autocrop()
144                            .centered_on(pt),
145                    );
146                }
147            }
148            LaneType::LightRail => {
149                let track_width = lane.width / 4.0;
150                batch.push(
151                    app.cs().light_rail_track,
152                    lane.lane_center_pts
153                        .must_shift_right((lane.width - track_width) / 2.5)
154                        .make_polygons(track_width),
155                );
156                batch.push(
157                    app.cs().light_rail_track,
158                    lane.lane_center_pts
159                        .must_shift_left((lane.width - track_width) / 2.5)
160                        .make_polygons(track_width),
161                );
162
163                for (pt, angle) in lane
164                    .lane_center_pts
165                    .step_along(Distance::meters(3.0), Distance::meters(3.0))
166                {
167                    // Reuse perp_line. Project away an arbitrary amount
168                    let pt2 = pt.project_away(Distance::meters(1.0), angle);
169                    batch.push(
170                        app.cs().light_rail_track,
171                        perp_line(Line::must_new(pt, pt2), lane.width).make_polygons(track_width),
172                    );
173                }
174            }
175            LaneType::Buffer(style) => {
176                calculate_buffer_markings(app, style, lane, &mut batch);
177            }
178            LaneType::Footway | LaneType::SharedUse => {
179                // Dashed lines on both sides
180                for dir in [-1.0, 1.0] {
181                    let pl = lane
182                        .lane_center_pts
183                        .shift_either_direction(dir * lane.width / 2.0)
184                        .unwrap();
185                    batch.extend(
186                        Color::BLACK,
187                        pl.exact_dashed_polygons(
188                            Distance::meters(0.25),
189                            Distance::meters(1.0),
190                            Distance::meters(1.5),
191                        ),
192                    );
193                }
194            }
195        }
196
197        if road.is_private() {
198            if let Some(color) = app.cs().private_road {
199                batch.push(color.alpha(0.5), self.polygon.clone());
200            }
201        }
202
203        if self.zorder < 0 {
204            batch = batch.color(RewriteColor::ChangeAlpha(0.5));
205        }
206
207        batch
208    }
209
210    pub fn clear_rendering(&mut self) {
211        *self.draw_default.borrow_mut() = None;
212    }
213}
214
215impl Renderable for DrawLane {
216    fn get_id(&self) -> ID {
217        ID::Lane(self.id)
218    }
219
220    fn draw(&self, g: &mut GfxCtx, app: &dyn AppLike, _: &DrawOptions) {
221        // Lazily calculate, because these are expensive to all do up-front, and most players won't
222        // exhaustively see every lane during a single session
223        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 get_outline(&self, map: &Map) -> Tessellation {
231        let lane = map.get_l(self.id);
232        lane.lane_center_pts
233            .to_thick_boundary(lane.width, OUTLINE_THICKNESS)
234            .unwrap_or_else(|| Tessellation::from(self.polygon.clone()))
235    }
236
237    fn get_bounds(&self, map: &Map) -> Bounds {
238        map.get_l(self.id).get_thick_polygon().get_bounds()
239    }
240
241    fn contains_pt(&self, pt: Pt2D, _: &Map) -> bool {
242        self.polygon.contains_pt(pt)
243    }
244
245    fn get_zorder(&self) -> isize {
246        self.zorder
247    }
248}
249
250// TODO this always does it at pt1
251fn perp_line(l: Line, length: Distance) -> Line {
252    let pt1 = l.shift_right(length / 2.0).pt1();
253    let pt2 = l.shift_left(length / 2.0).pt1();
254    Line::must_new(pt1, pt2)
255}
256
257fn calculate_sidewalk_lines(lane: &Lane) -> Vec<Polygon> {
258    lane.lane_center_pts
259        .step_along(lane.width, lane.width)
260        .into_iter()
261        .map(|(pt, angle)| {
262            // Reuse perp_line. Project away an arbitrary amount
263            let pt2 = pt.project_away(Distance::meters(1.0), angle);
264            perp_line(Line::must_new(pt, pt2), lane.width).make_polygons(Distance::meters(0.25))
265        })
266        .collect()
267}
268
269fn calculate_parking_lines(lane: &Lane, map: &Map) -> Vec<Polygon> {
270    let leg_length = Distance::meters(1.0);
271
272    let mut result = Vec::new();
273    let num_spots = lane.number_parking_spots(map.get_config());
274    if num_spots > 0 {
275        for idx in 0..=num_spots {
276            let (pt, lane_angle) = lane
277                .lane_center_pts
278                .must_dist_along(map.get_config().street_parking_spot_length * (1.0 + idx as f64));
279            let perp_angle = if map.get_config().driving_side == DrivingSide::Right {
280                lane_angle.rotate_degs(270.0)
281            } else {
282                lane_angle.rotate_degs(90.0)
283            };
284            // Find the outside of the lane. Actually, shift inside a little bit, since the line
285            // will have thickness, but shouldn't really intersect the adjacent line
286            // when drawn.
287            let t_pt = pt.project_away(lane.width * 0.4, perp_angle);
288            // The perp leg
289            let p1 = t_pt.project_away(leg_length, perp_angle.opposite());
290            result.push(Line::must_new(t_pt, p1).make_polygons(Distance::meters(0.25)));
291            // Upper leg
292            let p2 = t_pt.project_away(leg_length, lane_angle);
293            result.push(Line::must_new(t_pt, p2).make_polygons(Distance::meters(0.25)));
294            // Lower leg
295            let p3 = t_pt.project_away(leg_length, lane_angle.opposite());
296            result.push(Line::must_new(t_pt, p3).make_polygons(Distance::meters(0.25)));
297        }
298    }
299
300    result
301}
302
303// Because the stripe straddles two lanes, it'll be partly hidden on one side. There are a bunch of
304// ways to work around this z-order issue. The current approach is to rely on the fact that
305// quadtrees return LaneIDs in order, and lanes are always created from left->right.
306fn calculate_driving_lines(lane: &Lane, road: &Road) -> Vec<Polygon> {
307    let idx = lane.id.offset;
308
309    // If the lane to the left of us isn't in the same direction or isn't the same type, don't
310    // need dashed lines.
311    if idx == 0
312        || lane.dir != road.lanes[idx - 1].dir
313        || lane.lane_type != road.lanes[idx - 1].lane_type
314    {
315        return Vec::new();
316    }
317
318    let lane_edge_pts = if lane.dir == Direction::Fwd {
319        lane.lane_center_pts.must_shift_left(lane.width / 2.0)
320    } else {
321        lane.lane_center_pts.must_shift_right(lane.width / 2.0)
322    };
323    lane_edge_pts.dashed_lines(
324        Distance::meters(0.25),
325        Distance::meters(1.0),
326        Distance::meters(1.5),
327    )
328}
329
330fn calculate_turn_markings(map: &Map, lane: &Lane) -> Vec<Polygon> {
331    // Does this lane connect to every other possible outbound lane of the same type, excluding
332    // U-turns to the same road? If so, then there's nothing unexpected to communicate.
333    let i = map.get_i(lane.dst_i);
334    if i.outgoing_lanes.iter().all(|l| {
335        let l = map.get_l(*l);
336        l.lane_type != lane.lane_type
337            || l.id.road == lane.id.road
338            || map
339                .maybe_get_t(TurnID {
340                    parent: i.id,
341                    src: lane.id,
342                    dst: l.id,
343                })
344                .is_some()
345    }) {
346        return Vec::new();
347    }
348
349    // Only show one arrow per road. They should all be the same angle, so just use last.
350    let mut turn_angles_roads: Vec<_> = map
351        .get_turns_from_lane(lane.id)
352        .iter()
353        .map(|t| (t.id.dst.road, t.angle()))
354        .collect();
355    turn_angles_roads.dedup_by(|(r1, _), (r2, _)| r1 == r2);
356
357    let turn_angles: Vec<_> = turn_angles_roads.iter().map(|(_, a)| a).collect();
358
359    let mut results = Vec::new();
360    let thickness = Distance::meters(0.2);
361
362    // The distance of the end of a straight arrow from the intersection
363    let location = Distance::meters(4.0);
364    // The length of a straight arrow (turn arrows are shorter)
365    let length_max = Distance::meters(3.0);
366    // The width of a double (left+right) u-turn arrow
367    let width_max = Distance::meters(3.0)
368        .min(lane.width - 4.0 * thickness)
369        .max(Distance::ZERO);
370    // The width of the leftmost/rightmost turn arrow
371    let (left, right) = turn_angles
372        .iter()
373        .map(|a| {
374            width_max / 2.0
375                * (a.simple_shortest_rotation_towards(Angle::ZERO) / 2.0)
376                    .to_radians()
377                    .sin()
378                    .min(0.5)
379        })
380        .fold((Distance::ZERO, Distance::ZERO), |(min, max), s| {
381            (s.min(min), s.max(max))
382        });
383    // Put the middle, not the straight line of the marking in the middle of the lane
384    let offset = (right + left) / 2.0;
385
386    // If the lane is too short to fit the arrows, don't make them
387    if lane.length() < length_max + location {
388        return Vec::new();
389    }
390
391    let (start_pt_unshifted, start_angle) = lane
392        .lane_center_pts
393        .must_dist_along(lane.length() - (length_max + location));
394    let start_pt = start_pt_unshifted.project_away(
395        offset.abs(),
396        start_angle.rotate_degs(if offset > Distance::ZERO { -90.0 } else { 90.0 }),
397    );
398
399    for turn_angle in turn_angles {
400        let half_angle =
401            Angle::degrees(turn_angle.simple_shortest_rotation_towards(Angle::ZERO) / 2.0);
402
403        let end_pt = start_pt
404            .project_away(
405                half_angle.normalized_radians().cos() * length_max,
406                start_angle,
407            )
408            .project_away(
409                half_angle.normalized_radians().sin().abs().min(0.5) * width_max,
410                start_angle
411                    + if half_angle > Angle::ZERO {
412                        Angle::degrees(90.0)
413                    } else {
414                        Angle::degrees(-90.0)
415                    },
416            );
417
418        fn to_pt(pt: Pt2D) -> Point<f64> {
419            Point::new(pt.x(), pt.y())
420        }
421
422        fn from_pt(pt: Point<f64>) -> Pt2D {
423            Pt2D::new(pt.x, pt.y)
424        }
425
426        let intersection = InfiniteLine::from_pt_angle(start_pt, start_angle)
427            .intersection(&InfiniteLine::from_pt_angle(
428                end_pt,
429                start_angle + *turn_angle,
430            ))
431            .unwrap_or(start_pt);
432        let curve = if turn_angle.approx_parallel(
433            Angle::ZERO,
434            (length_max / (width_max / 2.0)).atan().to_degrees(),
435        ) || start_pt.approx_eq(intersection, geom::EPSILON_DIST)
436        {
437            CubicBezierSegment {
438                from: to_pt(start_pt),
439                ctrl1: to_pt(start_pt.project_away(length_max / 2.0, start_angle)),
440                ctrl2: to_pt(
441                    end_pt.project_away(length_max / 2.0, (start_angle + *turn_angle).opposite()),
442                ),
443                to: to_pt(end_pt),
444            }
445        } else {
446            QuadraticBezierSegment {
447                from: to_pt(start_pt),
448                ctrl: to_pt(intersection),
449                to: to_pt(end_pt),
450            }
451            .to_cubic()
452        };
453
454        let pieces = 5;
455        let mut curve_pts: Vec<_> = (0..=pieces)
456            .map(|i| from_pt(curve.sample(1.0 / f64::from(pieces) * f64::from(i))))
457            .collect();
458        // add extra piece to ensure end segment is tangent.
459        curve_pts.push(
460            curve_pts
461                .last()
462                .unwrap()
463                .project_away(thickness, start_angle + *turn_angle),
464        );
465        curve_pts.dedup();
466
467        results.push(
468            PolyLine::new(curve_pts)
469                .unwrap()
470                .make_arrow(thickness, ArrowCap::Triangle),
471        );
472    }
473
474    results
475}
476
477fn calculate_one_way_markings(lane: &Lane, road: &Road) -> Vec<Tessellation> {
478    let mut results = Vec::new();
479    if road
480        .lanes
481        .iter()
482        .any(|l| l.dir != lane.dir && l.lane_type == LaneType::Driving)
483    {
484        // Not a one-way
485        return results;
486    }
487
488    let arrow_len = Distance::meters(1.75);
489    let thickness = Distance::meters(0.25);
490    // Stop 1m before the calculate_turn_markings() stuff starts
491    for (pt, angle) in lane.lane_center_pts.step_along_start_end(
492        Distance::meters(30.0),
493        arrow_len,
494        arrow_len + Distance::meters(8.0),
495    ) {
496        results.push(
497            PolyLine::must_new(vec![
498                pt.project_away(arrow_len / 2.0, angle.opposite()),
499                pt.project_away(arrow_len / 2.0, angle),
500            ])
501            .make_arrow(thickness * 2.0, ArrowCap::Triangle)
502            .to_outline(thickness / 2.0),
503        );
504    }
505    results
506}
507
508fn calculate_buffer_markings(
509    app: &dyn AppLike,
510    style: BufferType,
511    lane: &Lane,
512    batch: &mut GeomBatch,
513) {
514    let color = app.cs().general_road_marking;
515
516    let side_lines = |batch: &mut GeomBatch| {
517        let thickness = Distance::meters(0.25);
518        batch.push(
519            color,
520            lane.lane_center_pts
521                .must_shift_right((lane.width - thickness) / 2.0)
522                .make_polygons(thickness),
523        );
524        batch.push(
525            color,
526            lane.lane_center_pts
527                .must_shift_left((lane.width - thickness) / 2.0)
528                .make_polygons(thickness),
529        );
530    };
531
532    let stripes = |batch: &mut GeomBatch, step_size, buffer_ends| {
533        for (center, angle) in lane.lane_center_pts.step_along(step_size, buffer_ends) {
534            // Extend the stripes into the side lines
535            let thickness = Distance::meters(0.25);
536            let left = center.project_away(lane.width / 2.0 + thickness, angle.rotate_degs(45.0));
537            let right = center.project_away(
538                lane.width / 2.0 + thickness,
539                angle.rotate_degs(45.0).opposite(),
540            );
541            batch.push(
542                color,
543                Line::must_new(left, right).make_polygons(Distance::meters(0.3)),
544            );
545        }
546    };
547
548    let dark_grey = Color::grey(0.6);
549    let light_grey = Color::grey(0.8);
550    match style {
551        // TODO osm2streets is getting nice rendering logic. Treat Verge like Stripes for now,
552        // before we cutover
553        BufferType::Stripes | BufferType::Verge => {
554            side_lines(batch);
555            stripes(batch, Distance::meters(3.0), Distance::meters(5.0));
556        }
557        BufferType::FlexPosts => {
558            side_lines(batch);
559            stripes(batch, Distance::meters(3.0), Distance::meters(2.5));
560            for (pt, _) in lane
561                .lane_center_pts
562                .step_along(Distance::meters(3.0), Distance::meters(2.5 + 1.5))
563            {
564                let circle = Circle::new(pt, 0.3 * lane.width);
565                batch.push(light_grey, circle.to_polygon());
566                if let Ok(poly) = circle.to_outline(Distance::meters(0.25)) {
567                    batch.push(dark_grey, poly);
568                }
569            }
570        }
571        BufferType::Planters => {
572            side_lines(batch);
573            // TODO Center the planters between the stripes
574            stripes(batch, Distance::meters(3.0), Distance::meters(5.0));
575            for poly in lane.lane_center_pts.dashed_lines(
576                0.6 * lane.width,
577                Distance::meters(2.0),
578                Distance::meters(2.5),
579            ) {
580                batch.push(Color::hex("#108833"), poly.clone());
581                batch.push(
582                    Color::hex("#A8882A"),
583                    poly.to_outline(Distance::meters(0.25)),
584                );
585            }
586        }
587        BufferType::JerseyBarrier => {
588            let buffer_ends = Distance::meters(2.0);
589            if let Ok(pl) = lane
590                .lane_center_pts
591                .maybe_exact_slice(buffer_ends, lane.length() - buffer_ends)
592            {
593                batch.push(dark_grey, pl.make_polygons(0.8 * lane.width));
594                batch.push(light_grey, pl.make_polygons(0.5 * lane.width));
595            }
596        }
597        BufferType::Curb => {
598            batch.push(dark_grey, lane.get_thick_polygon());
599        }
600    }
601}