Skip to main content

threecrate_simplification/
quadric_error.rs

1//! Quadric error decimation
2//! 
3//! Implementation of the Garland-Heckbert algorithm for mesh simplification
4//! based on quadric error metrics.
5
6use threecrate_core::{TriangleMesh, Result, Error, Point3f};
7use crate::MeshSimplifier;
8use nalgebra::{Matrix4, Vector4};
9// use rayon::prelude::*;
10use priority_queue::PriorityQueue;
11use std::collections::{HashMap, HashSet};
12use std::cmp::Ordering;
13
14/// Edge collapse operation
15#[derive(Debug, Clone)]
16struct EdgeCollapse {
17    /// Source vertex index
18    vertex1: usize,
19    /// Target vertex index  
20    vertex2: usize,
21    /// New position after collapse
22    new_position: Point3f,
23    /// Quadric error cost
24    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        // Reverse ordering for min-heap (smallest cost first)
44        other.cost.total_cmp(&self.cost)
45    }
46}
47
48/// Face adjacency information
49#[allow(dead_code)]
50#[derive(Debug, Clone)]
51struct FaceInfo {
52    vertices: [usize; 3],
53    plane: Vector4<f64>, // ax + by + cz + d = 0
54}
55
56/// Vertex information including quadric matrix
57#[derive(Debug, Clone)]
58struct VertexInfo {
59    position: Point3f,
60    quadric: Matrix4<f64>,
61    faces: HashSet<usize>,
62    neighbors: HashSet<usize>,
63}
64
65/// Quadric error decimation simplifier
66pub struct QuadricErrorSimplifier {
67    /// Configuration parameters
68    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    /// Create new quadric error simplifier with default settings
85    pub fn new() -> Self {
86        Self::default()
87    }
88    
89    /// Create with custom parameters
90    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    /// Compute plane equation from triangle vertices
103    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        // Handle degenerate triangles
109        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    /// Compute quadric matrix from plane equation
118    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, a*b, a*c, a*d,
126            a*b, b*b, b*c, b*d,
127            a*c, b*c, c*c, c*d,
128            a*d, b*d, c*d, d*d,
129        )
130    }
131    
132    /// Initialize vertex information including quadrics
133    fn initialize_vertices(&self, mesh: &TriangleMesh) -> Vec<VertexInfo> {
134        let mut vertices: Vec<VertexInfo> = mesh.vertices.iter().enumerate().map(|(_i, &pos)| {
135            VertexInfo {
136                position: pos,
137                quadric: Matrix4::zeros(),
138                faces: HashSet::new(),
139                neighbors: HashSet::new(),
140            }
141        }).collect();
142        
143        // Compute face planes and accumulate quadrics
144        for (face_idx, face) in mesh.faces.iter().enumerate() {
145            let v0 = mesh.vertices[face[0]];
146            let v1 = mesh.vertices[face[1]];
147            let v2 = mesh.vertices[face[2]];
148            
149            let plane = self.compute_plane(&v0, &v1, &v2);
150            let quadric = self.plane_to_quadric(&plane);
151            
152            // Add quadric to each vertex of the face
153            for &vertex_idx in face.iter() {
154                vertices[vertex_idx].quadric += quadric;
155                vertices[vertex_idx].faces.insert(face_idx);
156            }
157            
158            // Build adjacency
159            vertices[face[0]].neighbors.insert(face[1]);
160            vertices[face[0]].neighbors.insert(face[2]);
161            vertices[face[1]].neighbors.insert(face[0]);
162            vertices[face[1]].neighbors.insert(face[2]);
163            vertices[face[2]].neighbors.insert(face[0]);
164            vertices[face[2]].neighbors.insert(face[1]);
165        }
166        
167        vertices
168    }
169    
170    /// Find boundary edges in the mesh
171    fn find_boundary_edges(&self, mesh: &TriangleMesh) -> HashSet<(usize, usize)> {
172        let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
173        for face in &mesh.faces {
174            let edges = [
175                (face[0].min(face[1]), face[0].max(face[1])),
176                (face[1].min(face[2]), face[1].max(face[2])),
177                (face[2].min(face[0]), face[2].max(face[0])),
178            ];
179            for edge in edges.iter() {
180                *edge_count.entry(*edge).or_insert(0) += 1;
181            }
182        }
183        edge_count.into_iter()
184            .filter(|(_, count)| *count == 1)
185            .map(|(edge, _)| edge)
186            .collect()
187    }
188
189    /// Find boundary edges from a face list (used during simplification)
190    fn find_boundary_edges_from_faces(&self, faces: &[[usize; 3]]) -> HashSet<(usize, usize)> {
191        let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
192
193        for face in faces {
194            let edges = [
195                (face[0].min(face[1]), face[0].max(face[1])),
196                (face[1].min(face[2]), face[1].max(face[2])),
197                (face[2].min(face[0]), face[2].max(face[0])),
198            ];
199
200            for edge in edges.iter() {
201                *edge_count.entry(*edge).or_insert(0) += 1;
202            }
203        }
204
205        edge_count.into_iter()
206            .filter(|(_, count)| *count == 1)
207            .map(|(edge, _)| edge)
208            .collect()
209    }
210
211    /// Get all boundary vertices from boundary edges
212    fn get_boundary_vertices(&self, boundary_edges: &HashSet<(usize, usize)>) -> HashSet<usize> {
213        let mut boundary_verts = HashSet::new();
214        for &(v1, v2) in boundary_edges {
215            boundary_verts.insert(v1);
216            boundary_verts.insert(v2);
217        }
218        boundary_verts
219    }
220    
221    /// Compute optimal position and cost for edge collapse
222    fn compute_collapse_cost(&self, v1_idx: usize, v2_idx: usize, vertices: &[VertexInfo]) -> Option<EdgeCollapse> {
223        let v1 = &vertices[v1_idx];
224        let v2 = &vertices[v2_idx];
225        
226        // Check edge length constraint
227        if let Some(max_len) = self.max_edge_length {
228            let edge_len = (v1.position - v2.position).magnitude();
229            if edge_len > max_len {
230                return None;
231            }
232        }
233        
234        // Combined quadric
235        let q_combined = v1.quadric + v2.quadric;
236        
237        // Try to solve for optimal position: ∇(v^T Q v) = 0
238        // This gives us: 2Qv = 0, or the 3x3 upper-left block of Q
239        let q_3x3 = q_combined.fixed_view::<3, 3>(0, 0);
240        let q_3x1 = q_combined.fixed_view::<3, 1>(0, 3);
241        
242        let optimal_pos = if let Some(inv_q) = q_3x3.try_inverse() {
243            let optimal_homogeneous = -inv_q * q_3x1;
244            Point3f::new(
245                optimal_homogeneous[0] as f32,
246                optimal_homogeneous[1] as f32,
247                optimal_homogeneous[2] as f32,
248            )
249        } else {
250                         // If not invertible, use midpoint
251             Point3f::from((v1.position.coords + v2.position.coords) * 0.5)
252        };
253        
254        // Compute quadric error at optimal position
255        let pos_homogeneous = Vector4::new(
256            optimal_pos.x as f64,
257            optimal_pos.y as f64,
258            optimal_pos.z as f64,
259            1.0,
260        );
261        
262                 let cost = (pos_homogeneous.transpose() * q_combined * pos_homogeneous)[0];
263        
264        Some(EdgeCollapse {
265            vertex1: v1_idx,
266            vertex2: v2_idx,
267            new_position: optimal_pos,
268            cost,
269        })
270    }
271    
272    /// Generate all valid edge collapses
273    fn generate_edge_collapses(&self, vertices: &[VertexInfo], boundary_edges: &HashSet<(usize, usize)>) -> Vec<EdgeCollapse> {
274        let mut collapses = Vec::new();
275
276        // Get all boundary vertices
277        let boundary_verts = if self.preserve_boundary {
278            self.get_boundary_vertices(boundary_edges)
279        } else {
280            HashSet::new()
281        };
282
283        for (v1_idx, vertex) in vertices.iter().enumerate() {
284            for &v2_idx in &vertex.neighbors {
285                if v1_idx < v2_idx { // Avoid duplicate edges
286                    // Skip any edge involving a boundary vertex if preservation is enabled
287                    if self.preserve_boundary {
288                        if boundary_verts.contains(&v1_idx) || boundary_verts.contains(&v2_idx) {
289                            continue;
290                        }
291                    }
292                    if let Some(collapse) = self.compute_collapse_cost(v1_idx, v2_idx, vertices) {
293                        collapses.push(collapse);
294                    }
295                }
296            }
297        }
298        collapses
299    }
300    
301    /// Apply edge collapse to mesh data structures
302    fn apply_collapse(
303        &self,
304        collapse: &EdgeCollapse,
305        vertices: &mut Vec<VertexInfo>,
306        faces: &mut Vec<[usize; 3]>,
307        vertex_mapping: &mut HashMap<usize, usize>,
308    ) -> Result<()> {
309        let v1_idx = collapse.vertex1;
310        let v2_idx = collapse.vertex2;
311        
312                 // Update vertex position and combine quadrics
313         let v2_quadric = vertices[v2_idx].quadric.clone();
314         vertices[v1_idx].position = collapse.new_position;
315         vertices[v1_idx].quadric += v2_quadric;
316        
317        // Update faces that reference v2 to reference v1
318        for face in faces.iter_mut() {
319            for vertex_ref in face.iter_mut() {
320                if *vertex_ref == v2_idx {
321                    *vertex_ref = v1_idx;
322                }
323            }
324        }
325        
326        // Remove degenerate faces (triangles with repeated vertices)
327        faces.retain(|face| {
328            face[0] != face[1] && face[1] != face[2] && face[2] != face[0]
329        });
330        
331        // Update vertex mapping
332        vertex_mapping.insert(v2_idx, v1_idx);
333        
334        // Update adjacency information
335        let v2_neighbors = vertices[v2_idx].neighbors.clone();
336        for &neighbor in &v2_neighbors {
337            if neighbor != v1_idx {
338                vertices[v1_idx].neighbors.insert(neighbor);
339                vertices[neighbor].neighbors.remove(&v2_idx);
340                vertices[neighbor].neighbors.insert(v1_idx);
341            }
342        }
343        vertices[v1_idx].neighbors.remove(&v2_idx);
344        
345        // Mark v2 as invalid
346        vertices[v2_idx].neighbors.clear();
347        vertices[v2_idx].faces.clear();
348        
349        Ok(())
350    }
351    
352    /// Rebuild mesh from simplified vertex and face data
353    fn rebuild_mesh(&self, vertices: &[VertexInfo], faces: &[[usize; 3]]) -> TriangleMesh {
354        // Create mapping from old indices to new indices (compacting)
355        let mut old_to_new: HashMap<usize, usize> = HashMap::new();
356        let mut new_vertices = Vec::new();
357        
358        // Collect valid vertices
359        for (old_idx, vertex) in vertices.iter().enumerate() {
360            if !vertex.neighbors.is_empty() || !vertex.faces.is_empty() {
361                old_to_new.insert(old_idx, new_vertices.len());
362                new_vertices.push(vertex.position);
363            }
364        }
365        
366        // Update face indices
367        let new_faces: Vec<[usize; 3]> = faces.iter()
368            .filter_map(|face| {
369                if let (Some(&new_v0), Some(&new_v1), Some(&new_v2)) = (
370                    old_to_new.get(&face[0]),
371                    old_to_new.get(&face[1]),
372                    old_to_new.get(&face[2]),
373                ) {
374                    Some([new_v0, new_v1, new_v2])
375                } else {
376                    None
377                }
378            })
379            .collect();
380        
381        TriangleMesh::from_vertices_and_faces(new_vertices, new_faces)
382    }
383}
384
385impl MeshSimplifier for QuadricErrorSimplifier {
386    /// Simplify mesh with target reduction ratio
387    fn simplify(&self, mesh: &TriangleMesh, reduction_ratio: f32) -> Result<TriangleMesh> {
388        if mesh.is_empty() {
389            return Err(Error::InvalidData("Mesh is empty".to_string()));
390        }
391        
392        if !(0.0..=1.0).contains(&reduction_ratio) {
393            return Err(Error::InvalidData("Reduction ratio must be between 0.0 and 1.0".to_string()));
394        }
395        
396        if reduction_ratio == 0.0 {
397            return Ok(mesh.clone());
398        }
399        
400        let target_face_count = ((1.0 - reduction_ratio) * mesh.faces.len() as f32) as usize;
401        
402        // Initialize vertex information
403        let mut vertices = self.initialize_vertices(mesh);
404        let mut faces = mesh.faces.clone();
405        let mut vertex_mapping = HashMap::new();
406        
407        // Find boundary edges
408        let boundary_edges = self.find_boundary_edges(mesh);
409        
410        // Generate initial edge collapses
411        let mut collapse_queue = PriorityQueue::new();
412        let initial_collapses = self.generate_edge_collapses(&vertices, &boundary_edges);
413        
414        for (idx, collapse) in initial_collapses.into_iter().enumerate() {
415            collapse_queue.push(idx, collapse);
416        }
417        
418        // Perform edge collapses until target is reached
419        let mut collapse_counter = 0;
420        while faces.len() > target_face_count && !collapse_queue.is_empty() {
421            if let Some((_, collapse)) = collapse_queue.pop() {
422                // Verify collapse is still valid
423                if vertices[collapse.vertex1].neighbors.contains(&collapse.vertex2) {
424                    self.apply_collapse(&collapse, &mut vertices, &mut faces, &mut vertex_mapping)?;
425                    collapse_counter += 1;
426                    // Periodically regenerate collapses to maintain quality
427                    if collapse_counter % 100 == 0 {
428                        collapse_queue.clear();
429                        // Recompute boundary edges based on current face configuration
430                        let current_boundary_edges = self.find_boundary_edges_from_faces(&faces);
431                        let new_collapses = self.generate_edge_collapses(&vertices, &current_boundary_edges);
432                        for (idx, collapse) in new_collapses.into_iter().enumerate() {
433                            collapse_queue.push(collapse_counter * 1000 + idx, collapse);
434                        }
435                    }
436                }
437            }
438        }
439        
440        Ok(self.rebuild_mesh(&vertices, &faces))
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447    use nalgebra::Point3;
448
449    #[test]
450    fn test_quadric_error_simplifier_creation() {
451        let simplifier = QuadricErrorSimplifier::new();
452        assert!(simplifier.preserve_boundary);
453        assert!(simplifier.max_edge_length.is_none());
454    }
455
456    #[test]
457    fn test_plane_computation() {
458        let simplifier = QuadricErrorSimplifier::new();
459        let v0 = Point3::new(0.0, 0.0, 0.0);
460        let v1 = Point3::new(1.0, 0.0, 0.0);
461        let v2 = Point3::new(0.0, 1.0, 0.0);
462        
463        let plane = simplifier.compute_plane(&v0, &v1, &v2);
464        
465        // Should be z = 0 plane: 0x + 0y + 1z + 0 = 0
466        assert!((plane[0]).abs() < 1e-6);
467        assert!((plane[1]).abs() < 1e-6);
468        assert!((plane[2] - 1.0).abs() < 1e-6);
469        assert!((plane[3]).abs() < 1e-6);
470    }
471
472    #[test]
473    fn test_empty_mesh() {
474        let simplifier = QuadricErrorSimplifier::new();
475        let mesh = TriangleMesh::new();
476        
477        let result = simplifier.simplify(&mesh, 0.5);
478        assert!(result.is_err());
479    }
480
481    #[test]
482    fn test_zero_reduction() {
483        let simplifier = QuadricErrorSimplifier::new();
484        let vertices = vec![
485            Point3::new(0.0, 0.0, 0.0),
486            Point3::new(1.0, 0.0, 0.0),
487            Point3::new(0.5, 1.0, 0.0),
488        ];
489        let faces = vec![[0, 1, 2]];
490        let mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
491        
492        let result = simplifier.simplify(&mesh, 0.0).unwrap();
493        assert_eq!(result.vertex_count(), 3);
494        assert_eq!(result.face_count(), 1);
495    }
496
497    #[test]
498    fn test_invalid_reduction_ratio() {
499        let simplifier = QuadricErrorSimplifier::new();
500        let vertices = vec![
501            Point3::new(0.0, 0.0, 0.0),
502            Point3::new(1.0, 0.0, 0.0),
503            Point3::new(0.5, 1.0, 0.0),
504        ];
505        let faces = vec![[0, 1, 2]];
506        let mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
507        
508        assert!(simplifier.simplify(&mesh, -0.1).is_err());
509        assert!(simplifier.simplify(&mesh, 1.1).is_err());
510    }
511
512    #[test]
513    fn test_quadric_matrix_computation() {
514        let simplifier = QuadricErrorSimplifier::new();
515        let plane = Vector4::new(1.0, 0.0, 0.0, -1.0); // x = 1 plane
516        
517        let quadric = simplifier.plane_to_quadric(&plane);
518        
519        // Check diagonal elements
520        assert!((quadric[(0, 0)] - 1.0).abs() < 1e-10);
521        assert!((quadric[(1, 1)] - 0.0).abs() < 1e-10);
522        assert!((quadric[(2, 2)] - 0.0).abs() < 1e-10);
523        assert!((quadric[(3, 3)] - 1.0).abs() < 1e-10);
524    }
525
526    #[test]
527    fn test_tetrahedron_simplification() {
528        let simplifier = QuadricErrorSimplifier::new();
529        // Create a tetrahedron
530        let vertices = vec![
531            Point3::new(0.0, 0.0, 0.0),
532            Point3::new(1.0, 0.0, 0.0),
533            Point3::new(0.5, 1.0, 0.0),
534            Point3::new(0.5, 0.5, 1.0),
535        ];
536        let faces = vec![
537            [0, 1, 2],
538            [0, 1, 3],
539            [0, 2, 3],
540            [1, 2, 3],
541        ];
542        let mesh = TriangleMesh::from_vertices_and_faces(vertices, faces);
543        let simplified = simplifier.simplify(&mesh, 0.5).unwrap();
544        // Should have fewer faces
545        assert!(simplified.face_count() <= mesh.face_count());
546        assert!(simplified.vertex_count() <= mesh.vertex_count());
547    }
548
549    #[test]
550    fn test_grid_boundary_preservation() {
551        // Reproduce issue #52: 11x11 grid mesh simplification
552        let simplifier = QuadricErrorSimplifier::new();
553
554        // Create 11x11 grid at Z=-2000, spacing 400 units
555        let grid_size = 11;
556        let spacing = 400.0;
557        let z = -2000.0;
558
559        let mut vertices = Vec::new();
560        for y in 0..grid_size {
561            for x in 0..grid_size {
562                vertices.push(Point3::new(
563                    x as f32 * spacing,
564                    y as f32 * spacing,
565                    z,
566                ));
567            }
568        }
569
570        // Generate triangle faces for the grid
571        let mut faces = Vec::new();
572        for y in 0..(grid_size - 1) {
573            for x in 0..(grid_size - 1) {
574                let top_left = y * grid_size + x;
575                let top_right = top_left + 1;
576                let bottom_left = (y + 1) * grid_size + x;
577                let bottom_right = bottom_left + 1;
578
579                // Two triangles per quad
580                faces.push([top_left, bottom_left, top_right]);
581                faces.push([top_right, bottom_left, bottom_right]);
582            }
583        }
584
585        let mesh = TriangleMesh::from_vertices_and_faces(vertices.clone(), faces.clone());
586
587        println!("Original mesh: {} vertices, {} faces", mesh.vertex_count(), mesh.face_count());
588
589        // Identify original boundary vertices (edges of the grid)
590        let mut original_boundary_verts = std::collections::HashSet::new();
591        for i in 0..grid_size {
592            // Top edge
593            original_boundary_verts.insert(i);
594            // Bottom edge
595            original_boundary_verts.insert((grid_size - 1) * grid_size + i);
596            // Left edge
597            original_boundary_verts.insert(i * grid_size);
598            // Right edge
599            original_boundary_verts.insert(i * grid_size + (grid_size - 1));
600        }
601
602        println!("Original boundary vertices: {}", original_boundary_verts.len());
603
604        // Debug: Check boundary edge detection
605        let boundary_edges = simplifier.find_boundary_edges(&mesh);
606        println!("Detected boundary edges: {}", boundary_edges.len());
607
608        // Count unique vertices in boundary edges
609        let mut detected_boundary_verts = std::collections::HashSet::new();
610        for &(v1, v2) in &boundary_edges {
611            detected_boundary_verts.insert(v1);
612            detected_boundary_verts.insert(v2);
613        }
614        println!("Detected boundary vertices: {}", detected_boundary_verts.len());
615
616        // Simplify with 50% reduction
617        let simplified = simplifier.simplify(&mesh, 0.5).unwrap();
618
619        println!("Simplified mesh: {} vertices, {} faces", simplified.vertex_count(), simplified.face_count());
620
621        // Check if boundary vertices are preserved
622        // We need to check if the boundary vertices' positions are still in the simplified mesh
623        let mut boundary_positions = std::collections::HashSet::new();
624        for &idx in &original_boundary_verts {
625            let pos = vertices[idx];
626            boundary_positions.insert((
627                (pos.x * 100.0) as i32,
628                (pos.y * 100.0) as i32,
629                (pos.z * 100.0) as i32,
630            ));
631        }
632
633        let mut simplified_positions = std::collections::HashSet::new();
634        for &pos in &simplified.vertices {
635            simplified_positions.insert((
636                (pos.x * 100.0) as i32,
637                (pos.y * 100.0) as i32,
638                (pos.z * 100.0) as i32,
639            ));
640        }
641
642        let preserved_boundary_count = boundary_positions.intersection(&simplified_positions).count();
643        println!("Preserved boundary vertices: {}/{}", preserved_boundary_count, boundary_positions.len());
644
645        // Boundary vertices should be preserved when preserve_boundary is true
646        // We expect all or most boundary vertices to remain
647        assert!(preserved_boundary_count as f32 / boundary_positions.len() as f32 > 0.9,
648            "Expected at least 90% of boundary vertices to be preserved, but only {}% were preserved",
649            (preserved_boundary_count as f32 / boundary_positions.len() as f32 * 100.0));
650    }
651}