Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable colors for wireframe #5303

Merged
merged 3 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crates/bevy_pbr/src/render/wireframe.wgsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
struct WireframeMaterial {
color: vec4<f32>,
};

@group(1) @binding(0)
var<uniform> material: WireframeMaterial;
@fragment
fn fragment(in: MeshVertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
return material.color;
}
85 changes: 73 additions & 12 deletions crates/bevy_pbr/src/wireframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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::{Mesh, MeshVertexBufferLayout},
prelude::Shader,
Expand All @@ -25,7 +26,6 @@ pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(19259
/// This is a native only feature.
#[derive(Debug, Default)]
pub struct WireframePlugin;

impl Plugin for WireframePlugin {
fn build(&self, app: &mut bevy_app::App) {
load_internal_asset!(
Expand All @@ -43,7 +43,12 @@ impl Plugin for WireframePlugin {
.add_systems(Startup, setup_global_wireframe_material)
.add_systems(
Update,
(apply_global_wireframe_material, apply_wireframe_material),
(
global_color_changed.run_if(resource_changed::<WireframeConfig>()),
wireframe_color_changed,
apply_wireframe_material,
apply_global_wireframe_material.run_if(resource_changed::<WireframeConfig>()),
),
);
}
}
Expand All @@ -56,6 +61,17 @@ impl Plugin for WireframePlugin {
#[reflect(Component, Default)]
pub struct Wireframe;

/// Sets the color of the [`Wireframe`] of the entity it is attached to.
/// If this component is present but there's no [`Wireframe`] component,
/// it will still affect the color of the wireframe when [`WireframeConfig::global`] is set to true.
///
/// This overrides the [`WireframeConfig::default_color`].
#[derive(Component, Debug, Clone, Default, Reflect)]
#[reflect(Component, Default)]
pub struct WireframeColor {
pub color: Color,
}

/// Disables wireframe rendering for any entity it is attached to.
/// It will ignore the [`WireframeConfig`] global setting.
///
Expand All @@ -70,6 +86,10 @@ pub struct WireframeConfig {
/// Whether to show wireframes for all meshes.
/// Can be overridden for individual meshes by adding a [`Wireframe`] or [`NoWireframe`] component.
pub global: bool,
/// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have
/// wireframes using this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`],
/// but no [`WireframeColor`].
pub default_color: Color,
}

#[derive(Resource)]
Expand All @@ -81,19 +101,53 @@ struct GlobalWireframeMaterial {
fn setup_global_wireframe_material(
mut commands: Commands,
mut materials: ResMut<Assets<WireframeMaterial>>,
config: Res<WireframeConfig>,
) {
// Create the handle used for the global material
commands.insert_resource(GlobalWireframeMaterial {
handle: materials.add(WireframeMaterial {}),
handle: materials.add(WireframeMaterial {
color: config.default_color,
}),
});
}

/// Updates the wireframe material of all entities without a [`WireframeColor`] or without a [`Wireframe`] component
fn global_color_changed(
config: Res<WireframeConfig>,
mut materials: ResMut<Assets<WireframeMaterial>>,
global_material: Res<GlobalWireframeMaterial>,
) {
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 [`WireframeColor`] changes
#[allow(clippy::type_complexity)]
fn wireframe_color_changed(
mut materials: ResMut<Assets<WireframeMaterial>>,
mut colors_changed: Query<
(&mut Handle<WireframeMaterial>, &WireframeColor),
(With<Wireframe>, Changed<WireframeColor>),
>,
) {
for (mut handle, wireframe_color) in &mut colors_changed {
*handle = materials.add(WireframeMaterial {
color: wireframe_color.color,
});
}
}

/// Applies or remove the wireframe material to any mesh with a [`Wireframe`] component.
fn apply_wireframe_material(
mut commands: Commands,
mut materials: ResMut<Assets<WireframeMaterial>>,
wireframes: Query<Entity, (With<Wireframe>, Without<Handle<WireframeMaterial>>)>,
wireframes: Query<
(Entity, Option<&WireframeColor>),
(With<Wireframe>, Without<Handle<WireframeMaterial>>),
>,
mut removed_wireframes: RemovedComponents<Wireframe>,
global_material: Res<GlobalWireframeMaterial>,
) {
for e in removed_wireframes.read() {
if let Some(mut commands) = commands.get_entity(e) {
Expand All @@ -102,8 +156,16 @@ fn apply_wireframe_material(
}

let mut wireframes_to_spawn = vec![];
for e in &wireframes {
wireframes_to_spawn.push((e, materials.add(WireframeMaterial {})));
for (e, wireframe_color) in &wireframes {
let material = if let Some(wireframe_color) = wireframe_color {
materials.add(WireframeMaterial {
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);
}
Expand Down Expand Up @@ -133,10 +195,6 @@ fn apply_global_wireframe_material(
>,
global_material: Res<GlobalWireframeMaterial>,
) {
if !config.is_changed() {
return;
}

if config.global {
let mut material_to_spawn = vec![];
for e in &meshes_without_material {
Expand All @@ -145,7 +203,7 @@ fn apply_global_wireframe_material(
material_to_spawn.push((e, global_material.handle.clone()));
}
commands.insert_or_spawn_batch(material_to_spawn);
} else if !config.global {
} else {
for e in &meshes_with_global_material {
commands.entity(e).remove::<Handle<WireframeMaterial>>();
}
Expand All @@ -154,7 +212,10 @@ fn apply_global_wireframe_material(

#[derive(Default, AsBindGroup, TypeUuid, TypePath, Debug, Clone, Asset)]
#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"]
struct WireframeMaterial {}
pub struct WireframeMaterial {
#[uniform(0)]
pub color: Color,
}

impl Material for WireframeMaterial {
fn fragment_shader() -> ShaderRef {
Expand Down
112 changes: 83 additions & 29 deletions examples/3d/wireframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//! This is a native only feature.

use bevy::{
pbr::wireframe::{NoWireframe, Wireframe, WireframeConfig, WireframePlugin},
pbr::wireframe::{NoWireframe, Wireframe, WireframeColor, WireframeConfig, WireframePlugin},
prelude::*,
render::{
render_resource::WgpuFeatures,
Expand All @@ -31,12 +31,18 @@ fn main() {
// You need to add this plugin to enable wireframe rendering
WireframePlugin,
))
.insert_resource(WireframeToggleTimer(Timer::from_seconds(
1.0,
TimerMode::Repeating,
)))
// Wireframes can be configured with this resource. This can be changed at runtime.
.insert_resource(WireframeConfig {
// 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)
.add_systems(Update, toggle_global_wireframe_setting)
.add_systems(Update, update_colors)
.run();
}

Expand All @@ -54,14 +60,15 @@ fn setup(
});

// Red cube: Never renders a wireframe
commands
.spawn(PbrBundle {
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::RED.into()),
transform: Transform::from_xyz(-1.0, 0.5, -1.0),
..default()
})
.insert(NoWireframe);
},
NoWireframe,
));
// Orange cube: Follows global wireframe setting
commands.spawn(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
Expand All @@ -70,42 +77,89 @@ fn setup(
..default()
});
// Green cube: Always renders a wireframe
commands
.spawn(PbrBundle {
commands.spawn((
PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
material: materials.add(Color::GREEN.into()),
transform: Transform::from_xyz(1.0, 0.5, 1.0),
..default()
})
.insert(Wireframe);
},
Wireframe,
// This lets you configure the wireframe color of this entity.
// If not set, this will use the color in `WireframeConfig`
WireframeColor {
color: Color::GREEN,
},
));

// light
commands.spawn(PointLightBundle {
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});

// camera
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}

/// This timer is used to periodically toggle the wireframe rendering.
#[derive(Resource)]
struct WireframeToggleTimer(Timer);
// Text used to show controls
commands.spawn(
TextBundle::from_section("", TextStyle::default()).with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(10.0),
left: Val::Px(10.0),
..default()
}),
);
}

/// Periodically turns the global wireframe setting on and off, to show the differences between
/// [`Wireframe`], [`NoWireframe`], and just a mesh.
fn toggle_global_wireframe_setting(
time: Res<Time>,
mut timer: ResMut<WireframeToggleTimer>,
mut wireframe_config: ResMut<WireframeConfig>,
/// This system let's you toggle various wireframe settings
fn update_colors(
keyboard_input: Res<Input<KeyCode>>,
mut config: ResMut<WireframeConfig>,
mut wireframe_colors: Query<&mut WireframeColor>,
mut text: Query<&mut Text>,
) {
if timer.0.tick(time.delta()).just_finished() {
// 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.
wireframe_config.global = !wireframe_config.global;
text.single_mut().sections[0].value = format!(
"
Controls
---------------
Z - Toggle global
X - Change global color
C - Change color of the green cube wireframe

WireframeConfig
-------------
Global: {}
Color: {:?}
",
config.global, config.default_color,
);

// Toggle showing a wireframe on all meshes
if keyboard_input.just_pressed(KeyCode::Z) {
config.global = !config.global;
}

// Toggle the global wireframe color
if keyboard_input.just_pressed(KeyCode::X) {
config.default_color = if config.default_color == Color::WHITE {
Color::PINK
} else {
Color::WHITE
};
}

// Toggle the color of a wireframe using WireframeColor and not the global color
if keyboard_input.just_pressed(KeyCode::C) {
for mut color in &mut wireframe_colors {
color.color = if color.color == Color::GREEN {
Color::RED
} else {
Color::GREEN
};
}
}
}