//! A module for the [`GizmoConfig<T>`] [`Resource`].12use bevy_camera::visibility::RenderLayers;3pub use bevy_gizmos_macros::GizmoConfigGroup;45use {crate::GizmoAsset, bevy_asset::Handle, bevy_ecs::component::Component};67use bevy_ecs::{reflect::ReflectResource, resource::Resource};8use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};9use bevy_utils::TypeIdMap;10use core::{11any::TypeId,12hash::Hash,13ops::{Deref, DerefMut},14panic,15};1617/// An enum configuring how line joints will be drawn.18#[derive(Debug, Default, Copy, Clone, Reflect, PartialEq, Eq, Hash)]19#[reflect(Default, PartialEq, Hash, Clone)]20pub enum GizmoLineJoint {21/// Does not draw any line joints.22#[default]23None,24/// Extends both lines at the joining point until they meet in a sharp point.25Miter,26/// Draws a round corner with the specified resolution between the two lines.27///28/// The resolution determines the amount of triangles drawn per joint,29/// e.g. `GizmoLineJoint::Round(4)` will draw 4 triangles at each line joint.30Round(u32),31/// Draws a bevel, a straight line in this case, to connect the ends of both lines.32Bevel,33}3435/// An enum used to configure the style of gizmo lines, similar to CSS line-style36#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]37#[reflect(Default, PartialEq, Hash, Clone)]38#[non_exhaustive]39pub enum GizmoLineStyle {40/// A solid line without any decorators41#[default]42Solid,43/// A dotted line44Dotted,45/// A dashed line with configurable gap and line sizes46Dashed {47/// The length of the gap in `line_width`s48gap_scale: f32,49/// The length of the visible line in `line_width`s50line_scale: f32,51},52}5354impl Eq for GizmoLineStyle {}5556impl Hash for GizmoLineStyle {57fn hash<H: core::hash::Hasher>(&self, state: &mut H) {58match self {59Self::Solid => {600u64.hash(state);61}62Self::Dotted => 1u64.hash(state),63Self::Dashed {64gap_scale,65line_scale,66} => {672u64.hash(state);68gap_scale.to_bits().hash(state);69line_scale.to_bits().hash(state);70}71}72}73}7475/// A trait used to create gizmo configs groups.76///77/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]78///79/// Make sure to derive [`Default`] + [`Reflect`] and register in the app using `app.init_gizmo_group::<T>()`80pub trait GizmoConfigGroup: Reflect + TypePath + Default {}8182/// The default gizmo config group.83#[derive(Default, Reflect, GizmoConfigGroup)]84#[reflect(Default)]85pub struct DefaultGizmoConfigGroup;8687/// Used when the gizmo config group needs to be type-erased.88/// Also used for retained gizmos, which can't have a gizmo config group.89#[derive(Default, Reflect, GizmoConfigGroup, Debug, Clone)]90#[reflect(Default, Clone)]91pub struct ErasedGizmoConfigGroup;9293/// A [`Resource`] storing [`GizmoConfig`] and [`GizmoConfigGroup`] structs94///95/// Use `app.init_gizmo_group::<T>()` to register a custom config group.96#[derive(Reflect, Resource, Default)]97#[reflect(Resource, Default)]98pub struct GizmoConfigStore {99// INVARIANT: must map TypeId::of::<T>() to correct type T100#[reflect(ignore)]101store: TypeIdMap<(GizmoConfig, Box<dyn Reflect>)>,102}103104impl GizmoConfigStore {105/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]106pub fn get_config_dyn(&self, config_type_id: &TypeId) -> Option<(&GizmoConfig, &dyn Reflect)> {107let (config, ext) = self.store.get(config_type_id)?;108Some((config, ext.deref()))109}110111/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`112///113/// # Panics114/// If the config does not exist for [`GizmoConfigGroup`] `T`115///116/// For a non-panicking version, see [`get_config`].117///118/// [`get_config`]: Self::get_config119pub fn config<T: GizmoConfigGroup>(&self) -> (&GizmoConfig, &T) {120let Some(configs) = self.get_config() else {121panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());122};123configs124}125126/// Returns Some([`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T` if they exist,127/// else None.128///129/// If the configs will always be present, use [`config`].130///131/// [`config`]: Self::config132pub fn get_config<T: GizmoConfigGroup>(&self) -> Option<(&GizmoConfig, &T)> {133let (config, ext) = self.get_config_dyn(&TypeId::of::<T>())?;134// hash map invariant guarantees that &dyn Reflect is of correct type T135let ext = ext.as_any().downcast_ref().unwrap();136Some((config, ext))137}138139/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]140pub fn get_config_mut_dyn(141&mut self,142config_type_id: &TypeId,143) -> Option<(&mut GizmoConfig, &mut dyn Reflect)> {144let (config, ext) = self.store.get_mut(config_type_id)?;145Some((config, ext.deref_mut()))146}147148/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`149///150/// # Panics151/// If the config does not exist for [`GizmoConfigGroup`] `T`152///153/// For a non-panicking version, see [`get_config_mut`].154///155/// [`get_config_mut`]: Self::get_config_mut156pub fn config_mut<T: GizmoConfigGroup>(&mut self) -> (&mut GizmoConfig, &mut T) {157let Some(configs) = self.get_config_mut() else {158panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());159};160configs161}162163/// Returns mutable Some([`GizmoConfig`] and [`GizmoConfigGroup`]) associated with [`GizmoConfigGroup`] `T` if they exist,164/// else None165///166/// If the configs will always be present, use [`config_mut`].167///168/// [`config_mut`]: Self::config_mut169pub fn get_config_mut<T: GizmoConfigGroup>(&mut self) -> Option<(&mut GizmoConfig, &mut T)> {170let (config, ext) = self.get_config_mut_dyn(&TypeId::of::<T>())?;171// hash map invariant guarantees that &dyn Reflect is of correct type T172let ext = ext.as_any_mut().downcast_mut().unwrap();173Some((config, ext))174}175176/// Returns an iterator over all [`GizmoConfig`]s.177pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &GizmoConfig, &dyn Reflect)> + '_ {178self.store179.iter()180.map(|(id, (config, ext))| (id, config, ext.deref()))181}182183/// Returns an iterator over all [`GizmoConfig`]s, by mutable reference.184pub fn iter_mut(185&mut self,186) -> impl Iterator<Item = (&TypeId, &mut GizmoConfig, &mut dyn Reflect)> + '_ {187self.store188.iter_mut()189.map(|(id, (config, ext))| (id, config, ext.deref_mut()))190}191192/// Inserts [`GizmoConfig`] and [`GizmoConfigGroup`] replacing old values193pub fn insert<T: GizmoConfigGroup>(&mut self, config: GizmoConfig, ext_config: T) {194// INVARIANT: hash map must correctly map TypeId::of::<T>() to &dyn Reflect of type T195self.store196.insert(TypeId::of::<T>(), (config, Box::new(ext_config)));197}198199pub(crate) fn register<T: GizmoConfigGroup>(&mut self) {200self.insert(GizmoConfig::default(), T::default());201}202}203204/// A struct that stores configuration for gizmos.205#[derive(Clone, Reflect, Debug)]206#[reflect(Clone, Default)]207pub struct GizmoConfig {208/// Set to `false` to stop drawing gizmos.209///210/// Defaults to `true`.211pub enabled: bool,212/// Line settings.213pub line: GizmoLineConfig,214/// How closer to the camera than real geometry the gizmos should be.215///216/// In 2D this setting has no effect and is effectively always -1.217///218/// Value between -1 and 1 (inclusive).219/// * 0 means that there is no change to the line position when rendering220/// * 1 means it is furthest away from camera as possible221/// * -1 means that it will always render in front of other things.222///223/// This is typically useful if you are drawing wireframes on top of polygons224/// and your wireframe is z-fighting (flickering on/off) with your main model.225/// You would set this value to a negative number close to 0.226pub depth_bias: f32,227/// Describes which rendering layers gizmos will be rendered to.228///229/// Gizmos will only be rendered to cameras with intersecting layers.230pub render_layers: RenderLayers,231}232233impl Default for GizmoConfig {234fn default() -> Self {235Self {236enabled: true,237line: Default::default(),238depth_bias: 0.,239render_layers: Default::default(),240}241}242}243244/// A struct that stores configuration for gizmos.245#[derive(Clone, Reflect, Debug)]246#[reflect(Clone, Default)]247pub struct GizmoLineConfig {248/// Line width specified in pixels.249///250/// If `perspective` is `true` then this is the size in pixels at the camera's near plane.251///252/// Defaults to `2.0`.253pub width: f32,254/// Apply perspective to gizmo lines.255///256/// This setting only affects 3D, non-orthographic cameras.257///258/// Defaults to `false`.259pub perspective: bool,260/// Determine the style of gizmo lines.261pub style: GizmoLineStyle,262/// Describe how lines should join.263pub joints: GizmoLineJoint,264}265266impl Default for GizmoLineConfig {267fn default() -> Self {268Self {269width: 2.,270perspective: false,271style: GizmoLineStyle::Solid,272joints: GizmoLineJoint::None,273}274}275}276277/// Configuration for gizmo meshes.278#[derive(Component)]279pub struct GizmoMeshConfig {280/// Apply perspective to gizmo lines.281///282/// This setting only affects 3D, non-orthographic cameras.283///284/// Defaults to `false`.285pub line_perspective: bool,286/// Determine the style of gizmo lines.287pub line_style: GizmoLineStyle,288/// Describe how lines should join.289pub line_joints: GizmoLineJoint,290/// Describes which rendering layers gizmos will be rendered to.291///292/// Gizmos will only be rendered to cameras with intersecting layers.293pub render_layers: RenderLayers,294/// Handle of the gizmo asset.295pub handle: Handle<GizmoAsset>,296}297298299