use std::collections::HashMap;
use crate::data::{CellArray, Points, PolyData};
pub fn subdivide(input: &PolyData, iterations: usize) -> PolyData {
let mut current = input.clone();
for _ in 0..iterations {
current = subdivide_once(¤t);
}
current
}
fn subdivide_once(input: &PolyData) -> PolyData {
let n_pts = input.points.len();
let mut new_points = Points::<f64>::new();
for i in 0..n_pts {
new_points.push(input.points.get(i));
}
let mut edge_midpoints: HashMap<(usize, usize), usize> = HashMap::new();
let mut edge_triangles: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
let mut triangles: Vec<[usize; 3]> = Vec::new();
for cell in input.polys.iter() {
if cell.len() != 3 {
continue;
}
let tri_idx = triangles.len();
let t = [cell[0] as usize, cell[1] as usize, cell[2] as usize];
triangles.push(t);
for j in 0..3 {
let a = t[j];
let b = t[(j + 1) % 3];
let edge = if a < b { (a, b) } else { (b, a) };
edge_triangles.entry(edge).or_default().push(tri_idx);
}
}
for (&(a, b), tris) in &edge_triangles {
if edge_midpoints.contains_key(&(a, b)) {
continue;
}
let pa = input.points.get(a);
let pb = input.points.get(b);
let mid = if tris.len() == 2 {
let c = opposite_vertex(&triangles[tris[0]], a, b);
let d = opposite_vertex(&triangles[tris[1]], a, b);
let pc = input.points.get(c);
let pd = input.points.get(d);
[
0.375 * (pa[0] + pb[0]) + 0.125 * (pc[0] + pd[0]),
0.375 * (pa[1] + pb[1]) + 0.125 * (pc[1] + pd[1]),
0.375 * (pa[2] + pb[2]) + 0.125 * (pc[2] + pd[2]),
]
} else {
[
0.5 * (pa[0] + pb[0]),
0.5 * (pa[1] + pb[1]),
0.5 * (pa[2] + pb[2]),
]
};
let idx = new_points.len();
new_points.push(mid);
edge_midpoints.insert((a, b), idx);
}
let mut neighbors: Vec<Vec<usize>> = vec![Vec::new(); n_pts];
for &(a, b) in edge_triangles.keys() {
if !neighbors[a].contains(&b) {
neighbors[a].push(b);
}
if !neighbors[b].contains(&a) {
neighbors[b].push(a);
}
}
let mut is_boundary = vec![false; n_pts];
for (&(_a, _b), tris) in &edge_triangles {
if tris.len() == 1 {
is_boundary[_a] = true;
is_boundary[_b] = true;
}
}
for i in 0..n_pts {
let p = input.points.get(i);
let n = neighbors[i].len();
if n == 0 {
continue;
}
if is_boundary[i] {
continue;
}
let beta = if n == 3 {
3.0 / 16.0
} else {
3.0 / (8.0 * n as f64)
};
let mut avg = [0.0f64; 3];
for &nb in &neighbors[i] {
let q = input.points.get(nb);
avg[0] += q[0];
avg[1] += q[1];
avg[2] += q[2];
}
let new_pos = [
(1.0 - n as f64 * beta) * p[0] + beta * avg[0],
(1.0 - n as f64 * beta) * p[1] + beta * avg[1],
(1.0 - n as f64 * beta) * p[2] + beta * avg[2],
];
new_points.set(i, new_pos);
}
let mut polys = CellArray::new();
for tri in &triangles {
let [v0, v1, v2] = *tri;
let m01 = *edge_midpoints.get(&edge_key(v0, v1)).unwrap();
let m12 = *edge_midpoints.get(&edge_key(v1, v2)).unwrap();
let m20 = *edge_midpoints.get(&edge_key(v2, v0)).unwrap();
polys.push_cell(&[v0 as i64, m01 as i64, m20 as i64]);
polys.push_cell(&[v1 as i64, m12 as i64, m01 as i64]);
polys.push_cell(&[v2 as i64, m20 as i64, m12 as i64]);
polys.push_cell(&[m01 as i64, m12 as i64, m20 as i64]);
}
let mut output = PolyData::new();
output.points = new_points;
output.polys = polys;
output
}
fn edge_key(a: usize, b: usize) -> (usize, usize) {
if a < b { (a, b) } else { (b, a) }
}
fn opposite_vertex(tri: &[usize; 3], a: usize, b: usize) -> usize {
for &v in tri {
if v != a && v != b {
return v;
}
}
tri[0] }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subdivide_single_triangle() {
let pd = PolyData::from_triangles(
vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
vec![[0, 1, 2]],
);
let result = subdivide(&pd, 1);
assert_eq!(result.polys.num_cells(), 4);
assert_eq!(result.points.len(), 6);
}
#[test]
fn subdivide_two_iterations() {
let pd = PolyData::from_triangles(
vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
vec![[0, 1, 2]],
);
let result = subdivide(&pd, 2);
assert_eq!(result.polys.num_cells(), 16);
}
#[test]
fn subdivide_preserves_manifold() {
let pd = PolyData::from_triangles(
vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.5, 1.0, 0.0],
[0.5, -1.0, 0.0],
],
vec![[0, 1, 2], [0, 3, 1]],
);
let result = subdivide(&pd, 1);
assert_eq!(result.polys.num_cells(), 8);
}
}