use crate::parallel;
use threecrate_core::{Error, NormalPoint3f, Point3f, PointCloud, Result, TriangleMesh};
#[derive(Debug, Clone)]
pub struct PoissonConfig {
pub iso_level: f32,
pub depth: u32,
pub prune_depth: u32,
pub full_depth: u32,
pub scale: f32,
pub samples_per_node: f32,
pub confidence: bool,
pub output_density: bool,
pub cg_depth: u32,
}
impl Default for PoissonConfig {
fn default() -> Self {
Self {
iso_level: 0.0,
depth: 8,
prune_depth: 5,
full_depth: 10,
scale: 1.1,
samples_per_node: 1.0,
confidence: false,
output_density: false,
cg_depth: 8,
}
}
}
pub fn poisson_reconstruction(
cloud: &PointCloud<NormalPoint3f>,
config: &PoissonConfig,
) -> Result<TriangleMesh> {
if cloud.is_empty() {
return Err(Error::InvalidData("Point cloud is empty".to_string()));
}
if cloud.points.len() < 10 {
return Err(Error::InvalidData(
"Point cloud too small for Poisson reconstruction (minimum 10 points)".to_string(),
));
}
let points: Vec<nalgebra_compat::Point3<f64>> = parallel::parallel_map(&cloud.points, |p| {
nalgebra_compat::Point3::new(
p.position.x as f64,
p.position.y as f64,
p.position.z as f64,
)
});
let normals: Vec<nalgebra_compat::Vector3<f64>> = parallel::parallel_map(&cloud.points, |p| {
nalgebra_compat::Vector3::new(p.normal.x as f64, p.normal.y as f64, p.normal.z as f64)
});
for (i, normal) in normals.iter().enumerate() {
let magnitude = normal.magnitude();
if magnitude < 1e-6 || (magnitude - 1.0).abs() > 0.1 {
return Err(Error::InvalidData(format!(
"Invalid normal at point {}: magnitude {}",
i, magnitude
)));
}
}
let depth = std::cmp::min(config.depth as usize, 6); let cg_depth = std::cmp::min(config.cg_depth as usize, 8);
let poisson = poisson_reconstruction::PoissonReconstruction::from_points_and_normals(
&points[..],
&normals[..],
config.scale as f64, depth, cg_depth, 0, );
let mesh_buffers = poisson.reconstruct_mesh_buffers();
if mesh_buffers.vertices().is_empty() {
return Err(Error::Algorithm(
"Poisson reconstruction generated no vertices".to_string(),
));
}
let vertices: Vec<Point3f> = parallel::parallel_map(mesh_buffers.vertices(), |v| {
Point3f::new(v.x as f32, v.y as f32, v.z as f32)
});
let indices = mesh_buffers.indices();
if indices.len() % 3 != 0 {
return Err(Error::Algorithm(
"Invalid triangle indices from Poisson reconstruction".to_string(),
));
}
let faces: Vec<[usize; 3]> =
parallel::parallel_map(&indices.chunks(3).collect::<Vec<_>>(), |chunk| {
[chunk[0] as usize, chunk[1] as usize, chunk[2] as usize]
});
if faces.is_empty() {
return Err(Error::Algorithm(
"Poisson reconstruction generated no triangles".to_string(),
));
}
let mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
Ok(mesh)
}
pub fn poisson_reconstruction_default(cloud: &PointCloud<NormalPoint3f>) -> Result<TriangleMesh> {
poisson_reconstruction(cloud, &PoissonConfig::default())
}
pub fn poisson_reconstruction_with_normals(
cloud: &PointCloud<Point3f>,
k: usize,
config: &PoissonConfig,
) -> Result<TriangleMesh> {
let normals_cloud = threecrate_algorithms::estimate_normals(cloud, k)?;
poisson_reconstruction(&normals_cloud, config)
}
#[deprecated(
note = "Use poisson_reconstruction_default or poisson_reconstruction_with_normals instead"
)]
pub fn poisson_reconstruction_legacy(cloud: &PointCloud<Point3f>) -> Result<TriangleMesh> {
poisson_reconstruction_with_normals(cloud, 20, &PoissonConfig::default())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_poisson_config_default() {
let config = PoissonConfig::default();
assert_eq!(config.depth, 8);
assert_eq!(config.iso_level, 0.0);
assert!(!config.confidence);
}
#[test]
fn test_poisson_reconstruction_empty_cloud() {
let cloud = PointCloud::<NormalPoint3f>::new();
let result = poisson_reconstruction(&cloud, &PoissonConfig::default());
assert!(result.is_err());
}
#[test]
fn test_poisson_reconstruction_api_fixed() {
let points = vec![
NormalPoint3f {
position: Point3f::new(0.0, 0.0, 0.0),
normal: nalgebra::Vector3::new(0.0, 0.0, 1.0),
},
NormalPoint3f {
position: Point3f::new(1.0, 0.0, 0.0),
normal: nalgebra::Vector3::new(0.0, 0.0, 1.0),
},
NormalPoint3f {
position: Point3f::new(0.0, 1.0, 0.0),
normal: nalgebra::Vector3::new(0.0, 0.0, 1.0),
},
];
let cloud = PointCloud::from_points(points);
let config = PoissonConfig::default();
let result = poisson_reconstruction(&cloud, &config);
match result {
Ok(_) => {
println!("Poisson reconstruction API is working!");
}
Err(e) => {
let error_msg = e.to_string();
assert!(
!error_msg.contains("temporarily disabled"),
"API should no longer be disabled: {}",
error_msg
);
if error_msg.contains("too small for Poisson reconstruction") {
println!(
"✓ Poisson reconstruction API fixed and working with proper validation"
);
} else {
println!("Poisson reconstruction returned other error: {}", error_msg);
}
}
}
}
}