1use crate::model::Mesh;
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct RepairOptions {
11 pub stitch_epsilon: f32,
15 pub remove_degenerate: bool,
17 pub remove_duplicate_faces: bool,
19 pub harmonize_orientations: bool,
22 pub remove_islands: bool,
25 pub fill_holes: bool,
28}
29
30impl Default for RepairOptions {
31 fn default() -> Self {
32 Self {
33 stitch_epsilon: 1e-4, remove_degenerate: true,
35 remove_duplicate_faces: true,
36 harmonize_orientations: true,
37 remove_islands: false,
38 fill_holes: false,
39 }
40 }
41}
42
43pub trait MeshRepair {
62 fn repair(&mut self, options: RepairOptions) -> RepairStats;
80}
81
82#[derive(Debug, Clone, Default)]
87pub struct RepairStats {
88 pub vertices_removed: usize,
90 pub triangles_removed: usize,
92 pub triangles_flipped: usize,
94 pub triangles_added: usize,
96}
97
98impl MeshRepair for Mesh {
99 fn repair(&mut self, options: RepairOptions) -> RepairStats {
100 let mut stats = RepairStats::default();
101
102 if options.stitch_epsilon > 0.0 {
103 let removed = stitch_vertices(self, options.stitch_epsilon);
104 stats.vertices_removed += removed;
105 }
106
107 if options.remove_degenerate || options.remove_duplicate_faces {
108 let removed_tris = clean_triangles(
109 self,
110 options.remove_degenerate,
111 options.remove_duplicate_faces,
112 );
113 stats.triangles_removed += removed_tris;
114 }
115
116 let removed_verts = remove_unused_vertices(self);
119 stats.vertices_removed += removed_verts;
120
121 if options.remove_islands {
122 let removed = remove_islands(self);
123 stats.triangles_removed += removed;
124 }
125
126 if options.fill_holes {
127 let added = fill_holes(self);
128 stats.triangles_added += added;
129 }
130
131 if options.harmonize_orientations {
132 let flipped = harmonize_orientations(self);
133 stats.triangles_flipped += flipped;
134 }
135
136 stats
137 }
138}
139
140fn remove_islands(mesh: &mut Mesh) -> usize {
141 if mesh.triangles.is_empty() {
142 return 0;
143 }
144
145 let mut edge_to_tris: HashMap<(u32, u32), Vec<usize>> = HashMap::new();
147 for (i, tri) in mesh.triangles.iter().enumerate() {
148 let edges = [
149 sort_unord_edge(tri.v1, tri.v2),
150 sort_unord_edge(tri.v2, tri.v3),
151 sort_unord_edge(tri.v3, tri.v1),
152 ];
153 for e in edges {
154 edge_to_tris.entry(e).or_default().push(i);
155 }
156 }
157
158 let mut visited = vec![false; mesh.triangles.len()];
159 let mut component_tris = Vec::new();
160
161 for start_idx in 0..mesh.triangles.len() {
162 if visited[start_idx] {
163 continue;
164 }
165
166 let mut current_comp = Vec::new();
167 let mut stack = vec![start_idx];
168 visited[start_idx] = true;
169
170 while let Some(curr_idx) = stack.pop() {
171 current_comp.push(curr_idx);
172 let tri = &mesh.triangles[curr_idx];
173 let edges = [
174 sort_unord_edge(tri.v1, tri.v2),
175 sort_unord_edge(tri.v2, tri.v3),
176 sort_unord_edge(tri.v3, tri.v1),
177 ];
178
179 for e in edges {
180 if let Some(neighbors) = edge_to_tris.get(&e) {
181 for &neigh_idx in neighbors {
182 if !visited[neigh_idx] {
183 visited[neigh_idx] = true;
184 stack.push(neigh_idx);
185 }
186 }
187 }
188 }
189 }
190 component_tris.push(current_comp);
191 }
192
193 if component_tris.len() <= 1 {
194 return 0;
195 }
196
197 component_tris.sort_by_key(|b| std::cmp::Reverse(b.len()));
199
200 let initial_count = mesh.triangles.len();
201 let largest_comp = &component_tris[0];
202 let mut new_triangles = Vec::with_capacity(largest_comp.len());
203 for &idx in largest_comp {
204 new_triangles.push(mesh.triangles[idx]);
205 }
206
207 mesh.triangles = new_triangles;
208 initial_count - mesh.triangles.len()
209}
210
211fn sort_unord_edge(v1: u32, v2: u32) -> (u32, u32) {
212 if v1 < v2 { (v1, v2) } else { (v2, v1) }
213}
214
215fn fill_holes(mesh: &mut Mesh) -> usize {
216 if mesh.triangles.is_empty() {
217 return 0;
218 }
219
220 let mut edge_counts = HashMap::new();
222 for tri in &mesh.triangles {
223 let edges = [
224 sort_unord_edge(tri.v1, tri.v2),
225 sort_unord_edge(tri.v2, tri.v3),
226 sort_unord_edge(tri.v3, tri.v1),
227 ];
228 for e in edges {
229 *edge_counts.entry(e).or_insert(0) += 1;
230 }
231 }
232
233 let mut boundary_edges = Vec::new();
234 for (edge, count) in edge_counts {
235 if count == 1 {
236 boundary_edges.push(edge);
237 }
238 }
239
240 if boundary_edges.is_empty() {
241 return 0;
242 }
243
244 let mut adj = HashMap::new();
246 for &(v1, v2) in &boundary_edges {
247 adj.entry(v1).or_insert_with(Vec::new).push(v2);
248 adj.entry(v2).or_insert_with(Vec::new).push(v1);
249 }
250
251 let mut added_count = 0;
253 let mut visited_verts = std::collections::HashSet::new();
254
255 let verts: Vec<u32> = adj.keys().cloned().collect();
256 for &start_v in &verts {
257 if visited_verts.contains(&start_v) {
258 continue;
259 }
260
261 let mut loop_verts = Vec::new();
263 let mut curr = start_v;
264
265 loop {
266 loop_verts.push(curr);
267 visited_verts.insert(curr);
268
269 let possible_next = adj.get(&curr).unwrap();
270 let mut next_v = None;
271 for &n in possible_next {
272 if n == start_v && loop_verts.len() >= 3 {
273 break;
275 }
276 if !visited_verts.contains(&n) {
277 next_v = Some(n);
278 break;
279 }
280 }
281
282 if let Some(nv) = next_v {
283 curr = nv;
284 } else {
285 break;
286 }
287 }
288
289 if loop_verts.len() >= 3 {
291 let v0 = loop_verts[0];
292 for i in 1..(loop_verts.len() - 1) {
293 let v1 = loop_verts[i];
294 let v2 = loop_verts[i + 1];
295 mesh.add_triangle(v0, v1, v2);
296 added_count += 1;
297 }
298 }
299 }
300
301 added_count
302}
303
304fn harmonize_orientations(mesh: &mut Mesh) -> usize {
305 if mesh.triangles.is_empty() {
306 return 0;
307 }
308
309 let mut edge_to_tris = HashMap::new();
312 for (i, tri) in mesh.triangles.iter().enumerate() {
313 let edges = [(tri.v1, tri.v2), (tri.v2, tri.v3), (tri.v3, tri.v1)];
314 for e in edges {
315 let key = if e.0 < e.1 { (e.0, e.1) } else { (e.1, e.0) };
316 edge_to_tris
317 .entry(key)
318 .or_insert_with(Vec::new)
319 .push((i, e));
320 }
321 }
322
323 let mut flipped_count = 0;
324 let mut visited = vec![false; mesh.triangles.len()];
325
326 for start_idx in 0..mesh.triangles.len() {
327 if visited[start_idx] {
328 continue;
329 }
330
331 let mut queue = std::collections::VecDeque::new();
332 queue.push_back(start_idx);
333 visited[start_idx] = true;
334
335 while let Some(curr_idx) = queue.pop_front() {
336 let curr_tri = mesh.triangles[curr_idx];
337 let curr_edges = [
338 (curr_tri.v1, curr_tri.v2),
339 (curr_tri.v2, curr_tri.v3),
340 (curr_tri.v3, curr_tri.v1),
341 ];
342
343 for e in curr_edges {
344 let key = if e.0 < e.1 { (e.0, e.1) } else { (e.1, e.0) };
345 if let Some(neighbors) = edge_to_tris.get(&key) {
346 for &(neigh_idx, neigh_edge) in neighbors {
347 if visited[neigh_idx] {
348 continue;
349 }
350
351 if e == neigh_edge {
355 let nt = &mut mesh.triangles[neigh_idx];
357 std::mem::swap(&mut nt.v2, &mut nt.v3);
358 std::mem::swap(&mut nt.p2, &mut nt.p3);
360 flipped_count += 1;
361 }
362
363 visited[neigh_idx] = true;
364 queue.push_back(neigh_idx);
365 }
366 }
367 }
368 }
369 }
370
371 flipped_count
372}
373
374fn remove_unused_vertices(mesh: &mut Mesh) -> usize {
375 let initial_count = mesh.vertices.len();
376 if initial_count == 0 {
377 return 0;
378 }
379
380 let mut used = vec![false; initial_count];
382 for tri in &mesh.triangles {
383 used[tri.v1 as usize] = true;
384 used[tri.v2 as usize] = true;
385 used[tri.v3 as usize] = true;
386 }
387
388 let mut new_vertices = Vec::with_capacity(initial_count);
390 let mut old_to_new = vec![0u32; initial_count];
391
392 for (old_idx, &is_used) in used.iter().enumerate() {
393 if is_used {
394 old_to_new[old_idx] = new_vertices.len() as u32;
395 new_vertices.push(mesh.vertices[old_idx]);
396 }
397 }
398
399 let removed = initial_count - new_vertices.len();
400
401 mesh.vertices = new_vertices;
403 for tri in &mut mesh.triangles {
404 tri.v1 = old_to_new[tri.v1 as usize];
405 tri.v2 = old_to_new[tri.v2 as usize];
406 tri.v3 = old_to_new[tri.v3 as usize];
407 }
408
409 removed
410}
411
412fn stitch_vertices(mesh: &mut Mesh, epsilon: f32) -> usize {
413 if mesh.vertices.is_empty() {
414 return 0;
415 }
416 let initial_count = mesh.vertices.len();
419 let mut new_vertices = Vec::with_capacity(initial_count);
420 let mut point_map: HashMap<(i64, i64, i64), u32> = HashMap::new();
422 let mut index_remap = vec![0u32; initial_count];
424
425 let inv_eps = 1.0 / epsilon;
427
428 for (old_idx, v) in mesh.vertices.iter().enumerate() {
429 let key = (
430 (v.x * inv_eps).round() as i64,
431 (v.y * inv_eps).round() as i64,
432 (v.z * inv_eps).round() as i64,
433 );
434
435 if let Some(&existing_idx) = point_map.get(&key) {
436 index_remap[old_idx] = existing_idx;
438 } else {
439 let new_idx = new_vertices.len() as u32;
441 new_vertices.push(*v);
442 point_map.insert(key, new_idx);
443 index_remap[old_idx] = new_idx;
444 }
445 }
446
447 let merged_count = initial_count - new_vertices.len();
448
449 mesh.vertices = new_vertices;
451
452 for tri in &mut mesh.triangles {
454 tri.v1 = index_remap[tri.v1 as usize];
455 tri.v2 = index_remap[tri.v2 as usize];
456 tri.v3 = index_remap[tri.v3 as usize];
457 }
458
459 merged_count
460}
461
462fn clean_triangles(mesh: &mut Mesh, remove_degenerate: bool, remove_duplicates: bool) -> usize {
463 let initial_count = mesh.triangles.len();
464 let mut valid_triangles = Vec::with_capacity(initial_count);
465
466 let mut seen_faces = std::collections::HashSet::new();
468
469 for tri in &mesh.triangles {
470 if (tri.v1 == tri.v2 || tri.v2 == tri.v3 || tri.v3 == tri.v1) && remove_degenerate {
472 continue;
473 }
474
475 if remove_degenerate {
477 let area = mesh.compute_triangle_area(tri);
478 if area <= 1e-9 {
479 continue;
480 }
481 }
482
483 if remove_duplicates {
485 let mut indices = [tri.v1, tri.v2, tri.v3];
486 indices.sort_unstable(); if !seen_faces.insert(indices) {
491 continue;
492 }
493 }
494
495 valid_triangles.push(*tri);
496 }
497
498 let removed = initial_count - valid_triangles.len();
499 mesh.triangles = valid_triangles;
500 removed
501}