bevy/examples/3d/3d_gizmos.rs
jeliag f6b40a6e43
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>
2024-01-18 15:52:50 +00:00

167 lines
5.5 KiB
Rust

//! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging.
use std::f32::consts::PI;
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>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0., 1.5, 6.).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
// plane
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Plane::from_size(5.0)),
material: materials.add(Color::rgb(0.3, 0.5, 0.3)),
..default()
});
// cube
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Cube { size: 1.0 }),
material: materials.add(Color::rgb(0.8, 0.7, 0.6)),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 250000.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// example instructions
commands.spawn(
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 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()
},
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::Px(12.0),
left: Val::Px(12.0),
..default()
}),
);
}
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.25)),
Color::BLACK,
);
gizmos.rect(
Vec3::new(time.elapsed_seconds().cos() * 2.5, 1., 0.),
Quat::from_rotation_y(PI / 2.),
Vec2::splat(2.),
Color::GREEN,
);
my_gizmos.sphere(Vec3::new(1., 0.5, 0.), Quat::IDENTITY, 0.5, Color::RED);
for y in [0., 0.5, 1.] {
gizmos.ray(
Vec3::new(1., y, 0.),
Vec3::new(-3., (time.elapsed_seconds() * 3.).sin(), 0.),
Color::BLUE,
);
}
// Circles have 32 line-segments by default.
my_gizmos.circle(Vec3::ZERO, Vec3::Y, 3., Color::BLACK);
// You may want to increase this for larger circles or spheres.
my_gizmos
.circle(Vec3::ZERO, Vec3::Y, 3.1, Color::NAVY)
.segments(64);
my_gizmos
.sphere(Vec3::ZERO, Quat::IDENTITY, 3.2, Color::BLACK)
.circle_segments(64);
gizmos.arrow(Vec3::ZERO, Vec3::ONE * 1.5, Color::YELLOW);
}
fn rotate_camera(mut query: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
let mut transform = query.single_mut();
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 2.));
}
fn update_config(
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;
}
}