From f6b40a6e430ce0e3c9237b9afa57c96b2751e28c Mon Sep 17 00:00:00 2001 From: jeliag Date: Thu, 18 Jan 2024 16:52:50 +0100 Subject: [PATCH 1/4] Multiple Configurations for Gizmos (#10342) # Objective This PR aims to implement multiple configs for gizmos as discussed in #9187. ## Solution Configs for the new `GizmoConfigGroup`s are stored in a `GizmoConfigStore` resource and can be accesses using a type based key or iterated over. This type based key doubles as a standardized location where plugin authors can put their own configuration not covered by the standard `GizmoConfig` struct. For example the `AabbGizmoGroup` has a default color and toggle to show all AABBs. New configs can be registered using `app.init_gizmo_group::()` during startup. When requesting the `Gizmos` system parameter the generic type determines which config is used. The config structs are available through the `Gizmos` system parameter allowing for easy access while drawing your gizmos. Internally, resources and systems used for rendering (up to an including the extract system) are generic over the type based key and inserted on registering a new config. ## Alternatives The configs could be stored as components on entities with markers which would make better use of the ECS. I also implemented this approach ([here](https://github.com/jeliag/bevy/tree/gizmo-multiconf-comp)) and believe that the ergonomic benefits of a central config store outweigh the decreased use of the ECS. ## Unsafe Code Implementing system parameter by hand is unsafe but seems to be required to access the config store once and not on every gizmo draw function call. This is critical for performance. ~Is there a better way to do this?~ ## Future Work New gizmos (such as #10038, and ideas from #9400) will require custom configuration structs. Should there be a new custom config for every gizmo type, or should we group them together in a common configuration? (for example `EditorGizmoConfig`, or something more fine-grained) ## Changelog - Added `GizmoConfigStore` resource and `GizmoConfigGroup` trait - Added `init_gizmo_group` to `App` - Added early returns to gizmo drawing increasing performance when gizmos are disabled - Changed `GizmoConfig` and aabb gizmos to use new `GizmoConfigStore` - Changed `Gizmos` system parameter to use type based key to retrieve config - Changed resources and systems used for gizmo rendering to be generic over type based key - Changed examples (3d_gizmos, 2d_gizmos) to showcase new API ## Migration Guide - `GizmoConfig` is no longer a resource and has to be accessed through `GizmoConfigStore` resource. The default config group is `DefaultGizmoGroup`, but consider using your own custom config group if applicable. --------- Co-authored-by: Nicola Papale --- crates/bevy_gizmos/Cargo.toml | 1 + crates/bevy_gizmos/macros/Cargo.toml | 19 ++ crates/bevy_gizmos/macros/src/lib.rs | 23 ++ crates/bevy_gizmos/src/aabb.rs | 120 +++++++++ crates/bevy_gizmos/src/arcs.rs | 17 +- crates/bevy_gizmos/src/arrows.rs | 24 +- crates/bevy_gizmos/src/circles.rs | 30 ++- crates/bevy_gizmos/src/config.rs | 163 ++++++++++++ crates/bevy_gizmos/src/gizmos.rs | 148 +++++++++-- crates/bevy_gizmos/src/lib.rs | 242 +++++------------- crates/bevy_gizmos/src/pipeline_2d.rs | 18 +- crates/bevy_gizmos/src/pipeline_3d.rs | 16 +- examples/2d/2d_gizmos.rs | 40 ++- examples/3d/3d_gizmos.rs | 63 ++++- .../tools/scene_viewer/scene_viewer_plugin.rs | 4 +- 15 files changed, 674 insertions(+), 254 deletions(-) create mode 100644 crates/bevy_gizmos/macros/Cargo.toml create mode 100644 crates/bevy_gizmos/macros/src/lib.rs create mode 100644 crates/bevy_gizmos/src/aabb.rs create mode 100644 crates/bevy_gizmos/src/config.rs 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