threecrate_algorithms/
point_cloud_ops.rs

1//! Point cloud operations including k-nearest neighbors search
2
3use threecrate_core::{PointCloud, Point3f, NearestNeighborSearch};
4use crate::nearest_neighbor::{KdTree, BruteForceSearch};
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
21    /// use threecrate_core::{PointCloud, Point3f};
22    /// use threecrate_algorithms::point_cloud_ops::PointCloudNeighbors;
23    /// 
24    /// let mut cloud = PointCloud::new();
25    /// cloud.push(Point3f::new(0.0, 0.0, 0.0));
26    /// cloud.push(Point3f::new(1.0, 0.0, 0.0));
27    /// cloud.push(Point3f::new(0.0, 1.0, 0.0));
28    /// 
29    /// let neighbors = cloud.k_nearest_neighbors(2);
30    /// // neighbors[0] contains the 2 nearest neighbors for point 0
31    /// ```
32    fn k_nearest_neighbors(&self, k: usize) -> Vec<Vec<(usize, f32)>>;
33
34    /// Find k nearest neighbors for a specific query point using KD-tree
35    /// 
36    /// # Arguments
37    /// * `query` - The query point to find neighbors for
38    /// * `k` - Number of nearest neighbors to find
39    /// 
40    /// # Returns
41    /// * `Vec<(usize, f32)>` - Vector of (index, distance) pairs for the k nearest neighbors
42    fn find_k_nearest(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)>;
43
44    /// Find all neighbors within a given radius using KD-tree
45    /// 
46    /// # Arguments
47    /// * `query` - The query point to find neighbors for
48    /// * `radius` - Search radius
49    /// 
50    /// # Returns
51    /// * `Vec<(usize, f32)>` - Vector of (index, distance) pairs for neighbors within radius
52    fn find_radius_neighbors(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)>;
53
54    /// Find k nearest neighbors using brute force search (for small datasets or testing)
55    /// 
56    /// This method is useful for small point clouds or when you want to verify
57    /// the results of the KD-tree implementation.
58    /// 
59    /// # Arguments
60    /// * `query` - The query point to find neighbors for
61    /// * `k` - Number of nearest neighbors to find
62    /// 
63    /// # Returns
64    /// * `Vec<(usize, f32)>` - Vector of (index, distance) pairs for the k nearest neighbors
65    fn find_k_nearest_brute_force(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)>;
66
67    /// Find all neighbors within a given radius using brute force search
68    /// 
69    /// # Arguments
70    /// * `query` - The query point to find neighbors for
71    /// * `radius` - Search radius
72    /// 
73    /// # Returns
74    /// * `Vec<(usize, f32)>` - Vector of (index, distance) pairs for neighbors within radius
75    fn find_radius_neighbors_brute_force(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)>;
76}
77
78impl PointCloudNeighbors for PointCloud<Point3f> {
79    fn k_nearest_neighbors(&self, k: usize) -> Vec<Vec<(usize, f32)>> {
80        if self.is_empty() || k == 0 {
81            return Vec::new();
82        }
83
84        // Use KD-tree for efficient search
85        let kdtree = KdTree::new(&self.points)
86            .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)
113            .expect("Failed to build KD-tree");
114        
115        kdtree.find_k_nearest(query, k)
116    }
117
118    fn find_radius_neighbors(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)> {
119        if self.is_empty() || radius <= 0.0 {
120            return Vec::new();
121        }
122
123        let kdtree = KdTree::new(&self.points)
124            .expect("Failed to build KD-tree");
125        
126        kdtree.find_radius_neighbors(query, radius)
127    }
128
129    fn find_k_nearest_brute_force(&self, query: &Point3f, k: usize) -> Vec<(usize, f32)> {
130        if self.is_empty() || k == 0 {
131            return Vec::new();
132        }
133
134        let brute_force = BruteForceSearch::new(&self.points);
135        brute_force.find_k_nearest(query, k)
136    }
137
138    fn find_radius_neighbors_brute_force(&self, query: &Point3f, radius: f32) -> Vec<(usize, f32)> {
139        if self.is_empty() || radius <= 0.0 {
140            return Vec::new();
141        }
142
143        let brute_force = BruteForceSearch::new(&self.points);
144        brute_force.find_radius_neighbors(query, radius)
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use threecrate_core::Point3f;
152
153    #[test]
154    fn test_point_cloud_k_nearest_neighbors() {
155        let mut cloud = PointCloud::new();
156        cloud.push(Point3f::new(0.0, 0.0, 0.0));
157        cloud.push(Point3f::new(1.0, 0.0, 0.0));
158        cloud.push(Point3f::new(0.0, 1.0, 0.0));
159        cloud.push(Point3f::new(1.0, 1.0, 0.0));
160
161        let neighbors = cloud.k_nearest_neighbors(2);
162        assert_eq!(neighbors.len(), 4);
163        
164        // Each point should have 2 neighbors (excluding itself)
165        for point_neighbors in &neighbors {
166            assert_eq!(point_neighbors.len(), 2);
167        }
168    }
169
170    #[test]
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    fn test_brute_force_consistency() {
207        let mut cloud = PointCloud::new();
208        cloud.push(Point3f::new(0.0, 0.0, 0.0));
209        cloud.push(Point3f::new(1.0, 0.0, 0.0));
210        cloud.push(Point3f::new(0.0, 1.0, 0.0));
211        cloud.push(Point3f::new(1.0, 1.0, 0.0));
212
213        let query = Point3f::new(0.5, 0.5, 0.0);
214        let k = 2;
215        
216        let mut kdtree_result = cloud.find_k_nearest(&query, k);
217        let mut brute_result = cloud.find_k_nearest_brute_force(&query, k);
218        
219        // Sort by distance first, then by index for consistent comparison
220        kdtree_result.sort_by(|a, b| {
221            a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)
222                .then(a.0.cmp(&b.0))
223        });
224        brute_result.sort_by(|a, b| {
225            a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)
226                .then(a.0.cmp(&b.0))
227        });
228        
229        assert_eq!(kdtree_result.len(), brute_result.len());
230        
231        // Check that the distances match (within tolerance)
232        for (kdtree_neighbor, brute_neighbor) in kdtree_result.iter().zip(brute_result.iter()) {
233            assert!((kdtree_neighbor.1 - brute_neighbor.1).abs() < 1e-6);
234        }
235    }
236}