bevy/crates/bevy_gizmos/src/config.rs
Lynn c425fc7f32
Add dashed lines (#16884)
# Objective

- Fixes #16873

## Solution

- Added  `GizmoLineStyle::Dashed {gap_scale, line_scale}`
- The `gap_scale` and `line_scale` describe the lengths of the gaps and
visible line-segments in terms of line-widths. For example, if
`gap_scale == 1.0` and `line_scale == 3.0` the gaps are square and the
the visible segments are three line-widths long.
- The new `GizmoLineStyle` can be used both in 3D and 2D and with both
perspective and orthographic cameras.
- Updated the `2d_gizmos` and `3d_gizmos` examples to include the new
line-style.
- Display a warning, when using negative `gap_scale` or `line_scale`.
- Notably, `Hash` and `Eq` are manually implemented for `GizmoLineStyle`
since both are not implemented for `f32` which prevents deriving these
traits for `GizmoLineStyle`.

## Testing

- The results can be verified visually

---

## Showcase
The following images depict dashed lines with `gap_scale == 3.0` and
`line_scale == 5.0` in perspective 3D and orthographic 2D.


![linestyle-dashed-2d](https://github.com/user-attachments/assets/3541cc55-63c2-4600-882b-3da61f9472bd)

![linestyle-dashed-3d](https://github.com/user-attachments/assets/6b106352-8e74-44a0-b481-46510d4f9148)

---------

Co-authored-by: Hennadii Chernyshchyk <genaloner@gmail.com>
2024-12-18 20:43:58 +00:00

254 lines
8.8 KiB
Rust

//! A module for the [`GizmoConfig<T>`] [`Resource`].
use crate::{self as bevy_gizmos};
pub use bevy_gizmos_macros::GizmoConfigGroup;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
use {crate::GizmoAsset, bevy_asset::Handle, bevy_ecs::component::Component};
use bevy_ecs::{reflect::ReflectResource, system::Resource};
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
use bevy_utils::TypeIdMap;
use core::{
any::TypeId,
hash::Hash,
ops::{Deref, DerefMut},
panic,
};
/// An enum configuring how line joints will be drawn.
#[derive(Debug, Default, Copy, Clone, Reflect, PartialEq, Eq, Hash)]
pub enum GizmoLineJoint {
/// Does not draw any line joints.
#[default]
None,
/// Extends both lines at the joining point until they meet in a sharp point.
Miter,
/// Draws a round corner with the specified resolution between the two lines.
///
/// The resolution determines the amount of triangles drawn per joint,
/// e.g. `GizmoLineJoint::Round(4)` will draw 4 triangles at each line joint.
Round(u32),
/// Draws a bevel, a straight line in this case, to connect the ends of both lines.
Bevel,
}
/// An enum used to configure the style of gizmo lines, similar to CSS line-style
#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
#[non_exhaustive]
pub enum GizmoLineStyle {
/// A solid line without any decorators
#[default]
Solid,
/// A dotted line
Dotted,
/// A dashed line with configurable gap and line sizes
Dashed {
/// The length of the gap in `line_width`s
gap_scale: f32,
/// The length of the visible line in `line_width`s
line_scale: f32,
},
}
impl Eq for GizmoLineStyle {}
impl Hash for GizmoLineStyle {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
match self {
Self::Solid => {
0u64.hash(state);
}
Self::Dotted => 1u64.hash(state),
Self::Dashed {
gap_scale,
line_scale,
} => {
2u64.hash(state);
gap_scale.to_bits().hash(state);
line_scale.to_bits().hash(state);
}
}
}
}
/// A trait used to create gizmo configs groups.
///
/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
///
/// Make sure to derive [`Default`] + [`Reflect`] and register in the app using `app.init_gizmo_group::<T>()`
pub trait GizmoConfigGroup: Reflect + TypePath + Default {}
/// The default gizmo config group.
#[derive(Default, Reflect, GizmoConfigGroup)]
pub struct DefaultGizmoConfigGroup;
/// Used when the gizmo config group needs to be type-erased.
/// Also used for retained gizmos, which can't have a gizmo config group.
#[derive(Default, Reflect, GizmoConfigGroup, Debug, Clone)]
pub struct ErasedGizmoConfigGroup;
/// A [`Resource`] storing [`GizmoConfig`] and [`GizmoConfigGroup`] structs
///
/// Use `app.init_gizmo_group::<T>()` to register a custom config group.
#[derive(Reflect, Resource, Default)]
#[reflect(Resource, Default)]
pub struct GizmoConfigStore {
// INVARIANT: must map TypeId::of::<T>() to correct type T
#[reflect(ignore)]
store: TypeIdMap<(GizmoConfig, Box<dyn Reflect>)>,
}
impl GizmoConfigStore {
/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
pub fn get_config_dyn(&self, config_type_id: &TypeId) -> Option<(&GizmoConfig, &dyn Reflect)> {
let (config, ext) = self.store.get(config_type_id)?;
Some((config, ext.deref()))
}
/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
pub fn config<T: GizmoConfigGroup>(&self) -> (&GizmoConfig, &T) {
let Some((config, ext)) = self.get_config_dyn(&TypeId::of::<T>()) else {
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
};
// hash map invariant guarantees that &dyn Reflect is of correct type T
let ext = ext.as_any().downcast_ref().unwrap();
(config, ext)
}
/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
pub fn get_config_mut_dyn(
&mut self,
config_type_id: &TypeId,
) -> Option<(&mut GizmoConfig, &mut dyn Reflect)> {
let (config, ext) = self.store.get_mut(config_type_id)?;
Some((config, ext.deref_mut()))
}
/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
pub fn config_mut<T: GizmoConfigGroup>(&mut self) -> (&mut GizmoConfig, &mut T) {
let Some((config, ext)) = self.get_config_mut_dyn(&TypeId::of::<T>()) else {
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
};
// hash map invariant guarantees that &dyn Reflect is of correct type T
let ext = ext.as_any_mut().downcast_mut().unwrap();
(config, ext)
}
/// Returns an iterator over all [`GizmoConfig`]s.
pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &GizmoConfig, &dyn Reflect)> + '_ {
self.store
.iter()
.map(|(id, (config, ext))| (id, config, ext.deref()))
}
/// Returns an iterator over all [`GizmoConfig`]s, by mutable reference.
pub fn iter_mut(
&mut self,
) -> impl Iterator<Item = (&TypeId, &mut GizmoConfig, &mut dyn Reflect)> + '_ {
self.store
.iter_mut()
.map(|(id, (config, ext))| (id, config, ext.deref_mut()))
}
/// Inserts [`GizmoConfig`] and [`GizmoConfigGroup`] replacing old values
pub fn insert<T: GizmoConfigGroup>(&mut self, config: GizmoConfig, ext_config: T) {
// INVARIANT: hash map must correctly map TypeId::of::<T>() to &dyn Reflect of type T
self.store
.insert(TypeId::of::<T>(), (config, Box::new(ext_config)));
}
pub(crate) fn register<T: GizmoConfigGroup>(&mut self) {
self.insert(GizmoConfig::default(), T::default());
}
}
/// A struct that stores configuration for gizmos.
#[derive(Clone, Reflect, Debug)]
pub struct GizmoConfig {
/// Set to `false` to stop drawing gizmos.
///
/// Defaults to `true`.
pub enabled: bool,
/// Line settings.
pub line: GizmoLineConfig,
/// How closer to the camera than real geometry the gizmos should be.
///
/// In 2D this setting has no effect and is effectively always -1.
///
/// Value between -1 and 1 (inclusive).
/// * 0 means that there is no change to the line position when rendering
/// * 1 means it is furthest away from camera as possible
/// * -1 means that it will always render in front of other things.
///
/// This is typically useful if you are drawing wireframes on top of polygons
/// and your wireframe is z-fighting (flickering on/off) with your main model.
/// You would set this value to a negative number close to 0.
pub depth_bias: f32,
/// Describes which rendering layers gizmos will be rendered to.
///
/// Gizmos will only be rendered to cameras with intersecting layers.
#[cfg(feature = "bevy_render")]
pub render_layers: bevy_render::view::RenderLayers,
}
impl Default for GizmoConfig {
fn default() -> Self {
Self {
enabled: true,
line: Default::default(),
depth_bias: 0.,
#[cfg(feature = "bevy_render")]
render_layers: Default::default(),
}
}
}
/// A struct that stores configuration for gizmos.
#[derive(Clone, Reflect, Debug)]
pub struct GizmoLineConfig {
/// Line width specified in pixels.
///
/// If `perspective` is `true` then this is the size in pixels at the camera's near plane.
///
/// Defaults to `2.0`.
pub width: f32,
/// Apply perspective to gizmo lines.
///
/// This setting only affects 3D, non-orthographic cameras.
///
/// Defaults to `false`.
pub perspective: bool,
/// Determine the style of gizmo lines.
pub style: GizmoLineStyle,
/// Describe how lines should join.
pub joints: GizmoLineJoint,
}
impl Default for GizmoLineConfig {
fn default() -> Self {
Self {
width: 2.,
perspective: false,
style: GizmoLineStyle::Solid,
joints: GizmoLineJoint::None,
}
}
}
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
#[derive(Component)]
pub(crate) struct GizmoMeshConfig {
pub line_perspective: bool,
pub line_style: GizmoLineStyle,
pub line_joints: GizmoLineJoint,
pub render_layers: bevy_render::view::RenderLayers,
pub handle: Handle<GizmoAsset>,
}