Skip to content

Commit

Permalink
calculate flat normals for mesh if missing (bevyengine#1808)
Browse files Browse the repository at this point in the history
If the gltf loader encounters a mesh without normal attributes, it will duplicate the vertex attributes and compute flat normals, as defined by https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes:

> **Implementation Note**: When normals are not specified, client implementations should calculate flat normals.

![image](https://user-images.githubusercontent.com/22177966/113483243-bb204880-94a2-11eb-8fa1-c4828a4882c5.png)

Helps with bevyengine#1802 

Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
2 people authored and ostwilkens committed Jul 27, 2021
1 parent db51f31 commit 0dfe85b
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 0 deletions.
1 change: 1 addition & 0 deletions crates/bevy_gltf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ bevy_render = { path = "../bevy_render", version = "0.5.0" }
bevy_transform = { path = "../bevy_transform", version = "0.5.0" }
bevy_math = { path = "../bevy_math", version = "0.5.0" }
bevy_scene = { path = "../bevy_scene", version = "0.5.0" }
bevy_log = { path = "../bevy_log", version = "0.5.0" }

# other
gltf = { version = "0.15.2", default-features = false, features = ["utils", "names", "KHR_materials_unlit"] }
Expand Down
15 changes: 15 additions & 0 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,21 @@ async fn load_gltf<'a, 'b>(
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
};

if mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_none() {
let vertex_count_before = mesh.count_vertices();
mesh.duplicate_vertices();
mesh.compute_flat_normals();
let vertex_count_after = mesh.count_vertices();

if vertex_count_before != vertex_count_after {
bevy_log::debug!("Missing vertex normals in indexed geometry, computing them as flat. Vertex count increased from {} to {}", vertex_count_before, vertex_count_after);
} else {
bevy_log::debug!(
"Missing vertex normals in indexed geometry, computing them as flat."
);
}
}

let mesh = load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh));
primitives.push(super::GltfPrimitive {
mesh,
Expand Down
112 changes: 112 additions & 0 deletions crates/bevy_render/src/mesh/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ impl VertexAttributeValues {
self.len() == 0
}

fn as_float3(&self) -> Option<&[[f32; 3]]> {
match self {
VertexAttributeValues::Float3(values) => Some(values),
_ => None,
}
}

// TODO: add vertex format as parameter here and perform type conversions
/// Flattens the VertexAttributeArray into a sequence of bytes. This is
/// useful for serialization and sending to the GPU.
Expand Down Expand Up @@ -254,6 +261,29 @@ pub enum Indices {
U32(Vec<u32>),
}

impl Indices {
fn iter(&self) -> impl Iterator<Item = usize> + '_ {
match self {
Indices::U16(vec) => IndicesIter::U16(vec.iter()),
Indices::U32(vec) => IndicesIter::U32(vec.iter()),
}
}
}
enum IndicesIter<'a> {
U16(std::slice::Iter<'a, u16>),
U32(std::slice::Iter<'a, u32>),
}
impl Iterator for IndicesIter<'_> {
type Item = usize;

fn next(&mut self) -> Option<Self::Item> {
match self {
IndicesIter::U16(iter) => iter.next().map(|val| *val as usize),
IndicesIter::U32(iter) => iter.next().map(|val| *val as usize),
}
}
}

impl From<&Indices> for IndexFormat {
fn from(indices: &Indices) -> Self {
match indices {
Expand Down Expand Up @@ -431,6 +461,88 @@ impl Mesh {

attributes_interleaved_buffer
}

/// Duplicates the vertex attributes so that no vertices are shared.
///
/// This can dramatically increase the vertex count, so make sure this is what you want.
/// Does nothing if no [Indices] are set.
pub fn duplicate_vertices(&mut self) {
fn duplicate<T: Copy>(values: &[T], indices: impl Iterator<Item = usize>) -> Vec<T> {
indices.map(|i| values[i]).collect()
}

assert!(
matches!(self.primitive_topology, PrimitiveTopology::TriangleList),
"can only duplicate vertices for `TriangleList`s"
);

let indices = match self.indices.take() {
Some(indices) => indices,
None => return,
};
for (_, attributes) in self.attributes.iter_mut() {
let indices = indices.iter();
match attributes {
VertexAttributeValues::Float(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Int(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Uint(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Float2(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Int2(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Uint2(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Float3(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Int3(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Uint3(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Int4(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Uint4(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Float4(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Short2(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Short2Norm(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Ushort2(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Ushort2Norm(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Short4(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Short4Norm(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Ushort4(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Ushort4Norm(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Char2(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Char2Norm(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Uchar2(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Uchar2Norm(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Char4(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Char4Norm(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Uchar4(vec) => *vec = duplicate(&vec, indices),
VertexAttributeValues::Uchar4Norm(vec) => *vec = duplicate(&vec, indices),
}
}
}

/// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh.
///
/// Panics if [`Indices`] are set.
/// Consider calling [Mesh::duplicate_vertices] or export your mesh with normal attributes.
pub fn compute_flat_normals(&mut self) {
if self.indices().is_some() {
panic!("`compute_flat_normals` can't work on indexed geometry. Consider calling `Mesh::duplicate_vertices`.");
}

let positions = self
.attribute(Mesh::ATTRIBUTE_POSITION)
.unwrap()
.as_float3()
.expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`");

let normals: Vec<_> = positions
.chunks_exact(3)
.map(|p| face_normal(p[0], p[1], p[2]))
.flat_map(|normal| std::array::IntoIter::new([normal, normal, normal]))
.collect();

self.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
}
}

fn face_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] {
let (a, b, c) = (Vec3::from(a), Vec3::from(b), Vec3::from(c));
(b - a).cross(c - a).normalize().into()
}

fn remove_resource_save(
Expand Down

0 comments on commit 0dfe85b

Please sign in to comment.