Skip to main content

rustsim_crowd/
broadphase.rs

1//! Broadphase acceleration for crowd models.
2//!
3//! Every force-based or velocity-based model in this crate is
4//! fundamentally a pair-wise interaction: pedestrian *i* only needs to
5//! consider pedestrians *j* within a small cutoff radius where the
6//! interaction term is non-negligible. The naïve double loop is
7//! `O(n^2)`; wrapping `rustsim-broadphase::UniformGrid2` around it
8//! turns every `step` into `O(n + n * k)` where `k` is the average
9//! per-agent neighbour count, which is typically 5–20 for realistic
10//! crowd densities.
11//!
12//! Each model picks its own cutoff through the function below:
13//!
14//! - [`NeighborGrid::rebuild`] drops the previous frame and re-inserts
15//!   every pedestrian by its current position. Callers own the grid,
16//!   so one allocation per *simulation*, not per *tick*.
17//! - [`NeighborGrid::for_each_neighbor`] hands the caller `(j, pos_j,
18//!   ped_j)` for every pedestrian inside the cutoff radius of a source
19//!   position, skipping the source index itself.
20//!
21//! The grid keys are `u64` agent indices cast from `usize`. Pedestrian
22//! slices must fit in `u64::MAX` — trivially satisfied in practice.
23
24use rustsim_broadphase::UniformGrid2;
25
26use crate::common::{Pedestrian, Vec2};
27
28/// Per-tick neighbour grid for the crowd models.
29///
30/// Typical usage:
31///
32/// ```ignore
33/// let mut grid = NeighborGrid::new(1.0);
34/// for _step in 0..num_steps {
35///     grid.rebuild(&peds);
36///     social_force::step_with_grid(&mut peds, &walls, &params, dt, &grid);
37/// }
38/// ```
39#[derive(Debug, Clone)]
40pub struct NeighborGrid {
41    grid: UniformGrid2,
42}
43
44impl NeighborGrid {
45    /// Construct a grid with the given cell size (metres).
46    ///
47    /// Choose `cell_size` close to the dominant model cutoff — e.g.
48    /// `1.0` m for Social Force with default `b_ped`, `3.0` m for
49    /// Optimal Steps with default `ped_range`, or the output of
50    /// [`recommended_cell_size`].
51    ///
52    /// # Panics
53    /// Panics if `cell_size` is not strictly positive.
54    pub fn new(cell_size: f64) -> Self {
55        Self {
56            grid: UniformGrid2::new(cell_size),
57        }
58    }
59
60    /// Drop every entry.
61    #[inline]
62    pub fn clear(&mut self) {
63        self.grid.clear();
64    }
65
66    /// Clear the grid and re-insert every pedestrian keyed by its
67    /// slice index cast to `u64`.
68    pub fn rebuild(&mut self, peds: &[Pedestrian]) {
69        self.grid.clear();
70        for (i, p) in peds.iter().enumerate() {
71            self.grid.insert(i as u64, p.pos);
72        }
73    }
74
75    /// Invoke `f(j, pos_j, ped_j)` for every pedestrian within
76    /// `radius` of `pos_i`, skipping index `i`.
77    ///
78    /// `peds` must be the same slice that was passed to
79    /// [`rebuild`](Self::rebuild); the grid stores only indices and
80    /// positions, so the caller provides the slice for the actual
81    /// pedestrian record lookup.
82    #[inline]
83    pub fn for_each_neighbor<F>(&self, i: usize, radius: f64, peds: &[Pedestrian], mut f: F)
84    where
85        F: FnMut(usize, &Pedestrian),
86    {
87        let pos_i = peds[i].pos;
88        self.grid.for_each_within(pos_i, radius, |k, _pos_j| {
89            let j = k as usize;
90            if j == i {
91                return;
92            }
93            if j >= peds.len() {
94                // Stale entry from a previous rebuild that spanned a
95                // larger slice — defensive guard, never triggered in
96                // the intended `rebuild → step` sequence.
97                return;
98            }
99            f(j, &peds[j]);
100        });
101    }
102
103    /// Underlying grid, for diagnostics or custom queries.
104    #[inline]
105    pub fn inner(&self) -> &UniformGrid2 {
106        &self.grid
107    }
108}
109
110/// Suggested cell size for a given interaction cutoff radius.
111///
112/// Uniform hash-grids prune best when the cell edge matches the query
113/// radius; anything smaller wastes cells, anything larger wastes
114/// comparisons. Returns `radius` clamped to at least `0.1` m so
115/// pathological inputs never produce zero-sized cells.
116#[inline]
117pub fn recommended_cell_size(radius: f64) -> f64 {
118    radius.max(0.1)
119}
120
121/// Reusable scratch buffers for the crowd models.
122///
123/// Every `step_*` path in this crate needs exactly one `Vec<Vec2>` of
124/// length `peds.len()` (for next-tick accelerations, velocities, or
125/// positions) and one [`NeighborGrid`] rebuilt against the current
126/// pedestrian slice. `Scratch` bundles both so a caller can allocate
127/// **once per simulation** instead of twice per tick.
128///
129/// ```ignore
130/// let mut scratch = Scratch::with_capacity(peds.len(), 1.0);
131/// for _ in 0..num_steps {
132///     social_force::step_scratch(&mut peds, &walls, &params, dt, &mut scratch);
133/// }
134/// ```
135///
136/// `Scratch` is not tied to a specific model: the same instance can
137/// drive `social_force`, `collision_free_speed`, `anticipation_velocity`,
138/// `generalized_centrifugal_force`, or `optimal_steps` step-by-step,
139/// provided the grid's `cell_size` is a sensible cap on the largest
140/// [`neighbor_cutoff`](crate::social_force::neighbor_cutoff) in play.
141#[derive(Debug, Clone)]
142pub struct Scratch {
143    /// Per-agent working buffer (re-used as accelerations, velocities,
144    /// or next-step positions depending on the caller).
145    pub(crate) buf: Vec<Vec2>,
146    /// Neighbour grid rebuilt against the pedestrian slice each tick.
147    pub grid: NeighborGrid,
148}
149
150impl Scratch {
151    /// Empty scratch with a grid of the given cell size (metres).
152    ///
153    /// # Panics
154    /// Panics if `cell_size` is not strictly positive.
155    #[inline]
156    pub fn new(cell_size: f64) -> Self {
157        Self {
158            buf: Vec::new(),
159            grid: NeighborGrid::new(cell_size),
160        }
161    }
162
163    /// Scratch pre-sized for `n` pedestrians and a grid of `cell_size`.
164    #[inline]
165    pub fn with_capacity(n: usize, cell_size: f64) -> Self {
166        Self {
167            buf: Vec::with_capacity(n),
168            grid: NeighborGrid::new(cell_size),
169        }
170    }
171
172    /// Clear the buffers without releasing their capacity.
173    #[inline]
174    pub fn clear(&mut self) {
175        self.buf.clear();
176        self.grid.clear();
177    }
178
179    /// Resize `buf` to length `n` (zero-filled) without freeing capacity
180    /// and rebuild `grid` against `peds`. Called by every `step_scratch`.
181    #[inline]
182    pub(crate) fn prepare(&mut self, peds: &[Pedestrian]) {
183        let n = peds.len();
184        self.buf.clear();
185        self.buf.resize(n, [0.0, 0.0]);
186        self.grid.rebuild(peds);
187    }
188
189    /// Split-borrow the scratch buffer and the read-only grid.
190    #[inline]
191    pub(crate) fn split(&mut self) -> (&mut [Vec2], &NeighborGrid) {
192        (&mut self.buf, &self.grid)
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    fn ped_at(x: f64, y: f64) -> Pedestrian {
201        Pedestrian {
202            pos: [x, y],
203            vel: [0.0, 0.0],
204            radius: 0.25,
205            desired_speed: 1.34,
206            destination: [x + 10.0, y],
207        }
208    }
209
210    #[test]
211    fn rebuild_and_query_returns_only_nearby() {
212        let peds = vec![
213            ped_at(0.0, 0.0),
214            ped_at(0.3, 0.0),
215            ped_at(5.0, 0.0),
216            ped_at(0.0, 0.5),
217        ];
218        let mut grid = NeighborGrid::new(1.0);
219        grid.rebuild(&peds);
220        let mut hits = Vec::new();
221        grid.for_each_neighbor(0, 1.0, &peds, |j, _| hits.push(j));
222        hits.sort();
223        assert_eq!(hits, vec![1, 3]);
224    }
225
226    #[test]
227    fn skips_self_index() {
228        let peds = vec![ped_at(0.0, 0.0), ped_at(0.1, 0.0)];
229        let mut grid = NeighborGrid::new(1.0);
230        grid.rebuild(&peds);
231        let mut hits = Vec::new();
232        grid.for_each_neighbor(0, 5.0, &peds, |j, _| hits.push(j));
233        assert_eq!(hits, vec![1]);
234    }
235
236    #[test]
237    fn empty_slice_is_safe() {
238        let peds: Vec<Pedestrian> = Vec::new();
239        let mut grid = NeighborGrid::new(1.0);
240        grid.rebuild(&peds);
241        // No assertion needed — just must not panic.
242    }
243
244    #[test]
245    fn scratch_reuses_capacity_across_ticks() {
246        let peds = vec![ped_at(0.0, 0.0), ped_at(0.5, 0.0), ped_at(1.0, 0.0)];
247        let mut scratch = Scratch::with_capacity(3, 1.0);
248        scratch.prepare(&peds);
249        assert_eq!(scratch.buf.len(), 3);
250        let cap_after_first = scratch.buf.capacity();
251        // Second tick must not re-allocate.
252        scratch.prepare(&peds);
253        assert_eq!(scratch.buf.len(), 3);
254        assert_eq!(scratch.buf.capacity(), cap_after_first);
255    }
256}