From c425fc7f32f90de840e5260cc057e37e5b83d33a Mon Sep 17 00:00:00 2001 From: Lynn <62256001+lynn-lumen@users.noreply.github.com> Date: Wed, 18 Dec 2024 21:43:58 +0100 Subject: [PATCH] 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 --- crates/bevy_gizmos/src/config.rs | 31 ++++++++++++++++++++++++++- crates/bevy_gizmos/src/lib.rs | 28 ++++++++++++++++++++++-- crates/bevy_gizmos/src/lines.wgsl | 23 ++++++++++++++++++-- crates/bevy_gizmos/src/pipeline_2d.rs | 1 + crates/bevy_gizmos/src/pipeline_3d.rs | 1 + crates/bevy_gizmos/src/retained.rs | 20 +++++++++++++++++ examples/gizmos/2d_gizmos.rs | 8 +++++++ examples/gizmos/3d_gizmos.rs | 8 +++++++ 8 files changed, 115 insertions(+), 5 deletions(-) diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs index 946ca4f33852c..76b22bead2495 100644 --- a/crates/bevy_gizmos/src/config.rs +++ b/crates/bevy_gizmos/src/config.rs @@ -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(&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. diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index a3e34e9ca7f35..242f19bc6bd5d 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -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>, config: Extract>, ) { + 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. diff --git a/crates/bevy_gizmos/src/lines.wgsl b/crates/bevy_gizmos/src/lines.wgsl index 67d34e8a46f25..1181e3f59cdb8 100644 --- a/crates/bevy_gizmos/src/lines.wgsl +++ b/crates/bevy_gizmos/src/lines.wgsl @@ -8,9 +8,12 @@ struct LineGizmoUniform { world_from_local: mat3x4, 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, + _padding: vec3, #endif } @@ -28,6 +31,7 @@ struct VertexOutput { @builtin(position) clip_position: vec4, @location(0) color: vec4, @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, b: vec4) -> vec4 { @@ -147,6 +153,7 @@ struct FragmentInput { @builtin(position) position: vec4, @location(0) color: vec4, @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)); +} diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index d4420a86889b5..89d6cec6260b6 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -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 { diff --git a/crates/bevy_gizmos/src/pipeline_3d.rs b/crates/bevy_gizmos/src/pipeline_3d.rs index 7b960e173c5f2..025cc4c7c033b 100644 --- a/crates/bevy_gizmos/src/pipeline_3d.rs +++ b/crates/bevy_gizmos/src/pipeline_3d.rs @@ -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 { diff --git a/crates/bevy_gizmos/src/retained.rs b/crates/bevy_gizmos/src/retained.rs index ab525082691b2..9cb6791aca182 100644 --- a/crates/bevy_gizmos/src/retained.rs +++ b/crates/bevy_gizmos/src/retained.rs @@ -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(), }, diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index bdf444dd5d8fc..fd6f69610c126 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -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, }; } diff --git a/examples/gizmos/3d_gizmos.rs b/examples/gizmos/3d_gizmos.rs index 1a34717bf3698..09514f365fbd9 100644 --- a/examples/gizmos/3d_gizmos.rs +++ b/examples/gizmos/3d_gizmos.rs @@ -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, }; }