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}