1use geom::{ArrowCap, Circle, Distance, PolyLine, Polygon, Pt2D, Tessellation};
2use map_gui::colors::ColorScheme;
3use map_gui::render::{DrawOptions, OUTLINE_THICKNESS};
4use map_gui::AppLike;
5use map_model::{DrivingSide, Map};
6use sim::{DrawPedCrowdInput, DrawPedestrianInput, Intent, PedCrowdLocation, PedestrianID, Sim};
7use widgetry::{Color, Drawable, GeomBatch, GfxCtx, Line, Prerender, Text};
8
9use crate::render::{grey_out_unhighlighted_people, GameRenderable};
10use crate::ID;
11
12pub struct DrawPedestrian {
13 pub id: PedestrianID,
14 body_circle: Circle,
15 zorder: isize,
16
17 draw_default: Drawable,
18}
19
20impl DrawPedestrian {
21 pub fn new(
22 input: DrawPedestrianInput,
23 step_count: usize,
24 map: &Map,
25 sim: &Sim,
26 prerender: &Prerender,
27 cs: &ColorScheme,
28 ) -> DrawPedestrian {
29 let mut draw_default = GeomBatch::new();
30 DrawPedestrian::geometry(&mut draw_default, sim, cs, &input, step_count);
31
32 let radius = sim::pedestrian_body_radius();
33 let body_circle = Circle::new(input.pos, radius);
34
35 if let Some(t) = input.waiting_for_turn {
36 let angle = input.pos.angle_to(map.get_t(t).geom.middle());
38 draw_default.push(
39 cs.turn_arrow,
40 PolyLine::must_new(vec![
41 input.pos.project_away(radius / 2.0, angle.opposite()),
42 input.pos.project_away(radius / 2.0, angle),
43 ])
44 .make_arrow(Distance::meters(0.15), ArrowCap::Triangle),
45 );
46 }
47
48 if input.intent == Some(Intent::SteepUphill) {
49 let bubble_z = -0.0001;
50 let mut bubble_batch =
51 GeomBatch::load_svg(prerender, "system/assets/map/thought_bubble.svg")
52 .scale(0.05)
53 .centered_on(input.pos)
54 .translate(2.0, -3.5)
55 .set_z_offset(bubble_z);
56 bubble_batch.append(
57 GeomBatch::load_svg(prerender, "system/assets/tools/uphill.svg")
58 .scale(0.05)
59 .centered_on(input.pos)
60 .translate(2.2, -4.2)
61 .set_z_offset(bubble_z),
62 );
63
64 draw_default.append(bubble_batch);
65 }
66
67 DrawPedestrian {
68 id: input.id,
69 body_circle,
70 zorder: input.on.get_zorder(map),
71 draw_default: prerender.upload(draw_default),
72 }
73 }
74
75 pub fn geometry(
76 batch: &mut GeomBatch,
77 sim: &Sim,
78 cs: &ColorScheme,
79 input: &DrawPedestrianInput,
80 step_count: usize,
81 ) {
82 let radius = sim::pedestrian_body_radius();
87 let body_circle = Circle::new(input.pos, radius);
88
89 let foot_radius = 0.2 * radius;
90 let hand_radius = 0.2 * radius;
91 let left_foot_angle = 30.0;
92 let right_foot_angle = -30.0;
93 let left_hand_angle = 70.0;
94 let right_hand_angle = -70.0;
95
96 let left_foot = Circle::new(
97 input
98 .pos
99 .project_away(radius, input.facing.rotate_degs(left_foot_angle)),
100 foot_radius,
101 );
102 let right_foot = Circle::new(
103 input
104 .pos
105 .project_away(radius, input.facing.rotate_degs(right_foot_angle)),
106 foot_radius,
107 );
108 let left_hand = Circle::new(
109 input
110 .pos
111 .project_away(radius, input.facing.rotate_degs(left_hand_angle)),
112 hand_radius,
113 );
114 let right_hand = Circle::new(
115 input
116 .pos
117 .project_away(radius, input.facing.rotate_degs(right_hand_angle)),
118 hand_radius,
119 );
120 let foot_color = cs.ped_foot;
121 let hand_color = cs.ped_head;
122 let jitter = input.id.0 % 2 == 0;
124 let remainder = step_count % 6;
125 if input.waiting_for_turn.is_some() || input.waiting_for_bus {
126 batch.push(foot_color, left_foot.to_polygon());
127 batch.push(foot_color, right_foot.to_polygon());
128 batch.push(hand_color, left_hand.to_polygon());
129 batch.push(hand_color, right_hand.to_polygon());
130 } else if jitter == (remainder < 3) {
131 batch.push(foot_color, left_foot.to_polygon());
132 batch.push(
133 foot_color,
134 Circle::new(
135 input
136 .pos
137 .project_away(0.9 * radius, input.facing.rotate_degs(right_foot_angle)),
138 foot_radius,
139 )
140 .to_polygon(),
141 );
142
143 batch.push(hand_color, right_hand.to_polygon());
144 batch.push(
145 hand_color,
146 Circle::new(
147 input
148 .pos
149 .project_away(0.9 * radius, input.facing.rotate_degs(left_hand_angle)),
150 hand_radius,
151 )
152 .to_polygon(),
153 );
154 } else {
155 batch.push(foot_color, right_foot.to_polygon());
156 batch.push(
157 foot_color,
158 Circle::new(
159 input
160 .pos
161 .project_away(0.9 * radius, input.facing.rotate_degs(left_foot_angle)),
162 foot_radius,
163 )
164 .to_polygon(),
165 );
166
167 batch.push(hand_color, left_hand.to_polygon());
168 batch.push(
169 hand_color,
170 Circle::new(
171 input
172 .pos
173 .project_away(0.9 * radius, input.facing.rotate_degs(right_hand_angle)),
174 hand_radius,
175 )
176 .to_polygon(),
177 );
178 };
179
180 let head_circle = Circle::new(input.pos, 0.5 * radius);
181 batch.push(
182 grey_out_unhighlighted_people(
183 if input.preparing_bike {
184 cs.ped_preparing_bike_body
185 } else {
186 cs.rotating_color_agents(input.id.0)
187 },
188 &Some(input.person),
189 sim,
190 ),
191 body_circle.to_polygon(),
192 );
193 batch.push(cs.ped_head, head_circle.to_polygon());
194 }
195}
196
197impl GameRenderable for DrawPedestrian {
198 fn get_id(&self) -> ID {
199 ID::Pedestrian(self.id)
200 }
201
202 fn draw(&self, g: &mut GfxCtx, _: &dyn AppLike, _: &DrawOptions) {
203 g.redraw(&self.draw_default);
204 }
205
206 fn get_outline(&self, _: &Map) -> Tessellation {
207 Tessellation::from(
208 Circle::new(self.body_circle.center, Distance::meters(2.0))
209 .to_outline(OUTLINE_THICKNESS)
210 .unwrap(),
211 )
212 }
213
214 fn contains_pt(&self, pt: Pt2D, _: &Map) -> bool {
215 Circle::new(self.body_circle.center, Distance::meters(2.0)).contains_pt(pt)
216 }
217
218 fn get_zorder(&self) -> isize {
219 self.zorder
220 }
221}
222
223pub struct DrawPedCrowd {
224 members: Vec<PedestrianID>,
225 blob: Polygon,
226 blob_pl: PolyLine,
227 zorder: isize,
228
229 draw_default: Drawable,
230}
231
232impl DrawPedCrowd {
233 pub fn new(
234 input: DrawPedCrowdInput,
235 map: &Map,
236 prerender: &Prerender,
237 cs: &ColorScheme,
238 ) -> DrawPedCrowd {
239 let radius = sim::pedestrian_body_radius();
240
241 let pl_shifted = match input.location {
242 PedCrowdLocation::Sidewalk(on, contraflow) => {
243 let pl_slice = on.get_polyline(map).exact_slice(input.low, input.high);
244 if contraflow == (map.get_config().driving_side == DrivingSide::Right) {
245 pl_slice.shift_left(radius)
246 } else {
247 pl_slice.shift_right(radius)
248 }
249 .unwrap_or_else(|_| on.get_polyline(map).exact_slice(input.low, input.high))
250 }
251 PedCrowdLocation::BldgDriveway(b) => map
252 .get_b(b)
253 .driveway_geom
254 .exact_slice(input.low, input.high),
255 PedCrowdLocation::LotDriveway(pl) => map
256 .get_pl(pl)
257 .sidewalk_line
258 .to_polyline()
259 .exact_slice(input.low, input.high),
260 };
261 let blob = pl_shifted.make_polygons(radius * 2.0);
262 let mut batch = GeomBatch::new();
263 batch.push(cs.ped_crowd, blob.clone());
264 batch.append(
265 Text::from(Line(format!("{}", input.members.len())).fg(Color::BLACK))
266 .render_autocropped(prerender)
267 .scale(0.02)
268 .centered_on(blob.center()),
269 );
270
271 DrawPedCrowd {
272 members: input.members,
273 blob_pl: pl_shifted,
274 blob,
275 zorder: match input.location {
276 PedCrowdLocation::Sidewalk(on, _) => on.get_zorder(map),
277 PedCrowdLocation::BldgDriveway(_) => 0,
278 PedCrowdLocation::LotDriveway(_) => 0,
279 },
280 draw_default: prerender.upload(batch),
281 }
282 }
283}
284
285impl GameRenderable for DrawPedCrowd {
286 fn get_id(&self) -> ID {
287 ID::PedCrowd(self.members.clone())
289 }
290
291 fn draw(&self, g: &mut GfxCtx, _: &dyn AppLike, _: &DrawOptions) {
292 g.redraw(&self.draw_default);
293 }
294
295 fn get_outline(&self, _: &Map) -> Tessellation {
296 self.blob_pl
297 .to_thick_boundary(sim::pedestrian_body_radius() * 2.0, OUTLINE_THICKNESS)
298 .unwrap_or_else(|| Tessellation::from(self.blob.clone()))
299 }
300
301 fn get_zorder(&self) -> isize {
302 self.zorder
303 }
304
305 fn contains_pt(&self, pt: Pt2D, _: &Map) -> bool {
306 self.blob.contains_pt(pt)
307 }
308}