alum/decimate/
edge_length.rs

1use crate::{DotProductAdaptor, Error, HH, PolyMeshT, VH};
2use std::ops::{Mul, Sub};
3
4use super::Decimater;
5
6/// Decimater implementation that prioritizes the collapse of short edges.
7pub struct EdgeLengthDecimater<const DIM: usize, A>
8where
9    A: DotProductAdaptor<DIM>,
10    A::Vector: Sub<Output = A::Vector>,
11    A::Scalar: PartialOrd,
12{
13    max_length: A::Scalar,
14}
15
16impl<const DIM: usize, A> EdgeLengthDecimater<DIM, A>
17where
18    A: DotProductAdaptor<DIM>,
19    A::Vector: Sub<Output = A::Vector>,
20    A::Scalar: PartialOrd + Mul<Output = A::Scalar>,
21{
22    /// Create a new EdgeLengthDecimater.
23    ///
24    /// Edges longer than `max_length` will not be collapsed.
25    pub fn new(max_length: A::Scalar) -> Self {
26        EdgeLengthDecimater {
27            max_length: max_length * max_length,
28        }
29    }
30}
31
32impl<const DIM: usize, A> Decimater<PolyMeshT<DIM, A>> for EdgeLengthDecimater<DIM, A>
33where
34    A: DotProductAdaptor<DIM>,
35    A::Vector: Sub<Output = A::Vector>,
36    A::Scalar: PartialOrd + Mul<Output = A::Scalar>,
37{
38    type Cost = A::Scalar;
39
40    /// Cost of collapsing an edge, i.e. the squared length of the edge.
41    fn collapse_cost(&self, mesh: &PolyMeshT<DIM, A>, h: HH) -> Option<Self::Cost> {
42        let points = mesh.points();
43        let points = points.try_borrow().ok()?;
44        let elen = mesh.calc_edge_length_sqr(h.edge(), &points);
45        if elen < self.max_length {
46            Some(elen)
47        } else {
48            None
49        }
50    }
51
52    fn before_collapse(&mut self, _mesh: &PolyMeshT<DIM, A>, _h: HH) -> Result<(), Error> {
53        Ok(()) // Do nothing.
54    }
55
56    fn after_collapse(&mut self, _mesh: &PolyMeshT<DIM, A>, _v: VH) -> Result<(), Error> {
57        Ok(()) // Do nothing.
58    }
59}
60
61#[cfg(test)]
62mod test {
63    use super::EdgeLengthDecimater;
64    use crate::{HasDecimation, HasIterators, HasTopology, obj::test::bunny_mesh};
65
66    #[test]
67    fn t_bunny_edge_length_decimate() {
68        let mut mesh = bunny_mesh();
69        let (nv, ne, nf) = (mesh.num_vertices(), mesh.num_edges(), mesh.num_faces());
70        let nloops_before = {
71            let mut visited = mesh.create_halfedge_prop(false);
72            let mut visited = visited.try_borrow_mut().unwrap();
73            mesh.halfedges().fold(0usize, |count, h| {
74                if !h.is_boundary(&mesh) || visited[h] {
75                    count
76                } else {
77                    for h2 in mesh.loop_ccw_iter(h) {
78                        visited[h2] = true;
79                    }
80                    count + 1
81                }
82            })
83        };
84        let mut decimater = EdgeLengthDecimater::new(0.1);
85        mesh.decimate(&mut decimater, 2000)
86            .expect("Cannot decimate");
87        mesh.garbage_collection()
88            .expect("Failed to garbage collect");
89        mesh.check_topology().expect("Topological errors found");
90        assert_eq!(nv - 2000, mesh.num_vertices());
91        assert!(mesh.num_edges() < ne);
92        assert!(mesh.num_faces() < nf);
93        let nloops_after = {
94            let mut visited = mesh.create_halfedge_prop(false);
95            let mut visited = visited.try_borrow_mut().unwrap();
96            mesh.halfedges().fold(0usize, |count, h| {
97                if !h.is_boundary(&mesh) || visited[h] {
98                    count
99                } else {
100                    for h2 in mesh.loop_ccw_iter(h) {
101                        visited[h2] = true;
102                    }
103                    count + 1
104                }
105            })
106        };
107        assert_eq!(nloops_before, nloops_after);
108    }
109}