threecrate_reconstruction/
alpha_shape.rs1use threecrate_core::{Error, NormalPoint3f, Point3f, PointCloud, Result, TriangleMesh};
6
7#[derive(Debug, Clone)]
9pub struct AlphaComplexConfig {
10 pub alpha: f32,
12 pub mode: AlphaMode,
14 pub min_triangle_area: f32,
16 pub fast_mode: bool,
18}
19
20#[derive(Debug, Clone, PartialEq)]
22pub enum AlphaMode {
23 BoundaryOnly,
25 Full,
27 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#[derive(Debug, Clone)]
44struct AlphaSimplex {
45 vertices: Vec<usize>,
47 circumradius_sq: f32,
49 dimension: usize,
51 is_boundary: bool,
53 measure: f32,
55}
56
57impl AlphaSimplex {
58 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, measure: area,
73 }
74 }
75
76 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 if a_sq < 1e-20 || b_sq < 1e-20 || c_sq < 1e-20 {
84 return f32::INFINITY;
85 }
86
87 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 (a_sq * b_sq * c_sq) / (16.0 * area_sq)
99 }
100
101 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 fn is_alpha_valid(&self, alpha: f32, min_measure: f32) -> bool {
111 self.circumradius_sq <= alpha * alpha && self.measure >= min_measure
112 }
113}
114
115pub struct AlphaComplex {
117 points: Vec<Point3f>,
118 simplices: Vec<AlphaSimplex>,
119 config: AlphaComplexConfig,
120}
121
122impl AlphaComplex {
123 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 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 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 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 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 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 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 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 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 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 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 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 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 fn is_triangle_geometrically_valid(&self, triangle: &AlphaSimplex) -> Result<bool> {
307 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 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 let quality = (4.0 * triangle.measure) / (perimeter * perimeter);
336
337 Ok(quality > 0.01) }
340
341 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 let mut filtered_simplices = Vec::new();
357 for simplex in &self.simplices {
358 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 fn compute_local_alpha(&self, simplex: &AlphaSimplex) -> Result<f32> {
374 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 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 distances.sort_by(|a, b| a.partial_cmp(b).unwrap());
396 let median_dist = distances[distances.len() / 2];
397
398 Ok(median_dist * 1.8)
400 }
401
402 pub fn extract_surface(&mut self) -> Result<TriangleMesh> {
404 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 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 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 let mesh = TriangleMesh::from_vertices_and_faces(self.points.clone(), faces);
436
437 Ok(mesh)
438 }
439
440 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#[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
473pub 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
487pub 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
496pub 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
507pub 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 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 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 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); }
576
577 distances.sort_by(|a, b| a.partial_cmp(b).unwrap());
579 let median_dist = distances[distances.len() / 2];
580
581 Ok(median_dist * 1.8)
583}
584
585#[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); }
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 }
668 }
669 }
670}