1use crate::broadphase::{NeighborGrid, Scratch};
34use crate::common::{
35 add, clamp_speed, closest_point_on_segment, dot, norm, scale, sub, Pedestrian, PedestrianModel,
36 Vec2, WallSegment,
37};
38
39#[derive(Debug, Clone, Copy, PartialEq)]
41pub struct Params {
42 pub tau: f64,
44 pub mass: f64,
46 pub a: f64,
48 pub b: f64,
50 pub wall_gain: f64,
52 pub max_speed: f64,
54 pub min_clearance: f64,
57 pub arrival_radius: f64,
61 pub max_accel: f64,
69}
70
71impl Default for Params {
72 fn default() -> Self {
73 Self {
74 tau: 0.5,
75 mass: 80.0,
76 a: 0.18,
77 b: 0.08,
78 wall_gain: 5.0,
79 max_speed: 2.5,
80 min_clearance: 0.02,
81 arrival_radius: 0.3,
82 max_accel: 20.0,
83 }
84 }
85}
86
87impl Params {
88 pub fn validate(&self, dt: f64) -> Result<(), crate::error::CrowdError> {
96 use crate::error::{require_dt, require_nonneg, require_positive, CrowdError};
97 const M: &str = "GeneralizedCentrifugalForce";
98 require_dt(M, dt)?;
99 require_positive(M, "tau", self.tau)?;
100 require_positive(M, "mass", self.mass)?;
101 require_positive(M, "a", self.a)?;
102 require_nonneg(M, "b", self.b)?;
103 require_nonneg(M, "wall_gain", self.wall_gain)?;
104 require_positive(M, "max_speed", self.max_speed)?;
105 require_positive(M, "min_clearance", self.min_clearance)?;
106 require_nonneg(M, "arrival_radius", self.arrival_radius)?;
107 require_positive(M, "max_accel", self.max_accel)?;
108 let product = dt * self.max_accel;
109 if product > self.max_speed {
110 return Err(CrowdError::CflViolation {
111 model: M,
112 product,
113 max_speed: self.max_speed,
114 max_dt: self.max_speed / self.max_accel,
115 });
116 }
117 Ok(())
118 }
119}
120
121#[derive(Debug, Clone, Copy, Default)]
124pub struct GeneralizedCentrifugalForce;
125
126impl PedestrianModel for GeneralizedCentrifugalForce {
127 type Params = Params;
128
129 fn name(&self) -> &'static str {
130 "Generalized Centrifugal Force"
131 }
132
133 fn step(&self, peds: &mut [Pedestrian], walls: &[WallSegment], params: &Params, dt: f64) {
134 #[allow(deprecated)]
135 step(peds, walls, params, dt);
136 }
137}
138
139#[deprecated(
145 since = "0.0.3",
146 note = "O(n²) reference path with per-tick heap allocation; use \
147 `step_scratch` (zero-alloc) or `step_with_grid` (broadphase) \
148 instead. See docs/rustsim-crowd.md P1-7."
149)]
150#[allow(clippy::needless_range_loop)]
151pub fn step(peds: &mut [Pedestrian], walls: &[WallSegment], params: &Params, dt: f64) {
152 let n = peds.len();
153 let mut accels = vec![[0.0f64; 2]; n];
154
155 for i in 0..n {
156 let p = &peds[i];
157 let mut f = driving_force(p, params);
158
159 for j in 0..n {
160 if i == j {
161 continue;
162 }
163 f = add(f, ped_force(p, &peds[j], params));
164 }
165
166 for w in walls {
167 f = add(f, wall_force(p, w, params));
168 }
169
170 accels[i] = crate::social_force::cap_accel(scale(f, 1.0 / params.mass), params.max_accel);
171 }
172
173 for (p, a) in peds.iter_mut().zip(accels.iter()) {
174 p.vel = add(p.vel, scale(*a, dt));
175 p.vel = clamp_speed(p.vel, params.max_speed);
176 p.pos = add(p.pos, scale(p.vel, dt));
177 }
178}
179
180#[inline]
191pub fn neighbor_cutoff(_params: &Params) -> f64 {
192 5.0
193}
194
195#[allow(clippy::needless_range_loop)]
200pub fn step_with_grid(
201 peds: &mut [Pedestrian],
202 walls: &[WallSegment],
203 params: &Params,
204 dt: f64,
205 grid: &NeighborGrid,
206) {
207 let n = peds.len();
208 let cutoff = neighbor_cutoff(params);
209 let mut accels = vec![[0.0f64; 2]; n];
210
211 for i in 0..n {
212 let p = &peds[i];
213 let mut f = driving_force(p, params);
214
215 grid.for_each_neighbor(i, cutoff, peds, |_j, q| {
216 f = add(f, ped_force(p, q, params));
217 });
218
219 for w in walls {
220 f = add(f, wall_force(p, w, params));
221 }
222
223 accels[i] = crate::social_force::cap_accel(scale(f, 1.0 / params.mass), params.max_accel);
224 }
225
226 for (p, a) in peds.iter_mut().zip(accels.iter()) {
227 p.vel = add(p.vel, scale(*a, dt));
228 p.vel = clamp_speed(p.vel, params.max_speed);
229 p.pos = add(p.pos, scale(p.vel, dt));
230 }
231}
232
233#[allow(clippy::needless_range_loop)]
237pub fn step_scratch(
238 peds: &mut [Pedestrian],
239 walls: &[WallSegment],
240 params: &Params,
241 dt: f64,
242 scratch: &mut Scratch,
243) {
244 let n = peds.len();
245 let cutoff = neighbor_cutoff(params);
246 scratch.prepare(peds);
247 let (accels, grid) = scratch.split();
248
249 for i in 0..n {
250 let p = &peds[i];
251 let mut f = driving_force(p, params);
252 grid.for_each_neighbor(i, cutoff, peds, |_j, q| {
253 f = add(f, ped_force(p, q, params));
254 });
255 for w in walls {
256 f = add(f, wall_force(p, w, params));
257 }
258 accels[i] = crate::social_force::cap_accel(scale(f, 1.0 / params.mass), params.max_accel);
259 }
260
261 for (p, a) in peds.iter_mut().zip(accels.iter()) {
262 p.vel = add(p.vel, scale(*a, dt));
263 p.vel = clamp_speed(p.vel, params.max_speed);
264 p.pos = add(p.pos, scale(p.vel, dt));
265 }
266}
267
268#[cfg(feature = "rayon")]
275#[allow(clippy::needless_range_loop)]
276pub fn step_scratch_par(
277 peds: &mut [Pedestrian],
278 walls: &[WallSegment],
279 params: &Params,
280 dt: f64,
281 scratch: &mut Scratch,
282) {
283 use rayon::prelude::*;
284
285 let cutoff = neighbor_cutoff(params);
286 scratch.prepare(peds);
287 let (accels, grid) = scratch.split();
288 let peds_ro: &[Pedestrian] = peds;
289
290 accels.par_iter_mut().enumerate().for_each(|(i, a_slot)| {
291 let p = &peds_ro[i];
292 let mut f = driving_force(p, params);
293 grid.for_each_neighbor(i, cutoff, peds_ro, |_j, q| {
294 f = add(f, ped_force(p, q, params));
295 });
296 for w in walls {
297 f = add(f, wall_force(p, w, params));
298 }
299 *a_slot = crate::social_force::cap_accel(scale(f, 1.0 / params.mass), params.max_accel);
300 });
301
302 for (p, a) in peds.iter_mut().zip(accels.iter()) {
303 p.vel = add(p.vel, scale(*a, dt));
304 p.vel = clamp_speed(p.vel, params.max_speed);
305 p.pos = add(p.pos, scale(p.vel, dt));
306 }
307}
308
309#[cfg(feature = "simd")]
326#[allow(clippy::needless_range_loop)]
327pub fn step_scratch_simd(
328 peds: &mut [Pedestrian],
329 walls: &[WallSegment],
330 params: &Params,
331 dt: f64,
332 scratch: &mut Scratch,
333) {
334 let n = peds.len();
335 let cutoff = neighbor_cutoff(params);
336 scratch.prepare(peds);
337 let (accels, grid) = scratch.split();
338
339 for i in 0..n {
340 let p = &peds[i];
341 let mut f = driving_force(p, params);
342
343 let mut idxs: [Option<usize>; 4] = [None, None, None, None];
344 let mut filled: usize = 0;
345 grid.for_each_neighbor(i, cutoff, peds, |j, _q| {
346 idxs[filled] = Some(j);
347 filled += 1;
348 if filled == 4 {
349 let buf: [Option<&Pedestrian>; 4] = [
350 Some(&peds[idxs[0].unwrap()]),
351 Some(&peds[idxs[1].unwrap()]),
352 Some(&peds[idxs[2].unwrap()]),
353 Some(&peds[idxs[3].unwrap()]),
354 ];
355 let pf = crate::simd::gcf_pair_force_x4(p, buf, params);
356 f = add(f, pf);
357 idxs = [None, None, None, None];
358 filled = 0;
359 }
360 });
361 if filled > 0 {
362 let buf: [Option<&Pedestrian>; 4] = [
363 idxs[0].map(|k| &peds[k]),
364 idxs[1].map(|k| &peds[k]),
365 idxs[2].map(|k| &peds[k]),
366 idxs[3].map(|k| &peds[k]),
367 ];
368 let pf = crate::simd::gcf_pair_force_x4(p, buf, params);
369 f = add(f, pf);
370 }
371
372 for w in walls {
373 f = add(f, wall_force(p, w, params));
374 }
375 accels[i] = crate::social_force::cap_accel(scale(f, 1.0 / params.mass), params.max_accel);
376 }
377
378 for (p, a) in peds.iter_mut().zip(accels.iter()) {
379 p.vel = add(p.vel, scale(*a, dt));
380 p.vel = clamp_speed(p.vel, params.max_speed);
381 p.pos = add(p.pos, scale(p.vel, dt));
382 }
383}
384
385#[inline]
387pub fn driving_force(p: &Pedestrian, params: &Params) -> Vec2 {
388 let target = scale(
389 p.desired_direction(),
390 p.effective_desired_speed(params.arrival_radius),
391 );
392 let delta = sub(target, p.vel);
393 scale(delta, params.mass / params.tau)
394}
395
396#[inline]
398pub fn body_radius(p: &Pedestrian, params: &Params) -> f64 {
399 params.a + params.b * p.speed()
400}
401
402#[inline]
404pub fn ped_force(p: &Pedestrian, q: &Pedestrian, params: &Params) -> Vec2 {
405 let diff = sub(p.pos, q.pos);
406 let d = norm(diff);
407 if d < 1e-9 {
408 return [0.0, 0.0];
409 }
410 let e_ji = scale(diff, 1.0 / d);
411
412 let v_rel = sub(p.vel, q.vel);
414 let approach = (-dot(v_rel, e_ji)).max(0.0);
415
416 let r_i = body_radius(p, params);
417 let r_j = body_radius(q, params);
418 let clearance = (d - r_i - r_j).max(params.min_clearance);
419
420 let magnitude = params.mass * (p.desired_speed + approach).powi(2) / clearance;
421 scale(e_ji, magnitude)
422}
423
424#[inline]
426pub fn wall_force(p: &Pedestrian, wall: &WallSegment, params: &Params) -> Vec2 {
427 let closest = closest_point_on_segment(p.pos, wall.a, wall.b);
428 let diff = sub(p.pos, closest);
429 let d = norm(diff);
430 if d < 1e-9 {
431 return [0.0, 0.0];
432 }
433 let e = scale(diff, 1.0 / d);
434 let r = body_radius(p, params);
435 let clearance = (d - r).max(params.min_clearance);
436 let magnitude = params.wall_gain * params.mass * (p.desired_speed).powi(2) / clearance;
437 scale(e, magnitude)
438}
439
440#[cfg(test)]
441#[allow(deprecated)] mod tests {
443 use super::*;
444
445 #[test]
446 fn single_agent_accelerates_toward_goal() {
447 let mut peds = vec![Pedestrian {
448 pos: [0.0, 0.0],
449 vel: [0.0, 0.0],
450 radius: 0.25,
451 desired_speed: 1.34,
452 destination: [10.0, 0.0],
453 }];
454 for _ in 0..50 {
455 step(&mut peds, &[], &Params::default(), 0.05);
456 }
457 assert!(peds[0].vel[0] > 0.5);
458 }
459
460 #[test]
461 fn head_on_pair_does_not_overlap() {
462 let mut peds = vec![
463 Pedestrian {
464 pos: [-4.0, 0.1],
465 vel: [0.0, 0.0],
466 radius: 0.25,
467 desired_speed: 1.34,
468 destination: [4.0, 0.1],
469 },
470 Pedestrian {
471 pos: [4.0, -0.1],
472 vel: [0.0, 0.0],
473 radius: 0.25,
474 desired_speed: 1.34,
475 destination: [-4.0, -0.1],
476 },
477 ];
478 for _ in 0..400 {
479 step(&mut peds, &[], &Params::default(), 0.02);
480 }
481 let d = norm(sub(peds[0].pos, peds[1].pos));
482 let min_sep =
483 body_radius(&peds[0], &Params::default()) + body_radius(&peds[1], &Params::default());
484 assert!(
485 d + 0.02 >= min_sep,
486 "agents too close: d={d}, min={min_sep}"
487 );
488 }
489}