use glam::Vec3A;
pub fn calculate_smooth_normals<P>(positions: &[P], indices: &[u32]) -> Vec<Vec3A>
where
P: Into<Vec3A> + Copy,
{
if positions.is_empty() || indices.is_empty() {
return Vec::new();
}
let mut normals = vec![Vec3A::ZERO; positions.len()];
update_smooth_normals(positions, &mut normals, indices);
normals
}
fn update_smooth_normals<P>(positions: &[P], normals: &mut [Vec3A], indices: &[u32])
where
P: Into<Vec3A> + Copy,
{
for face in indices.chunks(3) {
if let [v0, v1, v2] = face {
let normal = calculate_normal(
positions[*v0 as usize].into(),
positions[*v1 as usize].into(),
positions[*v2 as usize].into(),
);
normals[*v0 as usize] += normal;
normals[*v1 as usize] += normal;
normals[*v2 as usize] += normal;
}
}
for normal in normals.iter_mut() {
*normal = normal.normalize_or_zero();
}
}
#[inline(always)]
fn calculate_normal(v1: Vec3A, v2: Vec3A, v3: Vec3A) -> Vec3A {
let u = v2 - v1;
let v = v3 - v1;
u.cross(v)
}
pub mod ffi {
use super::*;
#[no_mangle]
pub unsafe extern "C" fn calculate_smooth_normals(
positions: *const glam::Vec3A,
normals: *mut glam::Vec3A,
pos_nrm_length: u32,
indices: *const u32,
indices_length: u32,
) {
let pos = std::slice::from_raw_parts(positions, pos_nrm_length as usize);
let nrm = std::slice::from_raw_parts_mut(normals, pos_nrm_length as usize);
let indices = std::slice::from_raw_parts(indices, indices_length as usize);
update_smooth_normals(pos, nrm, indices);
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
const EPSILON: f32 = 0.0001;
#[test]
fn positive_normal() {
let v1 = Vec3A::new(-5f32, 5f32, 1f32);
let v2 = Vec3A::new(-5f32, 0f32, 1f32);
let v3 = Vec3A::new(0f32, 0f32, 1f32);
let normal = calculate_normal(v1, v2, v3).normalize();
assert_eq!(0f32, normal.x);
assert_eq!(0f32, normal.y);
assert_eq!(1f32, normal.z);
}
#[test]
fn negative_normal() {
let v1 = Vec3A::new(-5f32, 5f32, 1f32);
let v2 = Vec3A::new(-5f32, 0f32, 1f32);
let v3 = Vec3A::new(0f32, 0f32, 1f32);
let normal = calculate_normal(v3, v2, v1).normalize();
assert_eq!(0f32, normal.x);
assert_eq!(0f32, normal.y);
assert_eq!(-1f32, normal.z);
}
#[test]
fn smooth_normals_no_points_no_indices() {
let normals = calculate_smooth_normals::<Vec3A>(&[], &[]);
assert!(normals.is_empty());
}
#[test]
fn smooth_normals_no_points() {
let normals = calculate_smooth_normals::<Vec3A>(&[], &[0, 1, 2]);
assert!(normals.is_empty());
}
#[test]
fn smooth_normals_no_indices() {
let points = vec![
Vec3A::new(1f32, 0f32, 0f32),
Vec3A::new(0f32, 1f32, 0f32),
Vec3A::new(0f32, 0f32, 1f32),
];
let normals = calculate_smooth_normals::<Vec3A>(&points, &[]);
assert!(normals.is_empty());
}
#[test]
fn smooth_normals_three_points() {
let points = vec![
Vec3A::new(1f32, 0f32, 0f32),
Vec3A::new(0f32, 1f32, 0f32),
Vec3A::new(0f32, 0f32, 1f32),
];
let normals = calculate_smooth_normals::<Vec3A>(&points, &[0, 1, 2]);
assert_relative_eq!(1f32, normals[0].length(), epsilon = EPSILON);
assert_relative_eq!(1f32, normals[1].length(), epsilon = EPSILON);
assert_relative_eq!(1f32, normals[2].length(), epsilon = EPSILON);
}
#[test]
fn smooth_normals_zero_normal() {
let points = vec![Vec3A::X, Vec3A::X, Vec3A::X];
let normals = calculate_smooth_normals::<Vec3A>(&points, &[0, 1, 2]);
for normal in normals {
assert_relative_eq!(0.0, normal.x, epsilon = EPSILON);
assert_relative_eq!(0.0, normal.y, epsilon = EPSILON);
assert_relative_eq!(0.0, normal.z, epsilon = EPSILON);
}
}
#[test]
fn smooth_normals_ffi() {
let pos = [Vec3A::ONE, Vec3A::ONE];
let mut nrm = [Vec3A::ONE, Vec3A::ONE];
let indices = [0, 1, 0, 1, 0, 1, 1, 1, 0];
unsafe {
ffi::calculate_smooth_normals(
pos.as_ptr(),
nrm.as_mut_ptr(),
pos.len() as u32,
indices.as_ptr(),
indices.len() as u32,
);
}
assert_eq!(nrm[0], Vec3A::ONE.normalize());
assert_eq!(nrm[1], Vec3A::ONE.normalize());
}
}