use crate::{Error, MeshBuffer, Real, Result, Sdf, vec3};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum NormalStrategy<R: Real> {
AnalyticGradient,
CentralDifference {
step: R,
},
AreaWeightedFaces,
}
pub fn recompute<R, S>(mesh: &mut MeshBuffer<R>, sdf: &S, strategy: NormalStrategy<R>) -> Result<()>
where
R: Real,
S: Sdf<Scalar = R>,
{
match strategy {
NormalStrategy::AnalyticGradient => from_field(mesh, |p| sdf.gradient(p)),
NormalStrategy::CentralDifference { step } => {
if !step.is_finite() || step <= R::ZERO {
return Err(Error::InvalidCellSize {
value: f64::from(step.as_f32()),
});
}
from_field(mesh, |p| central_difference(sdf, p, step))
}
NormalStrategy::AreaWeightedFaces => area_weighted(mesh),
}
}
#[must_use]
pub fn central_difference<R, S>(sdf: &S, p: [R; 3], step: R) -> [R; 3]
where
R: Real,
S: Sdf<Scalar = R>,
{
let inv = (R::TWO * step).recip();
[
(sdf.sample([p[0] + step, p[1], p[2]]) - sdf.sample([p[0] - step, p[1], p[2]])) * inv,
(sdf.sample([p[0], p[1] + step, p[2]]) - sdf.sample([p[0], p[1] - step, p[2]])) * inv,
(sdf.sample([p[0], p[1], p[2] + step]) - sdf.sample([p[0], p[1], p[2] - step])) * inv,
]
}
fn from_field<R, F>(mesh: &mut MeshBuffer<R>, mut gradient: F) -> Result<()>
where
R: Real,
F: FnMut([R; 3]) -> [R; 3],
{
for (vertex, position) in mesh.positions.iter().enumerate() {
let g = gradient(*position);
let length = vec3::length(g);
if length > R::ZERO && length.is_finite() {
mesh.normals[vertex] = vec3::scale(g, length.recip());
} else {
return Err(Error::DegenerateNormal {
vertex: vertex as u64,
});
}
}
Ok(())
}
fn area_weighted<R: Real>(mesh: &mut MeshBuffer<R>) -> Result<()> {
mesh.normals.fill([R::ZERO; 3]);
for tri in mesh.indices.as_chunks::<3>().0 {
let (i, j, k) = (tri[0] as usize, tri[1] as usize, tri[2] as usize);
if i >= mesh.positions.len() || j >= mesh.positions.len() || k >= mesh.positions.len() {
continue;
}
let a = mesh.positions[i];
let cross = vec3::cross(
vec3::sub(mesh.positions[j], a),
vec3::sub(mesh.positions[k], a),
);
for vertex in [i, j, k] {
for (axis, slot) in mesh.normals[vertex].iter_mut().enumerate() {
*slot += cross[axis];
}
}
}
for (vertex, normal) in mesh.normals.iter_mut().enumerate() {
let length = vec3::length(*normal);
if length > R::ZERO && length.is_finite() {
*normal = vec3::scale(*normal, length.recip());
} else {
return Err(Error::DegenerateNormal {
vertex: vertex as u64,
});
}
}
Ok(())
}
#[cfg(test)]
mod tests;