#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct NormalRecompute {
pub smoothing_angle: f32,
pub vertex_count: usize,
}
#[allow(dead_code)]
pub fn recompute_flat_normals(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> Vec<[f32; 3]> {
let mut normals = vec![[0.0f32; 3]; positions.len()];
for tri in indices {
let a = positions[tri[0] as usize];
let b = positions[tri[1] as usize];
let c = positions[tri[2] as usize];
let n = face_normal(a, b, c);
for &vi in tri {
normals[vi as usize] = n;
}
}
normals
}
#[allow(dead_code)]
pub fn recompute_smooth_normals(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> Vec<[f32; 3]> {
let mut normals = vec![[0.0f32; 3]; positions.len()];
for tri in indices {
let a = positions[tri[0] as usize];
let b = positions[tri[1] as usize];
let c = positions[tri[2] as usize];
let n = face_normal_unnormalized(a, b, c);
for &vi in tri {
normals[vi as usize][0] += n[0];
normals[vi as usize][1] += n[1];
normals[vi as usize][2] += n[2];
}
}
for n in &mut normals {
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if len > 1e-12 {
n[0] /= len;
n[1] /= len;
n[2] /= len;
}
}
normals
}
#[allow(dead_code)]
pub fn recompute_weighted_normals(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> Vec<[f32; 3]> {
let mut normals = vec![[0.0f32; 3]; positions.len()];
for tri in indices {
let p = [
positions[tri[0] as usize],
positions[tri[1] as usize],
positions[tri[2] as usize],
];
let fn_val = face_normal_unnormalized(p[0], p[1], p[2]);
for i in 0..3 {
let e1 = [
p[(i + 1) % 3][0] - p[i][0],
p[(i + 1) % 3][1] - p[i][1],
p[(i + 1) % 3][2] - p[i][2],
];
let e2 = [
p[(i + 2) % 3][0] - p[i][0],
p[(i + 2) % 3][1] - p[i][1],
p[(i + 2) % 3][2] - p[i][2],
];
let l1 = (e1[0] * e1[0] + e1[1] * e1[1] + e1[2] * e1[2]).sqrt();
let l2 = (e2[0] * e2[0] + e2[1] * e2[1] + e2[2] * e2[2]).sqrt();
let angle = if l1 > 1e-12 && l2 > 1e-12 {
let dot = (e1[0] * e2[0] + e1[1] * e2[1] + e1[2] * e2[2]) / (l1 * l2);
dot.clamp(-1.0, 1.0).acos()
} else {
0.0
};
let vi = tri[i] as usize;
normals[vi][0] += fn_val[0] * angle;
normals[vi][1] += fn_val[1] * angle;
normals[vi][2] += fn_val[2] * angle;
}
}
for n in &mut normals {
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if len > 1e-12 {
n[0] /= len;
n[1] /= len;
n[2] /= len;
}
}
normals
}
#[allow(dead_code)]
pub fn normal_smoothing_angle() -> f32 {
PI / 3.0
}
#[allow(dead_code)]
pub fn smooth_normals_by_group(
positions: &[[f32; 3]],
indices: &[[u32; 3]],
angle_threshold: f32,
) -> Vec<[f32; 3]> {
let face_normals: Vec<[f32; 3]> = indices
.iter()
.map(|tri| face_normal(positions[tri[0] as usize], positions[tri[1] as usize], positions[tri[2] as usize]))
.collect();
let mut normals = vec![[0.0f32; 3]; positions.len()];
let cos_threshold = angle_threshold.cos();
for (fi, tri) in indices.iter().enumerate() {
for &vi in tri {
let mut sum = [0.0f32; 3];
for (fj, tri2) in indices.iter().enumerate() {
if tri2.contains(&vi) {
let dot = face_normals[fi][0] * face_normals[fj][0]
+ face_normals[fi][1] * face_normals[fj][1]
+ face_normals[fi][2] * face_normals[fj][2];
if dot >= cos_threshold {
sum[0] += face_normals[fj][0];
sum[1] += face_normals[fj][1];
sum[2] += face_normals[fj][2];
}
}
}
let len = (sum[0] * sum[0] + sum[1] * sum[1] + sum[2] * sum[2]).sqrt();
if len > 1e-12 {
normals[vi as usize] = [sum[0] / len, sum[1] / len, sum[2] / len];
}
}
}
normals
}
#[allow(dead_code)]
pub fn normal_recompute_count(positions: &[[f32; 3]]) -> usize {
positions.len()
}
#[allow(dead_code)]
pub fn auto_smooth_normals(positions: &[[f32; 3]], indices: &[[u32; 3]]) -> Vec<[f32; 3]> {
smooth_normals_by_group(positions, indices, normal_smoothing_angle())
}
#[allow(dead_code)]
pub fn normal_flip_check(positions: &[[f32; 3]], normals: &[[f32; 3]], indices: &[[u32; 3]]) -> Vec<usize> {
let mut flipped = Vec::new();
for (fi, tri) in indices.iter().enumerate() {
let fn_val = face_normal(
positions[tri[0] as usize],
positions[tri[1] as usize],
positions[tri[2] as usize],
);
for &vi in tri {
let vn = normals[vi as usize];
let dot = fn_val[0] * vn[0] + fn_val[1] * vn[1] + fn_val[2] * vn[2];
if dot < 0.0 {
flipped.push(fi);
break;
}
}
}
flipped
}
fn face_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] {
let n = face_normal_unnormalized(a, b, c);
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
if len < 1e-12 {
[0.0, 0.0, 0.0]
} else {
[n[0] / len, n[1] / len, n[2] / len]
}
}
fn face_normal_unnormalized(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] {
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]];
[
ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0],
]
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_mesh() -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
let p = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]];
let i = vec![[0, 1, 2], [1, 3, 2]];
(p, i)
}
#[test]
fn test_recompute_flat_normals() {
let (p, i) = simple_mesh();
let normals = recompute_flat_normals(&p, &i);
assert_eq!(normals.len(), p.len());
assert!((normals[0][2] - 1.0).abs() < 1e-4);
}
#[test]
fn test_recompute_smooth_normals() {
let (p, i) = simple_mesh();
let normals = recompute_smooth_normals(&p, &i);
for n in &normals {
let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
assert!((len - 1.0).abs() < 1e-4);
}
}
#[test]
fn test_recompute_weighted_normals() {
let (p, i) = simple_mesh();
let normals = recompute_weighted_normals(&p, &i);
assert_eq!(normals.len(), p.len());
}
#[test]
fn test_normal_smoothing_angle() {
let angle = normal_smoothing_angle();
assert!((angle - PI / 3.0).abs() < 1e-6);
}
#[test]
fn test_smooth_normals_by_group() {
let (p, i) = simple_mesh();
let normals = smooth_normals_by_group(&p, &i, PI / 4.0);
assert_eq!(normals.len(), p.len());
}
#[test]
fn test_normal_recompute_count() {
let (p, _) = simple_mesh();
assert_eq!(normal_recompute_count(&p), 4);
}
#[test]
fn test_auto_smooth_normals() {
let (p, i) = simple_mesh();
let normals = auto_smooth_normals(&p, &i);
assert_eq!(normals.len(), p.len());
}
#[test]
fn test_normal_flip_check() {
let (p, i) = simple_mesh();
let normals = recompute_smooth_normals(&p, &i);
let flipped = normal_flip_check(&p, &normals, &i);
assert!(flipped.is_empty());
}
#[test]
fn test_normal_flip_check_flipped() {
let (p, i) = simple_mesh();
let normals = vec![[0.0, 0.0, -1.0]; p.len()];
let flipped = normal_flip_check(&p, &normals, &i);
assert!(!flipped.is_empty());
}
#[test]
fn test_empty_mesh_normals() {
let normals = recompute_smooth_normals(&[], &[]);
assert!(normals.is_empty());
}
}