From c5dd17a5d9628a80f73fae6af229b0f619a77858 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 09:38:25 +0000 Subject: [PATCH 1/8] Add optional transparancy passthrough for sprite backend --- crates/bevy_sprite/src/picking_backend.rs | 61 +++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index ad2afc33d1954..65c07e51e6803 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -11,16 +11,39 @@ use bevy_ecs::prelude::*; use bevy_image::Image; use bevy_math::{prelude::*, FloatExt, FloatOrd}; use bevy_picking::backend::prelude::*; +use bevy_reflect::prelude::*; use bevy_render::prelude::*; use bevy_transform::prelude::*; use bevy_window::PrimaryWindow; +/// Runtime settings for the [`SpriteBackend`]. +#[derive(Resource, Reflect)] +#[reflect(Resource, Default)] +pub struct SpriteBackendSettings { + /// When set to `true` picking will ignore any part of a sprite which is transparent + /// Off by default for backwards compatibility. This setting is provided to give you fine-grained + /// control over if transparncy on sprites is ignored. + pub passthrough_transparency: bool, + /// How Opaque does part of a sprite need to be in order count as none-transparent (defaults to 10) + pub transparency_cutoff: u8, +} + +impl Default for SpriteBackendSettings { + fn default() -> Self { + Self { + passthrough_transparency: true, + transparency_cutoff: 10, + } + } +} + #[derive(Clone)] pub struct SpritePickingPlugin; impl Plugin for SpritePickingPlugin { fn build(&self, app: &mut App) { - app.add_systems(PreUpdate, sprite_picking.in_set(PickSet::Backend)); + app.init_resource::() + .add_systems(PreUpdate, sprite_picking.in_set(PickSet::Backend)); } } @@ -30,6 +53,7 @@ pub fn sprite_picking( primary_window: Query>, images: Res>, texture_atlas_layout: Res>, + settings: Res, sprite_query: Query<( Entity, &Sprite, @@ -130,12 +154,43 @@ pub fn sprite_picking( let is_cursor_in_sprite = rect.contains(cursor_pos_sprite); - blocked = is_cursor_in_sprite + let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite + && (!settings.passthrough_transparency || { + let texture: &Image = images.get(&sprite.image)?; + // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise + let texture_rect = sprite + .texture_atlas + .as_ref() + .and_then(|atlas| { + texture_atlas_layout + .get(&atlas.layout) + .map(|f| f.textures[atlas.index]) + }) + .or(Some(URect::new(0, 0, texture.width(), texture.height())))?; + // get mouse position on texture + let texture_position = (texture_rect.center().as_vec2() + + cursor_pos_sprite.trunc()) + .as_uvec2(); + // grab pixel + let pixel_index = + (texture_position.y * texture.width() + texture_position.x) as usize; + // check transparancy + if let Some(pixel_data) = + texture.data.get(pixel_index * 4..(pixel_index * 4 + 4)) + { + let transparency = pixel_data[3]; + transparency > settings.transparency_cutoff + } else { + false + } + }); + + blocked = cursor_in_valid_pixels_of_sprite && picking_behavior .map(|p| p.should_block_lower) .unwrap_or(true); - is_cursor_in_sprite.then(|| { + cursor_in_valid_pixels_of_sprite.then(|| { let hit_pos_world = sprite_transform.transform_point(cursor_pos_sprite.extend(0.0)); // Transform point from world to camera space to get the Z distance From bba576b67ff04d5549a06db68ad8d8eb5b05eded Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 09:39:49 +0000 Subject: [PATCH 2/8] set correct default --- crates/bevy_sprite/src/picking_backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 65c07e51e6803..b59917c7a902e 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -31,7 +31,7 @@ pub struct SpriteBackendSettings { impl Default for SpriteBackendSettings { fn default() -> Self { Self { - passthrough_transparency: true, + passthrough_transparency: false, transparency_cutoff: 10, } } From f9088c793af70d70d69437f6d893100304f6a4b7 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 09:44:44 +0000 Subject: [PATCH 3/8] fix typo --- crates/bevy_sprite/src/picking_backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index b59917c7a902e..cfe29c823248a 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -174,7 +174,7 @@ pub fn sprite_picking( // grab pixel let pixel_index = (texture_position.y * texture.width() + texture_position.x) as usize; - // check transparancy + // check transparency if let Some(pixel_data) = texture.data.get(pixel_index * 4..(pixel_index * 4 + 4)) { From d1013cfe85a21fbd35ae86ed8a1a48da811543d7 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 09:51:08 +0000 Subject: [PATCH 4/8] fix too many arguments --- crates/bevy_sprite/src/picking_backend.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index cfe29c823248a..939afbc36b4ea 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -47,6 +47,7 @@ impl Plugin for SpritePickingPlugin { } } +#[allow(clippy::too_many_arguments)] pub fn sprite_picking( pointers: Query<(&PointerId, &PointerLocation)>, cameras: Query<(Entity, &Camera, &GlobalTransform, &OrthographicProjection)>, From 1924e0256e8f9f3273c6c094cfb170dbe29771a2 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 09:58:25 +0000 Subject: [PATCH 5/8] fix doc typo --- crates/bevy_sprite/src/picking_backend.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 939afbc36b4ea..a02323fcf29eb 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -22,7 +22,7 @@ use bevy_window::PrimaryWindow; pub struct SpriteBackendSettings { /// When set to `true` picking will ignore any part of a sprite which is transparent /// Off by default for backwards compatibility. This setting is provided to give you fine-grained - /// control over if transparncy on sprites is ignored. + /// control over if transparency on sprites is ignored. pub passthrough_transparency: bool, /// How Opaque does part of a sprite need to be in order count as none-transparent (defaults to 10) pub transparency_cutoff: u8, From 42c156bd05a6c25c1276dcf5e6fa82b10a443615 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 10:02:45 +0000 Subject: [PATCH 6/8] clean up final check into match --- crates/bevy_sprite/src/picking_backend.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index a02323fcf29eb..377690f2fda7a 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -176,13 +176,13 @@ pub fn sprite_picking( let pixel_index = (texture_position.y * texture.width() + texture_position.x) as usize; // check transparency - if let Some(pixel_data) = - texture.data.get(pixel_index * 4..(pixel_index * 4 + 4)) - { - let transparency = pixel_data[3]; - transparency > settings.transparency_cutoff - } else { - false + match texture.data.get(pixel_index * 4..(pixel_index * 4 + 4)) { + // If possible check the transparency bit is above cutoff + Some(pixel_data) if pixel_data[3] > settings.transparency_cutoff => { + true + } + // If not possible, it's not in the sprite + _ => false, } }); From 3ca1039930269554364775cde298f7056d0c5a28 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 10:18:01 +0000 Subject: [PATCH 7/8] fix docs and naming --- crates/bevy_sprite/src/picking_backend.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index 377690f2fda7a..d3a8b295b56e6 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -16,22 +16,24 @@ use bevy_render::prelude::*; use bevy_transform::prelude::*; use bevy_window::PrimaryWindow; -/// Runtime settings for the [`SpriteBackend`]. +/// Runtime settings for the [`SpritePickingPlugin`]. #[derive(Resource, Reflect)] #[reflect(Resource, Default)] pub struct SpriteBackendSettings { /// When set to `true` picking will ignore any part of a sprite which is transparent /// Off by default for backwards compatibility. This setting is provided to give you fine-grained /// control over if transparency on sprites is ignored. - pub passthrough_transparency: bool, + pub transparency_passthrough: bool, /// How Opaque does part of a sprite need to be in order count as none-transparent (defaults to 10) + /// + /// This is on a scale from 0 - 255 representing the alpha channel value you'd get in most art programs. pub transparency_cutoff: u8, } impl Default for SpriteBackendSettings { fn default() -> Self { Self { - passthrough_transparency: false, + transparency_passthrough: false, transparency_cutoff: 10, } } @@ -156,7 +158,7 @@ pub fn sprite_picking( let is_cursor_in_sprite = rect.contains(cursor_pos_sprite); let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite - && (!settings.passthrough_transparency || { + && (!settings.transparency_passthrough || { let texture: &Image = images.get(&sprite.image)?; // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise let texture_rect = sprite From 703810190a894dbec792b47777ca000b067c7115 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 10:19:44 +0000 Subject: [PATCH 8/8] change transparency to alpha in variable names --- crates/bevy_sprite/src/picking_backend.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/bevy_sprite/src/picking_backend.rs b/crates/bevy_sprite/src/picking_backend.rs index d3a8b295b56e6..0cc4b9a6f076e 100644 --- a/crates/bevy_sprite/src/picking_backend.rs +++ b/crates/bevy_sprite/src/picking_backend.rs @@ -20,21 +20,21 @@ use bevy_window::PrimaryWindow; #[derive(Resource, Reflect)] #[reflect(Resource, Default)] pub struct SpriteBackendSettings { - /// When set to `true` picking will ignore any part of a sprite which is transparent + /// When set to `true` picking will ignore any part of a sprite which has an alpha lower than the cutoff /// Off by default for backwards compatibility. This setting is provided to give you fine-grained /// control over if transparency on sprites is ignored. - pub transparency_passthrough: bool, + pub alpha_passthrough: bool, /// How Opaque does part of a sprite need to be in order count as none-transparent (defaults to 10) /// /// This is on a scale from 0 - 255 representing the alpha channel value you'd get in most art programs. - pub transparency_cutoff: u8, + pub alpha_cutoff: u8, } impl Default for SpriteBackendSettings { fn default() -> Self { Self { - transparency_passthrough: false, - transparency_cutoff: 10, + alpha_passthrough: false, + alpha_cutoff: 10, } } } @@ -158,7 +158,7 @@ pub fn sprite_picking( let is_cursor_in_sprite = rect.contains(cursor_pos_sprite); let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite - && (!settings.transparency_passthrough || { + && (!settings.alpha_passthrough || { let texture: &Image = images.get(&sprite.image)?; // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise let texture_rect = sprite @@ -179,10 +179,8 @@ pub fn sprite_picking( (texture_position.y * texture.width() + texture_position.x) as usize; // check transparency match texture.data.get(pixel_index * 4..(pixel_index * 4 + 4)) { - // If possible check the transparency bit is above cutoff - Some(pixel_data) if pixel_data[3] > settings.transparency_cutoff => { - true - } + // If possible check the alpha bit is above cutoff + Some(pixel_data) if pixel_data[3] > settings.alpha_cutoff => true, // If not possible, it's not in the sprite _ => false, }