pub type Vec2 = [f64; 2];
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct Pedestrian {
pub pos: Vec2,
pub vel: Vec2,
pub radius: f64,
pub desired_speed: f64,
pub destination: Vec2,
}
impl Pedestrian {
#[inline]
pub fn new(pos: Vec2, vel: Vec2, radius: f64, desired_speed: f64, destination: Vec2) -> Self {
Self {
pos,
vel,
radius,
desired_speed,
destination,
}
}
#[inline]
pub fn to_destination(&self) -> Vec2 {
sub(self.destination, self.pos)
}
#[inline]
pub fn desired_direction(&self) -> Vec2 {
normalize(self.to_destination())
}
#[inline]
pub fn speed(&self) -> f64 {
norm(self.vel)
}
#[inline]
pub fn distance_to_destination(&self) -> f64 {
norm(self.to_destination())
}
#[inline]
pub fn has_arrived(&self, arrival_radius: f64) -> bool {
self.distance_to_destination() <= arrival_radius
}
#[inline]
pub fn effective_desired_speed(&self, arrival_radius: f64) -> f64 {
if arrival_radius <= 0.0 {
return self.desired_speed;
}
let d = self.distance_to_destination();
if d >= arrival_radius {
self.desired_speed
} else {
self.desired_speed * (d / arrival_radius)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WallSegment {
pub a: Vec2,
pub b: Vec2,
}
pub trait PedestrianModel {
type Params;
fn name(&self) -> &'static str;
fn step(&self, peds: &mut [Pedestrian], walls: &[WallSegment], params: &Self::Params, dt: f64);
}
#[inline]
pub(crate) fn add(a: Vec2, b: Vec2) -> Vec2 {
[a[0] + b[0], a[1] + b[1]]
}
#[inline]
pub(crate) fn sub(a: Vec2, b: Vec2) -> Vec2 {
[a[0] - b[0], a[1] - b[1]]
}
#[inline]
pub(crate) fn scale(a: Vec2, s: f64) -> Vec2 {
[a[0] * s, a[1] * s]
}
#[inline]
pub(crate) fn dot(a: Vec2, b: Vec2) -> f64 {
a[0] * b[0] + a[1] * b[1]
}
#[inline]
pub(crate) fn norm(a: Vec2) -> f64 {
(a[0] * a[0] + a[1] * a[1]).sqrt()
}
#[inline]
pub(crate) fn normalize(a: Vec2) -> Vec2 {
let n = norm(a);
if n < 1e-12 {
[0.0, 0.0]
} else {
[a[0] / n, a[1] / n]
}
}
#[inline]
pub(crate) fn closest_point_on_segment(p: Vec2, a: Vec2, b: Vec2) -> Vec2 {
let ab = sub(b, a);
let denom = dot(ab, ab);
if denom < 1e-18 {
return a;
}
let t = (dot(sub(p, a), ab) / denom).clamp(0.0, 1.0);
add(a, scale(ab, t))
}
#[inline]
pub(crate) fn clamp_speed(v: Vec2, max_speed: f64) -> Vec2 {
let s = norm(v);
if s > max_speed && s > 1e-12 {
scale(v, max_speed / s)
} else {
v
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize_zero_is_zero() {
assert_eq!(normalize([0.0, 0.0]), [0.0, 0.0]);
}
#[test]
fn closest_point_on_segment_endpoints_and_middle() {
let a = [0.0, 0.0];
let b = [10.0, 0.0];
assert_eq!(closest_point_on_segment([-5.0, 5.0], a, b), a);
assert_eq!(closest_point_on_segment([15.0, 5.0], a, b), b);
assert_eq!(closest_point_on_segment([5.0, 5.0], a, b), [5.0, 0.0]);
}
#[test]
fn clamp_speed_basic() {
let v = clamp_speed([3.0, 4.0], 2.5);
let s = norm(v);
assert!((s - 2.5).abs() < 1e-9);
}
#[test]
fn pedestrian_new_matches_literal_construction() {
let via_new = Pedestrian::new([1.0, 2.0], [0.3, 0.4], 0.25, 1.34, [10.0, 0.0]);
let via_lit = Pedestrian {
pos: [1.0, 2.0],
vel: [0.3, 0.4],
radius: 0.25,
desired_speed: 1.34,
destination: [10.0, 0.0],
};
assert_eq!(via_new, via_lit);
}
#[test]
fn effective_desired_speed_tapers_inside_arrival_radius() {
let p = Pedestrian {
pos: [4.7, 0.0], vel: [0.0, 0.0],
radius: 0.25,
desired_speed: 1.0,
destination: [5.0, 0.0],
};
assert!((p.effective_desired_speed(0.3) - 1.0).abs() < 1e-12);
let p_half = Pedestrian {
pos: [4.85, 0.0],
..p
};
assert!((p_half.effective_desired_speed(0.3) - 0.5).abs() < 1e-12);
let p_there = Pedestrian {
pos: [5.0, 0.0],
..p
};
assert_eq!(p_there.effective_desired_speed(0.3), 0.0);
assert_eq!(p_there.effective_desired_speed(0.0), 1.0);
}
#[test]
fn has_arrived_reports_inside_radius() {
let p = Pedestrian {
pos: [4.9, 0.0],
vel: [0.0, 0.0],
radius: 0.25,
desired_speed: 1.0,
destination: [5.0, 0.0],
};
assert!(p.has_arrived(0.2));
assert!(!p.has_arrived(0.05));
}
}