diff --git a/examples/animated_fox.rs b/examples/animated_fox.rs index 8771353..416b349 100644 --- a/examples/animated_fox.rs +++ b/examples/animated_fox.rs @@ -6,7 +6,8 @@ use bevy::{ window::close_on_esc, }; use bevy_mod_outline::{ - AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin, OutlineVolume, + AutoGenerateOutlineNormalsPlugin, InheritOutlineBundle, OutlineBundle, OutlinePlugin, + OutlineVolume, }; #[derive(Resource)] @@ -65,10 +66,19 @@ fn setup( }); // Fox - commands.spawn(SceneBundle { - scene: asset_server.load("Fox.glb#Scene0"), - ..default() - }); + commands + .spawn(SceneBundle { + scene: asset_server.load("Fox.glb#Scene0"), + ..default() + }) + .insert(OutlineBundle { + outline: OutlineVolume { + visible: true, + width: 3.0, + colour: Color::RED, + }, + ..default() + }); } // Once the scene is loaded, start the animation and add an outline @@ -86,14 +96,9 @@ fn setup_scene_once_loaded( { if scene_manager.instance_is_ready(**scene) { for entity in scene_manager.iter_instance_entities(**scene) { - commands.entity(entity).insert(OutlineBundle { - outline: OutlineVolume { - visible: true, - width: 3.0, - colour: Color::RED, - }, - ..default() - }); + commands + .entity(entity) + .insert(InheritOutlineBundle::default()); } player.play(animation.0.clone_weak()).repeat(); *done = true; diff --git a/examples/hollow.rs b/examples/hollow.rs index 404438c..5757f46 100644 --- a/examples/hollow.rs +++ b/examples/hollow.rs @@ -56,7 +56,18 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }) .insert(Rotates) - .insert(ComputedOutlineDepth::default()); + .insert(OutlineBundle { + outline: OutlineVolume { + visible: true, + width: 7.5, + colour: Color::BLUE, + }, + stencil: OutlineStencil { + enabled: true, + offset: 0.0, + }, + ..default() + }); } // Once the scene is loaded, start the animation and add an outline @@ -73,19 +84,7 @@ fn setup_scene_once_loaded( for entity in scene_manager.iter_instance_entities(**scene) { commands .entity(entity) - .insert(OutlineBundle { - outline: OutlineVolume { - visible: true, - width: 7.5, - colour: Color::BLUE, - }, - stencil: OutlineStencil { - enabled: true, - offset: 0.0, - }, - ..default() - }) - .insert(InheritOutlineDepth); + .insert(InheritOutlineBundle::default()); if let Ok(name) = name_query.get(entity) { if name.as_str() == "inside" { commands.entity(entity).insert(RotatesHue); diff --git a/examples/morph_targets.rs b/examples/morph_targets.rs index e437244..d7e04c8 100644 --- a/examples/morph_targets.rs +++ b/examples/morph_targets.rs @@ -9,7 +9,8 @@ use bevy::{prelude::*, scene::SceneInstance}; use bevy_mod_outline::{ - AutoGenerateOutlineNormalsPlugin, OutlineBundle, OutlinePlugin, OutlineVolume, + AutoGenerateOutlineNormalsPlugin, InheritOutlineBundle, OutlineBundle, OutlinePlugin, + OutlineVolume, }; use std::f32::consts::PI; @@ -46,10 +47,19 @@ fn setup(asset_server: Res, mut commands: Commands) { the_wave: asset_server.load("MorphStressTest.gltf#Animation2"), mesh: asset_server.load("MorphStressTest.gltf#Mesh0/Primitive0"), }); - commands.spawn(SceneBundle { - scene: asset_server.load("MorphStressTest.gltf#Scene0"), - ..default() - }); + commands + .spawn(SceneBundle { + scene: asset_server.load("MorphStressTest.gltf#Scene0"), + ..default() + }) + .insert(OutlineBundle { + outline: OutlineVolume { + visible: true, + width: 3.0, + colour: Color::RED, + }, + ..default() + }); commands.spawn(DirectionalLightBundle { directional_light: DirectionalLight { color: Color::WHITE, @@ -78,14 +88,9 @@ fn setup_outlines( if let Ok(scene) = scene_query.get_single() { if scene_manager.instance_is_ready(**scene) { for entity in scene_manager.iter_instance_entities(**scene) { - commands.entity(entity).insert(OutlineBundle { - outline: OutlineVolume { - visible: true, - width: 3.0, - colour: Color::RED, - }, - ..default() - }); + commands + .entity(entity) + .insert(InheritOutlineBundle::default()); *has_setup = true; } } diff --git a/examples/pieces.rs b/examples/pieces.rs index 7f4341f..2e04dec 100644 --- a/examples/pieces.rs +++ b/examples/pieces.rs @@ -29,19 +29,6 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - let outline = OutlineBundle { - outline: OutlineVolume { - visible: true, - colour: Color::WHITE, - width: 10.0, - }, - stencil: OutlineStencil { - offset: 5.0, - ..default() - }, - ..default() - }; - // Add sphere with child meshes sticking out of it commands .spawn(PbrBundle { @@ -57,7 +44,18 @@ fn setup( transform: Transform::from_translation(Vec3::new(0.0, 1.0, 0.0)), ..default() }) - .insert(outline.clone()) + .insert(OutlineBundle { + outline: OutlineVolume { + visible: true, + colour: Color::WHITE, + width: 10.0, + }, + stencil: OutlineStencil { + offset: 5.0, + ..default() + }, + ..default() + }) .insert(Rotates) .with_children(|parent| { parent @@ -78,8 +76,7 @@ fn setup( .with_translation(Vec3::new(0.0, 0.0, 0.75)), ..default() }) - .insert(outline.clone()) - .insert(InheritOutlineDepth); + .insert(InheritOutlineBundle::default()); parent .spawn(PbrBundle { mesh: meshes.add( @@ -96,8 +93,7 @@ fn setup( .with_translation(Vec3::new(0.0, 0.0, -0.75)), ..default() }) - .insert(outline.clone()) - .insert(InheritOutlineDepth); + .insert(InheritOutlineBundle::default()); }); // Add plane, light source, and camera diff --git a/src/computed.rs b/src/computed.rs index abce7d6..a701733 100644 --- a/src/computed.rs +++ b/src/computed.rs @@ -1,76 +1,113 @@ -use bevy::prelude::*; +use bevy::{ecs::query::QueryItem, prelude::*}; -use crate::uniforms::DepthMode; +use crate::{uniforms::DepthMode, InheritOutline, OutlineMode, OutlineStencil, OutlineVolume}; + +#[derive(Clone, Default)] +pub(crate) struct ComputedVolume { + pub(crate) enabled: bool, + pub(crate) offset: f32, + pub(crate) colour: Vec4, +} + +#[derive(Clone, Default)] +pub(crate) struct ComputedStencil { + pub(crate) enabled: bool, + pub(crate) offset: f32, +} + +#[derive(Clone)] +pub(crate) struct ComputedMode { + pub(crate) world_origin: Vec3, + pub(crate) depth_mode: DepthMode, +} + +impl Default for ComputedMode { + fn default() -> Self { + Self { + world_origin: Vec3::NAN, + depth_mode: DepthMode::Real, + } + } +} + +#[derive(Copy, Clone, Default)] +pub(crate) enum Source { + #[default] + Set, + Inherited, +} + +#[derive(Clone, Default)] +pub(crate) struct Sourced { + pub(crate) value: T, + pub(crate) source: Source, +} + +impl Sourced { + pub fn inherit(value: &T) -> Self { + Sourced { + value: value.clone(), + source: Source::Inherited, + } + } + + pub fn set(value: T) -> Self { + Sourced { + value, + source: Source::Set, + } + } + + pub fn is_changed(&self, tuple: Option<(&U, bool)>) -> bool { + tuple.is_some() != matches!(self.source, Source::Set) + || if let Some((_, c)) = tuple { c } else { false } + } +} + +#[derive(Clone, Default)] +pub(crate) struct ComputedInternal { + pub(crate) inherited_from: Option, + pub(crate) volume: Sourced, + pub(crate) stencil: Sourced, + pub(crate) mode: Sourced, +} /// A component for storing the computed depth at which the outline lies. #[derive(Clone, Component, Default)] -pub struct ComputedOutlineDepth { - pub(crate) world_origin: Vec3, - pub(crate) depth_mode: DepthMode, - pub(crate) inherited: Option, -} +pub struct ComputedOutline(pub(crate) Option); -/// A component which specifies how the outline depth should be computed. -#[derive(Clone, Component)] -#[non_exhaustive] -pub enum SetOutlineDepth { - /// A flat plane facing the camera and intersecting the specified point in model-space. - Flat { model_origin: Vec3 }, - /// Real model-space. - Real, -} - -/// A component which specifies that this outline lies at the same depth as its parent. -#[derive(Clone, Component, Default)] -pub struct InheritOutlineDepth; +type OutlineComponents<'a> = ( + (&'a GlobalTransform, Changed), + Option<(&'a OutlineVolume, Changed)>, + Option<(&'a OutlineStencil, Changed)>, + Option<(&'a OutlineMode, Changed)>, +); #[allow(clippy::type_complexity)] -pub(crate) fn compute_outline_depth( +pub(crate) fn compute_outline( mut root_query: Query< ( Entity, - &mut ComputedOutlineDepth, - &GlobalTransform, - Changed, - Option<(&SetOutlineDepth, Changed)>, + &mut ComputedOutline, + OutlineComponents, Option<&Children>, ), - Without, + Without, >, - mut computed_query: Query<&mut ComputedOutlineDepth, With>, + mut child_query_mut: Query<(&mut ComputedOutline, OutlineComponents), With>, child_query: Query<&Children>, ) { - for (entity, mut computed, transform, changed_transform, set_depth, children) in - root_query.iter_mut() - { - let changed = !computed.depth_mode.is_valid() - || computed.inherited.is_some() - || changed_transform - || set_depth.filter(|(_, c)| *c).is_some(); - if changed { - let (origin, depth_mode) = if let Some((sd, _)) = set_depth { - match sd { - SetOutlineDepth::Flat { - model_origin: origin, - } => (*origin, DepthMode::Flat), - SetOutlineDepth::Real => (Vec3::NAN, DepthMode::Real), - } - } else { - (Vec3::ZERO, DepthMode::Flat) - }; - let matrix = transform.compute_matrix(); - computed.world_origin = matrix.project_point3(origin); - computed.depth_mode = depth_mode; - computed.inherited = None; - } + for (entity, mut computed, components, children) in root_query.iter_mut() { + let changed = update_computed_outline(&mut computed, components, &default(), None, false); if let Some(cs) = children { + let parent_computed = &computed.0.as_ref().unwrap().clone(); for child in cs.iter() { - propagate_outline_depth( - &computed, + propagate_computed_outline( + parent_computed, changed, entity, *child, - &mut computed_query, + &mut child_query_mut, &child_query, ); } @@ -78,31 +115,92 @@ pub(crate) fn compute_outline_depth( } } -fn propagate_outline_depth( - root_computed: &ComputedOutlineDepth, - mut changed: bool, - parent: Entity, +fn propagate_computed_outline( + parent_computed: &ComputedInternal, + parent_changed: bool, + parent_entity: Entity, entity: Entity, - computed_query: &mut Query<&mut ComputedOutlineDepth, With>, + child_query_mut: &mut Query<(&mut ComputedOutline, OutlineComponents), With>, child_query: &Query<&Children>, ) { - if let Ok(mut computed) = computed_query.get_mut(entity) { - changed |= !computed.depth_mode.is_valid() | (computed.inherited != Some(parent)); - if changed { - *computed = root_computed.clone(); - computed.inherited = Some(parent); - } + if let Ok((mut computed, components)) = child_query_mut.get_mut(entity) { + let changed = update_computed_outline( + &mut computed, + components, + parent_computed, + Some(parent_entity), + parent_changed, + ); if let Ok(cs) = child_query.get(entity) { + let parent_computed = &computed.0.as_ref().unwrap().clone(); for child in cs.iter() { - propagate_outline_depth( - root_computed, + propagate_computed_outline( + parent_computed, changed, entity, *child, - computed_query, + child_query_mut, child_query, ); } } } } + +fn update_computed_outline( + computed: &mut ComputedOutline, + ((transform, changed_transform), volume, stencil, mode): QueryItem<'_, OutlineComponents>, + parent_computed: &ComputedInternal, + parent_entity: Option, + force_update: bool, +) -> bool { + let changed = force_update + || if let ComputedOutline(Some(computed)) = computed { + computed.inherited_from != parent_entity + || (changed_transform && matches!(mode, Some((OutlineMode::FlatVertex { .. }, _)))) + || computed.volume.is_changed(volume) + || computed.stencil.is_changed(stencil) + || computed.mode.is_changed(mode) + } else { + true + }; + if changed { + *computed = ComputedOutline(Some(ComputedInternal { + inherited_from: parent_entity, + volume: if let Some((vol, _)) = volume { + Sourced::set(ComputedVolume { + enabled: vol.visible && vol.colour.a() != 0.0, + offset: vol.width, + colour: vol.colour.into(), + }) + } else { + Sourced::inherit(&parent_computed.volume.value) + }, + stencil: if let Some((sten, _)) = stencil { + Sourced::set(ComputedStencil { + enabled: sten.enabled, + offset: sten.offset, + }) + } else { + Sourced::inherit(&parent_computed.stencil.value) + }, + mode: if let Some((m, _)) = mode { + Sourced::set(match m { + OutlineMode::FlatVertex { + model_origin: origin, + } => ComputedMode { + world_origin: transform.compute_matrix().project_point3(*origin), + depth_mode: DepthMode::Flat, + }, + OutlineMode::RealVertex => ComputedMode { + world_origin: Vec3::NAN, + depth_mode: DepthMode::Real, + }, + }) + } else { + Sourced::inherit(&parent_computed.mode.value) + }, + })); + } + changed +} diff --git a/src/draw.rs b/src/draw.rs index 97cc631..8746dc5 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -8,9 +8,8 @@ use bevy::render::view::{ExtractedView, RenderLayers}; use crate::node::{OpaqueOutline, StencilOutline, TransparentOutline}; use crate::pipeline::{OutlinePipeline, PassType, PipelineKey}; use crate::uniforms::{ - DepthMode, OutlineFragmentUniform, OutlineStencilFlags, OutlineStencilUniform, - OutlineVolumeFlags, OutlineVolumeUniform, SetOutlineStencilBindGroup, - SetOutlineVolumeBindGroup, + ExtractedOutline, OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform, + SetOutlineStencilBindGroup, SetOutlineVolumeBindGroup, }; use crate::view_uniforms::SetOutlineViewBindGroup; use crate::OutlineRenderLayers; @@ -34,9 +33,8 @@ pub(crate) fn queue_outline_stencil_mesh( render_meshes: Res>, material_meshes: Query<( Entity, - &Handle, &OutlineStencilUniform, - &OutlineStencilFlags, + &ExtractedOutline, &OutlineRenderLayers, )>, mut views: Query<( @@ -57,21 +55,16 @@ pub(crate) fn queue_outline_stencil_mesh( for (view, mut stencil_phase, view_mask) in views.iter_mut() { let rangefinder = view.rangefinder3d(); let view_mask = view_mask.copied().unwrap_or_default(); - for (entity, mesh_handle, stencil_uniform, stencil_flags, outline_mask) in - material_meshes.iter() - { + for (entity, stencil_uniform, outline, outline_mask) in material_meshes.iter() { if !view_mask.intersects(outline_mask) { continue; // Layer not enabled } - if stencil_flags.depth_mode == DepthMode::Invalid { - continue; // DepthMode not propagated - } - let Some(mesh) = render_meshes.get(mesh_handle) else { + let Some(mesh) = render_meshes.get(&outline.mesh) else { continue; // No mesh }; let key = base_key .with_primitive_topology(mesh.primitive_topology) - .with_depth_mode(stencil_flags.depth_mode) + .with_depth_mode(outline.depth_mode) .with_offset_zero(stencil_uniform.offset == 0.0) .with_morph_targets(mesh.morph_targets.is_some()); let Ok(pipeline) = @@ -110,9 +103,8 @@ pub(crate) fn queue_outline_volume_mesh( render_meshes: Res>, material_meshes: Query<( Entity, - &Handle, &OutlineVolumeUniform, - &OutlineVolumeFlags, + &ExtractedOutline, &OutlineFragmentUniform, &OutlineRenderLayers, )>, @@ -137,16 +129,13 @@ pub(crate) fn queue_outline_volume_mesh( for (view, mut opaque_phase, mut transparent_phase, view_mask) in views.iter_mut() { let view_mask = view_mask.copied().unwrap_or_default(); let rangefinder = view.rangefinder3d(); - for (entity, mesh_handle, volume_uniform, volume_flags, fragment_uniform, outline_mask) in + for (entity, volume_uniform, outline, fragment_uniform, outline_mask) in material_meshes.iter() { if !view_mask.intersects(outline_mask) { continue; // Layer not enabled } - if volume_flags.depth_mode == DepthMode::Invalid { - continue; // DepthMode not propagated - } - let Some(mesh) = render_meshes.get(mesh_handle) else { + let Some(mesh) = render_meshes.get(&outline.mesh) else { continue; // No mesh }; let transparent = fragment_uniform.colour[3] < 1.0; @@ -157,7 +146,7 @@ pub(crate) fn queue_outline_volume_mesh( } else { PassType::Opaque }) - .with_depth_mode(volume_flags.depth_mode) + .with_depth_mode(outline.depth_mode) .with_offset_zero(volume_uniform.offset == 0.0) .with_hdr_format(view.hdr) .with_morph_targets(mesh.morph_targets.is_some()); diff --git a/src/lib.rs b/src/lib.rs index 8f14bf5..5158fef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,17 +3,19 @@ //! //! Outlines are rendered in a seperate pass following the main 3D pass. The effect of this //! pass is to present the outlines in depth sorted order according to the model translation -//! of each mesh. This ensures that outlines are not clipped by other geometry. +//! of each mesh. This ensures that outlines are not clipped by non-outline geometry. //! //! The [`OutlineVolume`] component will, by itself, cover the original object entirely with //! the outline colour. The [`OutlineStencil`] component must also be added to prevent the body //! of an object from being filled it. This must be added to any entity which needs to appear on //! top of an outline. //! +//! The [`OutlineMode`] component specifies the rendering method. Outlines may be flattened into +//! a plane in order to further avoid clipping, or left in real space. +//! //! The [`OutlineBundle`] and [`OutlineStencilBundle`] bundles can be used to add the right -//! components, including the required [`ComputedOutlineDepth`] component. Optionally, the -//! [`SetOutlineDepth`] and [`InheritOutlineDepth`] components may also be added to control the -//! depth ordering of outlines. +//! components, including the required [`ComputedOutline`] component. Outlines can be inherited +//! from the parent via the [`InheritOutline`] component and [`InheritOutlineBundle`]. //! //! Vertex extrusion works best with meshes that have smooth surfaces. To avoid visual //! artefacts when outlining meshes with hard edges, see the @@ -29,7 +31,7 @@ use bevy::render::mesh::MeshVertexAttribute; use bevy::render::render_graph::RenderGraph; use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}; use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat}; -use bevy::render::view::RenderLayers; +use bevy::render::view::{RenderLayers, VisibilitySystems}; use bevy::render::{Render, RenderApp, RenderSet}; use bevy::transform::TransformSystem; use interpolation::Lerp; @@ -40,9 +42,8 @@ use crate::draw::{ use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline}; use crate::pipeline::{OutlinePipeline, FRAGMENT_SHADER_HANDLE, OUTLINE_SHADER_HANDLE}; use crate::uniforms::{ - extract_outline_stencil_uniforms, extract_outline_volume_uniforms, - queue_outline_stencil_bind_group, queue_outline_volume_bind_group, OutlineFragmentUniform, - OutlineStencilUniform, OutlineVolumeUniform, + extract_outline_uniforms, queue_outline_stencil_bind_group, queue_outline_volume_bind_group, + set_outline_visibility, OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform, }; use crate::view_uniforms::{ extract_outline_view_uniforms, queue_outline_view_bind_group, OutlineViewUniform, @@ -148,7 +149,7 @@ impl ExtractComponent for OutlineRenderLayers { Option<&'static OutlineRenderLayers>, Option<&'static RenderLayers>, ); - type Filter = Or<(With, With)>; + type Filter = With; type Out = Self; fn extract_component( @@ -163,19 +164,51 @@ impl ExtractComponent for OutlineRenderLayers { } } +/// A component which specifies how the outline should be rendered. +#[derive(Clone, Component)] +#[non_exhaustive] +pub enum OutlineMode { + /// Vertex extrusion flattened into a plane facing the camera and intersecting the specified + /// point in model-space. + FlatVertex { model_origin: Vec3 }, + /// Vertex extrusion in real model-space. + RealVertex, +} + +impl Default for OutlineMode { + fn default() -> Self { + OutlineMode::FlatVertex { + model_origin: Vec3::ZERO, + } + } +} + +/// A component for inheriting outlines from the parent entity. +#[derive(Clone, Component, Default)] +pub struct InheritOutline; + /// A bundle for rendering stenciled outlines around meshes. #[derive(Bundle, Clone, Default)] pub struct OutlineBundle { pub outline: OutlineVolume, pub stencil: OutlineStencil, - pub plane: ComputedOutlineDepth, + pub mode: OutlineMode, + pub computed: ComputedOutline, } /// A bundle for stenciling meshes in the outlining pass. #[derive(Bundle, Clone, Default)] pub struct OutlineStencilBundle { pub stencil: OutlineStencil, - pub plane: ComputedOutlineDepth, + pub mode: OutlineMode, + pub computed: ComputedOutline, +} + +/// A bundle for inheriting outlines from the parent entity. +#[derive(Bundle, Clone, Default)] +pub struct InheritOutlineBundle { + pub inherit: InheritOutline, + pub computed: ComputedOutline, } /// Adds support for rendering outlines. @@ -205,7 +238,10 @@ impl Plugin for OutlinePlugin { )) .add_systems( PostUpdate, - compute_outline_depth.after(TransformSystem::TransformPropagate), + ( + compute_outline.after(TransformSystem::TransformPropagate), + set_outline_visibility.in_set(VisibilitySystems::CheckVisibility), + ), ) .sub_app_mut(RenderApp) .init_resource::>() @@ -215,35 +251,31 @@ impl Plugin for OutlinePlugin { .add_render_command::() .add_render_command::() .add_render_command::() - .add_systems(ExtractSchedule, extract_outline_view_uniforms) - .add_systems(ExtractSchedule, extract_outline_stencil_uniforms) - .add_systems(ExtractSchedule, extract_outline_volume_uniforms) .add_systems( - Render, - sort_phase_system::.in_set(RenderSet::PhaseSort), + ExtractSchedule, + (extract_outline_uniforms, extract_outline_view_uniforms), ) .add_systems( Render, - sort_phase_system::.in_set(RenderSet::PhaseSort), + ( + sort_phase_system::, + sort_phase_system::, + ) + .in_set(RenderSet::PhaseSort), ) .add_systems( Render, - sort_phase_system::.in_set(RenderSet::PhaseSort), + ( + queue_outline_view_bind_group, + queue_outline_stencil_bind_group, + queue_outline_volume_bind_group, + ) + .in_set(RenderSet::Queue), ) .add_systems( Render, - queue_outline_view_bind_group.in_set(RenderSet::Queue), - ) - .add_systems( - Render, - queue_outline_stencil_bind_group.in_set(RenderSet::Queue), - ) - .add_systems( - Render, - queue_outline_volume_bind_group.in_set(RenderSet::Queue), - ) - .add_systems(Render, queue_outline_stencil_mesh.in_set(RenderSet::Queue)) - .add_systems(Render, queue_outline_volume_mesh.in_set(RenderSet::Queue)); + (queue_outline_stencil_mesh, queue_outline_volume_mesh).in_set(RenderSet::Queue), + ); let world = &mut app.sub_app_mut(RenderApp).world; let node = OutlineNode::new(world); diff --git a/src/uniforms.rs b/src/uniforms.rs index 3fc5e03..38cb984 100644 --- a/src/uniforms.rs +++ b/src/uniforms.rs @@ -13,10 +13,13 @@ use bevy::{ }, }; -use crate::{ - node::StencilOutline, pipeline::OutlinePipeline, ComputedOutlineDepth, OutlineStencil, - OutlineVolume, -}; +use crate::{node::StencilOutline, pipeline::OutlinePipeline, ComputedOutline}; + +#[derive(Component)] +pub(crate) struct ExtractedOutline { + pub depth_mode: DepthMode, + pub mesh: Handle, +} #[derive(Clone, Component, ShaderType)] pub(crate) struct OutlineStencilUniform { @@ -38,30 +41,12 @@ pub(crate) struct OutlineFragmentUniform { pub colour: Vec4, } -#[derive(Clone, Copy, Default, PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub(crate) enum DepthMode { - #[default] - Invalid = 0, Flat = 1, Real = 2, } -impl DepthMode { - pub fn is_valid(&self) -> bool { - *self != DepthMode::Invalid - } -} - -#[derive(Component)] -pub(crate) struct OutlineStencilFlags { - pub depth_mode: DepthMode, -} - -#[derive(Component)] -pub(crate) struct OutlineVolumeFlags { - pub depth_mode: DepthMode, -} - #[derive(Resource)] pub(crate) struct OutlineStencilBindGroup { pub bind_group: BindGroup, @@ -72,46 +57,46 @@ pub(crate) struct OutlineVolumeBindGroup { pub bind_group: BindGroup, } -pub(crate) fn extract_outline_stencil_uniforms( - mut commands: Commands, - query: Extract>, +pub(crate) fn set_outline_visibility( + mut query: Query<(&mut ComputedVisibility, &ComputedOutline)>, ) { - for (entity, stencil, computed) in query.iter() { - if !stencil.enabled { - continue; + for (mut visibility, computed) in query.iter_mut() { + if let ComputedOutline(Some(computed)) = computed { + if computed.volume.value.enabled || computed.stencil.value.enabled { + visibility.set_visible_in_view(); + } } - commands - .get_or_spawn(entity) - .insert(OutlineStencilUniform { - origin: computed.world_origin, - offset: stencil.offset, - }) - .insert(OutlineStencilFlags { - depth_mode: computed.depth_mode, - }); } } -pub(crate) fn extract_outline_volume_uniforms( +#[allow(clippy::type_complexity)] +pub(crate) fn extract_outline_uniforms( mut commands: Commands, - query: Extract>, + query: Extract)>>, ) { - for (entity, outline, computed) in query.iter() { - if !outline.visible || outline.colour.a() == 0.0 { - continue; - } - commands - .get_or_spawn(entity) - .insert(OutlineVolumeUniform { - origin: computed.world_origin, - offset: outline.width, - }) - .insert(OutlineFragmentUniform { - colour: outline.colour.as_linear_rgba_f32().into(), - }) - .insert(OutlineVolumeFlags { - depth_mode: computed.depth_mode, + for (entity, computed, mesh) in query.iter() { + let cmds = &mut commands.get_or_spawn(entity); + if let ComputedOutline(Some(computed)) = computed { + cmds.insert(ExtractedOutline { + depth_mode: computed.mode.value.depth_mode, + mesh: mesh.clone_weak(), }); + if computed.volume.value.enabled { + cmds.insert(OutlineVolumeUniform { + origin: computed.mode.value.world_origin, + offset: computed.volume.value.offset, + }) + .insert(OutlineFragmentUniform { + colour: computed.volume.value.colour, + }); + } + if computed.stencil.value.enabled { + cmds.insert(OutlineStencilUniform { + origin: computed.mode.value.world_origin, + offset: computed.stencil.value.offset, + }); + } + } } }