Skip to main content

threecrate_algorithms/
point_cloud_ops.rs

1//! Point cloud operations including k-nearest neighbors search
2
3use crate::nearest_neighbor::{BruteForceSearch, KdTree};
4use threecrate_core::{NearestNeighborSearch, Point3f, PointCloud};
5
6/// Extension trait for PointCloud to add k-nearest neighbors functionality
7pub trait PointCloudNeighbors {
8    /// Find k nearest neighbors for each point in the cloud using KD-tree
9    ///
10    /// This method returns a vector where each element contains the indices and distances
11    /// of the k nearest neighbors for the corresponding point in the cloud.
12    ///
13    /// # Arguments
14    /// * `k` - Number of nearest neighbors to find for each point
15    ///
16    /// # Returns
17    /// * `Vec<Vec<(usize, f32)>>` - Vector of neighbor results for each point
18    ///
19    /// # Example
20    /// ```rust,no_run
21    /// // Temporarily disabled due to stack overflow - needs investigation
22    /// use threecrate_core::{PointCloud, Point3f};
23    /// use threecrate_algorithms::point_cloud_ops::PointCloudNeighbors;
24    ///
25    /// let mut cloud = PointCloud::new();
26    /// cloud.push(Point3f::new(0.0, 0.0, 0.0));
27    /// cloud.push(Point3f::new(1.0, 0.0, 0.0));
28    /// cloud.push(Point3f::new(0.0, 1.0, 0.0));
29    ///
30    /// let neighbors = cloud.k_nearest_neighbors(2);
31    /// // neighbors[0] contains the 2 nearest neighbors for point 0
32    /// ```
33    fn k_nearest_neighbors(&self, k: usize) -> Vec<Vec<(usize, f32)>>;
34
35    /// Find k nearest neighbors for a specific query point using KD-tree
36    ///
37    /// # Arguments
38    /// * `query` - The query point to find neighbors for
39    /// * `k` - Number of nearest neighbors to find
40    ///
41    /// # Returns
42    /// * `Vec<(usize, f32)>` - Vector of (index, distance) pairs for the k nearest neighbors
43    fn find_k_nearest(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)>;
44
45    /// Find all neighbors within a given radius using KD-tree
46    ///
47    /// # Arguments
48    /// * `query` - The query point to find neighbors for
49    /// * `radius` - Search radius
50    ///
51    /// # Returns
52    /// * `Vec<(usize, f32)>` - Vector of (index, distance) pairs for neighbors within radius
53    fn find_radius_neighbors(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)>;
54
55    /// Find k nearest neighbors using brute force search (for small datasets or testing)
56    ///
57    /// This method is useful for small point clouds or when you want to verify
58    /// the results of the KD-tree implementation.
59    ///
60    /// # Arguments
61    /// * `query` - The query point to find neighbors for
62    /// * `k` - Number of nearest neighbors to find
63    ///
64    /// # Returns
65    /// * `Vec<(usize, f32)>` - Vector of (index, distance) pairs for the k nearest neighbors
66    fn find_k_nearest_brute_force(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)>;
67
68    /// Find all neighbors within a given radius using brute force search
69    ///
70    /// # Arguments
71    /// * `query` - The query point to find neighbors for
72    /// * `radius` - Search radius
73    ///
74    /// # Returns
75    /// * `Vec<(usize, f32)>` - Vector of (index, distance) pairs for neighbors within radius
76    fn find_radius_neighbors_brute_force(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)>;
77}
78
79impl PointCloudNeighbors for PointCloud<Point3f> {
80    fn k_nearest_neighbors(&self, k: usize) -> Vec<Vec<(usize, f32)>> {
81        if self.is_empty() || k == 0 {
82            return Vec::new();
83        }
84
85        // Use KD-tree for efficient search
86        let kdtree = KdTree::new(&self.points).expect("Failed to build KD-tree");
87
88        let mut results = Vec::with_capacity(self.len());
89
90        for (i, query_point) in self.points.iter().enumerate() {
91            let mut neighbors = kdtree.find_k_nearest(query_point, k + 1); // +1 to exclude self
92
93            // Remove the point itself from its own neighbors
94            neighbors.retain(|&(idx, _)| idx != i);
95
96            // Ensure we have exactly k neighbors (or fewer if not enough points)
97            if neighbors.len() > k {
98                neighbors.truncate(k);
99            }
100
101            results.push(neighbors);
102        }
103
104        results
105    }
106
107    fn find_k_nearest(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)> {
108        if self.is_empty() || k == 0 {
109            return Vec::new();
110        }
111
112        let kdtree = KdTree::new(&self.points).expect("Failed to build KD-tree");
113
114        kdtree.find_k_nearest(query, k)
115    }
116
117    fn find_radius_neighbors(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)> {
118        if self.is_empty() || radius <= 0.0 {
119            return Vec::new();
120        }
121
122        let kdtree = KdTree::new(&self.points).expect("Failed to build KD-tree");
123
124        kdtree.find_radius_neighbors(query, radius)
125    }
126
127    fn find_k_nearest_brute_force(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)> {
128        if self.is_empty() || k == 0 {
129            return Vec::new();
130        }
131
132        let brute_force = BruteForceSearch::new(&self.points);
133        brute_force.find_k_nearest(query, k)
134    }
135
136    fn find_radius_neighbors_brute_force(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)> {
137        if self.is_empty() || radius <= 0.0 {
138            return Vec::new();
139        }
140
141        let brute_force = BruteForceSearch::new(&self.points);
142        brute_force.find_radius_neighbors(query, radius)
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use threecrate_core::Point3f;
150
151    #[test]
152    #[ignore] // Temporarily disabled due to stack overflow - needs investigation
153    fn test_point_cloud_k_nearest_neighbors() {
154        let mut cloud = PointCloud::new();
155        cloud.push(Point3f::new(0.0, 0.0, 0.0));
156        cloud.push(Point3f::new(1.0, 0.0, 0.0));
157        cloud.push(Point3f::new(0.0, 1.0, 0.0));
158        cloud.push(Point3f::new(1.0, 1.0, 0.0));
159
160        let neighbors = cloud.k_nearest_neighbors(2);
161        assert_eq!(neighbors.len(), 4);
162
163        // Each point should have 2 neighbors (excluding itself)
164        for point_neighbors in &neighbors {
165            assert_eq!(point_neighbors.len(), 2);
166        }
167    }
168
169    #[test]
170    #[ignore] // Temporarily disabled due to stack overflow - needs investigation
171    fn test_point_cloud_find_k_nearest() {
172        let mut cloud = PointCloud::new();
173        cloud.push(Point3f::new(0.0, 0.0, 0.0));
174        cloud.push(Point3f::new(1.0, 0.0, 0.0));
175        cloud.push(Point3f::new(0.0, 1.0, 0.0));
176        cloud.push(Point3f::new(1.0, 1.0, 0.0));
177
178        let query = Point3f::new(0.5, 0.5, 0.0);
179        let nearest = cloud.find_k_nearest(&query, 2);
180
181        assert_eq!(nearest.len(), 2);
182        assert!(nearest[0].1 <= nearest[1].1); // Should be sorted by distance
183    }
184
185    #[test]
186    fn test_point_cloud_radius_neighbors() {
187        let mut cloud = PointCloud::new();
188        cloud.push(Point3f::new(0.0, 0.0, 0.0));
189        cloud.push(Point3f::new(1.0, 0.0, 0.0));
190        cloud.push(Point3f::new(0.0, 1.0, 0.0));
191        cloud.push(Point3f::new(1.0, 1.0, 0.0));
192
193        let query = Point3f::new(0.5, 0.5, 0.0);
194        let radius_neighbors = cloud.find_radius_neighbors(&query, 1.0);
195
196        // Should find all 4 points within radius 1.0
197        assert_eq!(radius_neighbors.len(), 4);
198
199        // All distances should be within radius
200        for (_, distance) in &radius_neighbors {
201            assert!(*distance <= 1.0);
202        }
203    }
204
205    #[test]
206    #[ignore] // Temporarily disabled due to stack overflow - needs investigation
207    fn test_brute_force_consistency() {
208        let mut cloud = PointCloud::new();
209        cloud.push(Point3f::new(0.0, 0.0, 0.0));
210        cloud.push(Point3f::new(1.0, 0.0, 0.0));
211        cloud.push(Point3f::new(0.0, 1.0, 0.0));
212        cloud.push(Point3f::new(1.0, 1.0, 0.0));
213
214        let query = Point3f::new(0.5, 0.5, 0.0);
215        let k = 2;
216
217        let mut kdtree_result = cloud.find_k_nearest(&query, k);
218        let mut brute_result = cloud.find_k_nearest_brute_force(&query, k);
219
220        // Sort by distance first, then by index for consistent comparison
221        kdtree_result.sort_by(|a, b| {
222            a.1.partial_cmp(&b.1)
223                .unwrap_or(std::cmp::Ordering::Equal)
224                .then(a.0.cmp(&b.0))
225        });
226        brute_result.sort_by(|a, b| {
227            a.1.partial_cmp(&b.1)
228                .unwrap_or(std::cmp::Ordering::Equal)
229                .then(a.0.cmp(&b.0))
230        });
231
232        assert_eq!(kdtree_result.len(), brute_result.len());
233
234        // Check that the distances match (within tolerance)
235        for (kdtree_neighbor, brute_neighbor) in kdtree_result.iter().zip(brute_result.iter()) {
236            assert!((kdtree_neighbor.1 - brute_neighbor.1).abs() < 1e-6);
237        }
238    }
239}