Skip to content

Commit c425fc7

Browse files
lynn-lumenShatur
andauthored
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 <[email protected]>
1 parent d8796ae commit c425fc7

File tree

8 files changed

+115
-5
lines changed

8 files changed

+115
-5
lines changed

crates/bevy_gizmos/src/config.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
1414
use bevy_utils::TypeIdMap;
1515
use core::{
1616
any::TypeId,
17+
hash::Hash,
1718
ops::{Deref, DerefMut},
1819
panic,
1920
};
@@ -36,14 +37,42 @@ pub enum GizmoLineJoint {
3637
}
3738

3839
/// An enum used to configure the style of gizmo lines, similar to CSS line-style
39-
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, Reflect)]
40+
#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
4041
#[non_exhaustive]
4142
pub enum GizmoLineStyle {
4243
/// A solid line without any decorators
4344
#[default]
4445
Solid,
4546
/// A dotted line
4647
Dotted,
48+
/// A dashed line with configurable gap and line sizes
49+
Dashed {
50+
/// The length of the gap in `line_width`s
51+
gap_scale: f32,
52+
/// The length of the visible line in `line_width`s
53+
line_scale: f32,
54+
},
55+
}
56+
57+
impl Eq for GizmoLineStyle {}
58+
59+
impl Hash for GizmoLineStyle {
60+
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
61+
match self {
62+
Self::Solid => {
63+
0u64.hash(state);
64+
}
65+
Self::Dotted => 1u64.hash(state),
66+
Self::Dashed {
67+
gap_scale,
68+
line_scale,
69+
} => {
70+
2u64.hash(state);
71+
gap_scale.to_bits().hash(state);
72+
line_scale.to_bits().hash(state);
73+
}
74+
}
75+
}
4776
}
4877

4978
/// A trait used to create gizmo configs groups.

crates/bevy_gizmos/src/lib.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ use bevy_ecs::{
8181
schedule::{IntoSystemConfigs, SystemSet},
8282
system::{Res, ResMut, Resource},
8383
};
84-
use bevy_math::Vec4;
84+
use bevy_math::{Vec3, Vec4};
8585
use bevy_reflect::TypePath;
8686

8787
#[cfg(all(
@@ -419,6 +419,9 @@ fn extract_gizmo_data(
419419
handles: Extract<Res<GizmoHandles>>,
420420
config: Extract<Res<GizmoConfigStore>>,
421421
) {
422+
use bevy_utils::warn_once;
423+
use config::GizmoLineStyle;
424+
422425
for (group_type_id, handle) in &handles.handles {
423426
let Some((config, _)) = config.get_config_dyn(group_type_id) else {
424427
continue;
@@ -438,12 +441,30 @@ fn extract_gizmo_data(
438441
0
439442
};
440443

444+
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
445+
gap_scale,
446+
line_scale,
447+
} = config.line.style
448+
{
449+
if gap_scale <= 0.0 {
450+
warn_once!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero.");
451+
}
452+
if line_scale <= 0.0 {
453+
warn_once!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero.");
454+
}
455+
(gap_scale, line_scale)
456+
} else {
457+
(1.0, 1.0)
458+
};
459+
441460
commands.spawn((
442461
LineGizmoUniform {
443462
world_from_local: Affine3::from(&Affine3A::IDENTITY).to_transpose(),
444463
line_width: config.line.width,
445464
depth_bias: config.depth_bias,
446465
joints_resolution,
466+
gap_scale,
467+
line_scale,
447468
#[cfg(feature = "webgl")]
448469
_padding: Default::default(),
449470
},
@@ -471,9 +492,12 @@ struct LineGizmoUniform {
471492
depth_bias: f32,
472493
// Only used by gizmo line t if the current configs `line_joints` is set to `GizmoLineJoint::Round(_)`
473494
joints_resolution: u32,
495+
// Only used if the current configs `line_style` is set to `GizmoLineStyle::Dashed{_}`
496+
gap_scale: f32,
497+
line_scale: f32,
474498
/// WebGL2 structs must be 16 byte aligned.
475499
#[cfg(feature = "webgl")]
476-
_padding: f32,
500+
_padding: Vec3,
477501
}
478502

479503
/// A collection of gizmos.

crates/bevy_gizmos/src/lines.wgsl

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ struct LineGizmoUniform {
88
world_from_local: mat3x4<f32>,
99
line_width: f32,
1010
depth_bias: f32,
11+
_joints_resolution: u32,
12+
gap_scale: f32,
13+
line_scale: f32,
1114
#ifdef SIXTEEN_BYTE_ALIGNMENT
1215
// WebGL2 structs must be 16 byte aligned.
13-
_padding: vec2<f32>,
16+
_padding: vec3<f32>,
1417
#endif
1518
}
1619

@@ -28,6 +31,7 @@ struct VertexOutput {
2831
@builtin(position) clip_position: vec4<f32>,
2932
@location(0) color: vec4<f32>,
3033
@location(1) uv: f32,
34+
@location(2) line_fraction: f32,
3135
};
3236

3337
const EPSILON: f32 = 4.88e-04;
@@ -126,7 +130,9 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
126130

127131
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
128132

129-
return VertexOutput(clip_position, color, uv);
133+
let line_fraction = 2.0 * line_gizmo.line_scale / (line_gizmo.gap_scale + line_gizmo.line_scale);
134+
uv /= (line_gizmo.gap_scale + line_gizmo.line_scale) / 2.0;
135+
return VertexOutput(clip_position, color, uv, line_fraction);
130136
}
131137

132138
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
@@ -147,6 +153,7 @@ struct FragmentInput {
147153
@builtin(position) position: vec4<f32>,
148154
@location(0) color: vec4<f32>,
149155
@location(1) uv: f32,
156+
@location(2) line_fraction: f32,
150157
};
151158

152159
struct FragmentOutput {
@@ -168,3 +175,15 @@ fn fragment_dotted(in: FragmentInput) -> FragmentOutput {
168175

169176
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
170177
}
178+
179+
@fragment
180+
fn fragment_dashed(in: FragmentInput) -> FragmentOutput {
181+
#ifdef PERSPECTIVE
182+
let uv = in.uv;
183+
#else
184+
let uv = in.uv * in.position.w;
185+
#endif
186+
let alpha = 1.0 - floor(min((uv % 2.0) / in.line_fraction, 1.0));
187+
188+
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
189+
}

crates/bevy_gizmos/src/pipeline_2d.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
118118
let fragment_entry_point = match key.line_style {
119119
GizmoLineStyle::Solid => "fragment_solid",
120120
GizmoLineStyle::Dotted => "fragment_dotted",
121+
GizmoLineStyle::Dashed { .. } => "fragment_dashed",
121122
};
122123

123124
RenderPipelineDescriptor {

crates/bevy_gizmos/src/pipeline_3d.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
124124
let fragment_entry_point = match key.line_style {
125125
GizmoLineStyle::Solid => "fragment_solid",
126126
GizmoLineStyle::Dotted => "fragment_dotted",
127+
GizmoLineStyle::Dashed { .. } => "fragment_dashed",
127128
};
128129

129130
RenderPipelineDescriptor {

crates/bevy_gizmos/src/retained.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ pub(crate) fn extract_linegizmos(
106106
) {
107107
use bevy_math::Affine3;
108108
use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
109+
use bevy_utils::warn_once;
110+
111+
use crate::config::GizmoLineStyle;
109112

110113
let mut values = Vec::with_capacity(*previous_len);
111114
for (entity, gizmo, transform, render_layers) in &query {
@@ -115,13 +118,30 @@ pub(crate) fn extract_linegizmos(
115118
} else {
116119
0
117120
};
121+
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
122+
gap_scale,
123+
line_scale,
124+
} = gizmo.line_config.style
125+
{
126+
if gap_scale <= 0.0 {
127+
warn_once!("when using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero");
128+
}
129+
if line_scale <= 0.0 {
130+
warn_once!("when using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero");
131+
}
132+
(gap_scale, line_scale)
133+
} else {
134+
(1.0, 1.0)
135+
};
118136

119137
values.push((
120138
LineGizmoUniform {
121139
world_from_local: Affine3::from(&transform.affine()).to_transpose(),
122140
line_width: gizmo.line_config.width,
123141
depth_bias: gizmo.depth_bias,
124142
joints_resolution,
143+
gap_scale,
144+
line_scale,
125145
#[cfg(feature = "webgl")]
126146
_padding: Default::default(),
127147
},

examples/gizmos/2d_gizmos.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ fn update_config(
142142
if keyboard.just_pressed(KeyCode::KeyU) {
143143
config.line.style = match config.line.style {
144144
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
145+
GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
146+
gap_scale: 3.0,
147+
line_scale: 5.0,
148+
},
145149
_ => GizmoLineStyle::Solid,
146150
};
147151
}
@@ -169,6 +173,10 @@ fn update_config(
169173
if keyboard.just_pressed(KeyCode::KeyI) {
170174
my_config.line.style = match my_config.line.style {
171175
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
176+
GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
177+
gap_scale: 3.0,
178+
line_scale: 5.0,
179+
},
172180
_ => GizmoLineStyle::Solid,
173181
};
174182
}

examples/gizmos/3d_gizmos.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ fn update_config(
237237
if keyboard.just_pressed(KeyCode::KeyU) {
238238
config.line.style = match config.line.style {
239239
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
240+
GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
241+
gap_scale: 3.0,
242+
line_scale: 5.0,
243+
},
240244
_ => GizmoLineStyle::Solid,
241245
};
242246
}
@@ -264,6 +268,10 @@ fn update_config(
264268
if keyboard.just_pressed(KeyCode::KeyI) {
265269
my_config.line.style = match my_config.line.style {
266270
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
271+
GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
272+
gap_scale: 3.0,
273+
line_scale: 5.0,
274+
},
267275
_ => GizmoLineStyle::Solid,
268276
};
269277
}

0 commit comments

Comments
 (0)