Skip to content

Commit 97a5059

Browse files
lynn-lumenBD103
andauthored
Gizmo line styles (#12394)
# Objective - Adds line styles to bevy gizmos, suggestion of #9400 - Currently solid and dotted lines are implemented but this can easily be extended to support dashed lines as well if that is wanted. ## Solution - Adds the enum `GizmoLineStyle` and uses it in each `GizmoConfig` to configure the style of the line. - Each "dot" in a dotted line has the same width and height as the `line_width` of the corresponding line. --- ## Changelog - Added `GizmoLineStyle` to `bevy_gizmos` - Added a `line_style: GizmoLineStyle ` attribute to `GizmoConfig` - Updated the `lines.wgsl` shader and the pipelines accordingly. ## Migration Guide - Any manually created `GizmoConfig` must now include the `line_style` attribute ## Additional information Some pretty pictures :) This is the 3d_gizmos example with/without `line_perspective`: <img width="1440" alt="Screenshot 2024-03-09 at 23 25 53" src="https://github.com/bevyengine/bevy/assets/62256001/b1b97311-e78d-4de3-8dfe-9e48a35bb27d"> <img width="1440" alt="Screenshot 2024-03-09 at 23 25 39" src="https://github.com/bevyengine/bevy/assets/62256001/50ee8ecb-5290-484d-ba36-7fd028374f7f"> And the 2d example: <img width="1440" alt="Screenshot 2024-03-09 at 23 25 06" src="https://github.com/bevyengine/bevy/assets/62256001/4452168f-d605-4333-bfa5-5461d268b132"> --------- Co-authored-by: BD103 <[email protected]>
1 parent b359740 commit 97a5059

File tree

7 files changed

+122
-23
lines changed

7 files changed

+122
-23
lines changed

crates/bevy_gizmos/src/config.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ pub enum GizmoLineJoint {
3030
Bevel,
3131
}
3232

33+
/// An enum used to configure the style of gizmo lines, similar to CSS line-style
34+
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, Reflect)]
35+
#[non_exhaustive]
36+
pub enum GizmoLineStyle {
37+
/// A solid line without any decorators
38+
#[default]
39+
Solid,
40+
/// A dotted line
41+
Dotted,
42+
}
43+
3344
/// A trait used to create gizmo configs groups.
3445
///
3546
/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
@@ -135,6 +146,8 @@ pub struct GizmoConfig {
135146
///
136147
/// Defaults to `false`.
137148
pub line_perspective: bool,
149+
/// Determine the style of gizmo lines.
150+
pub line_style: GizmoLineStyle,
138151
/// How closer to the camera than real geometry the line should be.
139152
///
140153
/// In 2D this setting has no effect and is effectively always -1.
@@ -163,6 +176,7 @@ impl Default for GizmoConfig {
163176
enabled: true,
164177
line_width: 2.,
165178
line_perspective: false,
179+
line_style: GizmoLineStyle::Solid,
166180
depth_bias: 0.,
167181
render_layers: Default::default(),
168182

@@ -174,13 +188,15 @@ impl Default for GizmoConfig {
174188
#[derive(Component)]
175189
pub(crate) struct GizmoMeshConfig {
176190
pub line_perspective: bool,
191+
pub line_style: GizmoLineStyle,
177192
pub render_layers: RenderLayers,
178193
}
179194

180195
impl From<&GizmoConfig> for GizmoMeshConfig {
181196
fn from(item: &GizmoConfig) -> Self {
182197
GizmoMeshConfig {
183198
line_perspective: item.line_perspective,
199+
line_style: item.line_style,
184200
render_layers: item.render_layers,
185201
}
186202
}

crates/bevy_gizmos/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub mod prelude {
5353
aabb::{AabbGizmoConfigGroup, ShowAabbGizmo},
5454
config::{
5555
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
56-
GizmoLineJoint,
56+
GizmoLineJoint, GizmoLineStyle,
5757
},
5858
gizmos::Gizmos,
5959
light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo},

crates/bevy_gizmos/src/lines.wgsl

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,20 @@ struct VertexInput {
2626
struct VertexOutput {
2727
@builtin(position) clip_position: vec4<f32>,
2828
@location(0) color: vec4<f32>,
29+
@location(1) uv: f32,
2930
};
3031

3132
const EPSILON: f32 = 4.88e-04;
3233

3334
@vertex
3435
fn vertex(vertex: VertexInput) -> VertexOutput {
35-
var positions = array<vec3<f32>, 6>(
36-
vec3(0., -0.5, 0.),
37-
vec3(0., -0.5, 1.),
38-
vec3(0., 0.5, 1.),
39-
vec3(0., -0.5, 0.),
40-
vec3(0., 0.5, 1.),
41-
vec3(0., 0.5, 0.)
36+
var positions = array<vec2<f32>, 6>(
37+
vec2(-0.5, 0.),
38+
vec2(-0.5, 1.),
39+
vec2(0.5, 1.),
40+
vec2(-0.5, 0.),
41+
vec2(0.5, 1.),
42+
vec2(0.5, 0.)
4243
);
4344
let position = positions[vertex.index];
4445

@@ -49,23 +50,52 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
4950
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
5051
clip_a = clip_near_plane(clip_a, clip_b);
5152
clip_b = clip_near_plane(clip_b, clip_a);
52-
53-
let clip = mix(clip_a, clip_b, position.z);
53+
let clip = mix(clip_a, clip_b, position.y);
5454

5555
let resolution = view.viewport.zw;
5656
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
5757
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
5858

59-
let x_basis = normalize(screen_a - screen_b);
60-
let y_basis = vec2(-x_basis.y, x_basis.x);
59+
let y_basis = normalize(screen_b - screen_a);
60+
let x_basis = vec2(-y_basis.y, y_basis.x);
6161

62-
var color = mix(vertex.color_a, vertex.color_b, position.z);
62+
var color = mix(vertex.color_a, vertex.color_b, position.y);
6363

6464
var line_width = line_gizmo.line_width;
6565
var alpha = 1.;
6666

67+
var uv: f32;
6768
#ifdef PERSPECTIVE
6869
line_width /= clip.w;
70+
71+
// get height of near clipping plane in world space
72+
let pos0 = view.inverse_projection * vec4(0, -1, 0, 1); // Bottom of the screen
73+
let pos1 = view.inverse_projection * vec4(0, 1, 0, 1); // Top of the screen
74+
let near_clipping_plane_height = length(pos0.xyz - pos1.xyz);
75+
76+
// We can't use vertex.position_X because we may have changed the clip positions with clip_near_plane
77+
let position_a = view.inverse_view_proj * clip_a;
78+
let position_b = view.inverse_view_proj * clip_b;
79+
let world_distance = length(position_a.xyz - position_b.xyz);
80+
81+
// Offset to compensate for moved clip positions. If removed dots on lines will slide when position a is ofscreen.
82+
let clipped_offset = length(position_a.xyz - vertex.position_a);
83+
84+
uv = (clipped_offset + position.y * world_distance) * resolution.y / near_clipping_plane_height / line_gizmo.line_width;
85+
#else
86+
// Get the distance of b to the camera along camera axes
87+
let camera_b = view.inverse_projection * clip_b;
88+
89+
// This differentiates between orthographic and perspective cameras.
90+
// For orthographic cameras no depth adaptment (depth_adaptment = 1) is needed.
91+
var depth_adaptment: f32;
92+
if (clip_b.w == 1.0) {
93+
depth_adaptment = 1.0;
94+
}
95+
else {
96+
depth_adaptment = -camera_b.z;
97+
}
98+
uv = position.y * depth_adaptment * length(screen_b - screen_a) / line_gizmo.line_width;
6999
#endif
70100

71101
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
@@ -74,8 +104,8 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
74104
line_width = 1.;
75105
}
76106

77-
let offset = line_width * (position.x * x_basis + position.y * y_basis);
78-
let screen = mix(screen_a, screen_b, position.z) + offset;
107+
let x_offset = line_width * position.x * x_basis;
108+
let screen = mix(screen_a, screen_b, position.y) + x_offset;
79109

80110
var depth: f32;
81111
if line_gizmo.depth_bias >= 0. {
@@ -93,7 +123,7 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
93123

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

96-
return VertexOutput(clip_position, color);
126+
return VertexOutput(clip_position, color, uv);
97127
}
98128

99129
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
@@ -111,14 +141,27 @@ fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
111141
}
112142

113143
struct FragmentInput {
144+
@builtin(position) position: vec4<f32>,
114145
@location(0) color: vec4<f32>,
146+
@location(1) uv: f32,
115147
};
116148

117149
struct FragmentOutput {
118150
@location(0) color: vec4<f32>,
119151
};
120152

121153
@fragment
122-
fn fragment(in: FragmentInput) -> FragmentOutput {
154+
fn fragment_solid(in: FragmentInput) -> FragmentOutput {
123155
return FragmentOutput(in.color);
124156
}
157+
@fragment
158+
fn fragment_dotted(in: FragmentInput) -> FragmentOutput {
159+
var alpha: f32;
160+
#ifdef PERSPECTIVE
161+
alpha = 1 - floor(in.uv % 2.0);
162+
#else
163+
alpha = 1 - floor((in.uv * in.position.w) % 2.0);
164+
#endif
165+
166+
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
167+
}

crates/bevy_gizmos/src/pipeline_2d.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
config::{GizmoLineJoint, GizmoMeshConfig},
2+
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
33
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
44
DrawLineJointGizmo, GizmoRenderSystem, LineGizmo, LineGizmoUniformBindgroupLayout,
55
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
@@ -83,6 +83,7 @@ impl FromWorld for LineGizmoPipeline {
8383
struct LineGizmoPipelineKey {
8484
mesh_key: Mesh2dPipelineKey,
8585
strip: bool,
86+
line_style: GizmoLineStyle,
8687
}
8788

8889
impl SpecializedRenderPipeline for LineGizmoPipeline {
@@ -105,6 +106,11 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
105106
self.uniform_layout.clone(),
106107
];
107108

109+
let fragment_entry_point = match key.line_style {
110+
GizmoLineStyle::Solid => "fragment_solid",
111+
GizmoLineStyle::Dotted => "fragment_dotted",
112+
};
113+
108114
RenderPipelineDescriptor {
109115
vertex: VertexState {
110116
shader: LINE_SHADER_HANDLE,
@@ -115,7 +121,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
115121
fragment: Some(FragmentState {
116122
shader: LINE_SHADER_HANDLE,
117123
shader_defs,
118-
entry_point: "fragment".into(),
124+
entry_point: fragment_entry_point.into(),
119125
targets: vec![Some(ColorTargetState {
120126
format,
121127
blend: Some(BlendState::ALPHA_BLENDING),
@@ -271,6 +277,7 @@ fn queue_line_gizmos_2d(
271277
LineGizmoPipelineKey {
272278
mesh_key,
273279
strip: line_gizmo.strip,
280+
line_style: config.line_style,
274281
},
275282
);
276283

crates/bevy_gizmos/src/pipeline_3d.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
2-
config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts,
3-
line_joint_gizmo_vertex_buffer_layouts, prelude::GizmoLineJoint, DrawLineGizmo,
2+
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
3+
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
44
DrawLineJointGizmo, GizmoRenderSystem, LineGizmo, LineGizmoUniformBindgroupLayout,
55
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
66
};
@@ -86,6 +86,7 @@ struct LineGizmoPipelineKey {
8686
view_key: MeshPipelineKey,
8787
strip: bool,
8888
perspective: bool,
89+
line_style: GizmoLineStyle,
8990
}
9091

9192
impl SpecializedRenderPipeline for LineGizmoPipeline {
@@ -114,6 +115,11 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
114115

115116
let layout = vec![view_layout, self.uniform_layout.clone()];
116117

118+
let fragment_entry_point = match key.line_style {
119+
GizmoLineStyle::Solid => "fragment_solid",
120+
GizmoLineStyle::Dotted => "fragment_dotted",
121+
};
122+
117123
RenderPipelineDescriptor {
118124
vertex: VertexState {
119125
shader: LINE_SHADER_HANDLE,
@@ -124,7 +130,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
124130
fragment: Some(FragmentState {
125131
shader: LINE_SHADER_HANDLE,
126132
shader_defs,
127-
entry_point: "fragment".into(),
133+
entry_point: fragment_entry_point.into(),
128134
targets: vec![Some(ColorTargetState {
129135
format,
130136
blend: Some(BlendState::ALPHA_BLENDING),
@@ -329,6 +335,7 @@ fn queue_line_gizmos_3d(
329335
view_key,
330336
strip: line_gizmo.strip,
331337
perspective: config.line_perspective,
338+
line_style: config.line_style,
332339
},
333340
);
334341

examples/gizmos/2d_gizmos.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
2424
"Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
2525
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
2626
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
27-
Press 'J' or 'K' to cycle through line joints for straight or round gizmos",
27+
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
28+
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
2829
TextStyle {
2930
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
3031
font_size: 24.,
@@ -107,6 +108,12 @@ fn update_config(
107108
if keyboard.just_pressed(KeyCode::Digit1) {
108109
config.enabled ^= true;
109110
}
111+
if keyboard.just_pressed(KeyCode::KeyU) {
112+
config.line_style = match config.line_style {
113+
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
114+
_ => GizmoLineStyle::Solid,
115+
};
116+
}
110117
if keyboard.just_pressed(KeyCode::KeyJ) {
111118
config.line_joints = match config.line_joints {
112119
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
@@ -128,6 +135,12 @@ fn update_config(
128135
if keyboard.just_pressed(KeyCode::Digit2) {
129136
my_config.enabled ^= true;
130137
}
138+
if keyboard.just_pressed(KeyCode::KeyI) {
139+
my_config.line_style = match my_config.line_style {
140+
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
141+
_ => GizmoLineStyle::Solid,
142+
};
143+
}
131144
if keyboard.just_pressed(KeyCode::KeyK) {
132145
my_config.line_joints = match my_config.line_joints {
133146
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,

examples/gizmos/3d_gizmos.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ fn setup(
5959
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
6060
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
6161
Press 'A' to show all AABB boxes\n\
62+
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
6263
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
6364
TextStyle {
6465
font_size: 20.,
@@ -169,6 +170,12 @@ fn update_config(
169170
if keyboard.just_pressed(KeyCode::Digit1) {
170171
config.enabled ^= true;
171172
}
173+
if keyboard.just_pressed(KeyCode::KeyU) {
174+
config.line_style = match config.line_style {
175+
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
176+
_ => GizmoLineStyle::Solid,
177+
};
178+
}
172179
if keyboard.just_pressed(KeyCode::KeyJ) {
173180
config.line_joints = match config.line_joints {
174181
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
@@ -190,6 +197,12 @@ fn update_config(
190197
if keyboard.just_pressed(KeyCode::Digit2) {
191198
my_config.enabled ^= true;
192199
}
200+
if keyboard.just_pressed(KeyCode::KeyI) {
201+
my_config.line_style = match my_config.line_style {
202+
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
203+
_ => GizmoLineStyle::Solid,
204+
};
205+
}
193206
if keyboard.just_pressed(KeyCode::KeyK) {
194207
my_config.line_joints = match my_config.line_joints {
195208
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,

0 commit comments

Comments
 (0)