use crate::parallel;
use std::collections::HashMap;
use threecrate_core::{Error, NormalPoint3f, Point3f, PointCloud, Result, TriangleMesh};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Algorithm {
Poisson,
BallPivoting,
Delaunay,
MovingLeastSquares,
MarchingCubes,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum QualityLevel {
Fast,
Balanced,
HighQuality,
MaxQuality,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum UseCase {
General,
Prototyping,
Engineering,
Organic,
NoisyData,
Sparse,
Dense,
}
#[derive(Debug, Clone)]
pub struct DataCharacteristics {
pub point_count: usize,
pub has_normals: bool,
pub density_uniformity: f32,
pub noise_level: f32,
pub avg_neighbor_distance: f32,
pub bounding_box: (Point3f, Point3f),
pub is_closed_surface: bool,
pub surface_complexity: f32,
pub distribution_type: DistributionType,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DistributionType {
Planar,
Spherical,
Cylindrical,
Arbitrary,
}
#[derive(Debug, Clone)]
pub struct PipelineConfig {
pub quality: QualityLevel,
pub use_case: UseCase,
pub preferred_algorithm: Option<Algorithm>,
pub fallback_algorithms: Vec<Algorithm>,
pub max_processing_time: Option<f32>,
pub enable_parallel: bool,
pub validate_output: bool,
pub auto_repair: bool,
}
impl Default for PipelineConfig {
fn default() -> Self {
Self {
quality: QualityLevel::Balanced,
use_case: UseCase::General,
preferred_algorithm: None,
fallback_algorithms: vec![
Algorithm::Delaunay,
Algorithm::BallPivoting,
Algorithm::MovingLeastSquares,
],
max_processing_time: None,
enable_parallel: true,
validate_output: true,
auto_repair: false,
}
}
}
#[derive(Debug)]
pub struct ReconstructionResult {
pub mesh: TriangleMesh,
pub algorithm_used: Algorithm,
pub processing_time: f32,
pub quality_metrics: QualityMetrics,
pub data_characteristics: DataCharacteristics,
}
#[derive(Debug, Clone)]
pub struct QualityMetrics {
pub vertex_count: usize,
pub triangle_count: usize,
pub avg_triangle_quality: f32,
pub watertightness: f32,
pub smoothness: f32,
pub geometric_accuracy: f32,
}
pub struct ReconstructionPipeline {
config: PipelineConfig,
}
impl ReconstructionPipeline {
pub fn new(config: PipelineConfig) -> Self {
Self { config }
}
pub fn default() -> Self {
Self::new(PipelineConfig::default())
}
pub fn for_use_case(use_case: UseCase) -> Self {
let mut config = PipelineConfig::default();
config.use_case = use_case;
match use_case {
UseCase::Prototyping => {
config.quality = QualityLevel::Fast;
config.fallback_algorithms = vec![Algorithm::Delaunay, Algorithm::BallPivoting];
}
UseCase::Engineering => {
config.quality = QualityLevel::HighQuality;
config.validate_output = true;
config.auto_repair = true;
}
UseCase::Organic => {
config.quality = QualityLevel::HighQuality;
config.fallback_algorithms = vec![
Algorithm::MovingLeastSquares,
Algorithm::Poisson,
Algorithm::BallPivoting,
];
}
UseCase::NoisyData => {
config.fallback_algorithms =
vec![Algorithm::MovingLeastSquares, Algorithm::Delaunay];
}
UseCase::Sparse => {
config.fallback_algorithms =
vec![Algorithm::Delaunay, Algorithm::MovingLeastSquares];
}
UseCase::Dense => {
config.fallback_algorithms = vec![
Algorithm::Poisson,
Algorithm::BallPivoting,
Algorithm::MarchingCubes,
];
}
UseCase::General => {
}
}
Self::new(config)
}
pub fn analyze_data(&self, cloud: &PointCloud<Point3f>) -> Result<DataCharacteristics> {
if cloud.is_empty() {
return Err(Error::InvalidData("Point cloud is empty".to_string()));
}
let point_count = cloud.points.len();
let (bounds_min, bounds_max) = parallel::point_cloud::parallel_bounding_box(&cloud.points)
.unwrap_or((Point3f::origin(), Point3f::new(1.0, 1.0, 1.0)));
let sample_size = point_count.min(1000);
let step = (point_count.max(1) / sample_size.max(1)).max(1);
let sample_points: Vec<Point3f> = cloud.points.iter().step_by(step).cloned().collect();
let neighbor_distances = self.compute_neighbor_distances(&sample_points)?;
let avg_neighbor_distance =
neighbor_distances.iter().sum::<f32>() / neighbor_distances.len() as f32;
let density_uniformity = self.estimate_density_uniformity(&neighbor_distances);
let distance_variance = self.compute_variance(&neighbor_distances);
let noise_level = (distance_variance / avg_neighbor_distance.powi(2)).min(1.0);
let distribution_type = self.classify_distribution(&cloud.points, &bounds_min, &bounds_max);
let surface_complexity = self.estimate_surface_complexity(&sample_points);
let is_closed_surface = self.estimate_surface_closure(&sample_points);
Ok(DataCharacteristics {
point_count,
has_normals: false, density_uniformity,
noise_level,
avg_neighbor_distance,
bounding_box: (bounds_min, bounds_max),
is_closed_surface,
surface_complexity,
distribution_type,
})
}
pub fn analyze_data_with_normals(
&self,
cloud: &PointCloud<NormalPoint3f>,
) -> Result<DataCharacteristics> {
let point_cloud: PointCloud<Point3f> =
PointCloud::from_points(cloud.points.iter().map(|p| p.position).collect());
let mut characteristics = self.analyze_data(&point_cloud)?;
characteristics.has_normals = true;
Ok(characteristics)
}
pub fn select_algorithm(&self, characteristics: &DataCharacteristics) -> Algorithm {
if let Some(preferred) = self.config.preferred_algorithm {
return preferred;
}
let mut scores = HashMap::new();
scores.insert(Algorithm::Delaunay, 0.5);
scores.insert(Algorithm::BallPivoting, 0.5);
scores.insert(Algorithm::MovingLeastSquares, 0.5);
scores.insert(Algorithm::MarchingCubes, 0.5);
scores.insert(
Algorithm::Poisson,
if characteristics.has_normals {
0.5
} else {
0.0
},
);
match characteristics.point_count {
0..=100 => {
*scores.get_mut(&Algorithm::Delaunay).unwrap() += 0.3;
*scores.get_mut(&Algorithm::MovingLeastSquares).unwrap() += 0.2;
}
101..=1000 => {
*scores.get_mut(&Algorithm::BallPivoting).unwrap() += 0.2;
*scores.get_mut(&Algorithm::Delaunay).unwrap() += 0.3;
}
1001..=10000 => {
*scores.get_mut(&Algorithm::BallPivoting).unwrap() += 0.3;
if characteristics.has_normals {
*scores.get_mut(&Algorithm::Poisson).unwrap() += 0.3;
}
}
_ => {
if characteristics.has_normals {
*scores.get_mut(&Algorithm::Poisson).unwrap() += 0.4;
}
*scores.get_mut(&Algorithm::MarchingCubes).unwrap() += 0.2;
}
}
if characteristics.density_uniformity > 0.7 {
*scores.get_mut(&Algorithm::BallPivoting).unwrap() += 0.2;
if characteristics.has_normals {
*scores.get_mut(&Algorithm::Poisson).unwrap() += 0.2;
}
} else {
*scores.get_mut(&Algorithm::MovingLeastSquares).unwrap() += 0.3;
*scores.get_mut(&Algorithm::Delaunay).unwrap() += 0.2;
}
if characteristics.noise_level > 0.3 {
*scores.get_mut(&Algorithm::MovingLeastSquares).unwrap() += 0.3;
*scores.get_mut(&Algorithm::Delaunay).unwrap() += 0.1;
*scores.get_mut(&Algorithm::BallPivoting).unwrap() -= 0.2;
}
if characteristics.surface_complexity > 0.7 {
if characteristics.has_normals {
*scores.get_mut(&Algorithm::Poisson).unwrap() += 0.3;
}
*scores.get_mut(&Algorithm::MovingLeastSquares).unwrap() += 0.2;
}
match characteristics.distribution_type {
DistributionType::Planar => {
*scores.get_mut(&Algorithm::Delaunay).unwrap() += 0.3;
}
DistributionType::Spherical | DistributionType::Cylindrical => {
*scores.get_mut(&Algorithm::BallPivoting).unwrap() += 0.2;
*scores.get_mut(&Algorithm::MarchingCubes).unwrap() += 0.3;
}
DistributionType::Arbitrary => {
}
}
match self.config.quality {
QualityLevel::Fast => {
*scores.get_mut(&Algorithm::Delaunay).unwrap() += 0.3;
}
QualityLevel::Balanced => {
*scores.get_mut(&Algorithm::BallPivoting).unwrap() += 0.2;
}
QualityLevel::HighQuality | QualityLevel::MaxQuality => {
if characteristics.has_normals {
*scores.get_mut(&Algorithm::Poisson).unwrap() += 0.3;
}
*scores.get_mut(&Algorithm::MovingLeastSquares).unwrap() += 0.2;
}
}
match self.config.use_case {
UseCase::Engineering => {
*scores.get_mut(&Algorithm::Delaunay).unwrap() += 0.2;
if characteristics.has_normals {
*scores.get_mut(&Algorithm::Poisson).unwrap() += 0.2;
}
}
UseCase::Organic => {
*scores.get_mut(&Algorithm::MovingLeastSquares).unwrap() += 0.3;
if characteristics.has_normals {
*scores.get_mut(&Algorithm::Poisson).unwrap() += 0.2;
}
}
UseCase::Prototyping => {
*scores.get_mut(&Algorithm::Delaunay).unwrap() += 0.4;
}
_ => {}
}
scores
.into_iter()
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
.map(|(algo, _)| algo)
.unwrap_or(Algorithm::Delaunay)
}
pub fn reconstruct(&self, cloud: &PointCloud<Point3f>) -> Result<ReconstructionResult> {
let start_time = std::time::Instant::now();
let characteristics = self.analyze_data(cloud)?;
let selected_algorithm = self.select_algorithm(&characteristics);
let mesh = match self.try_algorithm(cloud, selected_algorithm) {
Ok(mesh) => mesh,
Err(_) => {
let mut last_error = Error::Algorithm("No algorithms succeeded".to_string());
for &fallback_algo in &self.config.fallback_algorithms {
if fallback_algo != selected_algorithm {
match self.try_algorithm(cloud, fallback_algo) {
Ok(mesh) => {
let processing_time = start_time.elapsed().as_secs_f32();
let quality_metrics =
self.compute_quality_metrics(&mesh, &characteristics);
return Ok(ReconstructionResult {
mesh,
algorithm_used: fallback_algo,
processing_time,
quality_metrics,
data_characteristics: characteristics,
});
}
Err(e) => last_error = e,
}
}
}
return Err(last_error);
}
};
let processing_time = start_time.elapsed().as_secs_f32();
let quality_metrics = self.compute_quality_metrics(&mesh, &characteristics);
Ok(ReconstructionResult {
mesh,
algorithm_used: selected_algorithm,
processing_time,
quality_metrics,
data_characteristics: characteristics,
})
}
pub fn reconstruct_with_normals(
&self,
cloud: &PointCloud<NormalPoint3f>,
) -> Result<ReconstructionResult> {
let start_time = std::time::Instant::now();
let characteristics = self.analyze_data_with_normals(cloud)?;
let selected_algorithm = self.select_algorithm(&characteristics);
let mesh = match self.try_algorithm_with_normals(cloud, selected_algorithm) {
Ok(mesh) => mesh,
Err(_) => {
let mut last_error = Error::Algorithm("No algorithms succeeded".to_string());
for &fallback_algo in &self.config.fallback_algorithms {
if fallback_algo != selected_algorithm {
match self.try_algorithm_with_normals(cloud, fallback_algo) {
Ok(mesh) => {
let processing_time = start_time.elapsed().as_secs_f32();
let quality_metrics =
self.compute_quality_metrics(&mesh, &characteristics);
return Ok(ReconstructionResult {
mesh,
algorithm_used: fallback_algo,
processing_time,
quality_metrics,
data_characteristics: characteristics,
});
}
Err(e) => last_error = e,
}
}
}
return Err(last_error);
}
};
let processing_time = start_time.elapsed().as_secs_f32();
let quality_metrics = self.compute_quality_metrics(&mesh, &characteristics);
Ok(ReconstructionResult {
mesh,
algorithm_used: selected_algorithm,
processing_time,
quality_metrics,
data_characteristics: characteristics,
})
}
fn compute_neighbor_distances(&self, points: &[Point3f]) -> Result<Vec<f32>> {
if points.len() < 2 {
return Ok(vec![1.0]); }
let distances = parallel::parallel_map(points, |point| {
let mut min_dist = f32::INFINITY;
for other_point in points {
if point != other_point {
let dist = (point - other_point).magnitude();
if dist < min_dist {
min_dist = dist;
}
}
}
min_dist
});
let finite_distances: Vec<f32> = distances
.into_iter()
.filter(|&d| d.is_finite() && d > 0.0)
.collect();
if finite_distances.is_empty() {
Ok(vec![1.0])
} else {
Ok(finite_distances)
}
}
fn estimate_density_uniformity(&self, distances: &[f32]) -> f32 {
if distances.len() < 2 {
return 1.0;
}
let mean = distances.iter().sum::<f32>() / distances.len() as f32;
let variance = self.compute_variance(distances);
let cv = if mean > 0.0 {
variance.sqrt() / mean
} else {
0.0
};
(1.0 / (1.0 + cv)).min(1.0)
}
fn compute_variance(&self, values: &[f32]) -> f32 {
if values.len() < 2 {
return 0.0;
}
let mean = values.iter().sum::<f32>() / values.len() as f32;
let variance = values.iter().map(|x| (x - mean).powi(2)).sum::<f32>() / values.len() as f32;
variance
}
fn classify_distribution(
&self,
_points: &[Point3f],
bounds_min: &Point3f,
bounds_max: &Point3f,
) -> DistributionType {
let extents = [
bounds_max.x - bounds_min.x,
bounds_max.y - bounds_min.y,
bounds_max.z - bounds_min.z,
];
let max_extent = extents.iter().fold(0.0f32, |acc, &x| acc.max(x));
let min_extent = extents.iter().fold(f32::INFINITY, |acc, &x| acc.min(x));
if max_extent < 1e-6 {
return DistributionType::Arbitrary;
}
if min_extent < max_extent * 0.1 {
return DistributionType::Planar;
}
let extent_ratios = [
extents[0] / max_extent,
extents[1] / max_extent,
extents[2] / max_extent,
];
if extent_ratios.iter().all(|&r| r > 0.7) {
DistributionType::Spherical
} else if extent_ratios.iter().filter(|&&r| r > 0.7).count() == 2 {
DistributionType::Cylindrical
} else {
DistributionType::Arbitrary
}
}
fn estimate_surface_complexity(&self, points: &[Point3f]) -> f32 {
if points.len() < 10 {
return 0.5; }
let sample_size = points.len().min(100);
let step = (points.len().max(1) / sample_size.max(1)).max(1);
let sample_points: Vec<Point3f> = points.iter().step_by(step).cloned().collect();
let mut curvature_variations = Vec::new();
for (i, point) in sample_points.iter().enumerate() {
let mut neighbors = Vec::new();
for (j, other_point) in sample_points.iter().enumerate() {
if i != j {
let dist = (point - other_point).magnitude();
if dist < 0.1 {
neighbors.push(*other_point);
}
}
}
if neighbors.len() >= 3 {
let variation = self.estimate_local_curvature_variation(point, &neighbors);
curvature_variations.push(variation);
}
}
if curvature_variations.is_empty() {
0.5
} else {
let avg_variation =
curvature_variations.iter().sum::<f32>() / curvature_variations.len() as f32;
avg_variation.min(1.0)
}
}
fn estimate_local_curvature_variation(&self, center: &Point3f, neighbors: &[Point3f]) -> f32 {
if neighbors.len() < 3 {
return 0.0;
}
let mut angles = Vec::new();
for i in 0..neighbors.len() {
let v1 = (neighbors[i] - *center).normalize();
let v2 = (neighbors[(i + 1) % neighbors.len()] - *center).normalize();
let angle = v1.dot(&v2).clamp(-1.0, 1.0).acos();
angles.push(angle);
}
self.compute_variance(&angles) / std::f32::consts::PI
}
fn estimate_surface_closure(&self, points: &[Point3f]) -> bool {
if points.len() < 50 {
return false; }
let centroid = points.iter().fold(Point3f::origin(), |acc, p| {
Point3f::from(acc.coords + p.coords)
});
let centroid = Point3f::from(centroid.coords / points.len() as f32);
let distances: Vec<f32> = points.iter().map(|p| (p - centroid).magnitude()).collect();
let mean_dist = distances.iter().sum::<f32>() / distances.len() as f32;
let variance = self.compute_variance(&distances);
let cv = if mean_dist > 0.0 {
variance.sqrt() / mean_dist
} else {
1.0
};
cv < 0.3
}
fn try_algorithm(
&self,
cloud: &PointCloud<Point3f>,
algorithm: Algorithm,
) -> Result<TriangleMesh> {
match algorithm {
Algorithm::Delaunay => crate::delaunay::delaunay_triangulation_auto(cloud),
Algorithm::BallPivoting => {
let radius = crate::ball_pivoting::estimate_optimal_radius(cloud, 0.5)?;
crate::ball_pivoting::ball_pivoting_reconstruction(cloud, radius)
}
Algorithm::MovingLeastSquares => {
crate::moving_least_squares::moving_least_squares_auto(cloud)
}
Algorithm::MarchingCubes => {
let mls = crate::moving_least_squares::MLSSurface::new(
cloud,
crate::moving_least_squares::MLSConfig::default(),
)?;
mls.extract_mesh()
}
Algorithm::Poisson => {
Err(Error::InvalidData(
"Poisson reconstruction requires normals".to_string(),
))
}
}
}
fn try_algorithm_with_normals(
&self,
cloud: &PointCloud<NormalPoint3f>,
algorithm: Algorithm,
) -> Result<TriangleMesh> {
match algorithm {
Algorithm::Poisson => crate::poisson::poisson_reconstruction_default(cloud),
Algorithm::BallPivoting => {
let radius = {
let point_cloud: PointCloud<Point3f> =
PointCloud::from_points(cloud.points.iter().map(|p| p.position).collect());
crate::ball_pivoting::estimate_optimal_radius(&point_cloud, 0.5)?
};
crate::ball_pivoting::ball_pivoting_from_normals(cloud, radius)
}
Algorithm::Delaunay => {
let point_cloud: PointCloud<Point3f> =
PointCloud::from_points(cloud.points.iter().map(|p| p.position).collect());
crate::delaunay::delaunay_triangulation_auto(&point_cloud)
}
Algorithm::MovingLeastSquares => {
crate::moving_least_squares::moving_least_squares_from_normals(cloud)
}
Algorithm::MarchingCubes => {
let mls = crate::moving_least_squares::MLSSurface::from_normals(
cloud,
crate::moving_least_squares::MLSConfig::default(),
)?;
mls.extract_mesh()
}
}
}
fn compute_quality_metrics(
&self,
mesh: &TriangleMesh,
characteristics: &DataCharacteristics,
) -> QualityMetrics {
let vertex_count = mesh.vertex_count();
let triangle_count = mesh.face_count();
let avg_triangle_quality = 0.75; let watertightness = if characteristics.is_closed_surface {
0.8
} else {
0.6
};
let smoothness = 1.0 - characteristics.noise_level;
let geometric_accuracy = 0.8;
QualityMetrics {
vertex_count,
triangle_count,
avg_triangle_quality,
watertightness,
smoothness,
geometric_accuracy,
}
}
}
pub fn auto_reconstruct(cloud: &PointCloud<Point3f>) -> Result<TriangleMesh> {
let pipeline = ReconstructionPipeline::default();
Ok(pipeline.reconstruct(cloud)?.mesh)
}
pub fn auto_reconstruct_with_normals(cloud: &PointCloud<NormalPoint3f>) -> Result<TriangleMesh> {
let pipeline = ReconstructionPipeline::default();
Ok(pipeline.reconstruct_with_normals(cloud)?.mesh)
}
pub fn auto_reconstruct_with_quality(
cloud: &PointCloud<Point3f>,
quality: QualityLevel,
) -> Result<TriangleMesh> {
let mut config = PipelineConfig::default();
config.quality = quality;
let pipeline = ReconstructionPipeline::new(config);
Ok(pipeline.reconstruct(cloud)?.mesh)
}
pub fn auto_reconstruct_for_use_case(
cloud: &PointCloud<Point3f>,
use_case: UseCase,
) -> Result<TriangleMesh> {
let pipeline = ReconstructionPipeline::for_use_case(use_case);
Ok(pipeline.reconstruct(cloud)?.mesh)
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::Point3;
#[test]
fn test_pipeline_config_default() {
let config = PipelineConfig::default();
assert_eq!(config.quality, QualityLevel::Balanced);
assert_eq!(config.use_case, UseCase::General);
assert!(config.enable_parallel);
assert!(config.validate_output);
}
#[test]
fn test_pipeline_for_use_case() {
let pipeline = ReconstructionPipeline::for_use_case(UseCase::Prototyping);
assert_eq!(pipeline.config.quality, QualityLevel::Fast);
assert_eq!(pipeline.config.use_case, UseCase::Prototyping);
}
#[test]
fn test_data_analysis_empty_cloud() {
let pipeline = ReconstructionPipeline::default();
let cloud = PointCloud::new();
let result = pipeline.analyze_data(&cloud);
assert!(result.is_err());
}
#[test]
fn test_data_analysis_simple() {
let pipeline = ReconstructionPipeline::default();
let points = vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.5, 1.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
];
let cloud = PointCloud::from_points(points);
let characteristics = pipeline.analyze_data(&cloud).unwrap();
assert_eq!(characteristics.point_count, 4);
assert!(!characteristics.has_normals);
assert_eq!(characteristics.distribution_type, DistributionType::Planar);
}
#[test]
fn test_algorithm_selection_sparse_data() {
let pipeline = ReconstructionPipeline::default();
let characteristics = DataCharacteristics {
point_count: 50,
has_normals: false,
density_uniformity: 0.5,
noise_level: 0.1,
avg_neighbor_distance: 0.1,
bounding_box: (Point3f::origin(), Point3f::new(1.0, 1.0, 1.0)),
is_closed_surface: false,
surface_complexity: 0.3,
distribution_type: DistributionType::Planar,
};
let algorithm = pipeline.select_algorithm(&characteristics);
assert!(matches!(
algorithm,
Algorithm::Delaunay | Algorithm::MovingLeastSquares
));
}
#[test]
fn test_algorithm_selection_dense_with_normals() {
let pipeline = ReconstructionPipeline::default();
let characteristics = DataCharacteristics {
point_count: 5000,
has_normals: true,
density_uniformity: 0.8,
noise_level: 0.1,
avg_neighbor_distance: 0.05,
bounding_box: (Point3f::origin(), Point3f::new(1.0, 1.0, 1.0)),
is_closed_surface: true,
surface_complexity: 0.7,
distribution_type: DistributionType::Spherical,
};
let algorithm = pipeline.select_algorithm(&characteristics);
assert!(matches!(
algorithm,
Algorithm::Poisson | Algorithm::BallPivoting
));
}
#[test]
fn test_auto_reconstruct_simple() {
let points = vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.5, 1.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
];
let cloud = PointCloud::from_points(points);
let result = auto_reconstruct(&cloud);
match result {
Ok(mesh) => {
assert!(!mesh.is_empty());
}
Err(_) => {
println!(
"Auto reconstruction failed on simple test data (expected for some algorithms)"
);
}
}
}
#[test]
fn test_quality_levels() {
let quality_levels = [
QualityLevel::Fast,
QualityLevel::Balanced,
QualityLevel::HighQuality,
QualityLevel::MaxQuality,
];
for quality in &quality_levels {
let mut config = PipelineConfig::default();
config.quality = *quality;
let _pipeline = ReconstructionPipeline::new(config);
}
}
#[test]
fn test_use_cases() {
let use_cases = [
UseCase::General,
UseCase::Prototyping,
UseCase::Engineering,
UseCase::Organic,
UseCase::NoisyData,
UseCase::Sparse,
UseCase::Dense,
];
for use_case in &use_cases {
let pipeline = ReconstructionPipeline::for_use_case(*use_case);
assert_eq!(pipeline.config.use_case, *use_case);
}
}
#[test]
fn test_distribution_classification() {
let pipeline = ReconstructionPipeline::default();
let planar_points = vec![
Point3f::new(0.0, 0.0, 0.0),
Point3f::new(1.0, 0.0, 0.0),
Point3f::new(0.0, 1.0, 0.0),
Point3f::new(1.0, 1.0, 0.0),
];
let min_bounds = Point3f::new(0.0, 0.0, 0.0);
let max_bounds = Point3f::new(1.0, 1.0, 0.0);
let distribution = pipeline.classify_distribution(&planar_points, &min_bounds, &max_bounds);
assert_eq!(distribution, DistributionType::Planar);
let min_bounds_sphere = Point3f::new(-1.0, -1.0, -1.0);
let max_bounds_sphere = Point3f::new(1.0, 1.0, 1.0);
let distribution =
pipeline.classify_distribution(&planar_points, &min_bounds_sphere, &max_bounds_sphere);
assert_eq!(distribution, DistributionType::Spherical);
}
}