Skip to main content

euv_engine/spatial/
impl.rs

1use super::*;
2
3/// Implements construction, insertion, and querying for `SpatialHashGrid2D`.
4impl SpatialHashGrid2D {
5    /// Creates a new 2D spatial hash grid with the given cell size.
6    ///
7    /// # Arguments
8    ///
9    /// - `f64` - The world-space size of each grid cell.
10    ///
11    /// # Returns
12    ///
13    /// - `SpatialHashGrid2D` - The new grid.
14    pub fn create(cell_size: f64) -> SpatialHashGrid2D {
15        let safe_size: f64 = cell_size.max(EPSILON);
16        let mut grid: SpatialHashGrid2D = SpatialHashGrid2D::new(safe_size);
17        grid.set_inverse_cell_size(1.0 / safe_size);
18        grid
19    }
20
21    /// Creates a new 2D spatial hash grid with the default cell size.
22    ///
23    /// # Returns
24    ///
25    /// - `SpatialHashGrid2D` - The new grid.
26    pub fn with_default_size() -> SpatialHashGrid2D {
27        Self::create(SPATIAL_DEFAULT_CELL_SIZE_2D)
28    }
29
30    /// Inserts a body index into all cells overlapping the given bounding box.
31    ///
32    /// # Arguments
33    ///
34    /// - `usize` - The body index to insert.
35    /// - `Vector2D` - The minimum corner of the bounding box.
36    /// - `Vector2D` - The maximum corner of the bounding box.
37    pub fn insert(&mut self, index: usize, min: Vector2D, max: Vector2D) {
38        let inv: f64 = self.get_inverse_cell_size();
39        let min_col: i32 = (min.get_x() * inv).floor() as i32;
40        let min_row: i32 = (min.get_y() * inv).floor() as i32;
41        let max_col: i32 = (max.get_x() * inv).floor() as i32;
42        let max_row: i32 = (max.get_y() * inv).floor() as i32;
43        for col in min_col..=max_col {
44            for row in min_row..=max_row {
45                self.get_mut_cells()
46                    .entry((col, row))
47                    .or_default()
48                    .push(index);
49            }
50        }
51    }
52
53    /// Returns all candidate body indices whose cells overlap the given bounding box.
54    ///
55    /// Deduplicates indices so each candidate appears at most once.
56    ///
57    /// # Arguments
58    ///
59    /// - `Vector2D` - The minimum corner of the query box.
60    /// - `Vector2D` - The maximum corner of the query box.
61    ///
62    /// # Returns
63    ///
64    /// - `Vec<usize>` - The list of candidate body indices.
65    pub fn query(&self, min: Vector2D, max: Vector2D) -> Vec<usize> {
66        let inv: f64 = self.get_inverse_cell_size();
67        let min_col: i32 = (min.get_x() * inv).floor() as i32;
68        let min_row: i32 = (min.get_y() * inv).floor() as i32;
69        let max_col: i32 = (max.get_x() * inv).floor() as i32;
70        let max_row: i32 = (max.get_y() * inv).floor() as i32;
71        let mut seen: HashSet<usize> = HashSet::new();
72        let mut result: Vec<usize> = Vec::new();
73        for col in min_col..=max_col {
74            for row in min_row..=max_row {
75                if let Some(entries) = self.get_cells().get(&(col, row)) {
76                    for index in entries {
77                        if seen.insert(*index) {
78                            result.push(*index);
79                        }
80                    }
81                }
82            }
83        }
84        result
85    }
86
87    /// Removes all entries from the grid, preparing it for a fresh insertion pass.
88    pub fn clear(&mut self) {
89        // Preserve each cell's underlying Vec buffer across frames so the
90        // spatial hash doesn't pay a fresh allocation cost on every tick.
91        self.get_mut_cells().values_mut().for_each(Vec::clear);
92    }
93
94    /// Appends all candidate body indices overlapping the query box into `out`,
95    /// deduplicating via the caller-provided `seen` set.
96    ///
97    /// Both `out` and `seen` are cleared first, so the caller can reuse the same
98    /// buffers across all queries in a step without any per-query allocation.
99    ///
100    /// # Arguments
101    ///
102    /// - `Vector2D` - The minimum corner of the query box.
103    /// - `Vector2D` - The maximum corner of the query box.
104    /// - `&mut Vec<usize>` - The output buffer, cleared then filled with candidates.
105    /// - `&mut HashSet<usize>` - The dedup scratch set, cleared then reused.
106    pub fn query_into(
107        &self,
108        min: Vector2D,
109        max: Vector2D,
110        out: &mut Vec<usize>,
111        seen: &mut HashSet<usize>,
112    ) {
113        out.clear();
114        seen.clear();
115        let inv: f64 = self.get_inverse_cell_size();
116        let min_col: i32 = (min.get_x() * inv).floor() as i32;
117        let min_row: i32 = (min.get_y() * inv).floor() as i32;
118        let max_col: i32 = (max.get_x() * inv).floor() as i32;
119        let max_row: i32 = (max.get_y() * inv).floor() as i32;
120        for col in min_col..=max_col {
121            for row in min_row..=max_row {
122                if let Some(entries) = self.get_cells().get(&(col, row)) {
123                    for index in entries {
124                        if seen.insert(*index) {
125                            out.push(*index);
126                        }
127                    }
128                }
129            }
130        }
131    }
132}
133
134/// Implements construction, insertion, and querying for `SpatialHashGrid3D`.
135impl SpatialHashGrid3D {
136    /// Creates a new 3D spatial hash grid with the given cell size.
137    ///
138    /// # Arguments
139    ///
140    /// - `f64` - The world-space size of each grid cell.
141    ///
142    /// # Returns
143    ///
144    /// - `SpatialHashGrid3D` - The new grid.
145    pub fn create(cell_size: f64) -> SpatialHashGrid3D {
146        let safe_size: f64 = cell_size.max(EPSILON);
147        let mut grid: SpatialHashGrid3D = SpatialHashGrid3D::new(safe_size);
148        grid.set_inverse_cell_size(1.0 / safe_size);
149        grid
150    }
151
152    /// Creates a new 3D spatial hash grid with the default cell size.
153    ///
154    /// # Returns
155    ///
156    /// - `SpatialHashGrid3D` - The new grid.
157    pub fn with_default_size() -> SpatialHashGrid3D {
158        Self::create(SPATIAL_DEFAULT_CELL_SIZE_3D)
159    }
160
161    /// Inserts a body index into all cells overlapping the given 3D bounding box.
162    ///
163    /// # Arguments
164    ///
165    /// - `usize` - The body index to insert.
166    /// - `Vector3D` - The minimum corner of the bounding box.
167    /// - `Vector3D` - The maximum corner of the bounding box.
168    pub fn insert(&mut self, index: usize, min: Vector3D, max: Vector3D) {
169        let inv: f64 = self.get_inverse_cell_size();
170        let min_col: i32 = (min.get_x() * inv).floor() as i32;
171        let min_row: i32 = (min.get_y() * inv).floor() as i32;
172        let min_layer: i32 = (min.get_z() * inv).floor() as i32;
173        let max_col: i32 = (max.get_x() * inv).floor() as i32;
174        let max_row: i32 = (max.get_y() * inv).floor() as i32;
175        let max_layer: i32 = (max.get_z() * inv).floor() as i32;
176        for col in min_col..=max_col {
177            for row in min_row..=max_row {
178                for layer in min_layer..=max_layer {
179                    self.get_mut_cells()
180                        .entry((col, row, layer))
181                        .or_default()
182                        .push(index);
183                }
184            }
185        }
186    }
187
188    /// Returns all candidate body indices whose cells overlap the given 3D bounding box.
189    ///
190    /// Deduplicates indices so each candidate appears at most once.
191    ///
192    /// # Arguments
193    ///
194    /// - `Vector3D` - The minimum corner of the query box.
195    /// - `Vector3D` - The maximum corner of the query box.
196    ///
197    /// # Returns
198    ///
199    /// - `Vec<usize>` - The list of candidate body indices.
200    pub fn query(&self, min: Vector3D, max: Vector3D) -> Vec<usize> {
201        let inv: f64 = self.get_inverse_cell_size();
202        let min_col: i32 = (min.get_x() * inv).floor() as i32;
203        let min_row: i32 = (min.get_y() * inv).floor() as i32;
204        let min_layer: i32 = (min.get_z() * inv).floor() as i32;
205        let max_col: i32 = (max.get_x() * inv).floor() as i32;
206        let max_row: i32 = (max.get_y() * inv).floor() as i32;
207        let max_layer: i32 = (max.get_z() * inv).floor() as i32;
208        let mut seen: HashSet<usize> = HashSet::new();
209        let mut result: Vec<usize> = Vec::new();
210        for col in min_col..=max_col {
211            for row in min_row..=max_row {
212                for layer in min_layer..=max_layer {
213                    if let Some(entries) = self.get_cells().get(&(col, row, layer)) {
214                        for index in entries {
215                            if seen.insert(*index) {
216                                result.push(*index);
217                            }
218                        }
219                    }
220                }
221            }
222        }
223        result
224    }
225
226    /// Removes all entries from the grid, preparing it for a fresh insertion pass.
227    pub fn clear(&mut self) {
228        // Preserve each cell's underlying Vec buffer across frames so the
229        // spatial hash doesn't pay a fresh allocation cost on every tick.
230        self.get_mut_cells().values_mut().for_each(Vec::clear);
231    }
232
233    /// Appends all candidate body indices overlapping the query box into `out`,
234    /// deduplicating via the caller-provided `seen` set.
235    ///
236    /// Both `out` and `seen` are cleared first, so the caller can reuse the same
237    /// buffers across all queries in a step without any per-query allocation.
238    ///
239    /// # Arguments
240    ///
241    /// - `Vector3D` - The minimum corner of the query box.
242    /// - `Vector3D` - The maximum corner of the query box.
243    /// - `&mut Vec<usize>` - The output buffer, cleared then filled with candidates.
244    /// - `&mut HashSet<usize>` - The dedup scratch set, cleared then reused.
245    pub fn query_into(
246        &self,
247        min: Vector3D,
248        max: Vector3D,
249        out: &mut Vec<usize>,
250        seen: &mut HashSet<usize>,
251    ) {
252        out.clear();
253        seen.clear();
254        let inv: f64 = self.get_inverse_cell_size();
255        let min_col: i32 = (min.get_x() * inv).floor() as i32;
256        let min_row: i32 = (min.get_y() * inv).floor() as i32;
257        let min_layer: i32 = (min.get_z() * inv).floor() as i32;
258        let max_col: i32 = (max.get_x() * inv).floor() as i32;
259        let max_row: i32 = (max.get_y() * inv).floor() as i32;
260        let max_layer: i32 = (max.get_z() * inv).floor() as i32;
261        for col in min_col..=max_col {
262            for row in min_row..=max_row {
263                for layer in min_layer..=max_layer {
264                    if let Some(entries) = self.get_cells().get(&(col, row, layer)) {
265                        for index in entries {
266                            if seen.insert(*index) {
267                                out.push(*index);
268                            }
269                        }
270                    }
271                }
272            }
273        }
274    }
275}
276/// Default-construction for [`SpatialHashGrid2D`].
277impl Default for SpatialHashGrid2D {
278    /// Constructs a default [`SpatialHashGrid2D`] value.
279    ///
280    /// # Returns
281    ///
282    /// - `SpatialHashGrid2D` - A default-constructed instance with the documented initial state.
283    fn default() -> SpatialHashGrid2D {
284        SpatialHashGrid2D::with_default_size()
285    }
286}
287
288/// Implements `Default` for `SpatialHashGrid3D` with the default cell size.
289impl Default for SpatialHashGrid3D {
290    /// Constructs a default [`SpatialHashGrid3D`] value.
291    ///
292    /// # Returns
293    ///
294    /// - `SpatialHashGrid3D` - A default-constructed instance with the documented initial state.
295    fn default() -> SpatialHashGrid3D {
296        SpatialHashGrid3D::with_default_size()
297    }
298}