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, template::FromTemplate};
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 ///
114 /// # Panics
115 /// If the config does not exist for [`GizmoConfigGroup`] `T`
116 ///
117 /// For a non-panicking version, see [`get_config`].
118 ///
119 /// [`get_config`]: Self::get_config
120 pub fn config<T: GizmoConfigGroup>(&self) -> (&GizmoConfig, &T) {
121 let Some(configs) = self.get_config() else {
122 panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
123 };
124 configs
125 }
126
127 /// Returns Some([`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T` if they exist,
128 /// else None.
129 ///
130 /// If the configs will always be present, use [`config`].
131 ///
132 /// [`config`]: Self::config
133 pub fn get_config<T: GizmoConfigGroup>(&self) -> Option<(&GizmoConfig, &T)> {
134 let (config, ext) = self.get_config_dyn(&TypeId::of::<T>())?;
135 // hash map invariant guarantees that &dyn Reflect is of correct type T
136 let ext = ext.as_any().downcast_ref().unwrap();
137 Some((config, ext))
138 }
139
140 /// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
141 pub fn get_config_mut_dyn(
142 &mut self,
143 config_type_id: &TypeId,
144 ) -> Option<(&mut GizmoConfig, &mut dyn Reflect)> {
145 let (config, ext) = self.store.get_mut(config_type_id)?;
146 Some((config, ext.deref_mut()))
147 }
148
149 /// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
150 ///
151 /// # Panics
152 /// If the config does not exist for [`GizmoConfigGroup`] `T`
153 ///
154 /// For a non-panicking version, see [`get_config_mut`].
155 ///
156 /// [`get_config_mut`]: Self::get_config_mut
157 pub fn config_mut<T: GizmoConfigGroup>(&mut self) -> (&mut GizmoConfig, &mut T) {
158 let Some(configs) = self.get_config_mut() else {
159 panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
160 };
161 configs
162 }
163
164 /// Returns mutable Some([`GizmoConfig`] and [`GizmoConfigGroup`]) associated with [`GizmoConfigGroup`] `T` if they exist,
165 /// else None
166 ///
167 /// If the configs will always be present, use [`config_mut`].
168 ///
169 /// [`config_mut`]: Self::config_mut
170 pub fn get_config_mut<T: GizmoConfigGroup>(&mut self) -> Option<(&mut GizmoConfig, &mut T)> {
171 let (config, ext) = self.get_config_mut_dyn(&TypeId::of::<T>())?;
172 // hash map invariant guarantees that &dyn Reflect is of correct type T
173 let ext = ext.as_any_mut().downcast_mut().unwrap();
174 Some((config, ext))
175 }
176
177 /// Returns an iterator over all [`GizmoConfig`]s.
178 pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &GizmoConfig, &dyn Reflect)> + '_ {
179 self.store
180 .iter()
181 .map(|(id, (config, ext))| (id, config, ext.deref()))
182 }
183
184 /// Returns an iterator over all [`GizmoConfig`]s, by mutable reference.
185 pub fn iter_mut(
186 &mut self,
187 ) -> impl Iterator<Item = (&TypeId, &mut GizmoConfig, &mut dyn Reflect)> + '_ {
188 self.store
189 .iter_mut()
190 .map(|(id, (config, ext))| (id, config, ext.deref_mut()))
191 }
192
193 /// Inserts [`GizmoConfig`] and [`GizmoConfigGroup`] replacing old values
194 pub fn insert<T: GizmoConfigGroup>(&mut self, config: GizmoConfig, ext_config: T) {
195 // INVARIANT: hash map must correctly map TypeId::of::<T>() to &dyn Reflect of type T
196 self.store
197 .insert(TypeId::of::<T>(), (config, Box::new(ext_config)));
198 }
199
200 pub(crate) fn register<T: GizmoConfigGroup>(&mut self) {
201 self.insert(GizmoConfig::default(), T::default());
202 }
203}
204
205/// A struct that stores configuration for gizmos.
206#[derive(Clone, Reflect, Debug)]
207#[reflect(Clone, Default)]
208pub struct GizmoConfig {
209 /// Set to `false` to stop drawing gizmos.
210 ///
211 /// Defaults to `true`.
212 pub enabled: bool,
213 /// Line settings.
214 pub line: GizmoLineConfig,
215 /// How closer to the camera than real geometry the gizmos should be.
216 ///
217 /// In 2D this setting has no effect and is effectively always -1.
218 ///
219 /// Value between -1 and 1 (inclusive).
220 /// * 0 means that there is no change to the line position when rendering
221 /// * 1 means it is furthest away from camera as possible
222 /// * -1 means that it will always render in front of other things.
223 ///
224 /// This is typically useful if you are drawing wireframes on top of polygons
225 /// and your wireframe is z-fighting (flickering on/off) with your main model.
226 /// You would set this value to a negative number close to 0.
227 pub depth_bias: f32,
228 /// Describes which rendering layers gizmos will be rendered to.
229 ///
230 /// Gizmos will only be rendered to cameras with intersecting layers.
231 pub render_layers: RenderLayers,
232}
233
234impl Default for GizmoConfig {
235 fn default() -> Self {
236 Self {
237 enabled: true,
238 line: Default::default(),
239 depth_bias: 0.,
240 render_layers: Default::default(),
241 }
242 }
243}
244
245/// A struct that stores configuration for gizmos.
246#[derive(Clone, Reflect, Debug)]
247#[reflect(Clone, Default)]
248pub struct GizmoLineConfig {
249 /// Line width specified in pixels.
250 ///
251 /// If `perspective` is `true` then this is the size in pixels at the camera's near plane.
252 ///
253 /// Defaults to `2.0`.
254 pub width: f32,
255 /// Apply perspective to gizmo lines.
256 ///
257 /// This setting only affects 3D, non-orthographic cameras.
258 ///
259 /// Defaults to `false`.
260 pub perspective: bool,
261 /// Determine the style of gizmo lines.
262 pub style: GizmoLineStyle,
263 /// Describe how lines should join.
264 pub joints: GizmoLineJoint,
265}
266
267impl Default for GizmoLineConfig {
268 fn default() -> Self {
269 Self {
270 width: 2.,
271 perspective: false,
272 style: GizmoLineStyle::Solid,
273 joints: GizmoLineJoint::None,
274 }
275 }
276}
277
278/// Configuration for gizmo meshes.
279#[derive(Component, FromTemplate)]
280pub struct GizmoMeshConfig {
281 /// Apply perspective to gizmo lines.
282 ///
283 /// This setting only affects 3D, non-orthographic cameras.
284 ///
285 /// Defaults to `false`.
286 pub line_perspective: bool,
287 /// Determine the style of gizmo lines.
288 pub line_style: GizmoLineStyle,
289 /// Describe how lines should join.
290 pub line_joints: GizmoLineJoint,
291 /// Describes which rendering layers gizmos will be rendered to.
292 ///
293 /// Gizmos will only be rendered to cameras with intersecting layers.
294 pub render_layers: RenderLayers,
295 /// Handle of the gizmo asset.
296 pub handle: Handle<GizmoAsset>,
297}