1use crate::storage::data_structures::NodePoint;
9use glam::Vec3;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
14pub struct OctreeQueryStats {
15 pub nodes_visited: usize,
17 pub leaf_nodes_scanned: usize,
19 pub points_tested: usize,
21 pub points_returned: usize,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct BoundingBox {
28 pub min: Vec3,
30 pub max: Vec3,
32}
33
34impl BoundingBox {
35 pub fn new(min: Vec3, max: Vec3) -> Self {
37 Self { min, max }
38 }
39
40 pub fn centered(center: Vec3, half_size: f32) -> Self {
42 Self {
43 min: center - Vec3::splat(half_size),
44 max: center + Vec3::splat(half_size),
45 }
46 }
47
48 pub fn contains(&self, point: Vec3) -> bool {
50 point.x >= self.min.x
51 && point.x <= self.max.x
52 && point.y >= self.min.y
53 && point.y <= self.max.y
54 && point.z >= self.min.z
55 && point.z <= self.max.z
56 }
57
58 pub fn intersects(&self, other: &BoundingBox) -> bool {
60 self.min.x <= other.max.x
61 && self.max.x >= other.min.x
62 && self.min.y <= other.max.y
63 && self.max.y >= other.min.y
64 && self.min.z <= other.max.z
65 && self.max.z >= other.min.z
66 }
67
68 pub fn center(&self) -> Vec3 {
70 (self.min + self.max) * 0.5
71 }
72
73 pub fn size(&self) -> Vec3 {
75 self.max - self.min
76 }
77
78 pub fn subdivide(&self) -> [BoundingBox; 8] {
80 let center = self.center();
81 let _half_size = (self.max - self.min) * 0.5;
83
84 [
85 BoundingBox::new(self.min, center), BoundingBox::new(
88 Vec3::new(center.x, self.min.y, self.min.z),
89 Vec3::new(self.max.x, center.y, center.z),
90 ), BoundingBox::new(
92 Vec3::new(self.min.x, center.y, self.min.z),
93 Vec3::new(center.x, self.max.y, center.z),
94 ), BoundingBox::new(
96 Vec3::new(center.x, center.y, self.min.z),
97 Vec3::new(self.max.x, self.max.y, center.z),
98 ), BoundingBox::new(
101 Vec3::new(self.min.x, self.min.y, center.z),
102 Vec3::new(center.x, center.y, self.max.z),
103 ), BoundingBox::new(
105 Vec3::new(center.x, self.min.y, center.z),
106 Vec3::new(self.max.x, center.y, self.max.z),
107 ), BoundingBox::new(
109 Vec3::new(self.min.x, center.y, center.z),
110 Vec3::new(center.x, self.max.y, self.max.z),
111 ), BoundingBox::new(center, self.max), ]
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct OctreeNode {
120 pub bounds: BoundingBox,
122
123 pub depth: u32,
125
126 pub nodes: Vec<NodePoint>,
128
129 pub children: Option<[Box<OctreeNode>; 8]>,
131
132 pub is_subdivided: bool,
134}
135
136impl OctreeNode {
137 pub fn new(bounds: BoundingBox, depth: u32) -> Self {
139 Self {
140 bounds,
141 depth,
142 nodes: Vec::new(),
143 children: None,
144 is_subdivided: false,
145 }
146 }
147
148 pub fn is_leaf(&self) -> bool {
150 !self.is_subdivided
151 }
152
153 pub fn capacity(&self) -> usize {
155 (8 + self.depth as usize * 2).min(32)
158 }
159
160 pub fn insert(&mut self, node: NodePoint) -> bool {
167 let point = Vec3::new(node.x, node.y, node.z);
169 if !self.bounds.contains(point) {
170 return false;
171 }
172
173 if self.is_leaf() {
174 if self.nodes.len() < self.capacity() || self.depth >= 16 {
176 self.nodes.push(node);
177 return true;
178 }
179
180 self.subdivide();
182
183 let mut i = 0;
185 while i < self.nodes.len() {
186 let existing_node = self.nodes[i];
187 let redistributed = self.insert_into_children(existing_node);
188 if redistributed {
189 self.nodes.swap_remove(i);
190 } else {
191 i += 1;
192 }
193 }
194 }
195
196 self.insert_into_children(node)
198 }
199
200 fn subdivide(&mut self) {
202 let child_bounds = self.bounds.subdivide();
203 let child_depth = self.depth + 1;
204
205 let children = [
206 Box::new(OctreeNode::new(child_bounds[0], child_depth)),
207 Box::new(OctreeNode::new(child_bounds[1], child_depth)),
208 Box::new(OctreeNode::new(child_bounds[2], child_depth)),
209 Box::new(OctreeNode::new(child_bounds[3], child_depth)),
210 Box::new(OctreeNode::new(child_bounds[4], child_depth)),
211 Box::new(OctreeNode::new(child_bounds[5], child_depth)),
212 Box::new(OctreeNode::new(child_bounds[6], child_depth)),
213 Box::new(OctreeNode::new(child_bounds[7], child_depth)),
214 ];
215
216 self.children = Some(children);
217 self.is_subdivided = true;
218 }
219
220 fn insert_into_children(&mut self, node: NodePoint) -> bool {
222 if let Some(ref mut children) = self.children {
223 let point = Vec3::new(node.x, node.y, node.z);
224 for child in children.iter_mut() {
225 if child.bounds.contains(point) {
226 return child.insert(node);
227 }
228 }
229 }
230 false
231 }
232
233 pub fn query_sphere(&self, center: Vec3, radius: f32, results: &mut Vec<NodePoint>) {
237 let mut stats = OctreeQueryStats::default();
238 self.query_sphere_with_stats(center, radius, results, &mut stats);
239 }
240
241 pub fn query_sphere_with_stats(
243 &self,
244 center: Vec3,
245 radius: f32,
246 results: &mut Vec<NodePoint>,
247 stats: &mut OctreeQueryStats,
248 ) {
249 stats.nodes_visited += 1;
250
251 if !self.intersects_sphere(center, radius) {
253 return;
254 }
255
256 if self.is_leaf() {
258 stats.leaf_nodes_scanned += 1;
259 for node in &self.nodes {
260 stats.points_tested += 1;
261 let node_pos = Vec3::new(node.x, node.y, node.z);
262 let distance_sq = (node_pos - center).length_squared();
263 if distance_sq <= radius * radius {
264 results.push(*node);
265 stats.points_returned += 1;
266 }
267 }
268 } else {
269 if let Some(ref children) = self.children {
271 for child in children {
272 child.query_sphere_with_stats(center, radius, results, stats);
273 }
274 }
275 }
276 }
277
278 fn intersects_sphere(&self, center: Vec3, radius: f32) -> bool {
280 let closest_x = center.x.clamp(self.bounds.min.x, self.bounds.max.x);
282 let closest_y = center.y.clamp(self.bounds.min.y, self.bounds.max.y);
283 let closest_z = center.z.clamp(self.bounds.min.z, self.bounds.max.z);
284 let closest_point = Vec3::new(closest_x, closest_y, closest_z);
285
286 let distance_sq = (closest_point - center).length_squared();
288 distance_sq <= radius * radius
289 }
290
291 pub fn node_count(&self) -> usize {
293 let mut count = self.nodes.len();
294 if let Some(ref children) = self.children {
295 for child in children {
296 count += child.node_count();
297 }
298 }
299 count
300 }
301
302 pub fn leaf_count(&self) -> usize {
304 if self.is_leaf() {
305 1
306 } else {
307 let mut count = 0;
308 if let Some(ref children) = self.children {
309 for child in children {
310 count += child.leaf_count();
311 }
312 }
313 count
314 }
315 }
316
317 pub fn max_depth(&self) -> u32 {
319 if self.is_leaf() {
320 self.depth
321 } else {
322 let mut max_child_depth = self.depth;
323 if let Some(ref children) = self.children {
324 for child in children {
325 max_child_depth = max_child_depth.max(child.max_depth());
326 }
327 }
328 max_child_depth
329 }
330 }
331}
332
333#[derive(Debug, Clone)]
339pub struct Octree {
340 root: OctreeNode,
342
343 node_count: usize,
345}
346
347impl Octree {
348 pub fn new(bounds: BoundingBox) -> Self {
350 Self {
351 root: OctreeNode::new(bounds, 0),
352 node_count: 0,
353 }
354 }
355
356 pub fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
361 let mut nodes = Vec::with_capacity(self.node_count);
363 self.collect_nodes(&self.root, &mut nodes);
364
365 let mut bytes = Vec::new();
367
368 bytes.extend_from_slice(&self.root.bounds.min.x.to_le_bytes());
370 bytes.extend_from_slice(&self.root.bounds.min.y.to_le_bytes());
371 bytes.extend_from_slice(&self.root.bounds.min.z.to_le_bytes());
372 bytes.extend_from_slice(&self.root.bounds.max.x.to_le_bytes());
373 bytes.extend_from_slice(&self.root.bounds.max.y.to_le_bytes());
374 bytes.extend_from_slice(&self.root.bounds.max.z.to_le_bytes());
375
376 bytes.extend_from_slice(&nodes.len().to_le_bytes());
378
379 for node in nodes {
381 bytes.extend_from_slice(&node.id.to_le_bytes());
382 bytes.extend_from_slice(&node.x.to_le_bytes());
383 bytes.extend_from_slice(&node.y.to_le_bytes());
384 bytes.extend_from_slice(&node.z.to_le_bytes());
385 }
386
387 Ok(bytes)
388 }
389
390 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Box<dyn std::error::Error>> {
394 use std::convert::TryInto;
395
396 let mut offset = 0;
397
398 let min_x = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
400 offset += 4;
401 let min_y = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
402 offset += 4;
403 let min_z = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
404 offset += 4;
405 let max_x = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
406 offset += 4;
407 let max_y = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
408 offset += 4;
409 let max_z = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
410 offset += 4;
411
412 let bounds = BoundingBox::new(
413 Vec3::new(min_x, min_y, min_z),
414 Vec3::new(max_x, max_y, max_z),
415 );
416
417 let node_count = usize::from_le_bytes(bytes[offset..offset + 8].try_into()?);
419 offset += 8;
420
421 let mut nodes = Vec::with_capacity(node_count);
423 for _ in 0..node_count {
424 let id = u64::from_le_bytes(bytes[offset..offset + 8].try_into()?);
425 offset += 8;
426 let x = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
427 offset += 4;
428 let y = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
429 offset += 4;
430 let z = f32::from_le_bytes(bytes[offset..offset + 4].try_into()?);
431 offset += 4;
432 nodes.push(NodePoint { id, x, y, z });
433 }
434
435 let mut octree = Self::new(bounds);
437 for node in nodes {
438 octree.insert(node);
439 }
440
441 Ok(octree)
442 }
443
444 fn collect_nodes(&self, node: &OctreeNode, output: &mut Vec<NodePoint>) {
446 output.extend(&node.nodes);
448
449 if let Some(ref children) = node.children {
451 for child in children.iter() {
452 self.collect_nodes(child, output);
453 }
454 }
455 }
456
457 pub fn from_nodes(nodes: &[NodePoint]) -> Self {
462 if nodes.is_empty() {
463 return Self::new(BoundingBox::new(Vec3::ZERO, Vec3::ONE));
464 }
465
466 let mut min = Vec3::new(nodes[0].x, nodes[0].y, nodes[0].z);
468 let mut max = min;
469
470 for node in nodes {
471 let pos = Vec3::new(node.x, node.y, node.z);
472 min = min.min(pos);
473 max = max.max(pos);
474 }
475
476 let size = max - min;
478 let padding = size * 0.1;
479 let bounds = BoundingBox::new(min - padding, max + padding);
480
481 let mut octree = Self::new(bounds);
482 for &node in nodes {
483 octree.insert(node);
484 }
485 octree
486 }
487
488 pub fn insert(&mut self, node: NodePoint) -> bool {
490 if self.root.insert(node) {
491 self.node_count += 1;
492 true
493 } else {
494 false
495 }
496 }
497
498 pub fn query_sphere(&self, center: Vec3, radius: f32) -> Vec<NodePoint> {
503 let (results, _stats) = self.query_sphere_with_stats(center, radius);
504 results
505 }
506
507 pub fn query_sphere_with_stats(
509 &self,
510 center: Vec3,
511 radius: f32,
512 ) -> (Vec<NodePoint>, OctreeQueryStats) {
513 let mut results = Vec::new();
514 let mut stats = OctreeQueryStats::default();
515 self.root
516 .query_sphere_with_stats(center, radius, &mut results, &mut stats);
517 (results, stats)
518 }
519
520 pub fn locate_within_distance(
522 &self,
523 center: NodePoint,
524 distance_squared: f32,
525 ) -> impl Iterator<Item = NodePoint> {
526 let center_vec = Vec3::new(center.x, center.y, center.z);
527 let radius = distance_squared.sqrt();
528 self.query_sphere(center_vec, radius).into_iter()
529 }
530
531 pub fn query_aabb(&self, bounds: &BoundingBox) -> Vec<NodePoint> {
535 let center = bounds.center();
538 let size = bounds.size();
539 let radius = size.length() * 0.5; let candidates = self.query_sphere(center, radius);
542 candidates
543 .into_iter()
544 .filter(|node| {
545 let pos = Vec3::new(node.x, node.y, node.z);
546 bounds.contains(pos)
547 })
548 .collect()
549 }
550
551 pub fn query_knn(&self, center: Vec3, k: usize) -> Vec<(NodePoint, f32)> {
563 if k == 0 || self.is_empty() {
564 return Vec::new();
565 }
566
567 let bounds_size = self.root.bounds.size();
569 let max_radius = bounds_size.length();
570
571 let candidates = self.query_sphere(center, max_radius);
573
574 if candidates.is_empty() {
575 return Vec::new();
576 }
577
578 let mut with_distances: Vec<(NodePoint, f32)> = candidates
580 .into_iter()
581 .map(|node| {
582 let node_pos = Vec3::new(node.x, node.y, node.z);
583 let dist_sq = (node_pos - center).length_squared();
584 (node, dist_sq)
585 })
586 .collect();
587
588 with_distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
590
591 with_distances.truncate(k);
593 with_distances
594 }
595
596 pub fn query_knn_with_stats(
600 &self,
601 center: Vec3,
602 k: usize,
603 ) -> (Vec<(NodePoint, f32)>, OctreeQueryStats) {
604 let mut stats = OctreeQueryStats::default();
605
606 if k == 0 || self.is_empty() {
607 return (Vec::new(), stats);
608 }
609
610 let mut radius = 1.0f32;
612 let max_radius = self.root.bounds.size().length();
613 let mut results = Vec::new();
614
615 while radius <= max_radius && results.len() < k {
616 let mut batch = Vec::new();
617 self.root
618 .query_sphere_with_stats(center, radius, &mut batch, &mut stats);
619
620 for node in batch {
622 let node_pos = Vec3::new(node.x, node.y, node.z);
623 let dist_sq = (node_pos - center).length_squared();
624 results.push((node, dist_sq));
625 }
626
627 results.sort_by_key(|result| result.0.id);
629 results.dedup_by(|a, b| a.0.id == b.0.id);
630
631 radius *= 2.0;
632 }
633
634 results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
636 results.truncate(k);
637
638 (results, stats)
639 }
640
641 pub fn bounds(&self) -> &BoundingBox {
643 &self.root.bounds
644 }
645
646 pub fn len(&self) -> usize {
648 self.node_count
649 }
650
651 pub fn is_empty(&self) -> bool {
653 self.node_count == 0
654 }
655
656 pub fn statistics(&self) -> OctreeStats {
658 OctreeStats {
659 node_count: self.node_count,
660 leaf_count: self.root.leaf_count(),
661 max_depth: self.root.max_depth(),
662 }
663 }
664}
665
666#[derive(Debug, Clone, Copy)]
668pub struct OctreeStats {
669 pub node_count: usize,
671
672 pub leaf_count: usize,
674
675 pub max_depth: u32,
677}
678
679#[cfg(test)]
680mod tests {
681 use super::*;
682
683 #[test]
684 fn test_bounding_box_creation() {
685 let min = Vec3::new(-1.0, -2.0, -3.0);
686 let max = Vec3::new(1.0, 2.0, 3.0);
687 let bbox = BoundingBox::new(min, max);
688
689 assert_eq!(bbox.min, min);
690 assert_eq!(bbox.max, max);
691 }
692
693 #[test]
694 fn test_bounding_box_contains() {
695 let bbox = BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
696
697 assert!(bbox.contains(Vec3::ZERO));
699 assert!(bbox.contains(Vec3::new(0.5, 0.5, 0.5)));
700 assert!(bbox.contains(Vec3::new(-1.0, -1.0, -1.0))); assert!(bbox.contains(Vec3::new(1.0, 1.0, 1.0))); assert!(!bbox.contains(Vec3::new(2.0, 0.0, 0.0)));
705 assert!(!bbox.contains(Vec3::new(0.0, 2.0, 0.0)));
706 assert!(!bbox.contains(Vec3::new(0.0, 0.0, 2.0)));
707 }
708
709 #[test]
710 fn test_bounding_box_intersects() {
711 let bbox1 = BoundingBox::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0));
712
713 let bbox2 = BoundingBox::new(Vec3::new(0.5, 0.5, 0.5), Vec3::new(2.0, 2.0, 2.0));
714
715 let bbox3 = BoundingBox::new(Vec3::new(2.0, 2.0, 2.0), Vec3::new(3.0, 3.0, 3.0));
716
717 assert!(bbox1.intersects(&bbox2));
719
720 assert!(!bbox1.intersects(&bbox3));
722 }
723
724 #[test]
725 fn test_bounding_box_center_and_size() {
726 let bbox = BoundingBox::new(Vec3::new(-2.0, -1.0, 0.0), Vec3::new(2.0, 3.0, 4.0));
727
728 assert_eq!(bbox.center(), Vec3::new(0.0, 1.0, 2.0));
729 assert_eq!(bbox.size(), Vec3::new(4.0, 4.0, 4.0));
730 }
731
732 #[test]
733 fn test_bounding_box_subdivide() {
734 let bbox = BoundingBox::new(Vec3::new(-2.0, -2.0, -2.0), Vec3::new(2.0, 2.0, 2.0));
735
736 let octants = bbox.subdivide();
737 assert_eq!(octants.len(), 8);
738
739 for octant in &octants {
741 assert!(octant.min.x >= bbox.min.x);
742 assert!(octant.min.y >= bbox.min.y);
743 assert!(octant.min.z >= bbox.min.z);
744 assert!(octant.max.x <= bbox.max.x);
745 assert!(octant.max.y <= bbox.max.y);
746 assert!(octant.max.z <= bbox.max.z);
747 }
748
749 for i in 0..8 {
751 for j in (i + 1)..8 {
752 if octants[i].intersects(&octants[j]) {
754 let intersection = BoundingBox::new(
755 octants[i].min.max(octants[j].min),
756 octants[i].max.min(octants[j].max),
757 );
758 let size = intersection.size();
760 assert!(size.x == 0.0 || size.y == 0.0 || size.z == 0.0);
761 }
762 }
763 }
764 }
765
766 #[test]
767 fn test_octree_node_creation() {
768 let bounds = BoundingBox::new(Vec3::ZERO, Vec3::ONE);
769 let node = OctreeNode::new(bounds, 0);
770
771 assert_eq!(node.bounds, bounds);
772 assert_eq!(node.depth, 0);
773 assert!(node.is_leaf());
774 assert!(node.nodes.is_empty());
775 assert!(node.children.is_none());
776 }
777
778 #[test]
779 fn test_octree_node_capacity() {
780 let bounds = BoundingBox::new(Vec3::ZERO, Vec3::ONE);
781 let node = OctreeNode::new(bounds, 0);
782
783 assert_eq!(node.capacity(), 8);
785
786 let deep_node = OctreeNode::new(bounds, 5);
788 assert_eq!(deep_node.capacity(), 18); }
790
791 #[test]
792 fn test_octree_creation() {
793 let bounds = BoundingBox::new(Vec3::ZERO, Vec3::ONE);
794 let octree = Octree::new(bounds);
795
796 assert_eq!(octree.len(), 0);
797 assert!(octree.is_empty());
798 }
799
800 #[test]
801 fn test_octree_from_nodes() {
802 let nodes = vec![
803 NodePoint {
804 id: 1,
805 x: 0.0,
806 y: 0.0,
807 z: 0.0,
808 },
809 NodePoint {
810 id: 2,
811 x: 1.0,
812 y: 1.0,
813 z: 1.0,
814 },
815 NodePoint {
816 id: 3,
817 x: -1.0,
818 y: -1.0,
819 z: -1.0,
820 },
821 ];
822
823 let octree = Octree::from_nodes(&nodes);
824
825 assert_eq!(octree.len(), 3);
826 assert!(!octree.is_empty());
827 }
828
829 #[test]
830 fn test_octree_insert_and_query() {
831 let mut octree = Octree::new(BoundingBox::new(
832 Vec3::new(-10.0, -10.0, -10.0),
833 Vec3::new(10.0, 10.0, 10.0),
834 ));
835
836 let node1 = NodePoint {
838 id: 1,
839 x: 1.0,
840 y: 1.0,
841 z: 1.0,
842 };
843 let node2 = NodePoint {
844 id: 2,
845 x: -2.0,
846 y: -2.0,
847 z: -2.0,
848 };
849 let node3 = NodePoint {
850 id: 3,
851 x: 5.0,
852 y: 5.0,
853 z: 5.0,
854 };
855
856 assert!(octree.insert(node1));
857 assert!(octree.insert(node2));
858 assert!(octree.insert(node3));
859
860 assert_eq!(octree.len(), 3);
861
862 let results = octree.query_sphere(Vec3::ZERO, 3.0);
864 println!("Results in radius 3.0: {:?}", results);
865 assert_eq!(results.len(), 1); let results = octree.query_sphere(Vec3::ZERO, 10.0);
872 println!("Results in radius 10.0: {:?}", results);
873 assert_eq!(results.len(), 3); }
875
876 #[test]
877 fn test_octree_out_of_bounds() {
878 let mut octree = Octree::new(BoundingBox::new(
879 Vec3::new(-10.0, -10.0, -10.0),
880 Vec3::new(10.0, 10.0, 10.0),
881 ));
882
883 let out_of_bounds_node = NodePoint {
885 id: 1,
886 x: 15.0,
887 y: 15.0,
888 z: 15.0,
889 };
890 assert!(!octree.insert(out_of_bounds_node));
891
892 assert_eq!(octree.len(), 0);
894
895 let in_bounds_node = NodePoint {
897 id: 2,
898 x: 5.0,
899 y: 5.0,
900 z: 5.0,
901 };
902 assert!(octree.insert(in_bounds_node));
903
904 assert_eq!(octree.len(), 1);
906 }
907
908 #[test]
909 fn test_octree_query_stats_collects_lookup_counts() {
910 let mut octree = Octree::new(BoundingBox::new(
911 Vec3::new(-100.0, -100.0, -100.0),
912 Vec3::new(100.0, 100.0, 100.0),
913 ));
914
915 for i in 0..32 {
916 let x = (i as f32 - 16.0) * 3.0;
917 assert!(octree.insert(NodePoint {
918 id: i as u64,
919 x,
920 y: 0.0,
921 z: 0.0,
922 }));
923 }
924
925 let (results, stats) = octree.query_sphere_with_stats(Vec3::ZERO, 12.0);
926
927 assert!(!results.is_empty());
928 assert!(stats.nodes_visited > 0);
929 assert!(stats.leaf_nodes_scanned > 0);
930 assert!(stats.points_tested >= results.len());
931 assert_eq!(stats.points_returned, results.len());
932 }
933
934 #[test]
935 fn test_octree_lookup_growth_is_sublinear_for_8x_more_points() {
936 fn populate_axis_grid(octree: &mut Octree, axis: usize) {
937 let mut id = 0u64;
938 let span = 400.0f32;
939 let step = span / axis as f32;
940 let offset = -span * 0.5;
941 for xi in 0..axis {
942 for yi in 0..axis {
943 for zi in 0..axis {
944 let x = offset + xi as f32 * step;
945 let y = offset + yi as f32 * step;
946 let z = offset + zi as f32 * step;
947 let inserted = octree.insert(NodePoint { id, x, y, z });
948 assert!(inserted);
949 id += 1;
950 }
951 }
952 }
953 }
954
955 let bounds = BoundingBox::new(
956 Vec3::new(-256.0, -256.0, -256.0),
957 Vec3::new(256.0, 256.0, 256.0),
958 );
959
960 let mut small = Octree::new(bounds);
961 populate_axis_grid(&mut small, 8); let (_, small_stats) = small.query_sphere_with_stats(Vec3::new(0.0, 0.0, 0.0), 20.0);
963
964 let mut large = Octree::new(bounds);
965 populate_axis_grid(&mut large, 16); let (_, large_stats) = large.query_sphere_with_stats(Vec3::new(0.0, 0.0, 0.0), 20.0);
967
968 assert!(small_stats.nodes_visited > 0);
969 assert!(large_stats.nodes_visited > 0);
970 assert!(
971 large_stats.nodes_visited <= small_stats.nodes_visited * 5,
972 "lookup growth too steep: small={} large={}",
973 small_stats.nodes_visited,
974 large_stats.nodes_visited
975 );
976 }
977
978 #[test]
983 fn test_octree_serialization_basic() {
984 let mut octree = Octree::new(BoundingBox::new(
986 Vec3::new(-100.0, -100.0, -100.0),
987 Vec3::new(100.0, 100.0, 100.0),
988 ));
989
990 let nodes = vec![
991 NodePoint {
992 id: 1,
993 x: 10.0,
994 y: 20.0,
995 z: 30.0,
996 },
997 NodePoint {
998 id: 2,
999 x: -15.0,
1000 y: 25.0,
1001 z: -35.0,
1002 },
1003 NodePoint {
1004 id: 3,
1005 x: 50.0,
1006 y: 50.0,
1007 z: 50.0,
1008 },
1009 ];
1010
1011 for node in &nodes {
1012 assert!(octree.insert(*node), "Should insert node {}", node.id);
1013 }
1014
1015 assert_eq!(octree.len(), 3, "Should have 3 nodes");
1016
1017 let serialized = octree.to_bytes().expect("Should serialize octree");
1019 assert!(
1020 !serialized.is_empty(),
1021 "Serialized data should not be empty"
1022 );
1023
1024 let restored = Octree::from_bytes(&serialized).expect("Should deserialize octree");
1026
1027 assert_eq!(restored.len(), 3, "Restored octree should have 3 nodes");
1029
1030 let query_results = restored.query_sphere(Vec3::new(10.0, 20.0, 30.0), 5.0);
1032 assert!(
1033 query_results.iter().any(|n| n.id == 1),
1034 "Restored octree should find node 1"
1035 );
1036 }
1037
1038 #[test]
1042 fn test_octree_persistence_roundtrip() {
1043 let mut octree = Octree::new(BoundingBox::new(
1045 Vec3::new(-500.0, -500.0, -500.0),
1046 Vec3::new(500.0, 500.0, 500.0),
1047 ));
1048
1049 for i in 0..100 {
1051 let x = ((i % 10) as f32 - 5.0) * 50.0;
1052 let y = ((i / 10) as f32 - 5.0) * 50.0;
1053 let z = 0.0;
1054 octree.insert(NodePoint {
1055 id: i as u64,
1056 x,
1057 y,
1058 z,
1059 });
1060 }
1061
1062 assert_eq!(octree.len(), 100, "Should have 100 nodes");
1063
1064 let original_results = octree.query_sphere(Vec3::ZERO, 100.0);
1066 let original_count = original_results.len();
1067
1068 let bytes = octree.to_bytes().expect("Should serialize");
1070 let restored = Octree::from_bytes(&bytes).expect("Should deserialize");
1071
1072 assert_eq!(restored.len(), 100, "Restored octree should have 100 nodes");
1073
1074 let restored_results = restored.query_sphere(Vec3::ZERO, 100.0);
1076 assert_eq!(
1077 restored_results.len(),
1078 original_count,
1079 "Queries should return same count after roundtrip"
1080 );
1081 }
1082
1083 #[test]
1087 fn test_octree_cfg_store_integration() {
1088 use crate::cfg_store::{CfgBlock, CfgStore};
1089 use tempfile::tempdir;
1090
1091 let temp_dir = tempdir().unwrap();
1092 let db_path = temp_dir.path().join("test_octree_integration.db");
1093
1094 let mut store = CfgStore::create(&db_path).unwrap();
1096
1097 let blocks = vec![
1098 CfgBlock {
1099 id: 1,
1100 function_id: 42,
1101 block_kind: "entry".to_string(),
1102 terminator: "fallthrough".to_string(),
1103 byte_start: 0,
1104 byte_end: 50,
1105 start_line: 1,
1106 start_col: 0,
1107 end_line: 5,
1108 end_col: 10,
1109 dominator_depth: 0,
1110 loop_nesting: 0,
1111 branch_count: 0,
1112 },
1113 CfgBlock {
1114 id: 2,
1115 function_id: 42,
1116 block_kind: "if".to_string(),
1117 terminator: "conditional".to_string(),
1118 byte_start: 50,
1119 byte_end: 100,
1120 start_line: 6,
1121 start_col: 4,
1122 end_line: 10,
1123 end_col: 15,
1124 dominator_depth: 1,
1125 loop_nesting: 0,
1126 branch_count: 1,
1127 },
1128 ];
1129
1130 for block in &blocks {
1131 store.insert_block(block.clone()).unwrap();
1132 }
1133
1134 let nearby_before = store.query_nearby(Vec3::new(0.0, 0.0, 42.0), 5.0);
1136 assert!(
1137 !nearby_before.is_empty(),
1138 "Should find blocks before persistence"
1139 );
1140
1141 let octree_bytes = store.octree.to_bytes().expect("Should serialize octree");
1143
1144 let restored_octree = Octree::from_bytes(&octree_bytes).expect("Should deserialize octree");
1146
1147 let nearby_after = restored_octree.query_sphere(Vec3::new(0.0, 0.0, 42.0), 5.0);
1149 assert!(!nearby_after.is_empty(), "Should find blocks after restore");
1150 }
1151
1152 #[test]
1156 #[cfg(feature = "telemetry")]
1157 fn test_octree_telemetry_loop_guard() {
1158 use crate::telemetry::LoopGuard;
1159
1160 let guard = LoopGuard::new("octree_traversal", 100);
1162
1163 for i in 0..150 {
1165 if i < 100 {
1166 assert!(guard.check().is_ok(), "Should allow iteration {}", i);
1167 } else {
1168 assert!(guard.check().is_err(), "Should fail at iteration {}", i);
1169 break;
1170 }
1171 }
1172 }
1173
1174 #[test]
1176 fn test_knn_basic() {
1177 let mut octree = Octree::new(BoundingBox::new(
1178 Vec3::new(-100.0, -100.0, -100.0),
1179 Vec3::new(100.0, 100.0, 100.0),
1180 ));
1181
1182 let nodes = vec![
1184 NodePoint {
1185 id: 1,
1186 x: 0.0,
1187 y: 0.0,
1188 z: 0.0,
1189 }, NodePoint {
1191 id: 2,
1192 x: 1.0,
1193 y: 0.0,
1194 z: 0.0,
1195 }, NodePoint {
1197 id: 3,
1198 x: 3.0,
1199 y: 0.0,
1200 z: 0.0,
1201 }, NodePoint {
1203 id: 4,
1204 x: 10.0,
1205 y: 0.0,
1206 z: 0.0,
1207 }, ];
1209
1210 for node in &nodes {
1211 octree.insert(*node);
1212 }
1213
1214 let results = octree.query_knn(Vec3::new(0.0, 0.0, 0.0), 2);
1216
1217 assert_eq!(results.len(), 2);
1218 assert_eq!(results[0].0.id, 1); assert_eq!(results[1].0.id, 2); assert!((results[0].1 - 0.0).abs() < 0.001);
1221 assert!((results[1].1 - 1.0).abs() < 0.001);
1222 }
1223
1224 #[test]
1225 fn test_knn_empty_octree() {
1226 let octree = Octree::new(BoundingBox::new(
1227 Vec3::new(-10.0, -10.0, -10.0),
1228 Vec3::new(10.0, 10.0, 10.0),
1229 ));
1230
1231 let results = octree.query_knn(Vec3::ZERO, 5);
1232 assert!(results.is_empty());
1233 }
1234
1235 #[test]
1236 fn test_knn_k_larger_than_count() {
1237 let mut octree = Octree::new(BoundingBox::new(
1238 Vec3::new(-100.0, -100.0, -100.0),
1239 Vec3::new(100.0, 100.0, 100.0),
1240 ));
1241
1242 for i in 0..3 {
1244 octree.insert(NodePoint {
1245 id: i as u64,
1246 x: i as f32 * 10.0,
1247 y: 0.0,
1248 z: 0.0,
1249 });
1250 }
1251
1252 let results = octree.query_knn(Vec3::ZERO, 10);
1254 assert_eq!(results.len(), 3); }
1256
1257 #[test]
1258 fn test_knn_k_zero() {
1259 let mut octree = Octree::new(BoundingBox::new(
1260 Vec3::new(-10.0, -10.0, -10.0),
1261 Vec3::new(10.0, 10.0, 10.0),
1262 ));
1263 octree.insert(NodePoint {
1264 id: 1,
1265 x: 0.0,
1266 y: 0.0,
1267 z: 0.0,
1268 });
1269
1270 let results = octree.query_knn(Vec3::ZERO, 0);
1271 assert!(results.is_empty());
1272 }
1273
1274 #[test]
1275 fn test_knn_sorted_by_distance() {
1276 let mut octree = Octree::new(BoundingBox::new(
1277 Vec3::new(-100.0, -100.0, -100.0),
1278 Vec3::new(100.0, 100.0, 100.0),
1279 ));
1280
1281 let positions = [
1283 (5.0, 0.0, 0.0), (1.0, 0.0, 0.0), (10.0, 0.0, 0.0), (2.0, 0.0, 0.0), ];
1288
1289 for (i, (x, y, z)) in positions.iter().enumerate() {
1290 octree.insert(NodePoint {
1291 id: i as u64,
1292 x: *x,
1293 y: *y,
1294 z: *z,
1295 });
1296 }
1297
1298 let results = octree.query_knn(Vec3::ZERO, 4);
1299
1300 for i in 1..results.len() {
1302 assert!(
1303 results[i].1 >= results[i - 1].1,
1304 "Results should be sorted by distance"
1305 );
1306 }
1307
1308 assert_eq!(results[0].0.id, 1); assert_eq!(results[1].0.id, 3); assert_eq!(results[2].0.id, 0); assert_eq!(results[3].0.id, 2); }
1314
1315 #[test]
1316 fn test_knn_3d_positions() {
1317 let mut octree = Octree::new(BoundingBox::new(
1318 Vec3::new(-100.0, -100.0, -100.0),
1319 Vec3::new(100.0, 100.0, 100.0),
1320 ));
1321
1322 let nodes = vec![
1324 NodePoint {
1325 id: 1,
1326 x: 1.0,
1327 y: 1.0,
1328 z: 1.0,
1329 }, NodePoint {
1331 id: 2,
1332 x: 2.0,
1333 y: 2.0,
1334 z: 2.0,
1335 }, NodePoint {
1337 id: 3,
1338 x: 0.0,
1339 y: 0.0,
1340 z: 0.0,
1341 }, ];
1343
1344 for node in &nodes {
1345 octree.insert(*node);
1346 }
1347
1348 let results = octree.query_knn(Vec3::new(0.0, 0.0, 0.0), 3);
1349
1350 assert_eq!(results.len(), 3);
1351 assert_eq!(results[0].0.id, 3); assert_eq!(results[1].0.id, 1); assert_eq!(results[2].0.id, 2); assert!((results[0].1 - 0.0).abs() < 0.001);
1357 assert!((results[1].1 - 3.0).abs() < 0.001); assert!((results[2].1 - 12.0).abs() < 0.001); }
1360
1361 #[test]
1362 fn test_knn_with_stats() {
1363 let mut octree = Octree::new(BoundingBox::new(
1364 Vec3::new(-100.0, -100.0, -100.0),
1365 Vec3::new(100.0, 100.0, 100.0),
1366 ));
1367
1368 for i in 0..100 {
1369 octree.insert(NodePoint {
1370 id: i as u64,
1371 x: (i as f32 - 50.0) * 2.0,
1372 y: 0.0,
1373 z: 0.0,
1374 });
1375 }
1376
1377 let (results, stats) = octree.query_knn_with_stats(Vec3::ZERO, 10);
1378
1379 assert_eq!(results.len(), 10);
1380 assert!(stats.nodes_visited > 0, "Should track nodes visited");
1381 assert!(stats.points_tested > 0, "Should track points tested");
1382 }
1383}