Skip to main content

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}