diff --git a/crates/bevy_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml index c1de381454158..a76fd68389e4c 100644 --- a/crates/bevy_gizmos/Cargo.toml +++ b/crates/bevy_gizmos/Cargo.toml @@ -26,6 +26,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" } bevy_transform = { path = "../bevy_transform", version = "0.12.0" } bevy_log = { path = "../bevy_log", version = "0.12.0" } +bevy_gizmos_macros = { path = "macros", version = "0.12.0" } [lints] workspace = true diff --git a/crates/bevy_gizmos/macros/Cargo.toml b/crates/bevy_gizmos/macros/Cargo.toml new file mode 100644 index 0000000000000..376d442ed3dfd --- /dev/null +++ b/crates/bevy_gizmos/macros/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bevy_gizmos_macros" +version = "0.12.0" +edition = "2021" +description = "Derive implementations for bevy_gizmos" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[lib] +proc-macro = true + +[dependencies] +bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" } + +syn = "2.0" +proc-macro2 = "1.0" +quote = "1.0" diff --git a/crates/bevy_gizmos/macros/src/lib.rs b/crates/bevy_gizmos/macros/src/lib.rs new file mode 100644 index 0000000000000..46425ead704b7 --- /dev/null +++ b/crates/bevy_gizmos/macros/src/lib.rs @@ -0,0 +1,23 @@ +use bevy_macro_utils::BevyManifest; +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, parse_quote, DeriveInput, Path}; + +#[proc_macro_derive(GizmoConfigGroup)] +pub fn derive_gizmo_config_group(input: TokenStream) -> TokenStream { + let mut ast = parse_macro_input!(input as DeriveInput); + let bevy_gizmos_path: Path = BevyManifest::default().get_path("bevy_gizmos"); + let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect"); + + ast.generics.make_where_clause().predicates.push( + parse_quote! { Self: #bevy_reflect_path::Reflect + #bevy_reflect_path::TypePath + Default}, + ); + + let struct_name = &ast.ident; + let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + + TokenStream::from(quote! { + impl #impl_generics #bevy_gizmos_path::config::GizmoConfigGroup for #struct_name #type_generics #where_clause { + } + }) +} diff --git a/crates/bevy_gizmos/src/aabb.rs b/crates/bevy_gizmos/src/aabb.rs new file mode 100644 index 0000000000000..ed9eccae027b4 --- /dev/null +++ b/crates/bevy_gizmos/src/aabb.rs @@ -0,0 +1,120 @@ +//! A module adding debug visualization of [`Aabb`]s. + +use crate as bevy_gizmos; + +use bevy_app::{Plugin, PostUpdate}; +use bevy_ecs::{ + component::Component, + entity::Entity, + query::Without, + reflect::ReflectComponent, + schedule::IntoSystemConfigs, + system::{Query, Res}, +}; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_render::{color::Color, primitives::Aabb}; +use bevy_transform::{ + components::{GlobalTransform, Transform}, + TransformSystem, +}; + +use crate::{ + config::{GizmoConfigGroup, GizmoConfigStore}, + gizmos::Gizmos, + AppGizmoBuilder, +}; + +/// A [`Plugin`] that provides visualization of [`Aabb`]s for debugging. +pub struct AabbGizmoPlugin; + +impl Plugin for AabbGizmoPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.register_type::() + .init_gizmo_group::() + .add_systems( + PostUpdate, + ( + draw_aabbs, + draw_all_aabbs.run_if(|config: Res| { + config.config::().1.draw_all + }), + ) + .after(TransformSystem::TransformPropagate), + ); + } +} +/// The [`GizmoConfigGroup`] used for debug visualizations of [`Aabb`] components on entities +#[derive(Clone, Default, Reflect, GizmoConfigGroup)] +pub struct AabbGizmoConfigGroup { + /// Draws all bounding boxes in the scene when set to `true`. + /// + /// To draw a specific entity's bounding box, you can add the [`ShowAabbGizmo`] component. + /// + /// Defaults to `false`. + pub draw_all: bool, + /// The default color for bounding box gizmos. + /// + /// A random color is chosen per box if `None`. + /// + /// Defaults to `None`. + pub default_color: Option, +} + +/// Add this [`Component`] to an entity to draw its [`Aabb`] component. +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component, Default)] +pub struct ShowAabbGizmo { + /// The color of the box. + /// + /// The default color from the [`AabbGizmoConfigGroup`] config is used if `None`, + pub color: Option, +} + +fn draw_aabbs( + query: Query<(Entity, &Aabb, &GlobalTransform, &ShowAabbGizmo)>, + mut gizmos: Gizmos, +) { + for (entity, &aabb, &transform, gizmo) in &query { + let color = gizmo + .color + .or(gizmos.config_ext.default_color) + .unwrap_or_else(|| color_from_entity(entity)); + gizmos.cuboid(aabb_transform(aabb, transform), color); + } +} + +fn draw_all_aabbs( + query: Query<(Entity, &Aabb, &GlobalTransform), Without>, + mut gizmos: Gizmos, +) { + for (entity, &aabb, &transform) in &query { + let color = gizmos + .config_ext + .default_color + .unwrap_or_else(|| color_from_entity(entity)); + gizmos.cuboid(aabb_transform(aabb, transform), color); + } +} + +fn color_from_entity(entity: Entity) -> Color { + let index = entity.index(); + + // from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/ + // + // See https://en.wikipedia.org/wiki/Low-discrepancy_sequence + // Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range, + // so that the closer the numbers are, the larger the difference of their image. + const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up + const RATIO_360: f32 = 360.0 / u32::MAX as f32; + let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360; + + Color::hsl(hue, 1., 0.5) +} + +fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform { + transform + * GlobalTransform::from( + Transform::from_translation(aabb.center.into()) + .with_scale((aabb.half_extents * 2.).into()), + ) +} diff --git a/crates/bevy_gizmos/src/arcs.rs b/crates/bevy_gizmos/src/arcs.rs index c44ecd22b9d83..27f698e00c546 100644 --- a/crates/bevy_gizmos/src/arcs.rs +++ b/crates/bevy_gizmos/src/arcs.rs @@ -4,12 +4,12 @@ //! and assorted support items. use crate::circles::DEFAULT_CIRCLE_SEGMENTS; -use crate::prelude::Gizmos; +use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_math::Vec2; use bevy_render::color::Color; use std::f32::consts::TAU; -impl<'s> Gizmos<'s> { +impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// Draw an arc, which is a part of the circumference of a circle, in 2D. /// /// This should be called for each frame the arc needs to be rendered. @@ -46,7 +46,7 @@ impl<'s> Gizmos<'s> { arc_angle: f32, radius: f32, color: Color, - ) -> Arc2dBuilder<'_, 's> { + ) -> Arc2dBuilder<'_, 'w, 's, T> { Arc2dBuilder { gizmos: self, position, @@ -60,8 +60,8 @@ impl<'s> Gizmos<'s> { } /// A builder returned by [`Gizmos::arc_2d`]. -pub struct Arc2dBuilder<'a, 's> { - gizmos: &'a mut Gizmos<'s>, +pub struct Arc2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> { + gizmos: &'a mut Gizmos<'w, 's, T>, position: Vec2, direction_angle: f32, arc_angle: f32, @@ -70,7 +70,7 @@ pub struct Arc2dBuilder<'a, 's> { segments: Option, } -impl Arc2dBuilder<'_, '_> { +impl Arc2dBuilder<'_, '_, '_, T> { /// Set the number of line-segments for this arc. pub fn segments(mut self, segments: usize) -> Self { self.segments = Some(segments); @@ -78,8 +78,11 @@ impl Arc2dBuilder<'_, '_> { } } -impl Drop for Arc2dBuilder<'_, '_> { +impl Drop for Arc2dBuilder<'_, '_, '_, T> { fn drop(&mut self) { + if !self.gizmos.enabled { + return; + } let segments = match self.segments { Some(segments) => segments, // Do a linear interpolation between 1 and `DEFAULT_CIRCLE_SEGMENTS` diff --git a/crates/bevy_gizmos/src/arrows.rs b/crates/bevy_gizmos/src/arrows.rs index 9a1325c821302..b5416e412f558 100644 --- a/crates/bevy_gizmos/src/arrows.rs +++ b/crates/bevy_gizmos/src/arrows.rs @@ -3,20 +3,20 @@ //! Includes the implementation of [`Gizmos::arrow`] and [`Gizmos::arrow_2d`], //! and assorted support items. -use crate::prelude::Gizmos; +use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_math::{Quat, Vec2, Vec3}; use bevy_render::color::Color; /// A builder returned by [`Gizmos::arrow`] and [`Gizmos::arrow_2d`] -pub struct ArrowBuilder<'a, 's> { - gizmos: &'a mut Gizmos<'s>, +pub struct ArrowBuilder<'a, 'w, 's, T: GizmoConfigGroup> { + gizmos: &'a mut Gizmos<'w, 's, T>, start: Vec3, end: Vec3, color: Color, tip_length: f32, } -impl ArrowBuilder<'_, '_> { +impl ArrowBuilder<'_, '_, '_, T> { /// Change the length of the tips to be `length`. /// The default tip length is [length of the arrow]/10. /// @@ -37,9 +37,12 @@ impl ArrowBuilder<'_, '_> { } } -impl Drop for ArrowBuilder<'_, '_> { +impl Drop for ArrowBuilder<'_, '_, '_, T> { /// Draws the arrow, by drawing lines with the stored [`Gizmos`] fn drop(&mut self) { + if !self.gizmos.enabled { + return; + } // first, draw the body of the arrow self.gizmos.line(self.start, self.end, self.color); // now the hard part is to draw the head in a sensible way @@ -63,7 +66,7 @@ impl Drop for ArrowBuilder<'_, '_> { } } -impl<'s> Gizmos<'s> { +impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// Draw an arrow in 3D, from `start` to `end`. Has four tips for convienent viewing from any direction. /// /// This should be called for each frame the arrow needs to be rendered. @@ -78,7 +81,7 @@ impl<'s> Gizmos<'s> { /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` - pub fn arrow(&mut self, start: Vec3, end: Vec3, color: Color) -> ArrowBuilder<'_, 's> { + pub fn arrow(&mut self, start: Vec3, end: Vec3, color: Color) -> ArrowBuilder<'_, 'w, 's, T> { let length = (end - start).length(); ArrowBuilder { gizmos: self, @@ -103,7 +106,12 @@ impl<'s> Gizmos<'s> { /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` - pub fn arrow_2d(&mut self, start: Vec2, end: Vec2, color: Color) -> ArrowBuilder<'_, 's> { + pub fn arrow_2d( + &mut self, + start: Vec2, + end: Vec2, + color: Color, + ) -> ArrowBuilder<'_, 'w, 's, T> { self.arrow(start.extend(0.), end.extend(0.), color) } } diff --git a/crates/bevy_gizmos/src/circles.rs b/crates/bevy_gizmos/src/circles.rs index d24dec62e350e..9c218fe3ab5c1 100644 --- a/crates/bevy_gizmos/src/circles.rs +++ b/crates/bevy_gizmos/src/circles.rs @@ -3,7 +3,7 @@ //! Includes the implementation of [`Gizmos::circle`] and [`Gizmos::circle_2d`], //! and assorted support items. -use crate::prelude::Gizmos; +use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_math::{Quat, Vec2, Vec3}; use bevy_render::color::Color; use std::f32::consts::TAU; @@ -17,7 +17,7 @@ fn circle_inner(radius: f32, segments: usize) -> impl Iterator { }) } -impl<'s> Gizmos<'s> { +impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// Draw a circle in 3D at `position` with the flat side facing `normal`. /// /// This should be called for each frame the circle needs to be rendered. @@ -45,7 +45,7 @@ impl<'s> Gizmos<'s> { normal: Vec3, radius: f32, color: Color, - ) -> CircleBuilder<'_, 's> { + ) -> CircleBuilder<'_, 'w, 's, T> { CircleBuilder { gizmos: self, position, @@ -82,7 +82,7 @@ impl<'s> Gizmos<'s> { position: Vec2, radius: f32, color: Color, - ) -> Circle2dBuilder<'_, 's> { + ) -> Circle2dBuilder<'_, 'w, 's, T> { Circle2dBuilder { gizmos: self, position, @@ -94,8 +94,8 @@ impl<'s> Gizmos<'s> { } /// A builder returned by [`Gizmos::circle`]. -pub struct CircleBuilder<'a, 's> { - gizmos: &'a mut Gizmos<'s>, +pub struct CircleBuilder<'a, 'w, 's, T: GizmoConfigGroup> { + gizmos: &'a mut Gizmos<'w, 's, T>, position: Vec3, normal: Vec3, radius: f32, @@ -103,7 +103,7 @@ pub struct CircleBuilder<'a, 's> { segments: usize, } -impl CircleBuilder<'_, '_> { +impl CircleBuilder<'_, '_, '_, T> { /// Set the number of line-segments for this circle. pub fn segments(mut self, segments: usize) -> Self { self.segments = segments; @@ -111,8 +111,11 @@ impl CircleBuilder<'_, '_> { } } -impl Drop for CircleBuilder<'_, '_> { +impl Drop for CircleBuilder<'_, '_, '_, T> { fn drop(&mut self) { + if !self.gizmos.enabled { + return; + } let rotation = Quat::from_rotation_arc(Vec3::Z, self.normal); let positions = circle_inner(self.radius, self.segments) .map(|vec2| self.position + rotation * vec2.extend(0.)); @@ -121,15 +124,15 @@ impl Drop for CircleBuilder<'_, '_> { } /// A builder returned by [`Gizmos::circle_2d`]. -pub struct Circle2dBuilder<'a, 's> { - gizmos: &'a mut Gizmos<'s>, +pub struct Circle2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> { + gizmos: &'a mut Gizmos<'w, 's, T>, position: Vec2, radius: f32, color: Color, segments: usize, } -impl Circle2dBuilder<'_, '_> { +impl Circle2dBuilder<'_, '_, '_, T> { /// Set the number of line-segments for this circle. pub fn segments(mut self, segments: usize) -> Self { self.segments = segments; @@ -137,8 +140,11 @@ impl Circle2dBuilder<'_, '_> { } } -impl Drop for Circle2dBuilder<'_, '_> { +impl Drop for Circle2dBuilder<'_, '_, '_, T> { fn drop(&mut self) { + if !self.gizmos.enabled { + return; + } let positions = circle_inner(self.radius, self.segments).map(|vec2| vec2 + self.position); self.gizmos.linestrip_2d(positions, self.color); } diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs new file mode 100644 index 0000000000000..c2656268c2342 --- /dev/null +++ b/crates/bevy_gizmos/src/config.rs @@ -0,0 +1,163 @@ +//! A module for the [`GizmoConfig`] [`Resource`]. + +use crate as bevy_gizmos; +pub use bevy_gizmos_macros::GizmoConfigGroup; + +use bevy_ecs::{component::Component, system::Resource}; +use bevy_reflect::{Reflect, TypePath}; +use bevy_render::view::RenderLayers; +use bevy_utils::HashMap; +use core::panic; +use std::{ + any::TypeId, + ops::{Deref, DerefMut}, +}; + +/// A trait used to create gizmo configs groups. +/// +/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`] +/// +/// Make sure to derive [`Default`] + [`Reflect`] and register in the app using `app.init_gizmo_group::()` +pub trait GizmoConfigGroup: Reflect + TypePath + Default {} + +/// The default gizmo config group. +#[derive(Default, Reflect, GizmoConfigGroup)] +pub struct DefaultGizmoConfigGroup; + +/// A [`Resource`] storing [`GizmoConfig`] and [`GizmoConfigGroup`] structs +/// +/// Use `app.init_gizmo_group::()` to register a custom config group. +#[derive(Resource, Default)] +pub struct GizmoConfigStore { + // INVARIANT: must map TypeId::of::() to correct type T + store: HashMap)>, +} + +impl GizmoConfigStore { + /// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`] + pub fn get_config_dyn(&self, config_type_id: &TypeId) -> Option<(&GizmoConfig, &dyn Reflect)> { + let (config, ext) = self.store.get(config_type_id)?; + Some((config, ext.deref())) + } + + /// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T` + pub fn config(&self) -> (&GizmoConfig, &T) { + let Some((config, ext)) = self.get_config_dyn(&TypeId::of::()) else { + panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group()`?", T::type_path()); + }; + // hash map invariant guarantees that &dyn Reflect is of correct type T + let ext = ext.as_any().downcast_ref().unwrap(); + (config, ext) + } + + /// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`] + pub fn get_config_mut_dyn( + &mut self, + config_type_id: &TypeId, + ) -> Option<(&mut GizmoConfig, &mut dyn Reflect)> { + let (config, ext) = self.store.get_mut(config_type_id)?; + Some((config, ext.deref_mut())) + } + + /// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T` + pub fn config_mut(&mut self) -> (&mut GizmoConfig, &mut T) { + let Some((config, ext)) = self.get_config_mut_dyn(&TypeId::of::()) else { + panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group()`?", T::type_path()); + }; + // hash map invariant guarantees that &dyn Reflect is of correct type T + let ext = ext.as_any_mut().downcast_mut().unwrap(); + (config, ext) + } + + /// Returns an iterator over all [`GizmoConfig`]s. + pub fn iter(&self) -> impl Iterator + '_ { + self.store + .iter() + .map(|(id, (config, ext))| (id, config, ext.deref())) + } + + /// Returns an iterator over all [`GizmoConfig`]s, by mutable reference. + pub fn iter_mut( + &mut self, + ) -> impl Iterator + '_ { + self.store + .iter_mut() + .map(|(id, (config, ext))| (id, config, ext.deref_mut())) + } + + /// Inserts [`GizmoConfig`] and [`GizmoConfigGroup`] replacing old values + pub fn insert(&mut self, config: GizmoConfig, ext_config: T) { + // INVARIANT: hash map must correctly map TypeId::of::() to &dyn Reflect of type T + self.store + .insert(TypeId::of::(), (config, Box::new(ext_config))); + } + + pub(crate) fn register(&mut self) { + self.insert(GizmoConfig::default(), T::default()); + } +} + +/// A struct that stores configuration for gizmos. +#[derive(Clone, Reflect)] +pub struct GizmoConfig { + /// Set to `false` to stop drawing gizmos. + /// + /// Defaults to `true`. + pub enabled: bool, + /// Line width specified in pixels. + /// + /// If `line_perspective` is `true` then this is the size in pixels at the camera's near plane. + /// + /// Defaults to `2.0`. + pub line_width: f32, + /// Apply perspective to gizmo lines. + /// + /// This setting only affects 3D, non-orthographic cameras. + /// + /// Defaults to `false`. + pub line_perspective: bool, + /// How closer to the camera than real geometry the line should be. + /// + /// In 2D this setting has no effect and is effectively always -1. + /// + /// Value between -1 and 1 (inclusive). + /// * 0 means that there is no change to the line position when rendering + /// * 1 means it is furthest away from camera as possible + /// * -1 means that it will always render in front of other things. + /// + /// This is typically useful if you are drawing wireframes on top of polygons + /// and your wireframe is z-fighting (flickering on/off) with your main model. + /// You would set this value to a negative number close to 0. + pub depth_bias: f32, + /// Describes which rendering layers gizmos will be rendered to. + /// + /// Gizmos will only be rendered to cameras with intersecting layers. + pub render_layers: RenderLayers, +} + +impl Default for GizmoConfig { + fn default() -> Self { + Self { + enabled: true, + line_width: 2., + line_perspective: false, + depth_bias: 0., + render_layers: Default::default(), + } + } +} + +#[derive(Component)] +pub(crate) struct GizmoMeshConfig { + pub line_perspective: bool, + pub render_layers: RenderLayers, +} + +impl From<&GizmoConfig> for GizmoMeshConfig { + fn from(item: &GizmoConfig) -> Self { + GizmoMeshConfig { + line_perspective: item.line_perspective, + render_layers: item.render_layers, + } + } +} diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index fa2cb10351b26..f4541b7f509e6 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -1,25 +1,33 @@ //! A module for the [`Gizmos`] [`SystemParam`]. -use std::iter; +use std::{iter, marker::PhantomData}; use crate::circles::DEFAULT_CIRCLE_SEGMENTS; use bevy_ecs::{ - system::{Deferred, Resource, SystemBuffer, SystemMeta, SystemParam}, - world::World, + component::Tick, + system::{Deferred, ReadOnlySystemParam, Res, Resource, SystemBuffer, SystemMeta, SystemParam}, + world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use bevy_math::{Mat2, Quat, Vec2, Vec3}; use bevy_render::color::Color; use bevy_transform::TransformPoint; +use crate::{ + config::GizmoConfigGroup, + config::{DefaultGizmoConfigGroup, GizmoConfigStore}, + prelude::GizmoConfig, +}; + type PositionItem = [f32; 3]; type ColorItem = [f32; 4]; #[derive(Resource, Default)] -pub(crate) struct GizmoStorage { +pub(crate) struct GizmoStorage { pub list_positions: Vec, pub list_colors: Vec, pub strip_positions: Vec, pub strip_colors: Vec, + marker: PhantomData, } /// A [`SystemParam`] for drawing gizmos. @@ -27,22 +35,82 @@ pub(crate) struct GizmoStorage { /// They are drawn in immediate mode, which means they will be rendered only for /// the frames in which they are spawned. /// Gizmos should be spawned before the [`Last`](bevy_app::Last) schedule to ensure they are drawn. -#[derive(SystemParam)] -pub struct Gizmos<'s> { - buffer: Deferred<'s, GizmoBuffer>, +pub struct Gizmos<'w, 's, T: GizmoConfigGroup = DefaultGizmoConfigGroup> { + buffer: Deferred<'s, GizmoBuffer>, + pub(crate) enabled: bool, + /// The currently used [`GizmoConfig`] + pub config: &'w GizmoConfig, + /// The currently used [`GizmoConfigGroup`] + pub config_ext: &'w T, +} + +type GizmosState = ( + Deferred<'static, GizmoBuffer>, + Res<'static, GizmoConfigStore>, +); +#[doc(hidden)] +pub struct GizmosFetchState { + state: as SystemParam>::State, +} +// SAFETY: All methods are delegated to existing `SystemParam` implemntations +unsafe impl SystemParam for Gizmos<'_, '_, T> { + type State = GizmosFetchState; + type Item<'w, 's> = Gizmos<'w, 's, T>; + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + GizmosFetchState { + state: GizmosState::::init_state(world, system_meta), + } + } + fn new_archetype( + state: &mut Self::State, + archetype: &bevy_ecs::archetype::Archetype, + system_meta: &mut SystemMeta, + ) { + GizmosState::::new_archetype(&mut state.state, archetype, system_meta); + } + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + GizmosState::::apply(&mut state.state, system_meta, world); + } + unsafe fn get_param<'w, 's>( + state: &'s mut Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell<'w>, + change_tick: Tick, + ) -> Self::Item<'w, 's> { + let (f0, f1) = + GizmosState::::get_param(&mut state.state, system_meta, world, change_tick); + // Accessing the GizmoConfigStore in the immediate mode API reduces performance significantly. + // Implementing SystemParam manually allows us to do it to here + // Having config available allows for early returns when gizmos are disabled + let (config, config_ext) = f1.into_inner().config::(); + Gizmos { + buffer: f0, + enabled: config.enabled, + config, + config_ext, + } + } +} +// Safety: Each field is `ReadOnlySystemParam`, and Gizmos SystemParam does not mutate world +unsafe impl<'w, 's, T: GizmoConfigGroup> ReadOnlySystemParam for Gizmos<'w, 's, T> +where + Deferred<'s, GizmoBuffer>: ReadOnlySystemParam, + Res<'w, GizmoConfigStore>: ReadOnlySystemParam, +{ } #[derive(Default)] -struct GizmoBuffer { +struct GizmoBuffer { list_positions: Vec, list_colors: Vec, strip_positions: Vec, strip_colors: Vec, + marker: PhantomData, } -impl SystemBuffer for GizmoBuffer { +impl SystemBuffer for GizmoBuffer { fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { - let mut storage = world.resource_mut::(); + let mut storage = world.resource_mut::>(); storage.list_positions.append(&mut self.list_positions); storage.list_colors.append(&mut self.list_colors); storage.strip_positions.append(&mut self.strip_positions); @@ -50,7 +118,7 @@ impl SystemBuffer for GizmoBuffer { } } -impl<'s> Gizmos<'s> { +impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// Draw a line in 3D from `start` to `end`. /// /// This should be called for each frame the line needs to be rendered. @@ -67,6 +135,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn line(&mut self, start: Vec3, end: Vec3, color: Color) { + if !self.enabled { + return; + } self.extend_list_positions([start, end]); self.add_list_color(color, 2); } @@ -87,6 +158,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn line_gradient(&mut self, start: Vec3, end: Vec3, start_color: Color, end_color: Color) { + if !self.enabled { + return; + } self.extend_list_positions([start, end]); self.extend_list_colors([start_color, end_color]); } @@ -107,6 +181,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) { + if !self.enabled { + return; + } self.line(start, start + vector, color); } @@ -132,6 +209,9 @@ impl<'s> Gizmos<'s> { start_color: Color, end_color: Color, ) { + if !self.enabled { + return; + } self.line_gradient(start, start + vector, start_color, end_color); } @@ -151,6 +231,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn linestrip(&mut self, positions: impl IntoIterator, color: Color) { + if !self.enabled { + return; + } self.extend_strip_positions(positions); let len = self.buffer.strip_positions.len(); self.buffer @@ -179,6 +262,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn linestrip_gradient(&mut self, points: impl IntoIterator) { + if !self.enabled { + return; + } let points = points.into_iter(); let GizmoBuffer { @@ -227,7 +313,7 @@ impl<'s> Gizmos<'s> { rotation: Quat, radius: f32, color: Color, - ) -> SphereBuilder<'_, 's> { + ) -> SphereBuilder<'_, 'w, 's, T> { SphereBuilder { gizmos: self, position, @@ -254,6 +340,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn rect(&mut self, position: Vec3, rotation: Quat, size: Vec2, color: Color) { + if !self.enabled { + return; + } let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2.extend(0.)); self.linestrip([tl, tr, br, bl, tl], color); } @@ -274,6 +363,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn cuboid(&mut self, transform: impl TransformPoint, color: Color) { + if !self.enabled { + return; + } let rect = rect_inner(Vec2::ONE); // Front let [tlf, trf, brf, blf] = rect.map(|vec2| transform.transform_point(vec2.extend(0.5))); @@ -309,6 +401,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn line_2d(&mut self, start: Vec2, end: Vec2, color: Color) { + if !self.enabled { + return; + } self.line(start.extend(0.), end.extend(0.), color); } @@ -334,6 +429,9 @@ impl<'s> Gizmos<'s> { start_color: Color, end_color: Color, ) { + if !self.enabled { + return; + } self.line_gradient(start.extend(0.), end.extend(0.), start_color, end_color); } @@ -353,6 +451,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn linestrip_2d(&mut self, positions: impl IntoIterator, color: Color) { + if !self.enabled { + return; + } self.linestrip(positions.into_iter().map(|vec2| vec2.extend(0.)), color); } @@ -376,6 +477,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn linestrip_gradient_2d(&mut self, positions: impl IntoIterator) { + if !self.enabled { + return; + } self.linestrip_gradient( positions .into_iter() @@ -399,6 +503,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn ray_2d(&mut self, start: Vec2, vector: Vec2, color: Color) { + if !self.enabled { + return; + } self.line_2d(start, start + vector, color); } @@ -424,6 +531,9 @@ impl<'s> Gizmos<'s> { start_color: Color, end_color: Color, ) { + if !self.enabled { + return; + } self.line_gradient_2d(start, start + vector, start_color, end_color); } @@ -443,6 +553,9 @@ impl<'s> Gizmos<'s> { /// ``` #[inline] pub fn rect_2d(&mut self, position: Vec2, rotation: f32, size: Vec2, color: Color) { + if !self.enabled { + return; + } let rotation = Mat2::from_angle(rotation); let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2); self.linestrip_2d([tl, tr, br, bl, tl], color); @@ -481,8 +594,8 @@ impl<'s> Gizmos<'s> { } /// A builder returned by [`Gizmos::sphere`]. -pub struct SphereBuilder<'a, 's> { - gizmos: &'a mut Gizmos<'s>, +pub struct SphereBuilder<'a, 'w, 's, T: GizmoConfigGroup> { + gizmos: &'a mut Gizmos<'w, 's, T>, position: Vec3, rotation: Quat, radius: f32, @@ -490,7 +603,7 @@ pub struct SphereBuilder<'a, 's> { circle_segments: usize, } -impl SphereBuilder<'_, '_> { +impl SphereBuilder<'_, '_, '_, T> { /// Set the number of line-segments per circle for this sphere. pub fn circle_segments(mut self, segments: usize) -> Self { self.circle_segments = segments; @@ -498,8 +611,11 @@ impl SphereBuilder<'_, '_> { } } -impl Drop for SphereBuilder<'_, '_> { +impl Drop for SphereBuilder<'_, '_, '_, T> { fn drop(&mut self) { + if !self.gizmos.enabled { + return; + } for axis in Vec3::AXES { self.gizmos .circle(self.position, self.rotation * axis, self.radius, self.color) diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 43c2c547261e5..daeed30990d7e 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -13,7 +13,7 @@ //! # bevy_ecs::system::assert_is_system(system); //! ``` //! -//! See the documentation on [`Gizmos`] for more examples. +//! See the documentation on [Gizmos](crate::gizmos::Gizmos) for more examples. /// Label for the the render systems handling the #[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)] @@ -26,9 +26,11 @@ pub enum GizmoRenderSystem { QueueLineGizmos3d, } +pub mod aabb; pub mod arcs; pub mod arrows; pub mod circles; +pub mod config; pub mod gizmos; #[cfg(feature = "bevy_sprite")] @@ -39,29 +41,30 @@ mod pipeline_3d; /// The `bevy_gizmos` prelude. pub mod prelude { #[doc(hidden)] - pub use crate::{gizmos::Gizmos, AabbGizmo, AabbGizmoConfig, GizmoConfig}; + pub use crate::{ + aabb::{AabbGizmoConfigGroup, ShowAabbGizmo}, + config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore}, + gizmos::Gizmos, + AppGizmoBuilder, + }; } -use bevy_app::{Last, Plugin, PostUpdate}; +use aabb::AabbGizmoPlugin; +use bevy_app::{App, Last, Plugin}; use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; use bevy_core::cast_slice; use bevy_ecs::{ - change_detection::DetectChanges, component::Component, - entity::Entity, - query::{ROQueryItem, Without}, - reflect::{ReflectComponent, ReflectResource}, + query::ROQueryItem, schedule::{IntoSystemConfigs, SystemSet}, system::{ lifetimeless::{Read, SRes}, - Commands, Query, Res, ResMut, Resource, SystemParamItem, + Commands, Res, ResMut, Resource, SystemParamItem, }, }; -use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; +use bevy_reflect::TypePath; use bevy_render::{ - color::Color, extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, - primitives::Aabb, render_asset::{ PrepareAssetError, RenderAsset, RenderAssetPersistencePolicy, RenderAssetPlugin, RenderAssets, @@ -73,15 +76,14 @@ use bevy_render::{ ShaderType, VertexAttribute, VertexBufferLayout, VertexFormat, VertexStepMode, }, renderer::RenderDevice, - view::RenderLayers, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_transform::{ - components::{GlobalTransform, Transform}, - TransformSystem, +use bevy_utils::{tracing::warn, HashMap}; +use config::{ + DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoMeshConfig, }; -use gizmos::{GizmoStorage, Gizmos}; -use std::mem; +use gizmos::GizmoStorage; +use std::{any::TypeId, mem}; const LINE_SHADER_HANDLE: Handle = Handle::weak_from_u128(7414812689238026784); @@ -97,33 +99,22 @@ impl Plugin for GizmoPlugin { 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::() - .init_resource::() - .init_resource::() - .add_systems(Last, update_gizmo_meshes) - .add_systems( - PostUpdate, - ( - draw_aabbs, - draw_all_aabbs.run_if(|config: Res| config.aabb.draw_all), - ) - .after(TransformSystem::TransformPropagate), - ); + .init_resource::() + .init_gizmo_group::() + .add_plugins(AabbGizmoPlugin); let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; - render_app - .add_systems(ExtractSchedule, extract_gizmo_data) - .add_systems( - Render, - prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups), - ); + render_app.add_systems( + Render, + prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups), + ); #[cfg(feature = "bevy_sprite")] app.add_plugins(pipeline_2d::LineGizmo2dPlugin); @@ -149,152 +140,51 @@ impl Plugin for GizmoPlugin { } } -/// A [`Resource`] that stores configuration for gizmos. -#[derive(Resource, Clone, Reflect)] -#[reflect(Resource)] -pub struct GizmoConfig { - /// Set to `false` to stop drawing gizmos. - /// - /// Defaults to `true`. - pub enabled: bool, - /// Line width specified in pixels. - /// - /// If `line_perspective` is `true` then this is the size in pixels at the camera's near plane. - /// - /// Defaults to `2.0`. - pub line_width: f32, - /// Apply perspective to gizmo lines. - /// - /// This setting only affects 3D, non-orthographic cameras. - /// - /// Defaults to `false`. - pub line_perspective: bool, - /// How closer to the camera than real geometry the line should be. - /// - /// In 2D this setting has no effect and is effectively always -1. - /// - /// Value between -1 and 1 (inclusive). - /// * 0 means that there is no change to the line position when rendering - /// * 1 means it is furthest away from camera as possible - /// * -1 means that it will always render in front of other things. +/// 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). /// - /// This is typically useful if you are drawing wireframes on top of polygons - /// and your wireframe is z-fighting (flickering on/off) with your main model. - /// You would set this value to a negative number close to 0. - pub depth_bias: f32, - /// Configuration for the [`AabbGizmo`]. - pub aabb: AabbGizmoConfig, - /// Describes which rendering layers gizmos will be rendered to. - /// - /// Gizmos will only be rendered to cameras with intersecting layers. - pub render_layers: RenderLayers, + /// Configurations can be set using the [`GizmoConfigStore`] [`Resource`]. + fn init_gizmo_group(&mut self) -> &mut Self; } -impl Default for GizmoConfig { - fn default() -> Self { - Self { - enabled: true, - line_width: 2., - line_perspective: false, - depth_bias: 0., - aabb: Default::default(), - render_layers: Default::default(), +impl AppGizmoBuilder for App { + fn init_gizmo_group(&mut self) -> &mut Self { + if self.world.contains_resource::>() { + return self; } - } -} -/// Configuration for drawing the [`Aabb`] component on entities. -#[derive(Clone, Default, Reflect)] -pub struct AabbGizmoConfig { - /// Draws all bounding boxes in the scene when set to `true`. - /// - /// To draw a specific entity's bounding box, you can add the [`AabbGizmo`] component. - /// - /// Defaults to `false`. - pub draw_all: bool, - /// The default color for bounding box gizmos. - /// - /// A random color is chosen per box if `None`. - /// - /// Defaults to `None`. - pub default_color: Option, -} + self.init_resource::>() + .add_systems(Last, update_gizmo_meshes::); -/// Add this [`Component`] to an entity to draw its [`Aabb`] component. -#[derive(Component, Reflect, Default, Debug)] -#[reflect(Component, Default)] -pub struct AabbGizmo { - /// The color of the box. - /// - /// The default color from the [`GizmoConfig`] resource is used if `None`, - pub color: Option, -} - -fn draw_aabbs( - query: Query<(Entity, &Aabb, &GlobalTransform, &AabbGizmo)>, - config: Res, - mut gizmos: Gizmos, -) { - for (entity, &aabb, &transform, gizmo) in &query { - let color = gizmo - .color - .or(config.aabb.default_color) - .unwrap_or_else(|| color_from_entity(entity)); - gizmos.cuboid(aabb_transform(aabb, transform), color); - } -} - -fn draw_all_aabbs( - query: Query<(Entity, &Aabb, &GlobalTransform), Without>, - config: Res, - mut gizmos: Gizmos, -) { - for (entity, &aabb, &transform) in &query { - let color = config - .aabb - .default_color - .unwrap_or_else(|| color_from_entity(entity)); - gizmos.cuboid(aabb_transform(aabb, transform), color); - } -} + self.world + .resource_mut::() + .register::(); -fn color_from_entity(entity: Entity) -> Color { - let index = entity.index(); - - // from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/ - // - // See https://en.wikipedia.org/wiki/Low-discrepancy_sequence - // Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range, - // so that the closer the numbers are, the larger the difference of their image. - const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up - const RATIO_360: f32 = 360.0 / u32::MAX as f32; - let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360; + let Ok(render_app) = self.get_sub_app_mut(RenderApp) else { + return self; + }; - Color::hsl(hue, 1., 0.5) -} + render_app.add_systems(ExtractSchedule, extract_gizmo_data::); -fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform { - transform - * GlobalTransform::from( - Transform::from_translation(aabb.center.into()) - .with_scale((aabb.half_extents * 2.).into()), - ) + self + } } #[derive(Resource, Default)] struct LineGizmoHandles { - list: Option>, - strip: Option>, + list: HashMap>, + strip: HashMap>, } -fn update_gizmo_meshes( +fn update_gizmo_meshes( mut line_gizmos: ResMut>, mut handles: ResMut, - mut storage: ResMut, + mut storage: ResMut>, ) { if storage.list_positions.is_empty() { - handles.list = None; - } else if let Some(handle) = handles.list.as_ref() { + handles.list.remove(&TypeId::of::()); + } else if let Some(handle) = handles.list.get(&TypeId::of::()) { let list = line_gizmos.get_mut(handle).unwrap(); list.positions = mem::take(&mut storage.list_positions); @@ -308,12 +198,14 @@ fn update_gizmo_meshes( list.positions = mem::take(&mut storage.list_positions); list.colors = mem::take(&mut storage.list_colors); - handles.list = Some(line_gizmos.add(list)); + handles + .list + .insert(TypeId::of::(), line_gizmos.add(list)); } if storage.strip_positions.is_empty() { - handles.strip = None; - } else if let Some(handle) = handles.strip.as_ref() { + handles.strip.remove(&TypeId::of::()); + } else if let Some(handle) = handles.strip.get(&TypeId::of::()) { let strip = line_gizmos.get_mut(handle).unwrap(); strip.positions = mem::take(&mut storage.strip_positions); @@ -327,24 +219,27 @@ fn update_gizmo_meshes( strip.positions = mem::take(&mut storage.strip_positions); strip.colors = mem::take(&mut storage.strip_colors); - handles.strip = Some(line_gizmos.add(strip)); + handles + .strip + .insert(TypeId::of::(), line_gizmos.add(strip)); } } -fn extract_gizmo_data( +fn extract_gizmo_data( mut commands: Commands, handles: Extract>, - config: Extract>, + config: Extract>, ) { - if config.is_changed() { - commands.insert_resource(config.clone()); - } + let (config, _) = config.config::(); if !config.enabled { return; } - for handle in [&handles.list, &handles.strip].into_iter().flatten() { + for map in [&handles.list, &handles.strip].into_iter() { + let Some(handle) = map.get(&TypeId::of::()) else { + continue; + }; commands.spawn(( LineGizmoUniform { line_width: config.line_width, @@ -352,7 +247,8 @@ fn extract_gizmo_data( #[cfg(feature = "webgl")] _padding: Default::default(), }, - handle.clone_weak(), + (*handle).clone_weak(), + GizmoMeshConfig::from(config), )); } } diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index dd53a40e5fc9e..f55453fa67035 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -1,6 +1,6 @@ use crate::{ - line_gizmo_vertex_buffer_layouts, DrawLineGizmo, GizmoConfig, GizmoRenderSystem, LineGizmo, - LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_SHADER_HANDLE, + config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts, DrawLineGizmo, GizmoRenderSystem, + LineGizmo, LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_SHADER_HANDLE, }; use bevy_app::{App, Plugin}; use bevy_asset::Handle; @@ -144,8 +144,7 @@ fn queue_line_gizmos_2d( mut pipelines: ResMut>, pipeline_cache: Res, msaa: Res, - config: Res, - line_gizmos: Query<(Entity, &Handle)>, + line_gizmos: Query<(Entity, &Handle, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut views: Query<( &ExtractedView, @@ -156,14 +155,15 @@ fn queue_line_gizmos_2d( let draw_function = draw_functions.read().get_id::().unwrap(); for (view, mut transparent_phase, render_layers) in &mut views { - let render_layers = render_layers.copied().unwrap_or_default(); - if !config.render_layers.intersects(&render_layers) { - continue; - } let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr); - for (entity, handle) in &line_gizmos { + for (entity, handle, config) in &line_gizmos { + let render_layers = render_layers.copied().unwrap_or_default(); + if !config.render_layers.intersects(&render_layers) { + continue; + } + let Some(line_gizmo) = line_gizmo_assets.get(handle) else { continue; }; diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 9ea991e4753a2..bd5064e39d789 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -1,6 +1,6 @@ use crate::{ - line_gizmo_vertex_buffer_layouts, DrawLineGizmo, GizmoConfig, GizmoRenderSystem, LineGizmo, - LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_SHADER_HANDLE, + config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts, DrawLineGizmo, GizmoRenderSystem, + LineGizmo, LineGizmoUniformBindgroupLayout, SetLineGizmoBindGroup, LINE_SHADER_HANDLE, }; use bevy_app::{App, Plugin}; use bevy_asset::Handle; @@ -159,8 +159,7 @@ fn queue_line_gizmos_3d( mut pipelines: ResMut>, pipeline_cache: Res, msaa: Res, - config: Res, - line_gizmos: Query<(Entity, &Handle)>, + line_gizmos: Query<(Entity, &Handle, &GizmoMeshConfig)>, line_gizmo_assets: Res>, mut views: Query<( &ExtractedView, @@ -184,9 +183,6 @@ fn queue_line_gizmos_3d( ) in &mut views { let render_layers = render_layers.copied().unwrap_or_default(); - if !config.render_layers.intersects(&render_layers) { - continue; - } let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples()) | MeshPipelineKey::from_hdr(view.hdr); @@ -207,7 +203,11 @@ fn queue_line_gizmos_3d( view_key |= MeshPipelineKey::DEFERRED_PREPASS; } - for (entity, handle) in &line_gizmos { + for (entity, handle, config) in &line_gizmos { + if !config.render_layers.intersects(&render_layers) { + continue; + } + let Some(line_gizmo) = line_gizmo_assets.get(handle) else { continue; }; diff --git a/examples/2d/2d_gizmos.rs b/examples/2d/2d_gizmos.rs index d3dcd7c960f6a..a314809a8857f 100644 --- a/examples/2d/2d_gizmos.rs +++ b/examples/2d/2d_gizmos.rs @@ -7,16 +7,23 @@ use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins) + .init_gizmo_group::() .add_systems(Startup, setup) .add_systems(Update, (system, update_config)) .run(); } +// We can create our own gizmo config group! +#[derive(Default, Reflect, GizmoConfigGroup)] +struct MyRoundGizmos {} + fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2dBundle::default()); // text commands.spawn(TextBundle::from_section( - "Hold 'Left' or 'Right' to change the line width", + "Hold 'Left' or 'Right' to change the line width of straight gizmos\n\ + Hold 'Up' or 'Down' to change the line width of round gizmos\n\ + Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos", TextStyle { font: asset_server.load("fonts/FiraMono-Medium.ttf"), font_size: 24., @@ -25,7 +32,7 @@ fn setup(mut commands: Commands, asset_server: Res) { )); } -fn system(mut gizmos: Gizmos, time: Res