#![allow(dead_code)]
use std::collections::HashMap;
#[allow(dead_code)]
pub struct AdaptiveSubdivV2Config {
pub curvature_threshold: f32,
pub max_levels: u32,
}
#[allow(dead_code)]
impl Default for AdaptiveSubdivV2Config {
fn default() -> Self {
Self { curvature_threshold: 0.1, max_levels: 4 }
}
}
#[allow(dead_code)]
pub struct AdaptiveSubdivV2Result {
pub positions: Vec<[f32; 3]>,
pub indices: Vec<u32>,
pub faces_subdivided: usize,
}
#[inline]
fn add3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
#[inline]
fn scale3(v: [f32; 3], s: f32) -> [f32; 3] {
[v[0] * s, v[1] * s, v[2] * s]
}
#[inline]
fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
#[inline]
fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[inline]
fn len3(v: [f32; 3]) -> f32 {
dot3(v, v).sqrt()
}
#[allow(dead_code)]
pub fn dihedral_angle_v2(n0: [f32; 3], n1: [f32; 3]) -> f32 {
let cos_a = dot3(n0, n1).clamp(-1.0, 1.0);
cos_a.acos()
}
#[allow(dead_code)]
pub fn face_normal_v2(p0: [f32; 3], p1: [f32; 3], p2: [f32; 3]) -> [f32; 3] {
let e1 = sub3(p1, p0);
let e2 = sub3(p2, p0);
let n = [
e1[1] * e2[2] - e1[2] * e2[1],
e1[2] * e2[0] - e1[0] * e2[2],
e1[0] * e2[1] - e1[1] * e2[0],
];
let l = len3(n);
if l < 1e-10 { n } else { scale3(n, 1.0 / l) }
}
#[allow(dead_code)]
pub fn face_curvature_proxy(p0: [f32; 3], p1: [f32; 3], p2: [f32; 3]) -> f32 {
let l0 = len3(sub3(p1, p0));
let l1 = len3(sub3(p2, p1));
let l2 = len3(sub3(p0, p2));
l0.max(l1).max(l2)
}
#[allow(dead_code)]
pub fn needs_subdivision(p0: [f32; 3], p1: [f32; 3], p2: [f32; 3], threshold: f32) -> bool {
face_curvature_proxy(p0, p1, p2) > threshold
}
#[allow(dead_code)]
pub fn adaptive_subdivide_v2(
positions: &[[f32; 3]],
indices: &[u32],
config: &AdaptiveSubdivV2Config,
) -> AdaptiveSubdivV2Result {
let mut new_pos = positions.to_vec();
let mut new_idx: Vec<u32> = Vec::new();
let mut edge_map: HashMap<(u32, u32), u32> = HashMap::new();
let mut subdivided = 0usize;
let tri_count = indices.len() / 3;
for t in 0..tri_count {
let a = indices[3 * t];
let b = indices[3 * t + 1];
let c = indices[3 * t + 2];
let pa = positions[a as usize];
let pb = positions[b as usize];
let pc = positions[c as usize];
if needs_subdivision(pa, pb, pc, config.curvature_threshold) {
let get_mid = |p: u32, q: u32, pos: &mut Vec<[f32; 3]>, map: &mut HashMap<(u32, u32), u32>| {
let key = if p < q { (p, q) } else { (q, p) };
*map.entry(key).or_insert_with(|| {
let mp = scale3(add3(pos[p as usize], pos[q as usize]), 0.5);
let idx = pos.len() as u32;
pos.push(mp);
idx
})
};
let ab = get_mid(a, b, &mut new_pos, &mut edge_map);
let bc = get_mid(b, c, &mut new_pos, &mut edge_map);
let ca = get_mid(c, a, &mut new_pos, &mut edge_map);
new_idx.extend_from_slice(&[a, ab, ca]);
new_idx.extend_from_slice(&[ab, b, bc]);
new_idx.extend_from_slice(&[ca, bc, c]);
new_idx.extend_from_slice(&[ab, bc, ca]);
subdivided += 1;
} else {
new_idx.extend_from_slice(&[a, b, c]);
}
}
AdaptiveSubdivV2Result { positions: new_pos, indices: new_idx, faces_subdivided: subdivided }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_subdivision_below_threshold() {
let pos = vec![[0.0,0.0,0.0],[0.01,0.0,0.0],[0.0,0.01,0.0]];
let idx = vec![0,1,2];
let cfg = AdaptiveSubdivV2Config { curvature_threshold: 1.0, max_levels: 2 };
let r = adaptive_subdivide_v2(&pos, &idx, &cfg);
assert_eq!(r.faces_subdivided, 0);
assert_eq!(r.indices.len(), 3);
}
#[test]
fn subdivision_above_threshold() {
let pos = vec![[0.0,0.0,0.0],[2.0,0.0,0.0],[0.0,2.0,0.0]];
let idx = vec![0,1,2];
let cfg = AdaptiveSubdivV2Config { curvature_threshold: 1.0, max_levels: 2 };
let r = adaptive_subdivide_v2(&pos, &idx, &cfg);
assert_eq!(r.faces_subdivided, 1);
assert_eq!(r.indices.len(), 12);
}
#[test]
fn face_normal_unit_length() {
let n = face_normal_v2([0.0,0.0,0.0],[1.0,0.0,0.0],[0.0,1.0,0.0]);
let l = n[0]*n[0]+n[1]*n[1]+n[2]*n[2];
assert!((l - 1.0).abs() < 1e-5);
}
#[test]
fn curvature_proxy_positive() {
let c = face_curvature_proxy([0.0,0.0,0.0],[1.0,0.0,0.0],[0.5,1.0,0.0]);
assert!(c > 0.0);
}
#[test]
fn needs_subdivision_true() {
assert!(needs_subdivision([0.0,0.0,0.0],[5.0,0.0,0.0],[0.0,5.0,0.0], 1.0));
}
#[test]
fn needs_subdivision_false() {
assert!(!needs_subdivision([0.0,0.0,0.0],[0.1,0.0,0.0],[0.0,0.1,0.0], 1.0));
}
#[test]
fn dihedral_same_normals_zero() {
let n = [0.0, 0.0, 1.0];
let a = dihedral_angle_v2(n, n);
assert!(a.abs() < 1e-5);
}
#[test]
fn dihedral_opposite_normals_pi() {
use std::f32::consts::PI;
let n0 = [0.0, 0.0, 1.0];
let n1 = [0.0, 0.0, -1.0];
let a = dihedral_angle_v2(n0, n1);
assert!((a - PI).abs() < 1e-5);
}
#[test]
fn default_config() {
let c = AdaptiveSubdivV2Config::default();
assert_eq!(c.max_levels, 4);
}
}