traffic_sim/
vehicle.rs

1use self::acceleration::AccelerationModel;
2use self::dynamics::calc_direction;
3use crate::group::{LinkProjection, Obstacle};
4use crate::math::{project_local, rot90, CubicFn, Point2d, Vector2d};
5use crate::util::Interval;
6use crate::{Link, LinkId, LinkSet, VehicleId};
7use cgmath::prelude::*;
8use serde::{Deserialize, Serialize};
9use std::cell::Cell;
10
11mod acceleration;
12mod dynamics;
13
14/// The minimum lateral clearance for own vehicle to pass another, in m.
15const LATERAL_CLEARANCE: f64 = 0.5;
16
17/// A simulated vehicle.
18#[derive(Clone, Serialize, Deserialize, Debug)]
19pub struct Vehicle {
20    /// The vehicle's ID
21    pub(crate) id: VehicleId,
22    /// Half the vehicle's width in m.
23    half_wid: f64,
24    /// Half the vehicle's length in m.
25    half_len: f64,
26    /// Distance from vehicle's centre to centre of wheel axle.
27    wheel_base: f64,
28    /// The acceleration model
29    acc: AccelerationModel,
30    /// The longitudinal position along the current link, in m.
31    pos: f64,
32    /// The velocity in m/s.
33    vel: f64,
34    /// The number of frames that the vehicle has been stopped.
35    stop_cnt: usize,
36    /// The vehicle's route, including the link it's currently on.
37    route: Vec<LinkId>,
38    /// The number of links on the route already "entered".
39    entered: usize,
40    /// Whether the vehicle has queued to enter the next link.
41    /// Contains a sequence number which helps determine vehicle priority.
42    queued: Option<usize>,
43    /// Whether the vehicle will queue into or enter the next unentered link on its route.
44    /// 0 = no change, 1 = enqueue, 2 = enter
45    #[serde(skip)]
46    will_queue_or_enter: Cell<u8>,
47    /// Whether the vehicle can exit at the end of its route.
48    can_exit: bool,
49    /// The in-progress lane change, if there is one.
50    lane_change: Option<LaneChange>,
51    /// The world space coordinates of the centre of the vehicle.
52    world_pos: Point2d,
53    /// A world space vector tangent to the vehicle's heading.
54    world_dir: Vector2d,
55    /// The lateral extents used to compute the `rear_coords`.
56    rear_lats: Interval<f64>,
57    /// The two end points of a line behind the vehicle used for car following.
58    rear_coords: [Point2d; 2],
59}
60
61/// The attributes of a simulated vehicle.
62#[derive(Clone, Copy)]
63pub struct VehicleAttributes {
64    /// The vehicle width in m.
65    pub width: f64,
66    /// The vehicle length in m.
67    pub length: f64,
68    /// Distance from vehicle's centre to centre of wheel axle.
69    pub wheel_base: f64,
70    /// The maximum acceleration of the vehicle, in m/s^2.
71    pub max_acc: f64,
72    /// The comfortable deceleration of the vehicle, a negative number in m/s^2.
73    pub comf_dec: f64,
74}
75
76/// Represents an in-progress lane change.
77#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
78pub struct LaneChange {
79    /// The longitudinal position at which the lane change is complete.
80    pub end_pos: f64,
81    /// The vehicle's lateral offset during the lane change.
82    pub offset: CubicFn,
83}
84
85/// The result of an `get_route` call.
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub enum RouteState {
88    NotEntered,
89    QueuedAt(usize),
90    Entered,
91}
92
93pub enum ObstaclePassResult {
94    Pass,
95    Follow { pos: f64, vel: f64 },
96}
97
98impl Vehicle {
99    /// Creates a new vehicle.
100    pub(crate) fn new(id: VehicleId, attributes: &VehicleAttributes) -> Self {
101        Self {
102            id,
103            half_wid: 0.5 * attributes.width,
104            half_len: 0.5 * attributes.length,
105            wheel_base: attributes.wheel_base,
106            acc: AccelerationModel::new(&acceleration::ModelParams {
107                max_acceleration: attributes.max_acc,
108                comf_deceleration: attributes.comf_dec,
109            }),
110            pos: 0.0,
111            vel: 0.0,
112            stop_cnt: 0,
113            route: vec![],
114            entered: 0,
115            queued: None,
116            will_queue_or_enter: Cell::new(0),
117            can_exit: true,
118            lane_change: None,
119            world_pos: Point2d::new(0.0, 0.0),
120            world_dir: Vector2d::new(0.0, 0.0),
121            rear_lats: Default::default(),
122            rear_coords: [Point2d::new(0.0, 0.0); 2],
123        }
124    }
125
126    /// Gets the vehicle's ID.
127    pub fn id(&self) -> VehicleId {
128        self.id
129    }
130
131    /// The vehicle's width in m.
132    pub fn width(&self) -> f64 {
133        2.0 * self.half_wid
134    }
135
136    /// The vehicle's length in m.
137    pub fn length(&self) -> f64 {
138        2.0 * self.half_len
139    }
140
141    /// The ID of the link the vehicle is currently travelling on.
142    pub fn link_id(&self) -> Option<LinkId> {
143        self.route.get(0).copied()
144    }
145
146    /// The longitudinal position of the centre of the vehicle in m.
147    pub fn pos_mid(&self) -> f64 {
148        self.pos
149    }
150
151    /// The longitudinal position of the rear of the vehicle in m.
152    pub fn pos_rear(&self) -> f64 {
153        self.pos - self.half_len
154    }
155
156    /// The longitudinal position of the front of the vehicle in m.
157    pub fn pos_front(&self) -> f64 {
158        self.pos + self.half_len
159    }
160
161    /// The coordinates in world space of the centre of the vehicle.
162    pub fn position(&self) -> Point2d {
163        self.world_pos
164    }
165
166    /// A unit vector in world space aligned with the vehicle's heading.
167    pub fn direction(&self) -> Vector2d {
168        self.world_dir
169    }
170
171    /// The two end points of a line behind the vehicle used for car following.
172    pub fn rear_coords(&self) -> [Point2d; 2] {
173        self.rear_coords
174    }
175
176    /// The vehicle's velocity in m/s.
177    pub fn vel(&self) -> f64 {
178        self.vel
179    }
180
181    /// Whether the vehicle is stopped.
182    pub fn has_stopped(&self) -> bool {
183        self.vel < 0.1
184    }
185
186    /// Calculates the time it would take the vehicle to reach the given `pos`
187    /// if it accelerates to the maximum speed at top acceleration.
188    pub fn min_reach_time(&self, pos: f64, max_vel: f64) -> f64 {
189        let dist = f64::max(pos - self.pos, 0.0);
190        self.acc.min_reach_time(self.vel, dist, max_vel)
191    }
192
193    /// Gets the status of a link on the vehicle's route.
194    pub(crate) fn get_route(&self, idx: usize) -> Option<(LinkId, RouteState)> {
195        self.route.get(idx).map(|link_id| {
196            use std::cmp::Ordering::*;
197            let state = match (self.entered.cmp(&idx), self.queued) {
198                (Greater, _) => RouteState::Entered,
199                (Equal, Some(f)) => RouteState::QueuedAt(f),
200                _ => RouteState::NotEntered,
201            };
202            (*link_id, state)
203        })
204    }
205
206    /// Determines whether the vehicle can comfortably stop before reaching `pos`.
207    pub(crate) fn can_stop(&self, pos: f64) -> bool {
208        let net_dist = pos - self.pos_front();
209        net_dist >= self.stopping_distance()
210    }
211
212    /// Determines the comfortable stopping distance of the vehicle.
213    pub(crate) fn stopping_distance(&self) -> f64 {
214        self.acc.stopping_distance(self.vel)
215    }
216
217    /// Queues into the next unentered link on the vehicle's route.
218    pub(crate) fn queue_link(&self) {
219        self.will_queue_or_enter.set(1);
220    }
221
222    /// Queues into the next unentered link on the vehicle's route.
223    pub(crate) fn enter_link(&self) {
224        self.will_queue_or_enter.set(2);
225    }
226
227    /// Applies an acceleration to the vehicle so it follows an obstacle.
228    pub(crate) fn stop_at_line(&self, pos: f64) {
229        let net_dist = pos - self.pos_front();
230        self.acc.stop_at_line(net_dist, self.vel);
231    }
232
233    /// Applies an acceleration to the vehicle so it follows an obstacle.
234    pub(crate) fn follow_vehicle(&self, pos: f64, vel: f64) {
235        let net_dist = pos - self.pos_front();
236        self.acc.follow_vehicle(net_dist, self.vel, vel);
237    }
238
239    /// Applies an acceleration to the vehicle so it follows an obstacle.
240    pub(crate) fn follow_obstacle(&self, coords: [Point2d; 2], vel: f64) {
241        let mid_dist = f64::min(
242            self.direction().dot(coords[0] - self.position()),
243            self.direction().dot(coords[1] - self.position()),
244        );
245        let net_dist = mid_dist - self.half_len;
246        self.acc.follow_vehicle(net_dist, self.vel, vel);
247    }
248
249    /// Applies a current speed limit to the vehicle.
250    pub(crate) fn apply_current_speed_limit(&self, speed_limit: f64) {
251        self.acc.apply_current_speed_limit(self.vel, speed_limit);
252    }
253
254    /// Applies an upcoming speed limit to the vehicle.
255    pub(crate) fn apply_speed_limit(&self, speed_limit: f64, pos: f64) {
256        self.acc
257            .apply_speed_limit(self.vel, speed_limit, pos - self.pos);
258    }
259
260    /// Applies a maximum deceleration to the vehicle, causing it to stop.
261    pub(crate) fn emergency_stop(&self) {
262        self.acc.emergency_stop();
263    }
264
265    /// Projects the vehicle onto another link.
266    pub(crate) fn project(&self, projection: &LinkProjection) -> Obstacle {
267        projection.project(
268            self.rear_coords,
269            self.pos - self.half_len,
270            self.rear_lats,
271            self.vel,
272        )
273    }
274
275    /// Gets the vehicle's lateral offset from the centre line
276    /// at the given longitudinal position along the current link.
277    pub(crate) fn offset_at(&self, pos: f64) -> f64 {
278        self.lane_change
279            .filter(|lc| lc.end_pos > pos)
280            .map(|lc| lc.offset.y(pos))
281            .unwrap_or(0.0)
282    }
283
284    /// Resets internal model states in preparation for a new step of the simulation.
285    pub(crate) fn reset(&self) {
286        self.acc.reset()
287    }
288
289    /// Integrates the vehicle's velocity and position
290    ///
291    /// # Parameters
292    /// * `dt` - T he time step in seconds
293    pub(crate) fn integrate(&mut self, dt: f64, seq: &mut usize) {
294        // Enter/enqueue into next link
295        match self.will_queue_or_enter.get() {
296            0 => {}
297            1 => {
298                *seq += 1;
299                self.queued.get_or_insert(*seq);
300            }
301            2 => {
302                self.entered += 1;
303                self.queued = None;
304            }
305            _ => unreachable!(),
306        }
307        self.will_queue_or_enter.set(0);
308
309        // Perform the integration
310        let vel = f64::max(self.vel + dt * self.acc.acc(), 0.0);
311        let pos = self.pos + 0.5 * (self.vel + vel) * dt;
312        self.vel = vel;
313        self.pos = pos;
314
315        // Check for lane change completion
316        self.lane_change = self.lane_change.filter(|lc| lc.end_pos > pos);
317
318        // Update the stop count
319        self.update_stop_count();
320    }
321
322    /// Updates the vehicle's stop counter.
323    fn update_stop_count(&mut self) {
324        if self.vel < 0.1 {
325            self.stop_cnt += 1;
326        } else {
327            self.stop_cnt = 0;
328        }
329    }
330
331    /// Checks whether the vehicle has travelled past the end of its current link,
332    /// and if so, advances it to the next link on its route if there is one.
333    /// Returns `true` iff the vehicle was advanced.
334    ///
335    /// # Parameters
336    /// * `links` - The links in the network
337    /// * `now` - The current frame of simulation
338    pub(crate) fn advance(&mut self, links: &LinkSet) -> bool {
339        if let Some(link_id) = self.route.get(0) {
340            let length = links[*link_id].length();
341            if length < self.pos {
342                self.route.remove(0);
343                self.entered = usize::max(self.entered - 1, 1);
344                self.pos -= length;
345                if let Some(lc) = self.lane_change.as_mut() {
346                    lc.offset = lc.offset.translate_x(length);
347                    lc.end_pos -= length;
348                }
349                return true;
350            }
351        }
352        false
353    }
354
355    /// Sets the vehicle's position in the network.
356    /// This also clears the vehicle's route.
357    pub(crate) fn set_location(&mut self, link: LinkId, pos: f64, lane_change: Option<LaneChange>) {
358        self.route = vec![link];
359        self.entered = 1;
360        self.pos = pos;
361        self.lane_change = lane_change;
362        self.can_exit = false;
363    }
364
365    /// Sets the vehicle's route.
366    pub(crate) fn set_route(&mut self, route: &[LinkId], can_exit: bool) {
367        self.route.truncate(1);
368        self.route.extend(route);
369        self.entered = 1;
370        self.can_exit = can_exit;
371    }
372
373    /// Updates the vehicle's world coordinates
374    pub(crate) fn update_coords(&mut self, links: &LinkSet) {
375        let link = &links[self.route[0]];
376        let curve = link.curve();
377
378        // Sample the curve
379        let sample = curve.sample_centre(self.pos);
380        let (pos, offset) = match self.lane_change {
381            Some(lc) => {
382                let offset = lc.offset.y(self.pos);
383                let pos = sample.lat_offset(offset);
384                (pos, offset)
385            }
386            None => (sample.pos, 0.0),
387        };
388
389        // Default `world_dir` to be tangent to the link
390        if self.world_dir == Vector2d::new(0.0, 0.0) {
391            self.world_pos = pos - sample.tan;
392            self.world_dir = sample.tan;
393        }
394
395        // `perp` is perpendicular to the link
396        let perp = rot90(sample.tan);
397
398        // Compute the vehicle's heading
399        let dir = calc_direction(self.world_pos, self.world_dir, pos, self.wheel_base);
400        self.world_pos = pos;
401        self.world_dir = dir;
402
403        // Compute the vehicle's lateral extent, accounting for any rotation relative to the curve
404        let expand = -dir.dot(perp) * self.half_len;
405        self.rear_lats = Interval {
406            min: f64::min(expand + offset, 0.0) - self.half_wid,
407            max: f64::max(expand + offset, 0.0) + self.half_wid,
408        };
409
410        // Compute the vehicle's rear coordinates
411        let rear_mid = sample.long_offset(-self.half_len);
412        self.rear_coords = self.rear_lats.as_array().map(|lat| rear_mid + lat * perp);
413        // debug_line("rear coords", self.rear_coords[0], self.rear_coords[1]);
414    }
415
416    /// Sets the `active` flag for the links this vehicle has entered (including the one its currently on).
417    pub(crate) fn activate_links(&self, links: &mut LinkSet) {
418        for link_id in self.route.iter().take(self.entered) {
419            links[*link_id].activate();
420        }
421    }
422
423    /// Determines whether the vehicle can pass the given obstacle.
424    pub(crate) fn can_pass(&self, mut obstacle: Obstacle, link: &Link) -> ObstaclePassResult {
425        let is_clear = |obstacle: Obstacle| {
426            let offset = self.offset_at(obstacle.pos - self.half_len);
427            let distance = obstacle.lat.distance(offset);
428            distance > self.half_wid + LATERAL_CLEARANCE
429        };
430
431        if is_clear(obstacle) {
432            // let p = link.curve().sample_centre(obstacle.pos);
433            // let l = obstacle.lat;
434            // debug_line("pass", p.lat_offset(l.min), p.lat_offset(l.max));
435            return ObstaclePassResult::Pass;
436        }
437
438        loop {
439            let sample = link.curve().sample_centre(obstacle.pos);
440            let ps = obstacle
441                .rear_coords
442                .map(|p| project_local(p, sample.pos, rot90(sample.tan), sample.tan));
443            let delta_pos = f64::min(ps[0].y, ps[1].y);
444            obstacle.pos += delta_pos;
445            if delta_pos < 0.1 {
446                obstacle.lat = Interval {
447                    min: ps[0].x,
448                    max: ps[1].x,
449                };
450                break;
451            }
452        }
453
454        if is_clear(obstacle) {
455            // let p = link.curve().sample_centre(obstacle.pos);
456            // let l = obstacle.lat;
457            // debug_line("pass adjust", p.lat_offset(l.min), p.lat_offset(l.max));
458            ObstaclePassResult::Pass
459        } else {
460            // let p = link.curve().sample_centre(obstacle.pos);
461            // let l = obstacle.lat;
462            // debug_line("follow", p.lat_offset(l.min), p.lat_offset(l.max));
463            ObstaclePassResult::Follow {
464                pos: obstacle.pos,
465                vel: obstacle.vel,
466            }
467        }
468    }
469}