Skip to main content

euv_engine/spatial/
impl.rs

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