microcad_core/geo3d/
mesh.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::*;
5use cgmath::ElementWise;
6use manifold_rs::{Manifold, Mesh};
7
8/// Triangle mesh
9#[derive(Default, Clone)]
10pub struct TriangleMesh {
11    /// Mesh Vertices
12    pub vertices: Vec<Vertex>,
13    /// Triangle indices
14    pub triangle_indices: Vec<Triangle<u32>>,
15}
16
17/// Triangle iterator state.
18pub struct Triangles<'a> {
19    triangle_mesh: &'a TriangleMesh,
20    index: usize,
21}
22
23impl<'a> Iterator for Triangles<'a> {
24    type Item = Triangle<&'a Vertex>;
25
26    fn next(&mut self) -> Option<Self::Item> {
27        if self.index < self.triangle_mesh.triangle_indices.len() {
28            let t = self.triangle_mesh.triangle_indices[self.index];
29            self.index += 1;
30            Some(Triangle(
31                &self.triangle_mesh.vertices[t.0 as usize],
32                &self.triangle_mesh.vertices[t.1 as usize],
33                &self.triangle_mesh.vertices[t.2 as usize],
34            ))
35        } else {
36            None
37        }
38    }
39}
40
41impl TriangleMesh {
42    /// Is this mesh empty?
43    pub fn is_empty(&self) -> bool {
44        self.vertices.is_empty() || self.triangle_indices.is_empty()
45    }
46
47    /// Clear mesh.
48    pub fn clear(&mut self) {
49        self.vertices.clear();
50        self.triangle_indices.clear();
51    }
52
53    /// Fetch triangles.
54    pub fn fetch_triangles(&self) -> Vec<Triangle<Vertex>> {
55        self.triangle_indices
56            .iter()
57            .map(|t| {
58                Triangle(
59                    self.vertices[t.0 as usize],
60                    self.vertices[t.1 as usize],
61                    self.vertices[t.2 as usize],
62                )
63            })
64            .collect()
65    }
66
67    /// Append a triangle mesh.
68    pub fn append(&mut self, other: &TriangleMesh) {
69        let offset = self.vertices.len() as u32;
70        self.vertices.append(&mut other.vertices.clone());
71        self.triangle_indices.extend(
72            other
73                .triangle_indices
74                .iter()
75                .map(|t| Triangle(t.0 + offset, t.1 + offset, t.2 + offset)),
76        )
77    }
78
79    /// Triangles iterator.
80    pub fn triangles(&'_ self) -> Triangles<'_> {
81        Triangles {
82            triangle_mesh: self,
83            index: 0,
84        }
85    }
86
87    /// Convert mesh to manifold.
88    pub fn to_manifold(&self) -> Manifold {
89        let vertices = self
90            .vertices
91            .iter()
92            .flat_map(|v| vec![v.pos.x as f32, v.pos.y as f32, v.pos.z as f32])
93            .collect::<Vec<_>>();
94
95        let triangle_indices = self
96            .triangle_indices
97            .iter()
98            .flat_map(|t| vec![t.0, t.1, t.2])
99            .collect::<Vec<_>>();
100
101        assert_eq!(vertices.len(), self.vertices.len() * 3);
102        assert_eq!(triangle_indices.len(), self.triangle_indices.len() * 3);
103
104        Manifold::from_mesh(Mesh::new(&vertices, &triangle_indices))
105    }
106
107    /// Calculate volume of mesh.
108    pub fn volume(&self) -> f64 {
109        self.triangles()
110            .map(|t| t.signed_volume())
111            .sum::<f64>()
112            .abs()
113    }
114
115    /// Fetch a vertex triangle from index triangle.
116    pub fn fetch_triangle(&self, tri: Triangle<u32>) -> Triangle<&Vertex> {
117        Triangle(
118            &self.vertices[tri.0 as usize],
119            &self.vertices[tri.1 as usize],
120            &self.vertices[tri.2 as usize],
121        )
122    }
123
124    /// TriangleMesh.
125    pub fn repair(&mut self, bounds: &Bounds3D) {
126        // 1. Merge duplicate vertices using a spatial hash map (or hashmap keyed on quantized position)
127
128        let min = bounds.min;
129        let inv_size = 1.0 / (bounds.max - bounds.min);
130
131        // Quantize vertex positions to grid to group duplicates
132        let quantize = |pos: &Vec3| {
133            let mapped = (pos - min).mul_element_wise(inv_size) * (u32::MAX as Scalar);
134            (
135                mapped.x.floor() as u32,
136                mapped.y.floor() as u32,
137                mapped.z.floor() as u32,
138            )
139        };
140
141        let mut vertex_map: std::collections::HashMap<(u32, u32, u32), u32> =
142            std::collections::HashMap::new();
143        let mut new_vertices: Vec<Vertex> = Vec::with_capacity(self.vertices.len());
144        let mut remap: Vec<u32> = vec![0; self.vertices.len()];
145
146        for (i, vertex) in self.vertices.iter().enumerate() {
147            let key = quantize(&vertex.pos);
148            if let Some(&existing_idx) = vertex_map.get(&key) {
149                // Duplicate vertex found
150                remap[i] = existing_idx;
151            } else {
152                // New unique vertex
153                let new_idx = new_vertices.len() as u32;
154                new_vertices.push(*vertex);
155                vertex_map.insert(key, new_idx);
156                remap[i] = new_idx;
157            }
158        }
159
160        self.vertices = new_vertices;
161
162        // 2. Remap triangle indices and remove degenerate triangles (zero area or repeated vertices)
163        let mut new_triangles = Vec::with_capacity(self.triangle_indices.len());
164
165        for tri in &self.triangle_indices {
166            let tri_idx = crate::Triangle(
167                remap[tri.0 as usize],
168                remap[tri.1 as usize],
169                remap[tri.2 as usize],
170            );
171
172            if tri_idx.is_degenerated() {
173                continue;
174            }
175
176            // Optional: check zero-area triangle by computing cross product
177            let tri = self.fetch_triangle(tri_idx);
178
179            if tri.area() < 1e-8 {
180                continue; // Degenerate triangle
181            }
182
183            new_triangles.push(tri_idx);
184        }
185
186        self.triangle_indices = new_triangles;
187    }
188}
189
190impl CalcBounds3D for TriangleMesh {
191    fn calc_bounds_3d(&self) -> Bounds3D {
192        self.vertices.iter().map(|vertex| vertex.pos).collect()
193    }
194}
195
196impl From<Mesh> for TriangleMesh {
197    fn from(mesh: Mesh) -> Self {
198        let vertices = mesh.vertices();
199        let indices = mesh.indices();
200
201        // TODO: We could use unsafe std::ptr::copy and cast::transmute to avoid deep copy
202        // of vertices and indices
203
204        TriangleMesh {
205            vertices: (0..vertices.len())
206                .step_by(3)
207                .map(|i| Vertex {
208                    pos: Vec3::new(
209                        vertices[i] as f64,
210                        vertices[i + 1] as f64,
211                        vertices[i + 2] as f64,
212                    ),
213                    normal: Vec3::new(0.0, 0.0, 0.0),
214                })
215                .collect(),
216            triangle_indices: (0..indices.len())
217                .step_by(3)
218                .map(|i| Triangle(indices[i], indices[i + 1], indices[i + 2]))
219                .collect(),
220        }
221    }
222}
223
224impl From<TriangleMesh> for Mesh {
225    fn from(mesh: TriangleMesh) -> Self {
226        let mut vertices = Vec::new();
227        let mut indices = Vec::new();
228
229        for v in &mesh.vertices {
230            vertices.push(v.pos.x as f32);
231            vertices.push(v.pos.y as f32);
232            vertices.push(v.pos.z as f32);
233        }
234
235        for t in &mesh.triangle_indices {
236            indices.push(t.0);
237            indices.push(t.1);
238            indices.push(t.2);
239        }
240
241        Mesh::new(vertices.as_slice(), indices.as_slice())
242    }
243}
244
245impl From<Manifold> for TriangleMesh {
246    fn from(manifold: Manifold) -> Self {
247        TriangleMesh::from(manifold.to_mesh())
248    }
249}
250
251impl Transformed3D for TriangleMesh {
252    fn transformed_3d(&self, mat: &Mat4) -> Self {
253        let rot_mat = crate::Mat3::from_cols(mat.x.truncate(), mat.y.truncate(), mat.z.truncate());
254        Self {
255            vertices: self
256                .vertices
257                .iter()
258                .map(|v| Vertex {
259                    pos: (mat * v.pos.extend(1.0)).truncate(),
260                    normal: rot_mat * v.normal,
261                })
262                .collect(),
263            triangle_indices: self.triangle_indices.clone(),
264        }
265    }
266}
267
268impl WithBounds3D<TriangleMesh> {
269    /// Update bounds and repair mesh.
270    pub fn repair(&mut self) {
271        self.update_bounds();
272        self.inner.repair(&self.bounds);
273    }
274}
275
276impl From<Geometry3D> for TriangleMesh {
277    fn from(geo: Geometry3D) -> Self {
278        match geo {
279            Geometry3D::Mesh(triangle_mesh) => triangle_mesh,
280            Geometry3D::Manifold(manifold) => manifold.to_mesh().into(),
281            Geometry3D::Collection(ref collection) => collection.into(),
282        }
283    }
284}
285
286impl From<&Geometries3D> for TriangleMesh {
287    fn from(geo: &Geometries3D) -> Self {
288        let mut mesh = TriangleMesh::default();
289        geo.iter()
290            .for_each(|geo| mesh.append(&geo.as_ref().clone().into()));
291        mesh
292    }
293}
294
295#[test]
296fn test_triangle_mesh_transform() {
297    let mesh = TriangleMesh {
298        vertices: vec![
299            Vertex {
300                pos: Vec3::new(0.0, 0.0, 0.0),
301                normal: Vec3::new(0.0, 0.0, 1.0),
302            },
303            Vertex {
304                pos: Vec3::new(1.0, 0.0, 0.0),
305                normal: Vec3::new(0.0, 0.0, 1.0),
306            },
307            Vertex {
308                pos: Vec3::new(0.0, 1.0, 0.0),
309                normal: Vec3::new(0.0, 0.0, 1.0),
310            },
311        ],
312        triangle_indices: vec![Triangle(0, 1, 2)],
313    };
314
315    let mesh = mesh.transformed_3d(&crate::Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0)));
316
317    assert_eq!(mesh.vertices[0].pos, Vec3::new(1.0, 2.0, 3.0));
318    assert_eq!(mesh.vertices[0].normal, Vec3::new(0.0, 0.0, 1.0));
319    assert_eq!(mesh.vertices[1].pos, Vec3::new(2.0, 2.0, 3.0));
320    assert_eq!(mesh.vertices[1].normal, Vec3::new(0.0, 0.0, 1.0));
321    assert_eq!(mesh.vertices[2].pos, Vec3::new(1.0, 3.0, 3.0));
322    assert_eq!(mesh.vertices[2].normal, Vec3::new(0.0, 0.0, 1.0));
323}