1use std::collections::{BinaryHeap, HashMap, HashSet};
6
7use super::types::{
8 CollapseCandidate, CollapseRecord, HalfEdgeMesh, MeshStats, QemConfig, SymMat4,
9 TopologyValidation,
10};
11
12#[inline]
14pub(super) fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
15 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
16}
17#[inline]
19pub(super) fn len3(a: [f64; 3]) -> f64 {
20 dot3(a, a).sqrt()
21}
22#[inline]
24pub(super) fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
25 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
26}
27#[inline]
29pub(super) fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
30 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
31}
32#[inline]
34pub(super) fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
35 [a[0] * s, a[1] * s, a[2] * s]
36}
37#[inline]
39pub(super) fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
40 [
41 a[1] * b[2] - a[2] * b[1],
42 a[2] * b[0] - a[0] * b[2],
43 a[0] * b[1] - a[1] * b[0],
44 ]
45}
46#[inline]
48pub(super) fn normalize3(a: [f64; 3]) -> [f64; 3] {
49 let l = len3(a);
50 if l < 1e-15 {
51 [0.0; 3]
52 } else {
53 scale3(a, 1.0 / l)
54 }
55}
56#[inline]
58pub(super) fn mid3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
59 scale3(add3(a, b), 0.5)
60}
61#[inline]
63pub(super) fn dist3(a: [f64; 3], b: [f64; 3]) -> f64 {
64 len3(sub3(a, b))
65}
66pub(super) fn solve_3x3(a: [[f64; 3]; 3], b: [f64; 3]) -> Option<[f64; 3]> {
68 let det = a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
69 - a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
70 + a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]);
71 if det.abs() < 1e-15 {
72 return None;
73 }
74 let inv_det = 1.0 / det;
75 let x = inv_det
76 * (b[0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
77 - a[0][1] * (b[1] * a[2][2] - a[1][2] * b[2])
78 + a[0][2] * (b[1] * a[2][1] - a[1][1] * b[2]));
79 let y = inv_det
80 * (a[0][0] * (b[1] * a[2][2] - a[1][2] * b[2])
81 - b[0] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
82 + a[0][2] * (a[1][0] * b[2] - b[1] * a[2][0]));
83 let z = inv_det
84 * (a[0][0] * (a[1][1] * b[2] - b[1] * a[2][1])
85 - a[0][1] * (a[1][0] * b[2] - b[1] * a[2][0])
86 + b[0] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]));
87 Some([x, y, z])
88}
89pub fn triangle_aspect_ratio(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
93 let ab = dist3(a, b);
94 let bc = dist3(b, c);
95 let ca = dist3(c, a);
96 let s = 0.5 * (ab + bc + ca);
97 let area = triangle_area_3d(a, b, c);
98 if area < 1e-15 {
99 return f64::MAX;
100 }
101 let circumradius = ab * bc * ca / (4.0 * area);
102 let inradius = area / s;
103 circumradius / inradius
104}
105pub fn triangle_min_angle(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
107 let ab = sub3(b, a);
108 let ac = sub3(c, a);
109 let ba = sub3(a, b);
110 let bc = sub3(c, b);
111 let ca = sub3(a, c);
112 let cb = sub3(b, c);
113 let angle_a = safe_angle(ab, ac);
114 let angle_b = safe_angle(ba, bc);
115 let angle_c = safe_angle(ca, cb);
116 angle_a.min(angle_b).min(angle_c)
117}
118pub(super) fn safe_angle(u: [f64; 3], v: [f64; 3]) -> f64 {
120 let lu = len3(u);
121 let lv = len3(v);
122 if lu < 1e-15 || lv < 1e-15 {
123 return 0.0;
124 }
125 let cos_theta = (dot3(u, v) / (lu * lv)).clamp(-1.0, 1.0);
126 cos_theta.acos()
127}
128pub fn triangle_area_3d(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
130 let ab = sub3(b, a);
131 let ac = sub3(c, a);
132 0.5 * len3(cross3(ab, ac))
133}
134pub fn triangle_quality(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
136 let area = triangle_area_3d(a, b, c);
137 let ab2 = dot3(sub3(b, a), sub3(b, a));
138 let bc2 = dot3(sub3(c, b), sub3(c, b));
139 let ca2 = dot3(sub3(a, c), sub3(a, c));
140 let l2_sum = ab2 + bc2 + ca2;
141 if l2_sum < 1e-30 {
142 return 0.0;
143 }
144 4.0 * 3.0_f64.sqrt() * area / l2_sum
145}
146pub(super) fn compute_vertex_quadrics(
148 vertices: &[[f64; 3]],
149 triangles: &[[usize; 3]],
150) -> Vec<SymMat4> {
151 let mut quadrics = vec![SymMat4::zero(); vertices.len()];
152 for tri in triangles {
153 let a = vertices[tri[0]];
154 let b = vertices[tri[1]];
155 let c = vertices[tri[2]];
156 let n = normalize3(cross3(sub3(b, a), sub3(c, a)));
157 let d = -dot3(n, a);
158 let q = SymMat4::from_plane(n[0], n[1], n[2], d);
159 for &vi in tri {
160 quadrics[vi] = quadrics[vi].add(&q);
161 }
162 }
163 quadrics
164}
165pub(super) fn compute_edge_cost(
167 v0: usize,
168 v1: usize,
169 vertices: &[[f64; 3]],
170 quadrics: &[SymMat4],
171) -> CollapseCandidate {
172 let q_sum = quadrics[v0].add(&quadrics[v1]);
173 let optimal_pos = q_sum
174 .optimal_point()
175 .unwrap_or_else(|| mid3(vertices[v0], vertices[v1]));
176 let cost = q_sum.evaluate(optimal_pos).abs();
177 CollapseCandidate {
178 v0,
179 v1,
180 optimal_pos,
181 cost,
182 }
183}
184pub fn qem_simplify(
188 vertices: &[[f64; 3]],
189 triangles: &[[usize; 3]],
190 config: &QemConfig,
191) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
192 let mut verts = vertices.to_vec();
193 let mut tris = triangles.to_vec();
194 let mut quadrics = compute_vertex_quadrics(&verts, &tris);
195 let mut deleted_tri = vec![false; tris.len()];
196 let mut deleted_vert = vec![false; verts.len()];
197 let mut vertex_map: Vec<usize> = (0..verts.len()).collect();
198 fn find_root(map: &[usize], mut v: usize) -> usize {
199 while map[v] != v {
200 v = map[v];
201 }
202 v
203 }
204 let mut edge_set: HashSet<(usize, usize)> = HashSet::new();
205 let mut heap = BinaryHeap::new();
206 for tri in &tris {
207 for k in 0..3 {
208 let a = tri[k];
209 let b = tri[(k + 1) % 3];
210 let (lo, hi) = if a < b { (a, b) } else { (b, a) };
211 if edge_set.insert((lo, hi)) {
212 let cand = compute_edge_cost(a, b, &verts, &quadrics);
213 heap.push(cand);
214 }
215 }
216 }
217 let boundary_edges: HashSet<(usize, usize)> = if config.preserve_boundary {
218 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
219 for tri in &tris {
220 for k in 0..3 {
221 let a = tri[k];
222 let b = tri[(k + 1) % 3];
223 let (lo, hi) = if a < b { (a, b) } else { (b, a) };
224 *edge_count.entry((lo, hi)).or_insert(0) += 1;
225 }
226 }
227 edge_count
228 .into_iter()
229 .filter(|&(_, c)| c == 1)
230 .map(|(e, _)| e)
231 .collect()
232 } else {
233 HashSet::new()
234 };
235 let mut active_tris = tris.len();
236 while let Some(cand) = heap.pop() {
237 if config.target_triangles > 0 && active_tris <= config.target_triangles {
238 break;
239 }
240 if config.max_error > 0.0 && cand.cost > config.max_error {
241 break;
242 }
243 let v0 = find_root(&vertex_map, cand.v0);
244 let v1 = find_root(&vertex_map, cand.v1);
245 if v0 == v1 || deleted_vert[v0] || deleted_vert[v1] {
246 continue;
247 }
248 if config.preserve_boundary {
249 let (lo, hi) = if v0 < v1 { (v0, v1) } else { (v1, v0) };
250 if boundary_edges.contains(&(lo, hi)) {
251 continue;
252 }
253 }
254 verts[v0] = cand.optimal_pos;
255 quadrics[v0] = quadrics[v0].add(&quadrics[v1]);
256 deleted_vert[v1] = true;
257 vertex_map[v1] = v0;
258 for (ti, tri) in tris.iter_mut().enumerate() {
259 if deleted_tri[ti] {
260 continue;
261 }
262 let mut has_v0 = false;
263 let mut has_v1 = false;
264 for v in tri.iter() {
265 if find_root(&vertex_map, *v) == v0 {
266 has_v0 = true;
267 }
268 if *v == v1 || find_root(&vertex_map, *v) == v1 {
269 has_v1 = true;
270 }
271 }
272 for v in tri.iter_mut() {
273 if find_root(&vertex_map, *v) == v1 || *v == v1 {
274 *v = v0;
275 }
276 }
277 if tri[0] == tri[1] || tri[1] == tri[2] || tri[0] == tri[2] {
278 deleted_tri[ti] = true;
279 active_tris -= 1;
280 }
281 let _ = (has_v0, has_v1);
282 }
283 for (ti, tri) in tris.iter().enumerate() {
284 if deleted_tri[ti] {
285 continue;
286 }
287 for &v in tri {
288 if v == v0 {
289 for &u in tri {
290 if u != v0 && !deleted_vert[u] {
291 let nc = compute_edge_cost(v0, u, &verts, &quadrics);
292 heap.push(nc);
293 }
294 }
295 }
296 }
297 }
298 }
299 let mut new_verts = Vec::new();
300 let mut vert_remap = vec![usize::MAX; verts.len()];
301 for (i, v) in verts.iter().enumerate() {
302 if !deleted_vert[i] {
303 vert_remap[i] = new_verts.len();
304 new_verts.push(*v);
305 }
306 }
307 let mut new_tris = Vec::new();
308 for (ti, tri) in tris.iter().enumerate() {
309 if deleted_tri[ti] {
310 continue;
311 }
312 let a = vert_remap[find_root(&vertex_map, tri[0])];
313 let b = vert_remap[find_root(&vertex_map, tri[1])];
314 let c = vert_remap[find_root(&vertex_map, tri[2])];
315 if a != usize::MAX && b != usize::MAX && c != usize::MAX && a != b && b != c && a != c {
316 new_tris.push([a, b, c]);
317 }
318 }
319 (new_verts, new_tris)
320}
321pub fn vertex_clustering(
323 vertices: &[[f64; 3]],
324 triangles: &[[usize; 3]],
325 cell_size: f64,
326) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
327 if vertices.is_empty() || cell_size <= 0.0 {
328 return (Vec::new(), Vec::new());
329 }
330 let mut bb_min = vertices[0];
331 let mut bb_max = vertices[0];
332 for v in vertices {
333 for d in 0..3 {
334 bb_min[d] = bb_min[d].min(v[d]);
335 bb_max[d] = bb_max[d].max(v[d]);
336 }
337 }
338 let mut cell_map: HashMap<(i64, i64, i64), Vec<usize>> = HashMap::new();
339 for (i, v) in vertices.iter().enumerate() {
340 let cx = ((v[0] - bb_min[0]) / cell_size) as i64;
341 let cy = ((v[1] - bb_min[1]) / cell_size) as i64;
342 let cz = ((v[2] - bb_min[2]) / cell_size) as i64;
343 cell_map.entry((cx, cy, cz)).or_default().push(i);
344 }
345 let mut vertex_to_cluster = vec![0_usize; vertices.len()];
346 let mut new_verts = Vec::new();
347 for indices in cell_map.values() {
348 let cluster_idx = new_verts.len();
349 let mut avg = [0.0; 3];
350 for &i in indices {
351 avg = add3(avg, vertices[i]);
352 vertex_to_cluster[i] = cluster_idx;
353 }
354 avg = scale3(avg, 1.0 / indices.len() as f64);
355 new_verts.push(avg);
356 }
357 let mut new_tris = Vec::new();
358 for tri in triangles {
359 let a = vertex_to_cluster[tri[0]];
360 let b = vertex_to_cluster[tri[1]];
361 let c = vertex_to_cluster[tri[2]];
362 if a != b && b != c && a != c {
363 new_tris.push([a, b, c]);
364 }
365 }
366 (new_verts, new_tris)
367}
368pub fn edge_length_decimation(
370 vertices: &[[f64; 3]],
371 triangles: &[[usize; 3]],
372 min_edge_length: f64,
373) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
374 let mut verts = vertices.to_vec();
375 let mut tris = triangles.to_vec();
376 let mut deleted_tri = vec![false; tris.len()];
377 let mut vertex_map: Vec<usize> = (0..verts.len()).collect();
378 fn find(map: &[usize], mut v: usize) -> usize {
379 while map[v] != v {
380 v = map[v];
381 }
382 v
383 }
384 let threshold2 = min_edge_length * min_edge_length;
385 let mut short_edges: Vec<(usize, usize, f64)> = Vec::new();
386 for tri in &tris {
387 for k in 0..3 {
388 let a = tri[k];
389 let b = tri[(k + 1) % 3];
390 let d2 = dot3(sub3(verts[a], verts[b]), sub3(verts[a], verts[b]));
391 if d2 < threshold2 {
392 let (lo, hi) = if a < b { (a, b) } else { (b, a) };
393 short_edges.push((lo, hi, d2));
394 }
395 }
396 }
397 short_edges.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal));
398 short_edges.dedup_by(|a, b| a.0 == b.0 && a.1 == b.1);
399 for (v0, v1, _) in short_edges {
400 let r0 = find(&vertex_map, v0);
401 let r1 = find(&vertex_map, v1);
402 if r0 == r1 {
403 continue;
404 }
405 verts[r0] = mid3(verts[r0], verts[r1]);
406 vertex_map[r1] = r0;
407 for (ti, tri) in tris.iter_mut().enumerate() {
408 if deleted_tri[ti] {
409 continue;
410 }
411 for v in tri.iter_mut() {
412 if find(&vertex_map, *v) == r1 || *v == r1 {
413 *v = r0;
414 }
415 }
416 if tri[0] == tri[1] || tri[1] == tri[2] || tri[0] == tri[2] {
417 deleted_tri[ti] = true;
418 }
419 }
420 }
421 let mut alive_verts: Vec<bool> = vec![false; verts.len()];
422 let mut new_tris = Vec::new();
423 for (ti, tri) in tris.iter().enumerate() {
424 if deleted_tri[ti] {
425 continue;
426 }
427 let a = find(&vertex_map, tri[0]);
428 let b = find(&vertex_map, tri[1]);
429 let c = find(&vertex_map, tri[2]);
430 if a != b && b != c && a != c {
431 alive_verts[a] = true;
432 alive_verts[b] = true;
433 alive_verts[c] = true;
434 new_tris.push([a, b, c]);
435 }
436 }
437 let mut remap = vec![usize::MAX; verts.len()];
438 let mut new_verts = Vec::new();
439 for (i, &alive) in alive_verts.iter().enumerate() {
440 if alive {
441 remap[i] = new_verts.len();
442 new_verts.push(verts[i]);
443 }
444 }
445 for tri in &mut new_tris {
446 tri[0] = remap[tri[0]];
447 tri[1] = remap[tri[1]];
448 tri[2] = remap[tri[2]];
449 }
450 (new_verts, new_tris)
451}
452pub fn vertex_split(
456 vertices: &mut Vec<[f64; 3]>,
457 triangles: &mut Vec<[usize; 3]>,
458 record: &CollapseRecord,
459) {
460 let new_idx = vertices.len();
461 vertices.push(record.removed_pos);
462 vertices[record.kept] = record.kept_pos;
463 for tri in &record.removed_triangles {
464 let mut new_tri = *tri;
465 for v in &mut new_tri {
466 if *v == record.removed {
467 *v = new_idx;
468 }
469 }
470 triangles.push(new_tri);
471 }
472}
473pub fn point_to_triangle_dist(p: [f64; 3], a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
475 let ab = sub3(b, a);
476 let ac = sub3(c, a);
477 let ap = sub3(p, a);
478 let d1 = dot3(ab, ap);
479 let d2 = dot3(ac, ap);
480 if d1 <= 0.0 && d2 <= 0.0 {
481 return len3(ap);
482 }
483 let bp = sub3(p, b);
484 let d3 = dot3(ab, bp);
485 let d4 = dot3(ac, bp);
486 if d3 >= 0.0 && d4 <= d3 {
487 return len3(bp);
488 }
489 let cp = sub3(p, c);
490 let d5 = dot3(ab, cp);
491 let d6 = dot3(ac, cp);
492 if d6 >= 0.0 && d5 <= d6 {
493 return len3(cp);
494 }
495 let vc = d1 * d4 - d3 * d2;
496 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
497 let v = d1 / (d1 - d3);
498 let proj = add3(a, scale3(ab, v));
499 return dist3(p, proj);
500 }
501 let vb = d5 * d2 - d1 * d6;
502 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
503 let w = d2 / (d2 - d6);
504 let proj = add3(a, scale3(ac, w));
505 return dist3(p, proj);
506 }
507 let va = d3 * d6 - d5 * d4;
508 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
509 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
510 let proj = add3(b, scale3(sub3(c, b), w));
511 return dist3(p, proj);
512 }
513 let denom = 1.0 / (va + vb + vc);
514 let v = vb * denom;
515 let w = vc * denom;
516 let proj = add3(a, add3(scale3(ab, v), scale3(ac, w)));
517 dist3(p, proj)
518}
519pub fn hausdorff_one_sided(
523 verts_a: &[[f64; 3]],
524 _tris_a: &[[usize; 3]],
525 verts_b: &[[f64; 3]],
526 tris_b: &[[usize; 3]],
527 n_samples: usize,
528) -> f64 {
529 let mut max_dist = 0.0_f64;
530 let sample_count = n_samples.min(verts_a.len());
531 let step = if verts_a.len() > sample_count {
532 verts_a.len() / sample_count
533 } else {
534 1
535 };
536 for i in (0..verts_a.len()).step_by(step) {
537 let p = verts_a[i];
538 let mut min_d = f64::MAX;
539 for tri in tris_b {
540 let d = point_to_triangle_dist(p, verts_b[tri[0]], verts_b[tri[1]], verts_b[tri[2]]);
541 min_d = min_d.min(d);
542 }
543 max_dist = max_dist.max(min_d);
544 }
545 max_dist
546}
547pub fn hausdorff_symmetric(
549 verts_a: &[[f64; 3]],
550 tris_a: &[[usize; 3]],
551 verts_b: &[[f64; 3]],
552 tris_b: &[[usize; 3]],
553 n_samples: usize,
554) -> f64 {
555 let d_ab = hausdorff_one_sided(verts_a, tris_a, verts_b, tris_b, n_samples);
556 let d_ba = hausdorff_one_sided(verts_b, tris_b, verts_a, tris_a, n_samples);
557 d_ab.max(d_ba)
558}
559pub fn check_normal_deviation(
561 _mesh: &HalfEdgeMesh,
562 _v0: usize,
563 _v1: usize,
564 _new_pos: [f64; 3],
565 max_deviation: f64,
566) -> bool {
567 if max_deviation <= 0.0 {
568 return true;
569 }
570 let _threshold = max_deviation.cos();
571 true
572}
573pub fn validate_topology(vertices: &[[f64; 3]], triangles: &[[usize; 3]]) -> TopologyValidation {
575 let n_verts = vertices.len();
576 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
577 let mut vertex_used = vec![false; n_verts];
578 let mut degenerate = 0_usize;
579 for tri in triangles {
580 if tri[0] == tri[1] || tri[1] == tri[2] || tri[0] == tri[2] {
581 degenerate += 1;
582 continue;
583 }
584 if tri[0] >= n_verts || tri[1] >= n_verts || tri[2] >= n_verts {
585 degenerate += 1;
586 continue;
587 }
588 for &v in tri {
589 vertex_used[v] = true;
590 }
591 for k in 0..3 {
592 let a = tri[k];
593 let b = tri[(k + 1) % 3];
594 let (lo, hi) = if a < b { (a, b) } else { (b, a) };
595 *edge_count.entry((lo, hi)).or_insert(0) += 1;
596 }
597 }
598 let non_manifold = edge_count.values().filter(|&&c| c > 2).count();
599 let isolated = vertex_used.iter().filter(|&&u| !u).count();
600 TopologyValidation {
601 is_valid: non_manifold == 0 && degenerate == 0,
602 non_manifold_edges: non_manifold,
603 isolated_vertices: isolated,
604 degenerate_triangles: degenerate,
605 }
606}
607pub fn compute_mesh_stats(vertices: &[[f64; 3]], triangles: &[[usize; 3]]) -> MeshStats {
609 let mut edge_set: HashSet<(usize, usize)> = HashSet::new();
610 let mut sum_len = 0.0;
611 let mut min_len = f64::MAX;
612 let mut max_len = 0.0_f64;
613 let mut sum_qual = 0.0;
614 let mut min_qual = f64::MAX;
615 for tri in triangles {
616 let a = vertices[tri[0]];
617 let b = vertices[tri[1]];
618 let c = vertices[tri[2]];
619 let q = triangle_quality(a, b, c);
620 sum_qual += q;
621 min_qual = min_qual.min(q);
622 for k in 0..3 {
623 let va = tri[k];
624 let vb = tri[(k + 1) % 3];
625 let (lo, hi) = if va < vb { (va, vb) } else { (vb, va) };
626 if edge_set.insert((lo, hi)) {
627 let l = dist3(vertices[va], vertices[vb]);
628 sum_len += l;
629 min_len = min_len.min(l);
630 max_len = max_len.max(l);
631 }
632 }
633 }
634 let n_edges = edge_set.len();
635 let n_tris = triangles.len();
636 MeshStats {
637 n_vertices: vertices.len(),
638 n_triangles: n_tris,
639 n_edges,
640 avg_edge_length: if n_edges > 0 {
641 sum_len / n_edges as f64
642 } else {
643 0.0
644 },
645 min_edge_length: if n_edges > 0 { min_len } else { 0.0 },
646 max_edge_length: max_len,
647 avg_quality: if n_tris > 0 {
648 sum_qual / n_tris as f64
649 } else {
650 0.0
651 },
652 min_quality: if n_tris > 0 { min_qual } else { 0.0 },
653 }
654}
655pub fn find_boundary_edges(triangles: &[[usize; 3]]) -> Vec<(usize, usize)> {
657 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
658 for tri in triangles {
659 for k in 0..3 {
660 let a = tri[k];
661 let b = tri[(k + 1) % 3];
662 let (lo, hi) = if a < b { (a, b) } else { (b, a) };
663 *edge_count.entry((lo, hi)).or_insert(0) += 1;
664 }
665 }
666 edge_count
667 .into_iter()
668 .filter(|&(_, c)| c == 1)
669 .map(|(e, _)| e)
670 .collect()
671}
672pub fn find_boundary_vertices(triangles: &[[usize; 3]]) -> HashSet<usize> {
674 let edges = find_boundary_edges(triangles);
675 let mut verts = HashSet::new();
676 for (a, b) in edges {
677 verts.insert(a);
678 verts.insert(b);
679 }
680 verts
681}
682pub fn euler_characteristic(n_vertices: usize, triangles: &[[usize; 3]]) -> i64 {
684 let mut edge_set: HashSet<(usize, usize)> = HashSet::new();
685 for tri in triangles {
686 for k in 0..3 {
687 let a = tri[k];
688 let b = tri[(k + 1) % 3];
689 let (lo, hi) = if a < b { (a, b) } else { (b, a) };
690 edge_set.insert((lo, hi));
691 }
692 }
693 n_vertices as i64 - edge_set.len() as i64 + triangles.len() as i64
694}
695pub fn simplify_to_ratio(
697 vertices: &[[f64; 3]],
698 triangles: &[[usize; 3]],
699 ratio: f64,
700) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
701 let target = ((triangles.len() as f64 * ratio).ceil() as usize).max(1);
702 let config = QemConfig {
703 target_triangles: target,
704 max_error: 0.0,
705 preserve_boundary: true,
706 max_normal_deviation: 0.0,
707 preserve_texture_seams: false,
708 boundary_weight: 100.0,
709 };
710 qem_simplify(vertices, triangles, &config)
711}
712pub fn mesh_surface_area(vertices: &[[f64; 3]], triangles: &[[usize; 3]]) -> f64 {
714 let mut area = 0.0;
715 for tri in triangles {
716 area += triangle_area_3d(vertices[tri[0]], vertices[tri[1]], vertices[tri[2]]);
717 }
718 area
719}
720pub fn mesh_signed_volume(vertices: &[[f64; 3]], triangles: &[[usize; 3]]) -> f64 {
722 let mut vol = 0.0;
723 for tri in triangles {
724 let a = vertices[tri[0]];
725 let b = vertices[tri[1]];
726 let c = vertices[tri[2]];
727 vol += dot3(a, cross3(b, c));
728 }
729 vol / 6.0
730}
731pub fn count_connected_components(n_vertices: usize, triangles: &[[usize; 3]]) -> usize {
733 let mut parent: Vec<usize> = (0..n_vertices).collect();
734 fn find(parent: &mut [usize], mut x: usize) -> usize {
735 while parent[x] != x {
736 parent[x] = parent[parent[x]];
737 x = parent[x];
738 }
739 x
740 }
741 fn union(parent: &mut [usize], a: usize, b: usize) {
742 let ra = find(parent, a);
743 let rb = find(parent, b);
744 if ra != rb {
745 parent[ra] = rb;
746 }
747 }
748 for tri in triangles {
749 union(&mut parent, tri[0], tri[1]);
750 union(&mut parent, tri[1], tri[2]);
751 }
752 let used: HashSet<usize> = triangles.iter().flat_map(|t| t.iter().copied()).collect();
753 let roots: HashSet<usize> = used.iter().map(|&v| find(&mut parent, v)).collect();
754 roots.len()
755}
756pub fn compact_mesh(
758 vertices: &[[f64; 3]],
759 triangles: &[[usize; 3]],
760) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
761 let mut used = vec![false; vertices.len()];
762 for tri in triangles {
763 for &v in tri {
764 if v < vertices.len() {
765 used[v] = true;
766 }
767 }
768 }
769 let mut remap = vec![usize::MAX; vertices.len()];
770 let mut new_verts = Vec::new();
771 for (i, &u) in used.iter().enumerate() {
772 if u {
773 remap[i] = new_verts.len();
774 new_verts.push(vertices[i]);
775 }
776 }
777 let new_tris: Vec<[usize; 3]> = triangles
778 .iter()
779 .filter_map(|tri| {
780 let a = remap[tri[0]];
781 let b = remap[tri[1]];
782 let c = remap[tri[2]];
783 if a != usize::MAX && b != usize::MAX && c != usize::MAX {
784 Some([a, b, c])
785 } else {
786 None
787 }
788 })
789 .collect();
790 (new_verts, new_tris)
791}