Change outline normal generator to use face normals.

This commit is contained in:
Robin KAY 2023-03-19 12:34:14 +00:00
parent cf7358cc44
commit a55c60fd21
1 changed files with 50 additions and 35 deletions

View File

@ -1,6 +1,9 @@
use bevy::{ use bevy::{
prelude::*, prelude::*,
render::{mesh::VertexAttributeValues, render_resource::VertexFormat}, render::{
mesh::VertexAttributeValues,
render_resource::{PrimitiveTopology, VertexFormat},
},
utils::{FloatOrd, HashMap, HashSet}, utils::{FloatOrd, HashMap, HashSet},
}; };
@ -9,6 +12,8 @@ use crate::ATTRIBUTE_OUTLINE_NORMAL;
/// Failed to generate outline normals for the mesh. /// Failed to generate outline normals for the mesh.
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum GenerateOutlineNormalsError { pub enum GenerateOutlineNormalsError {
#[error("unsupported primitive topology '{0:?}'")]
UnsupportedPrimitiveTopology(PrimitiveTopology),
#[error("missing vertex attributes '{0}'")] #[error("missing vertex attributes '{0}'")]
MissingVertexAttribute(&'static str), MissingVertexAttribute(&'static str),
#[error("the '{0}' vertex attribute should have {1:?} format, but had {2:?} format")] #[error("the '{0}' vertex attribute should have {1:?} format, but had {2:?} format")]
@ -17,7 +22,7 @@ pub enum GenerateOutlineNormalsError {
/// Extension methods for [`Mesh`]. /// Extension methods for [`Mesh`].
pub trait OutlineMeshExt { pub trait OutlineMeshExt {
/// Generates outline normals for the mesh from the regular normals. /// Generates outline normals for the mesh from the face normals.
/// ///
/// Vertex extrusion only works for meshes with smooth surface normals. Hard edges cause /// Vertex extrusion only works for meshes with smooth surface normals. Hard edges cause
/// visual artefacts. This function generates faux-smooth normals for outlining purposes /// visual artefacts. This function generates faux-smooth normals for outlining purposes
@ -27,13 +32,17 @@ pub trait OutlineMeshExt {
/// perpendicular to the surface of the mesh, this technique may result in non-uniform /// perpendicular to the surface of the mesh, this technique may result in non-uniform
/// outline thickness. /// outline thickness.
/// ///
/// This function will silently do nothing if the outline normals would be equal to the /// This function only supports meshes with TriangleList topology.
/// regular normals.
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError>; fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError>;
} }
impl OutlineMeshExt for Mesh { impl OutlineMeshExt for Mesh {
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError> { fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError> {
if self.primitive_topology() != PrimitiveTopology::TriangleList {
return Err(GenerateOutlineNormalsError::UnsupportedPrimitiveTopology(
self.primitive_topology(),
));
}
let positions = match self.attribute(Mesh::ATTRIBUTE_POSITION).ok_or( let positions = match self.attribute(Mesh::ATTRIBUTE_POSITION).ok_or(
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name), GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
)? { )? {
@ -44,39 +53,45 @@ impl OutlineMeshExt for Mesh {
v.into(), v.into(),
)), )),
}?; }?;
let normals = match self.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or( let mut map = HashMap::<[FloatOrd; 3], Vec3>::with_capacity(positions.len());
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name), let mut proc = |p0: Vec3, p1: Vec3, p2: Vec3| {
)? { let face_normal = (p1 - p0).cross(p2 - p0).normalize_or_zero();
VertexAttributeValues::Float32x3(n) => Ok(n), for (cp0, cp1, cp2) in [(p0, p1, p2), (p1, p2, p0), (p2, p0, p1)] {
v => Err(GenerateOutlineNormalsError::InvalidVertexAttributeFormat( let angle = (cp1 - cp0).angle_between(cp2 - cp0);
Mesh::ATTRIBUTE_NORMAL.name, let n = map
VertexFormat::Float32x3, .entry([FloatOrd(cp0.x), FloatOrd(cp0.y), FloatOrd(cp0.z)])
v.into(), .or_default();
)), *n += angle * face_normal;
}?; }
let mut map = HashMap::with_capacity(positions.len()); };
let mut modified = false; if let Some(indices) = self.indices() {
for (p, n) in positions.iter().zip(normals.iter()) { let mut it = indices.iter();
let key = [FloatOrd(p[0]), FloatOrd(p[1]), FloatOrd(p[2])]; while let (Some(i0), Some(i1), Some(i2)) = (it.next(), it.next(), it.next()) {
let value = Vec3::from_array(*n); proc(
map.entry(key) Vec3::from_array(positions[i0]),
.and_modify(|e| { Vec3::from_array(positions[i1]),
modified = true; Vec3::from_array(positions[i2]),
*e += value );
}) }
.or_insert(value); } else {
} let mut it = positions.iter();
if modified { while let (Some(p0), Some(p1), Some(p2)) = (it.next(), it.next(), it.next()) {
let mut outlines = Vec::with_capacity(positions.len()); proc(
for p in positions.iter() { Vec3::from_array(*p0),
let key = [FloatOrd(p[0]), FloatOrd(p[1]), FloatOrd(p[2])]; Vec3::from_array(*p1),
outlines.push(map.get(&key).unwrap().normalize_or_zero().to_array()); Vec3::from_array(*p2),
);
} }
self.insert_attribute(
ATTRIBUTE_OUTLINE_NORMAL,
VertexAttributeValues::Float32x3(outlines),
);
} }
let mut outlines = Vec::with_capacity(positions.len());
for p in positions.iter() {
let key = [FloatOrd(p[0]), FloatOrd(p[1]), FloatOrd(p[2])];
outlines.push(map.get(&key).unwrap().normalize_or_zero().to_array());
}
self.insert_attribute(
ATTRIBUTE_OUTLINE_NORMAL,
VertexAttributeValues::Float32x3(outlines),
);
Ok(()) Ok(())
} }
} }