From 92bfe588ffe61083e4c26c2af1c4da50d66bff8b Mon Sep 17 00:00:00 2001 From: devil-ira Date: Sat, 29 Oct 2022 13:35:47 +0200 Subject: [PATCH 01/66] sordid --- Cargo.toml | 2 + crates/bevy_debug_draw/Cargo.toml | 31 +++ crates/bevy_debug_draw/src/debuglines.wgsl | 44 ++++ crates/bevy_debug_draw/src/lib.rs | 215 ++++++++++++++++++++ crates/bevy_debug_draw/src/pipeline_2d.rs | 140 +++++++++++++ crates/bevy_debug_draw/src/pipeline_3d.rs | 169 +++++++++++++++ crates/bevy_internal/Cargo.toml | 8 +- crates/bevy_internal/src/default_plugins.rs | 5 + crates/bevy_internal/src/lib.rs | 6 + crates/bevy_internal/src/prelude.rs | 4 + 10 files changed, 622 insertions(+), 2 deletions(-) create mode 100644 crates/bevy_debug_draw/Cargo.toml create mode 100644 crates/bevy_debug_draw/src/debuglines.wgsl create mode 100644 crates/bevy_debug_draw/src/lib.rs create mode 100644 crates/bevy_debug_draw/src/pipeline_2d.rs create mode 100644 crates/bevy_debug_draw/src/pipeline_3d.rs diff --git a/Cargo.toml b/Cargo.toml index c6ec1f14d6bd0..978385d364ee2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ default = [ "vorbis", "x11", "filesystem_watcher", + "bevy_debug_draw", ] # Force dynamic linking, which improves iterative compile times @@ -69,6 +70,7 @@ bevy_sprite = ["bevy_internal/bevy_sprite"] bevy_text = ["bevy_internal/bevy_text"] bevy_ui = ["bevy_internal/bevy_ui"] bevy_winit = ["bevy_internal/bevy_winit"] +bevy_debug_draw = ["bevy_internal/bevy_debug_draw"] # Tracing features trace_chrome = ["trace", "bevy_internal/trace_chrome"] diff --git a/crates/bevy_debug_draw/Cargo.toml b/crates/bevy_debug_draw/Cargo.toml new file mode 100644 index 0000000000000..9be2c80cf8404 --- /dev/null +++ b/crates/bevy_debug_draw/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bevy_debug_draw" +version = "0.9.0-dev" +edition = "2021" +description = "Provides debug drawing for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[features] +default = ["2d", "3d"] + +3d = ["bevy_pbr"] +2d = ["bevy_sprite"] + +[dependencies] +bevy_pbr = { path = "../bevy_pbr", version = "0.9.0-dev", optional = true } +bevy_sprite = { path = "../bevy_sprite", version = "0.9.0-dev", optional = true } +bevy_app = { path = "../bevy_app", version = "0.9.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev" } +bevy_transform = { path = "../bevy_transform", version = "0.9.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.9.0-dev" } +bevy_asset = { path = "../bevy_asset", version = "0.9.0-dev" } +bevy_render = { path = "../bevy_render", version = "0.9.0-dev" } +bevy_log = { path = "../bevy_log", version = "0.9.0-dev" } +bevy_time = { path = "../bevy_time", version = "0.9.0-dev" } +bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" } +bevy_core = { path = "../bevy_core", version = "0.9.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.9.0-dev" } \ No newline at end of file diff --git a/crates/bevy_debug_draw/src/debuglines.wgsl b/crates/bevy_debug_draw/src/debuglines.wgsl new file mode 100644 index 0000000000000..668e5dfad8a0f --- /dev/null +++ b/crates/bevy_debug_draw/src/debuglines.wgsl @@ -0,0 +1,44 @@ +#ifdef DEBUG_LINES_3D + #import bevy_pbr::mesh_view_bindings +#else + #import bevy_sprite::mesh2d_view_bindings +#endif + +struct VertexInput { + @location(0) pos: vec3, + @location(1) color: vec4, +} + +struct VertexOutput { + @builtin(position) pos: vec4, + @location(0) color: vec4, +} + +struct FragmentOutput { + @builtin(frag_depth) depth: f32, + @location(0) color: vec4, +} + +@vertex +fn vertex(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + + out.pos = view.view_proj * vec4(in.pos, 1.0); + out.color = in.color; + + return out; +} + +@fragment +fn fragment(in: VertexOutput) -> FragmentOutput { + var out: FragmentOutput; + +#ifdef DEPTH_TEST_ENABLED + out.depth = in.pos.z; +#else + out.depth = 1.0; +#endif + + out.color = in.color; + return out; +} diff --git a/crates/bevy_debug_draw/src/lib.rs b/crates/bevy_debug_draw/src/lib.rs new file mode 100644 index 0000000000000..20e19873bedef --- /dev/null +++ b/crates/bevy_debug_draw/src/lib.rs @@ -0,0 +1,215 @@ +use std::{marker::PhantomData, mem}; + +use bevy_app::{CoreStage, Plugin}; +use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; +use bevy_ecs::{ + prelude::{Component, Entity}, + query::With, + system::{Commands, Query, Res, ResMut, Resource, SystemParam}, +}; +use bevy_math::{Vec2, Vec3}; +use bevy_reflect::TypeUuid; +use bevy_render::{ + prelude::{Color, Mesh, SpatialBundle}, + render_phase::AddRenderCommand, + render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines}, + Extract, RenderApp, RenderStage, +}; + +#[cfg(feature = "3d")] +use bevy_pbr::{NotShadowCaster, NotShadowReceiver}; +#[cfg(feature = "2d")] +use bevy_sprite::Mesh2dHandle; + +#[cfg(feature = "2d")] +pub mod pipeline_2d; +#[cfg(feature = "3d")] +pub mod pipeline_3d; + +/// The `bevy_debug_draw` prelude. +pub mod prelude { + #[doc(hidden)] + pub use crate::{DebugDraw, DebugDraw2d, DebugDrawConfig, DebugDrawPlugin}; +} + +pub const SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7414812689238026784); + +pub struct DebugDrawPlugin; + +impl Plugin for DebugDrawPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.init_resource::() + .init_resource::() + .add_system_to_stage(CoreStage::PostUpdate, update) + .sub_app_mut(RenderApp) + .add_system_to_stage(RenderStage::Extract, extract); + + #[cfg(feature = "2d")] + { + use bevy_core_pipeline::core_2d::Transparent2d; + use pipeline_2d::*; + + app.sub_app_mut(RenderApp) + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Queue, queue); + } + + #[cfg(feature = "3d")] + { + use bevy_core_pipeline::core_3d::Opaque3d; + use pipeline_3d::*; + + app.sub_app_mut(RenderApp) + .add_render_command::() + .init_resource::() + .init_resource::>() + .add_system_to_stage(RenderStage::Queue, queue); + } + + load_internal_asset!(app, SHADER_HANDLE, "debuglines.wgsl", Shader::from_wgsl); + } +} + +#[derive(Resource, Clone, Copy)] +pub struct DebugDrawConfig { + /// Whether debug drawing should ignore depth and draw on top of everything else. + /// + /// Defaults to `true`. + pub always_on_top: bool, +} + +impl Default for DebugDrawConfig { + fn default() -> Self { + Self { + always_on_top: true, + } + } +} + +#[derive(Resource, Default)] +pub struct DebugDrawResource { + positions: Vec<[f32; 3]>, + colors: Vec<[f32; 4]>, + mesh_handle: Option>, +} + +impl DebugDrawResource { + /// Draw a line from `start` to `end`. + fn line(&mut self, start: Vec3, end: Vec3, color: Color) { + self.positions + .extend_from_slice(&[start.to_array(), end.to_array()]); + let color = color.as_linear_rgba_f32(); + self.colors.extend_from_slice(&[color, color]); + } + + /// Draw a line from `start` to `start + vector`. + fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) { + self.line(start, start + vector, color); + } + + fn clear(&mut self) { + self.positions.clear(); + self.colors.clear(); + } + + fn update_mesh(&mut self, mesh: &mut Mesh) { + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, mem::take(&mut self.positions)); + mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, mem::take(&mut self.colors)); + } +} + +#[derive(SystemParam)] +pub struct DebugDraw<'w, 's> { + debug_draw: ResMut<'w, DebugDrawResource>, + #[system_param(ignore)] + marker: PhantomData<&'s ()>, +} + +impl<'w, 's> DebugDraw<'w, 's> { + /// Draw a line from `start` to `end`. + pub fn line(&mut self, start: Vec3, end: Vec3, color: Color) { + self.debug_draw.line(start, end, color); + } + + /// Draw a line from `start` to `start + vector`. + pub fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) { + self.debug_draw.ray(start, vector, color); + } + + pub fn clear(&mut self) { + self.debug_draw.clear(); + } +} + +#[derive(SystemParam)] +pub struct DebugDraw2d<'w, 's> { + debug_draw: ResMut<'w, DebugDrawResource>, + #[system_param(ignore)] + marker: PhantomData<&'s ()>, +} + +impl<'w, 's> DebugDraw2d<'w, 's> { + /// Draw a line from `start` to `end`. + pub fn line(&mut self, start: Vec2, end: Vec2, color: Color) { + self.debug_draw + .line(start.extend(0.), end.extend(0.), color); + } + + /// Draw a line from `start` to `start + vector`. + pub fn ray(&mut self, start: Vec2, vector: Vec2, color: Color) { + self.debug_draw + .ray(start.extend(0.), vector.extend(0.), color); + } + + pub fn clear(&mut self) { + self.debug_draw.clear(); + } +} + +#[derive(Component)] +pub struct DebugDrawMesh; + +pub(crate) fn update( + mut draw: ResMut, + mut meshes: ResMut>, + mut commands: Commands, +) { + if let Some(mut mesh) = draw + .mesh_handle + .as_ref() + .and_then(|handle| meshes.get_mut(handle)) + { + draw.update_mesh(&mut mesh); + } else { + let mut mesh = Mesh::new(PrimitiveTopology::LineList); + draw.update_mesh(&mut mesh); + let mesh_handle = meshes.add(mesh); + commands.spawn(( + SpatialBundle::VISIBLE_IDENTITY, + DebugDrawMesh, + #[cfg(feature = "3d")] + (mesh_handle.clone_weak(), NotShadowCaster, NotShadowReceiver), + #[cfg(feature = "2d")] + Mesh2dHandle(mesh_handle.clone_weak()), + )); + draw.mesh_handle = Some(mesh_handle); + } +} + +/// Move the DebugDrawMesh marker Component to the render context. +pub(crate) fn extract( + mut commands: Commands, + query: Extract>>, + config: Extract>, +) { + for entity in &query { + commands.get_or_spawn(entity).insert(DebugDrawMesh); + } + + if config.is_changed() { + commands.insert_resource(**config); + } +} diff --git a/crates/bevy_debug_draw/src/pipeline_2d.rs b/crates/bevy_debug_draw/src/pipeline_2d.rs new file mode 100644 index 0000000000000..b895c0e4651e9 --- /dev/null +++ b/crates/bevy_debug_draw/src/pipeline_2d.rs @@ -0,0 +1,140 @@ +use bevy_asset::Handle; +use bevy_core_pipeline::core_2d::Transparent2d; +use bevy_ecs::{ + query::With, + system::{Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_render::{ + mesh::{Mesh, MeshVertexBufferLayout}, + render_asset::RenderAssets, + render_phase::{DrawFunctions, RenderPhase, SetItemPipeline}, + render_resource::*, + texture::BevyDefault, + view::{Msaa, VisibleEntities}, +}; +use bevy_sprite::*; +use bevy_utils::FloatOrd; + +use crate::{DebugDrawConfig, DebugDrawMesh, SHADER_HANDLE}; + +#[derive(Resource)] +pub(crate) struct DebugLinePipeline { + mesh_pipeline: Mesh2dPipeline, + shader: Handle, +} +impl FromWorld for DebugLinePipeline { + fn from_world(render_world: &mut World) -> Self { + DebugLinePipeline { + mesh_pipeline: render_world + .get_resource::() + .unwrap() + .clone(), + shader: SHADER_HANDLE.typed(), + } + } +} + +impl SpecializedMeshPipeline for DebugLinePipeline { + type Key = Mesh2dPipelineKey; + + fn specialize( + &self, + key: Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + let vertex_buffer_layout = layout.get_layout(&[ + Mesh::ATTRIBUTE_POSITION.at_shader_location(0), + Mesh::ATTRIBUTE_COLOR.at_shader_location(1), + ])?; + + Ok(RenderPipelineDescriptor { + vertex: VertexState { + shader: self.shader.clone_weak(), + entry_point: "vertex".into(), + shader_defs: vec![], + buffers: vec![vertex_buffer_layout], + }, + fragment: Some(FragmentState { + shader: self.shader.clone_weak(), + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::bevy_default(), + blend: Some(BlendState::ALPHA_BLENDING), + write_mask: ColorWrites::ALL, + })], + }), + layout: Some(vec![self.mesh_pipeline.view_layout.clone()]), + primitive: PrimitiveState { + front_face: FrontFace::Ccw, + cull_mode: None, + unclipped_depth: false, + polygon_mode: PolygonMode::Fill, + conservative: false, + topology: PrimitiveTopology::LineList, + strip_index_format: None, + }, + depth_stencil: None, + multisample: MultisampleState { + count: key.msaa_samples(), + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: None, + }) + } +} + +pub(crate) type DrawDebugLines = ( + SetItemPipeline, + SetMesh2dViewBindGroup<0>, + SetMesh2dBindGroup<1>, + DrawMesh2d, +); + +pub(crate) fn queue( + config: Res, + draw2d_functions: Res>, + debug_line_pipeline: Res, + mut pipeline_cache: ResMut, + mut specialized_pipelines: ResMut>, + render_meshes: Res>, + msaa: Res, + material_meshes: Query<(&Mesh2dUniform, &Mesh2dHandle), With>, + mut views: Query<(&VisibleEntities, &mut RenderPhase)>, +) { + for (view, mut phase) in &mut views { + let draw_mesh2d = draw2d_functions.read().get_id::().unwrap(); + let msaa_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples); + + for visible_entity in &view.entities { + if let Ok((uniform, mesh_handle)) = material_meshes.get(*visible_entity) { + if let Some(mesh) = render_meshes.get(&mesh_handle.0) { + let mesh_key = msaa_key + | Mesh2dPipelineKey::from_primitive_topology(PrimitiveTopology::LineList); + let mesh_z = if config.always_on_top { + f32::MAX + } else { + uniform.transform.w_axis.z + }; + let pipeline = specialized_pipelines + .specialize( + &mut pipeline_cache, + &debug_line_pipeline, + mesh_key, + &mesh.layout, + ) + .unwrap(); + phase.add(Transparent2d { + entity: *visible_entity, + draw_function: draw_mesh2d, + pipeline, + sort_key: FloatOrd(mesh_z), + batch_range: None, + }); + } + } + } + } +} diff --git a/crates/bevy_debug_draw/src/pipeline_3d.rs b/crates/bevy_debug_draw/src/pipeline_3d.rs new file mode 100644 index 0000000000000..ae755e21a2b27 --- /dev/null +++ b/crates/bevy_debug_draw/src/pipeline_3d.rs @@ -0,0 +1,169 @@ +use bevy_asset::Handle; +use bevy_core_pipeline::core_3d::Opaque3d; +use bevy_ecs::{ + entity::Entity, + query::With, + system::{Query, Res, ResMut, Resource}, + world::{FromWorld, World}, +}; +use bevy_pbr::*; +use bevy_render::{mesh::Mesh, render_resource::Shader}; +use bevy_render::{ + mesh::MeshVertexBufferLayout, + render_asset::RenderAssets, + render_phase::{DrawFunctions, RenderPhase, SetItemPipeline}, + render_resource::*, + texture::BevyDefault, + view::{ExtractedView, Msaa}, +}; + +use crate::{DebugDrawConfig, DebugDrawMesh, SHADER_HANDLE}; + +#[derive(Resource)] +pub(crate) struct DebugLinePipeline { + mesh_pipeline: MeshPipeline, + shader: Handle, +} + +impl FromWorld for DebugLinePipeline { + fn from_world(render_world: &mut World) -> Self { + DebugLinePipeline { + mesh_pipeline: render_world.get_resource::().unwrap().clone(), + shader: SHADER_HANDLE.typed(), + } + } +} + +impl SpecializedMeshPipeline for DebugLinePipeline { + type Key = (bool, MeshPipelineKey); + + fn specialize( + &self, + (depth_test, key): Self::Key, + layout: &MeshVertexBufferLayout, + ) -> Result { + let mut shader_defs = Vec::new(); + shader_defs.push("DEBUG_LINES_3D".to_string()); + if depth_test { + shader_defs.push("DEPTH_TEST_ENABLED".to_string()); + } + + let vertex_buffer_layout = layout.get_layout(&[ + Mesh::ATTRIBUTE_POSITION.at_shader_location(0), + Mesh::ATTRIBUTE_COLOR.at_shader_location(1), + ])?; + let (label, blend, depth_write_enabled); + if key.contains(MeshPipelineKey::TRANSPARENT_MAIN_PASS) { + label = "transparent_mesh_pipeline".into(); + blend = Some(BlendState::ALPHA_BLENDING); + // For the transparent pass, fragments that are closer will be alpha + // blended but their depth is not written to the depth buffer. + depth_write_enabled = false; + } else { + label = "opaque_mesh_pipeline".into(); + blend = Some(BlendState::REPLACE); + // For the opaque and alpha mask passes, fragments that are closer + // will replace the current fragment value in the output and the depth is + // written to the depth buffer. + depth_write_enabled = true; + } + + Ok(RenderPipelineDescriptor { + vertex: VertexState { + shader: self.shader.clone_weak(), + entry_point: "vertex".into(), + shader_defs: shader_defs.clone(), + buffers: vec![vertex_buffer_layout], + }, + fragment: Some(FragmentState { + shader: self.shader.clone_weak(), + shader_defs, + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::bevy_default(), + blend, + write_mask: ColorWrites::ALL, + })], + }), + layout: Some(vec![self.mesh_pipeline.view_layout.clone()]), + primitive: PrimitiveState { + front_face: FrontFace::Ccw, + cull_mode: None, + unclipped_depth: false, + polygon_mode: PolygonMode::Fill, + conservative: false, + topology: PrimitiveTopology::LineList, + strip_index_format: None, + }, + depth_stencil: Some(DepthStencilState { + format: TextureFormat::Depth32Float, + depth_write_enabled, + depth_compare: CompareFunction::Greater, + stencil: StencilState { + front: StencilFaceState::IGNORE, + back: StencilFaceState::IGNORE, + read_mask: 0, + write_mask: 0, + }, + bias: DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: MultisampleState { + count: key.msaa_samples(), + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some(label), + }) + } +} + +pub(crate) type DrawDebugLines = ( + SetItemPipeline, + SetMeshViewBindGroup<0>, + SetMeshBindGroup<1>, + DrawMesh, +); + +pub(crate) fn queue( + opaque_3d_draw_functions: Res>, + debug_line_pipeline: Res, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, + render_meshes: Res>, + msaa: Res, + material_meshes: Query<(Entity, &MeshUniform, &Handle), With>, + config: Res, + mut views: Query<(&ExtractedView, &mut RenderPhase)>, +) { + let draw_custom = opaque_3d_draw_functions + .read() + .get_id::() + .unwrap(); + let key = MeshPipelineKey::from_msaa_samples(msaa.samples); + for (view, mut phase) in &mut views { + let view_matrix = view.transform.compute_matrix(); + let view_row_2 = view_matrix.row(2); + for (entity, mesh_uniform, mesh_handle) in &material_meshes { + if let Some(mesh) = render_meshes.get(mesh_handle) { + let pipeline = pipelines + .specialize( + &mut pipeline_cache, + &debug_line_pipeline, + (!config.always_on_top, key), + &mesh.layout, + ) + .unwrap(); + phase.add(Opaque3d { + entity, + pipeline, + draw_function: draw_custom, + distance: view_row_2.dot(mesh_uniform.transform.col(3)), + }); + } + } + } +} diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index ac371a3d4a82a..91d73b838c56d 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -59,11 +59,14 @@ subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"] webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"] # enable systems that allow for automated testing on CI -bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"] +bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render?/ci_limits"] # Enable animation support, and glTF animation loading animation = ["bevy_animation", "bevy_gltf?/bevy_animation"] +bevy_sprite = ["dep:bevy_sprite", "bevy_debug_draw?/2d"] +bevy_pbr = ["dep:bevy_pbr", "bevy_debug_draw?/3d"] + [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.9.0" } @@ -97,8 +100,9 @@ bevy_text = { path = "../bevy_text", optional = true, version = "0.9.0" } bevy_ui = { path = "../bevy_ui", optional = true, version = "0.9.0" } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.9.0" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.9.0" } +bevy_debug_draw = { path = "../bevy_debug_draw", optional = true, version = "0.9.0", default-features = false } [target.'cfg(target_os = "android")'.dependencies] # This version *must* be the same as the version used by winit, # or Android will break: https://github.com/rust-windowing/winit#android -ndk-glue = {version = "0.7", features = ["logger"]} +ndk-glue = { version = "0.7", features = ["logger"] } diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 68dbc7ac89fba..979a29d622a01 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -113,6 +113,11 @@ impl PluginGroup for DefaultPlugins { group = group.add(bevy_animation::AnimationPlugin::default()); } + #[cfg(feature = "bevy_debug_draw")] + { + group = group.add(bevy_debug_draw::DebugDrawPlugin); + } + group } } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 90482b3e3615b..71702c798f55d 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -167,6 +167,12 @@ pub mod winit { pub use bevy_winit::*; } +#[cfg(feature = "bevy_debug_draw")] +pub mod debug_draw { + //! Immediate mode debug drawing. + pub use bevy_debug_draw::*; +} + #[cfg(feature = "bevy_dynamic_plugin")] pub mod dynamic_plugin { //! Dynamic linking of plugins diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index cc3614544d0bb..70b8d23a5c482 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -51,6 +51,10 @@ pub use crate::ui::prelude::*; #[cfg(feature = "bevy_dynamic_plugin")] pub use crate::dynamic_plugin::*; +#[doc(hidden)] +#[cfg(feature = "bevy_debug_draw")] +pub use crate::debug_draw::prelude::*; + #[doc(hidden)] #[cfg(feature = "bevy_gilrs")] pub use crate::gilrs::*; From 1bffe970453faf67706c779e6773774810712d82 Mon Sep 17 00:00:00 2001 From: devil-ira Date: Sun, 30 Oct 2022 14:06:37 +0100 Subject: [PATCH 02/66] Update API. Add basic shapes. --- Cargo.toml | 20 +++ crates/bevy_debug_draw/Cargo.toml | 6 - crates/bevy_debug_draw/src/lib.rs | 210 ++++++++++++++++++++---------- crates/bevy_internal/Cargo.toml | 4 +- examples/2d/2d_debug_draw.rs | 33 +++++ examples/3d/3d_debug_draw.rs | 50 +++++++ 6 files changed, 247 insertions(+), 76 deletions(-) create mode 100644 examples/2d/2d_debug_draw.rs create mode 100644 examples/3d/3d_debug_draw.rs diff --git a/Cargo.toml b/Cargo.toml index 978385d364ee2..7ddb368fa15e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -205,6 +205,16 @@ description = "Renders a rectangle, circle, and hexagon" category = "2D Rendering" wasm = true +[[example]] +name = "2d_debug_draw" +path = "examples/2d/2d_debug_draw.rs" + +[package.metadata.example.2d_debug_draw] +name = "2D Debug Drawing" +description = "Simple 2D scene with basic shapes and lighting" +category = "2D Rendering" +wasm = true + [[example]] name = "sprite" path = "examples/2d/sprite.rs" @@ -296,6 +306,16 @@ description = "A scene showcasing the built-in 3D shapes" category = "3D Rendering" wasm = true +[[example]] +name = "3d_debug_draw" +path = "examples/3d/3d_debug_draw.rs" + +[package.metadata.example.3d_debug_draw] +name = "3D Debug Drawing" +description = "Simple 3D scene with basic shapes and lighting" +category = "3D Rendering" +wasm = true + [[example]] name = "lighting" path = "examples/3d/lighting.rs" diff --git a/crates/bevy_debug_draw/Cargo.toml b/crates/bevy_debug_draw/Cargo.toml index 9be2c80cf8404..36811715378ef 100644 --- a/crates/bevy_debug_draw/Cargo.toml +++ b/crates/bevy_debug_draw/Cargo.toml @@ -8,12 +8,6 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT OR Apache-2.0" keywords = ["bevy"] -[features] -default = ["2d", "3d"] - -3d = ["bevy_pbr"] -2d = ["bevy_sprite"] - [dependencies] bevy_pbr = { path = "../bevy_pbr", version = "0.9.0-dev", optional = true } bevy_sprite = { path = "../bevy_sprite", version = "0.9.0-dev", optional = true } diff --git a/crates/bevy_debug_draw/src/lib.rs b/crates/bevy_debug_draw/src/lib.rs index 20e19873bedef..5a1e22610ce20 100644 --- a/crates/bevy_debug_draw/src/lib.rs +++ b/crates/bevy_debug_draw/src/lib.rs @@ -1,13 +1,13 @@ -use std::{marker::PhantomData, mem}; +use std::f32::consts::TAU; use bevy_app::{CoreStage, Plugin}; use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped}; use bevy_ecs::{ prelude::{Component, Entity}, query::With, - system::{Commands, Query, Res, ResMut, Resource, SystemParam}, + system::{Commands, Query, Res, ResMut, Resource}, }; -use bevy_math::{Vec2, Vec3}; +use bevy_math::{vec3, Quat, Vec2, Vec3}; use bevy_reflect::TypeUuid; use bevy_render::{ prelude::{Color, Mesh, SpatialBundle}, @@ -16,20 +16,20 @@ use bevy_render::{ Extract, RenderApp, RenderStage, }; -#[cfg(feature = "3d")] +#[cfg(feature = "bevy_pbr")] use bevy_pbr::{NotShadowCaster, NotShadowReceiver}; -#[cfg(feature = "2d")] +#[cfg(feature = "bevy_sprite")] use bevy_sprite::Mesh2dHandle; -#[cfg(feature = "2d")] +#[cfg(feature = "bevy_sprite")] pub mod pipeline_2d; -#[cfg(feature = "3d")] +#[cfg(feature = "bevy_pbr")] pub mod pipeline_3d; /// The `bevy_debug_draw` prelude. pub mod prelude { #[doc(hidden)] - pub use crate::{DebugDraw, DebugDraw2d, DebugDrawConfig, DebugDrawPlugin}; + pub use crate::{DebugDraw, DebugDrawConfig, DebugDrawPlugin}; } pub const SHADER_HANDLE: HandleUntyped = @@ -39,13 +39,15 @@ pub struct DebugDrawPlugin; impl Plugin for DebugDrawPlugin { fn build(&self, app: &mut bevy_app::App) { - app.init_resource::() + load_internal_asset!(app, SHADER_HANDLE, "debuglines.wgsl", Shader::from_wgsl); + + app.init_resource::() .init_resource::() .add_system_to_stage(CoreStage::PostUpdate, update) .sub_app_mut(RenderApp) .add_system_to_stage(RenderStage::Extract, extract); - #[cfg(feature = "2d")] + #[cfg(feature = "bevy_sprite")] { use bevy_core_pipeline::core_2d::Transparent2d; use pipeline_2d::*; @@ -57,7 +59,7 @@ impl Plugin for DebugDrawPlugin { .add_system_to_stage(RenderStage::Queue, queue); } - #[cfg(feature = "3d")] + #[cfg(feature = "bevy_pbr")] { use bevy_core_pipeline::core_3d::Opaque3d; use pipeline_3d::*; @@ -68,13 +70,15 @@ impl Plugin for DebugDrawPlugin { .init_resource::>() .add_system_to_stage(RenderStage::Queue, queue); } - - load_internal_asset!(app, SHADER_HANDLE, "debuglines.wgsl", Shader::from_wgsl); } } #[derive(Resource, Clone, Copy)] pub struct DebugDrawConfig { + /// Whether debug drawing should be shown. + /// + /// Defaults to `true`. + pub enabled: bool, /// Whether debug drawing should ignore depth and draw on top of everything else. /// /// Defaults to `true`. @@ -84,88 +88,149 @@ pub struct DebugDrawConfig { impl Default for DebugDrawConfig { fn default() -> Self { Self { + enabled: true, always_on_top: true, } } } -#[derive(Resource, Default)] -pub struct DebugDrawResource { +#[derive(Resource)] +pub struct DebugDraw { positions: Vec<[f32; 3]>, colors: Vec<[f32; 4]>, mesh_handle: Option>, + /// The amount of line segments to use when drawing a circle. + /// + /// Defaults to `24`. + pub circle_segments: u32, +} + +impl Default for DebugDraw { + fn default() -> Self { + DebugDraw { + positions: Vec::new(), + colors: Vec::new(), + mesh_handle: None, + circle_segments: 24, + } + } } -impl DebugDrawResource { +impl DebugDraw { /// Draw a line from `start` to `end`. - fn line(&mut self, start: Vec3, end: Vec3, color: Color) { - self.positions - .extend_from_slice(&[start.to_array(), end.to_array()]); + pub fn line(&mut self, start: Vec3, end: Vec3, color: Color) { + self.positions.extend([start.to_array(), end.to_array()]); let color = color.as_linear_rgba_f32(); - self.colors.extend_from_slice(&[color, color]); + self.colors.extend([color, color]); } /// Draw a line from `start` to `start + vector`. - fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) { + pub fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) { self.line(start, start + vector, color); } - fn clear(&mut self) { - self.positions.clear(); - self.colors.clear(); - } + /// Draw a circle at `position` with the flat side facing `normal`. + pub fn circle(&mut self, position: Vec3, normal: Vec3, radius: f32, color: Color) { + let rotation = Quat::from_rotation_arc(Vec3::Z, normal); + self.positions + .extend((0..self.circle_segments).into_iter().flat_map(|i| { + let angle = i as f32 * TAU / self.circle_segments as f32; - fn update_mesh(&mut self, mesh: &mut Mesh) { - mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, mem::take(&mut self.positions)); - mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, mem::take(&mut self.colors)); - } -} + let (y, x) = angle.sin_cos(); + let start = rotation * Vec3::new(x, y, 0.) * radius + position; -#[derive(SystemParam)] -pub struct DebugDraw<'w, 's> { - debug_draw: ResMut<'w, DebugDrawResource>, - #[system_param(ignore)] - marker: PhantomData<&'s ()>, -} + let (y, x) = (angle + TAU / self.circle_segments as f32).sin_cos(); + let end = rotation * Vec3::new(x, y, 0.) * radius + position; -impl<'w, 's> DebugDraw<'w, 's> { - /// Draw a line from `start` to `end`. - pub fn line(&mut self, start: Vec3, end: Vec3, color: Color) { - self.debug_draw.line(start, end, color); + [start.to_array(), end.to_array()] + })); + + self.colors.extend( + std::iter::repeat(color.as_linear_rgba_f32()).take(self.circle_segments as usize * 2), + ); } - /// Draw a line from `start` to `start + vector`. - pub fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) { - self.debug_draw.ray(start, vector, color); + /// Draw a sphere. + pub fn sphere(&mut self, position: Vec3, radius: f32, color: Color) { + self.circle(position, Vec3::X, radius, color); + self.circle(position, Vec3::Y, radius, color); + self.circle(position, Vec3::Z, radius, color); } - pub fn clear(&mut self) { - self.debug_draw.clear(); + /// Draw a rectangle. + pub fn rect(&mut self, position: Vec3, rotation: Quat, size: Vec2, color: Color) { + let half_size = size / 2.; + let tl = (position + rotation * vec3(-half_size.x, half_size.y, 0.)).to_array(); + let tr = (position + rotation * vec3(half_size.x, half_size.y, 0.)).to_array(); + let bl = (position + rotation * vec3(-half_size.x, -half_size.y, 0.)).to_array(); + let br = (position + rotation * vec3(half_size.x, -half_size.y, 0.)).to_array(); + self.positions.extend([tl, tr, tr, br, br, bl, bl, tl]); + self.colors + .extend(std::iter::repeat(color.as_linear_rgba_f32()).take(8)) } -} -#[derive(SystemParam)] -pub struct DebugDraw2d<'w, 's> { - debug_draw: ResMut<'w, DebugDrawResource>, - #[system_param(ignore)] - marker: PhantomData<&'s ()>, -} + /// Draw a box. + pub fn cuboid(&mut self, position: Vec3, rotation: Quat, size: Vec3, color: Color) { + let half_size = size / 2.; + // Front + let tlf = (position + rotation * vec3(-half_size.x, half_size.y, half_size.z)).to_array(); + let trf = (position + rotation * vec3(half_size.x, half_size.y, half_size.z)).to_array(); + let blf = (position + rotation * vec3(-half_size.x, -half_size.y, half_size.z)).to_array(); + let brf = (position + rotation * vec3(half_size.x, -half_size.y, half_size.z)).to_array(); + // Back + let tlb = (position + rotation * vec3(-half_size.x, half_size.y, -half_size.z)).to_array(); + let trb = (position + rotation * vec3(half_size.x, half_size.y, -half_size.z)).to_array(); + let blb = (position + rotation * vec3(-half_size.x, -half_size.y, -half_size.z)).to_array(); + let brb = (position + rotation * vec3(half_size.x, -half_size.y, -half_size.z)).to_array(); + self.positions.extend([ + tlf, trf, trf, brf, brf, blf, blf, tlf, // Front + tlb, trb, trb, brb, brb, blb, blb, tlb, // Back + tlf, tlb, trf, trb, brf, brb, blf, blb, // Front to back + ]); + self.colors + .extend(std::iter::repeat(color.as_linear_rgba_f32()).take(24)) + } + + /// Draw an axis-aligned box. + pub fn aab(&mut self, position: Vec3, size: Vec3, color: Color) { + self.cuboid(position, Quat::IDENTITY, size, color); + } -impl<'w, 's> DebugDraw2d<'w, 's> { /// Draw a line from `start` to `end`. - pub fn line(&mut self, start: Vec2, end: Vec2, color: Color) { - self.debug_draw - .line(start.extend(0.), end.extend(0.), color); + pub fn line_2d(&mut self, start: Vec2, end: Vec2, color: Color) { + self.line(start.extend(0.), end.extend(0.), color); } /// Draw a line from `start` to `start + vector`. - pub fn ray(&mut self, start: Vec2, vector: Vec2, color: Color) { - self.debug_draw - .ray(start.extend(0.), vector.extend(0.), color); + pub fn ray_2d(&mut self, start: Vec2, vector: Vec2, color: Color) { + self.ray(start.extend(0.), vector.extend(0.), color); + } + + pub fn circle_2d(&mut self, position: Vec2, radius: f32, color: Color) { + self.circle(position.extend(0.), Vec3::Z, radius, color); + } + + /// Draw a rectangle at `position`. + pub fn rect_2d(&mut self, position: Vec2, rotation: f32, size: Vec2, color: Color) { + self.rect( + position.extend(0.), + Quat::from_rotation_z(rotation), + size, + color, + ); } pub fn clear(&mut self) { - self.debug_draw.clear(); + self.positions.clear(); + self.colors.clear(); + } + + pub fn update_mesh(&mut self, mesh: &mut Mesh) { + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + std::mem::take(&mut self.positions), + ); + mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, std::mem::take(&mut self.colors)); } } @@ -173,29 +238,38 @@ impl<'w, 's> DebugDraw2d<'w, 's> { pub struct DebugDrawMesh; pub(crate) fn update( - mut draw: ResMut, + config: Res, + mut debug_draw: ResMut, mut meshes: ResMut>, mut commands: Commands, ) { - if let Some(mut mesh) = draw + if let Some(mut mesh) = debug_draw .mesh_handle .as_ref() .and_then(|handle| meshes.get_mut(handle)) { - draw.update_mesh(&mut mesh); - } else { + if config.enabled { + debug_draw.update_mesh(&mut mesh); + } else { + debug_draw.clear(); + mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION); + mesh.remove_attribute(Mesh::ATTRIBUTE_COLOR); + } + } else if config.enabled { let mut mesh = Mesh::new(PrimitiveTopology::LineList); - draw.update_mesh(&mut mesh); + debug_draw.update_mesh(&mut mesh); let mesh_handle = meshes.add(mesh); commands.spawn(( SpatialBundle::VISIBLE_IDENTITY, DebugDrawMesh, - #[cfg(feature = "3d")] + #[cfg(feature = "bevy_pbr")] (mesh_handle.clone_weak(), NotShadowCaster, NotShadowReceiver), - #[cfg(feature = "2d")] + #[cfg(feature = "bevy_sprite")] Mesh2dHandle(mesh_handle.clone_weak()), )); - draw.mesh_handle = Some(mesh_handle); + debug_draw.mesh_handle = Some(mesh_handle); + } else { + debug_draw.clear(); } } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 91d73b838c56d..f90708d87cd18 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -64,8 +64,8 @@ bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render?/ci_limits"] # Enable animation support, and glTF animation loading animation = ["bevy_animation", "bevy_gltf?/bevy_animation"] -bevy_sprite = ["dep:bevy_sprite", "bevy_debug_draw?/2d"] -bevy_pbr = ["dep:bevy_pbr", "bevy_debug_draw?/3d"] +bevy_sprite = ["dep:bevy_sprite", "bevy_debug_draw?/bevy_sprite"] +bevy_pbr = ["dep:bevy_pbr", "bevy_debug_draw?/bevy_pbr"] [dependencies] # bevy diff --git a/examples/2d/2d_debug_draw.rs b/examples/2d/2d_debug_draw.rs new file mode 100644 index 0000000000000..f69cc4dfeff62 --- /dev/null +++ b/examples/2d/2d_debug_draw.rs @@ -0,0 +1,33 @@ +//! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging. + +use bevy::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(system) + .run(); +} + +fn setup(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); +} + +fn system(mut draw: ResMut, time: Res