Skip to main content

threecrate_reconstruction/
alpha_shape.rs

1//! Alpha shape reconstruction with enhanced features
2//!
3//! Improved implementation with better geometric algorithms for surface reconstruction.
4
5use threecrate_core::{Error, NormalPoint3f, Point3f, PointCloud, Result, TriangleMesh};
6
7/// Alpha complex configuration
8#[derive(Debug, Clone)]
9pub struct AlphaComplexConfig {
10    /// Alpha parameter for shape filtering
11    pub alpha: f32,
12    /// Mode for alpha complex computation
13    pub mode: AlphaMode,
14    /// Minimum triangle area threshold
15    pub min_triangle_area: f32,
16    /// Enable fast computation mode (less accurate but faster)
17    pub fast_mode: bool,
18}
19
20/// Alpha complex computation modes
21#[derive(Debug, Clone, PartialEq)]
22pub enum AlphaMode {
23    /// Only boundary (manifold surface)
24    BoundaryOnly,
25    /// Full alpha complex including interior
26    Full,
27    /// Adaptive alpha based on local density
28    Adaptive,
29}
30
31impl Default for AlphaComplexConfig {
32    fn default() -> Self {
33        Self {
34            alpha: 1.0,
35            mode: AlphaMode::BoundaryOnly,
36            min_triangle_area: 1e-8,
37            fast_mode: false,
38        }
39    }
40}
41
42/// Enhanced alpha simplex
43#[derive(Debug, Clone)]
44struct AlphaSimplex {
45    /// Vertex indices
46    vertices: Vec<usize>,
47    /// Circumradius squared for efficiency
48    circumradius_sq: f32,
49    /// Simplex dimension (0=point, 1=edge, 2=triangle, 3=tetrahedron)
50    dimension: usize,
51    /// Whether this simplex is on the boundary
52    is_boundary: bool,
53    /// Associated data (area, volume, etc.)
54    measure: f32,
55}
56
57impl AlphaSimplex {
58    /// Create a triangle simplex
59    fn triangle(v1: usize, v2: usize, v3: usize, points: &[Point3f]) -> Self {
60        let p1 = &points[v1];
61        let p2 = &points[v2];
62        let p3 = &points[v3];
63
64        let circumradius_sq = Self::compute_circumradius_sq_triangle(p1, p2, p3);
65        let area = Self::compute_triangle_area(p1, p2, p3);
66
67        Self {
68            vertices: vec![v1, v2, v3],
69            circumradius_sq,
70            dimension: 2,
71            is_boundary: false, // Will be determined later
72            measure: area,
73        }
74    }
75
76    /// Compute circumradius squared for triangle (more efficient)
77    fn compute_circumradius_sq_triangle(p1: &Point3f, p2: &Point3f, p3: &Point3f) -> f32 {
78        let a_sq = (p2 - p1).magnitude_squared();
79        let b_sq = (p3 - p2).magnitude_squared();
80        let c_sq = (p1 - p3).magnitude_squared();
81
82        // Handle degenerate cases
83        if a_sq < 1e-20 || b_sq < 1e-20 || c_sq < 1e-20 {
84            return f32::INFINITY;
85        }
86
87        // Compute area using cross product
88        let v1 = p2 - p1;
89        let v2 = p3 - p1;
90        let cross = v1.cross(&v2);
91        let area_sq = cross.magnitude_squared() * 0.25;
92
93        if area_sq < 1e-20 {
94            return f32::INFINITY;
95        }
96
97        // Circumradius squared formula: R² = (abc)² / (16 * Area²)
98        (a_sq * b_sq * c_sq) / (16.0 * area_sq)
99    }
100
101    /// Compute triangle area
102    fn compute_triangle_area(p1: &Point3f, p2: &Point3f, p3: &Point3f) -> f32 {
103        let v1 = p2 - p1;
104        let v2 = p3 - p1;
105        let cross = v1.cross(&v2);
106        cross.magnitude() * 0.5
107    }
108
109    /// Check if this simplex is valid for the given alpha value
110    fn is_alpha_valid(&self, alpha: f32, min_measure: f32) -> bool {
111        self.circumradius_sq <= alpha * alpha && self.measure >= min_measure
112    }
113}
114
115/// Alpha Complex implementation
116pub struct AlphaComplex {
117    points: Vec<Point3f>,
118    simplices: Vec<AlphaSimplex>,
119    config: AlphaComplexConfig,
120}
121
122impl AlphaComplex {
123    /// Create a new Alpha Complex
124    pub fn new(cloud: &PointCloud<Point3f>, config: AlphaComplexConfig) -> Result<Self> {
125        if cloud.is_empty() {
126            return Err(Error::InvalidData("Point cloud is empty".to_string()));
127        }
128
129        let points = cloud.points.clone();
130
131        Ok(Self {
132            points,
133            simplices: Vec::new(),
134            config,
135        })
136    }
137
138    /// Create from point cloud with normals
139    pub fn from_normals(
140        cloud: &PointCloud<NormalPoint3f>,
141        config: AlphaComplexConfig,
142    ) -> Result<Self> {
143        let points: Vec<Point3f> = cloud.points.iter().map(|p| p.position).collect();
144        let point_cloud = PointCloud::from_points(points);
145        Self::new(&point_cloud, config)
146    }
147
148    /// Generate candidate triangles using spatial locality
149    fn generate_candidate_triangles(&mut self) -> Result<()> {
150        let n = self.points.len();
151
152        if n < 3 {
153            return Err(Error::InvalidData(
154                "Need at least 3 points for triangulation".to_string(),
155            ));
156        }
157
158        self.simplices.clear();
159
160        if self.config.fast_mode {
161            self.generate_triangles_fast()?;
162        } else {
163            self.generate_triangles_quality()?;
164        }
165
166        Ok(())
167    }
168
169    /// Fast triangle generation using simple neighbor finding
170    fn generate_triangles_fast(&mut self) -> Result<()> {
171        let max_triangles = if self.points.len() < 1000 {
172            50000
173        } else {
174            100000
175        };
176        let mut triangle_count = 0;
177
178        for i in 0..self.points.len() {
179            if triangle_count >= max_triangles {
180                break;
181            }
182
183            let point = &self.points[i];
184
185            // Find nearby points using simple distance check
186            let mut neighbors = Vec::new();
187            for (j, other_point) in self.points.iter().enumerate() {
188                if i != j {
189                    let dist = (point - other_point).magnitude();
190                    if dist <= self.config.alpha * 1.5 {
191                        neighbors.push(j);
192                    }
193                }
194            }
195
196            // Generate triangles with nearby points
197            for (idx_j, &j) in neighbors.iter().enumerate() {
198                if triangle_count >= max_triangles {
199                    break;
200                }
201                if j <= i {
202                    continue;
203                }
204
205                for &k in neighbors.iter().skip(idx_j + 1) {
206                    if triangle_count >= max_triangles {
207                        break;
208                    }
209                    if k <= i {
210                        continue;
211                    }
212
213                    let triangle = AlphaSimplex::triangle(i, j, k, &self.points);
214
215                    // Pre-filter by alpha to avoid storing too many candidates
216                    if triangle
217                        .is_alpha_valid(self.config.alpha * 1.5, self.config.min_triangle_area)
218                    {
219                        self.simplices.push(triangle);
220                        triangle_count += 1;
221                    }
222                }
223            }
224        }
225
226        Ok(())
227    }
228
229    /// Quality triangle generation with better geometric constraints
230    fn generate_triangles_quality(&mut self) -> Result<()> {
231        let mut edge_triangles: std::collections::HashMap<(usize, usize), Vec<usize>> =
232            std::collections::HashMap::new();
233        let max_triangles = 50000;
234        let mut triangle_count = 0;
235
236        for i in 0..self.points.len() {
237            if triangle_count >= max_triangles {
238                break;
239            }
240
241            let point = &self.points[i];
242
243            // Find neighbors within alpha distance
244            let mut neighbors = Vec::new();
245            for (j, other_point) in self.points.iter().enumerate() {
246                if i != j {
247                    let dist = (point - other_point).magnitude();
248                    if dist <= self.config.alpha {
249                        neighbors.push(j);
250                    }
251                }
252            }
253
254            // Generate triangles with geometric quality checks
255            for (idx_j, &j) in neighbors.iter().enumerate() {
256                if triangle_count >= max_triangles {
257                    break;
258                }
259                if j <= i {
260                    continue;
261                }
262
263                for &k in neighbors.iter().skip(idx_j + 1) {
264                    if triangle_count >= max_triangles {
265                        break;
266                    }
267                    if k <= i {
268                        continue;
269                    }
270
271                    let triangle = AlphaSimplex::triangle(i, j, k, &self.points);
272
273                    // Quality checks
274                    if self.is_triangle_geometrically_valid(&triangle)? {
275                        let triangle_idx = self.simplices.len();
276                        self.simplices.push(triangle);
277                        triangle_count += 1;
278
279                        // Track edge-triangle adjacency
280                        let edges = [(i, j), (j, k), (k, i)];
281                        for &(v1, v2) in &edges {
282                            let edge = (v1.min(v2), v1.max(v2));
283                            edge_triangles
284                                .entry(edge)
285                                .or_insert_with(Vec::new)
286                                .push(triangle_idx);
287                        }
288                    }
289                }
290            }
291        }
292
293        // Mark boundary triangles based on edge count
294        for triangles in edge_triangles.values() {
295            for &triangle_idx in triangles {
296                if triangles.len() == 1 {
297                    self.simplices[triangle_idx].is_boundary = true;
298                }
299            }
300        }
301
302        Ok(())
303    }
304
305    /// Check if triangle meets geometric validity criteria
306    fn is_triangle_geometrically_valid(&self, triangle: &AlphaSimplex) -> Result<bool> {
307        // Basic geometric checks
308        if triangle.circumradius_sq == f32::INFINITY {
309            return Ok(false);
310        }
311
312        if triangle.measure < self.config.min_triangle_area {
313            return Ok(false);
314        }
315
316        // Check aspect ratio (avoid very thin triangles)
317        let v1 = triangle.vertices[0];
318        let v2 = triangle.vertices[1];
319        let v3 = triangle.vertices[2];
320
321        let p1 = &self.points[v1];
322        let p2 = &self.points[v2];
323        let p3 = &self.points[v3];
324
325        let a = (p2 - p1).magnitude();
326        let b = (p3 - p2).magnitude();
327        let c = (p1 - p3).magnitude();
328
329        let perimeter = a + b + c;
330        if perimeter < 1e-10 {
331            return Ok(false);
332        }
333
334        // Quality measure: area / perimeter² (higher is better)
335        let quality = (4.0 * triangle.measure) / (perimeter * perimeter);
336
337        // Accept triangles with reasonable quality
338        Ok(quality > 0.01) // Configurable threshold
339    }
340
341    /// Filter simplices based on alpha and mode
342    fn filter_simplices(&mut self) -> Result<()> {
343        match self.config.mode {
344            AlphaMode::BoundaryOnly => {
345                self.simplices.retain(|s| {
346                    s.is_boundary
347                        && s.is_alpha_valid(self.config.alpha, self.config.min_triangle_area)
348                });
349            }
350            AlphaMode::Full => {
351                self.simplices
352                    .retain(|s| s.is_alpha_valid(self.config.alpha, self.config.min_triangle_area));
353            }
354            AlphaMode::Adaptive => {
355                // For adaptive mode, use local density estimation
356                let mut filtered_simplices = Vec::new();
357                for simplex in &self.simplices {
358                    // Compute adaptive alpha based on local point density around simplex
359                    let adaptive_alpha = self.compute_local_alpha(simplex)?;
360
361                    if simplex.is_alpha_valid(adaptive_alpha, self.config.min_triangle_area) {
362                        filtered_simplices.push(simplex.clone());
363                    }
364                }
365                self.simplices = filtered_simplices;
366            }
367        }
368
369        Ok(())
370    }
371
372    /// Compute local alpha value based on point density
373    fn compute_local_alpha(&self, simplex: &AlphaSimplex) -> Result<f32> {
374        // Compute centroid of simplex
375        let mut centroid = Point3f::origin();
376        for &vertex_idx in &simplex.vertices {
377            centroid = Point3f::from(centroid.coords + self.points[vertex_idx].coords);
378        }
379        centroid = Point3f::from(centroid.coords / simplex.vertices.len() as f32);
380
381        // Find average distance to nearby points
382        let mut distances = Vec::new();
383        for point in &self.points {
384            let dist = (point - centroid).magnitude();
385            if dist > 0.0 && dist < self.config.alpha * 3.0 {
386                distances.push(dist);
387            }
388        }
389
390        if distances.is_empty() {
391            return Ok(self.config.alpha);
392        }
393
394        // Use median distance as basis for adaptive alpha
395        distances.sort_by(|a, b| a.partial_cmp(b).unwrap());
396        let median_dist = distances[distances.len() / 2];
397
398        // Adaptive alpha is a multiple of local density
399        Ok(median_dist * 1.8)
400    }
401
402    /// Extract surface mesh from alpha complex
403    pub fn extract_surface(&mut self) -> Result<TriangleMesh> {
404        // Generate candidate simplices
405        self.generate_candidate_triangles()?;
406
407        if self.simplices.is_empty() {
408            return Err(Error::Algorithm(
409                "No candidate triangles generated".to_string(),
410            ));
411        }
412
413        // Filter based on alpha criteria
414        self.filter_simplices()?;
415
416        if self.simplices.is_empty() {
417            return Err(Error::Algorithm(
418                "No triangles survived alpha filtering".to_string(),
419            ));
420        }
421
422        // Extract triangular faces
423        let faces: Vec<[usize; 3]> = self
424            .simplices
425            .iter()
426            .filter(|s| s.dimension == 2)
427            .map(|s| [s.vertices[0], s.vertices[1], s.vertices[2]])
428            .collect();
429
430        if faces.is_empty() {
431            return Err(Error::Algorithm("No triangular faces found".to_string()));
432        }
433
434        // Create final mesh
435        let mesh = TriangleMesh::from_vertices_and_faces(self.points.clone(), faces);
436
437        Ok(mesh)
438    }
439
440    /// Get alpha complex statistics
441    pub fn get_statistics(&self) -> AlphaComplexStats {
442        let boundary_count = self.simplices.iter().filter(|s| s.is_boundary).count();
443        let triangle_count = self.simplices.iter().filter(|s| s.dimension == 2).count();
444
445        let avg_circumradius = if !self.simplices.is_empty() {
446            self.simplices
447                .iter()
448                .map(|s| s.circumradius_sq.sqrt())
449                .sum::<f32>()
450                / self.simplices.len() as f32
451        } else {
452            0.0
453        };
454
455        AlphaComplexStats {
456            total_simplices: self.simplices.len(),
457            boundary_simplices: boundary_count,
458            triangle_count,
459            average_circumradius: avg_circumradius,
460        }
461    }
462}
463
464/// Statistics about the alpha complex
465#[derive(Debug, Clone)]
466pub struct AlphaComplexStats {
467    pub total_simplices: usize,
468    pub boundary_simplices: usize,
469    pub triangle_count: usize,
470    pub average_circumradius: f32,
471}
472
473/// Convenience function for alpha shape reconstruction
474pub fn alpha_complex_reconstruction(
475    cloud: &PointCloud<Point3f>,
476    alpha: f32,
477) -> Result<TriangleMesh> {
478    let config = AlphaComplexConfig {
479        alpha,
480        mode: AlphaMode::BoundaryOnly,
481        ..Default::default()
482    };
483    let mut alpha_complex = AlphaComplex::new(cloud, config)?;
484    alpha_complex.extract_surface()
485}
486
487/// Enhanced alpha shape with full configuration
488pub fn alpha_complex_reconstruction_with_config(
489    cloud: &PointCloud<Point3f>,
490    config: &AlphaComplexConfig,
491) -> Result<TriangleMesh> {
492    let mut alpha_complex = AlphaComplex::new(cloud, config.clone())?;
493    alpha_complex.extract_surface()
494}
495
496/// Adaptive alpha shape that adjusts to local point density
497pub fn adaptive_alpha_reconstruction(cloud: &PointCloud<Point3f>) -> Result<TriangleMesh> {
498    let config = AlphaComplexConfig {
499        mode: AlphaMode::Adaptive,
500        fast_mode: false,
501        ..Default::default()
502    };
503    let mut alpha_complex = AlphaComplex::new(cloud, config)?;
504    alpha_complex.extract_surface()
505}
506
507// Keep existing simple functions for backward compatibility
508pub fn alpha_shape_reconstruction(cloud: &PointCloud<Point3f>, alpha: f32) -> Result<TriangleMesh> {
509    alpha_complex_reconstruction(cloud, alpha)
510}
511
512pub fn alpha_shape_reconstruction_with_config(
513    cloud: &PointCloud<Point3f>,
514    config: &AlphaShapeConfig,
515) -> Result<TriangleMesh> {
516    // Convert old config to new config
517    let new_config = AlphaComplexConfig {
518        alpha: config.alpha,
519        mode: if config.boundary_only {
520            AlphaMode::BoundaryOnly
521        } else {
522            AlphaMode::Full
523        },
524        min_triangle_area: config.min_triangle_area,
525        fast_mode: false,
526    };
527    alpha_complex_reconstruction_with_config(cloud, &new_config)
528}
529
530pub fn alpha_shape_from_normals(
531    cloud: &PointCloud<NormalPoint3f>,
532    alpha: f32,
533) -> Result<TriangleMesh> {
534    let config = AlphaComplexConfig {
535        alpha,
536        mode: AlphaMode::BoundaryOnly,
537        ..Default::default()
538    };
539    let mut alpha_complex = AlphaComplex::from_normals(cloud, config)?;
540    alpha_complex.extract_surface()
541}
542
543pub fn estimate_optimal_alpha(cloud: &PointCloud<Point3f>, k: usize) -> Result<f32> {
544    if cloud.is_empty() {
545        return Err(Error::InvalidData("Point cloud is empty".to_string()));
546    }
547
548    let mut distances = Vec::new();
549
550    // Sample points to estimate average k-nearest neighbor distance
551    let sample_size = (cloud.points.len() / 10).max(50).min(500);
552    let step = (cloud.points.len().max(1) / sample_size.max(1)).max(1);
553
554    for i in (0..cloud.points.len()).step_by(step) {
555        let point = &cloud.points[i];
556
557        // Find k nearest neighbors
558        let mut point_distances: Vec<f32> = cloud
559            .points
560            .iter()
561            .enumerate()
562            .filter(|(j, _)| *j != i)
563            .map(|(_, other)| (point - other).magnitude())
564            .collect();
565
566        point_distances.sort_by(|a, b| a.partial_cmp(b).unwrap());
567
568        if point_distances.len() >= k {
569            distances.push(point_distances[k - 1]);
570        }
571    }
572
573    if distances.is_empty() {
574        return Ok(1.0); // Default fallback
575    }
576
577    // Use median distance as basis for alpha
578    distances.sort_by(|a, b| a.partial_cmp(b).unwrap());
579    let median_dist = distances[distances.len() / 2];
580
581    // Optimal alpha is typically 1.5-2.0 times the k-th nearest neighbor distance
582    Ok(median_dist * 1.8)
583}
584
585// Keep existing config for backward compatibility
586#[derive(Debug, Clone)]
587pub struct AlphaShapeConfig {
588    pub alpha: f32,
589    pub boundary_only: bool,
590    pub min_triangle_area: f32,
591}
592
593impl Default for AlphaShapeConfig {
594    fn default() -> Self {
595        Self {
596            alpha: 1.0,
597            boundary_only: true,
598            min_triangle_area: 1e-8,
599        }
600    }
601}
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606    use nalgebra::Point3;
607
608    #[test]
609    fn test_alpha_complex_config_default() {
610        let config = AlphaComplexConfig::default();
611
612        assert_eq!(config.alpha, 1.0);
613        assert_eq!(config.mode, AlphaMode::BoundaryOnly);
614        assert_eq!(config.min_triangle_area, 1e-8);
615        assert!(!config.fast_mode);
616    }
617
618    #[test]
619    fn test_alpha_simplex_triangle() {
620        let points = vec![
621            Point3::new(0.0, 0.0, 0.0),
622            Point3::new(1.0, 0.0, 0.0),
623            Point3::new(0.0, 1.0, 0.0),
624        ];
625
626        let simplex = AlphaSimplex::triangle(0, 1, 2, &points);
627
628        assert_eq!(simplex.vertices, vec![0, 1, 2]);
629        assert_eq!(simplex.dimension, 2);
630        assert!(simplex.measure > 0.0); // Should have positive area
631    }
632
633    #[test]
634    fn test_empty_cloud() {
635        let config = AlphaComplexConfig::default();
636        let cloud = PointCloud::new();
637
638        let result = AlphaComplex::new(&cloud, config);
639        assert!(result.is_err());
640    }
641
642    #[test]
643    fn test_estimate_optimal_alpha_empty() {
644        let cloud = PointCloud::new();
645        let result = estimate_optimal_alpha(&cloud, 5);
646        assert!(result.is_err());
647    }
648
649    #[test]
650    fn test_alpha_complex_simple() {
651        let points = vec![
652            Point3::new(0.0, 0.0, 0.0),
653            Point3::new(1.0, 0.0, 0.0),
654            Point3::new(0.5, 1.0, 0.0),
655        ];
656        let cloud = PointCloud::from_points(points);
657
658        let result = alpha_complex_reconstruction(&cloud, 2.0);
659
660        match result {
661            Ok(mesh) => {
662                assert!(!mesh.is_empty());
663                assert!(mesh.vertex_count() <= 3);
664            }
665            Err(_) => {
666                // May fail on simple cases - that's acceptable
667            }
668        }
669    }
670}