//! This crate adds an immediate mode drawing api to Bevy for visual debugging. //! //! # Example //! ``` //! # use bevy_gizmos::prelude::*; //! # use bevy_render::prelude::*; //! # use bevy_math::prelude::*; //! # use bevy_color::palettes::basic::GREEN; //! fn system(mut gizmos: Gizmos) { //! gizmos.line(Vec3::ZERO, Vec3::X, GREEN); //! } //! # bevy_ecs::system::assert_is_system(system); //! ``` //! //! See the documentation on [Gizmos](crate::gizmos::Gizmos) for more examples. /// System set label for the systems handling the rendering of gizmos. #[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)] pub enum GizmoRenderSystem { /// Adds gizmos to the [`Transparent2d`](bevy_core_pipeline::core_2d::Transparent2d) render phase #[cfg(feature = "bevy_sprite")] QueueLineGizmos2d, /// Adds gizmos to the [`Transparent3d`](bevy_core_pipeline::core_3d::Transparent3d) render phase #[cfg(feature = "bevy_pbr")] QueueLineGizmos3d, } pub mod aabb; pub mod arcs; pub mod arrows; pub mod circles; pub mod config; pub mod gizmos; pub mod grid; pub mod primitives; #[cfg(feature = "bevy_sprite")] mod pipeline_2d; #[cfg(feature = "bevy_pbr")] mod pipeline_3d; /// The `bevy_gizmos` prelude. pub mod prelude { #[doc(hidden)] pub use crate::{ aabb::{AabbGizmoConfigGroup, ShowAabbGizmo}, config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore}, gizmos::Gizmos, primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d}, AppGizmoBuilder, }; } use aabb::AabbGizmoPlugin; use bevy_app::{App, Last, Plugin}; use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; use bevy_color::LinearRgba; use bevy_core::cast_slice; use bevy_ecs::{ component::Component, query::ROQueryItem, schedule::{IntoSystemConfigs, SystemSet}, system::{ lifetimeless::{Read, SRes}, Commands, Res, ResMut, Resource, SystemParamItem, }, }; use bevy_reflect::TypePath; use bevy_render::{ extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, render_asset::{ PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssetUsages, RenderAssets, }, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{ binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader, ShaderStages, ShaderType, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode, }, renderer::RenderDevice, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::TypeIdMap; use config::{ DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoMeshConfig, }; use gizmos::GizmoStorage; use std::{any::TypeId, mem}; const LINE_SHADER_HANDLE: Handle = Handle::weak_from_u128(7414812689238026784); /// A [`Plugin`] that provides an immediate mode drawing api for visual debugging. pub struct GizmoPlugin; impl Plugin for GizmoPlugin { fn build(&self, app: &mut bevy_app::App) { // Gizmos cannot work without either a 3D or 2D renderer. #[cfg(all(not(feature = "bevy_pbr"), not(feature = "bevy_sprite")))] bevy_utils::tracing::error!( "bevy_gizmos requires either bevy_pbr or bevy_sprite. Please enable one." ); load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl); app.register_type::() .register_type::() .add_plugins(UniformComponentPlugin::::default()) .init_asset::() .add_plugins(RenderAssetPlugin::::default()) .init_resource::() // We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist. .init_gizmo_group::() .add_plugins(AabbGizmoPlugin); let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app.add_systems( Render, prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups), ); render_app.add_systems(ExtractSchedule, extract_gizmo_data); #[cfg(feature = "bevy_sprite")] app.add_plugins(pipeline_2d::LineGizmo2dPlugin); #[cfg(feature = "bevy_pbr")] app.add_plugins(pipeline_3d::LineGizmo3dPlugin); } fn finish(&self, app: &mut bevy_app::App) { let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; let render_device = render_app.world.resource::(); let layout = render_device.create_bind_group_layout( "LineGizmoUniform layout", &BindGroupLayoutEntries::single( ShaderStages::VERTEX, uniform_buffer::(true), ), ); render_app.insert_resource(LineGizmoUniformBindgroupLayout { layout }); } } /// A trait adding `init_gizmo_group()` to the app pub trait AppGizmoBuilder { /// Registers [`GizmoConfigGroup`] `T` in the app enabling the use of [Gizmos<T>](crate::gizmos::Gizmos). /// /// Configurations can be set using the [`GizmoConfigStore`] [`Resource`]. fn init_gizmo_group(&mut self) -> &mut Self; /// Insert the [`GizmoConfigGroup`] in the app with the given value and [`GizmoConfig`]. /// /// This method should be preferred over [`AppGizmoBuilder::init_gizmo_group`] if and only if you need to configure fields upon initialization. fn insert_gizmo_group( &mut self, group: T, config: GizmoConfig, ) -> &mut Self; } impl AppGizmoBuilder for App { fn init_gizmo_group(&mut self) -> &mut Self { if self.world.contains_resource::>() { return self; } let mut handles = self .world .get_resource_or_insert_with::(Default::default); handles.list.insert(TypeId::of::(), None); handles.strip.insert(TypeId::of::(), None); self.init_resource::>() .add_systems(Last, update_gizmo_meshes::); self.world .get_resource_or_insert_with::(Default::default) .register::(); self } fn insert_gizmo_group( &mut self, group: T, config: GizmoConfig, ) -> &mut Self { self.world .get_resource_or_insert_with::(Default::default) .insert(config, group); if self.world.contains_resource::>() { return self; } let mut handles = self .world .get_resource_or_insert_with::(Default::default); handles.list.insert(TypeId::of::(), None); handles.strip.insert(TypeId::of::(), None); self.init_resource::>() .add_systems(Last, update_gizmo_meshes::); self } } /// Holds handles to the line gizmos for each gizmo configuration group // As `TypeIdMap` iteration order depends on the order of insertions and deletions, this uses // `Option` to be able to reserve the slot when creating the gizmo configuration group. // That way iteration order is stable across executions and depends on the order of configuration // group creation. #[derive(Resource, Default)] struct LineGizmoHandles { list: TypeIdMap>>, strip: TypeIdMap>>, } fn update_gizmo_meshes( mut line_gizmos: ResMut>, mut handles: ResMut, mut storage: ResMut>, ) { if storage.list_positions.is_empty() { handles.list.insert(TypeId::of::(), None); } else if let Some(handle) = handles.list.get_mut(&TypeId::of::()) { if let Some(handle) = handle { let list = line_gizmos.get_mut(handle.id()).unwrap(); list.positions = mem::take(&mut storage.list_positions); list.colors = mem::take(&mut storage.list_colors); } else { let mut list = LineGizmo { strip: false, ..Default::default() }; list.positions = mem::take(&mut storage.list_positions); list.colors = mem::take(&mut storage.list_colors); *handle = Some(line_gizmos.add(list)); } } if storage.strip_positions.is_empty() { handles.strip.insert(TypeId::of::(), None); } else if let Some(handle) = handles.strip.get_mut(&TypeId::of::()) { if let Some(handle) = handle { let strip = line_gizmos.get_mut(handle.id()).unwrap(); strip.positions = mem::take(&mut storage.strip_positions); strip.colors = mem::take(&mut storage.strip_colors); } else { let mut strip = LineGizmo { strip: true, ..Default::default() }; strip.positions = mem::take(&mut storage.strip_positions); strip.colors = mem::take(&mut storage.strip_colors); *handle = Some(line_gizmos.add(strip)); } } } fn extract_gizmo_data( mut commands: Commands, handles: Extract>, config: Extract>, ) { for (group_type_id, handle) in handles.list.iter().chain(handles.strip.iter()) { let Some((config, _)) = config.get_config_dyn(group_type_id) else { continue; }; if !config.enabled { continue; } let Some(handle) = handle else { continue; }; commands.spawn(( LineGizmoUniform { line_width: config.line_width, depth_bias: config.depth_bias, #[cfg(feature = "webgl")] _padding: Default::default(), }, (*handle).clone_weak(), GizmoMeshConfig::from(config), )); } } #[derive(Component, ShaderType, Clone, Copy)] struct LineGizmoUniform { line_width: f32, depth_bias: f32, /// WebGL2 structs must be 16 byte aligned. #[cfg(feature = "webgl")] _padding: bevy_math::Vec2, } #[derive(Asset, Debug, Default, Clone, TypePath)] struct LineGizmo { positions: Vec<[f32; 3]>, colors: Vec, /// Whether this gizmo's topology is a line-strip or line-list strip: bool, } #[derive(Debug, Clone)] struct GpuLineGizmo { position_buffer: Buffer, color_buffer: Buffer, vertex_count: u32, strip: bool, } impl RenderAsset for LineGizmo { type PreparedAsset = GpuLineGizmo; type Param = SRes; fn asset_usage(&self) -> RenderAssetUsages { RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD } fn prepare_asset( self, render_device: &mut SystemParamItem, ) -> Result> { let position_buffer_data = cast_slice(&self.positions); let position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { usage: BufferUsages::VERTEX, label: Some("LineGizmo Position Buffer"), contents: position_buffer_data, }); let color_buffer_data = cast_slice(&self.colors); let color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { usage: BufferUsages::VERTEX, label: Some("LineGizmo Color Buffer"), contents: color_buffer_data, }); Ok(GpuLineGizmo { position_buffer, color_buffer, vertex_count: self.positions.len() as u32, strip: self.strip, }) } } #[derive(Resource)] struct LineGizmoUniformBindgroupLayout { layout: BindGroupLayout, } #[derive(Resource)] struct LineGizmoUniformBindgroup { bindgroup: BindGroup, } fn prepare_line_gizmo_bind_group( mut commands: Commands, line_gizmo_uniform_layout: Res, render_device: Res, line_gizmo_uniforms: Res>, ) { if let Some(binding) = line_gizmo_uniforms.uniforms().binding() { commands.insert_resource(LineGizmoUniformBindgroup { bindgroup: render_device.create_bind_group( "LineGizmoUniform bindgroup", &line_gizmo_uniform_layout.layout, &BindGroupEntries::single(binding), ), }); } } struct SetLineGizmoBindGroup; impl RenderCommand

for SetLineGizmoBindGroup { type Param = SRes; type ViewQuery = (); type ItemQuery = Read>; #[inline] fn render<'w>( _item: &P, _view: ROQueryItem<'w, Self::ViewQuery>, uniform_index: Option>, bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some(uniform_index) = uniform_index else { return RenderCommandResult::Failure; }; pass.set_bind_group( I, &bind_group.into_inner().bindgroup, &[uniform_index.index()], ); RenderCommandResult::Success } } struct DrawLineGizmo; impl RenderCommand

for DrawLineGizmo { type Param = SRes>; type ViewQuery = (); type ItemQuery = Read>; #[inline] fn render<'w>( _item: &P, _view: ROQueryItem<'w, Self::ViewQuery>, handle: Option>, line_gizmos: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some(handle) = handle else { return RenderCommandResult::Failure; }; let Some(line_gizmo) = line_gizmos.into_inner().get(handle) else { return RenderCommandResult::Failure; }; if line_gizmo.vertex_count < 2 { return RenderCommandResult::Success; } let instances = if line_gizmo.strip { let item_size = VertexFormat::Float32x3.size(); let buffer_size = line_gizmo.position_buffer.size() - item_size; pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..buffer_size)); pass.set_vertex_buffer(1, line_gizmo.position_buffer.slice(item_size..)); let item_size = VertexFormat::Float32x4.size(); let buffer_size = line_gizmo.color_buffer.size() - item_size; pass.set_vertex_buffer(2, line_gizmo.color_buffer.slice(..buffer_size)); pass.set_vertex_buffer(3, line_gizmo.color_buffer.slice(item_size..)); u32::max(line_gizmo.vertex_count, 1) - 1 } else { pass.set_vertex_buffer(0, line_gizmo.position_buffer.slice(..)); pass.set_vertex_buffer(1, line_gizmo.color_buffer.slice(..)); line_gizmo.vertex_count / 2 }; pass.draw(0..6, 0..instances); RenderCommandResult::Success } } fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec { use VertexFormat::*; let mut position_layout = VertexBufferLayout { array_stride: Float32x3.size(), step_mode: VertexStepMode::Instance, attributes: vec![VertexAttribute { format: Float32x3, offset: 0, shader_location: 0, }], }; let mut color_layout = VertexBufferLayout { array_stride: Float32x4.size(), step_mode: VertexStepMode::Instance, attributes: vec![VertexAttribute { format: Float32x4, offset: 0, shader_location: 2, }], }; if strip { vec![ position_layout.clone(), { position_layout.attributes[0].shader_location = 1; position_layout }, color_layout.clone(), { color_layout.attributes[0].shader_location = 3; color_layout }, ] } else { position_layout.array_stride *= 2; position_layout.attributes.push(VertexAttribute { format: Float32x3, offset: Float32x3.size(), shader_location: 1, }); color_layout.array_stride *= 2; color_layout.attributes.push(VertexAttribute { format: Float32x4, offset: Float32x4.size(), shader_location: 3, }); vec![position_layout, color_layout] } }