From 02d66ea5cba70374f9116f848d7b7ce80ca92653 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Wed, 13 Nov 2024 19:20:42 +0000 Subject: [PATCH 1/8] Add transparancy passthrough for sprite backend --- backends/bevy_picking_sprite/Cargo.toml | 1 + backends/bevy_picking_sprite/src/lib.rs | 59 +++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/backends/bevy_picking_sprite/Cargo.toml b/backends/bevy_picking_sprite/Cargo.toml index c93c494a..926c9ff1 100644 --- a/backends/bevy_picking_sprite/Cargo.toml +++ b/backends/bevy_picking_sprite/Cargo.toml @@ -16,6 +16,7 @@ resolver = "2" bevy_app = { version = "0.14.0", default-features = false } bevy_asset = { version = "0.14.0", default-features = false } bevy_ecs = { version = "0.14.0", default-features = false } +bevy_reflect = { version = "0.14.0", default-features = false } bevy_math = { version = "0.14.0", default-features = false } bevy_render = { version = "0.14.0", default-features = false } bevy_sprite = { version = "0.14.0", default-features = false } diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index 1d6f799c..7ae2a855 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -10,6 +10,7 @@ use bevy_app::prelude::*; use bevy_asset::prelude::*; use bevy_ecs::prelude::*; use bevy_math::prelude::*; +use bevy_reflect::prelude::*; use bevy_render::prelude::*; use bevy_sprite::{Sprite, TextureAtlas, TextureAtlasLayout}; use bevy_transform::prelude::*; @@ -22,13 +23,35 @@ pub mod prelude { pub use crate::SpriteBackend; } +/// 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 245) + pub transparency_cutoff: u8, +} + +impl Default for SpriteBackendSettings { + fn default() -> Self { + Self { + passthrough_transparency: true, + transparency_cutoff: 10, + } + } +} + /// Adds picking support for [`bevy_sprite`]. #[derive(Clone)] pub struct SpriteBackend; impl Plugin for SpriteBackend { 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)); } } @@ -39,6 +62,7 @@ pub fn sprite_picking( primary_window: Query>, images: Res>, texture_atlas_layout: Res>, + settings: Res, sprite_query: Query< ( Entity, @@ -125,13 +149,42 @@ pub fn sprite_picking( .transform_point3((cursor_pos_world, 0.0).into()); let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate()); - blocked = is_cursor_in_sprite + + let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite + && settings.passthrough_transparency + && (image.is_none() || { + let texture: &Image = image.and_then(|i| images.get(i))?; + // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise + let texture_rect = atlas + .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())))?; + let texture_position = + texture_rect.center() + cursor_pos_sprite.truncate().as_uvec2(); + let pixel_index = (texture_position.y * texture.width() + + texture_position.x) + as usize; + if let Some(pixel_data) = + texture.data.get(pixel_index * 4..(pixel_index * 4 + 4)) + { + let transparency = pixel_data[3]; + println!("pixel transparency: {}", transparency); + transparency > settings.transparency_cutoff + } else { + false + } + }); + + blocked = cursor_in_valid_pixels_of_sprite && pickable.map(|p| p.should_block_lower) != Some(false); // HitData requires a depth as calculated from the camera's near clipping plane let depth = -cam_ortho.near - sprite_transform.translation().z; - is_cursor_in_sprite + cursor_in_valid_pixels_of_sprite .then_some((entity, HitData::new(cam_entity, depth, None, None))) }, ) From dae666fc8d823c7ca82887dfe9107c2152625cdc Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Wed, 13 Nov 2024 19:32:57 +0000 Subject: [PATCH 2/8] remove debug log --- backends/bevy_picking_sprite/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index 7ae2a855..51ba241c 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -152,7 +152,7 @@ pub fn sprite_picking( let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite && settings.passthrough_transparency - && (image.is_none() || { + && (image.is_some() || { let texture: &Image = image.and_then(|i| images.get(i))?; // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise let texture_rect = atlas @@ -162,16 +162,18 @@ pub fn sprite_picking( .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() + cursor_pos_sprite.truncate().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]; - println!("pixel transparency: {}", transparency); transparency > settings.transparency_cutoff } else { false From e90dd5001b0eddac013d8b5dbd436f4301f77067 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Wed, 13 Nov 2024 19:34:13 +0000 Subject: [PATCH 3/8] passthrough_transparency defaults to false for backwards compat --- backends/bevy_picking_sprite/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index 51ba241c..4a6edfd1 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -38,7 +38,7 @@ pub struct SpriteBackendSettings { impl Default for SpriteBackendSettings { fn default() -> Self { Self { - passthrough_transparency: true, + passthrough_transparency: false, transparency_cutoff: 10, } } From ffdd559be3ee100303c746430b7e9991c09fdb3d Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Wed, 13 Nov 2024 19:42:32 +0000 Subject: [PATCH 4/8] tweak checks --- backends/bevy_picking_sprite/src/lib.rs | 61 +++++++++++++------------ 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index 4a6edfd1..8c120109 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -151,34 +151,39 @@ pub fn sprite_picking( let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate()); let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite - && settings.passthrough_transparency - && (image.is_some() || { - let texture: &Image = image.and_then(|i| images.get(i))?; - // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise - let texture_rect = atlas - .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() + cursor_pos_sprite.truncate().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 - } - }); + && (!settings.passthrough_transparency + || image.is_some() && { + let texture: &Image = image.and_then(|i| images.get(i))?; + // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise + let texture_rect = atlas + .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() + cursor_pos_sprite.truncate().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 && pickable.map(|p| p.should_block_lower) != Some(false); From 622aa66c3121e9f1ef941dc86cf73e5e4cd4091b Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Wed, 13 Nov 2024 19:54:47 +0000 Subject: [PATCH 5/8] remove bang op for clarity --- backends/bevy_picking_sprite/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index 8c120109..ef3c019d 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -151,8 +151,8 @@ pub fn sprite_picking( let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate()); let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite - && (!settings.passthrough_transparency - || image.is_some() && { + && (settings.passthrough_transparency == false + || (image.is_some() && { let texture: &Image = image.and_then(|i| images.get(i))?; // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise let texture_rect = atlas @@ -183,7 +183,7 @@ pub fn sprite_picking( } else { false } - }); + })); blocked = cursor_in_valid_pixels_of_sprite && pickable.map(|p| p.should_block_lower) != Some(false); From 8e1a19e502f8c2ffc66f628e5f88ef0a503f303b Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Wed, 13 Nov 2024 20:33:25 +0000 Subject: [PATCH 6/8] re-add bang to keep clippy happy --- backends/bevy_picking_sprite/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index ef3c019d..15911009 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -151,7 +151,7 @@ pub fn sprite_picking( let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate()); let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite - && (settings.passthrough_transparency == false + && (!settings.passthrough_transparency || (image.is_some() && { let texture: &Image = image.and_then(|i| images.get(i))?; // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise From c687e81a447debc1c914f4baacc47e23f537d29f Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Wed, 13 Nov 2024 20:40:23 +0000 Subject: [PATCH 7/8] fix doc comment about defaults --- backends/bevy_picking_sprite/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index 15911009..f723dc58 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -31,7 +31,7 @@ pub struct SpriteBackendSettings { /// 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 245) + /// How Opaque does part of a sprite need to be in order count as none-transparent (defaults to 10) pub transparency_cutoff: u8, } From 3831d5d2de5724691b8077c8c1cafd66b11139b4 Mon Sep 17 00:00:00 2001 From: Michael Walter Van Der Velden Date: Thu, 14 Nov 2024 10:25:41 +0000 Subject: [PATCH 8/8] backport naming convention and improvements from bevy core version of pr --- backends/bevy_picking_sprite/src/lib.rs | 37 +++++++++++++------------ 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/backends/bevy_picking_sprite/src/lib.rs b/backends/bevy_picking_sprite/src/lib.rs index f723dc58..9be49a09 100644 --- a/backends/bevy_picking_sprite/src/lib.rs +++ b/backends/bevy_picking_sprite/src/lib.rs @@ -27,19 +27,21 @@ pub mod prelude { #[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 transparncy on sprites is ignored. - pub passthrough_transparency: bool, + /// control over if transparency on sprites is ignored. + pub alpha_passthrough: bool, /// How Opaque does part of a sprite need to be in order count as none-transparent (defaults to 10) - pub transparency_cutoff: u8, + /// + /// This is on a scale from 0 - 255 representing the alpha channel value you'd get in most art programs. + pub alpha_cutoff: u8, } impl Default for SpriteBackendSettings { fn default() -> Self { Self { - passthrough_transparency: false, - transparency_cutoff: 10, + alpha_passthrough: false, + alpha_cutoff: 10, } } } @@ -151,7 +153,7 @@ pub fn sprite_picking( let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate()); let cursor_in_valid_pixels_of_sprite = is_cursor_in_sprite - && (!settings.passthrough_transparency + && (!settings.alpha_passthrough || (image.is_some() && { let texture: &Image = image.and_then(|i| images.get(i))?; // If using a texture atlas, grab the offset of the current sprite index. (0,0) otherwise @@ -168,20 +170,21 @@ pub fn sprite_picking( texture.height(), )))?; // get mouse position on texture - let texture_position = - texture_rect.center() + cursor_pos_sprite.truncate().as_uvec2(); + let texture_position = (texture_rect.center().as_vec2() + + cursor_pos_sprite.truncate()) + .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 + // check transparency + match texture.data.get(pixel_index * 4..(pixel_index * 4 + 4)) { + // 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, } }));