diff --git a/Cargo.toml b/Cargo.toml index c1e1f34230dfc..d4d9b1efdbfe4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ bevy_winit = ["bevy_internal/bevy_winit"] trace_chrome = ["bevy_internal/trace_chrome"] trace = ["bevy_internal/trace"] wgpu_trace = ["bevy_internal/wgpu_trace"] +debug_draw = ["bevy_internal/bevy_debug_draw"] # Image format support for texture loading (PNG and HDR are enabled by default) hdr = ["bevy_internal/hdr"] @@ -119,6 +120,11 @@ path = "examples/2d/texture_atlas.rs" name = "3d_scene" path = "examples/3d/3d_scene.rs" +[[example]] +name = "debug_draw_3d" +path = "examples/3d/debug_draw_3d.rs" +required-features = ["debug_draw"] + [[example]] name = "load_gltf" path = "examples/3d/load_gltf.rs" diff --git a/crates/bevy_debug_draw/Cargo.toml b/crates/bevy_debug_draw/Cargo.toml new file mode 100644 index 0000000000000..b1ee401f01dde --- /dev/null +++ b/crates/bevy_debug_draw/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "bevy_debug_draw" +version = "0.4.0" +edition = "2018" +authors = [ + "Bevy Contributors ", + "Fabian Krauser ", +] +description = "Provides immediate mode debug drawing for the Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT" +keywords = ["bevy"] + + +[dependencies] +# bevy +bevy_render = { path = "../bevy_render", version = "0.4.0" } +bevy_app = { path = "../bevy_app", version = "0.4.0" } +bevy_asset = { path = "../bevy_asset", version = "0.4.0" } +bevy_core = { path = "../bevy_core", version = "0.4.0" } +bevy_log = { path = "../bevy_log", version = "0.4.0" } +bevy_derive = { path = "../bevy_derive", version = "0.4.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.4.0" } +bevy_math = { path = "../bevy_math", version = "0.4.0" } +bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] } +bevy_transform = { path = "../bevy_transform", version = "0.4.0" } +bevy_utils = { path = "../bevy_utils", version = "0.4.0" } \ No newline at end of file diff --git a/crates/bevy_debug_draw/src/debug_draw_3d.rs b/crates/bevy_debug_draw/src/debug_draw_3d.rs new file mode 100644 index 0000000000000..43aae9a4f0ffd --- /dev/null +++ b/crates/bevy_debug_draw/src/debug_draw_3d.rs @@ -0,0 +1,211 @@ +use bevy_app::{AppBuilder, CoreStage, Plugin}; +use bevy_asset::{AddAsset, Assets, Handle}; +use bevy_ecs::{ + prelude::{Commands, Query, ResMut, With}, + schedule::*, + system::*, +}; +use bevy_log::info; +use bevy_math::*; +use bevy_reflect::TypeUuid; +use bevy_render::{ + mesh::*, + pipeline::{CullMode, PipelineDescriptor, PrimitiveTopology, RenderPipeline}, + prelude::{Color, MeshBundle, RenderPipelines}, + render_graph::{base, AssetRenderResourcesNode, RenderGraph}, + renderer::RenderResources, + shader::{Shader, ShaderStage, ShaderStages}, +}; +use bevy_transform::{ + components::{GlobalTransform, Transform}, + TransformSystem, +}; + +use crate::gizmo::CoordinateGizmo; +/// Bevy immediate mode debug drawing: +/// This crate introduces a DebugDraw3D resource which provides functions such as `draw_line(start, end, color)` +/// Whenever such a draw_line function is called, a set of vertices is added to the DebugDraw3D objects data. +/// At the end of the frame the internal data is copied into a mesh entity for drawing and then cleared from the DebugDraw3D object. +/// With this, no persistent line drawing is possible and lines have to be added every frame (hence immediate mode). +/// For convenience a system called `debug_draw_all_gizmos` is provided that draws a coordinate gizmo for any `GlobalTransform`. +/// +/// ToDo: +/// * Add more convenience functions such as `draw_arrow(start, end, head_size, color)`, `draw_circle(origin, radius, axis, color)`, `draw_aabb(min,max,color)`. +/// * Modify the shader and access the depth buffer and perform hidden-line rendering rather than a binary depth test for better line visualization. +/// * Add the `debug_draw_all_gizmos` system to the plugin, using a parameter to turn it on or off. +/// * Add transparent triangle drawing (useful to visually project a line down on a plane) and matching utility functions. +/// * Add timed or persistent drawing: This requires storing `Line` structs containing a lifetime rather than directly pushing to an array. +/// * Even though this is a debug feature, there current approach may likely not be the most performant solution and optimizations/refactoring should be applied. + +pub struct DebugDrawPlugin; +impl Plugin for DebugDrawPlugin { + fn build(&self, app: &mut AppBuilder) { + app.add_asset::() + .init_resource::() + .add_startup_system(setup_debug_draw_3d.system()) + .add_system_to_stage( + CoreStage::PostUpdate, + update_debug_draw_3d + .system() + .after(TransformSystem::TransformPropagate), + ); + } +} + +/// DebugDraw3D Resource providing functions for immediate mode drawing +pub struct DebugDraw3D { + // The mesh data is held as plain arrays here + // If we wish to extend to more than just lines we may need multiple pairs that will later be ass, e.g. vertices_line and vertices_triangle + vertices: Vec<[f32; 3]>, + colors: Vec<[f32; 4]>, + dirty: bool, + clear: bool, +} + +impl Default for DebugDraw3D { + fn default() -> Self { + DebugDraw3D { + vertices: Default::default(), + colors: Default::default(), + dirty: true, + clear: true, + } + } +} + +impl DebugDraw3D { + pub fn draw_line(&mut self, start: Vec3, end: Vec3, color: Color) { + self.vertices.push(start.into()); + self.vertices.push(end.into()); + self.colors.push(color.into()); + self.colors.push(color.into()); + self.set_dirty(); + } + + /// Turning this off results in lines not being cleared anymore. + /// You will have to use clear() manually. + pub fn automatic(&mut self, clear: bool) { + self.clear = clear; + } + + /// You do not have to call this in automatic mode. + pub fn clear(&mut self) { + self.vertices.clear(); + self.colors.clear(); + } + + fn set_dirty(&mut self) { + self.dirty = true; + } + + fn reset(&mut self) { + if self.clear { + self.clear(); + } + self.dirty = false; + } +} +/// This component marks the internal entity that does the mesh drawing. +#[derive(Default)] +struct DebugDraw3DComponent; + +/// The Material holding the shader for debug drawing +#[derive(RenderResources, Default, TypeUuid)] +#[uuid = "188f0f97-60b2-476a-a749-7a0103adeeba"] +pub struct DebugDraw3DMaterial; + +///This system sets up the entity holding the actual mesh for drawing as well as the render pipeline step for the shader. +fn setup_debug_draw_3d( + mut commands: Commands, + mut pipelines: ResMut>, + mut shaders: ResMut>, + mut meshes: ResMut>, + mut materials: ResMut>, + mut render_graph: ResMut, +) { + // Crate a shader Pipeline + let mut p = PipelineDescriptor::default_config(ShaderStages { + vertex: shaders.add(Shader::from_glsl( + ShaderStage::Vertex, + include_str!("shaders/debugDrawLine.vert"), + )), + fragment: Some(shaders.add(Shader::from_glsl( + ShaderStage::Fragment, + include_str!("shaders/debugDrawLine.frag"), + ))), + }); + p.primitive.topology = PrimitiveTopology::LineList; + p.primitive.cull_mode = CullMode::None; + let pipeline_handle = pipelines.add(p); + let pipeline = RenderPipelines::from_pipelines(vec![RenderPipeline::new(pipeline_handle)]); + + // add the material to the pipeline + render_graph.add_system_node( + "debug_draw_3d", + AssetRenderResourcesNode::::new(false), + ); + // connect that node stage the MAIN_PASS node + render_graph + .add_node_edge("debug_draw_3d", base::node::MAIN_PASS) + .unwrap(); + + let material_instance = materials.add(DebugDraw3DMaterial {}); + + // Spawn a entity that will do the debug drawing with its mesh + commands + .spawn(MeshBundle { + mesh: meshes.add(Mesh::from(CoordinateGizmo { size: 1.0 })), + render_pipelines: pipeline, //we have to tell our mesh what pipeline to render in... + transform: Transform::from_translation(Vec3::ZERO), + ..Default::default() + }) + .with(material_instance) + .with(DebugDraw3DComponent::default()); + + info!("Loaded debug lines plugin."); +} + +/// This system updates the debug draw Entity with the data from +fn update_debug_draw_3d( + mut debug_draw: ResMut, + mut meshes: ResMut>, + query: Query<&Handle, With>, +) { + if !debug_draw.dirty { + return; + } else { + for mesh in query.iter() { + if let Some(mesh) = meshes.get_mut(mesh) { + mesh.set_attribute( + Mesh::ATTRIBUTE_POSITION, + VertexAttributeValues::Float3(debug_draw.vertices.clone()), + ); + mesh.set_attribute( + "Vertex_Color", + VertexAttributeValues::Float4(debug_draw.colors.clone()), + ); + } + } + } + debug_draw.reset(); +} + +pub fn debug_draw_all_gizmos(mut debug_draw: ResMut, query: Query<&GlobalTransform>) { + for transform in query.iter() { + debug_draw.draw_line( + transform.translation, + transform.translation + transform.local_x(), + Color::RED, + ); + debug_draw.draw_line( + transform.translation, + transform.translation + transform.local_y(), + Color::GREEN, + ); + debug_draw.draw_line( + transform.translation, + transform.translation + transform.local_z(), + Color::BLUE, + ); + } +} diff --git a/crates/bevy_debug_draw/src/gizmo.rs b/crates/bevy_debug_draw/src/gizmo.rs new file mode 100644 index 0000000000000..eee2cbd0a1e65 --- /dev/null +++ b/crates/bevy_debug_draw/src/gizmo.rs @@ -0,0 +1,45 @@ +use bevy_render::{mesh::*, pipeline::PrimitiveTopology}; + +pub struct CoordinateGizmo { + pub size: f32, +} + +impl CoordinateGizmo { + pub fn new(size: f32) -> CoordinateGizmo { + CoordinateGizmo { size } + } +} + +impl Default for CoordinateGizmo { + fn default() -> Self { + CoordinateGizmo { size: 1.0 } + } +} + +impl From for Mesh { + fn from(shape: CoordinateGizmo) -> Self { + let mut mesh = Mesh::new(PrimitiveTopology::LineList); + let vertices = vec![ + [0.0, 0.0, 0.0], + [shape.size, 0.0, 0.0], + [0.0, 0.0, 0.0], + [0.0, shape.size, 0.0], + [0.0, 0.0, 0.0], + [0.0, 0.0, shape.size], + ]; + mesh.set_attribute( + Mesh::ATTRIBUTE_POSITION, + VertexAttributeValues::Float3(vertices), + ); + let colors = vec![ + [1.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 1.0], + [0.0, 1.0, 0.0, 1.0], + [0.0, 1.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 1.0], + [0.0, 0.0, 1.0, 1.0], + ]; + mesh.set_attribute("Vertex_Color", VertexAttributeValues::Float4(colors)); + mesh + } +} diff --git a/crates/bevy_debug_draw/src/lib.rs b/crates/bevy_debug_draw/src/lib.rs new file mode 100644 index 0000000000000..79377976e21f7 --- /dev/null +++ b/crates/bevy_debug_draw/src/lib.rs @@ -0,0 +1,2 @@ +pub mod debug_draw_3d; +pub mod gizmo; diff --git a/crates/bevy_debug_draw/src/shaders/debugDrawLine.frag b/crates/bevy_debug_draw/src/shaders/debugDrawLine.frag new file mode 100644 index 0000000000000..4c47962d312a4 --- /dev/null +++ b/crates/bevy_debug_draw/src/shaders/debugDrawLine.frag @@ -0,0 +1,6 @@ +#version 450 +layout(location = 0) in vec4 vColor; +layout(location = 0) out vec4 o_Target; +void main() { + o_Target = vColor; +} \ No newline at end of file diff --git a/crates/bevy_debug_draw/src/shaders/debugDrawLine.vert b/crates/bevy_debug_draw/src/shaders/debugDrawLine.vert new file mode 100644 index 0000000000000..e470b300c6fcf --- /dev/null +++ b/crates/bevy_debug_draw/src/shaders/debugDrawLine.vert @@ -0,0 +1,15 @@ +#version 450 +layout(location = 0) in vec3 Vertex_Position; +layout(location = 1) in vec4 Vertex_Color; +layout(location = 0) out vec4 vColor; + +layout(set = 0, binding = 0) uniform Camera { + mat4 ViewProj; +}; +layout(set = 1, binding = 0) uniform Transform { + mat4 Model; +}; +void main() { + vColor = Vertex_Color; + gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0); +} \ No newline at end of file diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index d4556746d3508..854977e8b4c2f 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -63,6 +63,7 @@ bevy_audio = { path = "../bevy_audio", optional = true, version = "0.4.0" } bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.4.0" } bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.4.0" } bevy_render = { path = "../bevy_render", optional = true, version = "0.4.0" } +bevy_debug_draw = { path = "../bevy_debug_draw", optional = true, version = "0.4.0" } bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.4.0" } bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.4.0" } bevy_text = { path = "../bevy_text", optional = true, version = "0.4.0" } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 331a5e90ab256..5c1a8215141ac 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -106,6 +106,12 @@ pub mod render { pub use bevy_render::*; } +#[cfg(feature = "bevy_debug_draw")] +pub mod debug_draw { + //! Immediate mode debug drawing, like a visual println. + pub use bevy_debug_draw::*; +} + #[cfg(feature = "bevy_sprite")] pub mod sprite { //! Items for sprites, rects, texture atlases, etc. diff --git a/examples/3d/debug_draw_3d.rs b/examples/3d/debug_draw_3d.rs new file mode 100644 index 0000000000000..b7747e123aace --- /dev/null +++ b/examples/3d/debug_draw_3d.rs @@ -0,0 +1,101 @@ +use bevy::{debug_draw::debug_draw_3d::*, prelude::*}; + +fn main() { + App::build() + .insert_resource(Msaa { samples: 4 }) + .add_plugins(DefaultPlugins) + //Add the plugin for DebugDraw + .add_plugin(DebugDrawPlugin) + .add_startup_system(setup.system()) + .add_system(rotator_system.system()) + .add_system(deubg_draw_sample_system.system()) + //uncomment this prebuilt system to draw a coordinate gizmo for any entity with a `GlobalTransform` + // .add_system(debug_draw_all_gizmos.system()) + .run(); +} + +/// This system takes any entities with GlobalTransform and Rotator +/// and draws a line from it to the origin of the world. +fn deubg_draw_sample_system( + mut debug_draw: ResMut, + query: Query<&GlobalTransform, With>, +) { + for transform in query.iter() { + debug_draw.draw_line(Vec3::ZERO, transform.translation, Color::RED); + } + let bl = Vec3::new(-2.0, 0.0, -2.0); + let br = Vec3::new(2.0, 0.0, -2.0); + let tr = Vec3::new(2.0, 0.0, 2.0); + let tl = Vec3::new(-2.0, 0.0, 2.0); + //Draw a square + debug_draw.draw_line(bl, br, Color::BLUE); + debug_draw.draw_line(br, tr, Color::BLUE); + debug_draw.draw_line(tr, tl, Color::BLUE); + debug_draw.draw_line(tl, bl, Color::BLUE); +} + +/// this component indicates what entities should rotate +struct Rotator; + +/// rotates the parent, which will result in the child also rotating +fn rotator_system(time: Res