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 /// A cell size that is not finite and strictly positive cannot produce a
8 /// usable grid: clamping it up to [`EPSILON`] leaves an inverse cell size
9 /// of `1e6`, so a body of ordinary size maps to a column range in the
10 /// millions and a single `insert` or `query` degenerates into a loop over
11 /// billions of empty cells. Such a request is treated as "no cell size
12 /// given" and falls back to [`SPATIAL_DEFAULT_CELL_SIZE_2D`], which keeps
13 /// both the grid and the per-operation work bounded.
14 ///
15 /// # Arguments
16 ///
17 /// - `f64` - The world-space size of each grid cell.
18 ///
19 /// # Returns
20 ///
21 /// - `SpatialHashGrid2D` - The new grid.
22 pub fn create(cell_size: f64) -> SpatialHashGrid2D {
23 let safe_size: f64 = Self::usable_cell_size(cell_size, SPATIAL_DEFAULT_CELL_SIZE_2D);
24 let mut grid: SpatialHashGrid2D = SpatialHashGrid2D::new(safe_size);
25 grid.set_inverse_cell_size(1.0 / safe_size);
26 grid
27 }
28
29 /// Returns `cell_size` when it is finite and strictly positive, and
30 /// `fallback` otherwise.
31 ///
32 /// Shared by the 2D and 3D grid constructors so both reject a degenerate
33 /// cell size identically.
34 ///
35 /// # Arguments
36 ///
37 /// - `f64` - The requested cell size.
38 /// - `f64` - The cell size to use when the request is unusable.
39 ///
40 /// # Returns
41 ///
42 /// - `f64` - A cell size that yields a bounded column range.
43 fn usable_cell_size(cell_size: f64, fallback: f64) -> f64 {
44 if cell_size.is_finite() && cell_size > 0.0 {
45 cell_size
46 } else {
47 fallback
48 }
49 }
50
51 /// Creates a new 2D spatial hash grid with the default cell size.
52 ///
53 /// # Returns
54 ///
55 /// - `SpatialHashGrid2D` - The new grid.
56 pub fn with_default_size() -> SpatialHashGrid2D {
57 Self::create(SPATIAL_DEFAULT_CELL_SIZE_2D)
58 }
59
60 /// Inserts a body index into all cells overlapping the given bounding box.
61 ///
62 /// # Arguments
63 ///
64 /// - `usize` - The body index to insert.
65 /// - `Vector2D` - The minimum corner of the bounding box.
66 /// - `Vector2D` - The maximum corner of the bounding box.
67 pub fn insert(&mut self, index: usize, min: Vector2D, max: Vector2D) {
68 let inv: f64 = self.get_inverse_cell_size();
69 let min_col: i32 = (min.get_x() * inv).floor() as i32;
70 let min_row: i32 = (min.get_y() * inv).floor() as i32;
71 let max_col: i32 = (max.get_x() * inv).floor() as i32;
72 let max_row: i32 = (max.get_y() * inv).floor() as i32;
73 for col in min_col..=max_col {
74 for row in min_row..=max_row {
75 self.get_mut_cells()
76 .entry((col, row))
77 .or_default()
78 .push(index);
79 }
80 }
81 }
82
83 /// Returns all candidate body indices whose cells overlap the given bounding box.
84 ///
85 /// Deduplicates indices so each candidate appears at most once.
86 ///
87 /// # Arguments
88 ///
89 /// - `Vector2D` - The minimum corner of the query box.
90 /// - `Vector2D` - The maximum corner of the query box.
91 ///
92 /// # Returns
93 ///
94 /// - `Vec<usize>` - The list of candidate body indices.
95 pub fn query(&self, min: Vector2D, max: Vector2D) -> Vec<usize> {
96 let inv: f64 = self.get_inverse_cell_size();
97 let min_col: i32 = (min.get_x() * inv).floor() as i32;
98 let min_row: i32 = (min.get_y() * inv).floor() as i32;
99 let max_col: i32 = (max.get_x() * inv).floor() as i32;
100 let max_row: i32 = (max.get_y() * inv).floor() as i32;
101 let mut seen: HashSet<usize> = HashSet::new();
102 let mut result: Vec<usize> = Vec::new();
103 for col in min_col..=max_col {
104 for row in min_row..=max_row {
105 if let Some(entries) = self.get_cells().get(&(col, row)) {
106 for index in entries {
107 if seen.insert(*index) {
108 result.push(*index);
109 }
110 }
111 }
112 }
113 }
114 result
115 }
116
117 /// Removes all entries from the grid, preparing it for a fresh insertion pass.
118 ///
119 /// Cells that held bodies during the just-finished pass keep their key and
120 /// their underlying `Vec` buffer, so a body that stays put pays no
121 /// allocation on the next tick. Cells that ended the pass empty are dropped
122 /// outright: without that, a scene whose bodies roam across a large world
123 /// accumulates one permanent map entry per cell ever visited, and both the
124 /// map and the per-tick clear below it grow without bound for the lifetime
125 /// of the process. Dropping on empty bounds the map to the cells the
126 /// current pass actually populates, at the cost of one small `Vec`
127 /// allocation for a cell a body leaves and then re-enters.
128 pub fn clear(&mut self) {
129 // Preserve each occupied cell's underlying Vec buffer across frames so
130 // the spatial hash doesn't pay a fresh allocation cost on every tick,
131 // while reclaiming the keys of cells that went empty.
132 let cells: &mut SpatialCellMap2D = self.get_mut_cells();
133 cells.retain(|_, entries: &mut Vec<usize>| !entries.is_empty());
134 cells.values_mut().for_each(Vec::clear);
135 }
136
137 /// Appends all candidate body indices overlapping the query box into `out`,
138 /// deduplicating via the caller-provided `seen` set.
139 ///
140 /// Both `out` and `seen` are cleared first, so the caller can reuse the same
141 /// buffers across all queries in a step without any per-query allocation.
142 ///
143 /// # Arguments
144 ///
145 /// - `Vector2D` - The minimum corner of the query box.
146 /// - `Vector2D` - The maximum corner of the query box.
147 /// - `&mut Vec<usize>` - The output buffer, cleared then filled with candidates.
148 /// - `&mut HashSet<usize>` - The dedup scratch set, cleared then reused.
149 pub fn query_into(
150 &self,
151 min: Vector2D,
152 max: Vector2D,
153 out: &mut Vec<usize>,
154 seen: &mut HashSet<usize>,
155 ) {
156 out.clear();
157 seen.clear();
158 let inv: f64 = self.get_inverse_cell_size();
159 let min_col: i32 = (min.get_x() * inv).floor() as i32;
160 let min_row: i32 = (min.get_y() * inv).floor() as i32;
161 let max_col: i32 = (max.get_x() * inv).floor() as i32;
162 let max_row: i32 = (max.get_y() * inv).floor() as i32;
163 for col in min_col..=max_col {
164 for row in min_row..=max_row {
165 if let Some(entries) = self.get_cells().get(&(col, row)) {
166 for index in entries {
167 if seen.insert(*index) {
168 out.push(*index);
169 }
170 }
171 }
172 }
173 }
174 }
175}
176
177/// Implements construction, insertion, and querying for `SpatialHashGrid3D`.
178impl SpatialHashGrid3D {
179 /// Creates a new 3D spatial hash grid with the given cell size.
180 ///
181 /// A cell size that is not finite and strictly positive is rejected in
182 /// favour of [`SPATIAL_DEFAULT_CELL_SIZE_3D`], for the same reason as
183 /// [`SpatialHashGrid2D::create`]: a near-zero cell size makes the column
184 /// range of an ordinary body span millions of cells.
185 ///
186 /// # Arguments
187 ///
188 /// - `f64` - The world-space size of each grid cell.
189 ///
190 /// # Returns
191 ///
192 /// - `SpatialHashGrid3D` - The new grid.
193 pub fn create(cell_size: f64) -> SpatialHashGrid3D {
194 let safe_size: f64 =
195 SpatialHashGrid2D::usable_cell_size(cell_size, SPATIAL_DEFAULT_CELL_SIZE_3D);
196 let mut grid: SpatialHashGrid3D = SpatialHashGrid3D::new(safe_size);
197 grid.set_inverse_cell_size(1.0 / safe_size);
198 grid
199 }
200
201 /// Creates a new 3D spatial hash grid with the default cell size.
202 ///
203 /// # Returns
204 ///
205 /// - `SpatialHashGrid3D` - The new grid.
206 pub fn with_default_size() -> SpatialHashGrid3D {
207 Self::create(SPATIAL_DEFAULT_CELL_SIZE_3D)
208 }
209
210 /// Inserts a body index into all cells overlapping the given 3D bounding box.
211 ///
212 /// # Arguments
213 ///
214 /// - `usize` - The body index to insert.
215 /// - `Vector3D` - The minimum corner of the bounding box.
216 /// - `Vector3D` - The maximum corner of the bounding box.
217 pub fn insert(&mut self, index: usize, min: Vector3D, max: Vector3D) {
218 let inv: f64 = self.get_inverse_cell_size();
219 let min_col: i32 = (min.get_x() * inv).floor() as i32;
220 let min_row: i32 = (min.get_y() * inv).floor() as i32;
221 let min_layer: i32 = (min.get_z() * inv).floor() as i32;
222 let max_col: i32 = (max.get_x() * inv).floor() as i32;
223 let max_row: i32 = (max.get_y() * inv).floor() as i32;
224 let max_layer: i32 = (max.get_z() * inv).floor() as i32;
225 for col in min_col..=max_col {
226 for row in min_row..=max_row {
227 for layer in min_layer..=max_layer {
228 self.get_mut_cells()
229 .entry((col, row, layer))
230 .or_default()
231 .push(index);
232 }
233 }
234 }
235 }
236
237 /// Returns all candidate body indices whose cells overlap the given 3D bounding box.
238 ///
239 /// Deduplicates indices so each candidate appears at most once.
240 ///
241 /// # Arguments
242 ///
243 /// - `Vector3D` - The minimum corner of the query box.
244 /// - `Vector3D` - The maximum corner of the query box.
245 ///
246 /// # Returns
247 ///
248 /// - `Vec<usize>` - The list of candidate body indices.
249 pub fn query(&self, min: Vector3D, max: Vector3D) -> Vec<usize> {
250 let inv: f64 = self.get_inverse_cell_size();
251 let min_col: i32 = (min.get_x() * inv).floor() as i32;
252 let min_row: i32 = (min.get_y() * inv).floor() as i32;
253 let min_layer: i32 = (min.get_z() * inv).floor() as i32;
254 let max_col: i32 = (max.get_x() * inv).floor() as i32;
255 let max_row: i32 = (max.get_y() * inv).floor() as i32;
256 let max_layer: i32 = (max.get_z() * inv).floor() as i32;
257 let mut seen: HashSet<usize> = HashSet::new();
258 let mut result: Vec<usize> = Vec::new();
259 for col in min_col..=max_col {
260 for row in min_row..=max_row {
261 for layer in min_layer..=max_layer {
262 if let Some(entries) = self.get_cells().get(&(col, row, layer)) {
263 for index in entries {
264 if seen.insert(*index) {
265 result.push(*index);
266 }
267 }
268 }
269 }
270 }
271 }
272 result
273 }
274
275 /// Removes all entries from the grid, preparing it for a fresh insertion pass.
276 ///
277 /// Cells that held bodies during the just-finished pass keep their key and
278 /// their underlying `Vec` buffer, so a body that stays put pays no
279 /// allocation on the next tick. Cells that ended the pass empty are dropped
280 /// outright: without that, a scene whose bodies roam across a large world
281 /// accumulates one permanent map entry per cell ever visited, and both the
282 /// map and the per-tick clear below it grow without bound for the lifetime
283 /// of the process. Dropping on empty bounds the map to the cells the
284 /// current pass actually populates, at the cost of one small `Vec`
285 /// allocation for a cell a body leaves and then re-enters.
286 pub fn clear(&mut self) {
287 // Preserve each occupied cell's underlying Vec buffer across frames so
288 // the spatial hash doesn't pay a fresh allocation cost on every tick,
289 // while reclaiming the keys of cells that went empty.
290 let cells: &mut SpatialCellMap3D = self.get_mut_cells();
291 cells.retain(|_, entries: &mut Vec<usize>| !entries.is_empty());
292 cells.values_mut().for_each(Vec::clear);
293 }
294
295 /// Appends all candidate body indices overlapping the query box into `out`,
296 /// deduplicating via the caller-provided `seen` set.
297 ///
298 /// Both `out` and `seen` are cleared first, so the caller can reuse the same
299 /// buffers across all queries in a step without any per-query allocation.
300 ///
301 /// # Arguments
302 ///
303 /// - `Vector3D` - The minimum corner of the query box.
304 /// - `Vector3D` - The maximum corner of the query box.
305 /// - `&mut Vec<usize>` - The output buffer, cleared then filled with candidates.
306 /// - `&mut HashSet<usize>` - The dedup scratch set, cleared then reused.
307 pub fn query_into(
308 &self,
309 min: Vector3D,
310 max: Vector3D,
311 out: &mut Vec<usize>,
312 seen: &mut HashSet<usize>,
313 ) {
314 out.clear();
315 seen.clear();
316 let inv: f64 = self.get_inverse_cell_size();
317 let min_col: i32 = (min.get_x() * inv).floor() as i32;
318 let min_row: i32 = (min.get_y() * inv).floor() as i32;
319 let min_layer: i32 = (min.get_z() * inv).floor() as i32;
320 let max_col: i32 = (max.get_x() * inv).floor() as i32;
321 let max_row: i32 = (max.get_y() * inv).floor() as i32;
322 let max_layer: i32 = (max.get_z() * inv).floor() as i32;
323 for col in min_col..=max_col {
324 for row in min_row..=max_row {
325 for layer in min_layer..=max_layer {
326 if let Some(entries) = self.get_cells().get(&(col, row, layer)) {
327 for index in entries {
328 if seen.insert(*index) {
329 out.push(*index);
330 }
331 }
332 }
333 }
334 }
335 }
336 }
337}
338/// Default-construction for [`SpatialHashGrid2D`].
339impl Default for SpatialHashGrid2D {
340 /// Constructs a default [`SpatialHashGrid2D`] value.
341 ///
342 /// # Returns
343 ///
344 /// - `SpatialHashGrid2D` - A default-constructed instance with the documented initial state.
345 fn default() -> SpatialHashGrid2D {
346 SpatialHashGrid2D::with_default_size()
347 }
348}
349
350/// Implements `Default` for `SpatialHashGrid3D` with the default cell size.
351impl Default for SpatialHashGrid3D {
352 /// Constructs a default [`SpatialHashGrid3D`] value.
353 ///
354 /// # Returns
355 ///
356 /// - `SpatialHashGrid3D` - A default-constructed instance with the documented initial state.
357 fn default() -> SpatialHashGrid3D {
358 SpatialHashGrid3D::with_default_size()
359 }
360}
361/// Implements construction, insertion, subdivision, and querying for `QuadTree2D`.
362impl QuadTree2D {
363 /// Creates a new 2D quadtree rooted at the given region.
364 ///
365 /// The root region is normalized so the first corner is always the minimum,
366 /// and a zero-width or zero-height region is widened by [`EPSILON`] so
367 /// subdivision always makes progress. A capacity below one is raised to
368 /// one, and a depth above [`SPATIAL_MAX_DEPTH_2D`] is clamped.
369 ///
370 /// # Arguments
371 ///
372 /// - `Vector2D` - One corner of the root region.
373 /// - `Vector2D` - The opposite corner of the root region.
374 /// - `usize` - The number of entries a node may hold before subdividing.
375 /// - `usize` - The deepest subdivision level a node may reach.
376 ///
377 /// # Returns
378 ///
379 /// - `QuadTree2D` - The new empty quadtree.
380 pub fn create(
381 bounds_min: Vector2D,
382 bounds_max: Vector2D,
383 capacity: usize,
384 max_depth: usize,
385 ) -> QuadTree2D {
386 let min: Vector2D = Vector2D::new(
387 bounds_min.get_x().min(bounds_max.get_x()),
388 bounds_min.get_y().min(bounds_max.get_y()),
389 );
390 let max: Vector2D = Vector2D::new(
391 bounds_min.get_x().max(bounds_max.get_x()) + EPSILON,
392 bounds_min.get_y().max(bounds_max.get_y()) + EPSILON,
393 );
394 let root: QuadTreeNode2D = QuadTreeNode2D::new(
395 min,
396 max,
397 0,
398 [SPATIAL_QUAD_TREE_NO_CHILD; 4],
399 true,
400 false,
401 Vec::new(),
402 );
403 QuadTree2D {
404 nodes: vec![root],
405 capacity: capacity.max(1),
406 max_depth: max_depth.min(SPATIAL_MAX_DEPTH_2D),
407 count: 0,
408 }
409 }
410
411 /// Creates a new 2D quadtree with the default capacity, depth, and a root
412 /// region centred on the world origin.
413 ///
414 /// # Returns
415 ///
416 /// - `QuadTree2D` - The new empty quadtree.
417 pub fn with_default_size() -> QuadTree2D {
418 let extent: f64 = SPATIAL_DEFAULT_HALF_EXTENT_2D;
419 QuadTree2D::create(
420 Vector2D::new(-extent, -extent),
421 Vector2D::new(extent, extent),
422 SPATIAL_DEFAULT_CAPACITY_2D,
423 SPATIAL_DEFAULT_MAX_DEPTH_2D,
424 )
425 }
426
427 /// Creates a new 2D quadtree over a square region of the given half-extent,
428 /// using the default capacity and depth.
429 ///
430 /// # Arguments
431 ///
432 /// - `f64` - The half-extent of the root region in world units.
433 ///
434 /// # Returns
435 ///
436 /// - `QuadTree2D` - The new empty quadtree.
437 pub fn with_half_extent(half_extent: f64) -> QuadTree2D {
438 let extent: f64 = half_extent.abs().max(EPSILON);
439 QuadTree2D::create(
440 Vector2D::new(-extent, -extent),
441 Vector2D::new(extent, extent),
442 SPATIAL_DEFAULT_CAPACITY_2D,
443 SPATIAL_DEFAULT_MAX_DEPTH_2D,
444 )
445 }
446
447 /// Returns the number of bodies currently stored in the tree.
448 ///
449 /// # Returns
450 ///
451 /// - `usize` - The number of inserted bodies.
452 pub fn len(&self) -> usize {
453 self.get_count()
454 }
455
456 /// Reports whether the tree holds no bodies.
457 ///
458 /// # Returns
459 ///
460 /// - `bool` - `true` when no body is stored.
461 pub fn is_empty(&self) -> bool {
462 self.get_count() == 0
463 }
464
465 /// Returns the region covered by the root node.
466 ///
467 /// # Returns
468 ///
469 /// - `(Vector2D, Vector2D)` - The minimum and maximum corners of the root region.
470 pub fn bounds(&self) -> (Vector2D, Vector2D) {
471 let root: &QuadTreeNode2D = &self.get_nodes()[SPATIAL_QUAD_TREE_ROOT_INDEX];
472 (root.get_min(), root.get_max())
473 }
474
475 /// Inserts a body index together with its exact bounding box.
476 ///
477 /// The entry descends into the child region that fully contains it. An
478 /// entry that straddles a split boundary, or that is larger than the node
479 /// it lands in, stays in the current node and marks that node `loose` —
480 /// that is the standard quadtree invariant, and it is what guarantees a
481 /// body is stored exactly once, so queries never need dedup beyond the
482 /// caller's scratch set.
483 ///
484 /// # Arguments
485 ///
486 /// - `usize` - The body index to insert.
487 /// - `Vector2D` - The minimum corner of the bounding box.
488 /// - `Vector2D` - The maximum corner of the bounding box.
489 pub fn insert(&mut self, index: usize, min: Vector2D, max: Vector2D) {
490 let entry: QuadTreeEntry2D = QuadTreeEntry2D::new(index, min, max);
491 let mut handle: usize = SPATIAL_QUAD_TREE_ROOT_INDEX;
492 loop {
493 let node: &QuadTreeNode2D = &self.get_nodes()[handle];
494 if node.get_leaf() {
495 break;
496 }
497 let next: usize = QuadTree2D::child_containing(self.get_nodes(), handle, &entry);
498 if next == SPATIAL_QUAD_TREE_NO_CHILD {
499 break;
500 }
501 handle = next;
502 }
503 if !QuadTree2D::contains_box(
504 self.get_nodes()[handle].get_min(),
505 self.get_nodes()[handle].get_max(),
506 entry.get_min(),
507 entry.get_max(),
508 ) {
509 self.get_mut_nodes()[handle].set_loose(true);
510 }
511 self.get_mut_nodes()[handle].get_mut_entries().push(entry);
512 let capacity: usize = self.get_capacity();
513 let max_depth: usize = self.get_max_depth();
514 QuadTree2D::subdivide(self.get_mut_nodes(), handle, capacity, max_depth);
515 let count: usize = self.get_count();
516 self.set_count(count + 1);
517 }
518
519 /// Returns all body indices whose exact bounding box overlaps the query box.
520 ///
521 /// Each candidate index appears at most once.
522 ///
523 /// # Arguments
524 ///
525 /// - `Vector2D` - The minimum corner of the query box.
526 /// - `Vector2D` - The maximum corner of the query box.
527 ///
528 /// # Returns
529 ///
530 /// - `Vec<usize>` - The list of candidate body indices.
531 pub fn query(&self, min: Vector2D, max: Vector2D) -> Vec<usize> {
532 let mut out: Vec<usize> = Vec::new();
533 let mut seen: HashSet<usize> = HashSet::new();
534 self.query_into(min, max, &mut out, &mut seen);
535 out
536 }
537
538 /// Appends all candidate body indices overlapping the query box into `out`,
539 /// deduplicating via the caller-provided `seen` set.
540 ///
541 /// Both `out` and `seen` are cleared first, so the caller can reuse the same
542 /// buffers across all queries in a step without any per-query allocation.
543 ///
544 /// Nodes are pruned by region overlap, then every entry that survives
545 /// pruning is confirmed with an exact box-vs-box test. A node holding a body
546 /// larger than its own region is marked `loose` and skips the region prune
547 /// entirely, which is what keeps such a body — including one larger than the
548 /// whole root region — returned by every query that overlaps it. The
549 /// traversal uses an explicit stack, so no recursion is involved.
550 ///
551 /// # Arguments
552 ///
553 /// - `Vector2D` - The minimum corner of the query box.
554 /// - `Vector2D` - The maximum corner of the query box.
555 /// - `&mut Vec<usize>` - The output buffer, cleared then filled with candidates.
556 /// - `&mut HashSet<usize>` - The dedup scratch set, cleared then reused.
557 pub fn query_into(
558 &self,
559 min: Vector2D,
560 max: Vector2D,
561 out: &mut Vec<usize>,
562 seen: &mut HashSet<usize>,
563 ) {
564 out.clear();
565 seen.clear();
566 let mut stack: QuadTreeNodeStack2D = Vec::new();
567 stack.push(SPATIAL_QUAD_TREE_ROOT_INDEX);
568 while let Some(handle) = stack.pop() {
569 let node: &QuadTreeNode2D = &self.get_nodes()[handle];
570 if !node.get_loose()
571 && !QuadTree2D::boxes_overlap(node.get_min(), node.get_max(), min, max)
572 {
573 continue;
574 }
575 for entry in node.get_entries() {
576 if !QuadTree2D::boxes_overlap(entry.get_min(), entry.get_max(), min, max) {
577 continue;
578 }
579 let index: usize = entry.get_index();
580 if seen.insert(index) {
581 out.push(index);
582 }
583 }
584 if !node.get_leaf() {
585 for &child in node.get_children().iter() {
586 stack.push(child);
587 }
588 }
589 }
590 }
591
592 /// Removes every entry and child node, keeping only the empty root so the
593 /// same tree can be reused for a fresh insertion pass.
594 pub fn clear(&mut self) {
595 let mut root: QuadTreeNode2D = self.get_nodes()[SPATIAL_QUAD_TREE_ROOT_INDEX].clone();
596 root.set_children([SPATIAL_QUAD_TREE_NO_CHILD; 4]);
597 root.set_leaf(true);
598 root.set_loose(false);
599 root.get_mut_entries().clear();
600 self.get_mut_nodes().clear();
601 self.get_mut_nodes().push(root);
602 self.set_count(0);
603 }
604
605 /// Reports whether two axis-aligned boxes overlap.
606 ///
607 /// Touching edges count as overlapping, matching the inclusive cell
608 /// semantics of [`SpatialHashGrid2D::query`].
609 ///
610 /// # Arguments
611 ///
612 /// - `Vector2D` - The minimum corner of the first box.
613 /// - `Vector2D` - The maximum corner of the first box.
614 /// - `Vector2D` - The minimum corner of the second box.
615 /// - `Vector2D` - The maximum corner of the second box.
616 ///
617 /// # Returns
618 ///
619 /// - `bool` - `true` when the two boxes share at least one point.
620 pub fn boxes_overlap(
621 a_min: Vector2D,
622 a_max: Vector2D,
623 b_min: Vector2D,
624 b_max: Vector2D,
625 ) -> bool {
626 a_min.get_x() <= b_max.get_x()
627 && b_min.get_x() <= a_max.get_x()
628 && a_min.get_y() <= b_max.get_y()
629 && b_min.get_y() <= a_max.get_y()
630 }
631
632 /// Returns the child handle whose region fully contains the entry, or
633 /// [`SPATIAL_QUAD_TREE_NO_CHILD`] when the entry straddles the split.
634 ///
635 /// # Arguments
636 ///
637 /// - `&QuadTreeNodeList2D` - The node arena holding the child regions.
638 /// - `usize` - The handle of the subdivided parent node.
639 /// - `&QuadTreeEntry2D` - The entry being placed.
640 ///
641 /// # Returns
642 ///
643 /// - `usize` - The containing child handle, or `SPATIAL_QUAD_TREE_NO_CHILD`.
644 fn child_containing(
645 nodes: &QuadTreeNodeList2D,
646 handle: usize,
647 entry: &QuadTreeEntry2D,
648 ) -> usize {
649 let children: &QuadTreeChildren2D = nodes[handle].get_children();
650 for &child in children.iter() {
651 if child == SPATIAL_QUAD_TREE_NO_CHILD {
652 continue;
653 }
654 let region: &QuadTreeNode2D = &nodes[child];
655 if QuadTree2D::contains_box(
656 region.get_min(),
657 region.get_max(),
658 entry.get_min(),
659 entry.get_max(),
660 ) {
661 return child;
662 }
663 }
664 SPATIAL_QUAD_TREE_NO_CHILD
665 }
666
667 /// Reports whether the first region fully contains the second box.
668 ///
669 /// # Arguments
670 ///
671 /// - `Vector2D` - The minimum corner of the containing region.
672 /// - `Vector2D` - The maximum corner of the containing region.
673 /// - `Vector2D` - The minimum corner of the contained box.
674 /// - `Vector2D` - The maximum corner of the contained box.
675 ///
676 /// # Returns
677 ///
678 /// - `bool` - `true` when the region covers the box on both axes.
679 fn contains_box(
680 outer_min: Vector2D,
681 outer_max: Vector2D,
682 inner_min: Vector2D,
683 inner_max: Vector2D,
684 ) -> bool {
685 outer_min.get_x() <= inner_min.get_x()
686 && outer_min.get_y() <= inner_min.get_y()
687 && outer_max.get_x() >= inner_max.get_x()
688 && outer_max.get_y() >= inner_max.get_y()
689 }
690
691 /// Splits the node at `handle` into four children when it is over capacity
692 /// and still below the maximum depth, redistributing the entries it holds.
693 ///
694 /// Entries that fit entirely inside one child move down; everything else
695 /// stays in the parent and marks it `loose`. A node whose depth already
696 /// equals the maximum depth is left alone, so a pile of coincident bodies
697 /// terminates instead of subdividing forever.
698 ///
699 /// # Arguments
700 ///
701 /// - `&mut QuadTreeNodeList2D` - The mutable node arena to subdivide in place.
702 /// - `usize` - The handle of the node to subdivide.
703 /// - `usize` - The per-node entry capacity.
704 /// - `usize` - The deepest subdivision level allowed.
705 fn subdivide(nodes: &mut QuadTreeNodeList2D, handle: usize, capacity: usize, max_depth: usize) {
706 if !nodes[handle].get_leaf() || nodes[handle].get_depth() >= max_depth {
707 return;
708 }
709 if nodes[handle].get_entries().len() <= capacity {
710 return;
711 }
712 // Phase 1: take the parent's entries out and materialize the four child
713 // regions. Draining first is what keeps a straddling entry from being
714 // stored twice, once here and once in the child that accepted it.
715 let drained: QuadTreeEntryList2D = mem::take(nodes[handle].get_mut_entries());
716 let depth: usize = nodes[handle].get_depth() + 1;
717 let mut mid: Vector2D = Vector2D::new(
718 nodes[handle].get_min().get_x()
719 + (nodes[handle].get_max().get_x() - nodes[handle].get_min().get_x())
720 * SPATIAL_QUAD_TREE_MID_RATIO,
721 nodes[handle].get_min().get_y()
722 + (nodes[handle].get_max().get_y() - nodes[handle].get_min().get_y())
723 * SPATIAL_QUAD_TREE_MID_RATIO,
724 );
725 if mid.get_x() <= nodes[handle].get_min().get_x() {
726 mid = Vector2D::new(nodes[handle].get_min().get_x() + EPSILON, mid.get_y());
727 }
728 if mid.get_y() <= nodes[handle].get_min().get_y() {
729 mid = Vector2D::new(mid.get_x(), nodes[handle].get_min().get_y() + EPSILON);
730 }
731 let regions: [QuadTreeNode2D; 4] = [
732 QuadTreeNode2D::new(
733 nodes[handle].get_min(),
734 Vector2D::new(mid.get_x(), mid.get_y()),
735 depth,
736 [SPATIAL_QUAD_TREE_NO_CHILD; 4],
737 true,
738 false,
739 Vec::new(),
740 ),
741 QuadTreeNode2D::new(
742 Vector2D::new(mid.get_x(), nodes[handle].get_min().get_y()),
743 Vector2D::new(nodes[handle].get_max().get_x(), mid.get_y()),
744 depth,
745 [SPATIAL_QUAD_TREE_NO_CHILD; 4],
746 true,
747 false,
748 Vec::new(),
749 ),
750 QuadTreeNode2D::new(
751 Vector2D::new(nodes[handle].get_min().get_x(), mid.get_y()),
752 Vector2D::new(mid.get_x(), nodes[handle].get_max().get_y()),
753 depth,
754 [SPATIAL_QUAD_TREE_NO_CHILD; 4],
755 true,
756 false,
757 Vec::new(),
758 ),
759 QuadTreeNode2D::new(
760 mid,
761 nodes[handle].get_max(),
762 depth,
763 [SPATIAL_QUAD_TREE_NO_CHILD; 4],
764 true,
765 false,
766 Vec::new(),
767 ),
768 ];
769 let mut handles: QuadTreeChildren2D = [SPATIAL_QUAD_TREE_NO_CHILD; 4];
770 for (slot, region) in handles.iter_mut().zip(regions.iter()) {
771 *slot = nodes.len();
772 nodes.push(region.clone());
773 }
774 // Phase 2: demote the parent, then push every fully contained entry one
775 // level down and return the rest to the parent.
776 nodes[handle].set_children(handles);
777 nodes[handle].set_leaf(false);
778 for entry in drained.iter() {
779 let mut target: Option<usize> = None;
780 for &child in handles.iter() {
781 let region: &QuadTreeNode2D = &nodes[child];
782 if QuadTree2D::contains_box(
783 region.get_min(),
784 region.get_max(),
785 entry.get_min(),
786 entry.get_max(),
787 ) {
788 target = Some(child);
789 break;
790 }
791 }
792 match target {
793 Some(child) => {
794 nodes[child].get_mut_entries().push(*entry);
795 }
796 None => {
797 nodes[handle].set_loose(true);
798 nodes[handle].get_mut_entries().push(*entry);
799 }
800 }
801 }
802 // Phase 3: a child that is itself over capacity splits immediately, so
803 // one insert call always leaves the whole subtree consistent.
804 for &child in handles.iter() {
805 QuadTree2D::subdivide(nodes, child, capacity, max_depth);
806 }
807 }
808}
809
810/// Default-construction for [`QuadTree2D`].
811impl Default for QuadTree2D {
812 /// Constructs a default [`QuadTree2D`] value.
813 ///
814 /// # Returns
815 ///
816 /// - `QuadTree2D` - A default-constructed instance with the documented initial state.
817 fn default() -> QuadTree2D {
818 QuadTree2D::with_default_size()
819 }
820}