Skip to main content

landmark_common/
mesh_simplification.rs

1//! Mesh simplification and preprocessing tools for improving performance
2//!
3//! This module provides various algorithms to reduce mesh complexity before rasterization,
4//! which can significantly improve navigation mesh generation performance.
5
6use super::mesh::TriMesh;
7use crate::MeshError;
8use glam::Vec3;
9use std::collections::{HashMap, HashSet};
10
11/// Configuration for mesh simplification operations
12#[derive(Debug, Clone)]
13pub struct SimplificationConfig {
14    /// Target reduction ratio (0.0 = no reduction, 1.0 = remove everything)
15    pub reduction_ratio: f32,
16    /// Maximum allowed error tolerance for simplification
17    pub max_error: f32,
18    /// Whether to preserve mesh boundaries during simplification
19    pub preserve_boundaries: bool,
20    /// Whether to preserve topology (no holes created)
21    pub preserve_topology: bool,
22    /// Minimum triangle area threshold (smaller triangles removed)
23    pub min_triangle_area: f32,
24    /// Vertex welding threshold for duplicate removal
25    pub vertex_weld_threshold: f32,
26}
27
28impl Default for SimplificationConfig {
29    fn default() -> Self {
30        Self {
31            reduction_ratio: 0.5,         // 50% reduction
32            max_error: 0.01,              // 1cm error tolerance
33            preserve_boundaries: true,    // Keep mesh boundaries intact
34            preserve_topology: true,      // Don't create holes
35            min_triangle_area: 0.0001,    // Remove very small triangles
36            vertex_weld_threshold: 0.001, // 1mm welding threshold
37        }
38    }
39}
40
41/// Result of a mesh simplification operation
42#[derive(Debug)]
43pub struct SimplificationResult {
44    pub mesh: TriMesh,
45    pub stats: SimplificationStats,
46}
47
48/// Statistics from mesh simplification
49#[derive(Debug, Default)]
50pub struct SimplificationStats {
51    pub original_vertices: usize,
52    pub original_triangles: usize,
53    pub final_vertices: usize,
54    pub final_triangles: usize,
55    pub vertices_welded: usize,
56    pub degenerate_removed: usize,
57    pub small_triangles_removed: usize,
58    pub max_error_introduced: f32,
59}
60
61impl SimplificationStats {
62    /// Calculates the reduction percentage achieved
63    pub fn vertex_reduction_percent(&self) -> f32 {
64        if self.original_vertices == 0 {
65            return 0.0;
66        }
67        ((self.original_vertices - self.final_vertices) as f32 / self.original_vertices as f32)
68            * 100.0
69    }
70
71    /// Calculates the triangle reduction percentage achieved
72    pub fn triangle_reduction_percent(&self) -> f32 {
73        if self.original_triangles == 0 {
74            return 0.0;
75        }
76        ((self.original_triangles - self.final_triangles) as f32 / self.original_triangles as f32)
77            * 100.0
78    }
79}
80
81/// Edge in the mesh for edge collapse operations
82#[derive(Debug, Clone, PartialEq, Eq, Hash)]
83struct Edge {
84    v0: usize,
85    v1: usize,
86}
87
88impl Edge {
89    fn new(v0: usize, v1: usize) -> Self {
90        // Ensure consistent ordering
91        if v0 < v1 {
92            Self { v0, v1 }
93        } else {
94            Self { v0: v1, v1: v0 }
95        }
96    }
97}
98
99/// Quadric error metric for vertex positioning
100#[derive(Debug, Clone, Default)]
101struct QuadricError {
102    /// 4x4 quadric matrix flattened to 10 unique values (symmetric)
103    matrix: [f64; 10],
104}
105
106impl QuadricError {
107    /// Creates a new quadric from a triangle plane
108    fn from_triangle(v0: &Vec3, v1: &Vec3, v2: &Vec3) -> Self {
109        // Calculate triangle normal and plane equation ax + by + cz + d = 0
110        let edge1 = *v1 - *v0;
111        let edge2 = *v2 - *v0;
112        let normal = edge1.cross(edge2).normalize();
113
114        let a = normal.x as f64;
115        let b = normal.y as f64;
116        let c = normal.z as f64;
117        let d = -(a * v0.x as f64 + b * v0.y as f64 + c * v0.z as f64);
118
119        // Build quadric matrix from plane equation
120        let mut matrix = [0.0; 10];
121        matrix[0] = a * a; // a²
122        matrix[1] = a * b; // ab
123        matrix[2] = a * c; // ac
124        matrix[3] = a * d; // ad
125        matrix[4] = b * b; // b²
126        matrix[5] = b * c; // bc
127        matrix[6] = b * d; // bd
128        matrix[7] = c * c; // c²
129        matrix[8] = c * d; // cd
130        matrix[9] = d * d; // d²
131
132        Self { matrix }
133    }
134
135    /// Adds another quadric to this one
136    fn add(&mut self, other: &QuadricError) {
137        for i in 0..10 {
138            self.matrix[i] += other.matrix[i];
139        }
140    }
141
142    /// Calculates the error for a given vertex position
143    fn error(&self, v: &Vec3) -> f64 {
144        let x = v.x as f64;
145        let y = v.y as f64;
146        let z = v.z as f64;
147
148        // Evaluate quadric: v^T * Q * v
149        self.matrix[0] * x * x
150            + 2.0 * self.matrix[1] * x * y
151            + 2.0 * self.matrix[2] * x * z
152            + 2.0 * self.matrix[3] * x
153            + self.matrix[4] * y * y
154            + 2.0 * self.matrix[5] * y * z
155            + 2.0 * self.matrix[6] * y
156            + self.matrix[7] * z * z
157            + 2.0 * self.matrix[8] * z
158            + self.matrix[9]
159    }
160}
161
162/// Mesh simplifier with multiple algorithms
163pub struct MeshSimplifier {
164    config: SimplificationConfig,
165}
166
167impl MeshSimplifier {
168    pub fn new(config: SimplificationConfig) -> Self {
169        Self { config }
170    }
171
172    pub fn with_defaults() -> Self {
173        Self::new(SimplificationConfig::default())
174    }
175
176    /// Simplifies a mesh using the configured algorithm
177    pub fn simplify(&self, mesh: &TriMesh) -> Result<SimplificationResult, MeshError> {
178        let mut stats = SimplificationStats {
179            original_vertices: mesh.vert_count,
180            original_triangles: mesh.tri_count,
181            ..Default::default()
182        };
183
184        if mesh.tri_count == 0 {
185            return Ok(SimplificationResult {
186                mesh: mesh.clone(),
187                stats,
188            });
189        }
190
191        // Step 1: Preprocess mesh (weld vertices, remove degenerates)
192        let mut simplified_mesh = self.preprocess_mesh(mesh, &mut stats)?;
193
194        // Step 2: Apply main simplification algorithm
195        if self.config.reduction_ratio > 0.0 {
196            simplified_mesh = self.edge_collapse_simplification(&simplified_mesh, &mut stats)?;
197        }
198
199        // Step 3: Post-process (remove small triangles, final cleanup)
200        simplified_mesh = self.postprocess_mesh(&simplified_mesh, &mut stats)?;
201
202        stats.final_vertices = simplified_mesh.vert_count;
203        stats.final_triangles = simplified_mesh.tri_count;
204
205        Ok(SimplificationResult {
206            mesh: simplified_mesh,
207            stats,
208        })
209    }
210
211    /// Preprocesses the mesh by welding vertices and removing degenerate triangles
212    fn preprocess_mesh(
213        &self,
214        mesh: &TriMesh,
215        stats: &mut SimplificationStats,
216    ) -> Result<TriMesh, MeshError> {
217        // Step 1: Weld nearby vertices
218        let (vertices, vertex_mapping) = self.weld_vertices(&mesh.vertices, mesh.vert_count)?;
219        stats.vertices_welded = mesh.vert_count - vertices.len() / 3;
220
221        // Step 2: Remap indices and remove degenerate triangles
222        let mut indices = Vec::new();
223        let mut triangle_count = 0;
224
225        for i in 0..mesh.tri_count {
226            let base = i * 3;
227            let i0 = vertex_mapping[mesh.indices[base] as usize];
228            let i1 = vertex_mapping[mesh.indices[base + 1] as usize];
229            let i2 = vertex_mapping[mesh.indices[base + 2] as usize];
230
231            // Skip degenerate triangles
232            if i0 != i1 && i1 != i2 && i0 != i2 {
233                // Check for valid triangle area
234                let v0 = Vec3::new(vertices[i0 * 3], vertices[i0 * 3 + 1], vertices[i0 * 3 + 2]);
235                let v1 = Vec3::new(vertices[i1 * 3], vertices[i1 * 3 + 1], vertices[i1 * 3 + 2]);
236                let v2 = Vec3::new(vertices[i2 * 3], vertices[i2 * 3 + 1], vertices[i2 * 3 + 2]);
237
238                let area = Self::triangle_area(&v0, &v1, &v2);
239                if area >= self.config.min_triangle_area {
240                    indices.push(i0 as i32);
241                    indices.push(i1 as i32);
242                    indices.push(i2 as i32);
243                    triangle_count += 1;
244                } else {
245                    stats.small_triangles_removed += 1;
246                }
247            } else {
248                stats.degenerate_removed += 1;
249            }
250        }
251
252        let vert_count = vertices.len() / 3;
253        Ok(TriMesh {
254            vertices,
255            indices,
256            vert_count,
257            tri_count: triangle_count,
258        })
259    }
260
261    /// Welds nearby vertices together
262    fn weld_vertices(
263        &self,
264        vertices: &[f32],
265        vert_count: usize,
266    ) -> Result<(Vec<f32>, Vec<usize>), MeshError> {
267        let mut welded_vertices = Vec::new();
268        let mut vertex_mapping = vec![0; vert_count];
269
270        for i in 0..vert_count {
271            let v = Vec3::new(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]);
272
273            // Try to find an existing vertex to weld with
274            let mut found = false;
275            for (j, existing_idx) in (0..welded_vertices.len() / 3).enumerate() {
276                let existing = Vec3::new(
277                    welded_vertices[existing_idx * 3],
278                    welded_vertices[existing_idx * 3 + 1],
279                    welded_vertices[existing_idx * 3 + 2],
280                );
281
282                if (v - existing).length() <= self.config.vertex_weld_threshold {
283                    vertex_mapping[i] = j;
284                    found = true;
285                    break;
286                }
287            }
288
289            if !found {
290                let new_idx = welded_vertices.len() / 3;
291                welded_vertices.push(v.x);
292                welded_vertices.push(v.y);
293                welded_vertices.push(v.z);
294                vertex_mapping[i] = new_idx;
295            }
296        }
297
298        Ok((welded_vertices, vertex_mapping))
299    }
300
301    /// Applies edge collapse simplification using quadric error metrics
302    fn edge_collapse_simplification(
303        &self,
304        mesh: &TriMesh,
305        stats: &mut SimplificationStats,
306    ) -> Result<TriMesh, MeshError> {
307        if mesh.tri_count == 0 {
308            return Ok(mesh.clone());
309        }
310
311        // Calculate target triangle count
312        let target_triangles =
313            ((mesh.tri_count as f32) * (1.0 - self.config.reduction_ratio)) as usize;
314        if target_triangles >= mesh.tri_count {
315            return Ok(mesh.clone());
316        }
317
318        // Build adjacency information
319        let mut edge_to_triangles: HashMap<Edge, Vec<usize>> = HashMap::new();
320        let mut vertex_to_triangles: HashMap<usize, Vec<usize>> = HashMap::new();
321
322        for i in 0..mesh.tri_count {
323            let base = i * 3;
324            let v0 = mesh.indices[base] as usize;
325            let v1 = mesh.indices[base + 1] as usize;
326            let v2 = mesh.indices[base + 2] as usize;
327
328            // Add edges
329            for &(va, vb) in &[(v0, v1), (v1, v2), (v2, v0)] {
330                let edge = Edge::new(va, vb);
331                edge_to_triangles.entry(edge).or_default().push(i);
332                vertex_to_triangles.entry(va).or_default().push(i);
333                vertex_to_triangles.entry(vb).or_default().push(i);
334            }
335        }
336
337        // Calculate quadric error metrics for each vertex
338        let mut vertex_quadrics = vec![QuadricError::default(); mesh.vert_count];
339
340        for i in 0..mesh.tri_count {
341            let base = i * 3;
342            let v0_idx = mesh.indices[base] as usize;
343            let v1_idx = mesh.indices[base + 1] as usize;
344            let v2_idx = mesh.indices[base + 2] as usize;
345
346            let v0 = Vec3::new(
347                mesh.vertices[v0_idx * 3],
348                mesh.vertices[v0_idx * 3 + 1],
349                mesh.vertices[v0_idx * 3 + 2],
350            );
351            let v1 = Vec3::new(
352                mesh.vertices[v1_idx * 3],
353                mesh.vertices[v1_idx * 3 + 1],
354                mesh.vertices[v1_idx * 3 + 2],
355            );
356            let v2 = Vec3::new(
357                mesh.vertices[v2_idx * 3],
358                mesh.vertices[v2_idx * 3 + 1],
359                mesh.vertices[v2_idx * 3 + 2],
360            );
361
362            let quadric = QuadricError::from_triangle(&v0, &v1, &v2);
363            vertex_quadrics[v0_idx].add(&quadric);
364            vertex_quadrics[v1_idx].add(&quadric);
365            vertex_quadrics[v2_idx].add(&quadric);
366        }
367
368        // Build priority queue of edges to collapse
369        let mut edge_costs: Vec<(f64, Edge, Vec3)> = Vec::new();
370
371        for edge in edge_to_triangles.keys() {
372            if self.config.preserve_boundaries {
373                // Skip boundary edges (edges with only one adjacent triangle)
374                if edge_to_triangles[edge].len() < 2 {
375                    continue;
376                }
377            }
378
379            let v0 = Vec3::new(
380                mesh.vertices[edge.v0 * 3],
381                mesh.vertices[edge.v0 * 3 + 1],
382                mesh.vertices[edge.v0 * 3 + 2],
383            );
384            let v1 = Vec3::new(
385                mesh.vertices[edge.v1 * 3],
386                mesh.vertices[edge.v1 * 3 + 1],
387                mesh.vertices[edge.v1 * 3 + 2],
388            );
389
390            // Calculate optimal collapse position (midpoint for simplicity)
391            let collapse_pos = (v0 + v1) * 0.5;
392
393            // Calculate collapse cost using quadric error
394            let mut combined_quadric = vertex_quadrics[edge.v0].clone();
395            combined_quadric.add(&vertex_quadrics[edge.v1]);
396            let cost = combined_quadric.error(&collapse_pos);
397
398            edge_costs.push((cost, edge.clone(), collapse_pos));
399        }
400
401        // Sort by cost (lowest first)
402        edge_costs.sort_by(|a, b| a.0.total_cmp(&b.0));
403
404        // Perform edge collapses until target is reached
405        let mut active_triangles: HashSet<usize> = (0..mesh.tri_count).collect();
406        let mut vertex_positions: Vec<Vec3> = (0..mesh.vert_count)
407            .map(|i| {
408                Vec3::new(
409                    mesh.vertices[i * 3],
410                    mesh.vertices[i * 3 + 1],
411                    mesh.vertices[i * 3 + 2],
412                )
413            })
414            .collect();
415
416        let mut collapsed_edges = 0;
417        let max_collapses = mesh.tri_count - target_triangles;
418
419        for (cost, edge, collapse_pos) in edge_costs {
420            if collapsed_edges >= max_collapses {
421                break;
422            }
423
424            if cost > self.config.max_error as f64 {
425                break;
426            }
427
428            // Check if edge is still valid (both vertices exist)
429            if edge.v0 >= vertex_positions.len() || edge.v1 >= vertex_positions.len() {
430                continue;
431            }
432
433            // Remove triangles that use this edge
434            if let Some(triangles) = edge_to_triangles.get(&edge) {
435                for &tri_idx in triangles {
436                    active_triangles.remove(&tri_idx);
437                }
438                collapsed_edges += triangles.len();
439            }
440
441            // Update vertex position
442            vertex_positions[edge.v0] = collapse_pos;
443
444            // Mark second vertex as collapsed (reuse index for first vertex)
445            // This is a simplified approach - a full implementation would need
446            // more sophisticated vertex merging
447
448            stats.max_error_introduced = stats.max_error_introduced.max(cost as f32);
449        }
450
451        // Rebuild mesh from active triangles
452        let mut new_vertices = Vec::new();
453        let mut new_indices = Vec::new();
454        let mut vertex_map = HashMap::new();
455        let mut next_vertex_idx: i32 = 0;
456
457        for &tri_idx in &active_triangles {
458            let base = tri_idx * 3;
459            let mut triangle_indices: Vec<i32> = Vec::new();
460
461            for offset in 0..3 {
462                let old_idx = mesh.indices[base + offset] as usize;
463
464                if let Some(&new_idx) = vertex_map.get(&old_idx) {
465                    triangle_indices.push(new_idx);
466                } else {
467                    let pos = &vertex_positions[old_idx];
468                    new_vertices.push(pos.x);
469                    new_vertices.push(pos.y);
470                    new_vertices.push(pos.z);
471                    vertex_map.insert(old_idx, next_vertex_idx);
472                    triangle_indices.push(next_vertex_idx);
473                    next_vertex_idx += 1;
474                }
475            }
476
477            // Add triangle if it's still valid
478            if triangle_indices[0] != triangle_indices[1]
479                && triangle_indices[1] != triangle_indices[2]
480                && triangle_indices[0] != triangle_indices[2]
481            {
482                new_indices.push(triangle_indices[0]);
483                new_indices.push(triangle_indices[1]);
484                new_indices.push(triangle_indices[2]);
485            }
486        }
487
488        let vert_count = new_vertices.len() / 3;
489        let tri_count = new_indices.len() / 3;
490        Ok(TriMesh {
491            vertices: new_vertices,
492            indices: new_indices,
493            vert_count,
494            tri_count,
495        })
496    }
497
498    /// Post-processes the mesh with final cleanup
499    fn postprocess_mesh(
500        &self,
501        mesh: &TriMesh,
502        stats: &mut SimplificationStats,
503    ) -> Result<TriMesh, MeshError> {
504        // Remove any remaining degenerate or small triangles
505        let mut new_indices = Vec::new();
506        let mut triangle_count = 0;
507
508        for i in 0..mesh.tri_count {
509            let base = i * 3;
510            let i0 = mesh.indices[base] as usize;
511            let i1 = mesh.indices[base + 1] as usize;
512            let i2 = mesh.indices[base + 2] as usize;
513
514            if i0 != i1 && i1 != i2 && i0 != i2 {
515                let v0 = Vec3::new(
516                    mesh.vertices[i0 * 3],
517                    mesh.vertices[i0 * 3 + 1],
518                    mesh.vertices[i0 * 3 + 2],
519                );
520                let v1 = Vec3::new(
521                    mesh.vertices[i1 * 3],
522                    mesh.vertices[i1 * 3 + 1],
523                    mesh.vertices[i1 * 3 + 2],
524                );
525                let v2 = Vec3::new(
526                    mesh.vertices[i2 * 3],
527                    mesh.vertices[i2 * 3 + 1],
528                    mesh.vertices[i2 * 3 + 2],
529                );
530
531                let area = Self::triangle_area(&v0, &v1, &v2);
532                if area >= self.config.min_triangle_area {
533                    new_indices.push(mesh.indices[base]);
534                    new_indices.push(mesh.indices[base + 1]);
535                    new_indices.push(mesh.indices[base + 2]);
536                    triangle_count += 1;
537                } else {
538                    stats.small_triangles_removed += 1;
539                }
540            } else {
541                stats.degenerate_removed += 1;
542            }
543        }
544
545        Ok(TriMesh {
546            vertices: mesh.vertices.clone(),
547            indices: new_indices,
548            vert_count: mesh.vert_count,
549            tri_count: triangle_count,
550        })
551    }
552
553    /// Calculates the area of a triangle
554    fn triangle_area(v0: &Vec3, v1: &Vec3, v2: &Vec3) -> f32 {
555        let edge1 = *v1 - *v0;
556        let edge2 = *v2 - *v0;
557        edge1.cross(edge2).length() * 0.5
558    }
559}
560
561/// Utility functions for mesh preprocessing
562impl MeshSimplifier {
563    /// Analyzes mesh complexity and recommends simplification parameters
564    pub fn analyze_mesh(mesh: &TriMesh) -> MeshComplexityAnalysis {
565        let mut analysis = MeshComplexityAnalysis::default();
566
567        if mesh.tri_count == 0 {
568            return analysis;
569        }
570
571        analysis.vertex_count = mesh.vert_count;
572        analysis.triangle_count = mesh.tri_count;
573
574        // Calculate triangle areas
575        let mut total_area = 0.0;
576        let mut min_area = f32::MAX;
577        let mut max_area = f32::MIN;
578        let mut small_triangle_count = 0;
579
580        for i in 0..mesh.tri_count {
581            let base = i * 3;
582            let v0 = Vec3::new(
583                mesh.vertices[mesh.indices[base] as usize * 3],
584                mesh.vertices[mesh.indices[base] as usize * 3 + 1],
585                mesh.vertices[mesh.indices[base] as usize * 3 + 2],
586            );
587            let v1 = Vec3::new(
588                mesh.vertices[mesh.indices[base + 1] as usize * 3],
589                mesh.vertices[mesh.indices[base + 1] as usize * 3 + 1],
590                mesh.vertices[mesh.indices[base + 1] as usize * 3 + 2],
591            );
592            let v2 = Vec3::new(
593                mesh.vertices[mesh.indices[base + 2] as usize * 3],
594                mesh.vertices[mesh.indices[base + 2] as usize * 3 + 1],
595                mesh.vertices[mesh.indices[base + 2] as usize * 3 + 2],
596            );
597
598            let area = Self::triangle_area(&v0, &v1, &v2);
599            total_area += area;
600            min_area = min_area.min(area);
601            max_area = max_area.max(area);
602
603            if area < 0.001 {
604                // Very small triangles
605                small_triangle_count += 1;
606            }
607        }
608
609        analysis.average_triangle_area = total_area / mesh.tri_count as f32;
610        analysis.min_triangle_area = min_area;
611        analysis.max_triangle_area = max_area;
612        analysis.small_triangle_ratio = small_triangle_count as f32 / mesh.tri_count as f32;
613
614        // Calculate mesh density (triangles per unit area)
615        let (bmin, bmax) = mesh.calculate_bounds();
616        let bbox_area = (bmax.x - bmin.x) * (bmax.z - bmin.z);
617        if bbox_area > 0.0 {
618            analysis.triangle_density = mesh.tri_count as f32 / bbox_area;
619        }
620
621        // Recommend simplification based on analysis
622        analysis.recommended_reduction = analysis.calculate_recommended_reduction();
623
624        analysis
625    }
626}
627
628/// Analysis of mesh complexity for recommending simplification parameters
629#[derive(Debug, Default)]
630pub struct MeshComplexityAnalysis {
631    pub vertex_count: usize,
632    pub triangle_count: usize,
633    pub average_triangle_area: f32,
634    pub min_triangle_area: f32,
635    pub max_triangle_area: f32,
636    pub small_triangle_ratio: f32,
637    pub triangle_density: f32,
638    pub recommended_reduction: f32,
639}
640
641impl MeshComplexityAnalysis {
642    /// Calculates recommended reduction ratio based on mesh characteristics
643    fn calculate_recommended_reduction(&self) -> f32 {
644        let mut reduction: f32 = 0.0;
645
646        // High triangle count suggests aggressive reduction
647        if self.triangle_count > 100_000 {
648            reduction += 0.7;
649        } else if self.triangle_count > 50_000 {
650            reduction += 0.5;
651        } else if self.triangle_count > 10_000 {
652            reduction += 0.3;
653        }
654
655        // High density of small triangles suggests reduction
656        if self.small_triangle_ratio > 0.3 {
657            reduction += 0.3;
658        } else if self.small_triangle_ratio > 0.1 {
659            reduction += 0.2;
660        }
661
662        // High triangle density suggests reduction
663        if self.triangle_density > 1000.0 {
664            reduction += 0.2;
665        } else if self.triangle_density > 500.0 {
666            reduction += 0.1;
667        }
668
669        reduction.min(0.8) // Cap at 80% reduction
670    }
671}
672
673#[cfg(test)]
674mod tests {
675    use super::*;
676
677    fn create_test_mesh() -> TriMesh {
678        // Create a simple pyramid mesh
679        let mut mesh = TriMesh::new();
680
681        // Vertices (pyramid with square base)
682        let vertices = vec![
683            0.0, 0.0, 0.0, // 0: base corner
684            1.0, 0.0, 0.0, // 1: base corner
685            1.0, 0.0, 1.0, // 2: base corner
686            0.0, 0.0, 1.0, // 3: base corner
687            0.5, 1.0, 0.5, // 4: apex
688        ];
689
690        // Triangles
691        let indices = vec![
692            // Base (2 triangles)
693            0, 1, 2, 0, 2, 3, // Sides (4 triangles)
694            0, 4, 1, 1, 4, 2, 2, 4, 3, 3, 4, 0,
695        ];
696
697        mesh.vertices = vertices;
698        mesh.indices = indices;
699        mesh.vert_count = 5;
700        mesh.tri_count = 6;
701
702        mesh
703    }
704
705    #[test]
706    fn test_mesh_simplification_basic() {
707        let simplifier = MeshSimplifier::with_defaults();
708        let mesh = create_test_mesh();
709
710        let result = simplifier.simplify(&mesh).unwrap();
711
712        // Should have some reduction
713        assert!(result.stats.final_triangles <= result.stats.original_triangles);
714        assert!(result.stats.final_vertices <= result.stats.original_vertices);
715    }
716
717    #[test]
718    fn test_vertex_welding() {
719        let config = SimplificationConfig {
720            vertex_weld_threshold: 0.1,
721            reduction_ratio: 0.0, // No edge collapse, just welding
722            ..Default::default()
723        };
724        let simplifier = MeshSimplifier::new(config);
725
726        let mut mesh = create_test_mesh();
727
728        // Add duplicate vertices
729        mesh.vertices.extend_from_slice(&[0.05, 0.0, 0.0]); // Close to vertex 0
730        mesh.vert_count += 1;
731
732        let result = simplifier.simplify(&mesh).unwrap();
733
734        assert!(result.stats.vertices_welded > 0);
735    }
736
737    #[test]
738    fn test_mesh_analysis() {
739        let mesh = create_test_mesh();
740        let analysis = MeshSimplifier::analyze_mesh(&mesh);
741
742        assert_eq!(analysis.vertex_count, 5);
743        assert_eq!(analysis.triangle_count, 6);
744        assert!(analysis.average_triangle_area > 0.0);
745        assert!(analysis.recommended_reduction >= 0.0);
746    }
747
748    #[test]
749    fn test_degenerate_triangle_removal() {
750        let simplifier = MeshSimplifier::with_defaults();
751
752        let mut mesh = TriMesh::new();
753        mesh.vertices = vec![
754            0.0, 0.0, 0.0, // 0
755            1.0, 0.0, 0.0, // 1
756            0.0, 1.0, 0.0, // 2
757        ];
758        mesh.indices = vec![
759            0, 1, 2, // Valid triangle
760            0, 0, 1, // Degenerate (repeated vertex)
761        ];
762        mesh.vert_count = 3;
763        mesh.tri_count = 2;
764
765        let result = simplifier.simplify(&mesh).unwrap();
766
767        assert!(result.stats.degenerate_removed > 0);
768        assert_eq!(result.stats.final_triangles, 1);
769    }
770
771    #[test]
772    fn test_small_triangle_removal() {
773        let config = SimplificationConfig {
774            min_triangle_area: 0.1, // Remove triangles smaller than 0.1
775            reduction_ratio: 0.0,   // No edge collapse
776            ..Default::default()
777        };
778        let simplifier = MeshSimplifier::new(config);
779
780        let mut mesh = TriMesh::new();
781        mesh.vertices = vec![
782            0.0, 0.0, 0.0, // 0
783            1.0, 0.0, 0.0, // 1
784            0.0, 1.0, 0.0, // 2
785            0.01, 0.01, 0.0, // 3 (very close to 0)
786        ];
787        mesh.indices = vec![
788            0, 1, 2, // Large triangle
789            0, 1, 3, // Very small triangle
790        ];
791        mesh.vert_count = 4;
792        mesh.tri_count = 2;
793
794        let result = simplifier.simplify(&mesh).unwrap();
795
796        assert!(result.stats.small_triangles_removed > 0);
797    }
798}