Add AutoGenerateOutlineNormalsPluign.
This commit is contained in:
parent
4cf0082cbb
commit
94e1742b44
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
87
src/lib.rs
87
src/lib.rs
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue