Skip to main content

threecrate_algorithms/
nearest_neighbor.rs

1//! Nearest neighbor search implementations
2
3use std::cmp::Ordering;
4use threecrate_core::{NearestNeighborSearch, Point3f, Result};
5
6/// Sentinel used in place of a child index to mean "no child".
7const NIL: u32 = u32::MAX;
8
9/// KD-Tree node stored in a flat, contiguous array.
10///
11/// Children are referenced by index into the owning `Vec<KdNode>` rather than
12/// through `Box` pointers. Keeping every node in one allocation makes traversal
13/// cache-friendly — neighbour search is the dominant cost in normal estimation
14/// and ICP correspondence, so a contiguous layout directly moves those numbers.
15#[derive(Debug, Clone)]
16struct KdNode {
17    point: Point3f,
18    original_index: usize, // index into the original input slice
19    left: u32,             // child index, or NIL
20    right: u32,            // child index, or NIL
21    axis: u8,              // splitting axis: 0=x, 1=y, 2=z
22}
23
24/// Read-only view of one kd-tree node, for code that walks the tree itself
25/// (e.g. the GPU search in `threecrate-gpu`). See [`KdTree::flat_nodes`].
26#[derive(Debug, Clone, Copy)]
27pub struct FlatKdNode {
28    /// The point stored at this node.
29    pub point: Point3f,
30    /// Index of that point in the slice the tree was built from.
31    pub index: usize,
32    /// Position of the left child in [`KdTree::flat_nodes`], or [`KdTree::NO_CHILD`].
33    pub left: u32,
34    /// Position of the right child in [`KdTree::flat_nodes`], or [`KdTree::NO_CHILD`].
35    pub right: u32,
36    /// Splitting axis: 0 = x, 1 = y, 2 = z.
37    pub axis: u8,
38}
39
40/// Efficient KD-Tree implementation for nearest neighbor search.
41///
42/// Nodes live in a single contiguous `Vec` (`nodes`); children are referenced by
43/// index. `root` is the index of the tree root (always `0` when non-empty).
44pub struct KdTree {
45    nodes: Vec<KdNode>,
46    root: Option<u32>,
47    points: Vec<Point3f>, // Keep original points for reference
48}
49
50impl KdTree {
51    /// Child value in [`FlatKdNode`] meaning "no child".
52    pub const NO_CHILD: u32 = NIL;
53
54    /// The tree's nodes in their stored order. The root is the first node (when
55    /// the tree is not empty), and children are referenced by position in this
56    /// sequence. A query walks it like any binary kd-tree: go to the child on
57    /// the query's side of the split, and visit the other child only if the
58    /// split plane is closer than the current best match.
59    pub fn flat_nodes(&self) -> impl ExactSizeIterator<Item = FlatKdNode> + '_ {
60        self.nodes.iter().map(|node| FlatKdNode {
61            point: node.point,
62            index: node.original_index,
63            left: node.left,
64            right: node.right,
65            axis: node.axis,
66        })
67    }
68
69    /// Create a new KD-tree from a slice of points.
70    ///
71    /// Points with a NaN or infinite coordinate are left out of the tree and are
72    /// never returned as neighbors (indices of the remaining points still refer
73    /// to `points`). Organized clouds store invalid returns as NaN; a NaN split
74    /// value would send every query down one side and make the finite points on
75    /// the other side unreachable.
76    pub fn new(points: &[Point3f]) -> Result<Self> {
77        Self::from_points(points.to_vec())
78    }
79
80    /// Like [`KdTree::new`], but takes ownership of the points instead of
81    /// copying them (useful when the caller already built a converted copy).
82    pub fn from_points(points: Vec<Point3f>) -> Result<Self> {
83        let mut points_with_indices: Vec<(Point3f, usize)> = points
84            .iter()
85            .enumerate()
86            .filter(|(_, point)| point.coords.iter().all(|c| c.is_finite()))
87            .map(|(i, &point)| (point, i))
88            .collect();
89
90        if points_with_indices.is_empty() {
91            return Ok(Self {
92                nodes: Vec::new(),
93                root: None,
94                points,
95            });
96        }
97
98        // Every slot is overwritten by `build_tree`; the placeholder only lets
99        // us hand out disjoint `&mut` sub-slices to parallel subtree builds.
100        let placeholder = KdNode {
101            point: Point3f::origin(),
102            original_index: 0,
103            left: NIL,
104            right: NIL,
105            axis: 0,
106        };
107        let mut nodes: Vec<KdNode> = (0..points_with_indices.len())
108            .map(|_| placeholder.clone())
109            .collect();
110        Self::build_tree(&mut nodes, 0, &mut points_with_indices, 0);
111
112        Ok(Self {
113            nodes,
114            root: Some(0),
115            points,
116        })
117    }
118
119    /// Recursively build the KD-tree for `points` into `nodes`, whose first slot
120    /// sits at absolute array index `base`.
121    ///
122    /// Slots are filled in pre-order: the subtree root goes in `nodes[0]`, the
123    /// left subtree (`median` nodes) in `nodes[1..=median]`, and the right
124    /// subtree after it. Because each subtree's slot range is known up front,
125    /// the two halves are disjoint slices and large subtrees build in parallel.
126    fn build_tree(nodes: &mut [KdNode], base: u32, points: &mut [(Point3f, usize)], depth: usize) {
127        /// Below this size, spawning rayon tasks costs more than it saves.
128        const PARALLEL_THRESHOLD: usize = 4096;
129
130        let axis = depth % 3;
131        let len = points.len();
132        let median = (len - 1) / 2;
133
134        // `select_nth_unstable_by` is introselect: O(n) and, unlike a fixed
135        // last-element pivot, not quadratic on already-sorted input such as
136        // row-ordered depth images. Coordinates are finite here (`new` filters
137        // the rest), so `total_cmp` matches numeric order.
138        points.select_nth_unstable_by(median, |a, b| a.0.coords[axis].total_cmp(&b.0.coords[axis]));
139
140        let (left_points, rest) = points.split_at_mut(median);
141        let ((point, index), right_points) = (rest[0], &mut rest[1..]);
142
143        let (root, children) = nodes.split_first_mut().expect("one slot per point");
144        let (left_nodes, right_nodes) = children.split_at_mut(median);
145
146        let left_base = base + 1;
147        let right_base = base + 1 + median as u32;
148        *root = KdNode {
149            point,
150            original_index: index,
151            left: if left_points.is_empty() {
152                NIL
153            } else {
154                left_base
155            },
156            right: if right_points.is_empty() {
157                NIL
158            } else {
159                right_base
160            },
161            axis: axis as u8,
162        };
163
164        let build_left = |nodes: &mut [KdNode], points: &mut [(Point3f, usize)]| {
165            if !points.is_empty() {
166                Self::build_tree(nodes, left_base, points, depth + 1);
167            }
168        };
169        let build_right = |nodes: &mut [KdNode], points: &mut [(Point3f, usize)]| {
170            if !points.is_empty() {
171                Self::build_tree(nodes, right_base, points, depth + 1);
172            }
173        };
174
175        if len > PARALLEL_THRESHOLD {
176            rayon::join(
177                || build_left(left_nodes, left_points),
178                || build_right(right_nodes, right_points),
179            );
180        } else {
181            build_left(left_nodes, left_points);
182            build_right(right_nodes, right_points);
183        }
184    }
185
186    /// Find the `k` nearest neighbors of `query`, writing them into `out` as
187    /// `(original_index, squared_distance)`, closest first.
188    ///
189    /// `out` is cleared first and its allocation is reused, so calling this in a
190    /// loop (one buffer per thread) does no allocation per query. This is the hot
191    /// path of normal estimation. `k` is expected to be small (tens): neighbors
192    /// are kept in a sorted array rather than a heap.
193    pub fn find_k_nearest_into(&self, query: &Point3f, k: usize, out: &mut Vec<(usize, f32)>) {
194        /// See `find_nearest_bounded` for why 64 is enough.
195        const MAX_STACK: usize = 64;
196
197        out.clear();
198        let Some(root) = self.root else {
199            return;
200        };
201        if k == 0 {
202            return;
203        }
204
205        // Squared distance a point must beat to get in: the current k-th
206        // nearest once we have k, otherwise anything.
207        let bound = |out: &Vec<(usize, f32)>| {
208            if out.len() < k {
209                f32::INFINITY
210            } else {
211                out[k - 1].1
212            }
213        };
214
215        // (node, squared distance from the query to the split plane that
216        // separated it), re-checked on pop as in `find_nearest_bounded`.
217        let mut stack = [(0u32, 0.0f32); MAX_STACK];
218        stack[0] = (root, 0.0);
219        let mut len = 1usize;
220
221        while len > 0 {
222            len -= 1;
223            let (idx, plane_sq) = stack[len];
224            if plane_sq >= bound(out) {
225                continue;
226            }
227            let node = &self.nodes[idx as usize];
228
229            let dist_sq = Self::distance_squared(&node.point, query);
230            if dist_sq < bound(out) {
231                if out.len() == k {
232                    out.pop();
233                }
234                let pos = out.partition_point(|&(_, d)| d <= dist_sq);
235                out.insert(pos, (node.original_index, dist_sq));
236            }
237
238            let axis_dist =
239                query.coords[node.axis as usize] - node.point.coords[node.axis as usize];
240            let axis_dist_sq = axis_dist * axis_dist;
241            let (near, far) = if axis_dist <= 0.0 {
242                (node.left, node.right)
243            } else {
244                (node.right, node.left)
245            };
246
247            // Push far before near so near is popped first (LIFO).
248            if far != NIL && axis_dist_sq < bound(out) {
249                stack[len] = (far, axis_dist_sq);
250                len += 1;
251            }
252            if near != NIL {
253                stack[len] = (near, 0.0);
254                len += 1;
255            }
256        }
257    }
258
259    /// Find the single nearest neighbor of `query`, allocation-free.
260    ///
261    /// `bound_sq` is a squared-distance upper bound: only points strictly closer
262    /// than it are considered, and the far side of a split is skipped as soon as
263    /// it cannot beat the best so far. Pass `f32::INFINITY` for an unbounded
264    /// search. When the caller already knows a candidate (e.g. last ICP
265    /// iteration's match), pass its index and squared distance as `seed` so the
266    /// search starts pruned; `seed` is returned if nothing closer is found.
267    ///
268    /// Returns `(original_index, squared_distance)`, or `None` if no point lies
269    /// within the bound and no seed was given. This is the hot path for ICP
270    /// correspondence search, so — unlike `find_k_nearest` — it uses a fixed-size
271    /// stack and no heap, result `Vec`, or `sqrt`.
272    pub fn find_nearest_bounded(
273        &self,
274        query: &Point3f,
275        bound_sq: f32,
276        seed: Option<(usize, f32)>,
277    ) -> Option<(usize, f32)> {
278        // The tree is median-split, so its depth is at most ceil(log2(n + 1)),
279        // which for `u32` node indices is ≤ 33. The stack holds at most one
280        // deferred far child per level plus the current near child.
281        const MAX_STACK: usize = 64;
282
283        let (mut best, mut best_sq) = match seed {
284            Some((_, d)) if d < bound_sq => (seed, d),
285            _ => (None, bound_sq),
286        };
287
288        // Each entry is (node, squared distance from the query to the split
289        // plane that separated it). Far children are pushed before the near
290        // child is explored, so `best_sq` may have shrunk by the time one is
291        // popped; re-checking the plane distance then prunes the whole subtree.
292        let mut stack = [(0u32, 0.0f32); MAX_STACK];
293        let mut len = 0usize;
294        if let Some(root) = self.root {
295            stack[0] = (root, 0.0);
296            len = 1;
297        }
298
299        while len > 0 {
300            len -= 1;
301            let (idx, plane_sq) = stack[len];
302            if plane_sq >= best_sq {
303                continue;
304            }
305            let node = &self.nodes[idx as usize];
306
307            let dist_sq = Self::distance_squared(&node.point, query);
308            if dist_sq < best_sq {
309                best_sq = dist_sq;
310                best = Some((node.original_index, dist_sq));
311            }
312
313            let axis_dist =
314                query.coords[node.axis as usize] - node.point.coords[node.axis as usize];
315            let axis_dist_sq = axis_dist * axis_dist;
316            let (near, far) = if axis_dist <= 0.0 {
317                (node.left, node.right)
318            } else {
319                (node.right, node.left)
320            };
321
322            // Push far before near so near is popped first (LIFO).
323            if far != NIL && axis_dist_sq < best_sq {
324                stack[len] = (far, axis_dist_sq);
325                len += 1;
326            }
327            if near != NIL {
328                stack[len] = (near, 0.0);
329                len += 1;
330            }
331        }
332
333        best
334    }
335
336    /// Calculate squared distance between two points
337    fn distance_squared(a: &Point3f, b: &Point3f) -> f32 {
338        let dx = a.x - b.x;
339        let dy = a.y - b.y;
340        let dz = a.z - b.z;
341        dx * dx + dy * dy + dz * dz
342    }
343}
344
345impl NearestNeighborSearch for KdTree {
346    /// Find the `k` nearest neighbors, closest first, as `(index, distance)`.
347    ///
348    /// For many queries in a loop, [`KdTree::find_k_nearest_into`] avoids the
349    /// allocation per call.
350    fn find_k_nearest(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)> {
351        let mut neighbors = Vec::with_capacity(k);
352        self.find_k_nearest_into(query, k, &mut neighbors);
353        neighbors
354            .into_iter()
355            .map(|(index, distance_sq)| (index, distance_sq.sqrt()))
356            .collect()
357    }
358
359    /// Find all neighbors within `radius` using an iterative stack-based traversal.
360    fn find_radius_neighbors(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)> {
361        if radius <= 0.0 || self.points.is_empty() {
362            return Vec::new();
363        }
364
365        let radius_sq = radius * radius;
366        let mut result: Vec<(usize, f32)> = Vec::new();
367        let mut stack: Vec<u32> = Vec::new();
368
369        if let Some(root) = self.root {
370            stack.push(root);
371        }
372
373        while let Some(idx) = stack.pop() {
374            let node = &self.nodes[idx as usize];
375            let dist_sq = Self::distance_squared(&node.point, query);
376            if dist_sq <= radius_sq {
377                result.push((node.original_index, dist_sq.sqrt()));
378            }
379
380            let query_val = query.coords[node.axis as usize];
381            let node_val = node.point.coords[node.axis as usize];
382            let axis_dist = query_val - node_val;
383
384            let (near, far) = if query_val <= node_val {
385                (node.left, node.right)
386            } else {
387                (node.right, node.left)
388            };
389
390            // The far subtree can only contain in-radius points when the
391            // distance to the splitting hyperplane is within the search radius.
392            if axis_dist * axis_dist <= radius_sq {
393                if far != NIL {
394                    stack.push(far);
395                }
396            }
397            if near != NIL {
398                stack.push(near);
399            }
400        }
401
402        result.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
403        result
404    }
405}
406
407/// Simple brute force nearest neighbor search for small datasets
408pub struct BruteForceSearch {
409    points: Vec<Point3f>,
410}
411
412impl BruteForceSearch {
413    pub fn new(points: &[Point3f]) -> Self {
414        Self {
415            points: points.to_vec(),
416        }
417    }
418}
419
420impl NearestNeighborSearch for BruteForceSearch {
421    fn find_k_nearest(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)> {
422        if k == 0 || self.points.is_empty() {
423            return Vec::new();
424        }
425
426        let mut distances: Vec<(usize, f32)> = self
427            .points
428            .iter()
429            .enumerate()
430            .map(|(idx, point)| {
431                let dx = point.x - query.x;
432                let dy = point.y - query.y;
433                let dz = point.z - query.z;
434                let distance = (dx * dx + dy * dy + dz * dz).sqrt();
435                (idx, distance)
436            })
437            .collect();
438
439        // Sort by distance and take k nearest
440        distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
441        distances.truncate(k);
442        distances
443    }
444
445    fn find_radius_neighbors(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)> {
446        if radius <= 0.0 || self.points.is_empty() {
447            return Vec::new();
448        }
449
450        let radius_squared = radius * radius;
451        self.points
452            .iter()
453            .enumerate()
454            .filter_map(|(idx, point)| {
455                let dx = point.x - query.x;
456                let dy = point.y - query.y;
457                let dz = point.z - query.z;
458                let distance_squared = dx * dx + dy * dy + dz * dz;
459
460                if distance_squared <= radius_squared {
461                    Some((idx, distance_squared.sqrt()))
462                } else {
463                    None
464                }
465            })
466            .collect()
467    }
468}
469
470#[cfg(test)]
471mod tests {
472    use super::*;
473    use rand::Rng;
474    use threecrate_core::Point3f;
475
476    fn create_test_points() -> Vec<Point3f> {
477        vec![
478            Point3f::new(0.0, 0.0, 0.0),
479            Point3f::new(1.0, 0.0, 0.0),
480            Point3f::new(0.0, 1.0, 0.0),
481            Point3f::new(0.0, 0.0, 1.0),
482            Point3f::new(1.0, 1.0, 0.0),
483            Point3f::new(1.0, 0.0, 1.0),
484            Point3f::new(0.0, 1.0, 1.0),
485            Point3f::new(1.0, 1.0, 1.0),
486        ]
487    }
488
489    #[test]
490    fn test_kd_tree_construction() {
491        let points = create_test_points();
492        let kdtree = KdTree::new(&points).unwrap();
493
494        assert_eq!(kdtree.points.len(), points.len());
495        assert!(kdtree.root.is_some());
496    }
497
498    #[test]
499    fn test_empty_kd_tree() {
500        let kdtree = KdTree::new(&[]).unwrap();
501        assert!(kdtree.root.is_none());
502        assert!(kdtree.points.is_empty());
503
504        let query = Point3f::new(0.0, 0.0, 0.0);
505        let result = kdtree.find_k_nearest(&query, 5);
506        assert!(result.is_empty());
507    }
508
509    #[test]
510    fn test_k_nearest_neighbors_consistency() {
511        let points = create_test_points();
512        let kdtree = KdTree::new(&points).unwrap();
513        let brute_force = BruteForceSearch::new(&points);
514
515        let query = Point3f::new(0.5, 0.5, 0.5);
516        let k = 3;
517
518        let mut kdtree_result = kdtree.find_k_nearest(&query, k);
519        let mut brute_force_result = brute_force.find_k_nearest(&query, k);
520
521        println!("KD-tree result before sorting: {:?}", kdtree_result);
522        println!(
523            "Brute force result before sorting: {:?}",
524            brute_force_result
525        );
526
527        // Sort by distance first, then by index for consistent comparison
528        kdtree_result.sort_by(|a, b| {
529            a.1.partial_cmp(&b.1)
530                .unwrap_or(Ordering::Equal)
531                .then(a.0.cmp(&b.0))
532        });
533        brute_force_result.sort_by(|a, b| {
534            a.1.partial_cmp(&b.1)
535                .unwrap_or(Ordering::Equal)
536                .then(a.0.cmp(&b.0))
537        });
538
539        println!("KD-tree result after sorting: {:?}", kdtree_result);
540        println!("Brute force result after sorting: {:?}", brute_force_result);
541
542        // Results should have the same length
543        assert_eq!(kdtree_result.len(), brute_force_result.len());
544        assert_eq!(kdtree_result.len(), k);
545
546        // Results should be sorted by distance
547        for i in 1..kdtree_result.len() {
548            assert!(kdtree_result[i - 1].1 <= kdtree_result[i].1);
549            assert!(brute_force_result[i - 1].1 <= brute_force_result[i].1);
550        }
551
552        // Check that the distances match (within tolerance)
553        for (kdtree_neighbor, brute_neighbor) in kdtree_result.iter().zip(brute_force_result.iter())
554        {
555            assert!((kdtree_neighbor.1 - brute_neighbor.1).abs() < 1e-6);
556        }
557
558        // For points with the same distance, we don't require the exact same indices
559        // as long as the distances are correct, the implementation is working
560        println!(
561            "Test passed: Both methods found {} neighbors with correct distances",
562            k
563        );
564    }
565
566    #[test]
567    fn test_radius_neighbors_consistency() {
568        let points = create_test_points();
569        let kdtree = KdTree::new(&points).unwrap();
570        let brute_force = BruteForceSearch::new(&points);
571
572        let query = Point3f::new(0.5, 0.5, 0.5);
573        let radius = 1.5;
574
575        let mut kdtree_result = kdtree.find_radius_neighbors(&query, radius);
576        let mut brute_force_result = brute_force.find_radius_neighbors(&query, radius);
577
578        // Sort by distance first, then by index for consistent comparison
579        kdtree_result.sort_by(|a, b| {
580            a.1.partial_cmp(&b.1)
581                .unwrap_or(Ordering::Equal)
582                .then(a.0.cmp(&b.0))
583        });
584        brute_force_result.sort_by(|a, b| {
585            a.1.partial_cmp(&b.1)
586                .unwrap_or(Ordering::Equal)
587                .then(a.0.cmp(&b.0))
588        });
589
590        // Results should have the same length
591        assert_eq!(kdtree_result.len(), brute_force_result.len());
592
593        // Results should be sorted by distance
594        for i in 1..kdtree_result.len() {
595            assert!(kdtree_result[i - 1].1 <= kdtree_result[i].1);
596            assert!(brute_force_result[i - 1].1 <= brute_force_result[i].1);
597        }
598
599        // All distances should be within radius
600        for (_, distance) in &kdtree_result {
601            assert!(*distance <= radius);
602        }
603
604        for (_, distance) in &brute_force_result {
605            assert!(*distance <= radius);
606        }
607
608        // Check that the distances match (within tolerance)
609        for (kdtree_neighbor, brute_neighbor) in kdtree_result.iter().zip(brute_force_result.iter())
610        {
611            assert!((kdtree_neighbor.1 - brute_neighbor.1).abs() < 1e-6);
612        }
613
614        println!(
615            "Test passed: Both methods found {} neighbors within radius {}",
616            kdtree_result.len(),
617            radius
618        );
619    }
620
621    #[test]
622    fn test_edge_cases() {
623        let points = create_test_points();
624        let kdtree = KdTree::new(&points).unwrap();
625        let _brute_force = BruteForceSearch::new(&points);
626
627        let query = Point3f::new(0.0, 0.0, 0.0);
628
629        // Test k = 0
630        let result = kdtree.find_k_nearest(&query, 0);
631        assert!(result.is_empty());
632
633        // Test k larger than number of points
634        let result = kdtree.find_k_nearest(&query, 20);
635        assert_eq!(result.len(), points.len());
636
637        // Test radius = 0
638        let result = kdtree.find_radius_neighbors(&query, 0.0);
639        assert!(result.is_empty());
640
641        // Test negative radius
642        let result = kdtree.find_radius_neighbors(&query, -1.0);
643        assert!(result.is_empty());
644    }
645
646    #[test]
647    fn test_random_points() {
648        let mut rng = rand::rng();
649        let mut points = Vec::new();
650
651        // Generate 100 random points
652        for _ in 0..100 {
653            points.push(Point3f::new(
654                rng.random_range(-10.0..10.0),
655                rng.random_range(-10.0..10.0),
656                rng.random_range(-10.0..10.0),
657            ));
658        }
659
660        let kdtree = KdTree::new(&points).unwrap();
661        let brute_force = BruteForceSearch::new(&points);
662
663        // Test multiple random queries
664        for _ in 0..10 {
665            let query = Point3f::new(
666                rng.random_range(-5.0..5.0),
667                rng.random_range(-5.0..5.0),
668                rng.random_range(-5.0..5.0),
669            );
670
671            let k = rng.random_range(1..=10);
672            let radius = rng.random_range(1.0..5.0);
673
674            let mut kdtree_knn = kdtree.find_k_nearest(&query, k);
675            let mut brute_knn = brute_force.find_k_nearest(&query, k);
676
677            let mut kdtree_radius = kdtree.find_radius_neighbors(&query, radius);
678            let mut brute_radius = brute_force.find_radius_neighbors(&query, radius);
679
680            // Sort by distance first, then by index for consistent comparison
681            kdtree_knn.sort_by(|a, b| {
682                a.1.partial_cmp(&b.1)
683                    .unwrap_or(Ordering::Equal)
684                    .then(a.0.cmp(&b.0))
685            });
686            brute_knn.sort_by(|a, b| {
687                a.1.partial_cmp(&b.1)
688                    .unwrap_or(Ordering::Equal)
689                    .then(a.0.cmp(&b.0))
690            });
691
692            kdtree_radius.sort_by(|a, b| {
693                a.1.partial_cmp(&b.1)
694                    .unwrap_or(Ordering::Equal)
695                    .then(a.0.cmp(&b.0))
696            });
697            brute_radius.sort_by(|a, b| {
698                a.1.partial_cmp(&b.1)
699                    .unwrap_or(Ordering::Equal)
700                    .then(a.0.cmp(&b.0))
701            });
702
703            // Verify k-nearest neighbors consistency
704            assert_eq!(kdtree_knn.len(), brute_knn.len());
705            assert_eq!(kdtree_knn.len(), k.min(points.len()));
706
707            // Check that the distances match (within tolerance)
708            let min_len = kdtree_knn.len().min(brute_knn.len());
709            for i in 0..min_len {
710                assert!((kdtree_knn[i].1 - brute_knn[i].1).abs() < 1e-6);
711            }
712
713            // Verify radius neighbors consistency
714            assert_eq!(kdtree_radius.len(), brute_radius.len());
715
716            // Check that the distances match (within tolerance)
717            let min_len = kdtree_radius.len().min(brute_radius.len());
718            for i in 0..min_len {
719                assert!((kdtree_radius[i].1 - brute_radius[i].1).abs() < 1e-6);
720            }
721        }
722    }
723
724    #[test]
725    fn test_find_nearest_bounded_matches_brute_force() {
726        let mut rng = rand::rng();
727        let points: Vec<Point3f> = (0..5000)
728            .map(|_| {
729                Point3f::new(
730                    rng.random_range(-10.0..10.0),
731                    rng.random_range(-10.0..10.0),
732                    rng.random_range(-10.0..10.0),
733                )
734            })
735            .collect();
736        let kdtree = KdTree::new(&points).unwrap();
737        let brute_force = BruteForceSearch::new(&points);
738
739        for _ in 0..200 {
740            let query = Point3f::new(
741                rng.random_range(-12.0..12.0),
742                rng.random_range(-12.0..12.0),
743                rng.random_range(-12.0..12.0),
744            );
745            let (bf_idx, bf_dist) = brute_force.find_k_nearest(&query, 1)[0];
746            let bf_sq = bf_dist * bf_dist;
747
748            // Unbounded, unseeded
749            let (_, d) = kdtree
750                .find_nearest_bounded(&query, f32::INFINITY, None)
751                .unwrap();
752            assert!((d - bf_sq).abs() < 1e-4);
753
754            // Seeded with an arbitrary (usually far) point: still exact
755            let seed_idx = rng.random_range(0..points.len());
756            let seed_sq = (points[seed_idx] - query).magnitude_squared();
757            let (_, d) = kdtree
758                .find_nearest_bounded(&query, f32::INFINITY, Some((seed_idx, seed_sq)))
759                .unwrap();
760            assert!((d - bf_sq).abs() < 1e-4);
761
762            // Seeded with the true answer: returns it
763            let (idx, _) = kdtree
764                .find_nearest_bounded(&query, f32::INFINITY, Some((bf_idx, bf_sq)))
765                .unwrap();
766            assert_eq!(idx, bf_idx);
767
768            // A bound tighter than the nearest neighbor finds nothing
769            assert!(kdtree
770                .find_nearest_bounded(&query, bf_sq * 0.5, None)
771                .is_none());
772        }
773    }
774
775    #[test]
776    fn test_find_k_nearest_into_matches_brute_force() {
777        let mut rng = rand::rng();
778        let points: Vec<Point3f> = (0..5000)
779            .map(|_| {
780                Point3f::new(
781                    rng.random_range(-10.0..10.0),
782                    rng.random_range(-10.0..10.0),
783                    rng.random_range(-10.0..10.0),
784                )
785            })
786            .collect();
787        let kdtree = KdTree::new(&points).unwrap();
788        let brute_force = BruteForceSearch::new(&points);
789        let mut out = Vec::new();
790
791        for _ in 0..200 {
792            let query = Point3f::new(
793                rng.random_range(-12.0..12.0),
794                rng.random_range(-12.0..12.0),
795                rng.random_range(-12.0..12.0),
796            );
797            let k = rng.random_range(1..=32);
798            kdtree.find_k_nearest_into(&query, k, &mut out);
799            let expected = brute_force.find_k_nearest(&query, k);
800
801            assert_eq!(out.len(), k);
802            for (&(_, got_sq), &(_, want)) in out.iter().zip(&expected) {
803                assert!((got_sq.sqrt() - want).abs() < 1e-4);
804            }
805            // Sorted, closest first
806            assert!(out.windows(2).all(|w| w[0].1 <= w[1].1));
807        }
808    }
809
810    #[test]
811    fn test_kd_tree_with_nan_points() {
812        let mut points = create_test_points();
813        points.push(Point3f::new(f32::NAN, 0.0, 0.0));
814        points.push(Point3f::new(0.5, f32::NAN, f32::NAN));
815
816        // Building must not panic, and finite queries still resolve to the
817        // nearest finite point.
818        let kdtree = KdTree::new(&points).unwrap();
819        let (idx, d) = kdtree
820            .find_nearest_bounded(&Point3f::new(0.9, 0.1, 0.05), f32::INFINITY, None)
821            .unwrap();
822        assert_eq!(idx, 1);
823        assert!(d.is_finite());
824    }
825
826    #[test]
827    fn test_kd_tree_mostly_nan_points() {
828        // Mostly-invalid organized cloud: NaN points must not become split
829        // nodes that hide the finite points from the search.
830        let nan = Point3f::new(f32::NAN, f32::NAN, f32::NAN);
831        let mut points = vec![nan; 50];
832        points[17] = Point3f::new(0.0, 0.0, 0.0);
833        points[33] = Point3f::new(5.0, 5.0, 5.0);
834        points.push(Point3f::new(f32::INFINITY, 0.0, 0.0));
835
836        let kdtree = KdTree::new(&points).unwrap();
837        let query = Point3f::new(0.1, 0.0, 0.0);
838
839        let (idx, _) = kdtree
840            .find_nearest_bounded(&query, f32::INFINITY, None)
841            .unwrap();
842        assert_eq!(idx, 17);
843
844        let knn = kdtree.find_k_nearest(&query, 5);
845        assert_eq!(
846            knn.iter().map(|(i, _)| *i).collect::<Vec<_>>(),
847            vec![17, 33]
848        );
849
850        let radius = kdtree.find_radius_neighbors(&query, 1.0);
851        assert_eq!(radius.len(), 1);
852        assert_eq!(radius[0].0, 17);
853
854        // An all-invalid cloud yields an empty tree, not a panic
855        let empty = KdTree::new(&[nan, nan]).unwrap();
856        assert!(empty
857            .find_nearest_bounded(&query, f32::INFINITY, None)
858            .is_none());
859        assert!(empty.find_k_nearest(&query, 3).is_empty());
860    }
861
862    #[test]
863    fn test_performance_comparison() {
864        let mut rng = rand::rng();
865        let mut points = Vec::new();
866
867        // Generate 1000 random points for performance test
868        for _ in 0..1000 {
869            points.push(Point3f::new(
870                rng.random_range(-10.0..10.0),
871                rng.random_range(-10.0..10.0),
872                rng.random_range(-10.0..10.0),
873            ));
874        }
875
876        let kdtree = KdTree::new(&points).unwrap();
877        let brute_force = BruteForceSearch::new(&points);
878
879        let query = Point3f::new(0.0, 0.0, 0.0);
880        let k = 10;
881
882        // Time KD-tree search
883        let start = std::time::Instant::now();
884        let _kdtree_result = kdtree.find_k_nearest(&query, k);
885        let kdtree_time = start.elapsed();
886
887        // Time brute force search
888        let start = std::time::Instant::now();
889        let _brute_result = brute_force.find_k_nearest(&query, k);
890        let brute_time = start.elapsed();
891
892        // KD-tree should be faster for larger datasets
893        println!("KD-tree time: {:?}", kdtree_time);
894        println!("Brute force time: {:?}", brute_time);
895
896        // For 1000 points, KD-tree should be significantly faster
897        // Note: For small k values, brute force might actually be faster due to overhead
898        // So we'll just verify both methods work correctly
899        assert!(kdtree_time.as_nanos() > 0);
900        assert!(brute_time.as_nanos() > 0);
901    }
902
903    #[test]
904    fn test_debug_k_nearest() {
905        let points = vec![
906            Point3f::new(0.0, 0.0, 0.0),
907            Point3f::new(1.0, 0.0, 0.0),
908            Point3f::new(0.0, 1.0, 0.0),
909            Point3f::new(0.0, 0.0, 1.0),
910            Point3f::new(1.0, 1.0, 0.0),
911            Point3f::new(1.0, 0.0, 1.0),
912            Point3f::new(0.0, 1.0, 1.0),
913            Point3f::new(1.0, 1.0, 1.0),
914        ];
915
916        let kdtree = KdTree::new(&points).unwrap();
917        let brute_force = BruteForceSearch::new(&points);
918
919        let query = Point3f::new(0.5, 0.5, 0.5);
920        let k = 3;
921
922        let mut kdtree_result = kdtree.find_k_nearest(&query, k);
923        let mut brute_force_result = brute_force.find_k_nearest(&query, k);
924
925        kdtree_result.sort_by(|a, b| {
926            a.1.partial_cmp(&b.1)
927                .unwrap_or(Ordering::Equal)
928                .then(a.0.cmp(&b.0))
929        });
930        brute_force_result.sort_by(|a, b| {
931            a.1.partial_cmp(&b.1)
932                .unwrap_or(Ordering::Equal)
933                .then(a.0.cmp(&b.0))
934        });
935
936        assert_eq!(kdtree_result.len(), brute_force_result.len());
937        assert_eq!(kdtree_result.len(), k);
938        for (kd, bf) in kdtree_result.iter().zip(brute_force_result.iter()) {
939            assert!(
940                (kd.1 - bf.1).abs() < 1e-6,
941                "distance mismatch: kd={}, bf={}",
942                kd.1,
943                bf.1
944            );
945        }
946    }
947}