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 <nicopap@users.noreply.github.com>
This commit is contained in:
jeliag 2024-01-18 16:52:50 +01:00 committed by GitHub
parent c9e1fcdb35
commit f6b40a6e43
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 678 additions and 258 deletions

View file

@ -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

View file

@ -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"

View file

@ -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 {
}
})
}

View file

@ -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::<AabbGizmoConfigGroup>()
.init_gizmo_group::<AabbGizmoConfigGroup>()
.add_systems(
PostUpdate,
(
draw_aabbs,
draw_all_aabbs.run_if(|config: Res<GizmoConfigStore>| {
config.config::<AabbGizmoConfigGroup>().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<Color>,
}
/// 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<Color>,
}
fn draw_aabbs(
query: Query<(Entity, &Aabb, &GlobalTransform, &ShowAabbGizmo)>,
mut gizmos: Gizmos<AabbGizmoConfigGroup>,
) {
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<ShowAabbGizmo>>,
mut gizmos: Gizmos<AabbGizmoConfigGroup>,
) {
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()),
)
}

View file

@ -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<usize>,
}
impl Arc2dBuilder<'_, '_> {
impl<T: GizmoConfigGroup> 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<T: GizmoConfigGroup> 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`

View file

@ -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<T: GizmoConfigGroup> 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<T: GizmoConfigGroup> 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)
}
}

View file

@ -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<Item = Vec2> {
})
}
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<T: GizmoConfigGroup> 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<T: GizmoConfigGroup> 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<T: GizmoConfigGroup> 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<T: GizmoConfigGroup> 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);
}

View file

@ -0,0 +1,163 @@
//! A module for the [`GizmoConfig<T>`] [`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::<T>()`
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::<T>()` to register a custom config group.
#[derive(Resource, Default)]
pub struct GizmoConfigStore {
// INVARIANT: must map TypeId::of::<T>() to correct type T
store: HashMap<TypeId, (GizmoConfig, Box<dyn Reflect>)>,
}
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<T: GizmoConfigGroup>(&self) -> (&GizmoConfig, &T) {
let Some((config, ext)) = self.get_config_dyn(&TypeId::of::<T>()) else {
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", 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<T: GizmoConfigGroup>(&mut self) -> (&mut GizmoConfig, &mut T) {
let Some((config, ext)) = self.get_config_mut_dyn(&TypeId::of::<T>()) else {
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", 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<Item = (&TypeId, &GizmoConfig, &dyn Reflect)> + '_ {
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<Item = (&TypeId, &mut GizmoConfig, &mut dyn Reflect)> + '_ {
self.store
.iter_mut()
.map(|(id, (config, ext))| (id, config, ext.deref_mut()))
}
/// Inserts [`GizmoConfig`] and [`GizmoConfigGroup`] replacing old values
pub fn insert<T: GizmoConfigGroup>(&mut self, config: GizmoConfig, ext_config: T) {
// INVARIANT: hash map must correctly map TypeId::of::<T>() to &dyn Reflect of type T
self.store
.insert(TypeId::of::<T>(), (config, Box::new(ext_config)));
}
pub(crate) fn register<T: GizmoConfigGroup>(&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,
}
}
}

View file

@ -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<T: GizmoConfigGroup> {
pub list_positions: Vec<PositionItem>,
pub list_colors: Vec<ColorItem>,
pub strip_positions: Vec<PositionItem>,
pub strip_colors: Vec<ColorItem>,
marker: PhantomData<T>,
}
/// 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<T>>,
pub(crate) enabled: bool,
/// The currently used [`GizmoConfig`]
pub config: &'w GizmoConfig,
/// The currently used [`GizmoConfigGroup`]
pub config_ext: &'w T,
}
type GizmosState<T> = (
Deferred<'static, GizmoBuffer<T>>,
Res<'static, GizmoConfigStore>,
);
#[doc(hidden)]
pub struct GizmosFetchState<T: GizmoConfigGroup> {
state: <GizmosState<T> as SystemParam>::State,
}
// SAFETY: All methods are delegated to existing `SystemParam` implemntations
unsafe impl<T: GizmoConfigGroup> SystemParam for Gizmos<'_, '_, T> {
type State = GizmosFetchState<T>;
type Item<'w, 's> = Gizmos<'w, 's, T>;
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
GizmosFetchState {
state: GizmosState::<T>::init_state(world, system_meta),
}
}
fn new_archetype(
state: &mut Self::State,
archetype: &bevy_ecs::archetype::Archetype,
system_meta: &mut SystemMeta,
) {
GizmosState::<T>::new_archetype(&mut state.state, archetype, system_meta);
}
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
GizmosState::<T>::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::<T>::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::<T>();
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<T>>: ReadOnlySystemParam,
Res<'w, GizmoConfigStore>: ReadOnlySystemParam,
{
}
#[derive(Default)]
struct GizmoBuffer {
struct GizmoBuffer<T: GizmoConfigGroup> {
list_positions: Vec<PositionItem>,
list_colors: Vec<ColorItem>,
strip_positions: Vec<PositionItem>,
strip_colors: Vec<ColorItem>,
marker: PhantomData<T>,
}
impl SystemBuffer for GizmoBuffer {
impl<T: GizmoConfigGroup> SystemBuffer for GizmoBuffer<T> {
fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) {
let mut storage = world.resource_mut::<GizmoStorage>();
let mut storage = world.resource_mut::<GizmoStorage<T>>();
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<Item = Vec3>, 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<Item = (Vec3, Color)>) {
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<Item = Vec2>, 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<Item = (Vec2, Color)>) {
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<T: GizmoConfigGroup> 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<T: GizmoConfigGroup> 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)

View file

@ -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<Shader> = Handle::weak_from_u128(7414812689238026784);
@ -97,30 +99,19 @@ impl Plugin for GizmoPlugin {
load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl);
app.register_type::<GizmoConfig>()
.register_type::<AabbGizmoConfig>()
.add_plugins(UniformComponentPlugin::<LineGizmoUniform>::default())
.init_asset::<LineGizmo>()
.add_plugins(RenderAssetPlugin::<LineGizmo>::default())
.init_resource::<LineGizmoHandles>()
.init_resource::<GizmoConfig>()
.init_resource::<GizmoStorage>()
.add_systems(Last, update_gizmo_meshes)
.add_systems(
PostUpdate,
(
draw_aabbs,
draw_all_aabbs.run_if(|config: Res<GizmoConfig>| config.aabb.draw_all),
)
.after(TransformSystem::TransformPropagate),
);
.init_resource::<GizmoConfigStore>()
.init_gizmo_group::<DefaultGizmoConfigGroup>()
.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_app.add_systems(
Render,
prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups),
);
@ -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.
/// A trait adding `init_gizmo_group<T>()` to the app
pub trait AppGizmoBuilder {
/// Registers [`GizmoConfigGroup`] `T` in the app enabling the use of [Gizmos&lt;T&gt;](crate::gizmos::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,
/// 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<T: GizmoConfigGroup + Default>(&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<T: GizmoConfigGroup + Default>(&mut self) -> &mut Self {
if self.world.contains_resource::<GizmoStorage<T>>() {
return self;
}
self.init_resource::<GizmoStorage<T>>()
.add_systems(Last, update_gizmo_meshes::<T>);
self.world
.resource_mut::<GizmoConfigStore>()
.register::<T>();
let Ok(render_app) = self.get_sub_app_mut(RenderApp) else {
return self;
};
render_app.add_systems(ExtractSchedule, extract_gizmo_data::<T>);
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<Color>,
}
/// 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<Color>,
}
fn draw_aabbs(
query: Query<(Entity, &Aabb, &GlobalTransform, &AabbGizmo)>,
config: Res<GizmoConfig>,
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<AabbGizmo>>,
config: Res<GizmoConfig>,
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);
}
}
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()),
)
}
#[derive(Resource, Default)]
struct LineGizmoHandles {
list: Option<Handle<LineGizmo>>,
strip: Option<Handle<LineGizmo>>,
list: HashMap<TypeId, Handle<LineGizmo>>,
strip: HashMap<TypeId, Handle<LineGizmo>>,
}
fn update_gizmo_meshes(
fn update_gizmo_meshes<T: GizmoConfigGroup>(
mut line_gizmos: ResMut<Assets<LineGizmo>>,
mut handles: ResMut<LineGizmoHandles>,
mut storage: ResMut<GizmoStorage>,
mut storage: ResMut<GizmoStorage<T>>,
) {
if storage.list_positions.is_empty() {
handles.list = None;
} else if let Some(handle) = handles.list.as_ref() {
handles.list.remove(&TypeId::of::<T>());
} else if let Some(handle) = handles.list.get(&TypeId::of::<T>()) {
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::<T>(), 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::<T>());
} else if let Some(handle) = handles.strip.get(&TypeId::of::<T>()) {
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::<T>(), line_gizmos.add(strip));
}
}
fn extract_gizmo_data(
fn extract_gizmo_data<T: GizmoConfigGroup>(
mut commands: Commands,
handles: Extract<Res<LineGizmoHandles>>,
config: Extract<Res<GizmoConfig>>,
config: Extract<Res<GizmoConfigStore>>,
) {
if config.is_changed() {
commands.insert_resource(config.clone());
}
let (config, _) = config.config::<T>();
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::<T>()) 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),
));
}
}

View file

@ -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<SpecializedRenderPipelines<LineGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
msaa: Res<Msaa>,
config: Res<GizmoConfig>,
line_gizmos: Query<(Entity, &Handle<LineGizmo>)>,
line_gizmos: Query<(Entity, &Handle<LineGizmo>, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
mut views: Query<(
&ExtractedView,
@ -156,14 +155,15 @@ fn queue_line_gizmos_2d(
let draw_function = draw_functions.read().get_id::<DrawLineGizmo2d>().unwrap();
for (view, mut transparent_phase, render_layers) in &mut views {
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
| Mesh2dPipelineKey::from_hdr(view.hdr);
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 mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
| Mesh2dPipelineKey::from_hdr(view.hdr);
for (entity, handle) in &line_gizmos {
let Some(line_gizmo) = line_gizmo_assets.get(handle) else {
continue;
};

View file

@ -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<SpecializedRenderPipelines<LineGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
msaa: Res<Msaa>,
config: Res<GizmoConfig>,
line_gizmos: Query<(Entity, &Handle<LineGizmo>)>,
line_gizmos: Query<(Entity, &Handle<LineGizmo>, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<LineGizmo>>,
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;
};

View file

@ -7,16 +7,23 @@ use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_gizmo_group::<MyRoundGizmos>()
.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<AssetServer>) {
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<AssetServer>) {
));
}
fn system(mut gizmos: Gizmos, time: Res<Time>) {
fn system(mut gizmos: Gizmos, mut my_gizmos: Gizmos<MyRoundGizmos>, time: Res<Time>) {
let sin = time.elapsed_seconds().sin() * 50.;
gizmos.line_2d(Vec2::Y * -sin, Vec2::splat(-80.), Color::RED);
gizmos.ray_2d(Vec2::Y * sin, Vec2::splat(80.), Color::GREEN);
@ -46,13 +53,15 @@ fn system(mut gizmos: Gizmos, time: Res<Time>) {
);
// The circles have 32 line-segments by default.
gizmos.circle_2d(Vec2::ZERO, 120., Color::BLACK);
my_gizmos.circle_2d(Vec2::ZERO, 120., Color::BLACK);
// You may want to increase this for larger circles.
gizmos.circle_2d(Vec2::ZERO, 300., Color::NAVY).segments(64);
my_gizmos
.circle_2d(Vec2::ZERO, 300., Color::NAVY)
.segments(64);
// Arcs default amount of segments is linearly interpolated between
// 1 and 32, using the arc length as scalar.
gizmos.arc_2d(Vec2::ZERO, sin / 10., PI / 2., 350., Color::ORANGE_RED);
my_gizmos.arc_2d(Vec2::ZERO, sin / 10., PI / 2., 350., Color::ORANGE_RED);
gizmos.arrow_2d(
Vec2::ZERO,
@ -62,14 +71,33 @@ fn system(mut gizmos: Gizmos, time: Res<Time>) {
}
fn update_config(
mut config: ResMut<GizmoConfig>,
mut config_store: ResMut<GizmoConfigStore>,
keyboard: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
let (config, _) = config_store.config_mut::<DefaultGizmoConfigGroup>();
if keyboard.pressed(KeyCode::ArrowRight) {
config.line_width += 5. * time.delta_seconds();
config.line_width = config.line_width.clamp(0., 50.);
}
if keyboard.pressed(KeyCode::ArrowLeft) {
config.line_width -= 5. * time.delta_seconds();
config.line_width = config.line_width.clamp(0., 50.);
}
if keyboard.just_pressed(KeyCode::Digit1) {
config.enabled ^= true;
}
let (my_config, _) = config_store.config_mut::<MyRoundGizmos>();
if keyboard.pressed(KeyCode::ArrowUp) {
my_config.line_width += 5. * time.delta_seconds();
my_config.line_width = my_config.line_width.clamp(0., 50.);
}
if keyboard.pressed(KeyCode::ArrowDown) {
my_config.line_width -= 5. * time.delta_seconds();
my_config.line_width = my_config.line_width.clamp(0., 50.);
}
if keyboard.just_pressed(KeyCode::Digit2) {
my_config.enabled ^= true;
}
}

View file

@ -7,11 +7,16 @@ use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_gizmo_group::<MyRoundGizmos>()
.add_systems(Startup, setup)
.add_systems(Update, (system, rotate_camera, update_config))
.run();
}
// We can create our own gizmo config group!
#[derive(Default, Reflect, GizmoConfigGroup)]
struct MyRoundGizmos {}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
@ -50,7 +55,10 @@ fn setup(
TextBundle::from_section(
"Press 'D' to toggle drawing gizmos on top of everything else in the scene\n\
Press 'P' to toggle perspective for line gizmos\n\
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\n\
Press 'A' to show all AABB boxes",
TextStyle {
font_size: 20.,
..default()
@ -65,9 +73,9 @@ fn setup(
);
}
fn system(mut gizmos: Gizmos, time: Res<Time>) {
fn system(mut gizmos: Gizmos, mut my_gizmos: Gizmos<MyRoundGizmos>, time: Res<Time>) {
gizmos.cuboid(
Transform::from_translation(Vec3::Y * 0.5).with_scale(Vec3::splat(1.)),
Transform::from_translation(Vec3::Y * 0.5).with_scale(Vec3::splat(1.25)),
Color::BLACK,
);
gizmos.rect(
@ -77,7 +85,7 @@ fn system(mut gizmos: Gizmos, time: Res<Time>) {
Color::GREEN,
);
gizmos.sphere(Vec3::new(1., 0.5, 0.), Quat::IDENTITY, 0.5, Color::RED);
my_gizmos.sphere(Vec3::new(1., 0.5, 0.), Quat::IDENTITY, 0.5, Color::RED);
for y in [0., 0.5, 1.] {
gizmos.ray(
@ -88,12 +96,12 @@ fn system(mut gizmos: Gizmos, time: Res<Time>) {
}
// Circles have 32 line-segments by default.
gizmos.circle(Vec3::ZERO, Vec3::Y, 3., Color::BLACK);
my_gizmos.circle(Vec3::ZERO, Vec3::Y, 3., Color::BLACK);
// You may want to increase this for larger circles or spheres.
gizmos
my_gizmos
.circle(Vec3::ZERO, Vec3::Y, 3.1, Color::NAVY)
.segments(64);
gizmos
my_gizmos
.sphere(Vec3::ZERO, Quat::IDENTITY, 3.2, Color::BLACK)
.circle_segments(64);
@ -107,24 +115,53 @@ fn rotate_camera(mut query: Query<&mut Transform, With<Camera>>, time: Res<Time>
}
fn update_config(
mut config: ResMut<GizmoConfig>,
mut config_store: ResMut<GizmoConfigStore>,
keyboard: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
if keyboard.just_pressed(KeyCode::KeyD) {
for (_, config, _) in config_store.iter_mut() {
config.depth_bias = if config.depth_bias == 0. { -1. } else { 0. };
}
}
if keyboard.just_pressed(KeyCode::KeyP) {
for (_, config, _) in config_store.iter_mut() {
// Toggle line_perspective
config.line_perspective ^= true;
// Increase the line width when line_perspective is on
config.line_width *= if config.line_perspective { 5. } else { 1. / 5. };
}
}
let (config, _) = config_store.config_mut::<DefaultGizmoConfigGroup>();
if keyboard.pressed(KeyCode::ArrowRight) {
config.line_width += 5. * time.delta_seconds();
config.line_width = config.line_width.clamp(0., 50.);
}
if keyboard.pressed(KeyCode::ArrowLeft) {
config.line_width -= 5. * time.delta_seconds();
config.line_width = config.line_width.clamp(0., 50.);
}
if keyboard.just_pressed(KeyCode::Digit1) {
config.enabled ^= true;
}
let (my_config, _) = config_store.config_mut::<MyRoundGizmos>();
if keyboard.pressed(KeyCode::ArrowUp) {
my_config.line_width += 5. * time.delta_seconds();
my_config.line_width = my_config.line_width.clamp(0., 50.);
}
if keyboard.pressed(KeyCode::ArrowDown) {
my_config.line_width -= 5. * time.delta_seconds();
my_config.line_width = my_config.line_width.clamp(0., 50.);
}
if keyboard.just_pressed(KeyCode::Digit2) {
my_config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyA) {
// AABB gizmos are normally only drawn on entities with a ShowAabbGizmo component
// We can change this behaviour in the configuration of AabbGizmoGroup
config_store.config_mut::<AabbGizmoConfigGroup>().1.draw_all ^= true;
}
}

View file

@ -79,8 +79,8 @@ impl Plugin for SceneViewerPlugin {
}
}
fn toggle_bounding_boxes(mut config: ResMut<GizmoConfig>) {
config.aabb.draw_all ^= true;
fn toggle_bounding_boxes(mut config: ResMut<GizmoConfigStore>) {
config.config_mut::<AabbGizmoConfigGroup>().1.draw_all ^= true;
}
fn scene_load_check(