Skip to content

Commit f6b40a6

Browse files
jeliagnicopap
andauthored
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::<T>()` during startup. When requesting the `Gizmos<T>` 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 <[email protected]>
1 parent c9e1fcd commit f6b40a6

File tree

15 files changed

+674
-254
lines changed

15 files changed

+674
-254
lines changed

crates/bevy_gizmos/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.12.0" }
2626
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0" }
2727
bevy_transform = { path = "../bevy_transform", version = "0.12.0" }
2828
bevy_log = { path = "../bevy_log", version = "0.12.0" }
29+
bevy_gizmos_macros = { path = "macros", version = "0.12.0" }
2930

3031
[lints]
3132
workspace = true

crates/bevy_gizmos/macros/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "bevy_gizmos_macros"
3+
version = "0.12.0"
4+
edition = "2021"
5+
description = "Derive implementations for bevy_gizmos"
6+
homepage = "https://bevyengine.org"
7+
repository = "https://github.com/bevyengine/bevy"
8+
license = "MIT OR Apache-2.0"
9+
keywords = ["bevy"]
10+
11+
[lib]
12+
proc-macro = true
13+
14+
[dependencies]
15+
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.12.0" }
16+
17+
syn = "2.0"
18+
proc-macro2 = "1.0"
19+
quote = "1.0"

crates/bevy_gizmos/macros/src/lib.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use bevy_macro_utils::BevyManifest;
2+
use proc_macro::TokenStream;
3+
use quote::quote;
4+
use syn::{parse_macro_input, parse_quote, DeriveInput, Path};
5+
6+
#[proc_macro_derive(GizmoConfigGroup)]
7+
pub fn derive_gizmo_config_group(input: TokenStream) -> TokenStream {
8+
let mut ast = parse_macro_input!(input as DeriveInput);
9+
let bevy_gizmos_path: Path = BevyManifest::default().get_path("bevy_gizmos");
10+
let bevy_reflect_path: Path = BevyManifest::default().get_path("bevy_reflect");
11+
12+
ast.generics.make_where_clause().predicates.push(
13+
parse_quote! { Self: #bevy_reflect_path::Reflect + #bevy_reflect_path::TypePath + Default},
14+
);
15+
16+
let struct_name = &ast.ident;
17+
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
18+
19+
TokenStream::from(quote! {
20+
impl #impl_generics #bevy_gizmos_path::config::GizmoConfigGroup for #struct_name #type_generics #where_clause {
21+
}
22+
})
23+
}

crates/bevy_gizmos/src/aabb.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//! A module adding debug visualization of [`Aabb`]s.
2+
3+
use crate as bevy_gizmos;
4+
5+
use bevy_app::{Plugin, PostUpdate};
6+
use bevy_ecs::{
7+
component::Component,
8+
entity::Entity,
9+
query::Without,
10+
reflect::ReflectComponent,
11+
schedule::IntoSystemConfigs,
12+
system::{Query, Res},
13+
};
14+
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
15+
use bevy_render::{color::Color, primitives::Aabb};
16+
use bevy_transform::{
17+
components::{GlobalTransform, Transform},
18+
TransformSystem,
19+
};
20+
21+
use crate::{
22+
config::{GizmoConfigGroup, GizmoConfigStore},
23+
gizmos::Gizmos,
24+
AppGizmoBuilder,
25+
};
26+
27+
/// A [`Plugin`] that provides visualization of [`Aabb`]s for debugging.
28+
pub struct AabbGizmoPlugin;
29+
30+
impl Plugin for AabbGizmoPlugin {
31+
fn build(&self, app: &mut bevy_app::App) {
32+
app.register_type::<AabbGizmoConfigGroup>()
33+
.init_gizmo_group::<AabbGizmoConfigGroup>()
34+
.add_systems(
35+
PostUpdate,
36+
(
37+
draw_aabbs,
38+
draw_all_aabbs.run_if(|config: Res<GizmoConfigStore>| {
39+
config.config::<AabbGizmoConfigGroup>().1.draw_all
40+
}),
41+
)
42+
.after(TransformSystem::TransformPropagate),
43+
);
44+
}
45+
}
46+
/// The [`GizmoConfigGroup`] used for debug visualizations of [`Aabb`] components on entities
47+
#[derive(Clone, Default, Reflect, GizmoConfigGroup)]
48+
pub struct AabbGizmoConfigGroup {
49+
/// Draws all bounding boxes in the scene when set to `true`.
50+
///
51+
/// To draw a specific entity's bounding box, you can add the [`ShowAabbGizmo`] component.
52+
///
53+
/// Defaults to `false`.
54+
pub draw_all: bool,
55+
/// The default color for bounding box gizmos.
56+
///
57+
/// A random color is chosen per box if `None`.
58+
///
59+
/// Defaults to `None`.
60+
pub default_color: Option<Color>,
61+
}
62+
63+
/// Add this [`Component`] to an entity to draw its [`Aabb`] component.
64+
#[derive(Component, Reflect, Default, Debug)]
65+
#[reflect(Component, Default)]
66+
pub struct ShowAabbGizmo {
67+
/// The color of the box.
68+
///
69+
/// The default color from the [`AabbGizmoConfigGroup`] config is used if `None`,
70+
pub color: Option<Color>,
71+
}
72+
73+
fn draw_aabbs(
74+
query: Query<(Entity, &Aabb, &GlobalTransform, &ShowAabbGizmo)>,
75+
mut gizmos: Gizmos<AabbGizmoConfigGroup>,
76+
) {
77+
for (entity, &aabb, &transform, gizmo) in &query {
78+
let color = gizmo
79+
.color
80+
.or(gizmos.config_ext.default_color)
81+
.unwrap_or_else(|| color_from_entity(entity));
82+
gizmos.cuboid(aabb_transform(aabb, transform), color);
83+
}
84+
}
85+
86+
fn draw_all_aabbs(
87+
query: Query<(Entity, &Aabb, &GlobalTransform), Without<ShowAabbGizmo>>,
88+
mut gizmos: Gizmos<AabbGizmoConfigGroup>,
89+
) {
90+
for (entity, &aabb, &transform) in &query {
91+
let color = gizmos
92+
.config_ext
93+
.default_color
94+
.unwrap_or_else(|| color_from_entity(entity));
95+
gizmos.cuboid(aabb_transform(aabb, transform), color);
96+
}
97+
}
98+
99+
fn color_from_entity(entity: Entity) -> Color {
100+
let index = entity.index();
101+
102+
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
103+
//
104+
// See https://en.wikipedia.org/wiki/Low-discrepancy_sequence
105+
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
106+
// so that the closer the numbers are, the larger the difference of their image.
107+
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
108+
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
109+
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
110+
111+
Color::hsl(hue, 1., 0.5)
112+
}
113+
114+
fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform {
115+
transform
116+
* GlobalTransform::from(
117+
Transform::from_translation(aabb.center.into())
118+
.with_scale((aabb.half_extents * 2.).into()),
119+
)
120+
}

crates/bevy_gizmos/src/arcs.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
//! and assorted support items.
55
66
use crate::circles::DEFAULT_CIRCLE_SEGMENTS;
7-
use crate::prelude::Gizmos;
7+
use crate::prelude::{GizmoConfigGroup, Gizmos};
88
use bevy_math::Vec2;
99
use bevy_render::color::Color;
1010
use std::f32::consts::TAU;
1111

12-
impl<'s> Gizmos<'s> {
12+
impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
1313
/// Draw an arc, which is a part of the circumference of a circle, in 2D.
1414
///
1515
/// This should be called for each frame the arc needs to be rendered.
@@ -46,7 +46,7 @@ impl<'s> Gizmos<'s> {
4646
arc_angle: f32,
4747
radius: f32,
4848
color: Color,
49-
) -> Arc2dBuilder<'_, 's> {
49+
) -> Arc2dBuilder<'_, 'w, 's, T> {
5050
Arc2dBuilder {
5151
gizmos: self,
5252
position,
@@ -60,8 +60,8 @@ impl<'s> Gizmos<'s> {
6060
}
6161

6262
/// A builder returned by [`Gizmos::arc_2d`].
63-
pub struct Arc2dBuilder<'a, 's> {
64-
gizmos: &'a mut Gizmos<'s>,
63+
pub struct Arc2dBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
64+
gizmos: &'a mut Gizmos<'w, 's, T>,
6565
position: Vec2,
6666
direction_angle: f32,
6767
arc_angle: f32,
@@ -70,16 +70,19 @@ pub struct Arc2dBuilder<'a, 's> {
7070
segments: Option<usize>,
7171
}
7272

73-
impl Arc2dBuilder<'_, '_> {
73+
impl<T: GizmoConfigGroup> Arc2dBuilder<'_, '_, '_, T> {
7474
/// Set the number of line-segments for this arc.
7575
pub fn segments(mut self, segments: usize) -> Self {
7676
self.segments = Some(segments);
7777
self
7878
}
7979
}
8080

81-
impl Drop for Arc2dBuilder<'_, '_> {
81+
impl<T: GizmoConfigGroup> Drop for Arc2dBuilder<'_, '_, '_, T> {
8282
fn drop(&mut self) {
83+
if !self.gizmos.enabled {
84+
return;
85+
}
8386
let segments = match self.segments {
8487
Some(segments) => segments,
8588
// Do a linear interpolation between 1 and `DEFAULT_CIRCLE_SEGMENTS`

crates/bevy_gizmos/src/arrows.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
//! Includes the implementation of [`Gizmos::arrow`] and [`Gizmos::arrow_2d`],
44
//! and assorted support items.
55
6-
use crate::prelude::Gizmos;
6+
use crate::prelude::{GizmoConfigGroup, Gizmos};
77
use bevy_math::{Quat, Vec2, Vec3};
88
use bevy_render::color::Color;
99

1010
/// A builder returned by [`Gizmos::arrow`] and [`Gizmos::arrow_2d`]
11-
pub struct ArrowBuilder<'a, 's> {
12-
gizmos: &'a mut Gizmos<'s>,
11+
pub struct ArrowBuilder<'a, 'w, 's, T: GizmoConfigGroup> {
12+
gizmos: &'a mut Gizmos<'w, 's, T>,
1313
start: Vec3,
1414
end: Vec3,
1515
color: Color,
1616
tip_length: f32,
1717
}
1818

19-
impl ArrowBuilder<'_, '_> {
19+
impl<T: GizmoConfigGroup> ArrowBuilder<'_, '_, '_, T> {
2020
/// Change the length of the tips to be `length`.
2121
/// The default tip length is [length of the arrow]/10.
2222
///
@@ -37,9 +37,12 @@ impl ArrowBuilder<'_, '_> {
3737
}
3838
}
3939

40-
impl Drop for ArrowBuilder<'_, '_> {
40+
impl<T: GizmoConfigGroup> Drop for ArrowBuilder<'_, '_, '_, T> {
4141
/// Draws the arrow, by drawing lines with the stored [`Gizmos`]
4242
fn drop(&mut self) {
43+
if !self.gizmos.enabled {
44+
return;
45+
}
4346
// first, draw the body of the arrow
4447
self.gizmos.line(self.start, self.end, self.color);
4548
// now the hard part is to draw the head in a sensible way
@@ -63,7 +66,7 @@ impl Drop for ArrowBuilder<'_, '_> {
6366
}
6467
}
6568

66-
impl<'s> Gizmos<'s> {
69+
impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
6770
/// Draw an arrow in 3D, from `start` to `end`. Has four tips for convienent viewing from any direction.
6871
///
6972
/// This should be called for each frame the arrow needs to be rendered.
@@ -78,7 +81,7 @@ impl<'s> Gizmos<'s> {
7881
/// }
7982
/// # bevy_ecs::system::assert_is_system(system);
8083
/// ```
81-
pub fn arrow(&mut self, start: Vec3, end: Vec3, color: Color) -> ArrowBuilder<'_, 's> {
84+
pub fn arrow(&mut self, start: Vec3, end: Vec3, color: Color) -> ArrowBuilder<'_, 'w, 's, T> {
8285
let length = (end - start).length();
8386
ArrowBuilder {
8487
gizmos: self,
@@ -103,7 +106,12 @@ impl<'s> Gizmos<'s> {
103106
/// }
104107
/// # bevy_ecs::system::assert_is_system(system);
105108
/// ```
106-
pub fn arrow_2d(&mut self, start: Vec2, end: Vec2, color: Color) -> ArrowBuilder<'_, 's> {
109+
pub fn arrow_2d(
110+
&mut self,
111+
start: Vec2,
112+
end: Vec2,
113+
color: Color,
114+
) -> ArrowBuilder<'_, 'w, 's, T> {
107115
self.arrow(start.extend(0.), end.extend(0.), color)
108116
}
109117
}

0 commit comments

Comments
 (0)