#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct AutoSmoothConfig {
pub angle_threshold: f32,
pub boundary_sharp: bool,
}
impl Default for AutoSmoothConfig {
fn default() -> Self {
Self {
angle_threshold: std::f32::consts::PI / 4.0,
boundary_sharp: true,
}
}
}
#[derive(Debug, Clone)]
pub struct AutoSmoothResult {
pub sharp_pairs: Vec<(usize, usize)>,
pub smooth_normals: Vec<[f32; 3]>,
pub sharp_count: usize,
}
pub fn dihedral_angle(n_a: [f32; 3], n_b: [f32; 3]) -> f32 {
let dot = (n_a[0] * n_b[0] + n_a[1] * n_b[1] + n_a[2] * n_b[2]).clamp(-1.0, 1.0);
dot.acos()
}
fn normalize3(v: [f32; 3]) -> [f32; 3] {
let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
if len < 1e-12 {
[0.0, 0.0, 1.0]
} else {
[v[0] / len, v[1] / len, v[2] / len]
}
}
fn face_normal(positions: &[[f32; 3]], tri: [usize; 3]) -> [f32; 3] {
let a = positions[tri[0]];
let b = positions[tri[1]];
let c = positions[tri[2]];
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
normalize3([
ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0],
])
}
pub fn auto_smooth(
positions: &[[f32; 3]],
triangles: &[[usize; 3]],
config: &AutoSmoothConfig,
) -> AutoSmoothResult {
let n_verts = positions.len();
let n_tris = triangles.len();
let mut face_normals: Vec<[f32; 3]> = Vec::with_capacity(n_tris);
for tri in triangles {
face_normals.push(face_normal(positions, *tri));
}
let mut accum: Vec<[f32; 3]> = vec![[0.0, 0.0, 0.0]; n_verts];
let mut sharp_pairs: Vec<(usize, usize)> = Vec::new();
for (fi, tri) in triangles.iter().enumerate() {
let fn_i = face_normals[fi];
for &vi in tri.iter() {
accum[vi][0] += fn_i[0];
accum[vi][1] += fn_i[1];
accum[vi][2] += fn_i[2];
}
}
for a in 0..n_tris {
for b in (a + 1)..n_tris {
let angle = dihedral_angle(face_normals[a], face_normals[b]);
if angle > config.angle_threshold {
sharp_pairs.push((a, b));
}
}
}
let sharp_count = sharp_pairs.len();
let smooth_normals: Vec<[f32; 3]> = accum.iter().map(|&v| normalize3(v)).collect();
AutoSmoothResult {
sharp_pairs,
smooth_normals,
sharp_count,
}
}
pub fn count_sharp_vertices(result: &AutoSmoothResult) -> usize {
result.sharp_count
}
pub fn config_from_degrees(angle_deg: f32) -> AutoSmoothConfig {
AutoSmoothConfig {
angle_threshold: angle_deg.to_radians(),
boundary_sharp: true,
}
}
pub fn is_smooth_angle(n_a: [f32; 3], n_b: [f32; 3], threshold: f32) -> bool {
dihedral_angle(n_a, n_b) <= threshold
}
#[cfg(test)]
mod tests {
use super::*;
fn cube_verts() -> Vec<[f32; 3]> {
vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
]
}
#[test]
fn test_default_config() {
let cfg = AutoSmoothConfig::default();
assert!((cfg.angle_threshold - std::f32::consts::PI / 4.0).abs() < 1e-6);
}
#[test]
fn test_config_from_degrees() {
let cfg = config_from_degrees(90.0);
assert!((cfg.angle_threshold - std::f32::consts::FRAC_PI_2).abs() < 1e-5);
}
#[test]
fn test_dihedral_same_normal() {
let n = [0.0f32, 0.0, 1.0];
assert!(dihedral_angle(n, n) < 1e-5);
}
#[test]
fn test_dihedral_perpendicular() {
let a = [1.0f32, 0.0, 0.0];
let b = [0.0f32, 1.0, 0.0];
let angle = dihedral_angle(a, b);
assert!((angle - std::f32::consts::FRAC_PI_2).abs() < 1e-5);
}
#[test]
fn test_is_smooth_angle_true() {
let n = [0.0f32, 0.0, 1.0];
assert!(is_smooth_angle(n, n, 0.1));
}
#[test]
fn test_is_smooth_angle_false() {
let a = [1.0f32, 0.0, 0.0];
let b = [0.0f32, 1.0, 0.0];
assert!(!is_smooth_angle(a, b, 0.1));
}
#[test]
fn test_auto_smooth_single_tri() {
let verts = cube_verts();
let tris = vec![[0usize, 1, 2]];
let cfg = AutoSmoothConfig::default();
let result = auto_smooth(&verts, &tris, &cfg);
assert_eq!(result.sharp_pairs.len(), 0);
assert_eq!(result.smooth_normals.len(), verts.len());
}
#[test]
fn test_auto_smooth_two_tris_sharp() {
let verts = vec![
[0.0f32, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, -1.0],
];
let tris = vec![[0usize, 1, 2], [0, 1, 3]];
let cfg = AutoSmoothConfig {
angle_threshold: 0.1,
boundary_sharp: true,
};
let result = auto_smooth(&verts, &tris, &cfg);
assert!(result.sharp_count > 0);
}
#[test]
fn test_count_sharp_vertices() {
let result = AutoSmoothResult {
sharp_pairs: vec![(0, 1), (1, 2)],
smooth_normals: vec![],
sharp_count: 2,
};
assert_eq!(count_sharp_vertices(&result), 2);
}
}