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    /// 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}