use std::collections::{BTreeMap, HashMap};
use crate::Vec3;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct StitchReport {
pub stitched_edges: usize,
pub added_triangles: usize,
pub passes: usize,
}
fn point(positions: &[f64], index: u32) -> Vec3 {
let at = index as usize * 3;
Vec3::new(positions[at], positions[at + 1], positions[at + 2])
}
pub fn stitch_t_junctions(positions: &[f64], indices: &[u32], tolerance: f64) -> (Vec<u32>, StitchReport) {
let mut triangles = indices.to_vec();
let mut report = StitchReport::default();
if positions.len() % 3 != 0 || triangles.len() % 3 != 0 || !(tolerance >= 0.0) || !tolerance.is_finite() {
return (triangles, report);
}
let vertex_count = (positions.len() / 3) as u32;
if triangles.iter().any(|&i| i >= vertex_count) {
return (triangles, report);
}
for _ in 0..8 {
let mut uses = HashMap::<(u32, u32), u32>::new();
for triangle in triangles.chunks_exact(3) {
for corner in 0..3 {
let (a, b) = (triangle[corner], triangle[(corner + 1) % 3]);
*uses.entry((a.min(b), a.max(b))).or_default() += 1;
}
}
let uses = &uses;
let boundary_edges: Vec<(usize, usize)> = triangles
.chunks_exact(3)
.enumerate()
.flat_map(|(t, triangle)| {
(0..3).filter_map(move |corner| {
let (a, b) = (triangle[corner], triangle[(corner + 1) % 3]);
(uses[&(a.min(b), a.max(b))] == 1).then_some((t, corner))
})
})
.collect();
if boundary_edges.is_empty() {
break;
}
let mut boundary_vertices: Vec<u32> = boundary_edges
.iter()
.flat_map(|&(t, corner)| [triangles[3 * t + corner], triangles[3 * t + (corner + 1) % 3]])
.collect();
boundary_vertices.sort_unstable();
boundary_vertices.dedup();
let mut splits = BTreeMap::<(usize, usize), Vec<(f64, u32)>>::new();
for &(t, corner) in &boundary_edges {
let (a, b) = (triangles[3 * t + corner], triangles[3 * t + (corner + 1) % 3]);
let (pa, pb) = (point(positions, a), point(positions, b));
let ab = pb - pa;
let length_squared = ab.length_squared();
if length_squared <= 0.0 {
continue;
}
for &v in &boundary_vertices {
if v == a || v == b {
continue;
}
let pv = point(positions, v);
let s = (pv - pa).dot(ab) / length_squared;
if !(s > 0.0 && s < 1.0) {
continue;
}
let foot = pa + ab * s;
let along = ab.length();
if (pv - foot).length() <= tolerance
&& s * along > tolerance
&& (1.0 - s) * along > tolerance
{
splits.entry((t, corner)).or_default().push((s, v));
}
}
}
if splits.is_empty() {
break;
}
report.passes += 1;
let mut seen = std::collections::HashSet::new();
for ((t, corner), mut chain) in splits {
if !seen.insert(t) {
continue;
}
chain.sort_by(|x, y| x.0.total_cmp(&y.0));
chain.dedup_by_key(|x| x.1);
let (a, b, c) = (
triangles[3 * t + corner],
triangles[3 * t + (corner + 1) % 3],
triangles[3 * t + (corner + 2) % 3],
);
let mut previous = a;
let mut first = true;
for &(_, v) in &chain {
if first {
triangles[3 * t] = previous;
triangles[3 * t + 1] = v;
triangles[3 * t + 2] = c;
first = false;
} else {
triangles.extend_from_slice(&[previous, v, c]);
report.added_triangles += 1;
}
previous = v;
}
triangles.extend_from_slice(&[previous, b, c]);
report.added_triangles += 1;
report.stitched_edges += 1;
}
}
(triangles, report)
}