Skip to content

Commit

Permalink
Gizmo line styles (#12394)
Browse files Browse the repository at this point in the history
# 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]>
  • Loading branch information
lynn-lumen and BD103 authored Mar 25, 2024
1 parent b359740 commit 97a5059
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 23 deletions.
16 changes: 16 additions & 0 deletions crates/bevy_gizmos/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ pub enum GizmoLineJoint {
Bevel,
}

/// An enum used to configure the style of gizmo lines, similar to CSS line-style
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, Reflect)]
#[non_exhaustive]
pub enum GizmoLineStyle {
/// A solid line without any decorators
#[default]
Solid,
/// A dotted line
Dotted,
}

/// A trait used to create gizmo configs groups.
///
/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
Expand Down Expand Up @@ -135,6 +146,8 @@ pub struct GizmoConfig {
///
/// Defaults to `false`.
pub line_perspective: bool,
/// Determine the style of gizmo lines.
pub line_style: GizmoLineStyle,
/// How closer to the camera than real geometry the line should be.
///
/// In 2D this setting has no effect and is effectively always -1.
Expand Down Expand Up @@ -163,6 +176,7 @@ impl Default for GizmoConfig {
enabled: true,
line_width: 2.,
line_perspective: false,
line_style: GizmoLineStyle::Solid,
depth_bias: 0.,
render_layers: Default::default(),

Expand All @@ -174,13 +188,15 @@ impl Default for GizmoConfig {
#[derive(Component)]
pub(crate) struct GizmoMeshConfig {
pub line_perspective: bool,
pub line_style: GizmoLineStyle,
pub render_layers: RenderLayers,
}

impl From<&GizmoConfig> for GizmoMeshConfig {
fn from(item: &GizmoConfig) -> Self {
GizmoMeshConfig {
line_perspective: item.line_perspective,
line_style: item.line_style,
render_layers: item.render_layers,
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub mod prelude {
aabb::{AabbGizmoConfigGroup, ShowAabbGizmo},
config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
GizmoLineJoint,
GizmoLineJoint, GizmoLineStyle,
},
gizmos::Gizmos,
light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo},
Expand Down
75 changes: 59 additions & 16 deletions crates/bevy_gizmos/src/lines.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,20 @@ struct VertexInput {
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
};

const EPSILON: f32 = 4.88e-04;

@vertex
fn vertex(vertex: VertexInput) -> VertexOutput {
var positions = array<vec3<f32>, 6>(
vec3(0., -0.5, 0.),
vec3(0., -0.5, 1.),
vec3(0., 0.5, 1.),
vec3(0., -0.5, 0.),
vec3(0., 0.5, 1.),
vec3(0., 0.5, 0.)
var positions = array<vec2<f32>, 6>(
vec2(-0.5, 0.),
vec2(-0.5, 1.),
vec2(0.5, 1.),
vec2(-0.5, 0.),
vec2(0.5, 1.),
vec2(0.5, 0.)
);
let position = positions[vertex.index];

Expand All @@ -49,23 +50,52 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_b);
clip_b = clip_near_plane(clip_b, clip_a);

let clip = mix(clip_a, clip_b, position.z);
let clip = mix(clip_a, clip_b, position.y);

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

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

var color = mix(vertex.color_a, vertex.color_b, position.z);
var color = mix(vertex.color_a, vertex.color_b, position.y);

var line_width = line_gizmo.line_width;
var alpha = 1.;

var uv: f32;
#ifdef PERSPECTIVE
line_width /= clip.w;

// get height of near clipping plane in world space
let pos0 = view.inverse_projection * vec4(0, -1, 0, 1); // Bottom of the screen
let pos1 = view.inverse_projection * vec4(0, 1, 0, 1); // Top of the screen
let near_clipping_plane_height = length(pos0.xyz - pos1.xyz);

// We can't use vertex.position_X because we may have changed the clip positions with clip_near_plane
let position_a = view.inverse_view_proj * clip_a;
let position_b = view.inverse_view_proj * clip_b;
let world_distance = length(position_a.xyz - position_b.xyz);

// Offset to compensate for moved clip positions. If removed dots on lines will slide when position a is ofscreen.
let clipped_offset = length(position_a.xyz - vertex.position_a);

uv = (clipped_offset + position.y * world_distance) * resolution.y / near_clipping_plane_height / line_gizmo.line_width;
#else
// Get the distance of b to the camera along camera axes
let camera_b = view.inverse_projection * clip_b;

// This differentiates between orthographic and perspective cameras.
// For orthographic cameras no depth adaptment (depth_adaptment = 1) is needed.
var depth_adaptment: f32;
if (clip_b.w == 1.0) {
depth_adaptment = 1.0;
}
else {
depth_adaptment = -camera_b.z;
}
uv = position.y * depth_adaptment * length(screen_b - screen_a) / line_gizmo.line_width;
#endif

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

let offset = line_width * (position.x * x_basis + position.y * y_basis);
let screen = mix(screen_a, screen_b, position.z) + offset;
let x_offset = line_width * position.x * x_basis;
let screen = mix(screen_a, screen_b, position.y) + x_offset;

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

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

return VertexOutput(clip_position, color);
return VertexOutput(clip_position, color, uv);
}

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

struct FragmentInput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
};

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

@fragment
fn fragment(in: FragmentInput) -> FragmentOutput {
fn fragment_solid(in: FragmentInput) -> FragmentOutput {
return FragmentOutput(in.color);
}
@fragment
fn fragment_dotted(in: FragmentInput) -> FragmentOutput {
var alpha: f32;
#ifdef PERSPECTIVE
alpha = 1 - floor(in.uv % 2.0);
#else
alpha = 1 - floor((in.uv * in.position.w) % 2.0);
#endif

return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
}
11 changes: 9 additions & 2 deletions crates/bevy_gizmos/src/pipeline_2d.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
config::{GizmoLineJoint, GizmoMeshConfig},
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
DrawLineJointGizmo, GizmoRenderSystem, LineGizmo, LineGizmoUniformBindgroupLayout,
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
Expand Down Expand Up @@ -83,6 +83,7 @@ impl FromWorld for LineGizmoPipeline {
struct LineGizmoPipelineKey {
mesh_key: Mesh2dPipelineKey,
strip: bool,
line_style: GizmoLineStyle,
}

impl SpecializedRenderPipeline for LineGizmoPipeline {
Expand All @@ -105,6 +106,11 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
self.uniform_layout.clone(),
];

let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
};

RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_SHADER_HANDLE,
Expand All @@ -115,7 +121,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
fragment: Some(FragmentState {
shader: LINE_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
entry_point: fragment_entry_point.into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
Expand Down Expand Up @@ -271,6 +277,7 @@ fn queue_line_gizmos_2d(
LineGizmoPipelineKey {
mesh_key,
strip: line_gizmo.strip,
line_style: config.line_style,
},
);

Expand Down
13 changes: 10 additions & 3 deletions crates/bevy_gizmos/src/pipeline_3d.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
config::GizmoMeshConfig, line_gizmo_vertex_buffer_layouts,
line_joint_gizmo_vertex_buffer_layouts, prelude::GizmoLineJoint, DrawLineGizmo,
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
DrawLineJointGizmo, GizmoRenderSystem, LineGizmo, LineGizmoUniformBindgroupLayout,
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
};
Expand Down Expand Up @@ -86,6 +86,7 @@ struct LineGizmoPipelineKey {
view_key: MeshPipelineKey,
strip: bool,
perspective: bool,
line_style: GizmoLineStyle,
}

impl SpecializedRenderPipeline for LineGizmoPipeline {
Expand Down Expand Up @@ -114,6 +115,11 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {

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

let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
};

RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_SHADER_HANDLE,
Expand All @@ -124,7 +130,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
fragment: Some(FragmentState {
shader: LINE_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
entry_point: fragment_entry_point.into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
Expand Down Expand Up @@ -329,6 +335,7 @@ fn queue_line_gizmos_3d(
view_key,
strip: line_gizmo.strip,
perspective: config.line_perspective,
line_style: config.line_style,
},
);

Expand Down
15 changes: 14 additions & 1 deletion examples/gizmos/2d_gizmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
"Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
Press 'J' or 'K' to cycle through line joints for straight or round gizmos",
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
TextStyle {
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 24.,
Expand Down Expand Up @@ -107,6 +108,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit1) {
config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyU) {
config.line_style = match config.line_style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
_ => GizmoLineStyle::Solid,
};
}
if keyboard.just_pressed(KeyCode::KeyJ) {
config.line_joints = match config.line_joints {
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
Expand All @@ -128,6 +135,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit2) {
my_config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyI) {
my_config.line_style = match my_config.line_style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
_ => GizmoLineStyle::Solid,
};
}
if keyboard.just_pressed(KeyCode::KeyK) {
my_config.line_joints = match my_config.line_joints {
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
Expand Down
13 changes: 13 additions & 0 deletions examples/gizmos/3d_gizmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ fn setup(
Hold 'Up' or 'Down' to change the line width of round gizmos\n\
Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
Press 'A' to show all AABB boxes\n\
Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
Press 'J' or 'K' to cycle through line joins for straight or round gizmos",
TextStyle {
font_size: 20.,
Expand Down Expand Up @@ -169,6 +170,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit1) {
config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyU) {
config.line_style = match config.line_style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
_ => GizmoLineStyle::Solid,
};
}
if keyboard.just_pressed(KeyCode::KeyJ) {
config.line_joints = match config.line_joints {
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
Expand All @@ -190,6 +197,12 @@ fn update_config(
if keyboard.just_pressed(KeyCode::Digit2) {
my_config.enabled ^= true;
}
if keyboard.just_pressed(KeyCode::KeyI) {
my_config.line_style = match my_config.line_style {
GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
_ => GizmoLineStyle::Solid,
};
}
if keyboard.just_pressed(KeyCode::KeyK) {
my_config.line_joints = match my_config.line_joints {
GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
Expand Down

0 comments on commit 97a5059

Please sign in to comment.