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}