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}