map_gui/render/
road.rs
1use std::cell::RefCell;
2
3use geom::{Bounds, Distance, Pt2D, Tessellation};
4use map_model::{Building, LaneType, Map, Road, RoadID, NORMAL_LANE_THICKNESS};
5use widgetry::{Color, Drawable, GeomBatch, GfxCtx, Line, Prerender, Text};
6
7use crate::colors::ColorSchemeChoice;
8use crate::options::CameraAngle;
9use crate::render::lane::DrawLane;
10use crate::render::{DrawOptions, Renderable};
11use crate::{AppLike, ID};
12
13const LABEL_SCALE_FACTOR: f64 = 0.1;
15const DRAW_CURVEY_LABEL: bool = false;
18
19pub struct DrawRoad {
20 pub id: RoadID,
21 zorder: isize,
22
23 draw: RefCell<Option<Drawable>>,
24 pub lanes: Vec<DrawLane>,
25}
26
27impl DrawRoad {
28 pub fn new(r: &Road) -> DrawRoad {
29 DrawRoad {
30 id: r.id,
31 zorder: r.zorder,
32 draw: RefCell::new(None),
33 lanes: r.lanes.iter().map(|l| DrawLane::new(l, r)).collect(),
34 }
35 }
36
37 pub fn render<P: AsRef<Prerender>>(&self, prerender: &P, app: &dyn AppLike) -> GeomBatch {
38 let prerender = prerender.as_ref();
39 let r = app.map().get_r(self.id);
40
41 if r.is_light_rail() {
42 return GeomBatch::new();
44 }
45 let name = r.get_name(app.opts().language.as_ref());
46 let mut batch;
47 if r.length() >= Distance::meters(30.0) && name != "???" {
48 let text_width =
50 Distance::meters(Text::from(&name).rendered_width(prerender) * LABEL_SCALE_FACTOR);
51 batch = render_center_line(app, r, Some(text_width));
52
53 if DRAW_CURVEY_LABEL {
54 let fg = Color::WHITE;
55 if r.center_pts.quadrant() > 1 && r.center_pts.quadrant() < 4 {
56 batch.append(Line(name).fg(fg).outlined(Color::BLACK).render_curvey(
57 prerender,
58 &r.center_pts.reversed(),
59 LABEL_SCALE_FACTOR,
60 ));
61 } else {
62 batch.append(Line(name).fg(fg).outlined(Color::BLACK).render_curvey(
63 prerender,
64 &r.center_pts,
65 LABEL_SCALE_FACTOR,
66 ));
67 }
68 } else {
69 let mut center_line_color = app.cs().road_center_line(app.map());
70 if r.is_private() && app.cs().private_road.is_some() {
71 center_line_color = center_line_color.lerp(app.cs().private_road.unwrap(), 0.5)
72 }
73 let txt = Text::from(Line(name).fg(center_line_color));
74 let (pt, angle) = r.center_pts.must_dist_along(r.length() / 2.0);
75 batch.append(
76 txt.render_autocropped(prerender)
77 .scale(LABEL_SCALE_FACTOR)
78 .centered_on(pt)
79 .rotate_around_batch_center(angle.reorient()),
80 );
81 }
82 } else {
83 batch = render_center_line(app, r, None);
85 }
86
87 for b in app.map().road_to_buildings(self.id) {
90 draw_building_driveway(app, app.map().get_b(*b), &mut batch);
91 }
92
93 batch
94 }
95
96 pub fn clear_rendering(&mut self) {
97 *self.draw.borrow_mut() = None;
98 for l in &mut self.lanes {
99 l.clear_rendering();
100 }
101 }
102}
103
104impl Renderable for DrawRoad {
105 fn get_id(&self) -> ID {
106 ID::Road(self.id)
107 }
108
109 fn draw(&self, g: &mut GfxCtx, app: &dyn AppLike, _: &DrawOptions) {
110 let mut draw = self.draw.borrow_mut();
111 if draw.is_none() {
112 *draw = Some(g.upload(self.render(g, app)));
113 }
114 g.redraw(draw.as_ref().unwrap());
115 }
116
117 fn get_outline(&self, map: &Map) -> Tessellation {
118 Tessellation::from(map.get_r(self.id).get_thick_polygon())
120 }
121
122 fn get_bounds(&self, map: &Map) -> Bounds {
123 map.get_r(self.id).get_thick_polygon().get_bounds()
124 }
125
126 fn contains_pt(&self, pt: Pt2D, map: &Map) -> bool {
127 map.get_r(self.id).get_thick_polygon().contains_pt(pt)
128 }
129
130 fn get_zorder(&self) -> isize {
131 self.zorder
132 }
133}
134
135fn render_center_line(app: &dyn AppLike, r: &Road, text_width: Option<Distance>) -> GeomBatch {
138 let mut center_line_color = app.cs().road_center_line(app.map());
139 if r.is_private() && app.cs().private_road.is_some() {
140 center_line_color = center_line_color.lerp(app.cs().private_road.unwrap(), 0.5)
141 }
142
143 let mut batch = GeomBatch::new();
144
145 let mut width = Distance::ZERO;
147 for pair in r.lanes.windows(2) {
148 width += pair[0].width;
149 if pair[0].dir != pair[1].dir
150 && pair[0].lane_type.is_for_moving_vehicles()
151 && pair[1].lane_type.is_for_moving_vehicles()
152 {
153 let pl = r.shift_from_left_side(width).unwrap();
154 if let Some(text_width) = text_width {
155 let first_segment_distance = (pl.length() - text_width) / 2.0;
158 let last_segment_distance = first_segment_distance + text_width;
159 for slice in [
160 pl.slice(Distance::ZERO, first_segment_distance),
161 pl.slice(last_segment_distance, pl.length()),
162 ] {
163 if let Ok((line, _)) = slice {
164 batch.extend(
165 center_line_color,
166 line.dashed_lines(
167 Distance::meters(0.25),
168 Distance::meters(2.0),
169 Distance::meters(1.0),
170 ),
171 );
172 }
173 }
174 } else {
175 batch.extend(
177 center_line_color,
178 pl.dashed_lines(
179 Distance::meters(0.25),
180 Distance::meters(2.0),
181 Distance::meters(1.0),
182 ),
183 );
184 }
185 }
186 }
187
188 batch
189}
190
191fn draw_building_driveway(app: &dyn AppLike, bldg: &Building, batch: &mut GeomBatch) {
192 if app.opts().camera_angle == CameraAngle::Abstract || !app.opts().show_building_driveways {
193 return;
194 }
195
196 let orig_pl = &bldg.driveway_geom;
199 let driveway = orig_pl
200 .slice(
201 Distance::ZERO,
202 orig_pl.length() - app.map().get_l(bldg.sidewalk()).width / 2.0,
203 )
204 .map(|(pl, _)| pl)
205 .unwrap_or_else(|_| orig_pl.clone());
206 if driveway.length() > Distance::meters(0.1) {
207 batch.push(
208 if app.opts().color_scheme == ColorSchemeChoice::NightMode {
209 Color::hex("#4B4B4B")
210 } else {
211 app.cs().zoomed_road_surface(
212 LaneType::Sidewalk,
213 app.map().get_parent(bldg.sidewalk()).get_rank(),
214 )
215 },
216 driveway.make_polygons(NORMAL_LANE_THICKNESS),
217 );
218 }
219}