1use crate::{
2 core::selection::select_nodes_in_plane_direction,
3 io::{
4 obj::{read_obj, write_obj},
5 stl::{read_stl, write_stl, StlFormat},
6 },
7};
8use crate::error::Result;
9
10#[derive(Default, Debug, Clone)]
22pub struct Mesh {
23 pub nodes: Vec<[f64; 3]>,
24 pub cells: Vec<[usize; 3]>,
25}
26
27impl Mesh {
28 pub fn new(nodes: Vec<[f64; 3]>, cells: Vec<[usize; 3]>) -> Self {
34 Mesh { nodes, cells }
35 }
36
37 pub fn from_stl(filename: &str) -> Result<Self> {
40 let (nodes, cells) = read_stl(filename)?;
41 Ok(Mesh::new(nodes, cells))
42 }
43
44 pub fn from_obj(filename: &str) -> Result<Self> {
47 let (nodes, cells) = read_obj(filename)?;
48 Ok(Mesh::new(nodes, cells))
49 }
50
51 pub fn write_stl(
54 &self,
55 filename: Option<&str>,
56 format: Option<StlFormat>,
57 ) -> Result<()> {
58 write_stl(&self.nodes, &self.cells, filename, format)
59 }
60
61 pub fn write_obj(&self, filename: Option<&str>) -> Result<()> {
64 write_obj(&self.nodes, &self.cells, filename)
65 }
66
67 pub fn triangles(&self) -> Vec<[[f64; 3]; 3]> {
70 get_mesh_triangles(&self.nodes, &self.cells)
71 }
72
73 pub fn cell_normals(&self) -> Vec<[f64; 3]> {
76 get_mesh_cell_normals(&self.nodes, &self.cells)
77 }
78
79 pub fn slice(&self, origin: [f64; 3], normal: [f64; 3]) -> Option<Vec<[f64; 3]>> {
82 find_mesh_intersections_with_plane(&self.nodes, &self.cells, origin, normal)
83 }
84
85 pub fn clip(&self, origin: [f64; 3], normal: [f64; 3]) -> Mesh {
88 let (new_nodes, new_cells) =
89 clip_mesh_from_plane(&self.nodes, &self.cells, origin, normal);
90 Mesh::new(new_nodes, new_cells)
91 }
92}
93
94fn get_triangle(nodes: &[[f64; 3]], cell: &[usize; 3]) -> [[f64; 3]; 3] {
103 [nodes[cell[0]], nodes[cell[1]], nodes[cell[2]]]
104}
105
106pub fn get_mesh_triangles(
115 nodes: &[[f64; 3]],
116 cells: &[[usize; 3]],
117) -> Vec<[[f64; 3]; 3]> {
118 cells.iter().map(|cell| get_triangle(nodes, cell)).collect()
119}
120
121pub fn get_mesh_cell_normals(nodes: &[[f64; 3]], cells: &[[usize; 3]]) -> Vec<[f64; 3]> {
131 cells
132 .iter()
133 .map(|cell| {
134 let a = nodes[cell[0]];
135 let b = nodes[cell[1]];
136 let c = nodes[cell[2]];
137
138 let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
140 let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
141
142 let normal = [
144 ab[1] * ac[2] - ab[2] * ac[1],
145 ab[2] * ac[0] - ab[0] * ac[2],
146 ab[0] * ac[1] - ab[1] * ac[0],
147 ];
148
149 let length =
151 (normal[0].powi(2) + normal[1].powi(2) + normal[2].powi(2)).sqrt();
152 [normal[0] / length, normal[1] / length, normal[2] / length]
153 })
154 .collect()
155}
156
157pub fn find_mesh_intersections_with_plane(
189 nodes: &[[f64; 3]],
190 cells: &[[usize; 3]],
191 origin: [f64; 3],
192 normal: [f64; 3],
193) -> Option<Vec<[f64; 3]>> {
194 let d = origin
195 .iter()
196 .zip(normal.iter())
197 .map(|(o, n)| o * n)
198 .sum::<f64>();
199 let triangles = get_mesh_triangles(nodes, cells);
200
201 let mut intersections: Vec<[f64; 3]> = Vec::with_capacity(2 * triangles.len());
203
204 for [a, b, c] in triangles {
205 let mut edge_intersections: Vec<[f64; 3]> = Vec::with_capacity(2);
206
207 for &[point1, point2] in &[[a, b], [b, c], [c, a]] {
208 let [x1, y1, z1] = point1;
209 let [x2, y2, z2] = point2;
210
211 let t = (d - normal
212 .iter()
213 .zip(point1.iter())
214 .map(|(n, p)| n * p)
215 .sum::<f64>())
216 / normal
217 .iter()
218 .zip(point2.iter())
219 .zip(point1.iter())
220 .map(|((n, p2), p1)| n * (p2 - p1))
221 .sum::<f64>();
222
223 if (0.0..=1.0).contains(&t) {
224 let x = x1 + (x2 - x1) * t;
225 let y = y1 + (y2 - y1) * t;
226 let z = z1 + (z2 - z1) * t;
227 edge_intersections.push([x, y, z]);
228 }
229 }
230
231 if edge_intersections.len() == 2 {
232 intersections.push(edge_intersections[0]);
233 intersections.push(edge_intersections[1]);
234 }
235 }
236
237 if intersections.is_empty() {
238 None
239 } else {
240 Some(intersections)
241 }
242}
243
244pub fn clip_mesh_from_plane(
257 nodes: &[[f64; 3]],
258 cells: &[[usize; 3]],
259 origin: [f64; 3],
260 normal: [f64; 3],
261) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
262 let selected_node_indices = select_nodes_in_plane_direction(nodes, origin, normal);
264
265 let new_nodes = selected_node_indices
267 .iter()
268 .map(|&i| nodes[i])
269 .collect::<Vec<[f64; 3]>>();
270
271 let new_cells = cells
273 .iter()
274 .filter_map(|cell| {
275 if cell.iter().all(|&i| selected_node_indices.contains(&i)) {
276 Some([
277 selected_node_indices
278 .iter()
279 .position(|&x| x == cell[0])
280 .unwrap(),
281 selected_node_indices
282 .iter()
283 .position(|&x| x == cell[1])
284 .unwrap(),
285 selected_node_indices
286 .iter()
287 .position(|&x| x == cell[2])
288 .unwrap(),
289 ])
290 } else {
291 None
292 }
293 })
294 .collect::<Vec<[usize; 3]>>();
295
296 (new_nodes, new_cells)
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 const EPSILON: f64 = 1e-9;
304
305 #[test]
306 fn test_get_triangles() {
307 let nodes = vec![
308 [0.0, 0.0, 0.0],
309 [1.0, 0.0, 0.0],
310 [0.0, 1.0, 0.0],
311 [0.0, 0.0, 1.0],
312 ];
313 let cells = vec![[0, 1, 2], [0, 2, 3]];
314 let triangles = get_mesh_triangles(&nodes, &cells);
315
316 let expected_triangles = vec![
317 [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
318 [[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
319 ];
320
321 assert_eq!(triangles, expected_triangles)
322 }
323
324 #[test]
325 fn test_get_face_normals() {
326 let nodes = vec![
327 [0.0, 0.0, 0.0],
328 [1.0, 0.0, 0.0],
329 [0.0, 1.0, 0.0],
330 [0.0, 0.0, 1.0],
331 ];
332 let cells = vec![[0, 1, 2], [0, 2, 3]];
333 let normals = get_mesh_cell_normals(&nodes, &cells);
334
335 let expected_normals = vec![[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]];
336
337 assert_eq!(normals, expected_normals)
338 }
339
340 #[test]
341 fn test_slice_mesh_with_plane() {
342 let nodes = vec![
344 [0.0, 0.0, 0.0],
345 [1.0, 0.0, 0.0],
346 [0.5, 1.0, 0.0],
347 [0.5, 0.5, 1.0],
348 ];
349 let cells = vec![[0, 1, 2], [0, 1, 3], [1, 2, 3], [0, 2, 3]];
350 let mesh = Mesh::new(nodes, cells);
351
352 let origin = [0.0, 0.0, 0.5];
354 let normal = [0.0, 0.0, 1.0];
355
356 let intersections = mesh
357 .slice(origin, normal)
358 .expect("Should find intersections");
359
360 assert_eq!(
362 intersections.len(),
363 6,
364 "Slice should produce 3 line segments"
365 );
366 for point in intersections {
368 assert!((point[2] - 0.5).abs() < EPSILON);
369 }
370 }
371
372 #[test]
373 fn test_slice_mesh_no_intersection() {
374 let nodes = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
375 let cells = vec![[0, 1, 2]];
376 let mesh = Mesh::new(nodes, cells);
377
378 let origin = [0.0, 0.0, 10.0];
380 let normal = [0.0, 0.0, 1.0];
381
382 let result = mesh.slice(origin, normal);
383 assert!(result.is_none(), "Should not find any intersections");
384 }
385
386 #[test]
387 fn test_clip_mesh_from_plane() {
388 let nodes = vec![
390 [-1., -0.5, -0.5],
391 [-1., -0.5, 0.5],
392 [-1., 0.5, -0.5],
393 [-1., 0.5, 0.5],
394 [1., -0.5, -0.5],
395 [1., -0.5, 0.5],
396 [1., 0.5, -0.5],
397 [1., 0.5, 0.5],
398 ];
399 let cells = vec![
400 [0, 1, 3],
401 [0, 3, 2],
402 [4, 7, 5],
403 [4, 6, 7],
404 [0, 4, 5],
405 [0, 5, 1],
406 [2, 3, 7],
407 [2, 7, 6],
408 [0, 6, 4],
409 [0, 2, 6],
410 [1, 5, 7],
411 [1, 7, 3],
412 ];
413 let mesh = Mesh::new(nodes, cells);
414
415 let origin = [0.0, 0.0, 0.0];
417 let normal = [1.0, 0.0, 0.0]; let new_mesh = mesh.clip(origin, normal);
420
421 assert_eq!(new_mesh.nodes.len(), 4);
423 assert_eq!(new_mesh.cells.len(), 2);
425
426 for node in new_mesh.nodes {
428 assert!(node[0] >= -EPSILON);
429 }
430 }
431}