Skip to main content

rustsim_mobility/
obstacle.rs

1//! Cross-domain obstacle trait.
2//!
3//! The critical challenge in a coupled crowd-plus-traffic simulation is
4//! that crowd models must see vehicles as moving obstacles, and vehicle
5//! models must see pedestrians as obstacles. Neither domain crate should
6//! depend on the other.
7//!
8//! This module defines a tiny abstract interface both sides can
9//! implement in thin adapter types.
10
11use rustsim_geometry::vec3::Vec3;
12use rustsim_vehicle::{Vehicle, VehicleClass};
13
14/// Kind of an obstacle, used for priority and styling.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum ObstacleKind {
17    /// Individual pedestrian or cyclist body.
18    Pedestrian,
19    /// Road vehicle (car, taxi, van).
20    Vehicle,
21    /// Public-transit vehicle (bus, tram, metro, train).
22    Transit,
23    /// Static fixed object (kerb, bollard, furniture).
24    Static,
25}
26
27/// Instantaneous snapshot of an obstacle's bounding circle and velocity
28/// in 3-D. Implementations should project to `z = 0` if they live in 2-D.
29#[derive(Debug, Clone, Copy, PartialEq)]
30pub struct ObstacleSnapshot {
31    /// Stable identifier within the hosting simulation.
32    pub id: u64,
33    /// Kind.
34    pub kind: ObstacleKind,
35    /// Centre in 3-D.
36    pub center: Vec3,
37    /// Enclosing sphere radius in metres.
38    pub radius: f64,
39    /// Linear velocity in 3-D (m/s).
40    pub velocity: Vec3,
41}
42
43/// Any entity that can expose itself as an obstacle to foreign domains.
44pub trait Obstacle {
45    /// Produce an instantaneous snapshot for broadphase insertion.
46    fn snapshot(&self) -> ObstacleSnapshot;
47}
48
49impl Obstacle for ObstacleSnapshot {
50    #[inline]
51    fn snapshot(&self) -> ObstacleSnapshot {
52        *self
53    }
54}
55
56/// Thin obstacle adapter for a [`rustsim_crowd::Pedestrian`].
57pub struct PedestrianObstacle<'a> {
58    /// Stable identifier supplied by the hosting simulation.
59    pub id: u64,
60    /// Pedestrian state to expose as an obstacle.
61    pub pedestrian: &'a rustsim_crowd::Pedestrian,
62    /// Vertical coordinate to attach to the 2-D pedestrian state.
63    pub z: f64,
64}
65
66impl<'a> PedestrianObstacle<'a> {
67    /// Construct a pedestrian obstacle at ground level.
68    pub fn ground(id: u64, pedestrian: &'a rustsim_crowd::Pedestrian) -> Self {
69        Self {
70            id,
71            pedestrian,
72            z: 0.0,
73        }
74    }
75
76    /// Construct a pedestrian obstacle at a specific vertical coordinate.
77    pub fn at_z(id: u64, pedestrian: &'a rustsim_crowd::Pedestrian, z: f64) -> Self {
78        Self { id, pedestrian, z }
79    }
80}
81
82impl Obstacle for PedestrianObstacle<'_> {
83    fn snapshot(&self) -> ObstacleSnapshot {
84        ObstacleSnapshot {
85            id: self.id,
86            kind: ObstacleKind::Pedestrian,
87            center: [self.pedestrian.pos[0], self.pedestrian.pos[1], self.z],
88            radius: self.pedestrian.radius,
89            velocity: [self.pedestrian.vel[0], self.pedestrian.vel[1], 0.0],
90        }
91    }
92}
93
94/// Thin obstacle adapter for a [`rustsim_vehicle::Vehicle`].
95pub struct VehicleObstacle<'a> {
96    /// Stable identifier supplied by the hosting simulation.
97    pub id: u64,
98    /// Vehicle state to expose as an obstacle.
99    pub vehicle: &'a Vehicle,
100    /// Vertical coordinate to attach to the road-plane vehicle state.
101    pub z: f64,
102}
103
104impl<'a> VehicleObstacle<'a> {
105    /// Construct a vehicle obstacle at ground level.
106    pub fn ground(id: u64, vehicle: &'a Vehicle) -> Self {
107        Self {
108            id,
109            vehicle,
110            z: 0.0,
111        }
112    }
113
114    /// Construct a vehicle obstacle at a specific vertical coordinate.
115    pub fn at_z(id: u64, vehicle: &'a Vehicle, z: f64) -> Self {
116        Self { id, vehicle, z }
117    }
118}
119
120impl Obstacle for VehicleObstacle<'_> {
121    fn snapshot(&self) -> ObstacleSnapshot {
122        let (sin_heading, cos_heading) = self.vehicle.heading.sin_cos();
123        let center_x = self.vehicle.pos[0] + cos_heading * self.vehicle.length * 0.5;
124        let center_y = self.vehicle.pos[1] + sin_heading * self.vehicle.length * 0.5;
125        let radius = 0.5 * self.vehicle.length.hypot(self.vehicle.width);
126        let kind = match self.vehicle.class {
127            VehicleClass::Bus | VehicleClass::Rail => ObstacleKind::Transit,
128            _ => ObstacleKind::Vehicle,
129        };
130        ObstacleSnapshot {
131            id: self.id,
132            kind,
133            center: [center_x, center_y, self.z],
134            radius,
135            velocity: [
136                cos_heading * self.vehicle.speed,
137                sin_heading * self.vehicle.speed,
138                0.0,
139            ],
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn snapshot_is_self_describing() {
150        let s = ObstacleSnapshot {
151            id: 1,
152            kind: ObstacleKind::Vehicle,
153            center: [1.0, 2.0, 0.0],
154            radius: 1.2,
155            velocity: [3.0, 0.0, 0.0],
156        };
157        assert_eq!(s.snapshot(), s);
158    }
159
160    #[test]
161    fn adapters_project_domain_models_to_obstacles() {
162        let ped = rustsim_crowd::Pedestrian::new([1.0, 2.0], [0.5, 0.0], 0.25, 1.3, [5.0, 2.0]);
163        let snap = PedestrianObstacle::ground(7, &ped).snapshot();
164        assert_eq!(snap.kind, ObstacleKind::Pedestrian);
165        assert_eq!(snap.center, [1.0, 2.0, 0.0]);
166        assert_eq!(snap.radius, 0.25);
167
168        let mut vehicle = Vehicle::default_bus();
169        vehicle.pos = [10.0, 0.0];
170        vehicle.speed = 3.0;
171        let snap = VehicleObstacle::ground(8, &vehicle).snapshot();
172        assert_eq!(snap.kind, ObstacleKind::Transit);
173        assert!(snap.center[0] > vehicle.pos[0]);
174        assert_eq!(snap.velocity, [3.0, 0.0, 0.0]);
175    }
176}