bevy_gizmos/config.rs
1//! A module for the [`GizmoConfig<T>`] [`Resource`].
2
3use bevy_camera::visibility::RenderLayers;
4pub use bevy_gizmos_macros::GizmoConfigGroup;
5
6use {crate::GizmoAsset, bevy_asset::Handle, bevy_ecs::component::Component};
7
8use bevy_ecs::{reflect::ReflectResource, resource::Resource};
9use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
10use bevy_utils::TypeIdMap;
11use core::{
12 any::TypeId,
13 hash::Hash,
14 ops::{Deref, DerefMut},
15 panic,
16};
17
18/// An enum configuring how line joints will be drawn.
19#[derive(Debug, Default, Copy, Clone, Reflect, PartialEq, Eq, Hash)]
20#[reflect(Default, PartialEq, Hash, Clone)]
21pub enum GizmoLineJoint {
22 /// Does not draw any line joints.
23 #[default]
24 None,
25 /// Extends both lines at the joining point until they meet in a sharp point.
26 Miter,
27 /// Draws a round corner with the specified resolution between the two lines.
28 ///
29 /// The resolution determines the amount of triangles drawn per joint,
30 /// e.g. `GizmoLineJoint::Round(4)` will draw 4 triangles at each line joint.
31 Round(u32),
32 /// Draws a bevel, a straight line in this case, to connect the ends of both lines.
33 Bevel,
34}
35
36/// An enum used to configure the style of gizmo lines, similar to CSS line-style
37#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
38#[reflect(Default, PartialEq, Hash, Clone)]
39#[non_exhaustive]
40pub enum GizmoLineStyle {
41 /// A solid line without any decorators
42 #[default]
43 Solid,
44 /// A dotted line
45 Dotted,
46 /// A dashed line with configurable gap and line sizes
47 Dashed {
48 /// The length of the gap in `line_width`s
49 gap_scale: f32,
50 /// The length of the visible line in `line_width`s
51 line_scale: f32,
52 },
53}
54
55impl Eq for GizmoLineStyle {}
56
57impl Hash for GizmoLineStyle {
58 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
59 match self {
60 Self::Solid => {
61 0u64.hash(state);
62 }
63 Self::Dotted => 1u64.hash(state),
64 Self::Dashed {
65 gap_scale,
66 line_scale,
67 } => {
68 2u64.hash(state);
69 gap_scale.to_bits().hash(state);
70 line_scale.to_bits().hash(state);
71 }
72 }
73 }
74}
75
76/// A trait used to create gizmo configs groups.
77///
78/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
79///
80/// Make sure to derive [`Default`] + [`Reflect`] and register in the app using `app.init_gizmo_group::<T>()`
81pub trait GizmoConfigGroup: Reflect + TypePath + Default {}
82
83/// The default gizmo config group.
84#[derive(Default, Reflect, GizmoConfigGroup)]
85#[reflect(Default)]
86pub struct DefaultGizmoConfigGroup;
87
88/// Used when the gizmo config group needs to be type-erased.
89/// Also used for retained gizmos, which can't have a gizmo config group.
90#[derive(Default, Reflect, GizmoConfigGroup, Debug, Clone)]
91#[reflect(Default, Clone)]
92pub struct ErasedGizmoConfigGroup;
93
94/// A [`Resource`] storing [`GizmoConfig`] and [`GizmoConfigGroup`] structs
95///
96/// Use `app.init_gizmo_group::<T>()` to register a custom config group.
97#[derive(Reflect, Resource, Default)]
98#[reflect(Resource, Default)]
99pub struct GizmoConfigStore {
100 // INVARIANT: must map TypeId::of::<T>() to correct type T
101 #[reflect(ignore)]
102 store: TypeIdMap<(GizmoConfig, Box<dyn Reflect>)>,
103}
104
105impl GizmoConfigStore {
106 /// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
107 pub fn get_config_dyn(&self, config_type_id: &TypeId) -> Option<(&GizmoConfig, &dyn Reflect)> {
108 let (config, ext) = self.store.get(config_type_id)?;
109 Some((config, ext.deref()))
110 }
111
112 /// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
113 pub fn config<T: GizmoConfigGroup>(&self) -> (&GizmoConfig, &T) {
114 let Some((config, ext)) = self.get_config_dyn(&TypeId::of::<T>()) else {
115 panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
116 };
117 // hash map invariant guarantees that &dyn Reflect is of correct type T
118 let ext = ext.as_any().downcast_ref().unwrap();
119 (config, ext)
120 }
121
122 /// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
123 pub fn get_config_mut_dyn(
124 &mut self,
125 config_type_id: &TypeId,
126 ) -> Option<(&mut GizmoConfig, &mut dyn Reflect)> {
127 let (config, ext) = self.store.get_mut(config_type_id)?;
128 Some((config, ext.deref_mut()))
129 }
130
131 /// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
132 pub fn config_mut<T: GizmoConfigGroup>(&mut self) -> (&mut GizmoConfig, &mut T) {
133 let Some((config, ext)) = self.get_config_mut_dyn(&TypeId::of::<T>()) else {
134 panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
135 };
136 // hash map invariant guarantees that &dyn Reflect is of correct type T
137 let ext = ext.as_any_mut().downcast_mut().unwrap();
138 (config, ext)
139 }
140
141 /// Returns an iterator over all [`GizmoConfig`]s.
142 pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &GizmoConfig, &dyn Reflect)> + '_ {
143 self.store
144 .iter()
145 .map(|(id, (config, ext))| (id, config, ext.deref()))
146 }
147
148 /// Returns an iterator over all [`GizmoConfig`]s, by mutable reference.
149 pub fn iter_mut(
150 &mut self,
151 ) -> impl Iterator<Item = (&TypeId, &mut GizmoConfig, &mut dyn Reflect)> + '_ {
152 self.store
153 .iter_mut()
154 .map(|(id, (config, ext))| (id, config, ext.deref_mut()))
155 }
156
157 /// Inserts [`GizmoConfig`] and [`GizmoConfigGroup`] replacing old values
158 pub fn insert<T: GizmoConfigGroup>(&mut self, config: GizmoConfig, ext_config: T) {
159 // INVARIANT: hash map must correctly map TypeId::of::<T>() to &dyn Reflect of type T
160 self.store
161 .insert(TypeId::of::<T>(), (config, Box::new(ext_config)));
162 }
163
164 pub(crate) fn register<T: GizmoConfigGroup>(&mut self) {
165 self.insert(GizmoConfig::default(), T::default());
166 }
167}
168
169/// A struct that stores configuration for gizmos.
170#[derive(Clone, Reflect, Debug)]
171#[reflect(Clone, Default)]
172pub struct GizmoConfig {
173 /// Set to `false` to stop drawing gizmos.
174 ///
175 /// Defaults to `true`.
176 pub enabled: bool,
177 /// Line settings.
178 pub line: GizmoLineConfig,
179 /// How closer to the camera than real geometry the gizmos should be.
180 ///
181 /// In 2D this setting has no effect and is effectively always -1.
182 ///
183 /// Value between -1 and 1 (inclusive).
184 /// * 0 means that there is no change to the line position when rendering
185 /// * 1 means it is furthest away from camera as possible
186 /// * -1 means that it will always render in front of other things.
187 ///
188 /// This is typically useful if you are drawing wireframes on top of polygons
189 /// and your wireframe is z-fighting (flickering on/off) with your main model.
190 /// You would set this value to a negative number close to 0.
191 pub depth_bias: f32,
192 /// Describes which rendering layers gizmos will be rendered to.
193 ///
194 /// Gizmos will only be rendered to cameras with intersecting layers.
195 pub render_layers: RenderLayers,
196}
197
198impl Default for GizmoConfig {
199 fn default() -> Self {
200 Self {
201 enabled: true,
202 line: Default::default(),
203 depth_bias: 0.,
204 render_layers: Default::default(),
205 }
206 }
207}
208
209/// A struct that stores configuration for gizmos.
210#[derive(Clone, Reflect, Debug)]
211#[reflect(Clone, Default)]
212pub struct GizmoLineConfig {
213 /// Line width specified in pixels.
214 ///
215 /// If `perspective` is `true` then this is the size in pixels at the camera's near plane.
216 ///
217 /// Defaults to `2.0`.
218 pub width: f32,
219 /// Apply perspective to gizmo lines.
220 ///
221 /// This setting only affects 3D, non-orthographic cameras.
222 ///
223 /// Defaults to `false`.
224 pub perspective: bool,
225 /// Determine the style of gizmo lines.
226 pub style: GizmoLineStyle,
227 /// Describe how lines should join.
228 pub joints: GizmoLineJoint,
229}
230
231impl Default for GizmoLineConfig {
232 fn default() -> Self {
233 Self {
234 width: 2.,
235 perspective: false,
236 style: GizmoLineStyle::Solid,
237 joints: GizmoLineJoint::None,
238 }
239 }
240}
241
242/// Configuration for gizmo meshes.
243#[derive(Component)]
244pub struct GizmoMeshConfig {
245 /// Apply perspective to gizmo lines.
246 ///
247 /// This setting only affects 3D, non-orthographic cameras.
248 ///
249 /// Defaults to `false`.
250 pub line_perspective: bool,
251 /// Determine the style of gizmo lines.
252 pub line_style: GizmoLineStyle,
253 /// Describe how lines should join.
254 pub line_joints: GizmoLineJoint,
255 /// Describes which rendering layers gizmos will be rendered to.
256 ///
257 /// Gizmos will only be rendered to cameras with intersecting layers.
258 pub render_layers: RenderLayers,
259 /// Handle of the gizmo asset.
260 pub handle: Handle<GizmoAsset>,
261}