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

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
pub use bundle::*;
pub use dynamic_texture_atlas_builder::*;
pub use mesh2d::*;
#[cfg(feature = "bevy_sprite_picking_backend")]
pub use picking_backend::*;
pub use render::*;
pub use sprite::*;
pub use texture_atlas::*;
Expand Down Expand Up @@ -148,7 +150,7 @@ impl Plugin for SpritePlugin {

#[cfg(feature = "bevy_sprite_picking_backend")]
if self.add_picking {
app.add_plugins(picking_backend::SpritePickingPlugin);
app.add_plugins(SpritePickingPlugin);
}

if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
Expand Down
92 changes: 71 additions & 21 deletions crates/bevy_sprite/src/picking_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,63 @@ use core::cmp::Reverse;
use crate::{Sprite, TextureAtlasLayout};
use bevy_app::prelude::*;
use bevy_asset::prelude::*;
use bevy_color::Alpha;
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 {
vandie marked this conversation as resolved.
Show resolved Hide resolved
/// 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 an f32 representing the alpha value in a Bevy Color Value
AlphaThreshold(f32),
}

/// Runtime settings for the [`SpritePickingPlugin`].
#[derive(Resource, Reflect)]
#[reflect(Resource, Default)]
pub struct SpritePickingSettings {
/// 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 0.1
pub picking_mode: SpritePickingMode,
}

impl Default for SpritePickingSettings {
fn default() -> Self {
Self {
picking_mode: SpritePickingMode::AlphaThreshold(0.1),
}
}
}

#[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::<SpritePickingSettings>()
.add_systems(PreUpdate, sprite_picking.in_set(PickSet::Backend));
}
}

pub fn sprite_picking(
#[allow(clippy::too_many_arguments)]
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<SpritePickingSettings>,
sprite_query: Query<(
Entity,
&Sprite,
Expand Down Expand Up @@ -91,22 +125,6 @@ pub fn sprite_picking(
return None;
}

// Hit box in sprite coordinate system
let extents = match (sprite.custom_size, &sprite.texture_atlas) {
(Some(custom_size), _) => custom_size,
(None, None) => images.get(&sprite.image)?.size().as_vec2(),
(None, Some(atlas)) => texture_atlas_layout
.get(&atlas.layout)
.and_then(|layout| layout.textures.get(atlas.index))
// Dropped atlas layouts and indexes out of bounds are rendered as a sprite
.map_or(images.get(&sprite.image)?.size().as_vec2(), |rect| {
rect.size().as_vec2()
}),
};
let anchor = sprite.anchor.as_vec();
let center = -anchor * extents;
let rect = Rect::from_center_half_size(center, extents / 2.0);

// Transform cursor line segment to sprite coordinate system
let world_to_sprite = sprite_transform.affine().inverse();
let cursor_start_sprite = world_to_sprite.transform_point3(cursor_ray_world.origin);
Expand All @@ -133,14 +151,46 @@ pub fn sprite_picking(
.lerp(cursor_end_sprite, lerp_factor)
.xy();

let is_cursor_in_sprite = rect.contains(cursor_pos_sprite);
let Ok(cursor_pixel_space) = sprite.compute_pixel_space_point(
cursor_pos_sprite,
&images,
&texture_atlas_layout,
) else {
return None;
};

// Since the pixel space coordinate is `Ok`, we know the cursor is in the bounds of
// the sprite.

let cursor_in_valid_pixels_of_sprite = 'valid_pixel: {
match settings.picking_mode {
SpritePickingMode::AlphaThreshold(cutoff) => {
let Some(image) = images.get(&sprite.image) else {
vandie marked this conversation as resolved.
Show resolved Hide resolved
// [`Sprite::from_color`] returns a defaulted handle.
// This handle doesn't return a valid image, so returning false here would make picking "color sprites" impossible
break 'valid_pixel true;
};
// grab pixel and check alpha
let Ok(color) = image.get_color_at(
cursor_pixel_space.x as u32,
cursor_pixel_space.y as u32,
) else {
// We don't know how to interpret the pixel.
break 'valid_pixel false;
};
// Check the alpha is above the cutoff.
color.alpha() > cutoff
}
SpritePickingMode::BoundingBox => true,
}
};

blocked = is_cursor_in_sprite
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
Loading