From c3067d6bfa5cd5cebc8453b13a1a31dbdebc425f Mon Sep 17 00:00:00 2001 From: Sludge <96552222+SludgePhD@users.noreply.github.com> Date: Fri, 10 Nov 2023 03:46:56 +0100 Subject: [PATCH] Add Wireframe2d plugin --- crates/bevy_pbr/src/wireframe.rs | 2 +- crates/bevy_sprite/src/lib.rs | 1 + crates/bevy_sprite/src/mesh2d/wireframe.wgsl | 12 + crates/bevy_sprite/src/wireframe.rs | 221 +++++++++++++++++++ examples/2d/2d_shapes.rs | 13 +- 5 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 crates/bevy_sprite/src/mesh2d/wireframe.wgsl create mode 100644 crates/bevy_sprite/src/wireframe.rs diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 2d533e7e52f5c4..67704f0b652306 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -13,7 +13,7 @@ use bevy_render::{ pub const WIREFRAME_SHADER_HANDLE: Handle = Handle::weak_from_u128(192598014480025766); -/// A [`Plugin`] that draws wireframes. +/// A [`Plugin`] that draws 3D meshes as wireframes. /// /// Wireframes currently do not work when using webgl or webgpu. /// Supported rendering backends: diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 9f68a9a3b981e5..eeb4868730b3fd 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -9,6 +9,7 @@ mod texture_atlas; mod texture_atlas_builder; pub mod collide_aabb; +pub mod wireframe; pub mod prelude { #[doc(hidden)] diff --git a/crates/bevy_sprite/src/mesh2d/wireframe.wgsl b/crates/bevy_sprite/src/mesh2d/wireframe.wgsl new file mode 100644 index 00000000000000..526ede7ecd699e --- /dev/null +++ b/crates/bevy_sprite/src/mesh2d/wireframe.wgsl @@ -0,0 +1,12 @@ +#import bevy_sprite::mesh2d_vertex_output::VertexOutput + +struct WireframeMaterial { + color: vec4, +}; + +@group(1) @binding(0) +var material: WireframeMaterial; +@fragment +fn fragment(in: VertexOutput) -> @location(0) vec4 { + return material.color; +} diff --git a/crates/bevy_sprite/src/wireframe.rs b/crates/bevy_sprite/src/wireframe.rs new file mode 100644 index 00000000000000..634f9254b0732f --- /dev/null +++ b/crates/bevy_sprite/src/wireframe.rs @@ -0,0 +1,221 @@ +use bevy_app::{Plugin, Startup, Update}; +use bevy_asset::{load_internal_asset, Asset, Assets, Handle}; +use bevy_ecs::prelude::*; +use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath, TypeUuid}; +use bevy_render::{ + color::Color, extract_resource::ExtractResource, mesh::MeshVertexBufferLayout, prelude::*, + render_resource::*, +}; + +use crate::{Material2d, Material2dKey, Material2dPlugin, Mesh2dHandle}; + +pub const WIREFRAME_SHADER_HANDLE: Handle = Handle::weak_from_u128(192598014480025766); + +/// A [`Plugin`] that draws 2D meshes as wireframes. +/// +/// Wireframes currently do not work when using webgl or webgpu. +/// Supported rendering backends: +/// - DX12 +/// - Vulkan +/// - Metal +/// +/// This is a native only feature. +#[derive(Debug, Default)] +pub struct Wireframe2dPlugin; +impl Plugin for Wireframe2dPlugin { + fn build(&self, app: &mut bevy_app::App) { + load_internal_asset!( + app, + WIREFRAME_SHADER_HANDLE, + "mesh2d/wireframe.wgsl", + Shader::from_wgsl + ); + + app.register_type::() + .register_type::() + .register_type::() + .init_resource::() + .add_plugins(Material2dPlugin::::default()) + .add_systems(Startup, setup_global_wireframe_material) + .add_systems( + Update, + ( + global_color_changed.run_if(resource_changed::()), + wireframe_color_changed, + apply_wireframe_material, + apply_global_wireframe_material.run_if(resource_changed::()), + ), + ); + } +} + +/// Enables wireframe rendering for any entity it is attached to. +/// It will ignore the [`Wireframe2dConfig`] global setting. +/// +/// This requires the [`Wireframe2dPlugin`] to be enabled. +#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] +#[reflect(Component, Default)] +pub struct Wireframe2d; + +/// Sets the color of the [`Wireframe2d`] of the entity it is attached to. +/// If this component is present but there's no [`Wireframe2d`] component, +/// it will still affect the color of the wireframe when [`Wireframe2dConfig::global`] is set to true. +/// +/// This overrides the [`Wireframe2dConfig::default_color`]. +#[derive(Component, Debug, Clone, Default, Reflect)] +#[reflect(Component, Default)] +pub struct Wireframe2dColor { + pub color: Color, +} + +/// Disables wireframe rendering for any entity it is attached to. +/// It will ignore the [`Wireframe2dConfig`] global setting. +/// +/// This requires the [`Wireframe2dPlugin`] to be enabled. +#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)] +#[reflect(Component, Default)] +pub struct NoWireframe2d; + +#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)] +#[reflect(Resource)] +pub struct Wireframe2dConfig { + /// Whether to show wireframes for all meshes. + /// Can be overridden for individual meshes by adding a [`Wireframe2d`] or [`NoWireframe2d`] component. + pub global: bool, + /// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe2d`] component attached to it will have + /// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe2d`], + /// but no [`Wireframe2dColor`]. + pub default_color: Color, +} + +#[derive(Resource)] +struct GlobalWireframeMaterial { + // This handle will be reused when the global config is enabled + handle: Handle, +} + +fn setup_global_wireframe_material( + mut commands: Commands, + mut materials: ResMut>, + config: Res, +) { + // Create the handle used for the global material + commands.insert_resource(GlobalWireframeMaterial { + handle: materials.add(Wireframe2dMaterial { + color: config.default_color, + }), + }); +} + +/// Updates the wireframe material of all entities without a [`Wireframe2dColor`] or without a [`Wireframe2d`] component +fn global_color_changed( + config: Res, + mut materials: ResMut>, + global_material: Res, +) { + if let Some(global_material) = materials.get_mut(&global_material.handle) { + global_material.color = config.default_color; + } +} + +/// Updates the wireframe material when the color in [`Wireframe2dColor`] changes +#[allow(clippy::type_complexity)] +fn wireframe_color_changed( + mut materials: ResMut>, + mut colors_changed: Query< + (&mut Handle, &Wireframe2dColor), + (With, Changed), + >, +) { + for (mut handle, wireframe_color) in &mut colors_changed { + *handle = materials.add(Wireframe2dMaterial { + color: wireframe_color.color, + }); + } +} + +/// Applies or remove the wireframe material to any mesh with a [`Wireframe2d`] component. +fn apply_wireframe_material( + mut commands: Commands, + mut materials: ResMut>, + wireframes: Query< + (Entity, Option<&Wireframe2dColor>), + (With, Without>), + >, + mut removed_wireframes: RemovedComponents, + global_material: Res, +) { + for e in removed_wireframes.read() { + if let Some(mut commands) = commands.get_entity(e) { + commands.remove::>(); + } + } + + let mut wireframes_to_spawn = vec![]; + for (e, wireframe_color) in &wireframes { + let material = if let Some(wireframe_color) = wireframe_color { + materials.add(Wireframe2dMaterial { + color: wireframe_color.color, + }) + } else { + // If there's no color specified we can use the global material since it's already set to use the default_color + global_material.handle.clone() + }; + wireframes_to_spawn.push((e, material)); + } + commands.insert_or_spawn_batch(wireframes_to_spawn); +} + +type WireframeFilter = ( + With, + Without, + Without, +); + +/// Applies or removes a wireframe material on any mesh without a [`Wireframe2d`] component. +fn apply_global_wireframe_material( + mut commands: Commands, + config: Res, + meshes_without_material: Query>)>, + meshes_with_global_material: Query< + Entity, + (WireframeFilter, With>), + >, + global_material: Res, +) { + if config.global { + let mut material_to_spawn = vec![]; + for e in &meshes_without_material { + // We only add the material handle but not the Wireframe component + // This makes it easy to detect which mesh is using the global material and which ones are user specified + material_to_spawn.push((e, global_material.handle.clone())); + } + commands.insert_or_spawn_batch(material_to_spawn); + } else { + for e in &meshes_with_global_material { + commands.entity(e).remove::>(); + } + } +} + +#[derive(Default, AsBindGroup, TypeUuid, TypePath, Debug, Clone, Asset)] +#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"] +pub struct Wireframe2dMaterial { + #[uniform(0)] + pub color: Color, +} + +impl Material2d for Wireframe2dMaterial { + fn fragment_shader() -> ShaderRef { + WIREFRAME_SHADER_HANDLE.into() + } + + fn specialize( + descriptor: &mut RenderPipelineDescriptor, + _layout: &MeshVertexBufferLayout, + _key: Material2dKey, + ) -> Result<(), SpecializedMeshPipelineError> { + descriptor.primitive.polygon_mode = PolygonMode::Line; + Ok(()) + } +} diff --git a/examples/2d/2d_shapes.rs b/examples/2d/2d_shapes.rs index 7fa168fe697c11..1c0f497f3c0481 100644 --- a/examples/2d/2d_shapes.rs +++ b/examples/2d/2d_shapes.rs @@ -1,10 +1,21 @@ //! Shows how to render simple primitive shapes with a single color. use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; +use bevy_internal::sprite::wireframe::{Wireframe2dConfig, Wireframe2dPlugin}; fn main() { App::new() - .add_plugins(DefaultPlugins) + .add_plugins((DefaultPlugins, Wireframe2dPlugin)) + // Wireframes can be configured with this resource. This can be changed at runtime. + .insert_resource(Wireframe2dConfig { + // The global wireframe config enables drawing of wireframes on every mesh, + // except those with `NoWireframe`. Meshes with `Wireframe` will always have a wireframe, + // regardless of the global configuration. + global: true, + // Controls the default color of all wireframes. Used as the default color for global wireframes. + // Can be changed per mesh using the `WireframeColor` component. + default_color: Color::WHITE, + }) .add_systems(Startup, setup) .run(); }