1use crate::MeshSimplifier;
7use nalgebra::{Matrix4, Vector4};
8use threecrate_core::{Error, Point3f, Result, TriangleMesh};
9use priority_queue::PriorityQueue;
11use std::cmp::Ordering;
12use std::collections::{HashMap, HashSet};
13
14#[derive(Debug, Clone)]
16struct EdgeCollapse {
17 vertex1: usize,
19 vertex2: usize,
21 new_position: Point3f,
23 cost: f64,
25}
26
27impl PartialEq for EdgeCollapse {
28 fn eq(&self, other: &Self) -> bool {
29 self.cost.total_cmp(&other.cost) == Ordering::Equal
30 }
31}
32
33impl Eq for EdgeCollapse {}
34
35impl PartialOrd for EdgeCollapse {
36 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
37 Some(self.cmp(other))
38 }
39}
40
41impl Ord for EdgeCollapse {
42 fn cmp(&self, other: &Self) -> Ordering {
43 other.cost.total_cmp(&self.cost)
45 }
46}
47
48#[allow(dead_code)]
50#[derive(Debug, Clone)]
51struct FaceInfo {
52 vertices: [usize; 3],
53 plane: Vector4<f64>, }
55
56#[derive(Debug, Clone)]
58struct VertexInfo {
59 position: Point3f,
60 quadric: Matrix4<f64>,
61 faces: HashSet<usize>,
62 neighbors: HashSet<usize>,
63}
64
65pub struct QuadricErrorSimplifier {
67 pub max_edge_length: Option<f32>,
69 pub preserve_boundary: bool,
70 pub feature_angle_threshold: f32,
71}
72
73impl Default for QuadricErrorSimplifier {
74 fn default() -> Self {
75 Self {
76 max_edge_length: None,
77 preserve_boundary: true,
78 feature_angle_threshold: 45.0_f32.to_radians(),
79 }
80 }
81}
82
83impl QuadricErrorSimplifier {
84 pub fn new() -> Self {
86 Self::default()
87 }
88
89 pub fn with_params(
91 max_edge_length: Option<f32>,
92 preserve_boundary: bool,
93 feature_angle_threshold: f32,
94 ) -> Self {
95 Self {
96 max_edge_length,
97 preserve_boundary,
98 feature_angle_threshold,
99 }
100 }
101
102 fn compute_plane(&self, v0: &Point3f, v1: &Point3f, v2: &Point3f) -> Vector4<f64> {
104 let edge1 = v1 - v0;
105 let edge2 = v2 - v0;
106 let normal = edge1.cross(&edge2).normalize();
107
108 if !normal.iter().all(|x| x.is_finite()) {
110 return Vector4::new(0.0, 0.0, 1.0, 0.0);
111 }
112
113 let d = -normal.dot(&v0.coords);
114 Vector4::new(normal.x as f64, normal.y as f64, normal.z as f64, d as f64)
115 }
116
117 fn plane_to_quadric(&self, plane: &Vector4<f64>) -> Matrix4<f64> {
119 let a = plane[0];
120 let b = plane[1];
121 let c = plane[2];
122 let d = plane[3];
123
124 Matrix4::new(
125 a * a,
126 a * b,
127 a * c,
128 a * d,
129 a * b,
130 b * b,
131 b * c,
132 b * d,
133 a * c,
134 b * c,
135 c * c,
136 c * d,
137 a * d,
138 b * d,
139 c * d,
140 d * d,
141 )
142 }
143
144 fn initialize_vertices(&self, mesh: &TriangleMesh) -> Vec<VertexInfo> {
146 let mut vertices: Vec<VertexInfo> = mesh
147 .vertices
148 .iter()
149 .enumerate()
150 .map(|(_i, &pos)| VertexInfo {
151 position: pos,
152 quadric: Matrix4::zeros(),
153 faces: HashSet::new(),
154 neighbors: HashSet::new(),
155 })
156 .collect();
157
158 for (face_idx, face) in mesh.faces.iter().enumerate() {
160 let v0 = mesh.vertices[face[0]];
161 let v1 = mesh.vertices[face[1]];
162 let v2 = mesh.vertices[face[2]];
163
164 let plane = self.compute_plane(&v0, &v1, &v2);
165 let quadric = self.plane_to_quadric(&plane);
166
167 for &vertex_idx in face.iter() {
169 vertices[vertex_idx].quadric += quadric;
170 vertices[vertex_idx].faces.insert(face_idx);
171 }
172
173 vertices[face[0]].neighbors.insert(face[1]);
175 vertices[face[0]].neighbors.insert(face[2]);
176 vertices[face[1]].neighbors.insert(face[0]);
177 vertices[face[1]].neighbors.insert(face[2]);
178 vertices[face[2]].neighbors.insert(face[0]);
179 vertices[face[2]].neighbors.insert(face[1]);
180 }
181
182 vertices
183 }
184
185 fn find_boundary_edges(&self, mesh: &TriangleMesh) -> HashSet<(usize, usize)> {
187 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
188 for face in &mesh.faces {
189 let edges = [
190 (face[0].min(face[1]), face[0].max(face[1])),
191 (face[1].min(face[2]), face[1].max(face[2])),
192 (face[2].min(face[0]), face[2].max(face[0])),
193 ];
194 for edge in edges.iter() {
195 *edge_count.entry(*edge).or_insert(0) += 1;
196 }
197 }
198 edge_count
199 .into_iter()
200 .filter(|(_, count)| *count == 1)
201 .map(|(edge, _)| edge)
202 .collect()
203 }
204
205 fn find_boundary_edges_from_faces(&self, faces: &[[usize; 3]]) -> HashSet<(usize, usize)> {
207 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
208
209 for face in faces {
210 let edges = [
211 (face[0].min(face[1]), face[0].max(face[1])),
212 (face[1].min(face[2]), face[1].max(face[2])),
213 (face[2].min(face[0]), face[2].max(face[0])),
214 ];
215
216 for edge in edges.iter() {
217 *edge_count.entry(*edge).or_insert(0) += 1;
218 }
219 }
220
221 edge_count
222 .into_iter()
223 .filter(|(_, count)| *count == 1)
224 .map(|(edge, _)| edge)
225 .collect()
226 }
227
228 fn get_boundary_vertices(&self, boundary_edges: &HashSet<(usize, usize)>) -> HashSet<usize> {
230 let mut boundary_verts = HashSet::new();
231 for &(v1, v2) in boundary_edges {
232 boundary_verts.insert(v1);
233 boundary_verts.insert(v2);
234 }
235 boundary_verts
236 }
237
238 fn compute_collapse_cost(
240 &self,
241 v1_idx: usize,
242 v2_idx: usize,
243 vertices: &[VertexInfo],
244 ) -> Option<EdgeCollapse> {
245 let v1 = &vertices[v1_idx];
246 let v2 = &vertices[v2_idx];
247
248 if let Some(max_len) = self.max_edge_length {
250 let edge_len = (v1.position - v2.position).magnitude();
251 if edge_len > max_len {
252 return None;
253 }
254 }
255
256 let q_combined = v1.quadric + v2.quadric;
258
259 let q_3x3 = q_combined.fixed_view::<3, 3>(0, 0);
262 let q_3x1 = q_combined.fixed_view::<3, 1>(0, 3);
263
264 let optimal_pos = if let Some(inv_q) = q_3x3.try_inverse() {
265 let optimal_homogeneous = -inv_q * q_3x1;
266 Point3f::new(
267 optimal_homogeneous[0] as f32,
268 optimal_homogeneous[1] as f32,
269 optimal_homogeneous[2] as f32,
270 )
271 } else {
272 Point3f::from((v1.position.coords + v2.position.coords) * 0.5)
274 };
275
276 let pos_homogeneous = Vector4::new(
278 optimal_pos.x as f64,
279 optimal_pos.y as f64,
280 optimal_pos.z as f64,
281 1.0,
282 );
283
284 let cost = (pos_homogeneous.transpose() * q_combined * pos_homogeneous)[0];
285
286 Some(EdgeCollapse {
287 vertex1: v1_idx,
288 vertex2: v2_idx,
289 new_position: optimal_pos,
290 cost,
291 })
292 }
293
294 fn generate_edge_collapses(
296 &self,
297 vertices: &[VertexInfo],
298 boundary_edges: &HashSet<(usize, usize)>,
299 ) -> Vec<EdgeCollapse> {
300 let mut collapses = Vec::new();
301
302 let boundary_verts = if self.preserve_boundary {
304 self.get_boundary_vertices(boundary_edges)
305 } else {
306 HashSet::new()
307 };
308
309 for (v1_idx, vertex) in vertices.iter().enumerate() {
310 for &v2_idx in &vertex.neighbors {
311 if v1_idx < v2_idx {
312 if self.preserve_boundary {
315 if boundary_verts.contains(&v1_idx) || boundary_verts.contains(&v2_idx) {
316 continue;
317 }
318 }
319 if let Some(collapse) = self.compute_collapse_cost(v1_idx, v2_idx, vertices) {
320 collapses.push(collapse);
321 }
322 }
323 }
324 }
325 collapses
326 }
327
328 fn apply_collapse(
330 &self,
331 collapse: &EdgeCollapse,
332 vertices: &mut Vec<VertexInfo>,
333 faces: &mut Vec<[usize; 3]>,
334 vertex_mapping: &mut HashMap<usize, usize>,
335 ) -> Result<()> {
336 let v1_idx = collapse.vertex1;
337 let v2_idx = collapse.vertex2;
338
339 let v2_quadric = vertices[v2_idx].quadric.clone();
341 vertices[v1_idx].position = collapse.new_position;
342 vertices[v1_idx].quadric += v2_quadric;
343
344 for face in faces.iter_mut() {
346 for vertex_ref in face.iter_mut() {
347 if *vertex_ref == v2_idx {
348 *vertex_ref = v1_idx;
349 }
350 }
351 }
352
353 faces.retain(|face| face[0] != face[1] && face[1] != face[2] && face[2] != face[0]);
355
356 vertex_mapping.insert(v2_idx, v1_idx);
358
359 let v2_neighbors = vertices[v2_idx].neighbors.clone();
361 for &neighbor in &v2_neighbors {
362 if neighbor != v1_idx {
363 vertices[v1_idx].neighbors.insert(neighbor);
364 vertices[neighbor].neighbors.remove(&v2_idx);
365 vertices[neighbor].neighbors.insert(v1_idx);
366 }
367 }
368 vertices[v1_idx].neighbors.remove(&v2_idx);
369
370 vertices[v2_idx].neighbors.clear();
372 vertices[v2_idx].faces.clear();
373
374 Ok(())
375 }
376
377 fn rebuild_mesh(&self, vertices: &[VertexInfo], faces: &[[usize; 3]]) -> TriangleMesh {
379 let mut old_to_new: HashMap<usize, usize> = HashMap::new();
381 let mut new_vertices = Vec::new();
382
383 for (old_idx, vertex) in vertices.iter().enumerate() {
385 if !vertex.neighbors.is_empty() || !vertex.faces.is_empty() {
386 old_to_new.insert(old_idx, new_vertices.len());
387 new_vertices.push(vertex.position);
388 }
389 }
390
391 let new_faces: Vec<[usize; 3]> = faces
393 .iter()
394 .filter_map(|face| {
395 if let (Some(&new_v0), Some(&new_v1), Some(&new_v2)) = (
396 old_to_new.get(&face[0]),
397 old_to_new.get(&face[1]),
398 old_to_new.get(&face[2]),
399 ) {
400 Some([new_v0, new_v1, new_v2])
401 } else {
402 None
403 }
404 })
405 .collect();
406
407 TriangleMesh::from_vertices_and_faces(new_vertices, new_faces)
408 }
409}
410
411impl MeshSimplifier for QuadricErrorSimplifier {
412 fn simplify(&self, mesh: &TriangleMesh, reduction_ratio: f32) -> Result<TriangleMesh> {
414 if mesh.is_empty() {
415 return Err(Error::InvalidData("Mesh is empty".to_string()));
416 }
417
418 if !(0.0..=1.0).contains(&reduction_ratio) {
419 return Err(Error::InvalidData(
420 "Reduction ratio must be between 0.0 and 1.0".to_string(),
421 ));
422 }
423
424 if reduction_ratio == 0.0 {
425 return Ok(mesh.clone());
426 }
427
428 let target_face_count = ((1.0 - reduction_ratio) * mesh.faces.len() as f32) as usize;
429
430 let mut vertices = self.initialize_vertices(mesh);
432 let mut faces = mesh.faces.clone();
433 let mut vertex_mapping = HashMap::new();
434
435 let boundary_edges = self.find_boundary_edges(mesh);
437
438 let mut collapse_queue = PriorityQueue::new();
440 let initial_collapses = self.generate_edge_collapses(&vertices, &boundary_edges);
441
442 for (idx, collapse) in initial_collapses.into_iter().enumerate() {
443 collapse_queue.push(idx, collapse);
444 }
445
446 let mut collapse_counter = 0;
448 while faces.len() > target_face_count && !collapse_queue.is_empty() {
449 if let Some((_, collapse)) = collapse_queue.pop() {
450 if vertices[collapse.vertex1]
452 .neighbors
453 .contains(&collapse.vertex2)
454 {
455 self.apply_collapse(&collapse, &mut vertices, &mut faces, &mut vertex_mapping)?;
456 collapse_counter += 1;
457 if collapse_counter % 100 == 0 {
459 collapse_queue.clear();
460 let current_boundary_edges = self.find_boundary_edges_from_faces(&faces);
462 let new_collapses =
463 self.generate_edge_collapses(&vertices, ¤t_boundary_edges);
464 for (idx, collapse) in new_collapses.into_iter().enumerate() {
465 collapse_queue.push(collapse_counter * 1000 + idx, collapse);
466 }
467 }
468 }
469 }
470 }
471
472 if collapse_counter == 0 && faces.len() > target_face_count {
473 return Err(self.no_collapse_error(mesh, &boundary_edges));
474 }
475
476 Ok(self.rebuild_mesh(&vertices, &faces))
477 }
478}
479
480impl QuadricErrorSimplifier {
481 fn no_collapse_error(
484 &self,
485 mesh: &TriangleMesh,
486 boundary_edges: &HashSet<(usize, usize)>,
487 ) -> Error {
488 let on_boundary = self.get_boundary_vertices(boundary_edges).len();
489 if self.preserve_boundary && on_boundary > 0 {
490 Error::Algorithm(format!(
491 "Could not simplify: every edge touches an open edge of the mesh \
492 ({on_boundary} of {} vertices are on a hole or border), and \
493 preserve_boundary keeps those vertices fixed. Fill the holes or \
494 set preserve_boundary to false.",
495 mesh.vertices.len()
496 ))
497 } else {
498 Error::Algorithm(
499 "Could not simplify: no edge can be collapsed (check max_edge_length)".to_string(),
500 )
501 }
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508 use nalgebra::Point3;
509
510 #[test]
511 fn test_quadric_error_simplifier_creation() {
512 let simplifier = QuadricErrorSimplifier::new();
513 assert!(simplifier.preserve_boundary);
514 assert!(simplifier.max_edge_length.is_none());
515 }
516
517 #[test]
518 fn test_plane_computation() {
519 let simplifier = QuadricErrorSimplifier::new();
520 let v0 = Point3::new(0.0, 0.0, 0.0);
521 let v1 = Point3::new(1.0, 0.0, 0.0);
522 let v2 = Point3::new(0.0, 1.0, 0.0);
523
524 let plane = simplifier.compute_plane(&v0, &v1, &v2);
525
526 assert!((plane[0]).abs() < 1e-6);
528 assert!((plane[1]).abs() < 1e-6);
529 assert!((plane[2] - 1.0).abs() < 1e-6);
530 assert!((plane[3]).abs() < 1e-6);
531 }
532
533 #[test]
534 fn test_empty_mesh() {
535 let simplifier = QuadricErrorSimplifier::new();
536 let mesh = TriangleMesh::new();
537
538 let result = simplifier.simplify(&mesh, 0.5);
539 assert!(result.is_err());
540 }
541
542 #[test]
543 fn test_zero_reduction() {
544 let simplifier = QuadricErrorSimplifier::new();
545 let vertices = vec![
546 Point3::new(0.0, 0.0, 0.0),
547 Point3::new(1.0, 0.0, 0.0),
548 Point3::new(0.5, 1.0, 0.0),
549 ];
550 let faces = vec![[0, 1, 2]];
551 let mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
552
553 let result = simplifier.simplify(&mesh, 0.0).unwrap();
554 assert_eq!(result.vertex_count(), 3);
555 assert_eq!(result.face_count(), 1);
556 }
557
558 #[test]
559 fn test_invalid_reduction_ratio() {
560 let simplifier = QuadricErrorSimplifier::new();
561 let vertices = vec![
562 Point3::new(0.0, 0.0, 0.0),
563 Point3::new(1.0, 0.0, 0.0),
564 Point3::new(0.5, 1.0, 0.0),
565 ];
566 let faces = vec![[0, 1, 2]];
567 let mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
568
569 assert!(simplifier.simplify(&mesh, -0.1).is_err());
570 assert!(simplifier.simplify(&mesh, 1.1).is_err());
571 }
572
573 #[test]
574 fn test_quadric_matrix_computation() {
575 let simplifier = QuadricErrorSimplifier::new();
576 let plane = Vector4::new(1.0, 0.0, 0.0, -1.0); let quadric = simplifier.plane_to_quadric(&plane);
579
580 assert!((quadric[(0, 0)] - 1.0).abs() < 1e-10);
582 assert!((quadric[(1, 1)] - 0.0).abs() < 1e-10);
583 assert!((quadric[(2, 2)] - 0.0).abs() < 1e-10);
584 assert!((quadric[(3, 3)] - 1.0).abs() < 1e-10);
585 }
586
587 #[test]
588 fn test_tetrahedron_simplification() {
589 let simplifier = QuadricErrorSimplifier::new();
590 let vertices = vec![
592 Point3::new(0.0, 0.0, 0.0),
593 Point3::new(1.0, 0.0, 0.0),
594 Point3::new(0.5, 1.0, 0.0),
595 Point3::new(0.5, 0.5, 1.0),
596 ];
597 let faces = vec![[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]];
598 let mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
599 let simplified = simplifier.simplify(&mesh, 0.5).unwrap();
600 assert!(simplified.face_count() <= mesh.face_count());
602 assert!(simplified.vertex_count() <= mesh.vertex_count());
603 }
604
605 #[test]
606 fn test_grid_boundary_preservation() {
607 let simplifier = QuadricErrorSimplifier::new();
609
610 let grid_size = 11;
612 let spacing = 400.0;
613 let z = -2000.0;
614
615 let mut vertices = Vec::new();
616 for y in 0..grid_size {
617 for x in 0..grid_size {
618 vertices.push(Point3::new(x as f32 * spacing, y as f32 * spacing, z));
619 }
620 }
621
622 let mut faces = Vec::new();
624 for y in 0..(grid_size - 1) {
625 for x in 0..(grid_size - 1) {
626 let top_left = y * grid_size + x;
627 let top_right = top_left + 1;
628 let bottom_left = (y + 1) * grid_size + x;
629 let bottom_right = bottom_left + 1;
630
631 faces.push([top_left, bottom_left, top_right]);
633 faces.push([top_right, bottom_left, bottom_right]);
634 }
635 }
636
637 let mesh = TriangleMesh::from_vertices_and_faces(vertices.clone(), faces.clone());
638
639 println!(
640 "Original mesh: {} vertices, {} faces",
641 mesh.vertex_count(),
642 mesh.face_count()
643 );
644
645 let mut original_boundary_verts = std::collections::HashSet::new();
647 for i in 0..grid_size {
648 original_boundary_verts.insert(i);
650 original_boundary_verts.insert((grid_size - 1) * grid_size + i);
652 original_boundary_verts.insert(i * grid_size);
654 original_boundary_verts.insert(i * grid_size + (grid_size - 1));
656 }
657
658 println!(
659 "Original boundary vertices: {}",
660 original_boundary_verts.len()
661 );
662
663 let boundary_edges = simplifier.find_boundary_edges(&mesh);
665 println!("Detected boundary edges: {}", boundary_edges.len());
666
667 let mut detected_boundary_verts = std::collections::HashSet::new();
669 for &(v1, v2) in &boundary_edges {
670 detected_boundary_verts.insert(v1);
671 detected_boundary_verts.insert(v2);
672 }
673 println!(
674 "Detected boundary vertices: {}",
675 detected_boundary_verts.len()
676 );
677
678 let simplified = simplifier.simplify(&mesh, 0.5).unwrap();
680
681 println!(
682 "Simplified mesh: {} vertices, {} faces",
683 simplified.vertex_count(),
684 simplified.face_count()
685 );
686
687 let mut boundary_positions = std::collections::HashSet::new();
690 for &idx in &original_boundary_verts {
691 let pos = vertices[idx];
692 boundary_positions.insert((
693 (pos.x * 100.0) as i32,
694 (pos.y * 100.0) as i32,
695 (pos.z * 100.0) as i32,
696 ));
697 }
698
699 let mut simplified_positions = std::collections::HashSet::new();
700 for &pos in &simplified.vertices {
701 simplified_positions.insert((
702 (pos.x * 100.0) as i32,
703 (pos.y * 100.0) as i32,
704 (pos.z * 100.0) as i32,
705 ));
706 }
707
708 let preserved_boundary_count = boundary_positions
709 .intersection(&simplified_positions)
710 .count();
711 println!(
712 "Preserved boundary vertices: {}/{}",
713 preserved_boundary_count,
714 boundary_positions.len()
715 );
716
717 assert!(preserved_boundary_count as f32 / boundary_positions.len() as f32 > 0.9,
720 "Expected at least 90% of boundary vertices to be preserved, but only {}% were preserved",
721 (preserved_boundary_count as f32 / boundary_positions.len() as f32 * 100.0));
722 }
723
724 fn strip(n: usize) -> TriangleMesh {
726 let vertices = (0..n)
727 .flat_map(|i| {
728 [
729 Point3::new(i as f32, 0.0, 0.0),
730 Point3::new(i as f32, 1.0, 0.0),
731 ]
732 })
733 .collect();
734 let faces = (0..n - 1)
735 .flat_map(|i| {
736 let (a, b, c, d) = (2 * i, 2 * i + 1, 2 * i + 2, 2 * i + 3);
737 [[a, c, b], [b, c, d]]
738 })
739 .collect();
740 TriangleMesh::from_vertices_and_faces(vertices, faces)
741 }
742
743 #[test]
744 fn test_all_boundary_mesh_reports_why_it_cannot_simplify() {
745 let err = QuadricErrorSimplifier::new()
746 .simplify(&strip(20), 0.5)
747 .unwrap_err()
748 .to_string();
749 assert!(err.contains("preserve_boundary"), "{err}");
750 assert!(err.contains("40 of 40 vertices"), "{err}");
751 assert!(!err.contains(" "), "stray spaces: {err}");
752 }
753
754 #[test]
755 fn test_all_boundary_mesh_simplifies_without_boundary_preservation() {
756 let mesh = strip(20);
757 let simplifier = QuadricErrorSimplifier::with_params(None, false, 45.0_f32.to_radians());
758 let result = simplifier.simplify(&mesh, 0.5).unwrap();
759 assert!(result.faces.len() < mesh.faces.len());
760 }
761}