|
1 |
| -use crate::MeshPipeline; |
2 |
| -use crate::{DrawMesh, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup}; |
| 1 | +use crate::{Material, MaterialPipeline, MaterialPipelineKey, MaterialPlugin}; |
3 | 2 | use bevy_app::Plugin;
|
4 |
| -use bevy_asset::{load_internal_asset, Handle, HandleUntyped}; |
5 |
| -use bevy_core_pipeline::core_3d::Opaque3d; |
6 |
| -use bevy_ecs::{prelude::*, reflect::ReflectComponent}; |
7 |
| -use bevy_reflect::std_traits::ReflectDefault; |
| 3 | +use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; |
| 4 | +use bevy_ecs::prelude::*; |
8 | 5 | use bevy_reflect::{Reflect, TypeUuid};
|
9 | 6 | use bevy_render::{
|
10 | 7 | extract_resource::{ExtractResource, ExtractResourcePlugin},
|
11 | 8 | mesh::{Mesh, MeshVertexBufferLayout},
|
12 |
| - render_asset::RenderAssets, |
13 |
| - render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, |
| 9 | + prelude::{Color, Shader}, |
14 | 10 | render_resource::{
|
15 |
| - PipelineCache, PolygonMode, RenderPipelineDescriptor, Shader, SpecializedMeshPipeline, |
16 |
| - SpecializedMeshPipelineError, SpecializedMeshPipelines, |
| 11 | + AsBindGroup, PolygonMode, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError, |
17 | 12 | },
|
18 |
| - view::{ExtractedView, Msaa, VisibleEntities}, |
19 |
| - RenderApp, RenderStage, |
20 | 13 | };
|
21 |
| -use bevy_utils::tracing::error; |
22 | 14 |
|
23 |
| -pub const WIREFRAME_SHADER_HANDLE: HandleUntyped = |
| 15 | +pub const WIREFRAME_MATERIAL_SHADER_HANDLE: HandleUntyped = |
24 | 16 | HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 192598014480025766);
|
25 | 17 |
|
26 |
| -#[derive(Debug, Default)] |
27 |
| -pub struct WireframePlugin; |
| 18 | +/// Configuration resource for [`WireframePlugin`]. |
| 19 | +#[derive(Debug, Clone, Default, ExtractResource, Reflect)] |
| 20 | +#[reflect(Resource)] |
| 21 | +pub struct WireframeConfig { |
| 22 | + /// Whether to show wireframes for all meshes. |
| 23 | + /// If `false`, only meshes with a [Wireframe] component will be rendered. |
| 24 | + pub global: bool, |
| 25 | + /// If [`Self::global`] is set, any [`Entity`] that does not have a [`Wireframe`] component attached to it will have |
| 26 | + /// wireframes in this color. Otherwise, this will be the fallback color for any entity that has a [`Wireframe`], |
| 27 | + /// but no [`WireframeColor`]. |
| 28 | + pub color: Color, |
| 29 | +} |
28 | 30 |
|
| 31 | +/// Toggles wireframe rendering for any entity it is attached to. |
| 32 | +/// |
| 33 | +/// This requires the [`WireframePlugin`] to be enabled. |
| 34 | +#[derive(Component)] |
| 35 | +pub struct Wireframe; |
| 36 | + |
| 37 | +/// Sets the color of the [`Wireframe`] of the entity it is attached to. |
| 38 | +/// |
| 39 | +/// This overrides the [`WireframeConfig::default_color`]. |
| 40 | +#[derive(Component)] |
| 41 | +pub struct WireframeColor { |
| 42 | + pub color: Color, |
| 43 | +} |
| 44 | + |
| 45 | +/// A [`Plugin`] that draws wireframes. |
| 46 | +pub struct WireframePlugin; |
29 | 47 | impl Plugin for WireframePlugin {
|
30 | 48 | fn build(&self, app: &mut bevy_app::App) {
|
31 | 49 | load_internal_asset!(
|
32 | 50 | app,
|
33 |
| - WIREFRAME_SHADER_HANDLE, |
| 51 | + WIREFRAME_MATERIAL_SHADER_HANDLE, |
34 | 52 | "render/wireframe.wgsl",
|
35 | 53 | Shader::from_wgsl
|
36 | 54 | );
|
37 | 55 |
|
| 56 | + app.add_plugin(MaterialPlugin::<WireframeMaterial>::default()); |
| 57 | + |
38 | 58 | app.register_type::<WireframeConfig>()
|
39 | 59 | .init_resource::<WireframeConfig>()
|
40 | 60 | .add_plugin(ExtractResourcePlugin::<WireframeConfig>::default());
|
41 | 61 |
|
42 |
| - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { |
43 |
| - render_app |
44 |
| - .add_render_command::<Opaque3d, DrawWireframes>() |
45 |
| - .init_resource::<WireframePipeline>() |
46 |
| - .init_resource::<SpecializedMeshPipelines<WireframePipeline>>() |
47 |
| - .add_system_to_stage(RenderStage::Extract, extract_wireframes) |
48 |
| - .add_system_to_stage(RenderStage::Queue, queue_wireframes); |
49 |
| - } |
| 62 | + app.add_system(apply_global) |
| 63 | + .add_system(apply_material) |
| 64 | + .add_system(wireframe_color_changed) |
| 65 | + .add_system(global_color_changed); |
50 | 66 | }
|
51 | 67 | }
|
52 | 68 |
|
53 |
| -fn extract_wireframes(mut commands: Commands, query: Query<Entity, With<Wireframe>>) { |
54 |
| - for entity in &query { |
55 |
| - commands.get_or_spawn(entity).insert(Wireframe); |
| 69 | +/// Apply the wireframe material to any mesh with a `Wireframe` component. |
| 70 | +/// Uses `WireframeConfig::color` as a fallback if no `WireframeColor` component is found |
| 71 | +#[allow(clippy::type_complexity)] |
| 72 | +fn apply_material( |
| 73 | + mut commands: Commands, |
| 74 | + config: Res<WireframeConfig>, |
| 75 | + mut materials: ResMut<Assets<WireframeMaterial>>, |
| 76 | + wireframes: Query< |
| 77 | + (Entity, Option<&WireframeColor>), |
| 78 | + (With<Wireframe>, Without<Handle<WireframeMaterial>>), |
| 79 | + >, |
| 80 | +) { |
| 81 | + for (e, color) in &wireframes { |
| 82 | + commands.entity(e).insert(materials.add(WireframeMaterial { |
| 83 | + color: if let Some(wireframe_color) = color { |
| 84 | + wireframe_color.color |
| 85 | + } else { |
| 86 | + config.color |
| 87 | + }, |
| 88 | + })); |
56 | 89 | }
|
57 | 90 | }
|
58 | 91 |
|
59 |
| -/// Controls whether an entity should rendered in wireframe-mode if the [`WireframePlugin`] is enabled |
60 |
| -#[derive(Component, Debug, Clone, Default, Reflect)] |
61 |
| -#[reflect(Component, Default)] |
62 |
| -pub struct Wireframe; |
63 |
| - |
64 |
| -#[derive(Debug, Clone, Default, ExtractResource, Reflect)] |
65 |
| -#[reflect(Resource)] |
66 |
| -pub struct WireframeConfig { |
67 |
| - /// Whether to show wireframes for all meshes. If `false`, only meshes with a [Wireframe] component will be rendered. |
68 |
| - pub global: bool, |
69 |
| -} |
70 |
| - |
71 |
| -pub struct WireframePipeline { |
72 |
| - mesh_pipeline: MeshPipeline, |
73 |
| - shader: Handle<Shader>, |
74 |
| -} |
75 |
| -impl FromWorld for WireframePipeline { |
76 |
| - fn from_world(render_world: &mut World) -> Self { |
77 |
| - WireframePipeline { |
78 |
| - mesh_pipeline: render_world.resource::<MeshPipeline>().clone(), |
79 |
| - shader: WIREFRAME_SHADER_HANDLE.typed(), |
80 |
| - } |
| 92 | +/// Updates the wireframe material when the color in `WireframeColor` changes |
| 93 | +#[allow(clippy::type_complexity)] |
| 94 | +fn wireframe_color_changed( |
| 95 | + mut materials: ResMut<Assets<WireframeMaterial>>, |
| 96 | + mut colors_changed: Query< |
| 97 | + (&mut Handle<WireframeMaterial>, &WireframeColor), |
| 98 | + (With<Wireframe>, Changed<WireframeColor>), |
| 99 | + >, |
| 100 | +) { |
| 101 | + for (mut handle, wireframe_color) in &mut colors_changed { |
| 102 | + *handle = materials.add(WireframeMaterial { |
| 103 | + color: wireframe_color.color, |
| 104 | + }); |
81 | 105 | }
|
82 | 106 | }
|
83 | 107 |
|
84 |
| -impl SpecializedMeshPipeline for WireframePipeline { |
85 |
| - type Key = MeshPipelineKey; |
| 108 | +/// Updates the wireframe material of all entities without a specified color or without a `Wireframe` component |
| 109 | +fn global_color_changed( |
| 110 | + config: Res<WireframeConfig>, |
| 111 | + mut materials: ResMut<Assets<WireframeMaterial>>, |
| 112 | + mut wireframes: Query<&mut Handle<WireframeMaterial>, Without<WireframeColor>>, |
| 113 | +) { |
| 114 | + if !config.is_changed() { |
| 115 | + return; |
| 116 | + } |
86 | 117 |
|
87 |
| - fn specialize( |
88 |
| - &self, |
89 |
| - key: Self::Key, |
90 |
| - layout: &MeshVertexBufferLayout, |
91 |
| - ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> { |
92 |
| - let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; |
93 |
| - descriptor.vertex.shader = self.shader.clone_weak(); |
94 |
| - descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak(); |
95 |
| - descriptor.primitive.polygon_mode = PolygonMode::Line; |
96 |
| - descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; |
97 |
| - Ok(descriptor) |
| 118 | + for mut handle in &mut wireframes { |
| 119 | + *handle = materials.add(WireframeMaterial { |
| 120 | + color: config.color, |
| 121 | + }); |
98 | 122 | }
|
99 | 123 | }
|
100 | 124 |
|
101 |
| -#[allow(clippy::too_many_arguments)] |
102 |
| -fn queue_wireframes( |
103 |
| - opaque_3d_draw_functions: Res<DrawFunctions<Opaque3d>>, |
104 |
| - render_meshes: Res<RenderAssets<Mesh>>, |
105 |
| - wireframe_config: Res<WireframeConfig>, |
106 |
| - wireframe_pipeline: Res<WireframePipeline>, |
107 |
| - mut pipelines: ResMut<SpecializedMeshPipelines<WireframePipeline>>, |
108 |
| - mut pipeline_cache: ResMut<PipelineCache>, |
109 |
| - msaa: Res<Msaa>, |
110 |
| - mut material_meshes: ParamSet<( |
111 |
| - Query<(Entity, &Handle<Mesh>, &MeshUniform)>, |
112 |
| - Query<(Entity, &Handle<Mesh>, &MeshUniform), With<Wireframe>>, |
| 125 | +/// Applies or remove a wireframe material on any mesh without a `Wireframe` component. |
| 126 | +#[allow(clippy::type_complexity)] |
| 127 | +fn apply_global( |
| 128 | + mut commands: Commands, |
| 129 | + config: Res<WireframeConfig>, |
| 130 | + mut materials: ResMut<Assets<WireframeMaterial>>, |
| 131 | + mut q1: ParamSet<( |
| 132 | + Query< |
| 133 | + Entity, |
| 134 | + ( |
| 135 | + With<Handle<Mesh>>, |
| 136 | + Without<Handle<WireframeMaterial>>, |
| 137 | + Without<Wireframe>, |
| 138 | + ), |
| 139 | + >, |
| 140 | + Query< |
| 141 | + Entity, |
| 142 | + ( |
| 143 | + With<Handle<Mesh>>, |
| 144 | + With<Handle<WireframeMaterial>>, |
| 145 | + Without<Wireframe>, |
| 146 | + ), |
| 147 | + >, |
113 | 148 | )>,
|
114 |
| - mut views: Query<(&ExtractedView, &VisibleEntities, &mut RenderPhase<Opaque3d>)>, |
| 149 | + mut is_global_applied: Local<bool>, |
115 | 150 | ) {
|
116 |
| - let draw_custom = opaque_3d_draw_functions |
117 |
| - .read() |
118 |
| - .get_id::<DrawWireframes>() |
119 |
| - .unwrap(); |
120 |
| - let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples); |
121 |
| - for (view, visible_entities, mut opaque_phase) in &mut views { |
122 |
| - let rangefinder = view.rangefinder3d(); |
123 |
| - |
124 |
| - let add_render_phase = |
125 |
| - |(entity, mesh_handle, mesh_uniform): (Entity, &Handle<Mesh>, &MeshUniform)| { |
126 |
| - if let Some(mesh) = render_meshes.get(mesh_handle) { |
127 |
| - let key = msaa_key |
128 |
| - | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); |
129 |
| - let pipeline_id = pipelines.specialize( |
130 |
| - &mut pipeline_cache, |
131 |
| - &wireframe_pipeline, |
132 |
| - key, |
133 |
| - &mesh.layout, |
134 |
| - ); |
135 |
| - let pipeline_id = match pipeline_id { |
136 |
| - Ok(id) => id, |
137 |
| - Err(err) => { |
138 |
| - error!("{}", err); |
139 |
| - return; |
140 |
| - } |
141 |
| - }; |
142 |
| - opaque_phase.add(Opaque3d { |
143 |
| - entity, |
144 |
| - pipeline: pipeline_id, |
145 |
| - draw_function: draw_custom, |
146 |
| - distance: rangefinder.distance(&mesh_uniform.transform), |
147 |
| - }); |
148 |
| - } |
149 |
| - }; |
150 |
| - |
151 |
| - if wireframe_config.global { |
152 |
| - let query = material_meshes.p0(); |
153 |
| - visible_entities |
154 |
| - .entities |
155 |
| - .iter() |
156 |
| - .filter_map(|visible_entity| query.get(*visible_entity).ok()) |
157 |
| - .for_each(add_render_phase); |
158 |
| - } else { |
159 |
| - let query = material_meshes.p1(); |
160 |
| - visible_entities |
161 |
| - .entities |
162 |
| - .iter() |
163 |
| - .filter_map(|visible_entity| query.get(*visible_entity).ok()) |
164 |
| - .for_each(add_render_phase); |
| 151 | + if !config.is_changed() { |
| 152 | + return; |
| 153 | + } |
| 154 | + |
| 155 | + if !*is_global_applied && config.global { |
| 156 | + let global_material = materials.add(WireframeMaterial { |
| 157 | + color: config.color, |
| 158 | + }); |
| 159 | + |
| 160 | + for e in &mut q1.p0() { |
| 161 | + commands.entity(e).insert(global_material.clone()); |
165 | 162 | }
|
| 163 | + |
| 164 | + *is_global_applied = true; |
| 165 | + } else if *is_global_applied && !config.global { |
| 166 | + for e in &mut q1.p1() { |
| 167 | + commands.entity(e).remove::<Handle<WireframeMaterial>>(); |
| 168 | + } |
| 169 | + *is_global_applied = false; |
166 | 170 | }
|
167 | 171 | }
|
168 | 172 |
|
169 |
| -type DrawWireframes = ( |
170 |
| - SetItemPipeline, |
171 |
| - SetMeshViewBindGroup<0>, |
172 |
| - SetMeshBindGroup<1>, |
173 |
| - DrawMesh, |
174 |
| -); |
| 173 | +#[derive(Default, AsBindGroup, TypeUuid, Debug, Clone)] |
| 174 | +#[uuid = "9e694f70-9963-4418-8bc1-3474c66b13b8"] |
| 175 | +struct WireframeMaterial { |
| 176 | + #[uniform(0)] |
| 177 | + color: Color, |
| 178 | +} |
| 179 | + |
| 180 | +impl Material for WireframeMaterial { |
| 181 | + fn fragment_shader() -> ShaderRef { |
| 182 | + WIREFRAME_MATERIAL_SHADER_HANDLE.typed().into() |
| 183 | + } |
| 184 | + |
| 185 | + fn specialize( |
| 186 | + _pipeline: &MaterialPipeline<Self>, |
| 187 | + descriptor: &mut RenderPipelineDescriptor, |
| 188 | + _layout: &MeshVertexBufferLayout, |
| 189 | + _key: MaterialPipelineKey<Self>, |
| 190 | + ) -> Result<(), SpecializedMeshPipelineError> { |
| 191 | + descriptor.primitive.polygon_mode = PolygonMode::Line; |
| 192 | + descriptor.depth_stencil.as_mut().unwrap().bias.slope_scale = 1.0; |
| 193 | + Ok(()) |
| 194 | + } |
| 195 | +} |
0 commit comments