1use fast_mesh_store::Transaction;
4
5use crate::{
6 ComponentOptions, DegenerateOptions, DegenerateReport, FillOptions, FillReport, OrientReport,
7 RemoveReport, Result, WeldReport,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub struct AutoRepairOptions {
13 pub weld_tolerance: f32,
16 pub min_triangles: usize,
19 pub max_edges: Option<usize>,
22 pub fair: bool,
25}
26
27impl Default for AutoRepairOptions {
28 fn default() -> Self {
29 Self {
30 weld_tolerance: 1e-5,
31 min_triangles: 500,
32 max_edges: Some(64),
33 fair: true,
34 }
35 }
36}
37
38impl AutoRepairOptions {
39 pub fn builder() -> AutoRepairOptionsBuilder {
42 AutoRepairOptionsBuilder::default()
43 }
44}
45
46#[derive(Debug, Clone, Copy, Default)]
48pub struct AutoRepairOptionsBuilder {
49 options: AutoRepairOptions,
50}
51
52impl AutoRepairOptionsBuilder {
53 pub fn weld_tolerance(mut self, weld_tolerance: f32) -> Self {
55 self.options.weld_tolerance = weld_tolerance;
56 self
57 }
58
59 pub fn min_triangles(mut self, min_triangles: usize) -> Self {
61 self.options.min_triangles = min_triangles;
62 self
63 }
64
65 pub fn max_edges(mut self, max_edges: Option<usize>) -> Self {
67 self.options.max_edges = max_edges;
68 self
69 }
70
71 pub fn fair(mut self, fair: bool) -> Self {
73 self.options.fair = fair;
74 self
75 }
76
77 pub fn build(self) -> AutoRepairOptions {
79 self.options
80 }
81}
82
83#[derive(Debug, Clone, Default, PartialEq, Eq)]
85pub struct AutoRepairReport {
86 pub weld: WeldReport,
88 pub degenerate: DegenerateReport,
90 pub orient: OrientReport,
92 pub components: RemoveReport,
94 pub fill: FillReport,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum AutoRepairStage<'a> {
101 Weld(&'a WeldReport),
103 Degenerate(&'a DegenerateReport),
105 Orient(&'a OrientReport),
107 Components(&'a RemoveReport),
109 Fill(&'a FillReport),
111}
112
113impl AutoRepairReport {
114 pub fn stages(&self) -> [AutoRepairStage<'_>; 5] {
117 [
118 AutoRepairStage::Weld(&self.weld),
119 AutoRepairStage::Degenerate(&self.degenerate),
120 AutoRepairStage::Orient(&self.orient),
121 AutoRepairStage::Components(&self.components),
122 AutoRepairStage::Fill(&self.fill),
123 ]
124 }
125}
126
127pub fn auto_repair(tx: &mut Transaction, options: &AutoRepairOptions) -> Result<AutoRepairReport> {
169 let weld = crate::weld(tx, options.weld_tolerance)?;
170 let degenerate = crate::remove_degenerate(tx, &DegenerateOptions::default())?;
171 let orient = crate::orient(tx)?;
172 let components = crate::remove_small_components(
173 tx,
174 &ComponentOptions {
175 min_triangles: Some(options.min_triangles),
176 ..ComponentOptions::default()
177 },
178 )?;
179 let fill = crate::fill_holes(
180 tx,
181 &FillOptions {
182 max_edges: Some(options.max_edges.unwrap_or(usize::MAX)),
184 fair: options.fair,
185 ..FillOptions::default()
186 },
187 )?;
188 Ok(AutoRepairReport {
189 weld,
190 degenerate,
191 orient,
192 components,
193 fill,
194 })
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::test_util::{damaged_sphere, edit, grid_without, mesh_of, parts};
201 use crate::{OpsError, fail_allocations_after, weld};
202 use fast_mesh_store::StoreError;
203
204 fn options() -> AutoRepairOptions {
206 AutoRepairOptions {
207 weld_tolerance: 1e-4,
208 min_triangles: 10,
209 ..AutoRepairOptions::default()
210 }
211 }
212
213 #[test]
214 fn defaults_and_builder() {
215 let d = AutoRepairOptions::default();
216 assert_eq!(d.weld_tolerance, 1e-5);
217 assert_eq!(d.min_triangles, 500);
218 assert_eq!(d.max_edges, Some(64));
219 assert!(d.fair);
220 assert_eq!(AutoRepairOptions::builder().build(), d);
221 assert_eq!(
222 AutoRepairOptions::builder()
223 .weld_tolerance(0.5)
224 .min_triangles(3)
225 .max_edges(None)
226 .fair(false)
227 .build(),
228 AutoRepairOptions {
229 weld_tolerance: 0.5,
230 min_triangles: 3,
231 max_edges: None,
232 fair: false,
233 }
234 );
235 }
236
237 #[test]
238 fn matches_the_ops_run_by_hand_in_order() {
239 let o = options();
240 let (mut piped, mut by_hand) = (damaged_sphere(), damaged_sphere());
241 let (r, _) = edit(&mut piped, |tx| auto_repair(tx, &o));
242 let (expected, _) = edit(&mut by_hand, |tx| {
243 Ok(AutoRepairReport {
244 weld: weld(tx, o.weld_tolerance)?,
245 degenerate: crate::remove_degenerate(tx, &DegenerateOptions::default())?,
246 orient: crate::orient(tx)?,
247 components: crate::remove_small_components(
248 tx,
249 &ComponentOptions {
250 min_triangles: Some(o.min_triangles),
251 keep_largest: true,
252 min_area: None,
253 },
254 )?,
255 fill: crate::fill_holes(
256 tx,
257 &FillOptions {
258 max_edges: Some(64),
259 ..FillOptions::default()
260 },
261 )?,
262 })
263 });
264 assert_eq!(r, expected);
265 assert_eq!(parts(&piped), parts(&by_hand));
266 assert!(r.weld.merged_vertices > 0);
268 assert!(r.degenerate.removed_triangles > 0);
269 assert!(r.orient.flipped_triangles > 0);
270 assert_eq!(r.components.removed_components, 1);
271 assert!(!r.fill.filled.is_empty());
272 }
273
274 #[test]
275 fn weld_runs_before_remove_degenerate() {
276 let corners = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
278 let positions: Vec<f32> = corners.iter().chain(&corners).copied().collect();
279 let mut mesh = mesh_of(&positions, &[[0, 1, 2], [3, 4, 5]]);
280 let (r, _) = edit(&mut mesh, |tx| auto_repair(tx, &options()));
281 assert_eq!(r.weld.merged_vertices, 3);
282 assert_eq!(r.degenerate.removed_triangles, 1);
283 }
284
285 #[test]
286 fn stages_come_in_run_order() {
287 let r = AutoRepairReport::default();
288 assert_eq!(
289 r.stages(),
290 [
291 AutoRepairStage::Weld(&r.weld),
292 AutoRepairStage::Degenerate(&r.degenerate),
293 AutoRepairStage::Orient(&r.orient),
294 AutoRepairStage::Components(&r.components),
295 AutoRepairStage::Fill(&r.fill),
296 ]
297 );
298 }
299
300 #[test]
301 fn repaired_mesh_needs_nothing_more() {
302 let mut mesh = damaged_sphere();
303 edit(&mut mesh, |tx| auto_repair(tx, &options()));
304 let (again, empty) = edit(&mut mesh, |tx| auto_repair(tx, &options()));
305 assert!(empty);
306 assert_eq!(again.weld, WeldReport::default());
307 assert_eq!(again.degenerate, DegenerateReport::default());
308 assert_eq!(again.orient.flipped_triangles, 0);
309 assert_eq!(again.components, RemoveReport::default());
310 assert_eq!(again.fill, FillReport::default());
311 }
312
313 #[test]
314 fn max_edges_bounds_the_fill_and_none_means_any_size() {
315 let cells: Vec<(u32, u32)> = (2..6).flat_map(|y| (2..6).map(move |x| (x, y))).collect();
317 let run = |max_edges| {
318 let mut mesh = grid_without(8, &cells);
319 let o = AutoRepairOptions {
320 max_edges,
321 fair: false,
322 ..AutoRepairOptions::default()
323 };
324 edit(&mut mesh, |tx| auto_repair(tx, &o)).0.fill
325 };
326 let (smallest, inner, any) = (run(Some(4)), run(Some(16)), run(None));
327 assert_eq!((smallest.filled.len(), smallest.skipped.len()), (0, 2));
328 assert_eq!((inner.filled.len(), inner.skipped.len()), (1, 1));
329 assert!(any.skipped.is_empty());
331 assert_eq!(any.filled.len() + any.failed.len(), 2);
332 }
333
334 #[test]
335 fn min_triangles_keeps_the_largest_component() {
336 let o = AutoRepairOptions {
338 min_triangles: 1_000_000,
339 ..options()
340 };
341 let mut mesh = damaged_sphere();
342 let (r, _) = edit(&mut mesh, |tx| auto_repair(tx, &o));
343 assert_eq!(r.components.removed_components, 1);
344 assert!(mesh.triangle_count() > 2);
345 }
346
347 #[test]
348 fn invalid_tolerance_writes_nothing() {
349 for bad in [-1.0, f32::NAN, f32::INFINITY] {
350 let mut mesh = damaged_sphere();
351 let before = parts(&mesh);
352 let mut tx = mesh.begin_edit();
353 let o = AutoRepairOptions {
354 weld_tolerance: bad,
355 ..options()
356 };
357 assert!(matches!(
358 auto_repair(&mut tx, &o),
359 Err(OpsError::InvalidInput(_))
360 ));
361 assert!(tx.commit().unwrap().is_empty());
362 assert_eq!(parts(&mesh), before);
363 }
364 }
365
366 #[test]
367 fn a_failure_at_any_stage_rolls_back_when_dropped() {
368 let mut mesh = damaged_sphere();
369 let before = parts(&mesh);
370 let mut n = 0;
371 loop {
372 let mut tx = mesh.begin_edit();
373 fail_allocations_after(Some(n));
374 let r = auto_repair(&mut tx, &options());
375 fail_allocations_after(None);
376 if let Ok(report) = &r {
377 assert!(report.fill.filled.len() > 1);
379 assert!(n > 5);
380 break;
381 }
382 assert_eq!(r, Err(OpsError::Store(StoreError::OutOfMemory)));
383 drop(tx);
384 assert_eq!(parts(&mesh), before, "failure point {n}");
385 n += 1;
386 }
387 }
388}