oxiphysics_geometry/mesh_processing/
types.rs1#[allow(unused_imports)]
6use super::functions_2::*;
7use std::collections::HashSet;
8
9#[allow(unused_imports)]
10use super::functions::*;
11use super::functions::{Face, Uv, Vertex};
12
13#[derive(Debug, Clone)]
16pub struct AnisotropicSizeField {
17 pub k_min_dir: Vec<[f64; 3]>,
19 pub k_max_dir: Vec<[f64; 3]>,
21 pub len_min: Vec<f64>,
23 pub len_max: Vec<f64>,
25}
26#[derive(Debug, Clone)]
28pub struct MeshQualityStats {
29 pub mean_aspect_ratio: f64,
31 pub max_aspect_ratio: f64,
33 pub min_angle_deg: f64,
35 pub mean_area_quality: f64,
37}
38#[derive(Debug, Clone, Copy)]
41pub struct FeatureEdge {
42 pub v0: usize,
44 pub v1: usize,
46 pub dihedral_angle: f64,
48}
49#[derive(Debug, Clone, Copy, Default)]
53pub struct Quadric {
54 pub q: [f64; 10],
56}
57impl Quadric {
58 pub fn zero() -> Self {
60 Self { q: [0.0; 10] }
61 }
62 pub fn add(&self, other: &Quadric) -> Quadric {
64 let mut r = Quadric::zero();
65 for i in 0..10 {
66 r.q[i] = self.q[i] + other.q[i];
67 }
68 r
69 }
70 pub fn from_plane(a: f64, b: f64, c: f64, d: f64) -> Quadric {
73 Quadric {
74 q: [
75 a * a,
76 a * b,
77 a * c,
78 a * d,
79 b * b,
80 b * c,
81 b * d,
82 c * c,
83 c * d,
84 d * d,
85 ],
86 }
87 }
88 pub fn evaluate(&self, v: [f64; 3]) -> f64 {
90 let [x, y, z] = v;
91 let w = 1.0_f64;
92 let q = &self.q;
93 q[0] * x * x
94 + 2.0 * q[1] * x * y
95 + 2.0 * q[2] * x * z
96 + 2.0 * q[3] * x * w
97 + q[4] * y * y
98 + 2.0 * q[5] * y * z
99 + 2.0 * q[6] * y * w
100 + q[7] * z * z
101 + 2.0 * q[8] * z * w
102 + q[9] * w * w
103 }
104}
105#[derive(Debug, Clone, Copy)]
107pub struct TriangleQuality {
108 pub aspect_ratio: f64,
110 pub min_angle: f64,
112 pub max_angle: f64,
114 pub area_quality: f64,
116}
117#[derive(Debug, Clone)]
119pub struct ProcessMesh {
120 pub verts: Vec<Vertex>,
122 pub faces: Vec<Face>,
124 pub normals: Option<Vec<Vertex>>,
126 pub uvs: Option<Vec<Uv>>,
128}
129impl ProcessMesh {
130 pub fn new(verts: Vec<Vertex>, faces: Vec<Face>) -> Self {
132 Self {
133 verts,
134 faces,
135 normals: None,
136 uvs: None,
137 }
138 }
139 pub fn num_verts(&self) -> usize {
141 self.verts.len()
142 }
143 pub fn num_faces(&self) -> usize {
145 self.faces.len()
146 }
147 pub fn compute_normals(&mut self) {
149 let nv = self.verts.len();
150 let mut normals = vec![[0.0_f64; 3]; nv];
151 for &[a, b, c] in &self.faces {
152 let va = self.verts[a];
153 let vb = self.verts[b];
154 let vc = self.verts[c];
155 let ab = vec3_sub(vb, va);
156 let ac = vec3_sub(vc, va);
157 let n = vec3_cross(ab, ac);
158 for &i in &[a, b, c] {
159 normals[i] = vec3_add(normals[i], n);
160 }
161 }
162 for n in &mut normals {
163 *n = vec3_normalize(*n);
164 }
165 self.normals = Some(normals);
166 }
167 pub fn build_adjacency(&self) -> Vec<Vec<usize>> {
169 let nv = self.verts.len();
170 let mut adj: Vec<HashSet<usize>> = vec![HashSet::new(); nv];
171 for &[a, b, c] in &self.faces {
172 adj[a].insert(b);
173 adj[a].insert(c);
174 adj[b].insert(a);
175 adj[b].insert(c);
176 adj[c].insert(a);
177 adj[c].insert(b);
178 }
179 adj.into_iter().map(|s| s.into_iter().collect()).collect()
180 }
181}
182#[derive(Debug, Clone)]
184pub struct UvParameterization {
185 pub mesh: ProcessMesh,
187 pub uvs: Vec<Uv>,
189}
190#[derive(Debug, Clone)]
192pub struct AtlasPatch {
193 pub group_id: usize,
195 pub bounds: [f64; 4],
197 pub uvs: Vec<Uv>,
199}
200#[derive(Debug, Clone)]
202pub struct BooleanResult {
203 pub mesh: ProcessMesh,
205 pub is_exact: bool,
207}
208
209impl BooleanResult {
210 pub fn is_topologically_exact(&self) -> bool {
217 let mesh = &self.mesh;
218 if mesh.faces.is_empty() {
219 return false;
220 }
221 let mut edge_count: std::collections::HashMap<(usize, usize), usize> =
222 std::collections::HashMap::new();
223 for &[a, b, c] in &mesh.faces {
224 for &(u, v) in &[
225 (a.min(b), a.max(b)),
226 (b.min(c), b.max(c)),
227 (a.min(c), a.max(c)),
228 ] {
229 *edge_count.entry((u, v)).or_insert(0) += 1;
230 }
231 }
232 if edge_count.values().any(|&cnt| cnt != 2) {
233 return false;
234 }
235 let normals: Vec<[f64; 3]> = mesh
236 .faces
237 .iter()
238 .map(|&[a, b, c]| {
239 let va = mesh.verts[a];
240 let vb = mesh.verts[b];
241 let vc = mesh.verts[c];
242 let ab = [vb[0] - va[0], vb[1] - va[1], vb[2] - va[2]];
243 let ac = [vc[0] - va[0], vc[1] - va[1], vc[2] - va[2]];
244 [
245 ab[1] * ac[2] - ab[2] * ac[1],
246 ab[2] * ac[0] - ab[0] * ac[2],
247 ab[0] * ac[1] - ab[1] * ac[0],
248 ]
249 })
250 .collect();
251 for n in &normals {
252 if (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt() < 1e-12 {
253 return false;
254 }
255 }
256 let mut face_of_edge: std::collections::HashMap<(usize, usize), Vec<usize>> =
257 std::collections::HashMap::new();
258 for (fi, &[a, b, c]) in mesh.faces.iter().enumerate() {
259 for &(u, v) in &[
260 (a.min(b), a.max(b)),
261 (b.min(c), b.max(c)),
262 (a.min(c), a.max(c)),
263 ] {
264 face_of_edge.entry((u, v)).or_default().push(fi);
265 }
266 }
267 const MIN_ANGLE: f64 = 1e-6;
268 for fs in face_of_edge.values() {
269 if fs.len() != 2 {
270 continue;
271 }
272 let n0 = normals[fs[0]];
273 let n1 = normals[fs[1]];
274 let l0 = (n0[0] * n0[0] + n0[1] * n0[1] + n0[2] * n0[2]).sqrt();
275 let l1 = (n1[0] * n1[0] + n1[1] * n1[1] + n1[2] * n1[2]).sqrt();
276 if l0 < 1e-30 || l1 < 1e-30 {
277 return false;
278 }
279 let dot = (n0[0] * n1[0] + n0[1] * n1[1] + n0[2] * n1[2]) / (l0 * l1);
280 if dot.abs().min(1.0).acos() < MIN_ANGLE {
281 return false;
282 }
283 }
284 true
285 }
286}
287
288#[cfg(test)]
289mod boolean_result_tests {
290 use super::{BooleanResult, ProcessMesh};
291
292 fn make_tetrahedron() -> ProcessMesh {
293 let verts = vec![
294 [0.0f64, 0.0, 0.0],
295 [1.0, 0.0, 0.0],
296 [0.5, 1.0, 0.0],
297 [0.5, 0.333, 0.816],
298 ];
299 ProcessMesh::new(verts, vec![[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]])
300 }
301
302 #[test]
303 fn test_closed_tetrahedron_is_exact() {
304 let r = BooleanResult {
305 mesh: make_tetrahedron(),
306 is_exact: false,
307 };
308 assert!(r.is_topologically_exact());
309 }
310
311 #[test]
312 fn test_coplanar_patch_not_exact() {
313 let verts = vec![
314 [0.0f64, 0.0, 0.0],
315 [1.0, 0.0, 0.0],
316 [0.5, 1.0, 0.0],
317 [0.5, -1.0, 0.0],
318 ];
319 let mesh = ProcessMesh::new(verts, vec![[0, 1, 2], [0, 3, 1]]);
320 let r = BooleanResult {
321 mesh,
322 is_exact: false,
323 };
324 assert!(!r.is_topologically_exact());
325 }
326
327 #[test]
328 fn test_empty_mesh_not_exact() {
329 let r = BooleanResult {
330 mesh: ProcessMesh::new(vec![], vec![]),
331 is_exact: false,
332 };
333 assert!(!r.is_topologically_exact());
334 }
335
336 #[test]
337 fn test_is_exact_flag_wired() {
338 let mut r = BooleanResult {
339 mesh: make_tetrahedron(),
340 is_exact: false,
341 };
342 r.is_exact = r.is_topologically_exact();
343 assert!(r.is_exact);
344 }
345}