Add AutoGenerateOutlineNormalsPluign.

This commit is contained in:
Robin KAY 2022-08-28 11:00:20 +01:00
parent 4cf0082cbb
commit 94e1742b44
2 changed files with 128 additions and 79 deletions

120
src/generate.rs Normal file
View File

@ -0,0 +1,120 @@
use bevy::{
prelude::*,
render::{mesh::VertexAttributeValues, render_resource::VertexFormat},
utils::{FloatOrd, HashMap, HashSet},
};
use crate::ATTRIBUTE_OUTLINE_NORMAL;
/// Failed to generate outline normals for the mesh.
#[derive(thiserror::Error, Debug)]
pub enum GenerateOutlineNormalsError {
#[error("missing vertex attributes '{0}'")]
MissingVertexAttribute(&'static str),
#[error("the '{0}' vertex attribute should have {1:?} format, but had {2:?} format")]
InvalidVertexAttributeFormat(&'static str, VertexFormat, VertexFormat),
}
/// Extension methods for [`Mesh`].
pub trait OutlineMeshExt {
/// Generates outline normals for the mesh from the regular normals.
///
/// Vertex extrusion only works for meshes with smooth surface normals. Hard edges cause
/// visual artefacts. This function generates faux-smooth normals for outlining purposes
/// by grouping vertices by their position and averaging the normals at each point. These
/// outline normals are then inserted as a separate vertex attribute so that the regular
/// normals remain untouched. However, insofar as the outline normals are not
/// perpendicular to the surface of the mesh, this technique may result in non-uniform
/// outline thickness.
///
/// This function will silently do nothing if the outline normals would be equal to the
/// regular normals.
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError>;
}
impl OutlineMeshExt for Mesh {
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError> {
let positions = match self.attribute(Mesh::ATTRIBUTE_POSITION).ok_or(
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
)? {
VertexAttributeValues::Float32x3(p) => Ok(p),
v => Err(GenerateOutlineNormalsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_POSITION.name,
VertexFormat::Float32x3,
v.into(),
)),
}?;
let normals = match self.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or(
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
)? {
VertexAttributeValues::Float32x3(n) => Ok(n),
v => Err(GenerateOutlineNormalsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_NORMAL.name,
VertexFormat::Float32x3,
v.into(),
)),
}?;
let mut map = HashMap::with_capacity(positions.len());
let mut modified = false;
for (p, n) in positions.iter().zip(normals.iter()) {
let key = [FloatOrd(p[0]), FloatOrd(p[1]), FloatOrd(p[2])];
let value = Vec3::from_array(*n);
map.entry(key)
.and_modify(|e| {
modified = true;
*e += value
})
.or_insert(value);
}
if modified {
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(())
}
}
fn auto_generate_outline_normals(
mut meshes: ResMut<Assets<Mesh>>,
mut events: EventReader<'_, '_, AssetEvent<Mesh>>,
mut squelch: Local<HashSet<Handle<Mesh>>>,
) {
for event in events.iter() {
println!("{:?}", event);
match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
if squelch.contains(handle) {
// Suppress modification events created by this system
squelch.remove(handle);
} else if let Some(mesh) = meshes.get_mut(handle) {
let _ = mesh.generate_outline_normals();
squelch.insert(handle.clone_weak());
}
}
AssetEvent::Removed { handle } => {
squelch.remove(handle);
}
}
}
}
/// Automatically runs [`generate_outline_normals`](OutlineMeshExt::generate_outline_normals)
/// on every mesh.
///
/// This is provided as a convenience for simple projects. It runs the outline normal
/// generator every time a mesh asset is created or modified without consideration for
/// whether this is necessary or appropriate.
pub struct AutoGenerateOutlineNormalsPlugin;
impl Plugin for AutoGenerateOutlineNormalsPlugin {
fn build(&self, app: &mut App) {
app.add_system(auto_generate_outline_normals);
}
}

View File

@ -10,8 +10,10 @@
//! an object from being filled it. This must be added to any entity which needs to appear on //! an object from being filled it. This must be added to any entity which needs to appear on
//! top of an outline. //! top of an outline.
//! //!
//! Vertex extrusion works best with meshes that have smooth surfaces. For meshes with hard //! Vertex extrusion works best with meshes that have smooth surfaces. To avoid visual
//! edges, see the [`OutlineMeshExt::generate_outline_normals`] function. //! artefacts when outlining meshes with hard edges, see the
//! [`OutlineMeshExt::generate_outline_normals`] function and the
//! [`AutoGenerateOutlineNormalsPlugin`].
use bevy::asset::load_internal_asset; use bevy::asset::load_internal_asset;
use bevy::ecs::query::QueryItem; use bevy::ecs::query::QueryItem;
@ -19,12 +21,11 @@ use bevy::prelude::*;
use bevy::render::extract_component::{ use bevy::render::extract_component::{
ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
}; };
use bevy::render::mesh::{MeshVertexAttribute, VertexAttributeValues}; use bevy::render::mesh::MeshVertexAttribute;
use bevy::render::render_graph::RenderGraph; use bevy::render::render_graph::RenderGraph;
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}; use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat}; use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat};
use bevy::render::{RenderApp, RenderStage}; use bevy::render::{RenderApp, RenderStage};
use bevy::utils::{FloatOrd, HashMap};
use crate::draw::{queue_outline_mesh, queue_outline_stencil_mesh, DrawOutline, DrawStencil}; use crate::draw::{queue_outline_mesh, queue_outline_stencil_mesh, DrawOutline, DrawStencil};
use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline}; use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline};
@ -40,11 +41,14 @@ use crate::view_uniforms::{
}; };
mod draw; mod draw;
mod generate;
mod node; mod node;
mod pipeline; mod pipeline;
mod uniforms; mod uniforms;
mod view_uniforms; mod view_uniforms;
pub use generate::*;
// See https://alexanderameye.github.io/notes/rendering-outlines/ // See https://alexanderameye.github.io/notes/rendering-outlines/
/// The direction to extrude the vertex when rendering the outline. /// The direction to extrude the vertex when rendering the outline.
@ -88,81 +92,6 @@ pub struct OutlineBundle {
pub stencil: OutlineStencil, pub stencil: OutlineStencil,
} }
/// Failed to generate outline normals for the mesh.
#[derive(thiserror::Error, Debug)]
pub enum GenerateOutlineNormalsError {
#[error("missing vertex attributes '{0}'")]
MissingVertexAttribute(&'static str),
#[error("the '{0}' vertex attribute should have {1:?} format, but had {2:?} format")]
InvalidVertexAttributeFormat(&'static str, VertexFormat, VertexFormat),
}
/// Extension methods for [`Mesh`].
pub trait OutlineMeshExt {
/// Generates outline normals for the mesh from the regular normals.
///
/// Vertex extrusion only works for meshes with smooth surface normals. Hard edges cause
/// visual artefacts. This function generates faux-smooth normals for outlining purposes
/// by grouping vertices by their position and averaging the normals at each point. These
/// outline normals are then inserted as a separate vertex attribute so that the regular
/// normals remain untouched. However, insofar as the outline normals are not
/// perpendicular to the surface of the mesh, this technique may result in non-uniform
/// outline thickness.
///
/// This function will silently do nothing if the outline normals would be equal to the
/// regular normals.
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError>;
}
impl OutlineMeshExt for Mesh {
fn generate_outline_normals(&mut self) -> Result<(), GenerateOutlineNormalsError> {
let positions = match self.attribute(Mesh::ATTRIBUTE_POSITION).ok_or(
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
)? {
VertexAttributeValues::Float32x3(p) => Ok(p),
v => Err(GenerateOutlineNormalsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_POSITION.name,
VertexFormat::Float32x3,
v.into(),
)),
}?;
let normals = match self.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or(
GenerateOutlineNormalsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name),
)? {
VertexAttributeValues::Float32x3(n) => Ok(n),
v => Err(GenerateOutlineNormalsError::InvalidVertexAttributeFormat(
Mesh::ATTRIBUTE_NORMAL.name,
VertexFormat::Float32x3,
v.into(),
)),
}?;
let mut map = HashMap::with_capacity(positions.len());
let mut modified = false;
for (p, n) in positions.iter().zip(normals.iter()) {
let key = [FloatOrd(p[0]), FloatOrd(p[1]), FloatOrd(p[2])];
let value = Vec3::from_array(*n);
map.entry(key)
.and_modify(|e| {
modified = true;
*e += value
})
.or_insert(value);
}
if modified {
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(())
}
}
/// Adds support for rendering outlines. /// Adds support for rendering outlines.
pub struct OutlinePlugin; pub struct OutlinePlugin;