1use std::cmp::Ordering;
2
3use spatialrust_core::{HasPositions3, PointCloud, SpatialResult};
4
5use crate::{
6 chunked::{ChunkQueryRange, ChunkedNearestNeighborIndex, ChunkedRadiusSearchIndex},
7 NearestNeighborIndex, Neighbor, RadiusSearchIndex, SpatialIndex,
8};
9
10const LEAF_SIZE: usize = 16;
11const SEARCH_STACK_SIZE: usize = 64;
12const AXIS_LEAF: u8 = u8::MAX;
13const INVALID_NODE: u32 = u32::MAX;
14
15#[derive(Clone, Debug)]
17pub struct KdTree {
18 x: Vec<f32>,
19 y: Vec<f32>,
20 z: Vec<f32>,
21 points_order: Vec<u32>,
22 nodes: Vec<KdNode>,
23 root: u32,
24}
25
26#[derive(Clone, Copy, Debug)]
27struct KdNode {
28 split: f32,
29 left: u32,
30 right: u32,
31 start: u32,
32 end: u32,
33 axis: u8,
34}
35
36impl KdTree {
37 #[must_use]
39 pub fn from_slices(x: &[f32], y: &[f32], z: &[f32]) -> Self {
40 assert_eq!(x.len(), y.len());
41 assert_eq!(x.len(), z.len());
42
43 let len = x.len();
44 let mut points_order: Vec<u32> = (0..len as u32).collect();
45 let mut nodes = Vec::with_capacity(if len == 0 { 0 } else { len.div_ceil(LEAF_SIZE) * 2 });
46
47 let root = if len == 0 {
48 INVALID_NODE
49 } else {
50 build_node(x, y, z, &mut points_order, 0, len, &mut nodes)
51 };
52
53 Self { x: x.to_vec(), y: y.to_vec(), z: z.to_vec(), points_order, nodes, root }
54 }
55
56 pub fn from_point_cloud(cloud: &PointCloud) -> SpatialResult<Self> {
58 let (x, y, z) = cloud.positions3()?;
59 Ok(Self::from_slices(x, y, z))
60 }
61
62 fn point(&self, point_index: u32) -> (f32, f32, f32) {
63 let idx = point_index as usize;
64 (self.x[idx], self.y[idx], self.z[idx])
65 }
66
67 fn ordered_point(&self, order_index: u32) -> (u32, f32, f32, f32) {
68 let point_index = self.points_order[order_index as usize];
69 let (x, y, z) = self.point(point_index);
70 (point_index, x, y, z)
71 }
72
73 fn nearest_k_recursive(
74 &self,
75 node: u32,
76 qx: f32,
77 qy: f32,
78 qz: f32,
79 k: usize,
80 best: &mut KnnAccumulator,
81 ) {
82 if node == INVALID_NODE {
83 return;
84 }
85
86 let node_data = self.nodes[node as usize];
87 if node_data.axis == AXIS_LEAF {
88 let start = node_data.start as usize;
89 let end = node_data.end as usize;
90 for order_index in start..end {
91 let (index, px, py, pz) = self.ordered_point(order_index as u32);
92 best.insert(
93 k,
94 Neighbor {
95 index: index as usize,
96 distance_squared: squared_distance(px, py, pz, qx, qy, qz),
97 },
98 );
99 }
100 return;
101 }
102
103 let diff = match node_data.axis {
104 0 => qx - node_data.split,
105 1 => qy - node_data.split,
106 _ => qz - node_data.split,
107 };
108
109 let (near, far) = if diff <= 0.0 {
110 (node_data.left, node_data.right)
111 } else {
112 (node_data.right, node_data.left)
113 };
114
115 self.nearest_k_recursive(near, qx, qy, qz, k, best);
116
117 let worst = best.prune_distance(k);
118 if diff * diff < worst || best.len() < k {
119 self.nearest_k_recursive(far, qx, qy, qz, k, best);
120 }
121 }
122
123 fn radius_recursive(
124 &self,
125 node: u32,
126 qx: f32,
127 qy: f32,
128 qz: f32,
129 radius_sq: f32,
130 out: &mut Vec<Neighbor>,
131 ) {
132 if node == INVALID_NODE {
133 return;
134 }
135
136 let node_data = self.nodes[node as usize];
137 if node_data.axis == AXIS_LEAF {
138 let start = node_data.start as usize;
139 let end = node_data.end as usize;
140 for order_index in start..end {
141 let (index, px, py, pz) = self.ordered_point(order_index as u32);
142 let distance_squared = squared_distance(px, py, pz, qx, qy, qz);
143 if distance_squared <= radius_sq {
144 out.push(Neighbor { index: index as usize, distance_squared });
145 }
146 }
147 return;
148 }
149
150 let diff = match node_data.axis {
151 0 => qx - node_data.split,
152 1 => qy - node_data.split,
153 _ => qz - node_data.split,
154 };
155
156 let (near, far) = if diff <= 0.0 {
157 (node_data.left, node_data.right)
158 } else {
159 (node_data.right, node_data.left)
160 };
161
162 self.radius_recursive(near, qx, qy, qz, radius_sq, out);
163 if diff * diff <= radius_sq {
164 self.radius_recursive(far, qx, qy, qz, radius_sq, out);
165 }
166 }
167
168 #[must_use]
173 pub fn radius_reaches(&self, x: f32, y: f32, z: f32, radius: f32, target: usize) -> bool {
174 if target == 0 {
175 return true;
176 }
177 if self.is_empty() || radius < 0.0 {
178 return false;
179 }
180 self.radius_count_iterative(x, y, z, radius * radius, target)
181 }
182
183 fn radius_count_iterative(
184 &self,
185 qx: f32,
186 qy: f32,
187 qz: f32,
188 radius_sq: f32,
189 target: usize,
190 ) -> bool {
191 let mut count = 0usize;
192 let mut stack = [INVALID_NODE; SEARCH_STACK_SIZE];
193 let mut stack_len = 0usize;
194 let mut node = self.root;
195
196 loop {
197 while node != INVALID_NODE {
198 let node_data = self.nodes[node as usize];
199 if node_data.axis == AXIS_LEAF {
200 let start = node_data.start as usize;
201 let end = node_data.end as usize;
202 for order_index in start..end {
203 let (_, px, py, pz) = self.ordered_point(order_index as u32);
204 if squared_distance(px, py, pz, qx, qy, qz) <= radius_sq {
205 count += 1;
206 if count >= target {
207 return true;
208 }
209 }
210 }
211 break;
212 }
213
214 let diff = match node_data.axis {
215 0 => qx - node_data.split,
216 1 => qy - node_data.split,
217 _ => qz - node_data.split,
218 };
219 let (near, far) = if diff <= 0.0 {
220 (node_data.left, node_data.right)
221 } else {
222 (node_data.right, node_data.left)
223 };
224
225 if diff * diff <= radius_sq {
226 if stack_len < stack.len() {
227 stack[stack_len] = far;
228 stack_len += 1;
229 } else if self
230 .radius_count_recursive(far, qx, qy, qz, radius_sq, target, &mut count)
231 {
232 return true;
233 }
234 }
235 node = near;
236 }
237
238 if stack_len == 0 {
239 return false;
240 }
241 stack_len -= 1;
242 node = stack[stack_len];
243 }
244 }
245
246 fn radius_count_recursive(
249 &self,
250 node: u32,
251 qx: f32,
252 qy: f32,
253 qz: f32,
254 radius_sq: f32,
255 target: usize,
256 count: &mut usize,
257 ) -> bool {
258 if node == INVALID_NODE {
259 return false;
260 }
261
262 let node_data = self.nodes[node as usize];
263 if node_data.axis == AXIS_LEAF {
264 let start = node_data.start as usize;
265 let end = node_data.end as usize;
266 for order_index in start..end {
267 let (_, px, py, pz) = self.ordered_point(order_index as u32);
268 if squared_distance(px, py, pz, qx, qy, qz) <= radius_sq {
269 *count += 1;
270 if *count >= target {
271 return true;
272 }
273 }
274 }
275 return false;
276 }
277
278 let diff = match node_data.axis {
279 0 => qx - node_data.split,
280 1 => qy - node_data.split,
281 _ => qz - node_data.split,
282 };
283 let (near, far) = if diff <= 0.0 {
284 (node_data.left, node_data.right)
285 } else {
286 (node_data.right, node_data.left)
287 };
288
289 if self.radius_count_recursive(near, qx, qy, qz, radius_sq, target, count) {
290 return true;
291 }
292 if diff * diff <= radius_sq {
293 return self.radius_count_recursive(far, qx, qy, qz, radius_sq, target, count);
294 }
295 false
296 }
297}
298
299impl SpatialIndex for KdTree {
300 fn len(&self) -> usize {
301 self.x.len()
302 }
303}
304
305impl NearestNeighborIndex for KdTree {
306 fn nearest_one(&self, x: f32, y: f32, z: f32) -> Option<Neighbor> {
307 self.nearest_k(x, y, z, 1).into_iter().next()
308 }
309
310 fn nearest_k(&self, x: f32, y: f32, z: f32, k: usize) -> Vec<Neighbor> {
311 let mut best = Vec::with_capacity(k.min(self.len()));
312 self.nearest_k_into(x, y, z, k, &mut best);
313 best
314 }
315}
316
317impl KdTree {
318 pub fn nearest_k_into(&self, x: f32, y: f32, z: f32, k: usize, out: &mut Vec<Neighbor>) {
321 self.nearest_k_unsorted_into(x, y, z, k, out);
322 out.sort_by(|a, b| {
323 a.distance_squared.partial_cmp(&b.distance_squared).unwrap_or(Ordering::Equal)
324 });
325 }
326
327 pub fn nearest_k_unsorted_into(
331 &self,
332 x: f32,
333 y: f32,
334 z: f32,
335 k: usize,
336 out: &mut Vec<Neighbor>,
337 ) {
338 out.clear();
339 if self.is_empty() || k == 0 {
340 return;
341 }
342
343 out.reserve(k.min(self.len()));
344 let mut best = KnnAccumulator::new(out);
345 self.nearest_k_recursive(self.root, x, y, z, k, &mut best);
346 }
347}
348
349impl RadiusSearchIndex for KdTree {
350 fn radius_search(&self, x: f32, y: f32, z: f32, radius: f32) -> Vec<Neighbor> {
351 if self.is_empty() || radius < 0.0 {
352 return Vec::new();
353 }
354
355 let radius_sq = radius * radius;
356 let mut out = Vec::new();
357 self.radius_recursive(self.root, x, y, z, radius_sq, &mut out);
358 out
361 }
362}
363
364impl ChunkedRadiusSearchIndex for KdTree {
365 fn radius_search_chunk_into(
366 &self,
367 x: &[f32],
368 y: &[f32],
369 z: &[f32],
370 chunk: ChunkQueryRange,
371 radius: f32,
372 out: &mut Vec<(usize, Neighbor)>,
373 ) {
374 if self.is_empty() || radius < 0.0 || chunk.is_empty() {
375 return;
376 }
377
378 let radius_sq = radius * radius;
379 let mut scratch = Vec::new();
380 for index in chunk {
381 scratch.clear();
382 self.radius_recursive(self.root, x[index], y[index], z[index], radius_sq, &mut scratch);
383 for neighbor in scratch.drain(..) {
384 out.push((index, neighbor));
385 }
386 }
387 }
388}
389
390impl ChunkedNearestNeighborIndex for KdTree {
391 fn nearest_k_chunk_into(
392 &self,
393 x: &[f32],
394 y: &[f32],
395 z: &[f32],
396 chunk: ChunkQueryRange,
397 k: usize,
398 out: &mut Vec<(usize, Neighbor)>,
399 ) {
400 if chunk.is_empty() || k == 0 {
401 return;
402 }
403
404 let mut scratch = Vec::new();
405 for index in chunk {
406 scratch.clear();
407 self.nearest_k_unsorted_into(x[index], y[index], z[index], k, &mut scratch);
408 for neighbor in scratch.drain(..) {
409 out.push((index, neighbor));
410 }
411 }
412 }
413}
414
415#[allow(clippy::too_many_arguments)]
416fn build_node(
417 x: &[f32],
418 y: &[f32],
419 z: &[f32],
420 points_order: &mut [u32],
421 start: usize,
422 end: usize,
423 nodes: &mut Vec<KdNode>,
424) -> u32 {
425 let node_index = nodes.len() as u32;
426 nodes.push(KdNode {
427 split: 0.0,
428 left: INVALID_NODE,
429 right: INVALID_NODE,
430 start: start as u32,
431 end: end as u32,
432 axis: 0,
433 });
434
435 let count = end - start;
436 if count <= LEAF_SIZE {
437 nodes[node_index as usize].axis = AXIS_LEAF;
438 return node_index;
439 }
440
441 let axis = select_axis(x, y, z, points_order, start, end);
442 let mid = start + count / 2;
443 select_nth_by_axis(x, y, z, points_order, start, end, axis, mid);
444
445 let split_point = points_order[mid];
446 let split_value = coordinate(x, y, z, split_point, axis);
447 nodes[node_index as usize].axis = axis;
448 nodes[node_index as usize].split = split_value;
449
450 let left = build_node(x, y, z, points_order, start, mid, nodes);
451 let right = build_node(x, y, z, points_order, mid, end, nodes);
452
453 nodes[node_index as usize].left = left;
454 nodes[node_index as usize].right = right;
455 node_index
456}
457
458fn select_axis(
459 x: &[f32],
460 y: &[f32],
461 z: &[f32],
462 points_order: &[u32],
463 start: usize,
464 end: usize,
465) -> u8 {
466 let mut min = [f32::INFINITY; 3];
467 let mut max = [f32::NEG_INFINITY; 3];
468 for &point_index in &points_order[start..end] {
469 min[0] = min[0].min(x[point_index as usize]);
470 min[1] = min[1].min(y[point_index as usize]);
471 min[2] = min[2].min(z[point_index as usize]);
472 max[0] = max[0].max(x[point_index as usize]);
473 max[1] = max[1].max(y[point_index as usize]);
474 max[2] = max[2].max(z[point_index as usize]);
475 }
476
477 let mut best_axis = 0_u8;
478 let mut best_extent = max[0] - min[0];
479 for axis in 1_u8..3 {
480 let extent = max[axis as usize] - min[axis as usize];
481 if extent > best_extent {
482 best_extent = extent;
483 best_axis = axis;
484 }
485 }
486 best_axis
487}
488
489fn select_nth_by_axis(
490 x: &[f32],
491 y: &[f32],
492 z: &[f32],
493 points_order: &mut [u32],
494 start: usize,
495 end: usize,
496 axis: u8,
497 nth: usize,
498) {
499 let mut left = start;
500 let mut right = end;
501 while left < right {
502 let pivot = partition_by_axis(x, y, z, points_order, left, right, axis);
503 match nth.cmp(&pivot) {
504 Ordering::Less => right = pivot,
505 Ordering::Greater => left = pivot + 1,
506 Ordering::Equal => break,
507 }
508 }
509}
510
511fn partition_by_axis(
512 x: &[f32],
513 y: &[f32],
514 z: &[f32],
515 points_order: &mut [u32],
516 start: usize,
517 end: usize,
518 axis: u8,
519) -> usize {
520 let pivot_index = (start + end) / 2;
521 points_order.swap(start, pivot_index);
522 let pivot_point = points_order[start];
523 let pivot_value = coordinate(x, y, z, pivot_point, axis);
524
525 let mut store = start + 1;
526 for i in (start + 1)..end {
527 if coordinate(x, y, z, points_order[i], axis) < pivot_value {
528 points_order.swap(i, store);
529 store += 1;
530 }
531 }
532 points_order.swap(start, store - 1);
533 store - 1
534}
535
536fn coordinate(x: &[f32], y: &[f32], z: &[f32], point_index: u32, axis: u8) -> f32 {
537 match axis {
538 0 => x[point_index as usize],
539 1 => y[point_index as usize],
540 _ => z[point_index as usize],
541 }
542}
543
544fn squared_distance(px: f32, py: f32, pz: f32, qx: f32, qy: f32, qz: f32) -> f32 {
545 let dx = px - qx;
546 let dy = py - qy;
547 let dz = pz - qz;
548 dx * dx + dy * dy + dz * dz
549}
550
551#[derive(Debug)]
552struct KnnAccumulator<'a> {
553 neighbors: &'a mut Vec<Neighbor>,
554 worst_index: usize,
555 worst_distance_squared: f32,
556}
557
558impl<'a> KnnAccumulator<'a> {
559 fn new(neighbors: &'a mut Vec<Neighbor>) -> Self {
560 Self { neighbors, worst_index: 0, worst_distance_squared: 0.0 }
561 }
562
563 fn len(&self) -> usize {
564 self.neighbors.len()
565 }
566
567 fn prune_distance(&self, k: usize) -> f32 {
568 if self.neighbors.len() < k {
569 f32::INFINITY
570 } else {
571 self.worst_distance_squared
572 }
573 }
574
575 fn insert(&mut self, k: usize, candidate: Neighbor) {
576 if k == 0 {
577 return;
578 }
579
580 if self.neighbors.len() < k {
581 let distance_squared = candidate.distance_squared;
582 self.neighbors.push(candidate);
583 if self.neighbors.len() == 1 || distance_squared > self.worst_distance_squared {
584 self.worst_index = self.neighbors.len() - 1;
585 self.worst_distance_squared = distance_squared;
586 }
587 return;
588 }
589
590 if candidate.distance_squared >= self.worst_distance_squared {
591 return;
592 }
593
594 self.neighbors[self.worst_index] = candidate;
595 self.refresh_worst();
596 }
597
598 fn refresh_worst(&mut self) {
599 let mut worst_index = 0usize;
600 let mut worst_distance_squared = self.neighbors[0].distance_squared;
601 for (index, neighbor) in self.neighbors.iter().enumerate().skip(1) {
602 if neighbor.distance_squared > worst_distance_squared {
603 worst_index = index;
604 worst_distance_squared = neighbor.distance_squared;
605 }
606 }
607 self.worst_index = worst_index;
608 self.worst_distance_squared = worst_distance_squared;
609 }
610}
611
612#[cfg(test)]
613mod tests {
614 use super::KdTree;
615 use crate::{
616 brute::{brute_force_knn, brute_force_radius, BruteForceIndex},
617 NearestNeighborIndex, RadiusSearchIndex,
618 };
619 use spatialrust_core::{PointCloudBuilder, StandardSchemas};
620
621 use crate::SpatialIndex;
622
623 fn sample_cloud() -> (Vec<f32>, Vec<f32>, Vec<f32>) {
624 (
625 vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
626 vec![0.0, 0.0, 0.0, 1.0, 2.0, 0.0],
627 vec![0.0, 0.0, 0.0, 0.0, 0.0, 5.0],
628 )
629 }
630
631 #[test]
632 fn nearest_one_matches_brute_force() {
633 let (x, y, z) = sample_cloud();
634 let tree = KdTree::from_slices(&x, &y, &z);
635 let brute = BruteForceIndex::from_slices(&x, &y, &z);
636
637 let query = (2.1_f32, 0.0, 0.0);
638 assert_eq!(
639 tree.nearest_one(query.0, query.1, query.2),
640 brute.nearest_one(query.0, query.1, query.2)
641 );
642 }
643
644 #[test]
645 fn nearest_k_matches_brute_force() {
646 let (x, y, z) = sample_cloud();
647 let tree = KdTree::from_slices(&x, &y, &z);
648 let expected = brute_force_knn(&x, &y, &z, 1.0, 0.0, 0.0, 3);
649 let actual = tree.nearest_k(1.0, 0.0, 0.0, 3);
650 assert_eq!(actual, expected);
651 }
652
653 #[test]
654 fn radius_search_matches_brute_force() {
655 let (x, y, z) = sample_cloud();
656 let tree = KdTree::from_slices(&x, &y, &z);
657 let mut expected = brute_force_radius(&x, &y, &z, 2.0, 0.0, 0.0, 1.5);
658 let mut actual = tree.radius_search(2.0, 0.0, 0.0, 1.5);
659 expected.sort_by_key(|n| n.index);
661 actual.sort_by_key(|n| n.index);
662 assert_eq!(actual, expected);
663 }
664
665 #[test]
666 fn radius_reaches_matches_radius_search_count() {
667 let (x, y, z) = sample_cloud();
668 let tree = KdTree::from_slices(&x, &y, &z);
669 let count = tree.radius_search(2.0, 0.0, 0.0, 1.5).len();
670 assert!(tree.radius_reaches(2.0, 0.0, 0.0, 1.5, count));
672 assert!(!tree.radius_reaches(2.0, 0.0, 0.0, 1.5, count + 1));
673 assert!(tree.radius_reaches(2.0, 0.0, 0.0, 1.5, 0));
674 }
675
676 #[test]
677 fn radius_reaches_matches_brute_force_on_many_queries() {
678 let mut state = 0x8765_4321_u32;
679 let mut next = || {
680 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
681 (state as f32 / u32::MAX as f32) * 20.0 - 10.0
682 };
683
684 let mut x = Vec::new();
685 let mut y = Vec::new();
686 let mut z = Vec::new();
687 for _ in 0..257 {
688 x.push(next());
689 y.push(next());
690 z.push(next());
691 }
692
693 let tree = KdTree::from_slices(&x, &y, &z);
694 for radius in [0.5_f32, 2.0, 5.0] {
695 for _ in 0..32 {
696 let qx = next();
697 let qy = next();
698 let qz = next();
699 let count = brute_force_radius(&x, &y, &z, qx, qy, qz, radius).len();
700 assert!(tree.radius_reaches(qx, qy, qz, radius, 0));
701 if count > 0 {
702 assert!(tree.radius_reaches(qx, qy, qz, radius, count));
703 }
704 assert!(!tree.radius_reaches(qx, qy, qz, radius, count + 1));
705 }
706 }
707 }
708
709 #[test]
710 fn nearest_k_matches_brute_force_on_many_queries() {
711 let mut state = 0x1234_5678_u32;
712 let mut next = || {
713 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
714 (state as f32 / u32::MAX as f32) * 20.0 - 10.0
715 };
716
717 let mut x = Vec::new();
718 let mut y = Vec::new();
719 let mut z = Vec::new();
720 for _ in 0..257 {
721 x.push(next());
722 y.push(next());
723 z.push(next());
724 }
725
726 let tree = KdTree::from_slices(&x, &y, &z);
727 for k in [1_usize, 2, 5, 10, 33] {
728 for _ in 0..64 {
729 let qx = next();
730 let qy = next();
731 let qz = next();
732 let actual = tree.nearest_k(qx, qy, qz, k);
733 let expected = brute_force_knn(&x, &y, &z, qx, qy, qz, k);
734 assert_eq!(actual, expected, "k={k}, query=({qx}, {qy}, {qz})");
735 }
736 }
737 }
738
739 #[test]
740 fn builds_from_point_cloud() {
741 let mut builder = PointCloudBuilder::new(StandardSchemas::point_xyz());
742 builder.push_point([0.0, 0.0, 0.0]).unwrap();
743 builder.push_point([1.0, 0.0, 0.0]).unwrap();
744 let cloud = builder.build().unwrap();
745 let tree = KdTree::from_point_cloud(&cloud).unwrap();
746 assert_eq!(tree.len(), 2);
747 let nearest = tree.nearest_one(0.9, 0.0, 0.0).unwrap();
748 assert_eq!(nearest.index, 1);
749 }
750
751 #[test]
752 fn degenerate_points_return_valid_neighbor() {
753 let x = vec![1.0, 1.0, 1.0];
754 let y = vec![2.0, 2.0, 2.0];
755 let z = vec![3.0, 3.0, 3.0];
756 let tree = KdTree::from_slices(&x, &y, &z);
757 let neighbor = tree.nearest_one(1.0, 2.0, 2.0).unwrap();
758 assert_eq!(neighbor.distance_squared, 1.0);
759 }
760}