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