Skip to main content

geographdb_core/spatial/
octree.rs

1//! Octree spatial indexing for 3D-native graph database.
2//!
3//! This module provides an octree implementation optimized for 3D spatial
4//! queries in graph databases. Unlike traditional R-trees, octrees provide
5//! optimal spatial partitioning for uniform 3D data distributions and
6//! enable efficient pruning of graph traversal algorithms.
7
8use crate::storage::data_structures::NodePoint;
9use glam::Vec3;
10use serde::{Deserialize, Serialize};
11
12/// Per-query instrumentation for octree lookup complexity analysis.
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
14pub struct OctreeQueryStats {
15    /// Number of octree nodes visited during traversal.
16    pub nodes_visited: usize,
17    /// Number of leaf octants scanned.
18    pub leaf_nodes_scanned: usize,
19    /// Number of points distance-tested against the query sphere.
20    pub points_tested: usize,
21    /// Number of points that matched the query predicate.
22    pub points_returned: usize,
23}
24
25/// An octant in 3D space, represented by its bounding box.
26#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct BoundingBox {
28    /// Minimum corner of the bounding box.
29    pub min: Vec3,
30    /// Maximum corner of the bounding box.
31    pub max: Vec3,
32}
33
34impl BoundingBox {
35    /// Create a new bounding box from minimum and maximum corners.
36    pub fn new(min: Vec3, max: Vec3) -> Self {
37        Self { min, max }
38    }
39
40    /// Create a new bounding box centered at origin with given half-size.
41    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    /// Check if a point is contained within this bounding box.
49    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    /// Check if this bounding box intersects with another bounding box.
59    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    /// Calculate the center of this bounding box.
69    pub fn center(&self) -> Vec3 {
70        (self.min + self.max) * 0.5
71    }
72
73    /// Calculate the size (dimensions) of this bounding box.
74    pub fn size(&self) -> Vec3 {
75        self.max - self.min
76    }
77
78    /// Subdivide this bounding box into 8 octants.
79    pub fn subdivide(&self) -> [BoundingBox; 8] {
80        let center = self.center();
81        // Calculate half-size of this bounding box
82        let _half_size = (self.max - self.min) * 0.5;
83
84        [
85            // Bottom octants (z < center.z)
86            BoundingBox::new(self.min, center), // Bottom-front-left
87            BoundingBox::new(
88                Vec3::new(center.x, self.min.y, self.min.z),
89                Vec3::new(self.max.x, center.y, center.z),
90            ), // Bottom-front-right
91            BoundingBox::new(
92                Vec3::new(self.min.x, center.y, self.min.z),
93                Vec3::new(center.x, self.max.y, center.z),
94            ), // Bottom-back-left
95            BoundingBox::new(
96                Vec3::new(center.x, center.y, self.min.z),
97                Vec3::new(self.max.x, self.max.y, center.z),
98            ), // Bottom-back-right
99            // Top octants (z >= center.z)
100            BoundingBox::new(
101                Vec3::new(self.min.x, self.min.y, center.z),
102                Vec3::new(center.x, center.y, self.max.z),
103            ), // Top-front-left
104            BoundingBox::new(
105                Vec3::new(center.x, self.min.y, center.z),
106                Vec3::new(self.max.x, center.y, self.max.z),
107            ), // Top-front-right
108            BoundingBox::new(
109                Vec3::new(self.min.x, center.y, center.z),
110                Vec3::new(center.x, self.max.y, self.max.z),
111            ), // Top-back-left
112            BoundingBox::new(center, self.max), // Top-back-right
113        ]
114    }
115}
116
117/// A node in the octree structure.
118#[derive(Debug, Clone)]
119pub struct OctreeNode {
120    /// The bounding box that defines this node's spatial extent.
121    pub bounds: BoundingBox,
122
123    /// The depth of this node in the octree (0 for root).
124    pub depth: u32,
125
126    /// The nodes contained directly in this octant (if this is a leaf).
127    pub nodes: Vec<NodePoint>,
128
129    /// Child octants (if this node has been subdivided).
130    pub children: Option<[Box<OctreeNode>; 8]>,
131
132    /// Flag indicating if this node has been subdivided.
133    pub is_subdivided: bool,
134}
135
136impl OctreeNode {
137    /// Create a new octree node with the given bounds and depth.
138    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    /// Check if this node is a leaf (has no children).
149    pub fn is_leaf(&self) -> bool {
150        !self.is_subdivided
151    }
152
153    /// Get the maximum number of nodes this leaf can hold before subdivision.
154    pub fn capacity(&self) -> usize {
155        // Capacity increases with depth to prevent excessive subdivision
156        // at deeper levels where nodes are more sparse
157        (8 + self.depth as usize * 2).min(32)
158    }
159
160    /// Insert a node into this octree node.
161    ///
162    /// If this node is a leaf and has capacity, the node is added directly.
163    /// If this node is a leaf and is at capacity, it will be subdivided and
164    /// the nodes redistributed.
165    /// If this node has children, the node is inserted into the appropriate child.
166    pub fn insert(&mut self, node: NodePoint) -> bool {
167        // Check if the node is within this octant's bounds
168        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 we have capacity, add the node directly
175            if self.nodes.len() < self.capacity() || self.depth >= 16 {
176                self.nodes.push(node);
177                return true;
178            }
179
180            // Otherwise, subdivide and redistribute existing nodes
181            self.subdivide();
182
183            // Redistribute existing nodes to children
184            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        // Insert the new node into the appropriate child
197        self.insert_into_children(node)
198    }
199
200    /// Subdivide this node into 8 children.
201    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    /// Insert a node into the appropriate child octant.
221    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    /// Find all nodes within a spherical query region.
234    ///
235    /// This efficiently prunes octants that don't intersect with the query sphere.
236    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    /// Find all nodes within a spherical query region while collecting traversal stats.
242    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        // Quick rejection: if this octant doesn't intersect the query sphere, skip it
252        if !self.intersects_sphere(center, radius) {
253            return;
254        }
255
256        // If this is a leaf, check all nodes directly
257        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            // Recursively check children
270            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    /// Check if this octant intersects with a spherical query region.
279    fn intersects_sphere(&self, center: Vec3, radius: f32) -> bool {
280        // Find the closest point in the bounding box to the sphere center
281        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        // Check if the closest point is within the sphere
287        let distance_sq = (closest_point - center).length_squared();
288        distance_sq <= radius * radius
289    }
290
291    /// Get the total number of nodes in this subtree.
292    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    /// Get the total number of leaf nodes in this subtree.
303    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    /// Calculate the maximum depth of this subtree.
318    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/// An octree spatial index for 3D point data.
334///
335/// Octrees provide efficient spatial partitioning by recursively subdividing
336/// 3D space into 8 octants. This makes them particularly well-suited for
337/// uniform 3D data distributions and enables massive pruning during spatial queries.
338#[derive(Debug, Clone)]
339pub struct Octree {
340    /// The root node of the octree.
341    root: OctreeNode,
342
343    /// The total number of nodes in the octree.
344    node_count: usize,
345}
346
347impl Octree {
348    /// Create a new octree with the given bounds.
349    pub fn new(bounds: BoundingBox) -> Self {
350        Self {
351            root: OctreeNode::new(bounds, 0),
352            node_count: 0,
353        }
354    }
355
356    /// Serialize the octree to bytes.
357    ///
358    /// This enables persistence by converting the octree structure to a byte array
359    /// that can be saved to disk and later restored.
360    pub fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
361        // Collect all nodes from the octree
362        let mut nodes = Vec::with_capacity(self.node_count);
363        self.collect_nodes(&self.root, &mut nodes);
364
365        // Simple binary format: [bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z, node_count, nodes...]
366        let mut bytes = Vec::new();
367
368        // Serialize bounds
369        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        // Serialize node count
377        bytes.extend_from_slice(&nodes.len().to_le_bytes());
378
379        // Serialize each node
380        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    /// Deserialize an octree from bytes.
391    ///
392    /// Restores an octree that was previously serialized with `to_bytes`.
393    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        // Deserialize bounds
399        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        // Deserialize node count
418        let node_count = usize::from_le_bytes(bytes[offset..offset + 8].try_into()?);
419        offset += 8;
420
421        // Deserialize nodes
422        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        // Rebuild the octree
436        let mut octree = Self::new(bounds);
437        for node in nodes {
438            octree.insert(node);
439        }
440
441        Ok(octree)
442    }
443
444    /// Recursively collect all nodes from the octree.
445    fn collect_nodes(&self, node: &OctreeNode, output: &mut Vec<NodePoint>) {
446        // Add nodes from this octant
447        output.extend(&node.nodes);
448
449        // Recursively collect from children
450        if let Some(ref children) = node.children {
451            for child in children.iter() {
452                self.collect_nodes(child, output);
453            }
454        }
455    }
456
457    /// Create a new octree that encompasses all the given nodes.
458    ///
459    /// This automatically calculates appropriate bounds to contain all nodes
460    /// with some padding.
461    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        // Calculate bounds that encompass all nodes with padding
467        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        // Add 10% padding
477        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    /// Insert a node into the octree.
489    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    /// Find all nodes within a spherical query region.
499    ///
500    /// This efficiently prunes octants that don't intersect with the query sphere,
501    /// providing massive performance improvements over brute-force scanning.
502    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    /// Query nodes and return both results and traversal metrics.
508    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    /// Find all nodes within a distance from a given point.
521    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    /// Find all nodes within an axis-aligned bounding box.
532    ///
533    /// This is useful for rectangular region queries.
534    pub fn query_aabb(&self, bounds: &BoundingBox) -> Vec<NodePoint> {
535        // For simplicity, we'll use the sphere query implementation and then filter
536        // In a production implementation, we'd have a dedicated AABB query method
537        let center = bounds.center();
538        let size = bounds.size();
539        let radius = size.length() * 0.5; // Conservative estimate
540
541        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    /// Find the k nearest neighbors to a query point.
552    ///
553    /// Returns up to k nearest nodes sorted by ascending distance.
554    /// If fewer than k nodes exist in the octree, returns all nodes.
555    ///
556    /// # Arguments
557    /// * `center` - The query center point
558    /// * `k` - Maximum number of neighbors to return
559    ///
560    /// # Returns
561    /// Vec of (NodePoint, distance_squared) sorted by distance
562    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        // Start with a conservative radius estimate based on octree bounds
568        let bounds_size = self.root.bounds.size();
569        let max_radius = bounds_size.length();
570
571        // Initial query with large radius to get candidates
572        let candidates = self.query_sphere(center, max_radius);
573
574        if candidates.is_empty() {
575            return Vec::new();
576        }
577
578        // Compute distances and sort
579        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        // Sort by distance
589        with_distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
590
591        // Truncate to k results
592        with_distances.truncate(k);
593        with_distances
594    }
595
596    /// Find the k nearest neighbors with stats.
597    ///
598    /// Returns the k nearest nodes along with query statistics.
599    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        // Use expanding search: start with small radius, grow until we have k results
611        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            // Compute distances for new candidates
621            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            // Remove duplicates (a node might be found in multiple radius expansions)
628            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        // Final sort by distance and truncate
635        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    /// Get the bounding box of the entire octree.
642    pub fn bounds(&self) -> &BoundingBox {
643        &self.root.bounds
644    }
645
646    /// Get the total number of nodes in the octree.
647    pub fn len(&self) -> usize {
648        self.node_count
649    }
650
651    /// Check if the octree is empty.
652    pub fn is_empty(&self) -> bool {
653        self.node_count == 0
654    }
655
656    /// Get statistics about the octree structure.
657    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/// Statistics about an octree's structure.
667#[derive(Debug, Clone, Copy)]
668pub struct OctreeStats {
669    /// Total number of nodes in the octree.
670    pub node_count: usize,
671
672    /// Total number of leaf nodes in the octree.
673    pub leaf_count: usize,
674
675    /// Maximum depth of any node in the octree.
676    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        // Points inside
698        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))); // On boundary
701        assert!(bbox.contains(Vec3::new(1.0, 1.0, 1.0))); // On boundary
702
703        // Points outside
704        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        // Intersecting boxes
718        assert!(bbox1.intersects(&bbox2));
719
720        // Non-intersecting boxes
721        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        // Check that all octants are within the original bounds
740        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        // Check that octants don't overlap (except at boundaries)
750        for i in 0..8 {
751            for j in (i + 1)..8 {
752                // They should only intersect at boundaries
753                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                    // Intersection should have zero volume (be a point, line, or plane)
759                    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        // Root node should have capacity of 8
784        assert_eq!(node.capacity(), 8);
785
786        // Deeper nodes should have higher capacity
787        let deep_node = OctreeNode::new(bounds, 5);
788        assert_eq!(deep_node.capacity(), 18); // 8 + 5*2 = 18
789    }
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        // Insert some nodes
837        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        // Query nodes in a sphere around the origin
863        let results = octree.query_sphere(Vec3::ZERO, 3.0);
864        println!("Results in radius 3.0: {:?}", results);
865        // node1 (1,1,1) distance = sqrt(3) ≈ 1.73 < 3.0 ✓
866        // node2 (-2,-2,-2) distance = sqrt(12) ≈ 3.46 > 3.0 ✗
867        // For now, let's just check that we get the right count
868        assert_eq!(results.len(), 1); // Only node1 should be within radius 3.0
869
870        // Query nodes in a larger sphere
871        let results = octree.query_sphere(Vec3::ZERO, 10.0);
872        println!("Results in radius 10.0: {:?}", results);
873        assert_eq!(results.len(), 3); // all nodes should be within radius 10
874    }
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        // Try to insert a node outside bounds
884        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        // Verify octree is still empty
893        assert_eq!(octree.len(), 0);
894
895        // Insert a node within bounds
896        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        // Verify octree now has one node
905        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); // 512 points
962        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); // 4096 points (8x)
966        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    /// Phase D Layer 1 Test: Octree serialization/deserialization
979    ///
980    /// Tests that an octree can be saved to bytes and restored.
981    /// This is the foundation for persistent spatial indexing.
982    #[test]
983    fn test_octree_serialization_basic() {
984        // Create an octree with some nodes
985        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        // Serialize the octree
1018        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        // Deserialize the octree
1025        let restored = Octree::from_bytes(&serialized).expect("Should deserialize octree");
1026
1027        // Verify restored octree has same nodes
1028        assert_eq!(restored.len(), 3, "Restored octree should have 3 nodes");
1029
1030        // Verify queries work on restored octree
1031        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    /// Phase D Layer 2 Test: Octree persistence roundtrip
1039    ///
1040    /// Tests that an octree survives a save/load cycle with complex structure.
1041    #[test]
1042    fn test_octree_persistence_roundtrip() {
1043        // Create octree with enough nodes to trigger subdivision
1044        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        // Insert nodes across different octants to create tree structure
1050        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        // Query before serialization
1065        let original_results = octree.query_sphere(Vec3::ZERO, 100.0);
1066        let original_count = original_results.len();
1067
1068        // Serialize and deserialize
1069        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        // Query after serialization - should get same results
1075        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    /// Phase D Layer 3 Test: Octree persistence with CfgStore integration
1084    ///
1085    /// Tests that octree can be saved and loaded within a CfgStore context.
1086    #[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        // Create CfgStore and insert blocks
1095        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        // Query before persistence
1135        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        // Serialize octree
1142        let octree_bytes = store.octree.to_bytes().expect("Should serialize octree");
1143
1144        // Restore octree
1145        let restored_octree = Octree::from_bytes(&octree_bytes).expect("Should deserialize octree");
1146
1147        // Verify restored octree works
1148        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    /// Phase D Layer 4 Test: Telemetry for octree operations
1153    ///
1154    /// Verifies loop guards prevent excessive recursion in octree operations.
1155    #[test]
1156    #[cfg(feature = "telemetry")]
1157    fn test_octree_telemetry_loop_guard() {
1158        use crate::telemetry::LoopGuard;
1159
1160        // Test that loop guard catches excessive iterations
1161        let guard = LoopGuard::new("octree_traversal", 100);
1162
1163        // Simulate octree traversal
1164        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    // KNN Query Tests - Ported from geographdb_prototype
1175    #[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        // Insert nodes at known positions
1183        let nodes = vec![
1184            NodePoint {
1185                id: 1,
1186                x: 0.0,
1187                y: 0.0,
1188                z: 0.0,
1189            }, // Distance 0
1190            NodePoint {
1191                id: 2,
1192                x: 1.0,
1193                y: 0.0,
1194                z: 0.0,
1195            }, // Distance 1
1196            NodePoint {
1197                id: 3,
1198                x: 3.0,
1199                y: 0.0,
1200                z: 0.0,
1201            }, // Distance 9
1202            NodePoint {
1203                id: 4,
1204                x: 10.0,
1205                y: 0.0,
1206                z: 0.0,
1207            }, // Distance 100
1208        ];
1209
1210        for node in &nodes {
1211            octree.insert(*node);
1212        }
1213
1214        // Query for 2 nearest neighbors
1215        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); // Nearest (distance 0)
1219        assert_eq!(results[1].0.id, 2); // Second nearest (distance 1)
1220        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        // Insert only 3 nodes
1243        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        // Ask for 10 neighbors
1253        let results = octree.query_knn(Vec3::ZERO, 10);
1254        assert_eq!(results.len(), 3); // Returns all available
1255    }
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        // Insert nodes at random distances
1282        let positions = [
1283            (5.0, 0.0, 0.0),  // dist 25
1284            (1.0, 0.0, 0.0),  // dist 1
1285            (10.0, 0.0, 0.0), // dist 100
1286            (2.0, 0.0, 0.0),  // dist 4
1287        ];
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        // Verify sorted by ascending distance
1301        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        // Verify correct order
1309        assert_eq!(results[0].0.id, 1); // dist 1
1310        assert_eq!(results[1].0.id, 3); // dist 4
1311        assert_eq!(results[2].0.id, 0); // dist 25
1312        assert_eq!(results[3].0.id, 2); // dist 100
1313    }
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        // Nodes in 3D space
1323        let nodes = vec![
1324            NodePoint {
1325                id: 1,
1326                x: 1.0,
1327                y: 1.0,
1328                z: 1.0,
1329            }, // dist 3
1330            NodePoint {
1331                id: 2,
1332                x: 2.0,
1333                y: 2.0,
1334                z: 2.0,
1335            }, // dist 12
1336            NodePoint {
1337                id: 3,
1338                x: 0.0,
1339                y: 0.0,
1340                z: 0.0,
1341            }, // dist 0
1342        ];
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); // Origin
1352        assert_eq!(results[1].0.id, 1); // (1,1,1)
1353        assert_eq!(results[2].0.id, 2); // (2,2,2)
1354
1355        // Verify distances
1356        assert!((results[0].1 - 0.0).abs() < 0.001);
1357        assert!((results[1].1 - 3.0).abs() < 0.001); // 1^2 + 1^2 + 1^2 = 3
1358        assert!((results[2].1 - 12.0).abs() < 0.001); // 2^2 + 2^2 + 2^2 = 12
1359    }
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}