diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs index 04d11c8437d3d..92f6962c19384 100644 --- a/crates/bevy_gizmos/src/config.rs +++ b/crates/bevy_gizmos/src/config.rs @@ -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`] @@ -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. @@ -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(), @@ -174,6 +188,7 @@ impl Default for GizmoConfig { #[derive(Component)] pub(crate) struct GizmoMeshConfig { pub line_perspective: bool, + pub line_style: GizmoLineStyle, pub render_layers: RenderLayers, } @@ -181,6 +196,7 @@ 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, } } diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 9163a12bf669a..df89498b78090 100755 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -48,7 +48,7 @@ pub mod prelude { aabb::{AabbGizmoConfigGroup, ShowAabbGizmo}, config::{ DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, - GizmoLineJoint, + GizmoLineJoint, GizmoLineStyle, }, gizmos::Gizmos, light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo}, diff --git a/crates/bevy_gizmos/src/lines.wgsl b/crates/bevy_gizmos/src/lines.wgsl index d5c9e1e1476eb..6edc3eca677ec 100644 --- a/crates/bevy_gizmos/src/lines.wgsl +++ b/crates/bevy_gizmos/src/lines.wgsl @@ -26,19 +26,20 @@ struct VertexInput { struct VertexOutput { @builtin(position) clip_position: vec4, @location(0) color: vec4, + @location(1) uv: f32, }; const EPSILON: f32 = 4.88e-04; @vertex fn vertex(vertex: VertexInput) -> VertexOutput { - var positions = array, 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, 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]; @@ -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 @@ -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. { @@ -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, b: vec4) -> vec4 { @@ -111,7 +141,9 @@ fn clip_near_plane(a: vec4, b: vec4) -> vec4 { } struct FragmentInput { + @builtin(position) position: vec4, @location(0) color: vec4, + @location(1) uv: f32, }; struct FragmentOutput { @@ -119,6 +151,17 @@ struct FragmentOutput { }; @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)); +} diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index 8e44a6ddafc57..dac53a4fc4a3e 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -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, @@ -83,6 +83,7 @@ impl FromWorld for LineGizmoPipeline { struct LineGizmoPipelineKey { mesh_key: Mesh2dPipelineKey, strip: bool, + line_style: GizmoLineStyle, } impl SpecializedRenderPipeline for LineGizmoPipeline { @@ -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, @@ -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), @@ -271,6 +277,7 @@ fn queue_line_gizmos_2d( LineGizmoPipelineKey { mesh_key, strip: line_gizmo.strip, + line_style: config.line_style, }, ); diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index ecefb13d510cf..fcde4cc965ba3 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -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, }; @@ -86,6 +86,7 @@ struct LineGizmoPipelineKey { view_key: MeshPipelineKey, strip: bool, perspective: bool, + line_style: GizmoLineStyle, } impl SpecializedRenderPipeline for LineGizmoPipeline { @@ -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, @@ -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), @@ -329,6 +335,7 @@ fn queue_line_gizmos_3d( view_key, strip: line_gizmo.strip, perspective: config.line_perspective, + line_style: config.line_style, }, ); diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index c4aa2c03838ff..27e782da7b043 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -24,7 +24,8 @@ fn setup(mut commands: Commands, asset_server: Res) { "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., @@ -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, @@ -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, diff --git a/examples/gizmos/3d_gizmos.rs b/examples/gizmos/3d_gizmos.rs index b3f76575dc831..5f2f396612cec 100644 --- a/examples/gizmos/3d_gizmos.rs +++ b/examples/gizmos/3d_gizmos.rs @@ -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., @@ -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, @@ -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,