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