diff --git a/crates/bevy_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml index 3a264c6244609..4373a1f3e1fef 100644 --- a/crates/bevy_gizmos/Cargo.toml +++ b/crates/bevy_gizmos/Cargo.toml @@ -34,6 +34,7 @@ bevy_time = { path = "../bevy_time", version = "0.16.0-dev" } # other bytemuck = "1.0" tracing = { version = "0.1", default-features = false, features = ["std"] } +itertools = { version = "0.14.0", default-features = false } [lints] workspace = true diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs index 973fa1cf0fbce..3086dbf834666 100644 --- a/crates/bevy_gizmos/src/config.rs +++ b/crates/bevy_gizmos/src/config.rs @@ -52,6 +52,8 @@ pub enum GizmoLineStyle { gap_scale: f32, /// The length of the visible line in `line_width`s line_scale: f32, + /// Whether or not the gizmo lines are animated which makes the line move from src to dst + animated: bool, }, } @@ -67,10 +69,12 @@ impl Hash for GizmoLineStyle { Self::Dashed { gap_scale, line_scale, + animated, } => { 2u64.hash(state); gap_scale.to_bits().hash(state); line_scale.to_bits().hash(state); + animated.hash(state); } } } diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 3cd2c7c40447a..700c4aadf31d5 100755 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -176,6 +176,7 @@ impl Plugin for GizmoPlugin { #[cfg(feature = "bevy_render")] app.add_plugins(aabb::AabbGizmoPlugin) .add_plugins(UniformComponentPlugin::::default()) + .add_systems(bevy_app::Update, update_gizmo_time) .add_plugins(RenderAssetPlugin::::default()); #[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))] @@ -217,7 +218,7 @@ impl Plugin for GizmoPlugin { let line_layout = render_device.create_bind_group_layout( "LineGizmoUniform layout", &BindGroupLayoutEntries::single( - ShaderStages::VERTEX, + ShaderStages::VERTEX | ShaderStages::FRAGMENT, uniform_buffer::(true), ), ); @@ -418,11 +419,21 @@ fn update_gizmo_meshes( } } +fn update_gizmo_time( + mut q: bevy_ecs::system::Query<&mut LineGizmoUniform>, + time: Res, +) { + q.iter_mut().for_each(|mut uniform| { + uniform.time = time.elapsed_secs(); + }); +} + #[cfg(feature = "bevy_render")] fn extract_gizmo_data( mut commands: Commands, handles: Extract>, config: Extract>, + time: Extract>, ) { use bevy_utils::once; use config::GizmoLineStyle; @@ -450,6 +461,7 @@ fn extract_gizmo_data( let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed { gap_scale, line_scale, + .. } = config.line.style { if gap_scale <= 0.0 { @@ -471,6 +483,7 @@ fn extract_gizmo_data( joints_resolution, gap_scale, line_scale, + time: time.elapsed_secs(), #[cfg(feature = "webgl")] _padding: Default::default(), }, @@ -501,9 +514,10 @@ struct LineGizmoUniform { // Only used if the current configs `line_style` is set to `GizmoLineStyle::Dashed{_}` gap_scale: f32, line_scale: f32, + time: f32, /// WebGL2 structs must be 16 byte aligned. #[cfg(feature = "webgl")] - _padding: bevy_math::Vec3, + _padding: bevy_math::Vec2, } /// A collection of gizmos. diff --git a/crates/bevy_gizmos/src/lines.wgsl b/crates/bevy_gizmos/src/lines.wgsl index 1181e3f59cdb8..51b424746415c 100644 --- a/crates/bevy_gizmos/src/lines.wgsl +++ b/crates/bevy_gizmos/src/lines.wgsl @@ -11,9 +11,10 @@ struct LineGizmoUniform { _joints_resolution: u32, gap_scale: f32, line_scale: f32, + time: f32, #ifdef SIXTEEN_BYTE_ALIGNMENT // WebGL2 structs must be 16 byte aligned. - _padding: vec3, + _padding: vec2, #endif } @@ -164,6 +165,7 @@ struct FragmentOutput { fn fragment_solid(in: FragmentInput) -> FragmentOutput { return FragmentOutput(in.color); } + @fragment fn fragment_dotted(in: FragmentInput) -> FragmentOutput { var alpha: f32; @@ -187,3 +189,37 @@ fn fragment_dashed(in: FragmentInput) -> FragmentOutput { return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha)); } + +@fragment +fn fragment_dashed_animated(in: FragmentInput) -> FragmentOutput { +#ifdef PERSPECTIVE + let uv = in.uv; +#else + let uv = in.uv * in.position.w; +#endif + // apply animation speed, hardcoded but can be passed via configuration + let speed = 0.5; + let scaled_time = line_gizmo.time * speed; + + // - the uv coordinates are going to be wrapped every 2.0 units + // - we're going to subtract the time from the uv values + // + // the modulo operation doesn't play nice with these two facts which is why + // we need to manually shift and wrap the time + let wrapped_time = scaled_time % 2.0; + + // move the uv values, add two to ensure positive values + let shifted_uv = uv - wrapped_time + 2.0; + + // wrap the uv values and scale appropriately to dash length + let wrapped_uv = (shifted_uv % 2.0) / in.line_fraction; + + // calculate the alpha value to mask out the gaps of the dashed lines + // completely via the opacity of the color + // + // NOTE: the `1.0 -` at the start is a bit useless but I kept it for + // consistency since the other code does it as well + let alpha = 1.0 - floor(min(wrapped_uv, 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 3a4305549108b..a0e63ece0f346 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -119,7 +119,10 @@ impl SpecializedRenderPipeline for LineGizmoPipeline { let fragment_entry_point = match key.line_style { GizmoLineStyle::Solid => "fragment_solid", GizmoLineStyle::Dotted => "fragment_dotted", - GizmoLineStyle::Dashed { .. } => "fragment_dashed", + GizmoLineStyle::Dashed { animated: true, .. } => "fragment_dashed_animated", + GizmoLineStyle::Dashed { + animated: false, .. + } => "fragment_dashed", }; RenderPipelineDescriptor { diff --git a/crates/bevy_gizmos/src/retained.rs b/crates/bevy_gizmos/src/retained.rs index 88610b9744203..2d454435d9ef2 100644 --- a/crates/bevy_gizmos/src/retained.rs +++ b/crates/bevy_gizmos/src/retained.rs @@ -100,6 +100,7 @@ pub(crate) fn extract_linegizmos( mut commands: Commands, mut previous_len: Local, query: Extract)>>, + time: Extract>, ) { use bevy_math::Affine3; use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity}; @@ -119,6 +120,7 @@ pub(crate) fn extract_linegizmos( let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed { gap_scale, line_scale, + .. } = gizmo.line_config.style { if gap_scale <= 0.0 { @@ -140,6 +142,7 @@ pub(crate) fn extract_linegizmos( joints_resolution, gap_scale, line_scale, + time: time.elapsed_secs(), #[cfg(feature = "webgl")] _padding: Default::default(), }, diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index fd6f69610c126..0c49959b941e0 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -8,6 +8,7 @@ fn main() { App::new() .add_plugins(DefaultPlugins) .init_gizmo_group::() + .init_gizmo_group::() .add_systems(Startup, setup) .add_systems(Update, (draw_example_collection, update_config)) .run(); @@ -17,7 +18,11 @@ fn main() { #[derive(Default, Reflect, GizmoConfigGroup)] struct MyRoundGizmos {} -fn setup(mut commands: Commands) { +// We can create our own gizmo config group! +#[derive(Default, Reflect, GizmoConfigGroup)] +struct MyAnimatedGizmos {} + +fn setup(mut commands: Commands, mut config_store: ResMut) { commands.spawn(Camera2d); // text commands.spawn(( @@ -35,11 +40,17 @@ fn setup(mut commands: Commands) { ..default() }, )); + config_store.config_mut::().0.line.style = GizmoLineStyle::Dashed { + gap_scale: 10.0, + line_scale: 10.0, + animated: true, + }; } fn draw_example_collection( mut gizmos: Gizmos, mut my_gizmos: Gizmos, + mut my_animated: Gizmos, time: Res