Skip to content

Add optional transparency passthrough for sprite backend with bevy_picking #16388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from 17 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c5dd17a
Add optional transparancy passthrough for sprite backend
vandie Nov 14, 2024
bba576b
set correct default
vandie Nov 14, 2024
f9088c7
fix typo
vandie Nov 14, 2024
d1013cf
fix too many arguments
vandie Nov 14, 2024
1924e02
fix doc typo
vandie Nov 14, 2024
42c156b
clean up final check into match
vandie Nov 14, 2024
3ca1039
fix docs and naming
vandie Nov 14, 2024
7038101
change transparency to alpha in variable names
vandie Nov 14, 2024
1f48ff2
Add AlphaPassthrough enum and change default to on
vandie Nov 17, 2024
5ee1448
improve comments
vandie Nov 17, 2024
16f8654
Merge branch 'main' into ignore_transparancy_on_sprite_picking
vandie Nov 17, 2024
38eb823
update comments
vandie Nov 17, 2024
26f4511
update comments
vandie Nov 17, 2024
0737631
rename Sprite Picking Mode
vandie Nov 18, 2024
0acb495
fix typo
vandie Nov 18, 2024
e35c25d
Merge branch 'main' into ignore_transparancy_on_sprite_picking
vandie Nov 18, 2024
71f9383
Merge branch 'main' into ignore_transparancy_on_sprite_picking
vandie Nov 19, 2024
3afc5ca
switch to using bevy internal `get_color_at`
vandie Nov 21, 2024
f0a9095
add missing backticks
vandie Nov 21, 2024
c9fff89
fix double backticks
vandie Nov 21, 2024
c246fba
improve accuracy and stop out of range error
vandie Nov 22, 2024
513a934
Merge branch 'main' into ignore_transparancy_on_sprite_picking
vandie Nov 22, 2024
c64dbdf
change floor location
vandie Nov 22, 2024
50e862d
Merge branch 'main' into ignore_transparancy_on_sprite_picking
vandie Nov 22, 2024
323f08d
Merge branch 'main' into HEAD
andriyDev Nov 23, 2024
54bdc5f
Create `compute_pixel_space_point` to convert from a point relative t…
andriyDev Nov 23, 2024
6b5a176
Rewrite the sprite picking backend to use `Sprite::compute_pixel_spac…
andriyDev Nov 23, 2024
23cc626
Add a comment to clarify that we know the cursor is in the bounds of …
andriyDev Nov 24, 2024
f46f67c
Make the sprite_picking system private, but then re-export everything…
andriyDev Nov 24, 2024
e3d6736
Remove flipping the y-value of the cursor from the picking backend.
andriyDev Nov 24, 2024
f3476e0
Rename `SpriteBackendSettings` to `SpritePickingSettings`.
andriyDev Nov 24, 2024
8eb09ff
Move all the pixel computations to `Sprite::compute_pixel_space_point`.
vandie Nov 26, 2024
db392ab
Merge branch 'main' into ignore_transparancy_on_sprite_picking
vandie Nov 26, 2024
eca8e10
Merge branch 'main' into ignore_transparancy_on_sprite_picking
vandie Nov 28, 2024
22e7547
remove un-needed closure
vandie Nov 28, 2024
64f1cf7
fix comments
vandie Nov 28, 2024
7ed99c5
Merge branch 'main' into ignore_transparancy_on_sprite_picking
vandie Dec 2, 2024
8bff88a
added @andriyDev's comment to code
vandie Dec 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions crates/bevy_sprite/src/picking_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,58 @@ 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;

/// How should the [`SpritePickingPlugin`] handle picking and how should it handle transparent pixels
#[derive(Debug, Clone, Copy, Reflect)]
pub enum SpritePickingMode {
/// Even if a sprite is picked on a transparent pixel, it should still count within the backend.
/// Only consider the rect of a given sprite.
BoundingBox,
/// Ignore any part of a sprite which has a lower alpha value than the threshold (inclusive)
/// Threshold is given as a single u8 value (0-255) representing the alpha channel that you would see in most art programs
AlphaThreshold(u8),
}

/// Runtime settings for the [`SpritePickingPlugin`].
#[derive(Resource, Reflect)]
#[reflect(Resource, Default)]
pub struct SpriteBackendSettings {
/// Should the backend count transparent pixels as part of the sprite for picking purposes or should it use the bounding box of the sprite alone.
///
/// Defaults to an incusive alpha threshold of 10/255
pub picking_mode: SpritePickingMode,
}

impl Default for SpriteBackendSettings {
fn default() -> Self {
Self {
picking_mode: SpritePickingMode::AlphaThreshold(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::<SpriteBackendSettings>()
.add_systems(PreUpdate, sprite_picking.in_set(PickSet::Backend));
}
}

#[allow(clippy::too_many_arguments)]
pub fn sprite_picking(
pointers: Query<(&PointerId, &PointerLocation)>,
cameras: Query<(Entity, &Camera, &GlobalTransform, &OrthographicProjection)>,
primary_window: Query<Entity, With<PrimaryWindow>>,
images: Res<Assets<Image>>,
texture_atlas_layout: Res<Assets<TextureAtlasLayout>>,
settings: Res<SpriteBackendSettings>,
sprite_query: Query<(
Entity,
&Sprite,
Expand Down Expand Up @@ -135,12 +168,44 @@ 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 = match settings.picking_mode {
SpritePickingMode::AlphaThreshold(cutoff) if is_cursor_in_sprite => {
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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're getting the center and then casting to a float. Odd image sizes will mean the center won't be at 0.5, 0.5 like we'd expect. I'm pretty sure we need to cast the rect to f32 and then get the center, so texture_rect.as_rect().center().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, let me check my commits because I think I had some correction for this. Wonder if I accidentally removed it with all the reworking we've done.

+ cursor_pos_sprite.trunc())
.as_uvec2();
// grab pixel
let pixel_index =
(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 alpha bit is above cutoff
Some(pixel_data) if pixel_data[3] > cutoff => true,
// If not possible, it's not in the sprite
_ => false,
}
}
SpritePickingMode::BoundingBox => is_cursor_in_sprite,
_ => 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
Expand Down