gmac/core/mesh.rs
1use crate::core::selection::select_nodes_in_plane_direction;
2
3/// Represents a standard 3D mesh.
4/// A `Mesh` consists of nodes representing points in 3D space,
5/// and cells which are triangles connecting these points.
6///
7/// # Example
8/// ```
9/// # use some_module::Mesh;
10/// let nodes = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
11/// let cells = vec![[0, 1, 2]];
12/// let mesh = Mesh::new(nodes, cells);
13/// ```
14#[derive(Default, Debug, Clone)]
15pub struct Mesh {
16 pub nodes: Vec<[f64; 3]>,
17 pub cells: Vec<[usize; 3]>,
18}
19
20impl Mesh {
21 /// Constructs a new `Mesh`.
22 ///
23 /// # Arguments
24 /// * `nodes`: Nodes of the mesh.
25 /// * `cells`: Faces of the mesh.
26 pub fn new(nodes: Vec<[f64; 3]>, cells: Vec<[usize; 3]>) -> Self {
27 Mesh { nodes, cells }
28 }
29
30 /// Get nodes that make up cell triangles.
31 /// See `get_mesh_triangles`
32 pub fn triangles(&self) -> Vec<[[f64; 3]; 3]> {
33 get_mesh_triangles(&self.nodes, &self.cells)
34 }
35
36 /// Get cell normals.
37 /// See `get_mesh_cell_normals`
38 pub fn cell_normals(&self) -> Vec<[f64; 3]> {
39 get_mesh_cell_normals(&self.nodes, &self.cells)
40 }
41
42 /// Get nodes that are interpolated onto a slicing plane.
43 /// See `find_node_intersections_with_plane`
44 pub fn slice(&self, origin: [f64; 3], normal: [f64; 3]) -> Option<Vec<[f64; 3]>> {
45 find_mesh_intersections_with_plane(&self.nodes, &self.cells, origin, normal)
46 }
47
48 /// Get mesh in direction of plane.
49 /// See `clip_mesh_from_plane`
50 pub fn clip(&self, origin: [f64; 3], normal: [f64; 3]) -> Mesh {
51 let (new_nodes, new_cells) =
52 clip_mesh_from_plane(&self.nodes, &self.cells, origin, normal);
53 Mesh::new(new_nodes, new_cells)
54 }
55}
56
57/// Returns the coordinates of the vertices for a single cell.
58///
59/// # Arguments
60/// * `nodes`: Nodes of the mesh.
61/// * `cell`: Cell id.
62///
63/// # Returns
64/// A triangle in the form `[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]`.
65fn get_triangle(nodes: &[[f64; 3]], cell: &[usize; 3]) -> [[f64; 3]; 3] {
66 [nodes[cell[0]], nodes[cell[1]], nodes[cell[2]]]
67}
68
69/// Returns the coordinates of the vertices for each cell in the mesh.
70///
71/// # Arguments
72/// * `nodes`: Nodes of the mesh.
73/// * `cells`: Cells of the mesh.
74///
75/// # Returns
76/// A `Vec<[[f64; 3]; 3]>` where each element represents a triangle.
77pub fn get_mesh_triangles(
78 nodes: &[[f64; 3]],
79 cells: &[[usize; 3]],
80) -> Vec<[[f64; 3]; 3]> {
81 cells.iter().map(|cell| get_triangle(nodes, cell)).collect()
82}
83
84/// Computes and returns the normals for each cell in the mesh.
85/// The normal for each cell is computed using the right-hand rule.
86///
87/// # Arguments
88/// * `nodes`: Nodes of the mesh.
89/// * `cells`: Cells of the mesh.
90///
91/// # Returns
92/// A `Vec<[f64; 3]>` where each element is a normal corresponding to a cell.
93pub fn get_mesh_cell_normals(nodes: &[[f64; 3]], cells: &[[usize; 3]]) -> Vec<[f64; 3]> {
94 cells
95 .iter()
96 .map(|cell| {
97 let a = nodes[cell[0]];
98 let b = nodes[cell[1]];
99 let c = nodes[cell[2]];
100
101 // Calculate vectors ab and ac
102 let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
103 let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
104
105 // Calculate the cross product of ab and ac to get the normal
106 let normal = [
107 ab[1] * ac[2] - ab[2] * ac[1],
108 ab[2] * ac[0] - ab[0] * ac[2],
109 ab[0] * ac[1] - ab[1] * ac[0],
110 ];
111
112 // Normalize the normal vector
113 let length =
114 (normal[0].powi(2) + normal[1].powi(2) + normal[2].powi(2)).sqrt();
115 [normal[0] / length, normal[1] / length, normal[2] / length]
116 })
117 .collect()
118}
119
120/// Finds the intersection points of a mesh with a slicing plane.
121///
122/// # Arguments
123/// * `nodes`: Nodes of the mesh.
124/// * `cells`: Cells of the mesh.
125/// * `origin`: A 3D point `[x, y, z]` representing a point through which the
126/// slice plane passes.
127/// * `normal`: A 3D vector `[x, y, z]` representing the normal to the slice plane.
128///
129/// # Returns
130/// Returns a `Vec<[f64; 3]>` containing the intersection points of the mesh with
131/// the slice plane.
132/// Each intersection point is represented as a 3D point `[x, y, z]`.
133///
134/// # Example
135/// ```
136/// # Example usage
137/// let nodes = vec![
138/// [0.0, 0.0, 0.0],
139/// [1.0, 0.0, 0.0],
140/// [0.0, 1.0, 0.0],
141/// [0.0, 0.0, 1.0],
142/// ];
143/// let cells = vec![[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]];
144/// let mesh = Mesh { nodes, cells };
145///
146/// let origin = [0.5, 0.5, 0.5];
147/// let normal = [0.0, 0.0, 1.0];
148///
149/// let result = find_intersections_with_slice_plane(mesh, origin, normal);
150/// ```
151pub fn find_mesh_intersections_with_plane(
152 nodes: &[[f64; 3]],
153 cells: &[[usize; 3]],
154 origin: [f64; 3],
155 normal: [f64; 3],
156) -> Option<Vec<[f64; 3]>> {
157 let d = origin
158 .iter()
159 .zip(normal.iter())
160 .map(|(o, n)| o * n)
161 .sum::<f64>();
162 let triangles = get_mesh_triangles(nodes, cells);
163
164 // This is an arbitrary size for initial preallocation; you might have a better estimate
165 let mut intersections: Vec<[f64; 3]> = Vec::with_capacity(2 * triangles.len());
166
167 for [a, b, c] in triangles {
168 let mut edge_intersections: Vec<[f64; 3]> = Vec::with_capacity(2);
169
170 for &[point1, point2] in &[[a, b], [b, c], [c, a]] {
171 let [x1, y1, z1] = point1;
172 let [x2, y2, z2] = point2;
173
174 let t = (d - normal
175 .iter()
176 .zip(point1.iter())
177 .map(|(n, p)| n * p)
178 .sum::<f64>())
179 / normal
180 .iter()
181 .zip(point2.iter())
182 .zip(point1.iter())
183 .map(|((n, p2), p1)| n * (p2 - p1))
184 .sum::<f64>();
185
186 if (0.0..=1.0).contains(&t) {
187 let x = x1 + (x2 - x1) * t;
188 let y = y1 + (y2 - y1) * t;
189 let z = z1 + (z2 - z1) * t;
190 edge_intersections.push([x, y, z]);
191 }
192 }
193
194 if edge_intersections.len() == 2 {
195 intersections.push(edge_intersections[0]);
196 intersections.push(edge_intersections[1]);
197 }
198 }
199
200 if intersections.is_empty() {
201 None
202 } else {
203 Some(intersections)
204 }
205}
206
207/// Clips the mesh with a given slicing plane, keeping only the elements and nodes that
208/// lie in the direction of the plane's normal.
209///
210/// # Arguments
211/// * `origin`: A 3D point `[x, y, z]` representing a point through which the
212/// slice plane passes.
213/// * `normal`: A 3D vector `[x, y, z]` representing the normal to the slice plane.
214///
215/// # Returns
216/// Returns a new `Mesh` object containing only the nodes and cells (triangles)
217/// that are on the "positive" side of the slicing plane, i.e., in the direction
218/// of the plane's normal.
219pub fn clip_mesh_from_plane(
220 nodes: &[[f64; 3]],
221 cells: &[[usize; 3]],
222 origin: [f64; 3],
223 normal: [f64; 3],
224) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
225 // Get the indices of nodes that lie in the direction of the plane's normal
226 let selected_node_indices = select_nodes_in_plane_direction(nodes, origin, normal);
227
228 // Create a new list of nodes using the selected indices
229 let new_nodes = selected_node_indices
230 .iter()
231 .map(|&i| nodes[i])
232 .collect::<Vec<[f64; 3]>>();
233
234 // Create a new list of cells (elements) using only the selected nodes and reindexing them
235 let new_cells = cells
236 .iter()
237 .filter_map(|cell| {
238 if cell.iter().all(|&i| selected_node_indices.contains(&i)) {
239 Some([
240 selected_node_indices
241 .iter()
242 .position(|&x| x == cell[0])
243 .unwrap(),
244 selected_node_indices
245 .iter()
246 .position(|&x| x == cell[1])
247 .unwrap(),
248 selected_node_indices
249 .iter()
250 .position(|&x| x == cell[2])
251 .unwrap(),
252 ])
253 } else {
254 None
255 }
256 })
257 .collect::<Vec<[usize; 3]>>();
258
259 // Create a new Mesh using the selected nodes and cells
260 (new_nodes, new_cells)
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn test_get_triangles() {
269 let nodes = vec![
270 [0.0, 0.0, 0.0],
271 [1.0, 0.0, 0.0],
272 [0.0, 1.0, 0.0],
273 [0.0, 0.0, 1.0],
274 ];
275 let cells = vec![[0, 1, 2], [0, 2, 3]];
276 let triangles = get_mesh_triangles(&nodes, &cells);
277
278 let expected_triangles = vec![
279 [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
280 [[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
281 ];
282
283 assert_eq!(triangles, expected_triangles)
284 }
285
286 #[test]
287 fn test_get_face_normals() {
288 let nodes = vec![
289 [0.0, 0.0, 0.0],
290 [1.0, 0.0, 0.0],
291 [0.0, 1.0, 0.0],
292 [0.0, 0.0, 1.0],
293 ];
294 let cells = vec![[0, 1, 2], [0, 2, 3]];
295 let normals = get_mesh_cell_normals(&nodes, &cells);
296
297 let expected_normals = vec![[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]];
298
299 assert_eq!(normals, expected_normals)
300 }
301}