1use std::collections::{HashMap, HashSet, VecDeque};
6
7use super::types::{
8 AtlasPatch, BooleanResult, FeatureEdge, MeshQualityStats, ProcessMesh, Quadric,
9 TriangleQuality, UvParameterization,
10};
11
12pub type Vertex = [f64; 3];
14pub type Face = [usize; 3];
16pub type Uv = [f64; 2];
18#[inline]
19pub(super) fn vec3_add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
20 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
21}
22#[inline]
23pub(super) fn vec3_sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
24 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
25}
26#[inline]
27pub(super) fn vec3_scale(a: [f64; 3], s: f64) -> [f64; 3] {
28 [a[0] * s, a[1] * s, a[2] * s]
29}
30#[inline]
31pub(super) fn vec3_dot(a: [f64; 3], b: [f64; 3]) -> f64 {
32 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
33}
34#[inline]
35pub(super) fn vec3_cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
36 [
37 a[1] * b[2] - a[2] * b[1],
38 a[2] * b[0] - a[0] * b[2],
39 a[0] * b[1] - a[1] * b[0],
40 ]
41}
42#[inline]
43pub(super) fn vec3_len(a: [f64; 3]) -> f64 {
44 (a[0] * a[0] + a[1] * a[1] + a[2] * a[2]).sqrt()
45}
46#[inline]
47pub(super) fn vec3_normalize(a: [f64; 3]) -> [f64; 3] {
48 let l = vec3_len(a);
49 if l < 1e-15 {
50 [0.0, 0.0, 0.0]
51 } else {
52 vec3_scale(a, 1.0 / l)
53 }
54}
55#[cfg(test)]
56#[inline]
57pub(super) fn vec3_lerp(a: [f64; 3], b: [f64; 3], t: f64) -> [f64; 3] {
58 [
59 a[0] + t * (b[0] - a[0]),
60 a[1] + t * (b[1] - a[1]),
61 a[2] + t * (b[2] - a[2]),
62 ]
63}
64pub fn laplacian_smooth(mesh: &ProcessMesh, lambda: f64, iters: usize) -> ProcessMesh {
68 let mut out = mesh.clone();
69 let adj = mesh.build_adjacency();
70 for _ in 0..iters {
71 let old = out.verts.clone();
72 for (i, nbrs) in adj.iter().enumerate() {
73 if nbrs.is_empty() {
74 continue;
75 }
76 let mut avg = [0.0_f64; 3];
77 for &j in nbrs {
78 avg = vec3_add(avg, old[j]);
79 }
80 avg = vec3_scale(avg, 1.0 / nbrs.len() as f64);
81 let delta = vec3_sub(avg, old[i]);
82 out.verts[i] = vec3_add(old[i], vec3_scale(delta, lambda));
83 }
84 }
85 out
86}
87pub fn taubin_smooth(mesh: &ProcessMesh, lambda: f64, mu: f64, iters: usize) -> ProcessMesh {
92 let mut out = mesh.clone();
93 let adj = mesh.build_adjacency();
94 let one_step = |verts: &[Vertex], step: f64, a: &[Vec<usize>]| -> Vec<Vertex> {
95 let mut next = verts.to_vec();
96 for (i, nbrs) in a.iter().enumerate() {
97 if nbrs.is_empty() {
98 continue;
99 }
100 let mut avg = [0.0_f64; 3];
101 for &j in nbrs {
102 avg = vec3_add(avg, verts[j]);
103 }
104 avg = vec3_scale(avg, 1.0 / nbrs.len() as f64);
105 let delta = vec3_sub(avg, verts[i]);
106 next[i] = vec3_add(verts[i], vec3_scale(delta, step));
107 }
108 next
109 };
110 for _ in 0..iters {
111 out.verts = one_step(&out.verts, lambda, &adj);
112 out.verts = one_step(&out.verts, mu, &adj);
113 }
114 out
115}
116pub fn midpoint_subdivide(mesh: &ProcessMesh) -> ProcessMesh {
119 let mut verts = mesh.verts.clone();
120 let mut faces = Vec::new();
121 let mut edge_mid: HashMap<(usize, usize), usize> = HashMap::new();
122 let mut get_mid = |a: usize, b: usize, v: &mut Vec<Vertex>| -> usize {
123 let key = if a < b { (a, b) } else { (b, a) };
124 *edge_mid.entry(key).or_insert_with(|| {
125 let mid = vec3_scale(vec3_add(v[a], v[b]), 0.5);
126 let idx = v.len();
127 v.push(mid);
128 idx
129 })
130 };
131 for &[a, b, c] in &mesh.faces {
132 let ab = get_mid(a, b, &mut verts);
133 let bc = get_mid(b, c, &mut verts);
134 let ca = get_mid(c, a, &mut verts);
135 faces.push([a, ab, ca]);
136 faces.push([ab, b, bc]);
137 faces.push([ca, bc, c]);
138 faces.push([ab, bc, ca]);
139 }
140 ProcessMesh::new(verts, faces)
141}
142pub fn loop_subdivide(mesh: &ProcessMesh) -> ProcessMesh {
146 let mut verts = mesh.verts.clone();
147 let mut edge_mid: HashMap<(usize, usize), usize> = HashMap::new();
148 let mut edge_opp: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
149 for &[a, b, c] in &mesh.faces {
150 let edges = [(a, b, c), (b, c, a), (c, a, b)];
151 for (i, j, k) in edges {
152 let key = if i < j { (i, j) } else { (j, i) };
153 edge_opp.entry(key).or_default().push(k);
154 }
155 }
156 let mut get_mid = |a: usize, b: usize, v: &mut Vec<Vertex>| -> usize {
157 let key = if a < b { (a, b) } else { (b, a) };
158 *edge_mid.entry(key).or_insert_with(|| {
159 let opps = edge_opp.get(&key).cloned().unwrap_or_default();
160 let pos = if opps.len() == 2 {
161 let sum_ab = vec3_scale(vec3_add(v[a], v[b]), 3.0 / 8.0);
162 let sum_op = vec3_scale(vec3_add(v[opps[0]], v[opps[1]]), 1.0 / 8.0);
163 vec3_add(sum_ab, sum_op)
164 } else {
165 vec3_scale(vec3_add(v[a], v[b]), 0.5)
166 };
167 let idx = v.len();
168 v.push(pos);
169 idx
170 })
171 };
172 let adj = mesh.build_adjacency();
173 let orig = mesh.verts.clone();
174 let n = orig.len();
175 let mut new_pos = orig.clone();
176 for i in 0..n {
177 let nbrs = &adj[i];
178 let k = nbrs.len() as f64;
179 let beta = if k <= 3.0 {
180 3.0 / 16.0
181 } else {
182 3.0 / (8.0 * k)
183 };
184 let mut sum = [0.0_f64; 3];
185 for &j in nbrs {
186 sum = vec3_add(sum, orig[j]);
187 }
188 new_pos[i] = vec3_add(vec3_scale(orig[i], 1.0 - k * beta), vec3_scale(sum, beta));
189 }
190 let mut faces = Vec::new();
191 for &[a, b, c] in &mesh.faces {
192 let ab = get_mid(a, b, &mut verts);
193 let bc = get_mid(b, c, &mut verts);
194 let ca = get_mid(c, a, &mut verts);
195 faces.push([a, ab, ca]);
196 faces.push([ab, b, bc]);
197 faces.push([ca, bc, c]);
198 faces.push([ab, bc, ca]);
199 }
200 verts[..n].copy_from_slice(&new_pos[..n]);
201 ProcessMesh::new(verts, faces)
202}
203pub fn catmull_clark_subdivide(mesh: &ProcessMesh) -> ProcessMesh {
208 let nv = mesh.verts.len();
209 let mut new_verts = mesh.verts.clone();
210 let mut face_pts: Vec<Vertex> = Vec::with_capacity(mesh.faces.len());
211 for &[a, b, c] in &mesh.faces {
212 let fp = vec3_scale(
213 vec3_add(vec3_add(mesh.verts[a], mesh.verts[b]), mesh.verts[c]),
214 1.0 / 3.0,
215 );
216 face_pts.push(fp);
217 }
218 let mut edge_map: HashMap<(usize, usize), usize> = HashMap::new();
219 let mut edge_pts: Vec<Vertex> = Vec::new();
220 let mut vert_face_avg = vec![[0.0_f64; 3]; nv];
221 let mut vert_face_cnt = vec![0usize; nv];
222 for (fi, &[a, b, c]) in mesh.faces.iter().enumerate() {
223 let fp = face_pts[fi];
224 for &v in &[a, b, c] {
225 vert_face_avg[v] = vec3_add(vert_face_avg[v], fp);
226 vert_face_cnt[v] += 1;
227 }
228 for (i, j) in [(a, b), (b, c), (c, a)] {
229 let key = if i < j { (i, j) } else { (j, i) };
230 edge_map.entry(key).or_insert_with(|| {
231 let ep = vec3_scale(vec3_add(mesh.verts[i], mesh.verts[j]), 0.5);
232 let idx = edge_pts.len();
233 edge_pts.push(ep);
234 idx
235 });
236 }
237 }
238 let adj = mesh.build_adjacency();
239 for i in 0..nv {
240 let n = vert_face_cnt[i] as f64;
241 if n == 0.0 {
242 continue;
243 }
244 let f_avg = vec3_scale(vert_face_avg[i], 1.0 / n);
245 let nbrs = &adj[i];
246 let mut e_avg = [0.0_f64; 3];
247 for &j in nbrs {
248 e_avg = vec3_add(e_avg, mesh.verts[j]);
249 }
250 let m = nbrs.len() as f64;
251 e_avg = vec3_scale(e_avg, 1.0 / m.max(1.0));
252 new_verts[i] = vec3_scale(
253 vec3_add(
254 vec3_add(f_avg, vec3_scale(e_avg, 2.0)),
255 vec3_scale(mesh.verts[i], (n - 3.0).max(0.0)),
256 ),
257 1.0 / n,
258 );
259 }
260 let ep_offset = nv;
261 let fp_offset = ep_offset + edge_pts.len();
262 for ep in &edge_pts {
263 new_verts.push(*ep);
264 }
265 for fp in &face_pts {
266 new_verts.push(*fp);
267 }
268 let mut faces = Vec::new();
269 for (fi, &[a, b, c]) in mesh.faces.iter().enumerate() {
270 let fp_idx = fp_offset + fi;
271 let ab_idx = ep_offset
272 + *edge_map
273 .get(&if a < b { (a, b) } else { (b, a) })
274 .expect("key must exist");
275 let bc_idx = ep_offset
276 + *edge_map
277 .get(&if b < c { (b, c) } else { (c, b) })
278 .expect("key must exist");
279 let ca_idx = ep_offset
280 + *edge_map
281 .get(&if c < a { (c, a) } else { (a, c) })
282 .expect("key must exist");
283 faces.push([a, ab_idx, fp_idx]);
284 faces.push([ab_idx, b, fp_idx]);
285 faces.push([b, bc_idx, fp_idx]);
286 faces.push([bc_idx, c, fp_idx]);
287 faces.push([c, ca_idx, fp_idx]);
288 faces.push([ca_idx, a, fp_idx]);
289 }
290 ProcessMesh::new(new_verts, faces)
291}
292pub(super) fn face_quadrics(mesh: &ProcessMesh) -> Vec<Quadric> {
294 mesh.faces
295 .iter()
296 .map(|&[a, b, c]| {
297 let va = mesh.verts[a];
298 let vb = mesh.verts[b];
299 let vc = mesh.verts[c];
300 let ab = vec3_sub(vb, va);
301 let ac = vec3_sub(vc, va);
302 let n = vec3_normalize(vec3_cross(ab, ac));
303 let d = -vec3_dot(n, va);
304 Quadric::from_plane(n[0], n[1], n[2], d)
305 })
306 .collect()
307}
308pub(super) fn vertex_quadrics(mesh: &ProcessMesh) -> Vec<Quadric> {
310 let nv = mesh.verts.len();
311 let fq = face_quadrics(mesh);
312 let mut vq = vec![Quadric::zero(); nv];
313 for (fi, &[a, b, c]) in mesh.faces.iter().enumerate() {
314 for &v in &[a, b, c] {
315 vq[v] = vq[v].add(&fq[fi]);
316 }
317 }
318 vq
319}
320pub fn qem_decimate(mesh: &ProcessMesh, target_faces: usize) -> ProcessMesh {
323 if mesh.faces.len() <= target_faces {
324 return mesh.clone();
325 }
326 let nv = mesh.verts.len();
327 let mut verts = mesh.verts.clone();
328 let mut faces = mesh.faces.clone();
329 let mut vq = vertex_quadrics(mesh);
330 let mut removed_verts = vec![false; nv];
331 let mut removed_faces = vec![false; faces.len()];
332 while faces.iter().filter(|&&_f| true).count() - removed_faces.iter().filter(|&&r| r).count()
333 > target_faces
334 {
335 let mut best_cost = f64::INFINITY;
336 let mut best_edge: Option<(usize, usize)> = None;
337 let mut best_pos = [0.0_f64; 3];
338 let mut seen = HashSet::new();
339 for &[a, b, c] in &faces {
340 for (i, j) in [(a, b), (b, c), (c, a)] {
341 let key = if i < j { (i, j) } else { (j, i) };
342 if seen.contains(&key) {
343 continue;
344 }
345 seen.insert(key);
346 if removed_verts[i] || removed_verts[j] {
347 continue;
348 }
349 let q = vq[i].add(&vq[j]);
350 let mid = vec3_scale(vec3_add(verts[i], verts[j]), 0.5);
351 let cost = q.evaluate(mid);
352 if cost < best_cost {
353 best_cost = cost;
354 best_edge = Some((i, j));
355 best_pos = mid;
356 }
357 }
358 }
359 if let Some((vi, vj)) = best_edge {
360 verts[vi] = best_pos;
361 vq[vi] = vq[vi].add(&vq[vj]);
362 removed_verts[vj] = true;
363 for (fi, f) in faces.iter_mut().enumerate() {
364 if removed_faces[fi] {
365 continue;
366 }
367 for idx in f.iter_mut() {
368 if *idx == vj {
369 *idx = vi;
370 }
371 }
372 if f[0] == f[1] || f[1] == f[2] || f[0] == f[2] {
373 removed_faces[fi] = true;
374 }
375 }
376 } else {
377 break;
378 }
379 }
380 let remap: Vec<usize> = {
381 let mut m = vec![0usize; nv];
382 let mut cnt = 0;
383 for i in 0..nv {
384 if !removed_verts[i] {
385 m[i] = cnt;
386 cnt += 1;
387 }
388 }
389 m
390 };
391 let new_verts: Vec<Vertex> = (0..nv)
392 .filter(|&i| !removed_verts[i])
393 .map(|i| verts[i])
394 .collect();
395 let new_faces: Vec<Face> = faces
396 .iter()
397 .enumerate()
398 .filter(|&(fi, _)| !removed_faces[fi])
399 .map(|(_, &[a, b, c])| [remap[a], remap[b], remap[c]])
400 .collect();
401 ProcessMesh::new(new_verts, new_faces)
402}
403pub fn triangle_quality(va: Vertex, vb: Vertex, vc: Vertex) -> TriangleQuality {
405 let ab = vec3_sub(vb, va);
406 let ac = vec3_sub(vc, va);
407 let bc = vec3_sub(vc, vb);
408 let la = vec3_len(bc);
409 let lb = vec3_len(ac);
410 let lc = vec3_len(ab);
411 let s = (la + lb + lc) / 2.0;
412 let area2 = vec3_len(vec3_cross(ab, ac));
413 let area = area2 / 2.0;
414 let inradius = if s > 1e-15 { area / s } else { 0.0 };
415 let circumradius = if area > 1e-15 {
416 la * lb * lc / (4.0 * area)
417 } else {
418 f64::INFINITY
419 };
420 let aspect_ratio = if inradius > 1e-15 {
421 circumradius / (2.0 * inradius)
422 } else {
423 f64::INFINITY
424 };
425 let angle_a = if la > 1e-15 && lb > 1e-15 {
426 let cos_a = (lb * lb + lc * lc - la * la) / (2.0 * lb * lc);
427 cos_a.clamp(-1.0, 1.0).acos()
428 } else {
429 0.0
430 };
431 let angle_b = if la > 1e-15 && lc > 1e-15 {
432 let cos_b = (la * la + lc * lc - lb * lb) / (2.0 * la * lc);
433 cos_b.clamp(-1.0, 1.0).acos()
434 } else {
435 0.0
436 };
437 let angle_c = std::f64::consts::PI - angle_a - angle_b;
438 let perim = 2.0 * s;
439 let ideal_area = perim * perim * (3.0_f64.sqrt()) / 36.0;
440 let area_quality = if ideal_area > 1e-15 {
441 (area / ideal_area).min(1.0)
442 } else {
443 0.0
444 };
445 TriangleQuality {
446 aspect_ratio,
447 min_angle: angle_a.min(angle_b).min(angle_c),
448 max_angle: angle_a.max(angle_b).max(angle_c),
449 area_quality,
450 }
451}
452pub fn mesh_quality_stats(mesh: &ProcessMesh) -> MeshQualityStats {
454 if mesh.faces.is_empty() {
455 return MeshQualityStats {
456 mean_aspect_ratio: 0.0,
457 max_aspect_ratio: 0.0,
458 min_angle_deg: 0.0,
459 mean_area_quality: 0.0,
460 };
461 }
462 let n = mesh.faces.len() as f64;
463 let mut sum_ar = 0.0;
464 let mut max_ar = 0.0_f64;
465 let mut min_ang = f64::INFINITY;
466 let mut sum_aq = 0.0;
467 for &[a, b, c] in &mesh.faces {
468 let q = triangle_quality(mesh.verts[a], mesh.verts[b], mesh.verts[c]);
469 sum_ar += q.aspect_ratio;
470 max_ar = max_ar.max(q.aspect_ratio);
471 min_ang = min_ang.min(q.min_angle);
472 sum_aq += q.area_quality;
473 }
474 MeshQualityStats {
475 mean_aspect_ratio: sum_ar / n,
476 max_aspect_ratio: max_ar,
477 min_angle_deg: min_ang.to_degrees(),
478 mean_area_quality: sum_aq / n,
479 }
480}
481pub fn detect_boundary_loops(mesh: &ProcessMesh) -> Vec<Vec<usize>> {
486 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
487 for &[a, b, c] in &mesh.faces {
488 for (i, j) in [(a, b), (b, c), (c, a)] {
489 *edge_count.entry((i, j)).or_insert(0) += 1;
490 }
491 }
492 let mut boundary_adj: HashMap<usize, usize> = HashMap::new();
493 for &(i, j) in edge_count.keys() {
494 if edge_count.get(&(j, i)).copied().unwrap_or(0) == 0 {
495 boundary_adj.insert(i, j);
496 }
497 }
498 let mut visited = HashSet::new();
499 let mut loops = Vec::new();
500 for &start in boundary_adj.keys() {
501 if visited.contains(&start) {
502 continue;
503 }
504 let mut loop_ = Vec::new();
505 let mut cur = start;
506 loop {
507 if visited.contains(&cur) {
508 break;
509 }
510 visited.insert(cur);
511 loop_.push(cur);
512 match boundary_adj.get(&cur) {
513 Some(&next) => cur = next,
514 None => break,
515 }
516 }
517 if !loop_.is_empty() {
518 loops.push(loop_);
519 }
520 }
521 loops
522}
523pub fn fill_hole_fan(verts: &mut Vec<Vertex>, loop_: &[usize]) -> Vec<Face> {
527 if loop_.len() < 3 {
528 return Vec::new();
529 }
530 let mut centroid = [0.0_f64; 3];
531 for &vi in loop_ {
532 centroid = vec3_add(centroid, verts[vi]);
533 }
534 centroid = vec3_scale(centroid, 1.0 / loop_.len() as f64);
535 let center_idx = verts.len();
536 verts.push(centroid);
537 let mut new_faces = Vec::new();
538 let n = loop_.len();
539 for i in 0..n {
540 new_faces.push([loop_[(i + 1) % n], loop_[i], center_idx]);
541 }
542 new_faces
543}
544pub fn fill_all_holes(mesh: &ProcessMesh) -> ProcessMesh {
546 let mut out = mesh.clone();
547 loop {
548 let loops = detect_boundary_loops(&out);
549 if loops.is_empty() {
550 break;
551 }
552 for lp in &loops {
553 let new_faces = fill_hole_fan(&mut out.verts, lp);
554 out.faces.extend(new_faces);
555 }
556 if detect_boundary_loops(&out).len() >= loops.len() {
557 break;
558 }
559 }
560 out
561}
562pub fn fix_non_manifold_vertices(mesh: &ProcessMesh) -> ProcessMesh {
565 let nv = mesh.verts.len();
566 let mut vert_faces: Vec<Vec<usize>> = vec![Vec::new(); nv];
567 for (fi, &[a, b, c]) in mesh.faces.iter().enumerate() {
568 vert_faces[a].push(fi);
569 vert_faces[b].push(fi);
570 vert_faces[c].push(fi);
571 }
572 let mut new_verts = mesh.verts.clone();
573 let mut new_faces = mesh.faces.clone();
574 for vi in 0..nv {
575 let flist = &vert_faces[vi];
576 if flist.len() < 2 {
577 continue;
578 }
579 let face_set: HashSet<usize> = flist.iter().cloned().collect();
580 let mut visited = HashSet::new();
581 let mut queue = VecDeque::new();
582 queue.push_back(flist[0]);
583 while let Some(fi) = queue.pop_front() {
584 if visited.contains(&fi) {
585 continue;
586 }
587 visited.insert(fi);
588 let [a, b, c] = new_faces[fi];
589 for &fj in &vert_faces[a] {
590 if face_set.contains(&fj) && !visited.contains(&fj) {
591 queue.push_back(fj);
592 }
593 }
594 for &fj in &vert_faces[b] {
595 if face_set.contains(&fj) && !visited.contains(&fj) {
596 queue.push_back(fj);
597 }
598 }
599 for &fj in &vert_faces[c] {
600 if face_set.contains(&fj) && !visited.contains(&fj) {
601 queue.push_back(fj);
602 }
603 }
604 }
605 let extra: Vec<usize> = flist
606 .iter()
607 .filter(|&&f| !visited.contains(&f))
608 .cloned()
609 .collect();
610 if !extra.is_empty() {
611 let new_idx = new_verts.len();
612 new_verts.push(new_verts[vi]);
613 for fi in extra {
614 for idx in new_faces[fi].iter_mut() {
615 if *idx == vi {
616 *idx = new_idx;
617 }
618 }
619 }
620 }
621 }
622 ProcessMesh::new(new_verts, new_faces)
623}
624pub fn detect_feature_edges(mesh: &ProcessMesh, threshold_deg: f64) -> Vec<FeatureEdge> {
627 let thresh = threshold_deg.to_radians();
628 let face_normals: Vec<[f64; 3]> = mesh
629 .faces
630 .iter()
631 .map(|&[a, b, c]| {
632 let ab = vec3_sub(mesh.verts[b], mesh.verts[a]);
633 let ac = vec3_sub(mesh.verts[c], mesh.verts[a]);
634 vec3_normalize(vec3_cross(ab, ac))
635 })
636 .collect();
637 let mut edge_faces: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
638 for (fi, &[a, b, c]) in mesh.faces.iter().enumerate() {
639 for (i, j) in [(a, b), (b, c), (c, a)] {
640 let key = if i < j { (i, j) } else { (j, i) };
641 edge_faces.entry(key).or_default().push(fi);
642 }
643 }
644 let mut features = Vec::new();
645 for ((v0, v1), flist) in &edge_faces {
646 if flist.len() != 2 {
647 continue;
648 }
649 let n0 = face_normals[flist[0]];
650 let n1 = face_normals[flist[1]];
651 let cos_a = vec3_dot(n0, n1).clamp(-1.0, 1.0);
652 let angle = cos_a.acos();
653 if angle > thresh {
654 features.push(FeatureEdge {
655 v0: *v0,
656 v1: *v1,
657 dihedral_angle: angle,
658 });
659 }
660 }
661 features
662}
663pub fn isotropic_remesh(mesh: &ProcessMesh, target_len: f64, iters: usize) -> ProcessMesh {
668 let mut out = mesh.clone();
669 for _ in 0..iters {
670 out = split_long_edges(&out, target_len * 4.0 / 3.0);
671 out = collapse_short_edges(&out, target_len * 4.0 / 5.0);
672 out = laplacian_smooth(&out, 0.5, 1);
673 }
674 out
675}
676pub fn split_long_edges(mesh: &ProcessMesh, max_len: f64) -> ProcessMesh {
678 let mut verts = mesh.verts.clone();
679 let mut faces = Vec::new();
680 let mut edge_mid: HashMap<(usize, usize), usize> = HashMap::new();
681 let mut get_mid = |a: usize, b: usize, v: &mut Vec<Vertex>| -> Option<usize> {
682 let d = vec3_len(vec3_sub(v[b], v[a]));
683 if d > max_len {
684 let key = if a < b { (a, b) } else { (b, a) };
685 let idx = *edge_mid.entry(key).or_insert_with(|| {
686 let mid = vec3_scale(vec3_add(v[a], v[b]), 0.5);
687 let i = v.len();
688 v.push(mid);
689 i
690 });
691 Some(idx)
692 } else {
693 None
694 }
695 };
696 for &[a, b, c] in &mesh.faces {
697 let ab = get_mid(a, b, &mut verts);
698 let bc = get_mid(b, c, &mut verts);
699 let ca = get_mid(c, a, &mut verts);
700 match (ab, bc, ca) {
701 (None, None, None) => faces.push([a, b, c]),
702 (Some(m), None, None) => {
703 faces.push([a, m, c]);
704 faces.push([m, b, c]);
705 }
706 (None, Some(m), None) => {
707 faces.push([a, b, m]);
708 faces.push([a, m, c]);
709 }
710 (None, None, Some(m)) => {
711 faces.push([a, b, m]);
712 faces.push([m, b, c]);
713 }
714 (Some(ab), Some(bc), None) => {
715 faces.push([a, ab, c]);
716 faces.push([ab, b, bc]);
717 faces.push([ab, bc, c]);
718 }
719 (Some(ab), None, Some(ca)) => {
720 faces.push([a, ab, ca]);
721 faces.push([ab, b, c]);
722 faces.push([ab, c, ca]);
723 }
724 (None, Some(bc), Some(ca)) => {
725 faces.push([a, b, bc]);
726 faces.push([a, bc, ca]);
727 faces.push([ca, bc, c]);
728 }
729 (Some(ab), Some(bc), Some(ca)) => {
730 faces.push([a, ab, ca]);
731 faces.push([ab, b, bc]);
732 faces.push([ca, bc, c]);
733 faces.push([ab, bc, ca]);
734 }
735 }
736 }
737 ProcessMesh::new(verts, faces)
738}
739pub fn collapse_short_edges(mesh: &ProcessMesh, min_len: f64) -> ProcessMesh {
741 let nv = mesh.verts.len();
742 let mut verts = mesh.verts.clone();
743 let mut faces = mesh.faces.clone();
744 let mut remap: Vec<usize> = (0..nv).collect();
745 fn root(r: &mut Vec<usize>, i: usize) -> usize {
746 if r[i] == i {
747 i
748 } else {
749 let p = root(r, r[i]);
750 r[i] = p;
751 p
752 }
753 }
754 let mut collapsed = true;
755 while collapsed {
756 collapsed = false;
757 let mut seen = HashSet::new();
758 for &[a, b, c] in &faces {
759 for (i, j) in [(a, b), (b, c), (c, a)] {
760 let ri = root(&mut remap, i);
761 let rj = root(&mut remap, j);
762 if ri == rj {
763 continue;
764 }
765 let key = if ri < rj { (ri, rj) } else { (rj, ri) };
766 if seen.contains(&key) {
767 continue;
768 }
769 seen.insert(key);
770 let d = vec3_len(vec3_sub(verts[rj], verts[ri]));
771 if d < min_len {
772 verts[ri] = vec3_scale(vec3_add(verts[ri], verts[rj]), 0.5);
773 remap[rj] = ri;
774 collapsed = true;
775 }
776 }
777 }
778 for i in 0..nv {
779 let r = root(&mut remap, i);
780 remap[i] = r;
781 }
782 for f in &mut faces {
783 for idx in f.iter_mut() {
784 *idx = remap[*idx];
785 }
786 }
787 faces.retain(|&[a, b, c]| a != b && b != c && a != c);
788 }
789 let active: Vec<bool> = (0..nv).map(|i| remap[i] == i).collect();
790 let new_idx: Vec<usize> = {
791 let mut m = vec![0usize; nv];
792 let mut cnt = 0;
793 for i in 0..nv {
794 if active[i] {
795 m[i] = cnt;
796 cnt += 1;
797 }
798 }
799 m
800 };
801 let new_verts: Vec<Vertex> = (0..nv).filter(|&i| active[i]).map(|i| verts[i]).collect();
802 let new_faces: Vec<Face> = faces
803 .iter()
804 .map(|&[a, b, c]| {
805 let ra = root(&mut remap, a);
806 let rb = root(&mut remap, b);
807 let rc = root(&mut remap, c);
808 [new_idx[ra], new_idx[rb], new_idx[rc]]
809 })
810 .collect();
811 ProcessMesh::new(new_verts, new_faces)
812}
813pub fn tutte_parameterize(mesh: &ProcessMesh, iters: usize) -> UvParameterization {
817 let nv = mesh.verts.len();
818 let loops = detect_boundary_loops(mesh);
819 let boundary: Vec<usize> = loops.into_iter().next().unwrap_or_default();
820 let nb = boundary.len();
821 let mut uv = vec![[0.0_f64; 2]; nv];
822 for (k, &vi) in boundary.iter().enumerate() {
823 let angle = 2.0 * std::f64::consts::PI * k as f64 / nb as f64;
824 uv[vi] = [0.5 + 0.5 * angle.cos(), 0.5 + 0.5 * angle.sin()];
825 }
826 let boundary_set: HashSet<usize> = boundary.iter().cloned().collect();
827 let adj = mesh.build_adjacency();
828 for _ in 0..iters {
829 let old = uv.clone();
830 for i in 0..nv {
831 if boundary_set.contains(&i) {
832 continue;
833 }
834 let nbrs = &adj[i];
835 if nbrs.is_empty() {
836 continue;
837 }
838 let mut sum = [0.0_f64; 2];
839 for &j in nbrs {
840 sum[0] += old[j][0];
841 sum[1] += old[j][1];
842 }
843 let n = nbrs.len() as f64;
844 uv[i] = [sum[0] / n, sum[1] / n];
845 }
846 }
847 let mut out = mesh.clone();
848 out.uvs = Some(uv.clone());
849 UvParameterization { mesh: out, uvs: uv }
850}
851pub fn lscm_parameterize(mesh: &ProcessMesh) -> UvParameterization {
865 use oxiphysics_fem::parallel_solver::{CsrMatrix, ParallelPcgSolver};
866 use std::collections::BTreeMap;
867
868 let nv = mesh.verts.len();
869 let nf = mesh.faces.len();
870
871 if nv < 3 || nf == 0 {
873 return tutte_parameterize(mesh, 200);
874 }
875
876 let pin_col = [0usize, 1, 2, 3]; let pin_val = [0.0_f64, 0.0, 1.0, 0.0];
889
890 let mut col_map = vec![usize::MAX; 2 * nv];
892 let mut free_col = 0usize;
893 for (c, slot) in col_map.iter_mut().enumerate() {
894 if pin_col.contains(&c) {
895 continue;
896 }
897 *slot = free_col;
898 free_col += 1;
899 }
900 let n_free = free_col; let mut a_triplets: Vec<(usize, usize, f64)> = Vec::with_capacity(12 * nf);
908 let mut b_full = vec![0.0_f64; 2 * nf];
910
911 for (fi, &[vi, vj, vk]) in mesh.faces.iter().enumerate() {
912 let pi = mesh.verts[vi];
913 let pj = mesh.verts[vj];
914 let pk = mesh.verts[vk];
915
916 let d1 = vec3_sub(pj, pi); let d2 = vec3_sub(pk, pi); let x1 = vec3_len(d1); if x1 < 1e-14 {
922 continue; }
924 let e1 = vec3_scale(d1, 1.0 / x1); let n_hat = vec3_normalize(vec3_cross(d1, d2));
926 let e2 = vec3_cross(n_hat, e1);
927
928 let x2 = vec3_dot(d2, e1);
929 let y2 = vec3_dot(d2, e2);
930 if y2.abs() < 1e-14 {
931 continue; }
933
934 let inv_y2 = 1.0 / y2;
941 let row_re = 2 * fi;
942 let row_im = 2 * fi + 1;
943
944 let local_dofs = [2 * vi, 2 * vi + 1, 2 * vj, 2 * vj + 1, 2 * vk, 2 * vk + 1];
947 let re_coeffs = [
948 (x2 - x1) * inv_y2,
949 -1.0,
950 -x2 * inv_y2,
951 1.0,
952 x1 * inv_y2,
953 0.0,
954 ];
955 let im_coeffs = [
956 1.0,
957 (x2 - x1) * inv_y2,
958 -1.0,
959 -x2 * inv_y2,
960 0.0,
961 x1 * inv_y2,
962 ];
963
964 for k in 0..6 {
965 let gc = local_dofs[k];
966 if let Some(pin_pos) = pin_col.iter().position(|&p| p == gc) {
967 b_full[row_re] -= re_coeffs[k] * pin_val[pin_pos];
969 b_full[row_im] -= im_coeffs[k] * pin_val[pin_pos];
970 } else {
971 let fc = col_map[gc];
972 a_triplets.push((row_re, fc, re_coeffs[k]));
973 a_triplets.push((row_im, fc, im_coeffs[k]));
974 }
975 }
976 }
977
978 if n_free == 0 {
979 return tutte_parameterize(mesh, 200);
980 }
981
982 let mut ata_map: BTreeMap<(usize, usize), f64> = BTreeMap::new();
986 let mut atb = vec![0.0_f64; n_free];
987
988 for &(row, fc, val) in &a_triplets {
989 atb[fc] += val * b_full[row];
990 }
991
992 let mut row_entries: Vec<Vec<(usize, f64)>> = vec![Vec::new(); 2 * nf];
996 for &(row, fc, val) in &a_triplets {
997 row_entries[row].push((fc, val));
998 }
999 for entries in &row_entries {
1000 for &(fc_a, val_a) in entries {
1001 for &(fc_b, val_b) in entries {
1002 *ata_map.entry((fc_a, fc_b)).or_insert(0.0) += val_a * val_b;
1003 }
1004 }
1005 }
1006
1007 let mut row_offsets = vec![0usize; n_free + 1];
1009 for &(r, _) in ata_map.keys() {
1010 row_offsets[r + 1] += 1;
1011 }
1012 for i in 0..n_free {
1013 row_offsets[i + 1] += row_offsets[i];
1014 }
1015 let nnz = ata_map.len();
1016 let mut col_indices_csr = vec![0usize; nnz];
1017 let mut values_csr = vec![0.0_f64; nnz];
1018 let mut row_fill = vec![0usize; n_free];
1019 for (&(r, c), &v) in &ata_map {
1020 let pos = row_offsets[r] + row_fill[r];
1021 col_indices_csr[pos] = c;
1022 values_csr[pos] = v;
1023 row_fill[r] += 1;
1024 }
1025
1026 let ata = CsrMatrix {
1027 nrows: n_free,
1028 ncols: n_free,
1029 row_offsets,
1030 col_indices: col_indices_csr,
1031 values: values_csr,
1032 };
1033
1034 let solver = ParallelPcgSolver::new(2000, 1e-8);
1036 let mut x_free = vec![0.0_f64; n_free];
1037 let _stats = solver.solve(&ata, &atb, &mut x_free);
1038
1039 let mut uv = vec![[0.0_f64; 2]; nv];
1041 uv[0] = [0.0, 0.0];
1043 uv[1] = [1.0, 0.0];
1044 for (v, uv_v) in uv.iter_mut().enumerate() {
1046 let cu = 2 * v;
1047 let cv = 2 * v + 1;
1048 let u_val = if pin_col.contains(&cu) {
1049 let pos = pin_col.iter().position(|&p| p == cu).unwrap_or(0);
1050 pin_val[pos]
1051 } else {
1052 x_free[col_map[cu]]
1053 };
1054 let v_val = if pin_col.contains(&cv) {
1055 let pos = pin_col.iter().position(|&p| p == cv).unwrap_or(1);
1056 pin_val[pos]
1057 } else {
1058 x_free[col_map[cv]]
1059 };
1060 *uv_v = [u_val.clamp(0.0, 1.0), v_val.clamp(0.0, 1.0)];
1061 }
1062
1063 let mut out = mesh.clone();
1064 out.uvs = Some(uv.clone());
1065 UvParameterization { mesh: out, uvs: uv }
1066}
1067pub fn generate_texture_atlas(mesh: &ProcessMesh, atlas_size: usize) -> Vec<AtlasPatch> {
1070 let _ = atlas_size;
1071 let param = tutte_parameterize(mesh, 100);
1072 let uvs = param.uvs.clone();
1073 let bounds = uvs.iter().fold(
1074 [
1075 f64::INFINITY,
1076 f64::INFINITY,
1077 f64::NEG_INFINITY,
1078 f64::NEG_INFINITY,
1079 ],
1080 |mut b, &[u, v]| {
1081 if u < b[0] {
1082 b[0] = u;
1083 }
1084 if v < b[1] {
1085 b[1] = v;
1086 }
1087 if u > b[2] {
1088 b[2] = u;
1089 }
1090 if v > b[3] {
1091 b[3] = v;
1092 }
1093 b
1094 },
1095 );
1096 vec![AtlasPatch {
1097 group_id: 0,
1098 bounds,
1099 uvs,
1100 }]
1101}
1102pub fn point_in_mesh(p: [f64; 3], mesh: &ProcessMesh) -> bool {
1104 let dir = vec3_normalize([1.0, 1e-4, 1e-4]);
1105 let mut crossings = 0usize;
1106 for &[a, b, c] in &mesh.faces {
1107 if let Some(_t) =
1108 ray_triangle_intersect(p, dir, mesh.verts[a], mesh.verts[b], mesh.verts[c])
1109 {
1110 crossings += 1;
1111 }
1112 }
1113 crossings % 2 == 1
1114}
1115pub(super) fn ray_triangle_intersect(
1117 orig: [f64; 3],
1118 dir: [f64; 3],
1119 v0: [f64; 3],
1120 v1: [f64; 3],
1121 v2: [f64; 3],
1122) -> Option<f64> {
1123 let e1 = vec3_sub(v1, v0);
1124 let e2 = vec3_sub(v2, v0);
1125 let h = vec3_cross(dir, e2);
1126 let a = vec3_dot(e1, h);
1127 if a.abs() < 1e-15 {
1128 return None;
1129 }
1130 let f = 1.0 / a;
1131 let s = vec3_sub(orig, v0);
1132 let u = f * vec3_dot(s, h);
1133 if !(0.0..=1.0).contains(&u) {
1134 return None;
1135 }
1136 let q = vec3_cross(s, e1);
1137 let v = f * vec3_dot(dir, q);
1138 if v < 0.0 || u + v > 1.0 {
1139 return None;
1140 }
1141 let t = f * vec3_dot(e2, q);
1142 if t > 1e-15 { Some(t) } else { None }
1143}
1144pub fn mesh_union(a: &ProcessMesh, b: &ProcessMesh) -> BooleanResult {
1149 let offset_b = a.verts.len();
1150 let mut verts = a.verts.clone();
1151 verts.extend_from_slice(&b.verts);
1152 let faces_a: Vec<Face> = a
1153 .faces
1154 .iter()
1155 .filter(|&&[i, j, k]| {
1156 let centroid = vec3_scale(
1157 vec3_add(vec3_add(a.verts[i], a.verts[j]), a.verts[k]),
1158 1.0 / 3.0,
1159 );
1160 !point_in_mesh(centroid, b)
1161 })
1162 .cloned()
1163 .collect();
1164 let faces_b: Vec<Face> = b
1165 .faces
1166 .iter()
1167 .filter(|&&[i, j, k]| {
1168 let centroid = vec3_scale(
1169 vec3_add(vec3_add(b.verts[i], b.verts[j]), b.verts[k]),
1170 1.0 / 3.0,
1171 );
1172 !point_in_mesh(centroid, a)
1173 })
1174 .map(|&[i, j, k]| [i + offset_b, j + offset_b, k + offset_b])
1175 .collect();
1176 let mut faces = faces_a;
1177 faces.extend(faces_b);
1178 let mut result = BooleanResult {
1179 mesh: ProcessMesh::new(verts, faces),
1180 is_exact: false,
1181 };
1182 result.is_exact = result.is_topologically_exact();
1183 result
1184}