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>
This commit is contained in:
Lynn 2024-12-18 21:43:58 +01:00 committed by GitHub
parent d8796ae8b6
commit c425fc7f32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 115 additions and 5 deletions

View File

@ -14,6 +14,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
use bevy_utils::TypeIdMap;
use core::{
any::TypeId,
hash::Hash,
ops::{Deref, DerefMut},
panic,
};
@ -36,7 +37,7 @@ pub enum GizmoLineJoint {
}
/// An enum used to configure the style of gizmo lines, similar to CSS line-style
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, Reflect)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
#[non_exhaustive]
pub enum GizmoLineStyle {
/// A solid line without any decorators
@ -44,6 +45,34 @@ pub enum GizmoLineStyle {
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.

View File

@ -81,7 +81,7 @@ use bevy_ecs::{
schedule::{IntoSystemConfigs, SystemSet},
system::{Res, ResMut, Resource},
};
use bevy_math::Vec4;
use bevy_math::{Vec3, Vec4};
use bevy_reflect::TypePath;
#[cfg(all(
@ -419,6 +419,9 @@ fn extract_gizmo_data(
handles: Extract<Res<GizmoHandles>>,
config: Extract<Res<GizmoConfigStore>>,
) {
use bevy_utils::warn_once;
use config::GizmoLineStyle;
for (group_type_id, handle) in &handles.handles {
let Some((config, _)) = config.get_config_dyn(group_type_id) else {
continue;
@ -438,12 +441,30 @@ fn extract_gizmo_data(
0
};
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
gap_scale,
line_scale,
} = config.line.style
{
if gap_scale <= 0.0 {
warn_once!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero.");
}
if line_scale <= 0.0 {
warn_once!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero.");
}
(gap_scale, line_scale)
} else {
(1.0, 1.0)
};
commands.spawn((
LineGizmoUniform {
world_from_local: Affine3::from(&Affine3A::IDENTITY).to_transpose(),
line_width: config.line.width,
depth_bias: config.depth_bias,
joints_resolution,
gap_scale,
line_scale,
#[cfg(feature = "webgl")]
_padding: Default::default(),
},
@ -471,9 +492,12 @@ struct LineGizmoUniform {
depth_bias: f32,
// Only used by gizmo line t if the current configs `line_joints` is set to `GizmoLineJoint::Round(_)`
joints_resolution: u32,
// Only used if the current configs `line_style` is set to `GizmoLineStyle::Dashed{_}`
gap_scale: f32,
line_scale: f32,
/// WebGL2 structs must be 16 byte aligned.
#[cfg(feature = "webgl")]
_padding: f32,
_padding: Vec3,
}
/// A collection of gizmos.

View File

@ -8,9 +8,12 @@ struct LineGizmoUniform {
world_from_local: mat3x4<f32>,
line_width: f32,
depth_bias: f32,
_joints_resolution: u32,
gap_scale: f32,
line_scale: f32,
#ifdef SIXTEEN_BYTE_ALIGNMENT
// WebGL2 structs must be 16 byte aligned.
_padding: vec2<f32>,
_padding: vec3<f32>,
#endif
}
@ -28,6 +31,7 @@ struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
@location(2) line_fraction: f32,
};
const EPSILON: f32 = 4.88e-04;
@ -126,7 +130,9 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
return VertexOutput(clip_position, color, uv);
let line_fraction = 2.0 * line_gizmo.line_scale / (line_gizmo.gap_scale + line_gizmo.line_scale);
uv /= (line_gizmo.gap_scale + line_gizmo.line_scale) / 2.0;
return VertexOutput(clip_position, color, uv, line_fraction);
}
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
@ -147,6 +153,7 @@ struct FragmentInput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
@location(2) line_fraction: f32,
};
struct FragmentOutput {
@ -168,3 +175,15 @@ fn fragment_dotted(in: FragmentInput) -> FragmentOutput {
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
}
@fragment
fn fragment_dashed(in: FragmentInput) -> FragmentOutput {
#ifdef PERSPECTIVE
let uv = in.uv;
#else
let uv = in.uv * in.position.w;
#endif
let alpha = 1.0 - floor(min((uv % 2.0) / in.line_fraction, 1.0));
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
}

View File

@ -118,6 +118,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
GizmoLineStyle::Dashed { .. } => "fragment_dashed",
};
RenderPipelineDescriptor {

View File

@ -124,6 +124,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
GizmoLineStyle::Dashed { .. } => "fragment_dashed",
};
RenderPipelineDescriptor {

View File

@ -106,6 +106,9 @@ pub(crate) fn extract_linegizmos(
) {
use bevy_math::Affine3;
use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
use bevy_utils::warn_once;
use crate::config::GizmoLineStyle;
let mut values = Vec::with_capacity(*previous_len);
for (entity, gizmo, transform, render_layers) in &query {
@ -115,6 +118,21 @@ pub(crate) fn extract_linegizmos(
} else {
0
};
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
gap_scale,
line_scale,
} = gizmo.line_config.style
{
if gap_scale <= 0.0 {
warn_once!("when using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero");
}
if line_scale <= 0.0 {
warn_once!("when using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero");
}
(gap_scale, line_scale)
} else {
(1.0, 1.0)
};
values.push((
LineGizmoUniform {
@ -122,6 +140,8 @@ pub(crate) fn extract_linegizmos(
line_width: gizmo.line_config.width,
depth_bias: gizmo.depth_bias,
joints_resolution,
gap_scale,
line_scale,
#[cfg(feature = "webgl")]
_padding: Default::default(),
},

View File

@ -142,6 +142,10 @@ fn update_config(
if keyboard.just_pressed(KeyCode::KeyU) {
config.line.style = match config.line.style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
gap_scale: 3.0,
line_scale: 5.0,
},
_ => GizmoLineStyle::Solid,
};
}
@ -169,6 +173,10 @@ fn update_config(
if keyboard.just_pressed(KeyCode::KeyI) {
my_config.line.style = match my_config.line.style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
gap_scale: 3.0,
line_scale: 5.0,
},
_ => GizmoLineStyle::Solid,
};
}

View File

@ -237,6 +237,10 @@ fn update_config(
if keyboard.just_pressed(KeyCode::KeyU) {
config.line.style = match config.line.style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
gap_scale: 3.0,
line_scale: 5.0,
},
_ => GizmoLineStyle::Solid,
};
}
@ -264,6 +268,10 @@ fn update_config(
if keyboard.just_pressed(KeyCode::KeyI) {
my_config.line.style = match my_config.line.style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
gap_scale: 3.0,
line_scale: 5.0,
},
_ => GizmoLineStyle::Solid,
};
}