Skip to content
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

Add optional transparency passthrough for sprite backend with bevy_picking #16388

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
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
62 changes: 59 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,52 @@ 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 [`SpritePickingPlugin`].
#[derive(Resource, Reflect)]
#[reflect(Resource, Default)]
pub struct SpriteBackendSettings {
/// 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
Copy link
Contributor

Choose a reason for hiding this comment

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

It is mainly off by default for performance?

Copy link
Author

Choose a reason for hiding this comment

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

To be honest with you, I didn't want it to change the default behaviour in a way that would break existing use cases. I tend to favour opt in than opt out.

Copy link
Contributor

Choose a reason for hiding this comment

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

I just saw/remembered per the linked issue @alice-i-cecile seems to suggest it is on by default with a way to turn off. As a user, I want this OOTB myself so on by default makes sense to me, for what it’s worth. To avoid churn feel free to wait for other reviews before changing your code.

/// 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)
Copy link
Contributor

@mgi388 mgi388 Nov 14, 2024

Choose a reason for hiding this comment

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

Can you elaborate in the docs what the units are? 10 what? What is a valid range I could use, as a user?

///
/// 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 {
alpha_passthrough: false,
alpha_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::<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 @@ -130,12 +157,41 @@ 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.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
.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 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,
}
});

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