1313use crate :: { Anchor , Sprite } ;
1414use bevy_app:: prelude:: * ;
1515use bevy_asset:: prelude:: * ;
16- use bevy_camera:: { visibility:: ViewVisibility , Camera , Projection } ;
16+ use bevy_camera:: {
17+ visibility:: { RenderLayers , ViewVisibility } ,
18+ Camera , Projection ,
19+ } ;
1720use bevy_color:: Alpha ;
1821use bevy_ecs:: prelude:: * ;
1922use bevy_image:: prelude:: * ;
@@ -88,6 +91,7 @@ fn sprite_picking(
8891 & GlobalTransform ,
8992 & Projection ,
9093 Has < SpritePickingCamera > ,
94+ Option < & RenderLayers > ,
9195 ) > ,
9296 primary_window : Query < Entity , With < PrimaryWindow > > ,
9397 images : Res < Assets < Image > > ,
@@ -100,157 +104,188 @@ fn sprite_picking(
100104 & Anchor ,
101105 & Pickable ,
102106 & ViewVisibility ,
107+ Option < & RenderLayers > ,
103108 ) > ,
104109 mut pointer_hits_writer : MessageWriter < PointerHits > ,
110+ ray_map : Res < RayMap > ,
105111) {
106112 let mut sorted_sprites: Vec < _ > = sprite_query
107113 . iter ( )
108- . filter_map ( |( entity, sprite, transform, anchor, pickable, vis) | {
109- if !transform. affine ( ) . is_nan ( ) && vis. get ( ) {
110- Some ( ( entity, sprite, transform, anchor, pickable) )
111- } else {
112- None
113- }
114- } )
114+ . filter_map (
115+ |( entity, sprite, transform, anchor, pickable, vis, render_layers) | {
116+ if !transform. affine ( ) . is_nan ( ) && vis. get ( ) {
117+ Some ( ( entity, sprite, transform, anchor, pickable, render_layers) )
118+ } else {
119+ None
120+ }
121+ } ,
122+ )
115123 . collect ( ) ;
116124
117125 // radsort is a stable radix sort that performed better than `slice::sort_by_key`
118- radsort:: sort_by_key ( & mut sorted_sprites, |( _, _, transform, _, _) | {
126+ radsort:: sort_by_key ( & mut sorted_sprites, |( _, _, transform, _, _, _ ) | {
119127 -transform. translation ( ) . z
120128 } ) ;
121129
122130 let primary_window = primary_window. single ( ) . ok ( ) ;
123131
124- for ( pointer, location) in pointers. iter ( ) . filter_map ( |( pointer, pointer_location) | {
125- pointer_location. location ( ) . map ( |loc| ( pointer, loc) )
126- } ) {
132+ let pick_sets = ray_map. iter ( ) . flat_map ( |( ray_id, ray) | {
127133 let mut blocked = false ;
128- let Some ( ( cam_entity, camera, cam_transform, Projection :: Orthographic ( cam_ortho) , _) ) =
129- cameras
130- . iter ( )
131- . filter ( |( _, camera, _, _, cam_can_pick) | {
132- let marker_requirement = !settings. require_markers || * cam_can_pick;
133- camera. is_active && marker_requirement
134- } )
135- . find ( |( _, camera, _, _, _) | {
136- camera
137- . target
138- . normalize ( primary_window)
139- . is_some_and ( |x| x == location. target )
140- } )
134+
135+ let Ok ( (
136+ cam_entity,
137+ camera,
138+ cam_transform,
139+ Projection :: Orthographic ( cam_ortho) ,
140+ cam_can_pick,
141+ cam_render_layers,
142+ ) ) = cameras. get ( ray_id. camera )
141143 else {
142- continue ;
144+ return None ;
143145 } ;
144146
147+ let marker_requirement = !settings. require_markers || cam_can_pick;
148+ if !camera. is_active || !marker_requirement {
149+ return None ;
150+ }
151+
152+ let location = pointers. iter ( ) . find_map ( |( id, loc) | {
153+ if * id == ray_id. pointer {
154+ return loc. location . as_ref ( ) ;
155+ }
156+ None
157+ } ) ?;
158+
159+ if camera
160+ . target
161+ . normalize ( primary_window)
162+ . is_none_or ( |x| x != location. target )
163+ {
164+ return None ;
165+ }
166+
145167 let viewport_pos = location. position ;
146168 if let Some ( viewport) = camera. logical_viewport_rect ( )
147169 && !viewport. contains ( viewport_pos)
148170 {
149171 // The pointer is outside the viewport, skip it
150- continue ;
172+ return None ;
151173 }
152174
153- let Ok ( cursor_ray_world) = camera. viewport_to_world ( cam_transform, viewport_pos) else {
154- continue ;
155- } ;
156175 let cursor_ray_len = cam_ortho. far - cam_ortho. near ;
157- let cursor_ray_end = cursor_ray_world . origin + cursor_ray_world . direction * cursor_ray_len;
176+ let cursor_ray_end = ray . origin + ray . direction * cursor_ray_len;
158177
159178 let picks: Vec < ( Entity , HitData ) > = sorted_sprites
160179 . iter ( )
161180 . copied ( )
162- . filter_map ( |( entity, sprite, sprite_transform, anchor, pickable) | {
163- if blocked {
164- return None ;
165- }
181+ . filter_map (
182+ |( entity, sprite, sprite_transform, anchor, pickable, sprite_render_layers) | {
183+ if blocked {
184+ return None ;
185+ }
166186
167- // Transform cursor line segment to sprite coordinate system
168- let world_to_sprite = sprite_transform. affine ( ) . inverse ( ) ;
169- let cursor_start_sprite = world_to_sprite. transform_point3 ( cursor_ray_world. origin ) ;
170- let cursor_end_sprite = world_to_sprite. transform_point3 ( cursor_ray_end) ;
187+ // Filter out sprites based on whether they share RenderLayers with the current
188+ // ray's associated camera.
189+ // Any entity without a RenderLayers component will by default be
190+ // on RenderLayers::layer(0) only.
191+ if !cam_render_layers
192+ . unwrap_or_default ( )
193+ . intersects ( sprite_render_layers. unwrap_or_default ( ) )
194+ {
195+ return None ;
196+ }
171197
172- // Find where the cursor segment intersects the plane Z=0 (which is the sprite's
173- // plane in sprite-local space). It may not intersect if, for example, we're
174- // viewing the sprite side-on
175- if cursor_start_sprite. z == cursor_end_sprite. z {
176- // Cursor ray is parallel to the sprite and misses it
177- return None ;
178- }
179- let lerp_factor =
180- f32:: inverse_lerp ( cursor_start_sprite. z , cursor_end_sprite. z , 0.0 ) ;
181- if !( 0.0 ..=1.0 ) . contains ( & lerp_factor) {
182- // Lerp factor is out of range, meaning that while an infinite line cast by
183- // the cursor would intersect the sprite, the sprite is not between the
184- // camera's near and far planes
185- return None ;
186- }
187- // Otherwise we can interpolate the xy of the start and end positions by the
188- // lerp factor to get the cursor position in sprite space!
189- let cursor_pos_sprite = cursor_start_sprite
190- . lerp ( cursor_end_sprite, lerp_factor)
191- . xy ( ) ;
198+ // Transform cursor line segment to sprite coordinate system
199+ let world_to_sprite = sprite_transform. affine ( ) . inverse ( ) ;
200+ let cursor_start_sprite = world_to_sprite. transform_point3 ( ray. origin ) ;
201+ let cursor_end_sprite = world_to_sprite. transform_point3 ( cursor_ray_end) ;
202+
203+ // Find where the cursor segment intersects the plane Z=0 (which is the sprite's
204+ // plane in sprite-local space). It may not intersect if, for example, we're
205+ // viewing the sprite side-on
206+ if cursor_start_sprite. z == cursor_end_sprite. z {
207+ // Cursor ray is parallel to the sprite and misses it
208+ return None ;
209+ }
210+ let lerp_factor =
211+ f32:: inverse_lerp ( cursor_start_sprite. z , cursor_end_sprite. z , 0.0 ) ;
212+ if !( 0.0 ..=1.0 ) . contains ( & lerp_factor) {
213+ // Lerp factor is out of range, meaning that while an infinite line cast by
214+ // the cursor would intersect the sprite, the sprite is not between the
215+ // camera's near and far planes
216+ return None ;
217+ }
218+ // Otherwise we can interpolate the xy of the start and end positions by the
219+ // lerp factor to get the cursor position in sprite space!
220+ let cursor_pos_sprite = cursor_start_sprite
221+ . lerp ( cursor_end_sprite, lerp_factor)
222+ . xy ( ) ;
192223
193- let Ok ( cursor_pixel_space) = sprite. compute_pixel_space_point (
194- cursor_pos_sprite,
195- * anchor,
196- & images,
197- & texture_atlas_layout,
198- ) else {
199- return None ;
200- } ;
224+ let Ok ( cursor_pixel_space) = sprite. compute_pixel_space_point (
225+ cursor_pos_sprite,
226+ * anchor,
227+ & images,
228+ & texture_atlas_layout,
229+ ) else {
230+ return None ;
231+ } ;
201232
202- // Since the pixel space coordinate is `Ok`, we know the cursor is in the bounds of
203- // the sprite.
233+ // Since the pixel space coordinate is `Ok`, we know the cursor is in the bounds of
234+ // the sprite.
204235
205- let cursor_in_valid_pixels_of_sprite = ' valid_pixel: {
206- match settings. picking_mode {
207- SpritePickingMode :: AlphaThreshold ( cutoff) => {
208- let Some ( image) = images. get ( & sprite. image ) else {
209- // [`Sprite::from_color`] returns a defaulted handle.
210- // This handle doesn't return a valid image, so returning false here would make picking "color sprites" impossible
211- break ' valid_pixel true ;
212- } ;
213- // grab pixel and check alpha
214- let Ok ( color) = image. get_color_at (
215- cursor_pixel_space. x as u32 ,
216- cursor_pixel_space. y as u32 ,
217- ) else {
218- // We don't know how to interpret the pixel.
219- break ' valid_pixel false ;
220- } ;
221- // Check the alpha is above the cutoff.
222- color. alpha ( ) > cutoff
236+ let cursor_in_valid_pixels_of_sprite = ' valid_pixel: {
237+ match settings. picking_mode {
238+ SpritePickingMode :: AlphaThreshold ( cutoff) => {
239+ let Some ( image) = images. get ( & sprite. image ) else {
240+ // [`Sprite::from_color`] returns a defaulted handle.
241+ // This handle doesn't return a valid image, so returning false here would make picking "color sprites" impossible
242+ break ' valid_pixel true ;
243+ } ;
244+ // grab pixel and check alpha
245+ let Ok ( color) = image. get_color_at (
246+ cursor_pixel_space. x as u32 ,
247+ cursor_pixel_space. y as u32 ,
248+ ) else {
249+ // We don't know how to interpret the pixel.
250+ break ' valid_pixel false ;
251+ } ;
252+ // Check the alpha is above the cutoff.
253+ color. alpha ( ) > cutoff
254+ }
255+ SpritePickingMode :: BoundingBox => true ,
223256 }
224- SpritePickingMode :: BoundingBox => true ,
225- }
226- } ;
257+ } ;
227258
228- blocked = cursor_in_valid_pixels_of_sprite && pickable. should_block_lower ;
259+ blocked = cursor_in_valid_pixels_of_sprite && pickable. should_block_lower ;
229260
230- cursor_in_valid_pixels_of_sprite. then ( || {
231- let hit_pos_world =
232- sprite_transform. transform_point ( cursor_pos_sprite. extend ( 0.0 ) ) ;
233- // Transform point from world to camera space to get the Z distance
234- let hit_pos_cam = cam_transform
235- . affine ( )
236- . inverse ( )
237- . transform_point3 ( hit_pos_world) ;
238- // HitData requires a depth as calculated from the camera's near clipping plane
239- let depth = -cam_ortho. near - hit_pos_cam. z ;
240- (
241- entity,
242- HitData :: new (
243- cam_entity,
244- depth,
245- Some ( hit_pos_world) ,
246- Some ( * sprite_transform. back ( ) ) ,
247- ) ,
248- )
249- } )
250- } )
261+ cursor_in_valid_pixels_of_sprite. then ( || {
262+ let hit_pos_world =
263+ sprite_transform. transform_point ( cursor_pos_sprite. extend ( 0.0 ) ) ;
264+ // Transform point from world to camera space to get the Z distance
265+ let hit_pos_cam = cam_transform
266+ . affine ( )
267+ . inverse ( )
268+ . transform_point3 ( hit_pos_world) ;
269+ // HitData requires a depth as calculated from the camera's near clipping plane
270+ let depth = -cam_ortho. near - hit_pos_cam. z ;
271+ (
272+ entity,
273+ HitData :: new (
274+ cam_entity,
275+ depth,
276+ Some ( hit_pos_world) ,
277+ Some ( * sprite_transform. back ( ) ) ,
278+ ) ,
279+ )
280+ } )
281+ } ,
282+ )
251283 . collect ( ) ;
252284
253- let order = camera. order as f32 ;
254- pointer_hits_writer. write ( PointerHits :: new ( * pointer, picks, order) ) ;
255- }
285+ Some ( ( ray_id. pointer , picks, camera. order ) )
286+ } ) ;
287+
288+ pick_sets. for_each ( |( pointer, picks, order) | {
289+ pointer_hits_writer. write ( PointerHits :: new ( pointer, picks, order as f32 ) ) ;
290+ } ) ;
256291}
0 commit comments