-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Changes from 17 commits
c5dd17a
bba576b
f9088c7
d1013cf
1924e02
42c156b
3ca1039
7038101
1f48ff2
5ee1448
16f8654
38eb823
26f4511
0737631
0acb495
e35c25d
71f9383
3afc5ca
f0a9095
c9fff89
c246fba
513a934
c64dbdf
50e862d
323f08d
54bdc5f
6b5a176
23cc626
f46f67c
e3d6736
f3476e0
8eb09ff
db392ab
eca8e10
22e7547
64f1cf7
7ed99c5
8bff88a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||
vandie marked this conversation as resolved.
Show resolved
Hide resolved
vandie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.as_uvec2(); | ||
vandie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// grab pixel | ||
let pixel_index = | ||
vandie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(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, | ||
vandie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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 | ||
|
Uh oh!
There was an error while loading. Please reload this page.