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}