1use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin, MeshMaterial3d};
2use bevy_app::{Plugin, Startup, Update};
3use bevy_asset::{load_internal_asset, Asset, Assets, Handle};
4use bevy_color::{Color, LinearRgba};
5use bevy_ecs::prelude::*;
6use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
7use bevy_render::{
8 extract_resource::ExtractResource,
9 mesh::{Mesh3d, MeshVertexBufferLayoutRef},
10 prelude::*,
11 render_resource::*,
12};
13
14pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(192598014480025766);
15
16#[derive(Debug, Default)]
26pub struct WireframePlugin;
27impl Plugin for WireframePlugin {
28 fn build(&self, app: &mut bevy_app::App) {
29 load_internal_asset!(
30 app,
31 WIREFRAME_SHADER_HANDLE,
32 "render/wireframe.wgsl",
33 Shader::from_wgsl
34 );
35
36 app.register_type::<Wireframe>()
37 .register_type::<NoWireframe>()
38 .register_type::<WireframeConfig>()
39 .register_type::<WireframeColor>()
40 .init_resource::<WireframeConfig>()
41 .add_plugins(MaterialPlugin::<WireframeMaterial>::default())
42 .add_systems(Startup, setup_global_wireframe_material)
43 .add_systems(
44 Update,
45 (
46 global_color_changed.run_if(resource_changed::<WireframeConfig>),
47 wireframe_color_changed,
48 (apply_wireframe_material, apply_global_wireframe_material).chain(),
51 ),
52 );
53 }
54}
55
56#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
61#[reflect(Component, Default, Debug, PartialEq)]
62pub struct Wireframe;
63
64#[derive(Component, Debug, Clone, Default, Reflect)]
74#[reflect(Component, Default, Debug)]
75pub struct WireframeColor {
76 pub color: Color,
77}
78
79#[derive(Component, Debug, Clone, Default, Reflect, Eq, PartialEq)]
84#[reflect(Component, Default, Debug, PartialEq)]
85pub struct NoWireframe;
86
87#[derive(Resource, Debug, Clone, Default, ExtractResource, Reflect)]
88#[reflect(Resource, Debug, Default)]
89pub struct WireframeConfig {
90 pub global: bool,
93 pub default_color: Color,
97}
98
99#[derive(Resource)]
100struct GlobalWireframeMaterial {
101 handle: Handle<WireframeMaterial>,
103}
104
105fn setup_global_wireframe_material(
106 mut commands: Commands,
107 mut materials: ResMut<Assets<WireframeMaterial>>,
108 config: Res<WireframeConfig>,
109) {
110 commands.insert_resource(GlobalWireframeMaterial {
112 handle: materials.add(WireframeMaterial {
113 color: config.default_color.into(),
114 }),
115 });
116}
117
118fn global_color_changed(
120 config: Res<WireframeConfig>,
121 mut materials: ResMut<Assets<WireframeMaterial>>,
122 global_material: Res<GlobalWireframeMaterial>,
123) {
124 if let Some(global_material) = materials.get_mut(&global_material.handle) {
125 global_material.color = config.default_color.into();
126 }
127}
128
129#[allow(clippy::type_complexity)]
131fn wireframe_color_changed(
132 mut materials: ResMut<Assets<WireframeMaterial>>,
133 mut colors_changed: Query<
134 (&mut MeshMaterial3d<WireframeMaterial>, &WireframeColor),
135 (With<Wireframe>, Changed<WireframeColor>),
136 >,
137) {
138 for (mut handle, wireframe_color) in &mut colors_changed {
139 handle.0 = materials.add(WireframeMaterial {
140 color: wireframe_color.color.into(),
141 });
142 }
143}
144
145fn apply_wireframe_material(
148 mut commands: Commands,
149 mut materials: ResMut<Assets<WireframeMaterial>>,
150 wireframes: Query<
151 (Entity, Option<&WireframeColor>),
152 (With<Wireframe>, Without<MeshMaterial3d<WireframeMaterial>>),
153 >,
154 no_wireframes: Query<Entity, (With<NoWireframe>, With<MeshMaterial3d<WireframeMaterial>>)>,
155 mut removed_wireframes: RemovedComponents<Wireframe>,
156 global_material: Res<GlobalWireframeMaterial>,
157) {
158 for e in removed_wireframes.read().chain(no_wireframes.iter()) {
159 if let Some(mut commands) = commands.get_entity(e) {
160 commands.remove::<MeshMaterial3d<WireframeMaterial>>();
161 }
162 }
163
164 let mut material_to_spawn = vec![];
165 for (e, maybe_color) in &wireframes {
166 let material = get_wireframe_material(maybe_color, &mut materials, &global_material);
167 material_to_spawn.push((e, MeshMaterial3d(material)));
168 }
169 commands.insert_or_spawn_batch(material_to_spawn);
170}
171
172type WireframeFilter = (With<Mesh3d>, Without<Wireframe>, Without<NoWireframe>);
173
174fn apply_global_wireframe_material(
176 mut commands: Commands,
177 config: Res<WireframeConfig>,
178 meshes_without_material: Query<
179 (Entity, Option<&WireframeColor>),
180 (WireframeFilter, Without<MeshMaterial3d<WireframeMaterial>>),
181 >,
182 meshes_with_global_material: Query<
183 Entity,
184 (WireframeFilter, With<MeshMaterial3d<WireframeMaterial>>),
185 >,
186 global_material: Res<GlobalWireframeMaterial>,
187 mut materials: ResMut<Assets<WireframeMaterial>>,
188) {
189 if config.global {
190 let mut material_to_spawn = vec![];
191 for (e, maybe_color) in &meshes_without_material {
192 let material = get_wireframe_material(maybe_color, &mut materials, &global_material);
193 material_to_spawn.push((e, MeshMaterial3d(material)));
196 }
197 commands.insert_or_spawn_batch(material_to_spawn);
198 } else {
199 for e in &meshes_with_global_material {
200 commands
201 .entity(e)
202 .remove::<MeshMaterial3d<WireframeMaterial>>();
203 }
204 }
205}
206
207fn get_wireframe_material(
209 maybe_color: Option<&WireframeColor>,
210 wireframe_materials: &mut Assets<WireframeMaterial>,
211 global_material: &GlobalWireframeMaterial,
212) -> Handle<WireframeMaterial> {
213 if let Some(wireframe_color) = maybe_color {
214 wireframe_materials.add(WireframeMaterial {
215 color: wireframe_color.color.into(),
216 })
217 } else {
218 global_material.handle.clone()
220 }
221}
222
223#[derive(Default, AsBindGroup, TypePath, Debug, Clone, Asset)]
224pub struct WireframeMaterial {
225 #[uniform(0)]
226 pub color: LinearRgba,
227}
228
229impl Material for WireframeMaterial {
230 fn fragment_shader() -> ShaderRef {
231 WIREFRAME_SHADER_HANDLE.into()
232 }
233
234 fn specialize(
235 _pipeline: &MaterialPipeline<Self>,
236 descriptor: &mut RenderPipelineDescriptor,
237 _layout: &MeshVertexBufferLayoutRef,
238 _key: MaterialPipelineKey<Self>,
239 ) -> Result<(), SpecializedMeshPipelineError> {
240 descriptor.primitive.polygon_mode = PolygonMode::Line;
241 if let Some(depth_stencil) = descriptor.depth_stencil.as_mut() {
242 depth_stencil.bias.slope_scale = 1.0;
243 }
244 Ok(())
245 }
246}