bevy_gizmos/
config.rs

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