use rustsim::rustsim_core::prelude::{AgentStore, VecStore};
use rustsim::rustsim_crowd::prelude::*;
use rustsim::rustsim_crowd::social_force;
const CORRIDOR_L: f64 = 20.0;
const CORRIDOR_W: f64 = 3.0;
const DENSITY: f64 = 2.0;
const DT: f64 = 0.05;
const NUM_TICKS: usize = 400;
const RADIUS: f64 = 0.25;
const DESIRED_SPEED: f64 = 1.34;
const REPORT_EVERY: usize = 40;
fn corridor_walls() -> Vec<WallSegment> {
let half = CORRIDOR_W / 2.0;
vec![
WallSegment {
a: [0.0, half],
b: [CORRIDOR_L, half],
},
WallSegment {
a: [0.0, -half],
b: [CORRIDOR_L, -half],
},
]
}
fn seed_counterflow(store: &mut VecStore<CrowdAgent>) -> usize {
let usable_w = (CORRIDOR_W - 2.0 * RADIUS).max(0.01);
let n = (DENSITY * CORRIDOR_L * CORRIDOR_W).round() as usize;
let cols = ((CORRIDOR_L / 1.5).ceil() as usize).max(1);
let rows = n.div_ceil(cols);
let dx = CORRIDOR_L / cols as f64;
let dy = usable_w / rows.max(1) as f64;
for k in 0..n {
let c = k % cols;
let r = k / cols;
let jx = ((k.wrapping_mul(2_654_435_761) & 0xff) as f64) / 255.0 - 0.5;
let jy = ((k.wrapping_mul(40_503) & 0xff) as f64) / 255.0 - 0.5;
let x = (c as f64 + 0.5 + 0.8 * jx) * dx;
let y = -usable_w / 2.0 + (r as f64 + 0.5 + 0.8 * jy) * dy;
let dir: f64 = if k % 2 == 0 { 1.0 } else { -1.0 };
store.insert(CrowdAgent {
id: k as u64,
ped: Pedestrian::new(
[x, y],
[0.5 * DESIRED_SPEED * dir, 0.0],
RADIUS,
DESIRED_SPEED,
[x + 100.0 * dir, 0.0],
),
});
}
n
}
fn wrap_periodic(store: &mut VecStore<CrowdAgent>) {
let ids = store.iter_ids();
for id in ids {
if let Some(mut a) = store.get_mut(id) {
let x = a.ped.pos[0];
let wrapped = x.rem_euclid(CORRIDOR_L);
let shift = x - wrapped;
a.ped.pos[0] = wrapped;
a.ped.destination[0] -= shift;
}
}
}
fn mean_speed(store: &VecStore<CrowdAgent>) -> f64 {
let ids = store.iter_ids();
if ids.is_empty() {
return 0.0;
}
let mut s = 0.0;
for id in &ids {
if let Some(a) = store.get(*id) {
s += (a.ped.vel[0].powi(2) + a.ped.vel[1].powi(2)).sqrt();
}
}
s / ids.len() as f64
}
fn main() {
let params = social_force::Params::default();
params
.validate(DT)
.expect("social_force::Params::default() should validate at DT = 0.05 s");
let mut store: VecStore<CrowdAgent> = VecStore::new();
let n = seed_counterflow(&mut store);
let walls = corridor_walls();
let cell = recommended_cell_size(social_force::neighbor_cutoff(¶ms));
let mut scratch = Scratch::with_capacity(n, cell);
let mut peds_buf: Vec<Pedestrian> = Vec::with_capacity(n);
println!("# rustsim-crowd corridor example");
println!("# n = {n}, density = {DENSITY} ped/m^2, corridor = {CORRIDOR_L} x {CORRIDOR_W} m");
println!("# dt = {DT} s, model = Social Force (Helbing & Molnár 1995)");
println!("tick,t_s,mean_speed_m_s,v_ratio");
let warmup = (NUM_TICKS * 2) / 3;
let mut ss_accum = 0.0;
let mut ss_samples = 0usize;
for tick in 0..NUM_TICKS {
wrap_periodic(&mut store);
step_scratch_store(
&SocialForceModel,
&mut store,
&walls,
¶ms,
DT,
&mut scratch,
&mut peds_buf,
);
if tick % REPORT_EVERY == 0 {
let v = mean_speed(&store);
println!(
"{tick},{:.2},{v:.3},{:.3}",
tick as f64 * DT,
v / DESIRED_SPEED
);
}
if tick >= warmup {
ss_accum += mean_speed(&store);
ss_samples += 1;
}
}
let v_ss = ss_accum / ss_samples.max(1) as f64;
println!(
"# steady_state: density = {DENSITY:.2} ped/m^2, mean_speed = {v_ss:.3} m/s, \
v/v0 = {:.3}",
v_ss / DESIRED_SPEED
);
}