sim/mechanics/
car.rs

1use std::collections::{BTreeSet, VecDeque};
2
3use serde::{Deserialize, Serialize};
4
5use geom::{Distance, Duration, PolyLine, Time, EPSILON_DIST};
6use map_model::{Direction, LaneID, Map, Traversable};
7
8use crate::{
9    CarID, CarStatus, DistanceInterval, DrawCarInput, Intent, ParkingSpot, PersonID, Router,
10    TimeInterval, TransitSimState, TripID, Vehicle, VehicleType,
11};
12
13/// Represents a single vehicle. Note "car" is a misnomer; it could also be a bus or bike.
14#[derive(Debug, Serialize, Deserialize, Clone)]
15pub(crate) struct Car {
16    pub vehicle: Vehicle,
17    pub state: CarState,
18    pub router: Router,
19    /// None for buses
20    // TODO Can we scrap person here and use vehicle owner?
21    pub trip_and_person: Option<(TripID, PersonID)>,
22    pub started_at: Time,
23    pub total_blocked_time: Duration,
24
25    /// In reverse order -- most recently left is first. The sum length of these must be >=
26    /// vehicle.length.
27    pub last_steps: VecDeque<Traversable>,
28
29    /// Since lane over-taking isn't implemented yet, a vehicle tends to be stuck behind a slow
30    /// leader for a while. Avoid duplicate events.
31    pub wants_to_overtake: BTreeSet<CarID>,
32}
33
34impl Car {
35    /// Assumes the current head of the path is the thing to cross.
36    pub fn crossing_state(&self, start_dist: Distance, start_time: Time, map: &Map) -> CarState {
37        let end_dist = if self.router.last_step() {
38            self.router.get_end_dist()
39        } else {
40            self.router.head().get_polyline(map).length()
41        };
42        if end_dist < start_dist {
43            panic!(
44                "{} trying to make a crossing_state from {} to {} at {}. Something's very wrong",
45                self.vehicle.id, start_dist, end_dist, start_time
46            );
47        }
48
49        let dist_int = DistanceInterval::new_driving(start_dist, end_dist);
50        self.crossing_state_with_end_dist(dist_int, start_time, map)
51    }
52
53    pub fn crossing_state_with_end_dist(
54        &self,
55        dist_int: DistanceInterval,
56        start_time: Time,
57        map: &Map,
58    ) -> CarState {
59        let (speed, percent_incline) = self
60            .router
61            .get_path()
62            .current_step()
63            .max_speed_and_incline_along(
64                self.vehicle.max_speed,
65                self.vehicle.vehicle_type.to_constraints(),
66                map,
67            );
68        let dt = (dist_int.end - dist_int.start) / speed;
69        CarState::Crossing {
70            time_int: TimeInterval::new(start_time, start_time + dt),
71            dist_int,
72            steep_uphill: percent_incline >= 0.08,
73        }
74    }
75
76    pub fn get_draw_car(
77        &self,
78        front: Distance,
79        now: Time,
80        map: &Map,
81        transit: &TransitSimState,
82    ) -> DrawCarInput {
83        assert!(front >= Distance::ZERO);
84        // This goes from back to front
85        let mut partly_on = Vec::new();
86        let raw_body = if front >= self.vehicle.length {
87            self.router
88                .head()
89                .get_polyline(map)
90                .exact_slice(front - self.vehicle.length, front)
91        } else {
92            // TODO This is redoing some of the Path::trace work...
93            let mut result = self
94                .router
95                .head()
96                .get_polyline(map)
97                .slice(Distance::ZERO, front)
98                .map(|(pl, _)| pl.into_points())
99                .ok()
100                .unwrap_or_else(Vec::new);
101            let mut leftover = self.vehicle.length - front;
102            let mut i = 0;
103            while leftover > Distance::ZERO {
104                if i == self.last_steps.len() {
105                    // The vehicle is gradually appearing from somewhere. That's fine, just return
106                    // a truncated body.
107                    break;
108                }
109                partly_on.push(self.last_steps[i]);
110                let len = self.last_steps[i].get_polyline(map).length();
111                let start = (len - leftover).max(Distance::ZERO);
112                let piece = self.last_steps[i]
113                    .get_polyline(map)
114                    .slice(start, len)
115                    .map(|(pl, _)| pl.into_points())
116                    .ok()
117                    .unwrap_or_else(Vec::new);
118                result = match PolyLine::append(piece, result) {
119                    Ok(pl) => pl,
120                    Err(err) => panic!(
121                        "{} at {} has weird geom along {:?}: {}",
122                        self.vehicle.id, now, self.last_steps, err
123                    ),
124                };
125                leftover -= len;
126                i += 1;
127            }
128
129            if result.len() < 2 {
130                // Vehicles spawning at a border start with their front at literally 0 distance.
131                // Usually by the time we first try to render, they've advanced at least a little.
132                // But sometimes there's a race when we try to immediately draw them.
133                if let Ok((pl, _)) = self
134                    .router
135                    .head()
136                    .get_polyline(map)
137                    .slice(Distance::ZERO, 2.0 * EPSILON_DIST)
138                {
139                    result = pl.into_points();
140                }
141            }
142            match PolyLine::new(result) {
143                Ok(pl) => pl,
144                Err(err) => panic!("Weird body for {} at {}: {}", self.vehicle.id, now, err),
145            }
146        };
147
148        let body = match self.state {
149            CarState::ChangingLanes {
150                from,
151                to,
152                ref lc_time,
153                ..
154            } => {
155                let percent_time = 1.0 - lc_time.percent(now);
156                // TODO Can probably simplify this! Lifted from the parking case
157                // The car's body is already at 'to', so shift back
158                let mut diff = (to.offset as isize) - (from.offset as isize);
159                let from = map.get_l(from);
160                if from.dir == Direction::Fwd {
161                    diff *= -1;
162                }
163                // TODO Careful with this width math
164                let width = from.width * (diff as f64) * percent_time;
165                match raw_body.shift_right(width) {
166                    Ok(pl) => pl,
167                    Err(err) => {
168                        println!(
169                            "Body for lane-changing {} at {} broken: {}",
170                            self.vehicle.id, now, err
171                        );
172                        raw_body
173                    }
174                }
175            }
176            CarState::Unparking {
177                ref spot,
178                ref time_int,
179                ..
180            }
181            | CarState::Parking(_, ref spot, ref time_int) => {
182                let (percent_time, is_parking) = match self.state {
183                    CarState::Unparking { .. } => (1.0 - time_int.percent(now), false),
184                    CarState::Parking(_, _, _) => (time_int.percent(now), true),
185                    _ => unreachable!(),
186                };
187                match spot {
188                    ParkingSpot::Onstreet(parking_l, _) => {
189                        let driving_offset = self.router.head().as_lane().offset;
190                        let parking_offset = parking_l.offset;
191                        let mut diff = (parking_offset as isize) - (driving_offset as isize);
192                        if map.get_l(self.router.head().as_lane()).dir == Direction::Back {
193                            diff *= -1;
194                        }
195                        // TODO Sum widths in between, don't assume they're all the same as the
196                        // parking lane width!
197                        let width = map.get_l(*parking_l).width * (diff as f64) * percent_time;
198                        match raw_body.shift_right(width) {
199                            Ok(pl) => pl,
200                            Err(err) => {
201                                println!(
202                                    "Body for onstreet {} at {} broken: {}",
203                                    self.vehicle.id, now, err
204                                );
205                                raw_body
206                            }
207                        }
208                    }
209                    _ => {
210                        let driveway = match spot {
211                            ParkingSpot::Offstreet(b, _) => {
212                                map.get_b(*b).driving_connection(map).unwrap().1
213                            }
214                            ParkingSpot::Lot(pl, _) => map.get_pl(*pl).driveway_line.clone(),
215                            _ => unreachable!(),
216                        };
217
218                        // Append the car's polyline on the street with the driveway
219                        let maybe_full_piece = if is_parking {
220                            raw_body.clone().extend(driveway.reversed())
221                        } else {
222                            // It's possible to exit a driveway onto something other than the lane
223                            // closest to the building. So use force_extend to handle possibly
224                            // mismatching points.
225                            driveway
226                                .clone()
227                                .force_extend(raw_body.clone())
228                                .map(|pl| pl.reversed())
229                        };
230                        let sliced = match maybe_full_piece {
231                            Ok(full_piece) => {
232                                // Then make the car creep along the added length of the driveway (which
233                                // could be really short)
234                                let creep_along = driveway.length() * percent_time;
235                                // TODO Ideally the car would slowly (dis)appear into the building, but
236                                // some stuff downstream needs to understand that the windows and such will
237                                // get cut off. :)
238                                full_piece
239                                    .exact_slice(creep_along, creep_along + self.vehicle.length)
240                            }
241                            Err(err) => {
242                                // Just avoid crashing; we'll display something nonsensical (just
243                                // part of the car body on the lane) in the meantime
244                                error!(
245                                    "Body and driveway for {} at {} broken: {}",
246                                    self.vehicle.id, now, err
247                                );
248                                raw_body
249                            }
250                        };
251                        if is_parking {
252                            sliced
253                        } else {
254                            sliced.reversed()
255                        }
256                    }
257                }
258            }
259            _ => raw_body,
260        };
261
262        DrawCarInput {
263            id: self.vehicle.id,
264            waiting_for_turn: match self.state {
265                // TODO Maybe also when Crossing?
266                CarState::WaitingToAdvance { .. } | CarState::Queued { .. } => {
267                    match self.router.maybe_next() {
268                        Some(Traversable::Turn(t)) => Some(t),
269                        _ => None,
270                    }
271                }
272                _ => None,
273            },
274            status: match self.state {
275                CarState::Queued { .. } => CarStatus::Moving,
276                CarState::WaitingToAdvance { .. } => CarStatus::Moving,
277                CarState::Crossing { .. } => CarStatus::Moving,
278                CarState::ChangingLanes { .. } => CarStatus::Moving,
279                CarState::Unparking { .. } => CarStatus::Moving,
280                CarState::Parking(_, _, _) => CarStatus::Moving,
281                // Changing color for idling buses is helpful
282                CarState::IdlingAtStop(_, _) => CarStatus::Parked,
283            },
284            intent: if self.is_parking() || matches!(self.state, CarState::Unparking { .. }) {
285                Some(Intent::Parking)
286            } else {
287                match self.state {
288                    CarState::Crossing { steep_uphill, .. } if steep_uphill => {
289                        Some(Intent::SteepUphill)
290                    }
291                    _ => None,
292                }
293            },
294            on: self.router.head(),
295            partly_on,
296            label: if self.vehicle.vehicle_type == VehicleType::Bus
297                || self.vehicle.vehicle_type == VehicleType::Train
298            {
299                Some(
300                    map.get_tr(transit.bus_route(self.vehicle.id))
301                        .short_name
302                        .clone(),
303                )
304            } else {
305                None
306            },
307            body,
308            person: self.trip_and_person.map(|(_, p)| p),
309        }
310    }
311
312    pub fn is_parking(&self) -> bool {
313        if let CarState::Parking(_, _, _) = self.state {
314            return true;
315        }
316        self.router.is_parking()
317    }
318}
319
320/// See <https://a-b-street.github.io/docs/tech/trafficsim/discrete_event.html> for details about the
321/// state machine encoded here.
322#[derive(Debug, Serialize, Deserialize, Clone)]
323pub(crate) enum CarState {
324    Crossing {
325        time_int: TimeInterval,
326        dist_int: DistanceInterval,
327        steep_uphill: bool,
328    },
329    ChangingLanes {
330        from: LaneID,
331        to: LaneID,
332        // For the most part, act just like a Crossing state with these intervals
333        new_time: TimeInterval,
334        new_dist: DistanceInterval,
335        // How long does the lane-changing itself last? This must end before new_time_int does.
336        lc_time: TimeInterval,
337    },
338    Queued {
339        blocked_since: Time,
340        want_to_change_lanes: Option<LaneID>,
341    },
342    WaitingToAdvance {
343        blocked_since: Time,
344    },
345    /// Where's the front of the car while this is happening?
346    Unparking {
347        front: Distance,
348        spot: ParkingSpot,
349        time_int: TimeInterval,
350        blocked_starts: Vec<LaneID>,
351    },
352    Parking(Distance, ParkingSpot, TimeInterval),
353    IdlingAtStop(Distance, TimeInterval),
354}
355
356impl CarState {
357    pub fn get_end_time(&self) -> Time {
358        match self {
359            CarState::Crossing { ref time_int, .. } => time_int.end,
360            CarState::Queued { .. } => unreachable!(),
361            CarState::WaitingToAdvance { .. } => unreachable!(),
362            // Note this state lasts for lc_time, NOT for new_time.
363            CarState::ChangingLanes { ref lc_time, .. } => lc_time.end,
364            CarState::Unparking { ref time_int, .. } => time_int.end,
365            CarState::Parking(_, _, ref time_int) => time_int.end,
366            CarState::IdlingAtStop(_, ref time_int) => time_int.end,
367        }
368    }
369
370    pub fn time_spent_waiting(&self, now: Time) -> Duration {
371        match self {
372            CarState::Queued { blocked_since, .. }
373            | CarState::WaitingToAdvance { blocked_since } => now - *blocked_since,
374            _ => Duration::ZERO,
375        }
376    }
377}