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}