1pub(crate) mod core;
11
12use crate::model::{Bounds3, Point3, RigidTransform, TargetRef};
13use crate::numeric::Tolerance;
14use serde::{Deserialize, Serialize};
15
16#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct FaceSource {
20 pub solid: String,
22 pub face: String,
24 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
26 pub protected_only: bool,
27}
28
29#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct Triangle {
33 pub id: u32,
35 pub a: Point3,
36 pub b: Point3,
37 pub c: Point3,
38 pub bounds: Bounds3,
39 pub normal: Point3,
41 pub face: u32,
43}
44
45impl Triangle {
46 pub fn vertices(&self) -> [Point3; 3] {
47 [self.a, self.b, self.c]
48 }
49
50 pub fn area_vector(&self) -> Point3 {
52 self.b.sub(self.a).cross(self.c.sub(self.a))
53 }
54
55 pub fn area(&self) -> f64 {
56 0.5 * self.area_vector().length()
57 }
58
59 pub fn centroid(&self) -> Point3 {
60 self.a.add(self.b).add(self.c).scale(1.0 / 3.0)
61 }
62
63 pub fn is_vertical(&self, tol: &Tolerance) -> bool {
65 tol.is_horizontal(self.normal)
66 }
67
68 pub fn is_horizontal(&self, tol: &Tolerance) -> bool {
70 tol.is_vertical(self.normal)
71 }
72
73 pub fn min_z(&self) -> f64 {
74 self.bounds.min.z
75 }
76
77 pub fn max_z(&self) -> f64 {
78 self.bounds.max.z
79 }
80}
81
82#[derive(Clone, Debug, PartialEq, Eq)]
84pub enum MeshError {
85 NonFinite { triangle: usize },
87 BufferShape(String),
90 IndexOutOfRange { triangle: usize, index: u32 },
92 FaceOutOfRange { triangle: usize, face: u32 },
94}
95
96impl std::fmt::Display for MeshError {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 match self {
99 MeshError::NonFinite { triangle } => write!(f, "triangle {triangle} has a non-finite coordinate"),
100 MeshError::BufferShape(msg) => write!(f, "mesh buffers: {msg}"),
101 MeshError::IndexOutOfRange { triangle, index } => {
102 write!(f, "triangle {triangle} references vertex {index} past the buffer")
103 }
104 MeshError::FaceOutOfRange { triangle, face } => {
105 write!(f, "triangle {triangle} references face {face} past the face table")
106 }
107 }
108 }
109}
110
111impl std::error::Error for MeshError {}
112
113#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct MeshBuildReport {
119 pub triangles_in: u64,
120 pub triangles_kept: u64,
121 pub degenerate_dropped: u64,
122}
123
124#[derive(Debug, Default)]
126pub struct MeshBuilder {
127 faces: Vec<FaceSource>,
128 triangles: Vec<Triangle>,
129 bounds: Bounds3,
130 report: MeshBuildReport,
131 tolerance: Tolerance,
132}
133
134impl MeshBuilder {
135 pub fn new(tolerance: Tolerance) -> Self {
137 Self { tolerance, bounds: Bounds3::empty(), ..Default::default() }
138 }
139
140 pub fn add_face(&mut self, source: FaceSource) -> u32 {
142 self.faces.push(source);
143 (self.faces.len() - 1) as u32
144 }
145
146 pub fn add_triangle(&mut self, face: u32, a: Point3, b: Point3, c: Point3) -> Result<(), MeshError> {
149 let i = self.report.triangles_in as usize;
150 self.report.triangles_in += 1;
151 if face as usize >= self.faces.len() {
152 return Err(MeshError::FaceOutOfRange { triangle: i, face });
153 }
154 if !a.is_finite() || !b.is_finite() || !c.is_finite() {
155 return Err(MeshError::NonFinite { triangle: i });
156 }
157 let area_vector = b.sub(a).cross(c.sub(a));
158 let twice_area = area_vector.length();
159 if self.tolerance.is_zero_area(0.5 * twice_area) {
160 self.report.degenerate_dropped += 1;
161 return Ok(());
162 }
163 let normal = area_vector.scale(1.0 / twice_area);
164 let bounds = Bounds3::from_points([a, b, c]);
165 self.bounds = self.bounds.union(&bounds);
166 let id = self.triangles.len() as u32;
167 self.triangles.push(Triangle { id, a, b, c, bounds, normal, face });
168 self.report.triangles_kept += 1;
169 Ok(())
170 }
171
172 pub fn add_body_buffers(
176 &mut self,
177 solid: &str,
178 protected_only: bool,
179 positions: &[f64],
180 indices: &[u32],
181 face_ids: &[u32],
182 face_names: &[String],
183 ) -> Result<(), MeshError> {
184 if positions.len() % 3 != 0 {
185 return Err(MeshError::BufferShape("positions length is not a multiple of three".into()));
186 }
187 if indices.len() % 3 != 0 {
188 return Err(MeshError::BufferShape("indices length is not a multiple of three".into()));
189 }
190 let triangle_count = indices.len() / 3;
191 if face_ids.len() != triangle_count {
192 return Err(MeshError::BufferShape(format!(
193 "face_ids has {} entries for {} triangles",
194 face_ids.len(),
195 triangle_count
196 )));
197 }
198 let base_face = self.faces.len() as u32;
199 for name in face_names {
200 self.add_face(FaceSource { solid: solid.to_string(), face: name.clone(), protected_only });
201 }
202 let vertex_count = (positions.len() / 3) as u32;
203 let point = |index: u32| -> Point3 {
204 let i = index as usize * 3;
205 Point3::new(positions[i], positions[i + 1], positions[i + 2])
206 };
207 for (t, tri) in indices.chunks_exact(3).enumerate() {
208 for &index in tri {
209 if index >= vertex_count {
210 return Err(MeshError::IndexOutOfRange { triangle: self.report.triangles_in as usize, index });
211 }
212 }
213 let face_local = face_ids[t];
214 if face_local as usize >= face_names.len() {
215 return Err(MeshError::FaceOutOfRange { triangle: self.report.triangles_in as usize, face: face_local });
216 }
217 self.add_triangle(base_face + face_local, point(tri[0]), point(tri[1]), point(tri[2]))?;
218 }
219 Ok(())
220 }
221
222 pub fn finish(self) -> MeshSnapshot {
223 MeshSnapshot { faces: self.faces, triangles: self.triangles, bounds: self.bounds, report: self.report }
224 }
225}
226
227#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct MeshSnapshot {
231 pub faces: Vec<FaceSource>,
232 pub triangles: Vec<Triangle>,
233 pub bounds: Bounds3,
234 #[serde(default)]
235 pub report: MeshBuildReport,
236}
237
238impl MeshSnapshot {
239 pub fn empty() -> Self {
240 Self::default()
241 }
242
243 pub fn builder(tolerance: Tolerance) -> MeshBuilder {
244 MeshBuilder::new(tolerance)
245 }
246
247 pub fn transformed(&self, transform: &RigidTransform) -> MeshSnapshot {
250 if transform.is_identity() {
251 return self.clone();
252 }
253 let mut bounds = Bounds3::empty();
254 let triangles = self
255 .triangles
256 .iter()
257 .map(|t| {
258 let a = transform.apply_point(t.a);
259 let b = transform.apply_point(t.b);
260 let c = transform.apply_point(t.c);
261 let tb = Bounds3::from_points([a, b, c]);
262 bounds = bounds.union(&tb);
263 Triangle { id: t.id, a, b, c, bounds: tb, normal: transform.apply_vector(t.normal), face: t.face }
264 })
265 .collect();
266 MeshSnapshot { faces: self.faces.clone(), triangles, bounds, report: self.report }
267 }
268
269 pub fn triangle_count(&self) -> usize {
270 self.triangles.len()
271 }
272
273 pub fn is_empty(&self) -> bool {
274 self.triangles.is_empty()
275 }
276
277 pub fn triangle(&self, id: u32) -> &Triangle {
278 &self.triangles[id as usize]
279 }
280
281 pub fn face_of(&self, id: u32) -> &FaceSource {
282 &self.faces[self.triangles[id as usize].face as usize]
283 }
284
285 pub fn resolve_faces(&self, target: &TargetRef) -> Vec<u32> {
287 self.faces
288 .iter()
289 .enumerate()
290 .filter(|(_, f)| f.solid == target.solid && target.face.as_ref().map_or(true, |name| *name == f.face))
291 .map(|(i, _)| i as u32)
292 .collect()
293 }
294
295 pub fn triangles_of_faces(&self, faces: &[u32]) -> Vec<u32> {
297 let mut mark = vec![false; self.faces.len()];
298 for &f in faces {
299 if let Some(m) = mark.get_mut(f as usize) {
300 *m = true;
301 }
302 }
303 self.triangles.iter().filter(|t| mark[t.face as usize]).map(|t| t.id).collect()
304 }
305
306 pub fn triangles_of(&self, target: &TargetRef) -> Vec<u32> {
308 self.triangles_of_faces(&self.resolve_faces(target))
309 }
310
311 pub fn solids(&self) -> Vec<String> {
313 let mut names: Vec<String> = self.faces.iter().map(|f| f.solid.clone()).collect();
314 names.sort();
315 names.dedup();
316 names
317 }
318
319 pub fn bounds_of(&self, ids: &[u32]) -> Bounds3 {
321 ids.iter().fold(Bounds3::empty(), |b, &id| b.union(&self.triangles[id as usize].bounds))
322 }
323}
324
325pub fn to_machine(setup: &crate::model::Setup, model: &MeshSnapshot) -> MeshSnapshot {
330 model.transformed(&setup.model_to_machine)
331}
332
333pub trait TriangleSource {
336 fn triangle_count(&self) -> usize;
337 fn triangle(&self, id: u32) -> &Triangle;
338 fn bounds(&self) -> Bounds3;
339 fn face_source(&self, face: u32) -> &FaceSource;
340}
341
342impl TriangleSource for MeshSnapshot {
343 fn triangle_count(&self) -> usize {
344 self.triangles.len()
345 }
346
347 fn triangle(&self, id: u32) -> &Triangle {
348 &self.triangles[id as usize]
349 }
350
351 fn bounds(&self) -> Bounds3 {
352 self.bounds
353 }
354
355 fn face_source(&self, face: u32) -> &FaceSource {
356 &self.faces[face as usize]
357 }
358}
359