santa/
player.rs

1use std::collections::{HashMap, HashSet};
2
3use abstutil::MultiMap;
4use geom::{Angle, Circle, Distance, PolyLine, Pt2D, Speed};
5use map_gui::ID;
6use map_model::{BuildingID, Direction, IntersectionID, LaneType, RoadID};
7use widgetry::EventCtx;
8
9use crate::controls::InstantController;
10use crate::App;
11
12const ZOOM: f64 = 10.0;
13
14pub struct Player {
15    pos: Pt2D,
16    facing: Angle,
17    on: On,
18    bldgs_along_road: BuildingsAlongRoad,
19
20    controls: InstantController,
21}
22
23impl Player {
24    pub fn new(ctx: &mut EventCtx, app: &App, start: IntersectionID) -> Player {
25        ctx.canvas.cam_zoom = ZOOM;
26        let pos = app.map.get_i(start).polygon.center();
27        ctx.canvas.center_on_map_pt(pos);
28
29        Player {
30            pos,
31            facing: Angle::ZERO,
32            on: On::Intersection(start),
33            bldgs_along_road: BuildingsAlongRoad::new(app),
34
35            controls: InstantController::new(),
36        }
37    }
38
39    /// Returns any buildings we passed
40    pub fn update_with_speed(
41        &mut self,
42        ctx: &mut EventCtx,
43        app: &App,
44        speed: Speed,
45    ) -> Vec<BuildingID> {
46        if let Some((dx, dy)) = self.controls.displacement(ctx, speed) {
47            self.apply_displacement(ctx, app, dx, dy, true)
48        // TODO Do the center_on_map_pt here, actually
49        } else {
50            Vec::new()
51        }
52    }
53
54    fn pos_to_on(&self, app: &App, pos: Pt2D) -> Option<On> {
55        // Make sure we only move between roads/intersections that're actually connected. Don't
56        // warp to bridges/tunnels.
57        let (valid_roads, valid_intersections) = self.on.get_connections(app);
58
59        // Make sure we're still on the road
60        for id in app
61            .draw_map
62            .get_matching_objects(Circle::new(pos, Distance::meters(3.0)).get_bounds())
63        {
64            if let ID::Intersection(i) = id {
65                if valid_intersections.contains(&i) && app.map.get_i(i).polygon.contains_pt(pos) {
66                    return Some(On::Intersection(i));
67                }
68            } else if let ID::Road(r) = id {
69                let road = app.map.get_r(r);
70                if valid_roads.contains(&r)
71                    && !road.is_light_rail()
72                    && road.get_thick_polygon().contains_pt(pos)
73                {
74                    // Where along the road are we?
75                    let pt_on_center_line = road.center_pts.project_pt(pos);
76                    if let Some((dist, _)) = road.center_pts.dist_along_of_point(pt_on_center_line)
77                    {
78                        // We'll adjust the direction at the call-site if we're moving along the
79                        // same road. This heuristic is reasonable for moving from intersections to
80                        // roads.
81                        let dir = if dist < road.length() / 2.0 {
82                            Direction::Fwd
83                        } else {
84                            Direction::Back
85                        };
86                        return Some(On::Road(r, dist, dir));
87                    } else {
88                        error!(
89                            "{} snapped to {} on {}, but dist_along_of_point failed",
90                            pos, pt_on_center_line, r
91                        );
92                        return None;
93                    }
94                }
95            }
96        }
97        None
98    }
99
100    fn apply_displacement(
101        &mut self,
102        ctx: &mut EventCtx,
103        app: &App,
104        dx: f64,
105        dy: f64,
106        recurse: bool,
107    ) -> Vec<BuildingID> {
108        let new_pos = self.pos.offset(dx, dy);
109        let mut buildings_passed = Vec::new();
110        if let Some(mut new_on) = self.pos_to_on(app, new_pos) {
111            self.pos = new_pos;
112            ctx.canvas.center_on_map_pt(self.pos);
113
114            if let (On::Road(r1, dist1, _), On::Road(r2, dist2, _)) =
115                (self.on.clone(), new_on.clone())
116            {
117                if r1 == r2 {
118                    // Find all buildings in this range of distance along
119                    buildings_passed.extend(self.bldgs_along_road.query_range(r1, dist1, dist2));
120                    if dist1 < dist2 {
121                        new_on = On::Road(r2, dist2, Direction::Fwd);
122                    } else {
123                        new_on = On::Road(r2, dist2, Direction::Back);
124                    }
125                }
126            }
127            self.on = new_on;
128        } else {
129            // We went out of bounds. Undo this movement.
130
131            // Apply horizontal and vertical movement independently, so we "slide" along boundaries
132            // if possible
133            if recurse {
134                let orig = self.pos;
135                if dx != 0.0 {
136                    buildings_passed.extend(self.apply_displacement(ctx, app, dx, 0.0, false));
137                }
138                if dy != 0.0 {
139                    buildings_passed.extend(self.apply_displacement(ctx, app, 0.0, dy, false));
140                }
141
142                // Are we stuck?
143                if self.pos == orig {
144                    if true {
145                        // Resolve by just bouncing in the opposite direction. Jittery, but we keep
146                        // moving.
147                        buildings_passed.extend(self.apply_displacement(ctx, app, -dx, -dy, false));
148                    } else {
149                        // Find the exact point on the boundary where we go out of bounds
150                        let old_ring = match self.on {
151                            On::Intersection(i) => {
152                                app.map.get_i(i).polygon.get_outer_ring().clone()
153                            }
154                            On::Road(r, _, _) => {
155                                let road = app.map.get_r(r);
156                                road.center_pts.to_thick_ring(road.get_width())
157                            }
158                        };
159                        // TODO Brittle order, but should be the first from the PolyLine's
160                        // perspective
161                        if let Some(pt) = old_ring
162                            .all_intersections(&PolyLine::must_new(vec![self.pos, new_pos]))
163                            .get(0)
164                        {
165                            buildings_passed.extend(self.apply_displacement(
166                                ctx,
167                                app,
168                                pt.x() - self.pos.x(),
169                                pt.y() - self.pos.y(),
170                                false,
171                            ));
172                        }
173                    }
174                }
175            }
176        }
177
178        // Snap to the center of the road
179        if let On::Road(r, dist, dir) = self.on {
180            let (pt, angle) = app.map.get_r(r).center_pts.must_dist_along(dist);
181            self.pos = pt;
182            self.facing = if dir == Direction::Fwd {
183                angle.opposite()
184            } else {
185                angle
186            };
187            ctx.canvas.center_on_map_pt(self.pos);
188        } else {
189            self.facing = self.controls.facing;
190        }
191
192        buildings_passed
193    }
194
195    pub fn get_pos(&self) -> Pt2D {
196        self.pos
197    }
198
199    pub fn get_angle(&self) -> Angle {
200        self.facing
201    }
202
203    /// Is the player currently on a road with a bus or bike lane?
204    pub fn on_good_road(&self, app: &App) -> bool {
205        let roads = match self.on {
206            On::Road(r, _, _) => vec![r],
207            On::Intersection(i) => app.map.get_i(i).roads.iter().cloned().collect(),
208        };
209        for r in roads {
210            for l in &app.map.get_r(r).lanes {
211                if l.lane_type == LaneType::Biking || l.lane_type == LaneType::Bus {
212                    return true;
213                }
214            }
215        }
216        false
217    }
218
219    /// For the game over animation
220    pub fn override_pos(&mut self, pos: Pt2D) {
221        self.pos = pos;
222    }
223}
224
225#[derive(Clone, PartialEq)]
226enum On {
227    Intersection(IntersectionID),
228    // Distance along the center line, are we facing the same direction as the road
229    Road(RoadID, Distance, Direction),
230}
231
232impl On {
233    fn get_connections(&self, app: &App) -> (HashSet<RoadID>, HashSet<IntersectionID>) {
234        let mut valid_roads = HashSet::new();
235        let mut valid_intersections = HashSet::new();
236        match self {
237            On::Road(r, _, _) => {
238                let r = app.map.get_r(*r);
239                valid_intersections.insert(r.src_i);
240                valid_intersections.insert(r.dst_i);
241                // Intersections might be pretty small
242                valid_roads.extend(app.map.get_i(r.src_i).roads.clone());
243                valid_roads.extend(app.map.get_i(r.dst_i).roads.clone());
244            }
245            On::Intersection(i) => {
246                let i = app.map.get_i(*i);
247                for r in &i.roads {
248                    valid_roads.insert(*r);
249                    // Roads can be small
250                    let r = app.map.get_r(*r);
251                    valid_intersections.insert(r.src_i);
252                    valid_intersections.insert(r.dst_i);
253                }
254            }
255        }
256        (valid_roads, valid_intersections)
257    }
258}
259
260struct BuildingsAlongRoad {
261    // For each road, all of the buildings along it. Ascending distance, with the distance matching
262    // the road's center points.
263    per_road: HashMap<RoadID, Vec<(Distance, BuildingID)>>,
264}
265
266impl BuildingsAlongRoad {
267    fn new(app: &App) -> BuildingsAlongRoad {
268        let mut raw: MultiMap<RoadID, (Distance, BuildingID)> = MultiMap::new();
269        for b in app.map.all_buildings() {
270            // TODO Happily assuming road and lane length is roughly the same
271            let road = app.map.get_parent(b.sidewalk_pos.lane());
272            let dist = match app.map.get_l(b.sidewalk_pos.lane()).dir {
273                Direction::Fwd => b.sidewalk_pos.dist_along(),
274                Direction::Back => road.length() - b.sidewalk_pos.dist_along(),
275            };
276            raw.insert(road.id, (dist, b.id));
277        }
278
279        let mut per_road = HashMap::new();
280        for (road, list) in raw.consume() {
281            // BTreeSet will sort by the distance
282            per_road.insert(road, list.into_iter().collect());
283        }
284
285        BuildingsAlongRoad { per_road }
286    }
287
288    fn query_range(&self, road: RoadID, dist1: Distance, dist2: Distance) -> Vec<BuildingID> {
289        if dist1 > dist2 {
290            return self.query_range(road, dist2, dist1);
291        }
292
293        let mut results = Vec::new();
294        if let Some(list) = self.per_road.get(&road) {
295            // TODO Binary search to find start?
296            for (dist, b) in list {
297                if *dist >= dist1 && *dist <= dist2 {
298                    results.push(*b);
299                }
300            }
301        }
302        results
303    }
304}