threecrate_reconstruction/
poisson.rs1use crate::parallel;
4use threecrate_core::{Error, NormalPoint3f, Point3f, PointCloud, Result, TriangleMesh};
5
6#[derive(Debug, Clone)]
8pub struct PoissonConfig {
9 pub iso_level: f32,
11 pub depth: u32,
13 pub prune_depth: u32,
15 pub full_depth: u32,
17 pub scale: f32,
19 pub samples_per_node: f32,
21 pub confidence: bool,
23 pub output_density: bool,
25 pub cg_depth: u32,
27}
28
29impl Default for PoissonConfig {
30 fn default() -> Self {
31 Self {
32 iso_level: 0.0,
33 depth: 8,
34 prune_depth: 5,
35 full_depth: 10,
36 scale: 1.1,
37 samples_per_node: 1.0,
38 confidence: false,
39 output_density: false,
40 cg_depth: 8,
41 }
42 }
43}
44
45pub fn poisson_reconstruction(
54 cloud: &PointCloud<NormalPoint3f>,
55 config: &PoissonConfig,
56) -> Result<TriangleMesh> {
57 if cloud.is_empty() {
58 return Err(Error::InvalidData("Point cloud is empty".to_string()));
59 }
60
61 if cloud.points.len() < 10 {
63 return Err(Error::InvalidData(
64 "Point cloud too small for Poisson reconstruction (minimum 10 points)".to_string(),
65 ));
66 }
67
68 let points: Vec<nalgebra_compat::Point3<f64>> = parallel::parallel_map(&cloud.points, |p| {
71 nalgebra_compat::Point3::new(
72 p.position.x as f64,
73 p.position.y as f64,
74 p.position.z as f64,
75 )
76 });
77
78 let normals: Vec<nalgebra_compat::Vector3<f64>> = parallel::parallel_map(&cloud.points, |p| {
79 nalgebra_compat::Vector3::new(p.normal.x as f64, p.normal.y as f64, p.normal.z as f64)
80 });
81
82 for (i, normal) in normals.iter().enumerate() {
84 let magnitude = normal.magnitude();
85 if magnitude < 1e-6 || (magnitude - 1.0).abs() > 0.1 {
86 return Err(Error::InvalidData(format!(
87 "Invalid normal at point {}: magnitude {}",
88 i, magnitude
89 )));
90 }
91 }
92
93 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(
99 &points[..],
100 &normals[..],
101 config.scale as f64, depth, cg_depth, 0, );
106
107 let mesh_buffers = poisson.reconstruct_mesh_buffers();
109
110 if mesh_buffers.vertices().is_empty() {
112 return Err(Error::Algorithm(
113 "Poisson reconstruction generated no vertices".to_string(),
114 ));
115 }
116
117 let vertices: Vec<Point3f> = parallel::parallel_map(mesh_buffers.vertices(), |v| {
119 Point3f::new(v.x as f32, v.y as f32, v.z as f32)
120 });
121
122 let indices = mesh_buffers.indices();
124 if indices.len() % 3 != 0 {
125 return Err(Error::Algorithm(
126 "Invalid triangle indices from Poisson reconstruction".to_string(),
127 ));
128 }
129
130 let faces: Vec<[usize; 3]> =
131 parallel::parallel_map(&indices.chunks(3).collect::<Vec<_>>(), |chunk| {
132 [chunk[0] as usize, chunk[1] as usize, chunk[2] as usize]
133 });
134
135 if faces.is_empty() {
136 return Err(Error::Algorithm(
137 "Poisson reconstruction generated no triangles".to_string(),
138 ));
139 }
140
141 let mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
143
144 Ok(mesh)
145}
146
147pub fn poisson_reconstruction_default(cloud: &PointCloud<NormalPoint3f>) -> Result<TriangleMesh> {
155 poisson_reconstruction(cloud, &PoissonConfig::default())
156}
157
158pub fn poisson_reconstruction_with_normals(
168 cloud: &PointCloud<Point3f>,
169 k: usize,
170 config: &PoissonConfig,
171) -> Result<TriangleMesh> {
172 let normals_cloud = threecrate_algorithms::estimate_normals(cloud, k)?;
174
175 poisson_reconstruction(&normals_cloud, config)
177}
178
179#[deprecated(
181 note = "Use poisson_reconstruction_default or poisson_reconstruction_with_normals instead"
182)]
183pub fn poisson_reconstruction_legacy(cloud: &PointCloud<Point3f>) -> Result<TriangleMesh> {
184 poisson_reconstruction_with_normals(cloud, 20, &PoissonConfig::default())
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_poisson_config_default() {
193 let config = PoissonConfig::default();
194 assert_eq!(config.depth, 8);
195 assert_eq!(config.iso_level, 0.0);
196 assert!(!config.confidence);
197 }
198
199 #[test]
200 fn test_poisson_reconstruction_empty_cloud() {
201 let cloud = PointCloud::<NormalPoint3f>::new();
202 let result = poisson_reconstruction(&cloud, &PoissonConfig::default());
203 assert!(result.is_err());
204 }
205
206 #[test]
207 fn test_poisson_reconstruction_api_fixed() {
208 let points = vec![
211 NormalPoint3f {
212 position: Point3f::new(0.0, 0.0, 0.0),
213 normal: nalgebra::Vector3::new(0.0, 0.0, 1.0),
214 },
215 NormalPoint3f {
216 position: Point3f::new(1.0, 0.0, 0.0),
217 normal: nalgebra::Vector3::new(0.0, 0.0, 1.0),
218 },
219 NormalPoint3f {
220 position: Point3f::new(0.0, 1.0, 0.0),
221 normal: nalgebra::Vector3::new(0.0, 0.0, 1.0),
222 },
223 ];
224
225 let cloud = PointCloud::from_points(points);
226 let config = PoissonConfig::default();
227
228 let result = poisson_reconstruction(&cloud, &config);
229
230 match result {
232 Ok(_) => {
233 println!("Poisson reconstruction API is working!");
235 }
236 Err(e) => {
237 let error_msg = e.to_string();
238 assert!(
240 !error_msg.contains("temporarily disabled"),
241 "API should no longer be disabled: {}",
242 error_msg
243 );
244
245 if error_msg.contains("too small for Poisson reconstruction") {
247 println!(
248 "✓ Poisson reconstruction API fixed and working with proper validation"
249 );
250 } else {
251 println!("Poisson reconstruction returned other error: {}", error_msg);
252 }
253 }
254 }
255 }
256}