game/render/
pedestrian.rs

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            // A silly idea for peds... use hands to point at their turn?
37            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        // TODO Slight issues with rendering small pedestrians:
83        // - route visualization is thick
84        // - there are little skips when making turns
85        // - front paths are too skinny
86        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        // Jitter based on ID so we don't all walk synchronized.
123        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        // Expensive! :(
288        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}