1use std::cmp::Ordering;
4use threecrate_core::{NearestNeighborSearch, Point3f, Result};
5
6const NIL: u32 = u32::MAX;
8
9#[derive(Debug, Clone)]
16struct KdNode {
17 point: Point3f,
18 original_index: usize, left: u32, right: u32, axis: u8, }
23
24#[derive(Debug, Clone, Copy)]
27pub struct FlatKdNode {
28 pub point: Point3f,
30 pub index: usize,
32 pub left: u32,
34 pub right: u32,
36 pub axis: u8,
38}
39
40pub struct KdTree {
45 nodes: Vec<KdNode>,
46 root: Option<u32>,
47 points: Vec<Point3f>, }
49
50impl KdTree {
51 pub const NO_CHILD: u32 = NIL;
53
54 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 pub fn new(points: &[Point3f]) -> Result<Self> {
77 Self::from_points(points.to_vec())
78 }
79
80 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 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 fn build_tree(nodes: &mut [KdNode], base: u32, points: &mut [(Point3f, usize)], depth: usize) {
127 const PARALLEL_THRESHOLD: usize = 4096;
129
130 let axis = depth % 3;
131 let len = points.len();
132 let median = (len - 1) / 2;
133
134 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 pub fn find_k_nearest_into(&self, query: &Point3f, k: usize, out: &mut Vec<(usize, f32)>) {
194 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 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 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 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 pub fn find_nearest_bounded(
273 &self,
274 query: &Point3f,
275 bound_sq: f32,
276 seed: Option<(usize, f32)>,
277 ) -> Option<(usize, f32)> {
278 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 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 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 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 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 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 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
407pub 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 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 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 assert_eq!(kdtree_result.len(), brute_force_result.len());
544 assert_eq!(kdtree_result.len(), k);
545
546 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 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 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 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 assert_eq!(kdtree_result.len(), brute_force_result.len());
592
593 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 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 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 let result = kdtree.find_k_nearest(&query, 0);
631 assert!(result.is_empty());
632
633 let result = kdtree.find_k_nearest(&query, 20);
635 assert_eq!(result.len(), points.len());
636
637 let result = kdtree.find_radius_neighbors(&query, 0.0);
639 assert!(result.is_empty());
640
641 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 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 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 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 assert_eq!(kdtree_knn.len(), brute_knn.len());
705 assert_eq!(kdtree_knn.len(), k.min(points.len()));
706
707 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 assert_eq!(kdtree_radius.len(), brute_radius.len());
715
716 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 let (_, d) = kdtree
750 .find_nearest_bounded(&query, f32::INFINITY, None)
751 .unwrap();
752 assert!((d - bf_sq).abs() < 1e-4);
753
754 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 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 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 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 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 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 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 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 let start = std::time::Instant::now();
884 let _kdtree_result = kdtree.find_k_nearest(&query, k);
885 let kdtree_time = start.elapsed();
886
887 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 println!("KD-tree time: {:?}", kdtree_time);
894 println!("Brute force time: {:?}", brute_time);
895
896 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}