Revert "Support calculating normals for indexed meshes" (#12716) and add support for calculating smooth normals (#13333)

# Objective

- Refactor the changes merged in #11654 to compute flat normals for
indexed meshes instead of smooth normals.
- Fixes #12716 

## Solution

- Partially revert the changes in #11654 to compute flat normals for
both indexed and unindexed meshes in `compute_flat_normals`
- Create a new method, `compute_smooth_normals`, that computes smooth
normals for indexed meshes
- Create a new method, `compute_normals`, that computes smooth normals
for indexed meshes and flat normals for unindexed meshes by default. Use
this new method instead of `compute_flat_normals`.

## Testing

- Run the example with and without the changes to ensure that the
results are identical.
This commit is contained in:
Adith Ramachandran 2024-05-16 10:54:35 -04:00 committed by GitHub
parent 1efa578ffb
commit 65e62ba5eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -544,15 +544,44 @@ impl Mesh {
} }
/// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh.
/// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat
/// normals.
/// ///
/// # Panics /// # Panics
/// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
/// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
/// ///
/// FIXME: The should handle more cases since this is called as a part of gltf /// FIXME: This should handle more cases since this is called as a part of gltf
/// mesh loading where we can't really blame users for loading meshes that might
/// not conform to the limitations here!
pub fn compute_normals(&mut self) {
assert!(
matches!(self.primitive_topology, PrimitiveTopology::TriangleList),
"`compute_normals` can only work on `TriangleList`s"
);
if self.indices().is_none() {
self.compute_flat_normals();
} else {
self.compute_smooth_normals();
}
}
/// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh.
///
/// # Panics
/// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
/// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
/// Consider calling [`Mesh::duplicate_vertices`] or exporting your mesh with normal
/// attributes.
///
/// FIXME: This should handle more cases since this is called as a part of gltf
/// mesh loading where we can't really blame users for loading meshes that might /// mesh loading where we can't really blame users for loading meshes that might
/// not conform to the limitations here! /// not conform to the limitations here!
pub fn compute_flat_normals(&mut self) { pub fn compute_flat_normals(&mut self) {
assert!(
self.indices().is_none(),
"`compute_flat_normals` can't work on indexed geometry. Consider calling either `Mesh::compute_smooth_normals` or `Mesh::duplicate_vertices` followed by `Mesh::compute_flat_normals`."
);
assert!( assert!(
matches!(self.primitive_topology, PrimitiveTopology::TriangleList), matches!(self.primitive_topology, PrimitiveTopology::TriangleList),
"`compute_flat_normals` can only work on `TriangleList`s" "`compute_flat_normals` can only work on `TriangleList`s"
@ -564,42 +593,6 @@ impl Mesh {
.as_float3() .as_float3()
.expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`");
match self.indices() {
Some(indices) => {
let mut count: usize = 0;
let mut corners = [0_usize; 3];
let mut normals = vec![[0.0f32; 3]; positions.len()];
let mut adjacency_counts = vec![0_usize; positions.len()];
for i in indices.iter() {
corners[count % 3] = i;
count += 1;
if count % 3 == 0 {
let normal = face_normal(
positions[corners[0]],
positions[corners[1]],
positions[corners[2]],
);
for corner in corners {
normals[corner] =
(Vec3::from(normal) + Vec3::from(normals[corner])).into();
adjacency_counts[corner] += 1;
}
}
}
// average (smooth) normals for shared vertices...
// TODO: support different methods of weighting the average
for i in 0..normals.len() {
let count = adjacency_counts[i];
if count > 0 {
normals[i] = (Vec3::from(normals[i]) / (count as f32)).normalize().into();
}
}
self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
}
None => {
let normals: Vec<_> = positions let normals: Vec<_> = positions
.chunks_exact(3) .chunks_exact(3)
.map(|p| face_normal(p[0], p[1], p[2])) .map(|p| face_normal(p[0], p[1], p[2]))
@ -608,23 +601,106 @@ impl Mesh {
self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
} }
/// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared
/// vertices.
///
/// # Panics
/// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
/// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
/// Panics if the mesh does not have indices defined.
///
/// FIXME: This should handle more cases since this is called as a part of gltf
/// mesh loading where we can't really blame users for loading meshes that might
/// not conform to the limitations here!
pub fn compute_smooth_normals(&mut self) {
assert!(
matches!(self.primitive_topology, PrimitiveTopology::TriangleList),
"`compute_smooth_normals` can only work on `TriangleList`s"
);
assert!(
self.indices().is_some(),
"`compute_smooth_normals` can only work on indexed meshes"
);
let positions = self
.attribute(Mesh::ATTRIBUTE_POSITION)
.unwrap()
.as_float3()
.expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`");
let mut normals = vec![Vec3::ZERO; positions.len()];
let mut adjacency_counts = vec![0_usize; positions.len()];
self.indices()
.unwrap()
.iter()
.collect::<Vec<usize>>()
.chunks_exact(3)
.for_each(|face| {
let [a, b, c] = [face[0], face[1], face[2]];
let normal = Vec3::from(face_normal(positions[a], positions[b], positions[c]));
[a, b, c].iter().for_each(|pos| {
normals[*pos] += normal;
adjacency_counts[*pos] += 1;
});
});
// average (smooth) normals for shared vertices...
// TODO: support different methods of weighting the average
for i in 0..normals.len() {
let count = adjacency_counts[i];
if count > 0 {
normals[i] = (normals[i] / (count as f32)).normalize();
} }
} }
self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
}
/// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`].
/// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat
/// normals.
///
/// (Alternatively, you can use [`Mesh::compute_normals`] to mutate an existing mesh in-place)
///
/// # Panics
/// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
/// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
#[must_use]
pub fn with_computed_normals(mut self) -> Self {
self.compute_normals();
self
}
/// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`].
/// ///
/// (Alternatively, you can use [`Mesh::with_computed_flat_normals`] to mutate an existing mesh in-place) /// (Alternatively, you can use [`Mesh::compute_flat_normals`] to mutate an existing mesh in-place)
/// ///
/// # Panics /// # Panics
/// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3` or /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
/// if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
/// Consider calling [`Mesh::with_duplicated_vertices`] or export your mesh with normal attributes. /// Panics if the mesh has indices defined
#[must_use] #[must_use]
pub fn with_computed_flat_normals(mut self) -> Self { pub fn with_computed_flat_normals(mut self) -> Self {
self.compute_flat_normals(); self.compute_flat_normals();
self self
} }
/// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`].
///
/// (Alternatively, you can use [`Mesh::compute_smooth_normals`] to mutate an existing mesh in-place)
///
/// # Panics
/// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
/// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
/// Panics if the mesh does not have indices defined.
#[must_use]
pub fn with_computed_smooth_normals(mut self) -> Self {
self.compute_smooth_normals();
self
}
/// Generate tangents for the mesh using the `mikktspace` algorithm. /// Generate tangents for the mesh using the `mikktspace` algorithm.
/// ///
/// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. /// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful.