microcad_core/geo3d/
triangle_mesh.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{Bounds3D, FetchBounds3D, Vec3};
5use manifold_rs::{Manifold, Mesh};
6
7/// Vertex
8#[derive(Clone, Copy, Debug)]
9pub struct Vertex {
10    /// position
11    pub pos: Vec3,
12    /// normal vector
13    pub normal: Vec3,
14}
15
16/// Triangle
17#[derive(Clone, Copy, Debug)]
18pub struct Triangle<T>(pub T, pub T, pub T);
19
20impl Triangle<Vertex> {
21    /// Get normal of triangle
22    pub fn normal(&self) -> Vec3 {
23        (self.2.pos - self.0.pos).cross(self.1.pos - self.0.pos)
24    }
25}
26
27impl Triangle<&Vertex> {
28    /// Get normal of triangle
29    pub fn normal(&self) -> Vec3 {
30        (self.2.pos - self.0.pos).cross(self.1.pos - self.0.pos)
31    }
32
33    /// Get signed volume of triangle
34    ///
35    /// <https://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up>
36    pub fn signed_volume(&self) -> f64 {
37        let v210 = self.2.pos.x * self.1.pos.y * self.0.pos.z;
38        let v120 = self.1.pos.x * self.2.pos.y * self.0.pos.z;
39        let v201 = self.2.pos.x * self.0.pos.y * self.1.pos.z;
40        let v021 = self.0.pos.x * self.2.pos.y * self.1.pos.z;
41        let v102 = self.1.pos.x * self.0.pos.y * self.2.pos.z;
42        let v012 = self.0.pos.x * self.1.pos.y * self.2.pos.z;
43
44        (1.0 / 6.0) * (-v210 + v120 + v201 - v021 - v102 + v012)
45    }
46}
47
48/// Triangle mesh
49#[derive(Default, Clone)]
50pub struct TriangleMesh {
51    /// Mesh Vertices
52    pub vertices: Vec<Vertex>,
53    /// Triangle indices
54    pub triangle_indices: Vec<Triangle<u32>>,
55}
56
57/// Triangle iterator state.
58pub struct Triangles<'a> {
59    triangle_mesh: &'a TriangleMesh,
60    index: usize,
61}
62
63impl<'a> Iterator for Triangles<'a> {
64    type Item = Triangle<&'a Vertex>;
65
66    fn next(&mut self) -> Option<Self::Item> {
67        if self.index < self.triangle_mesh.triangle_indices.len() {
68            let t = self.triangle_mesh.triangle_indices[self.index];
69            self.index += 1;
70            Some(Triangle(
71                &self.triangle_mesh.vertices[t.0 as usize],
72                &self.triangle_mesh.vertices[t.1 as usize],
73                &self.triangle_mesh.vertices[t.2 as usize],
74            ))
75        } else {
76            None
77        }
78    }
79}
80
81impl TriangleMesh {
82    /// Clear mesh.
83    pub fn clear(&mut self) {
84        self.vertices.clear();
85        self.triangle_indices.clear();
86    }
87
88    /// Fetch triangles.
89    pub fn fetch_triangles(&self) -> Vec<Triangle<Vertex>> {
90        self.triangle_indices
91            .iter()
92            .map(|t| {
93                Triangle(
94                    self.vertices[t.0 as usize],
95                    self.vertices[t.1 as usize],
96                    self.vertices[t.2 as usize],
97                )
98            })
99            .collect()
100    }
101
102    /// Append a triangle mesh.
103    pub fn append(&mut self, other: &TriangleMesh) {
104        let offset = self.vertices.len() as u32;
105        self.vertices.extend_from_slice(&other.vertices);
106        self.triangle_indices.extend(
107            other
108                .triangle_indices
109                .iter()
110                .map(|t| Triangle(t.0 + offset, t.1 + offset, t.2 + offset)),
111        )
112    }
113
114    /// Triangles iterator.
115    pub fn triangles(&'_ self) -> Triangles<'_> {
116        Triangles {
117            triangle_mesh: self,
118            index: 0,
119        }
120    }
121
122    /// Convert mesh to manifold.
123    pub fn to_manifold(&self) -> Manifold {
124        let vertices = self
125            .vertices
126            .iter()
127            .flat_map(|v| vec![v.pos.x as f32, v.pos.y as f32, v.pos.z as f32])
128            .collect::<Vec<_>>();
129
130        let triangle_indices = self
131            .triangle_indices
132            .iter()
133            .flat_map(|t| vec![t.0, t.1, t.2])
134            .collect::<Vec<_>>();
135
136        assert_eq!(vertices.len(), self.vertices.len() * 3);
137        assert_eq!(triangle_indices.len(), self.triangle_indices.len() * 3);
138
139        Manifold::from_mesh(Mesh::new(&vertices, &triangle_indices))
140    }
141
142    /// Transform the mesh.
143    ///
144    /// # Arguments
145    /// - `transform`: Transformation matrix
146    ///
147    /// # Returns
148    /// Transformed mesh
149    pub fn transform(&self, transform: &crate::Mat4) -> Self {
150        let rot_mat = crate::Mat3::from_cols(
151            transform.x.truncate(),
152            transform.y.truncate(),
153            transform.z.truncate(),
154        );
155        let vertices = self
156            .vertices
157            .iter()
158            .map(|v| Vertex {
159                pos: (transform * v.pos.extend(1.0)).truncate(),
160                normal: rot_mat * v.normal,
161            })
162            .collect();
163
164        TriangleMesh {
165            vertices,
166            triangle_indices: self.triangle_indices.clone(),
167        }
168    }
169
170    /// Calculate volume of mesh.
171    pub fn volume(&self) -> f64 {
172        self.triangles()
173            .map(|t| t.signed_volume())
174            .sum::<f64>()
175            .abs()
176    }
177}
178
179impl FetchBounds3D for TriangleMesh {
180    fn fetch_bounds_3d(&self) -> Bounds3D {
181        self.vertices.iter().map(|vertex| vertex.pos).collect()
182    }
183}
184
185impl From<Mesh> for TriangleMesh {
186    fn from(mesh: Mesh) -> Self {
187        let vertices = mesh.vertices();
188        let indices = mesh.indices();
189
190        // TODO: We could use unsafe std::ptr::copy and cast::transmute to avoid deep copy
191        // of vertices and indices
192
193        TriangleMesh {
194            vertices: (0..vertices.len())
195                .step_by(3)
196                .map(|i| Vertex {
197                    pos: Vec3::new(
198                        vertices[i] as f64,
199                        vertices[i + 1] as f64,
200                        vertices[i + 2] as f64,
201                    ),
202                    normal: Vec3::new(0.0, 0.0, 0.0),
203                })
204                .collect(),
205            triangle_indices: (0..indices.len())
206                .step_by(3)
207                .map(|i| Triangle(indices[i], indices[i + 1], indices[i + 2]))
208                .collect(),
209        }
210    }
211}
212
213impl From<TriangleMesh> for Mesh {
214    fn from(mesh: TriangleMesh) -> Self {
215        let mut vertices = Vec::new();
216        let mut indices = Vec::new();
217
218        for v in &mesh.vertices {
219            vertices.push(v.pos.x as f32);
220            vertices.push(v.pos.y as f32);
221            vertices.push(v.pos.z as f32);
222        }
223
224        for t in &mesh.triangle_indices {
225            indices.push(t.0);
226            indices.push(t.1);
227            indices.push(t.2);
228        }
229
230        Mesh::new(vertices.as_slice(), indices.as_slice())
231    }
232}
233
234impl From<Manifold> for TriangleMesh {
235    fn from(manifold: Manifold) -> Self {
236        TriangleMesh::from(manifold.to_mesh())
237    }
238}
239
240#[test]
241fn test_triangle_mesh_transform() {
242    let mesh = TriangleMesh {
243        vertices: vec![
244            Vertex {
245                pos: Vec3::new(0.0, 0.0, 0.0),
246                normal: Vec3::new(0.0, 0.0, 1.0),
247            },
248            Vertex {
249                pos: Vec3::new(1.0, 0.0, 0.0),
250                normal: Vec3::new(0.0, 0.0, 1.0),
251            },
252            Vertex {
253                pos: Vec3::new(0.0, 1.0, 0.0),
254                normal: Vec3::new(0.0, 0.0, 1.0),
255            },
256        ],
257        triangle_indices: vec![Triangle(0, 1, 2)],
258    };
259
260    let mesh = mesh.transform(&crate::Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0)));
261
262    assert_eq!(mesh.vertices[0].pos, Vec3::new(1.0, 2.0, 3.0));
263    assert_eq!(mesh.vertices[0].normal, Vec3::new(0.0, 0.0, 1.0));
264    assert_eq!(mesh.vertices[1].pos, Vec3::new(2.0, 2.0, 3.0));
265    assert_eq!(mesh.vertices[1].normal, Vec3::new(0.0, 0.0, 1.0));
266    assert_eq!(mesh.vertices[2].pos, Vec3::new(1.0, 3.0, 3.0));
267    assert_eq!(mesh.vertices[2].normal, Vec3::new(0.0, 0.0, 1.0));
268}