map_gui/render/
map.rs

1use std::collections::HashMap;
2
3use abstutil::Timer;
4use geom::{Bounds, Distance, QuadTree, Tessellation};
5use map_model::{
6    AreaID, BuildingID, IntersectionID, LaneID, Map, ParkingLotID, Road, RoadID, TransitStopID,
7};
8use widgetry::{Color, Drawable, EventCtx, Fill, GeomBatch};
9
10use crate::colors::ColorScheme;
11use crate::options::Options;
12use crate::render::building::DrawBuilding;
13use crate::render::intersection::DrawIntersection;
14use crate::render::lane::DrawLane;
15use crate::render::parking_lot::DrawParkingLot;
16use crate::render::road::DrawRoad;
17use crate::render::transit_stop::DrawTransitStop;
18use crate::render::{DrawArea, Renderable};
19use crate::{AppLike, ID};
20
21pub struct DrawMap {
22    pub roads: Vec<DrawRoad>,
23    pub intersections: Vec<DrawIntersection>,
24    pub buildings: Vec<DrawBuilding>,
25    pub parking_lots: Vec<DrawParkingLot>,
26    pub bus_stops: HashMap<TransitStopID, DrawTransitStop>,
27    pub areas: Vec<DrawArea>,
28
29    pub boundary_polygon: Drawable,
30    pub draw_all_unzoomed_roads_and_intersections: Drawable,
31    pub draw_all_buildings: Drawable,
32    pub draw_all_building_outlines: Drawable,
33    pub draw_all_unzoomed_parking_lots: Drawable,
34    pub draw_all_areas: Drawable,
35
36    pub zorder_range: (isize, isize),
37    pub show_zorder: isize,
38
39    quadtree: QuadTree<ID>,
40}
41
42impl DrawMap {
43    pub fn new(
44        ctx: &mut EventCtx,
45        map: &Map,
46        opts: &Options,
47        cs: &ColorScheme,
48        timer: &mut Timer,
49    ) -> DrawMap {
50        let mut roads: Vec<DrawRoad> = Vec::new();
51        let mut low_z = 0;
52        let mut high_z = 0;
53        timer.start_iter("make DrawRoads", map.all_roads().len());
54        for r in map.all_roads() {
55            timer.next();
56            roads.push(DrawRoad::new(r));
57            low_z = low_z.min(r.zorder);
58            high_z = high_z.max(r.zorder);
59        }
60
61        let mut intersections: Vec<DrawIntersection> = Vec::new();
62        timer.start_iter("make DrawIntersections", map.all_intersections().len());
63        for i in map.all_intersections() {
64            timer.next();
65            intersections.push(DrawIntersection::new(i, map));
66        }
67
68        let draw_all_unzoomed_roads_and_intersections =
69            DrawMap::regenerate_unzoomed_layer(ctx, map, cs, opts, timer);
70
71        let (buildings, draw_all_buildings, draw_all_building_outlines) =
72            DrawMap::regenerate_buildings(ctx, map, cs, opts, timer);
73
74        timer.start("make DrawParkingLot");
75        let (parking_lots, draw_all_unzoomed_parking_lots) =
76            DrawMap::regenerate_parking_lots(ctx, map, cs, opts);
77        timer.stop("make DrawParkingLot");
78
79        timer.start_iter("make DrawTransitStop", map.all_transit_stops().len());
80        let mut bus_stops: HashMap<TransitStopID, DrawTransitStop> = HashMap::new();
81        for s in map.all_transit_stops().values() {
82            timer.next();
83            bus_stops.insert(s.id, DrawTransitStop::new(ctx, s, map, cs));
84        }
85
86        let mut areas: Vec<DrawArea> = Vec::new();
87        let mut all_areas = GeomBatch::new();
88        timer.start_iter("make DrawAreas", map.all_areas().len());
89        for a in map.all_areas() {
90            timer.next();
91            areas.push(DrawArea::new(ctx, a, cs, &mut all_areas));
92        }
93        timer.start("upload all areas");
94        let draw_all_areas = all_areas.upload(ctx);
95        timer.stop("upload all areas");
96
97        let boundary_polygon = ctx.upload(GeomBatch::from(vec![(
98            cs.map_background.clone(),
99            map.get_boundary_polygon().clone(),
100        )]));
101
102        timer.start("create quadtree");
103        let mut quadtree = QuadTree::builder();
104        // TODO use iter chain if everything was boxed as a renderable...
105        for obj in &roads {
106            quadtree.add_with_box(obj.get_id(), obj.get_bounds(map));
107        }
108        for obj in &intersections {
109            quadtree.add_with_box(obj.get_id(), obj.get_bounds(map));
110        }
111        for obj in &buildings {
112            quadtree.add_with_box(obj.get_id(), obj.get_bounds(map));
113        }
114        for obj in &parking_lots {
115            quadtree.add_with_box(obj.get_id(), obj.get_bounds(map));
116        }
117        // Don't put TransitStops in the quadtree
118        for obj in &areas {
119            quadtree.add_with_box(obj.get_id(), obj.get_bounds(map));
120        }
121        let quadtree = quadtree.build();
122        timer.stop("create quadtree");
123
124        info!(
125            "static DrawMap consumes {} MB on the GPU",
126            abstutil::prettyprint_usize(ctx.prerender.get_total_bytes_uploaded() / 1024 / 1024)
127        );
128
129        let bounds = map.get_bounds();
130        ctx.canvas.map_dims = (bounds.width(), bounds.height());
131
132        DrawMap {
133            roads,
134            intersections,
135            buildings,
136            parking_lots,
137            bus_stops,
138            areas,
139            boundary_polygon,
140            draw_all_unzoomed_roads_and_intersections,
141            draw_all_buildings,
142            draw_all_building_outlines,
143            draw_all_unzoomed_parking_lots,
144            draw_all_areas,
145
146            quadtree,
147
148            zorder_range: (low_z, high_z),
149            show_zorder: high_z,
150        }
151    }
152
153    pub fn regenerate_buildings(
154        ctx: &EventCtx,
155        map: &Map,
156        cs: &ColorScheme,
157        opts: &Options,
158        timer: &mut Timer,
159    ) -> (Vec<DrawBuilding>, Drawable, Drawable) {
160        let mut buildings: Vec<DrawBuilding> = Vec::new();
161        let mut all_buildings = GeomBatch::new();
162        let mut all_building_outlines = GeomBatch::new();
163        timer.start_iter("make DrawBuildings", map.all_buildings().len());
164        for b in map.all_buildings() {
165            timer.next();
166            buildings.push(DrawBuilding::new(
167                ctx,
168                b,
169                map,
170                cs,
171                opts,
172                &mut all_buildings,
173                &mut all_building_outlines,
174            ));
175        }
176        timer.start("upload all buildings");
177        let draw_all_buildings = all_buildings.upload(ctx);
178        let draw_all_building_outlines = all_building_outlines.upload(ctx);
179        timer.stop("upload all buildings");
180        (buildings, draw_all_buildings, draw_all_building_outlines)
181    }
182
183    pub fn regenerate_parking_lots(
184        ctx: &EventCtx,
185        map: &Map,
186        cs: &ColorScheme,
187        opts: &Options,
188    ) -> (Vec<DrawParkingLot>, Drawable) {
189        let mut parking_lots: Vec<DrawParkingLot> = Vec::new();
190        let mut all_unzoomed_parking_lots = GeomBatch::new();
191        for pl in map.all_parking_lots() {
192            parking_lots.push(DrawParkingLot::new(
193                ctx,
194                pl,
195                cs,
196                opts,
197                &mut all_unzoomed_parking_lots,
198            ));
199        }
200        (parking_lots, all_unzoomed_parking_lots.upload(ctx))
201    }
202
203    pub fn regenerate_unzoomed_layer(
204        ctx: &EventCtx,
205        map: &Map,
206        cs: &ColorScheme,
207        opts: &Options,
208        timer: &mut Timer,
209    ) -> Drawable {
210        timer.start("generate unzoomed roads and intersections");
211
212        // TODO Different in night mode
213        let outline_color = Color::BLACK;
214        let outline_thickness = Distance::meters(1.0);
215        // We want the outlines slightly above the equivalent layer. z-order is an isize, and f64
216        // makes sort_by_key annoying, so just multiply the existing z-orders by 10.
217        let outline_z_offset = 5;
218        let mut unzoomed_pieces: Vec<(isize, Fill, Tessellation)> = Vec::new();
219
220        for r in map.all_roads() {
221            let width = r.get_width();
222
223            unzoomed_pieces.push((
224                10 * r.zorder,
225                Fill::Color(if r.is_light_rail() {
226                    cs.light_rail_track
227                } else if r.is_cycleway() {
228                    cs.unzoomed_cycleway
229                } else if r.is_footway() {
230                    cs.unzoomed_footway
231                } else if r.is_private() && cs.private_road.is_some() {
232                    cs.private_road.unwrap()
233                } else {
234                    cs.unzoomed_road_surface(r.get_rank())
235                }),
236                r.center_pts.make_polygons(width).into(),
237            ));
238
239            if cs.road_outlines {
240                // Draw a thick outline on the left and right
241                for pl in [
242                    r.center_pts.shift_left(width / 2.0),
243                    r.center_pts.shift_right(width / 2.0),
244                ]
245                .into_iter()
246                .flatten()
247                {
248                    if (opts.simplify_basemap && r.is_cycleway()) || r.is_footway() {
249                        for p in pl.exact_dashed_polygons(
250                            0.5 * outline_thickness,
251                            Distance::meters(5.0),
252                            Distance::meters(2.0),
253                        ) {
254                            unzoomed_pieces.push((
255                                10 * r.zorder + outline_z_offset,
256                                outline_color.into(),
257                                p.into(),
258                            ));
259                        }
260                    } else {
261                        unzoomed_pieces.push((
262                            10 * r.zorder + outline_z_offset,
263                            outline_color.into(),
264                            pl.make_polygons(outline_thickness).into(),
265                        ));
266                    }
267                }
268            }
269        }
270
271        let traffic_signal_icon = if opts.show_traffic_signal_icon {
272            GeomBatch::load_svg(ctx, "system/assets/map/traffic_signal.svg").scale(0.8)
273        } else {
274            GeomBatch::new()
275        };
276
277        for i in map.all_intersections() {
278            let zorder = 10 * i.get_zorder(map);
279            let intersection_color = if opts.simplify_basemap
280                || i.is_stop_sign()
281                || (i.is_traffic_signal() && opts.show_traffic_signal_icon)
282            {
283                // Use the color of the road, so the intersection doesn't stand out
284                // TODO When cycleways meet footways, we fallback to unzoomed_road_surface. Maybe
285                // we need a ranking for types here too
286                if i.is_light_rail(map) {
287                    cs.light_rail_track
288                } else if i.is_cycleway(map) {
289                    cs.unzoomed_cycleway
290                } else if i.is_footway(map) {
291                    cs.unzoomed_footway
292                } else if i.is_private(map) && cs.private_road.is_some() {
293                    cs.private_road.unwrap()
294                } else {
295                    cs.unzoomed_road_surface(i.get_rank(map))
296                }
297            } else {
298                cs.unzoomed_interesting_intersection
299            };
300            unzoomed_pieces.push((zorder, intersection_color.into(), i.polygon.clone().into()));
301
302            if cs.road_outlines {
303                // It'd be nice to dash the outline for footways, but usually the pieces of the
304                // outline in between the roads are too small to dash, and using the entire thing
305                // would look like the intersection is blocked off
306                for pl in DrawIntersection::get_unzoomed_outline(i, map) {
307                    unzoomed_pieces.push((
308                        zorder + outline_z_offset,
309                        outline_color.into(),
310                        pl.make_polygons(outline_thickness).into(),
311                    ));
312                }
313            }
314
315            if opts.show_traffic_signal_icon && i.is_traffic_signal() {
316                // When the intersection has several z-orders meeting, we want to take the highest,
317                // so the icon is drawn over any connecting roads.
318                let icon_zorder = 10 * i.roads.iter().map(|r| map.get_r(*r).zorder).max().unwrap();
319                for (fill, polygon, _) in traffic_signal_icon
320                    .clone()
321                    .centered_on(i.polygon.polylabel())
322                    .consume()
323                {
324                    unzoomed_pieces.push((icon_zorder + outline_z_offset, fill, polygon));
325                }
326            }
327        }
328        unzoomed_pieces.sort_by_key(|(z, _, _)| *z);
329        let mut unzoomed_batch = GeomBatch::new();
330        for (_, fill, poly) in unzoomed_pieces {
331            unzoomed_batch.push(fill, poly);
332        }
333
334        let draw_all_unzoomed_roads_and_intersections = unzoomed_batch.upload(ctx);
335        timer.stop("generate unzoomed roads and intersections");
336        draw_all_unzoomed_roads_and_intersections
337    }
338
339    // The alt to these is implementing std::ops::Index, but that's way more verbose!
340    pub fn get_r(&self, id: RoadID) -> &DrawRoad {
341        &self.roads[id.0]
342    }
343
344    pub fn get_l(&self, id: LaneID) -> &DrawLane {
345        &self.get_r(id.road).lanes[id.offset]
346    }
347
348    pub fn get_i(&self, id: IntersectionID) -> &DrawIntersection {
349        &self.intersections[id.0]
350    }
351
352    pub fn get_b(&self, id: BuildingID) -> &DrawBuilding {
353        &self.buildings[id.0]
354    }
355
356    pub fn get_pl(&self, id: ParkingLotID) -> &DrawParkingLot {
357        &self.parking_lots[id.0]
358    }
359
360    pub fn get_ts(&self, id: TransitStopID) -> &DrawTransitStop {
361        &self.bus_stops[&id]
362    }
363
364    pub fn get_a(&self, id: AreaID) -> &DrawArea {
365        &self.areas[id.0]
366    }
367
368    pub fn get_obj<'a>(&self, id: ID) -> &dyn Renderable {
369        match id {
370            ID::Road(id) => self.get_r(id),
371            ID::Lane(id) => self.get_l(id),
372            ID::Intersection(id) => self.get_i(id),
373            ID::Building(id) => self.get_b(id),
374            ID::ParkingLot(id) => self.get_pl(id),
375            ID::TransitStop(id) => self.get_ts(id),
376            ID::Area(id) => self.get_a(id),
377        }
378    }
379
380    /// Unsorted, unexpanded, raw result.
381    pub fn get_matching_objects(&self, bounds: Bounds) -> Vec<ID> {
382        self.quadtree
383            .query_bbox_borrow(bounds)
384            .map(|id| id.clone())
385            .collect()
386    }
387
388    /// A simple variation of the one in game that shows all layers, ignores dynamic agents.
389    pub fn get_renderables_back_to_front(&self, bounds: Bounds, map: &Map) -> Vec<&dyn Renderable> {
390        let mut areas: Vec<&dyn Renderable> = Vec::new();
391        let mut parking_lots: Vec<&dyn Renderable> = Vec::new();
392        let mut lanes: Vec<&dyn Renderable> = Vec::new();
393        let mut roads: Vec<&dyn Renderable> = Vec::new();
394        let mut intersections: Vec<&dyn Renderable> = Vec::new();
395        let mut buildings: Vec<&dyn Renderable> = Vec::new();
396        let mut transit_stops: Vec<&dyn Renderable> = Vec::new();
397
398        for id in self.get_matching_objects(bounds) {
399            match id {
400                ID::Area(id) => areas.push(self.get_a(id)),
401                ID::Road(id) => {
402                    let road = self.get_r(id);
403                    for lane in &road.lanes {
404                        lanes.push(lane);
405                    }
406                    for ts in &map.get_r(id).transit_stops {
407                        transit_stops.push(self.get_ts(*ts));
408                    }
409                    roads.push(road);
410                }
411                ID::Intersection(id) => {
412                    intersections.push(self.get_i(id));
413                }
414                ID::Building(id) => buildings.push(self.get_b(id)),
415                ID::ParkingLot(id) => {
416                    parking_lots.push(self.get_pl(id));
417                }
418                ID::Lane(_) | ID::TransitStop(_) => {
419                    panic!("{:?} shouldn't be in the quadtree", id)
420                }
421            }
422        }
423
424        // From background to foreground Z-order
425        let mut borrows: Vec<&dyn Renderable> = Vec::new();
426        borrows.extend(areas);
427        borrows.extend(parking_lots);
428        borrows.extend(lanes);
429        borrows.extend(roads);
430        borrows.extend(intersections);
431        borrows.extend(buildings);
432        borrows.extend(transit_stops);
433
434        borrows.retain(|x| x.get_zorder() <= self.show_zorder);
435
436        // This is a stable sort.
437        borrows.sort_by_key(|x| x.get_zorder());
438
439        borrows
440    }
441
442    /// Build a single gigantic `GeomBatch` to render the entire map when zoomed in. Likely messes
443    /// up Z-ordering.
444    pub fn zoomed_batch(ctx: &EventCtx, app: &dyn AppLike) -> GeomBatch {
445        // TODO This repeats code. There are other approaches, like making EventCtx intercept
446        // "uploads" and instead save the batches.
447        let mut batch = GeomBatch::new();
448        let map = app.map();
449        let cs = app.cs();
450
451        batch.push(
452            cs.map_background.clone(),
453            map.get_boundary_polygon().clone(),
454        );
455
456        for a in map.all_areas() {
457            DrawArea::new(ctx, a, cs, &mut batch);
458        }
459
460        for pl in map.all_parking_lots() {
461            batch.append(
462                DrawParkingLot::new(ctx, pl, cs, app.opts(), &mut GeomBatch::new()).render(app),
463            );
464        }
465
466        for r in map.all_roads() {
467            for l in &r.lanes {
468                batch.append(DrawLane::new(l, r).render(ctx, app));
469            }
470        }
471
472        for r in map.all_roads() {
473            batch.append(DrawRoad::new(r).render(ctx, app));
474        }
475
476        for i in map.all_intersections() {
477            batch.append(DrawIntersection::new(i, map).render(ctx, app));
478        }
479
480        let mut bldgs_batch = GeomBatch::new();
481        let mut outlines_batch = GeomBatch::new();
482        for b in map.all_buildings() {
483            DrawBuilding::new(
484                ctx,
485                b,
486                map,
487                cs,
488                app.opts(),
489                &mut bldgs_batch,
490                &mut outlines_batch,
491            );
492        }
493        batch.append(bldgs_batch);
494        batch.append(outlines_batch);
495
496        batch
497    }
498
499    pub fn recreate_intersection(&mut self, i: IntersectionID, map: &Map) {
500        self.quadtree.remove(ID::Intersection(i)).unwrap();
501
502        let draw = DrawIntersection::new(map.get_i(i), map);
503        self.quadtree
504            .insert_with_box(draw.get_id(), draw.get_bounds(map));
505        self.intersections[i.0] = draw;
506    }
507
508    pub fn recreate_road(&mut self, road: &Road, map: &Map) {
509        self.quadtree.remove(ID::Road(road.id)).unwrap();
510
511        let draw = DrawRoad::new(road);
512        self.quadtree
513            .insert_with_box(draw.get_id(), draw.get_bounds(map));
514        self.roads[road.id.0] = draw;
515    }
516
517    pub fn free_memory(&mut self) {
518        // Clear the lazily evaluated zoomed-in details
519        for r in &mut self.roads {
520            r.clear_rendering();
521        }
522        for i in &mut self.intersections {
523            i.clear_rendering();
524        }
525        for b in &mut self.buildings {
526            b.clear_rendering();
527        }
528        for pl in &mut self.parking_lots {
529            pl.clear_rendering();
530        }
531    }
532}