Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"b945477d43ef90fc8dd06b2d7a7b5bd498f3627faaf26a91fc65aaacfadca228","Cargo.toml":"43031a3dba56b8d0dae6fc6b73eef4980e04381f460a82d0c6c01172910f5c25","LICENSE-APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE-MIT":"508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1","README.md":"98bfbef419392ef7a22b185c617b383c75af7f0854e2ee4d7d75b7bce7778269","src/backend.rs":"fe07613e992fcff7e21d39acdc96c03c6b8f86fd257b25da608cb6e4a16c3edd","src/events.rs":"e26ed251d4f15f59b4d0b14532adc8a7a112abdd6ff8a396e35d01db51f80e99","src/hover.rs":"0fea0d1d7b6b2b28995e7690ff8979d8f91fc5747f04396abb4c961e069c4844","src/input.rs":"3d8ed1d32db718f24d6f17ca287764de95a560331f4eb128a988caace178979c","src/lib.rs":"37d50167b8babe90c674519ad6b59110f39ca1175dcf5956e68e88809da6c207","src/mesh_picking/mod.rs":"0a6a017f278b0eb8ea18bd99f16c6a1b5f2a9cbc120a483f0f55be8e46202a95","src/mesh_picking/ray_cast/intersections.rs":"b5697f3bd86989d5b9a2aa98d1e42d8a49b21cb82182f2e8b44553a30b3d6e3a","src/mesh_picking/ray_cast/mod.rs":"39c44d2cce1568e67a9a1a2323b858ed28718c7ab8367a2f0d6ca6f50fba421e","src/pointer.rs":"189e7f6c697fb4c27d1c4f8fbba45f7bdd3dec68fa46577beb671817eaecbbde","src/window.rs":"72e0096716c29c5b31cbe5d68c47402530e8875dcab89bb9205d9056c302c574"},"package":"8ed04757938655ed8094ea1efb533f99063a8b22abffc22010c694d291522850"}

3128
vendor/bevy_picking/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

140
vendor/bevy_picking/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,140 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "bevy_picking"
version = "0.16.1"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Provides screen picking functionality for Bevy Engine"
homepage = "https://bevyengine.org"
readme = "README.md"
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["-Zunstable-options"]
[features]
bevy_mesh_picking_backend = [
"dep:bevy_mesh",
"dep:crossbeam-channel",
]
[lib]
name = "bevy_picking"
path = "src/lib.rs"
[dependencies.bevy_app]
version = "0.16.1"
[dependencies.bevy_asset]
version = "0.16.1"
[dependencies.bevy_derive]
version = "0.16.1"
[dependencies.bevy_ecs]
version = "0.16.1"
[dependencies.bevy_input]
version = "0.16.1"
[dependencies.bevy_math]
version = "0.16.1"
[dependencies.bevy_mesh]
version = "0.16.1"
optional = true
[dependencies.bevy_platform]
version = "0.16.1"
features = ["std"]
default-features = false
[dependencies.bevy_reflect]
version = "0.16.1"
[dependencies.bevy_render]
version = "0.16.1"
[dependencies.bevy_time]
version = "0.16.1"
[dependencies.bevy_transform]
version = "0.16.1"
[dependencies.bevy_utils]
version = "0.16.1"
[dependencies.bevy_window]
version = "0.16.1"
[dependencies.crossbeam-channel]
version = "0.5"
optional = true
[dependencies.tracing]
version = "0.1"
features = ["std"]
default-features = false
[dependencies.uuid]
version = "1.13.1"
features = ["v4"]
[target.'cfg(target_arch = "wasm32")'.dependencies.uuid]
version = "1.13.1"
features = ["js"]
default-features = false
[lints.clippy]
alloc_instead_of_core = "warn"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
needless_lifetimes = "allow"
nonstandard_macro_braces = "warn"
print_stderr = "warn"
print_stdout = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
ref_as_ptr = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
too_long_first_doc_paragraph = "allow"
too_many_arguments = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
[lints.rust]
missing_docs = "warn"
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(docsrs_dep)"]

176
vendor/bevy_picking/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

19
vendor/bevy_picking/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1
vendor/bevy_picking/README.md vendored Normal file
View File

@@ -0,0 +1 @@
# Bevy Picking

236
vendor/bevy_picking/src/backend.rs vendored Normal file
View File

@@ -0,0 +1,236 @@
//! This module provides a simple interface for implementing a picking backend.
//!
//! Don't be dissuaded by terminology like "backend"; the idea is dead simple. `bevy_picking`
//! will tell you where pointers are, all you have to do is send an event if the pointers are
//! hitting something. That's it. The rest of this documentation explains the requirements in more
//! detail.
//!
//! Because `bevy_picking` is very loosely coupled with its backends, you can mix and match as
//! many backends as you want. For example, you could use the `rapier` backend to raycast against
//! physics objects, a picking shader backend to pick non-physics meshes, and the `bevy_ui` backend
//! for your UI. The [`PointerHits`] instances produced by these various backends will be combined,
//! sorted, and used as a homogeneous input for the picking systems that consume these events.
//!
//! ## Implementation
//!
//! - A picking backend only has one job: read [`PointerLocation`](crate::pointer::PointerLocation)
//! components and produce [`PointerHits`] events. In plain English, a backend is provided the
//! location of pointers, and is asked to provide a list of entities under those pointers.
//!
//! - The [`PointerHits`] events produced by a backend do **not** need to be sorted or filtered, all
//! that is needed is an unordered list of entities and their [`HitData`].
//!
//! - Backends do not need to consider the [`Pickable`](crate::Pickable) component, though they may
//! use it for optimization purposes. For example, a backend that traverses a spatial hierarchy
//! may want to exit early if it intersects an entity that blocks lower entities from being
//! picked.
//!
//! ### Raycasting Backends
//!
//! Backends that require a ray to cast into the scene should use [`ray::RayMap`]. This
//! automatically constructs rays in world space for all cameras and pointers, handling details like
//! viewports and DPI for you.
use bevy_ecs::prelude::*;
use bevy_math::Vec3;
use bevy_reflect::Reflect;
/// The picking backend prelude.
///
/// This includes the most common types in this module, re-exported for your convenience.
pub mod prelude {
pub use super::{ray::RayMap, HitData, PointerHits};
pub use crate::{
pointer::{PointerId, PointerLocation},
PickSet, Pickable,
};
}
/// An event produced by a picking backend after it has run its hit tests, describing the entities
/// under a pointer.
///
/// Some backends may only support providing the topmost entity; this is a valid limitation. For
/// example, a picking shader might only have data on the topmost rendered output from its buffer.
///
/// Note that systems reading these events in [`PreUpdate`](bevy_app::PreUpdate) will not report ordering
/// ambiguities with picking backends. Take care to ensure such systems are explicitly ordered
/// against [`PickSet::Backend`](crate::PickSet::Backend), or better, avoid reading `PointerHits` in `PreUpdate`.
#[derive(Event, Debug, Clone, Reflect)]
#[reflect(Debug, Clone)]
pub struct PointerHits {
/// The pointer associated with this hit test.
pub pointer: prelude::PointerId,
/// An unordered collection of entities and their distance (depth) from the cursor.
pub picks: Vec<(Entity, HitData)>,
/// Set the order of this group of picks. Normally, this is the
/// [`bevy_render::camera::Camera::order`].
///
/// Used to allow multiple `PointerHits` submitted for the same pointer to be ordered.
/// `PointerHits` with a higher `order` will be checked before those with a lower `order`,
/// regardless of the depth of each entity pick.
///
/// In other words, when pick data is coalesced across all backends, the data is grouped by
/// pointer, then sorted by order, and checked sequentially, sorting each `PointerHits` by
/// entity depth. Events with a higher `order` are effectively on top of events with a lower
/// order.
///
/// ### Why is this an `f32`???
///
/// Bevy UI is special in that it can share a camera with other things being rendered. in order
/// to properly sort them, we need a way to make `bevy_ui`'s order a tiny bit higher, like adding
/// 0.5 to the order. We can't use integers, and we want users to be using camera.order by
/// default, so this is the best solution at the moment.
pub order: f32,
}
impl PointerHits {
#[expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
pub fn new(pointer: prelude::PointerId, picks: Vec<(Entity, HitData)>, order: f32) -> Self {
Self {
pointer,
picks,
order,
}
}
}
/// Holds data from a successful pointer hit test. See [`HitData::depth`] for important details.
#[derive(Clone, Debug, PartialEq, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct HitData {
/// The camera entity used to detect this hit. Useful when you need to find the ray that was
/// casted for this hit when using a raycasting backend.
pub camera: Entity,
/// `depth` only needs to be self-consistent with other [`PointerHits`]s using the same
/// [`RenderTarget`](bevy_render::camera::RenderTarget). However, it is recommended to use the
/// distance from the pointer to the hit, measured from the near plane of the camera, to the
/// point, in world space.
pub depth: f32,
/// The position reported by the backend, if the data is available. Position data may be in any
/// space (e.g. World space, Screen space, Local space), specified by the backend providing it.
pub position: Option<Vec3>,
/// The normal vector of the hit test, if the data is available from the backend.
pub normal: Option<Vec3>,
}
impl HitData {
#[expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
pub fn new(camera: Entity, depth: f32, position: Option<Vec3>, normal: Option<Vec3>) -> Self {
Self {
camera,
depth,
position,
normal,
}
}
}
pub mod ray {
//! Types and systems for constructing rays from cameras and pointers.
use crate::backend::prelude::{PointerId, PointerLocation};
use bevy_ecs::prelude::*;
use bevy_math::Ray3d;
use bevy_platform::collections::{hash_map::Iter, HashMap};
use bevy_reflect::Reflect;
use bevy_render::camera::Camera;
use bevy_transform::prelude::GlobalTransform;
use bevy_window::PrimaryWindow;
/// Identifies a ray constructed from some (pointer, camera) combination. A pointer can be over
/// multiple cameras, which is why a single pointer may have multiple rays.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Reflect)]
#[reflect(Clone, PartialEq, Hash)]
pub struct RayId {
/// The camera whose projection was used to calculate the ray.
pub camera: Entity,
/// The pointer whose pixel coordinates were used to calculate the ray.
pub pointer: PointerId,
}
impl RayId {
/// Construct a [`RayId`].
pub fn new(camera: Entity, pointer: PointerId) -> Self {
Self { camera, pointer }
}
}
/// A map from [`RayId`] to [`Ray3d`].
///
/// This map is cleared and re-populated every frame before any backends run. Ray-based picking
/// backends should use this when possible, as it automatically handles viewports, DPI, and
/// other details of building rays from pointer locations.
///
/// ## Usage
///
/// Iterate over each [`Ray3d`] and its [`RayId`] with [`RayMap::iter`].
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_picking::backend::ray::RayMap;
/// # use bevy_picking::backend::PointerHits;
/// // My raycasting backend
/// pub fn update_hits(ray_map: Res<RayMap>, mut output_events: EventWriter<PointerHits>,) {
/// for (&ray_id, &ray) in ray_map.iter() {
/// // Run a raycast with each ray, returning any `PointerHits` found.
/// }
/// }
/// ```
#[derive(Clone, Debug, Default, Resource)]
pub struct RayMap {
/// Cartesian product of all pointers and all cameras
/// Add your rays here to support picking through indirections,
/// e.g. rendered-to-texture cameras
pub map: HashMap<RayId, Ray3d>,
}
impl RayMap {
/// Iterates over all world space rays for every picking pointer.
pub fn iter(&self) -> Iter<'_, RayId, Ray3d> {
self.map.iter()
}
/// Clears the [`RayMap`] and re-populates it with one ray for each
/// combination of pointer entity and camera entity where the pointer
/// intersects the camera's viewport.
pub fn repopulate(
mut ray_map: ResMut<Self>,
primary_window_entity: Query<Entity, With<PrimaryWindow>>,
cameras: Query<(Entity, &Camera, &GlobalTransform)>,
pointers: Query<(&PointerId, &PointerLocation)>,
) {
ray_map.map.clear();
for (camera_entity, camera, camera_tfm) in &cameras {
if !camera.is_active {
continue;
}
for (&pointer_id, pointer_loc) in &pointers {
if let Some(ray) =
make_ray(&primary_window_entity, camera, camera_tfm, pointer_loc)
{
ray_map
.map
.insert(RayId::new(camera_entity, pointer_id), ray);
}
}
}
}
}
fn make_ray(
primary_window_entity: &Query<Entity, With<PrimaryWindow>>,
camera: &Camera,
camera_tfm: &GlobalTransform,
pointer_loc: &PointerLocation,
) -> Option<Ray3d> {
let pointer_loc = pointer_loc.location()?;
if !pointer_loc.is_in_viewport(camera, primary_window_entity) {
return None;
}
camera
.viewport_to_world(camera_tfm, pointer_loc.position)
.ok()
}
}

824
vendor/bevy_picking/src/events.rs vendored Normal file
View File

@@ -0,0 +1,824 @@
//! This module defines a stateful set of interaction events driven by the `PointerInput` stream
//! and the hover state of each Pointer.
//!
//! # Usage
//!
//! To receive events from this module, you must use an [`Observer`] or [`EventReader`] with [`Pointer<E>`] events.
//! The simplest example, registering a callback when an entity is hovered over by a pointer, looks like this:
//!
//! ```rust
//! # use bevy_ecs::prelude::*;
//! # use bevy_picking::prelude::*;
//! # let mut world = World::default();
//! world.spawn_empty()
//! .observe(|trigger: Trigger<Pointer<Over>>| {
//! println!("I am being hovered over");
//! });
//! ```
//!
//! Observers give us three important properties:
//! 1. They allow for attaching event handlers to specific entities,
//! 2. they allow events to bubble up the entity hierarchy,
//! 3. and they allow events of different types to be called in a specific order.
//!
//! The order in which interaction events are received is extremely important, and you can read more
//! about it on the docs for the dispatcher system: [`pointer_events`]. This system runs in
//! [`PreUpdate`](bevy_app::PreUpdate) in [`PickSet::Hover`](crate::PickSet::Hover). All pointer-event
//! observers resolve during the sync point between [`pointer_events`] and
//! [`update_interactions`](crate::hover::update_interactions).
//!
//! # Events Types
//!
//! The events this module defines fall into a few broad categories:
//! + Hovering and movement: [`Over`], [`Move`], and [`Out`].
//! + Clicking and pressing: [`Pressed`], [`Released`], and [`Click`].
//! + Dragging and dropping: [`DragStart`], [`Drag`], [`DragEnd`], [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`].
//!
//! When received by an observer, these events will always be wrapped by the [`Pointer`] type, which contains
//! general metadata about the pointer event.
use core::{fmt::Debug, time::Duration};
use bevy_ecs::{prelude::*, query::QueryData, system::SystemParam, traversal::Traversal};
use bevy_input::mouse::MouseScrollUnit;
use bevy_math::Vec2;
use bevy_platform::collections::HashMap;
use bevy_platform::time::Instant;
use bevy_reflect::prelude::*;
use bevy_render::camera::NormalizedRenderTarget;
use bevy_window::Window;
use tracing::debug;
use crate::{
backend::{prelude::PointerLocation, HitData},
hover::{HoverMap, PreviousHoverMap},
pointer::{Location, PointerAction, PointerButton, PointerId, PointerInput, PointerMap},
};
/// Stores the common data needed for all pointer events.
///
/// The documentation for the [`pointer_events`] explains the events this module exposes and
/// the order in which they fire.
#[derive(Clone, PartialEq, Debug, Reflect, Component)]
#[reflect(Component, Debug, Clone)]
pub struct Pointer<E: Debug + Clone + Reflect> {
/// The original target of this picking event, before bubbling
pub target: Entity,
/// The pointer that triggered this event
pub pointer_id: PointerId,
/// The location of the pointer during this event
pub pointer_location: Location,
/// Additional event-specific data. [`DragDrop`] for example, has an additional field to describe
/// the `Entity` that is being dropped on the target.
pub event: E,
}
/// A traversal query (i.e. it implements [`Traversal`]) intended for use with [`Pointer`] events.
///
/// This will always traverse to the parent, if the entity being visited has one. Otherwise, it
/// propagates to the pointer's window and stops there.
#[derive(QueryData)]
pub struct PointerTraversal {
child_of: Option<&'static ChildOf>,
window: Option<&'static Window>,
}
impl<E> Traversal<Pointer<E>> for PointerTraversal
where
E: Debug + Clone + Reflect,
{
fn traverse(item: Self::Item<'_>, pointer: &Pointer<E>) -> Option<Entity> {
let PointerTraversalItem { child_of, window } = item;
// Send event to parent, if it has one.
if let Some(child_of) = child_of {
return Some(child_of.parent());
};
// Otherwise, send it to the window entity (unless this is a window entity).
if window.is_none() {
if let NormalizedRenderTarget::Window(window_ref) = pointer.pointer_location.target {
return Some(window_ref.entity());
}
}
None
}
}
impl<E> Event for Pointer<E>
where
E: Debug + Clone + Reflect,
{
type Traversal = PointerTraversal;
const AUTO_PROPAGATE: bool = true;
}
impl<E: Debug + Clone + Reflect> core::fmt::Display for Pointer<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_fmt(format_args!(
"{:?}, {:.1?}, {:.1?}",
self.pointer_id, self.pointer_location.position, self.event
))
}
}
impl<E: Debug + Clone + Reflect> core::ops::Deref for Pointer<E> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.event
}
}
impl<E: Debug + Clone + Reflect> Pointer<E> {
/// Construct a new `Pointer<E>` event.
pub fn new(id: PointerId, location: Location, target: Entity, event: E) -> Self {
Self {
target,
pointer_id: id,
pointer_location: location,
event,
}
}
}
/// Fires when a pointer is canceled, and its current interaction state is dropped.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Cancel {
/// Information about the picking intersection.
pub hit: HitData,
}
/// Fires when a the pointer crosses into the bounds of the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Over {
/// Information about the picking intersection.
pub hit: HitData,
}
/// Fires when a the pointer crosses out of the bounds of the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Out {
/// Information about the latest prior picking intersection.
pub hit: HitData,
}
/// Fires when a pointer button is pressed over the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Pressed {
/// Pointer button pressed to trigger this event.
pub button: PointerButton,
/// Information about the picking intersection.
pub hit: HitData,
}
/// Fires when a pointer button is released over the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Released {
/// Pointer button lifted to trigger this event.
pub button: PointerButton,
/// Information about the picking intersection.
pub hit: HitData,
}
/// Fires when a pointer sends a pointer pressed event followed by a pointer released event, with the same
/// `target` entity for both events.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Click {
/// Pointer button pressed and lifted to trigger this event.
pub button: PointerButton,
/// Information about the picking intersection.
pub hit: HitData,
/// Duration between the pointer pressed and lifted for this click
pub duration: Duration,
}
/// Fires while a pointer is moving over the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Move {
/// Information about the picking intersection.
pub hit: HitData,
/// The change in position since the last move event.
pub delta: Vec2,
}
/// Fires when the `target` entity receives a pointer pressed event followed by a pointer move event.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragStart {
/// Pointer button pressed and moved to trigger this event.
pub button: PointerButton,
/// Information about the picking intersection.
pub hit: HitData,
}
/// Fires while the `target` entity is being dragged.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Drag {
/// Pointer button pressed and moved to trigger this event.
pub button: PointerButton,
/// The total distance vector of a drag, measured from drag start to the current position.
pub distance: Vec2,
/// The change in position since the last drag event.
pub delta: Vec2,
}
/// Fires when a pointer is dragging the `target` entity and a pointer released event is received.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragEnd {
/// Pointer button pressed, moved, and released to trigger this event.
pub button: PointerButton,
/// The vector of drag movement measured from start to final pointer position.
pub distance: Vec2,
}
/// Fires when a pointer dragging the `dragged` entity enters the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragEnter {
/// Pointer button pressed to enter drag.
pub button: PointerButton,
/// The entity that was being dragged when the pointer entered the `target` entity.
pub dragged: Entity,
/// Information about the picking intersection.
pub hit: HitData,
}
/// Fires while the `dragged` entity is being dragged over the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragOver {
/// Pointer button pressed while dragging over.
pub button: PointerButton,
/// The entity that was being dragged when the pointer was over the `target` entity.
pub dragged: Entity,
/// Information about the picking intersection.
pub hit: HitData,
}
/// Fires when a pointer dragging the `dragged` entity leaves the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragLeave {
/// Pointer button pressed while leaving drag.
pub button: PointerButton,
/// The entity that was being dragged when the pointer left the `target` entity.
pub dragged: Entity,
/// Information about the latest prior picking intersection.
pub hit: HitData,
}
/// Fires when a pointer drops the `dropped` entity onto the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragDrop {
/// Pointer button released to drop.
pub button: PointerButton,
/// The entity that was dropped onto the `target` entity.
pub dropped: Entity,
/// Information about the picking intersection.
pub hit: HitData,
}
/// Dragging state.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragEntry {
/// The position of the pointer at drag start.
pub start_pos: Vec2,
/// The latest position of the pointer during this drag, used to compute deltas.
pub latest_pos: Vec2,
}
/// Fires while a pointer is scrolling over the `target` entity.
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct Scroll {
/// The mouse scroll unit.
pub unit: MouseScrollUnit,
/// The horizontal scroll value.
pub x: f32,
/// The vertical scroll value.
pub y: f32,
/// Information about the picking intersection.
pub hit: HitData,
}
/// An entry in the cache that drives the `pointer_events` system, storing additional data
/// about pointer button presses.
#[derive(Debug, Clone, Default)]
pub struct PointerButtonState {
/// Stores the press location and start time for each button currently being pressed by the pointer.
pub pressing: HashMap<Entity, (Location, Instant, HitData)>,
/// Stores the starting and current locations for each entity currently being dragged by the pointer.
pub dragging: HashMap<Entity, DragEntry>,
/// Stores the hit data for each entity currently being dragged over by the pointer.
pub dragging_over: HashMap<Entity, HitData>,
}
/// State for all pointers.
#[derive(Debug, Clone, Default, Resource)]
pub struct PointerState {
/// Pressing and dragging state, organized by pointer and button.
pub pointer_buttons: HashMap<(PointerId, PointerButton), PointerButtonState>,
}
impl PointerState {
/// Retrieves the current state for a specific pointer and button, if it has been created.
pub fn get(&self, pointer_id: PointerId, button: PointerButton) -> Option<&PointerButtonState> {
self.pointer_buttons.get(&(pointer_id, button))
}
/// Provides write access to the state of a pointer and button, creating it if it does not yet exist.
pub fn get_mut(
&mut self,
pointer_id: PointerId,
button: PointerButton,
) -> &mut PointerButtonState {
self.pointer_buttons
.entry((pointer_id, button))
.or_default()
}
/// Clears all the data associated with all of the buttons on a pointer. Does not free the underlying memory.
pub fn clear(&mut self, pointer_id: PointerId) {
for button in PointerButton::iter() {
if let Some(state) = self.pointer_buttons.get_mut(&(pointer_id, button)) {
state.pressing.clear();
state.dragging.clear();
state.dragging_over.clear();
}
}
}
}
/// A helper system param for accessing the picking event writers.
#[derive(SystemParam)]
pub struct PickingEventWriters<'w> {
cancel_events: EventWriter<'w, Pointer<Cancel>>,
click_events: EventWriter<'w, Pointer<Click>>,
pressed_events: EventWriter<'w, Pointer<Pressed>>,
drag_drop_events: EventWriter<'w, Pointer<DragDrop>>,
drag_end_events: EventWriter<'w, Pointer<DragEnd>>,
drag_enter_events: EventWriter<'w, Pointer<DragEnter>>,
drag_events: EventWriter<'w, Pointer<Drag>>,
drag_leave_events: EventWriter<'w, Pointer<DragLeave>>,
drag_over_events: EventWriter<'w, Pointer<DragOver>>,
drag_start_events: EventWriter<'w, Pointer<DragStart>>,
scroll_events: EventWriter<'w, Pointer<Scroll>>,
move_events: EventWriter<'w, Pointer<Move>>,
out_events: EventWriter<'w, Pointer<Out>>,
over_events: EventWriter<'w, Pointer<Over>>,
released_events: EventWriter<'w, Pointer<Released>>,
}
/// Dispatches interaction events to the target entities.
///
/// Within a single frame, events are dispatched in the following order:
/// + [`Out`] → [`DragLeave`].
/// + [`DragEnter`] → [`Over`].
/// + Any number of any of the following:
/// + For each movement: [`DragStart`] → [`Drag`] → [`DragOver`] → [`Move`].
/// + For each button press: [`Pressed`] or [`Click`] → [`Released`] → [`DragDrop`] → [`DragEnd`] → [`DragLeave`].
/// + For each pointer cancellation: [`Cancel`].
///
/// Additionally, across multiple frames, the following are also strictly
/// ordered by the interaction state machine:
/// + When a pointer moves over the target:
/// [`Over`], [`Move`], [`Out`].
/// + When a pointer presses buttons on the target:
/// [`Pressed`], [`Click`], [`Released`].
/// + When a pointer drags the target:
/// [`DragStart`], [`Drag`], [`DragEnd`].
/// + When a pointer drags something over the target:
/// [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`].
/// + When a pointer is canceled:
/// No other events will follow the [`Cancel`] event for that pointer.
///
/// Two events -- [`Over`] and [`Out`] -- are driven only by the [`HoverMap`].
/// The rest rely on additional data from the [`PointerInput`] event stream. To
/// receive these events for a custom pointer, you must add [`PointerInput`]
/// events.
///
/// When the pointer goes from hovering entity A to entity B, entity A will
/// receive [`Out`] and then entity B will receive [`Over`]. No entity will ever
/// receive both an [`Over`] and and a [`Out`] event during the same frame.
///
/// When we account for event bubbling, this is no longer true. When the hovering focus shifts
/// between children, parent entities may receive redundant [`Out`] → [`Over`] pairs.
/// In the context of UI, this is especially problematic. Additional hierarchy-aware
/// events will be added in a future release.
///
/// Both [`Click`] and [`Released`] target the entity hovered in the *previous frame*,
/// rather than the current frame. This is because touch pointers hover nothing
/// on the frame they are released. The end effect is that these two events can
/// be received sequentially after an [`Out`] event (but always on the same frame
/// as the [`Out`] event).
///
/// Note: Though it is common for the [`PointerInput`] stream may contain
/// multiple pointer movements and presses each frame, the hover state is
/// determined only by the pointer's *final position*. Since the hover state
/// ultimately determines which entities receive events, this may mean that an
/// entity can receive events from before or after it was actually hovered.
pub fn pointer_events(
// Input
mut input_events: EventReader<PointerInput>,
// ECS State
pointers: Query<&PointerLocation>,
pointer_map: Res<PointerMap>,
hover_map: Res<HoverMap>,
previous_hover_map: Res<PreviousHoverMap>,
mut pointer_state: ResMut<PointerState>,
// Output
mut commands: Commands,
mut event_writers: PickingEventWriters,
) {
// Setup utilities
let now = Instant::now();
let pointer_location = |pointer_id: PointerId| {
pointer_map
.get_entity(pointer_id)
.and_then(|entity| pointers.get(entity).ok())
.and_then(|pointer| pointer.location.clone())
};
// If the entity was hovered by a specific pointer last frame...
for (pointer_id, hovered_entity, hit) in previous_hover_map
.iter()
.flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
{
// ...but is now not being hovered by that same pointer...
if !hover_map
.get(&pointer_id)
.iter()
.any(|e| e.contains_key(&hovered_entity))
{
let Some(location) = pointer_location(pointer_id) else {
debug!(
"Unable to get location for pointer {:?} during pointer out",
pointer_id
);
continue;
};
// Always send Out events
let out_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Out { hit: hit.clone() },
);
commands.trigger_targets(out_event.clone(), hovered_entity);
event_writers.out_events.write(out_event);
// Possibly send DragLeave events
for button in PointerButton::iter() {
let state = pointer_state.get_mut(pointer_id, button);
state.dragging_over.remove(&hovered_entity);
for drag_target in state.dragging.keys() {
let drag_leave_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
DragLeave {
button,
dragged: *drag_target,
hit: hit.clone(),
},
);
commands.trigger_targets(drag_leave_event.clone(), hovered_entity);
event_writers.drag_leave_events.write(drag_leave_event);
}
}
}
}
// If the entity is hovered...
for (pointer_id, hovered_entity, hit) in hover_map
.iter()
.flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
{
// ...but was not hovered last frame...
if !previous_hover_map
.get(&pointer_id)
.iter()
.any(|e| e.contains_key(&hovered_entity))
{
let Some(location) = pointer_location(pointer_id) else {
debug!(
"Unable to get location for pointer {:?} during pointer over",
pointer_id
);
continue;
};
// Possibly send DragEnter events
for button in PointerButton::iter() {
let state = pointer_state.get_mut(pointer_id, button);
for drag_target in state
.dragging
.keys()
.filter(|&&drag_target| hovered_entity != drag_target)
{
state.dragging_over.insert(hovered_entity, hit.clone());
let drag_enter_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
DragEnter {
button,
dragged: *drag_target,
hit: hit.clone(),
},
);
commands.trigger_targets(drag_enter_event.clone(), hovered_entity);
event_writers.drag_enter_events.write(drag_enter_event);
}
}
// Always send Over events
let over_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Over { hit: hit.clone() },
);
commands.trigger_targets(over_event.clone(), hovered_entity);
event_writers.over_events.write(over_event);
}
}
// Dispatch input events...
for PointerInput {
pointer_id,
location,
action,
} in input_events.read().cloned()
{
match action {
PointerAction::Press(button) => {
let state = pointer_state.get_mut(pointer_id, button);
// If it's a press, emit a Pressed event and mark the hovered entities as pressed
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
{
let pressed_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Pressed {
button,
hit: hit.clone(),
},
);
commands.trigger_targets(pressed_event.clone(), hovered_entity);
event_writers.pressed_events.write(pressed_event);
// Also insert the press into the state
state
.pressing
.insert(hovered_entity, (location.clone(), now, hit));
}
}
PointerAction::Release(button) => {
let state = pointer_state.get_mut(pointer_id, button);
// Emit Click and Up events on all the previously hovered entities.
for (hovered_entity, hit) in previous_hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
{
// If this pointer previously pressed the hovered entity, emit a Click event
if let Some((_, press_instant, _)) = state.pressing.get(&hovered_entity) {
let click_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Click {
button,
hit: hit.clone(),
duration: now - *press_instant,
},
);
commands.trigger_targets(click_event.clone(), hovered_entity);
event_writers.click_events.write(click_event);
}
// Always send the Released event
let released_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Released {
button,
hit: hit.clone(),
},
);
commands.trigger_targets(released_event.clone(), hovered_entity);
event_writers.released_events.write(released_event);
}
// Then emit the drop events.
for (drag_target, drag) in state.dragging.drain() {
// Emit DragDrop
for (dragged_over, hit) in state.dragging_over.iter() {
let drag_drop_event = Pointer::new(
pointer_id,
location.clone(),
*dragged_over,
DragDrop {
button,
dropped: drag_target,
hit: hit.clone(),
},
);
commands.trigger_targets(drag_drop_event.clone(), *dragged_over);
event_writers.drag_drop_events.write(drag_drop_event);
}
// Emit DragEnd
let drag_end_event = Pointer::new(
pointer_id,
location.clone(),
drag_target,
DragEnd {
button,
distance: drag.latest_pos - drag.start_pos,
},
);
commands.trigger_targets(drag_end_event.clone(), drag_target);
event_writers.drag_end_events.write(drag_end_event);
// Emit DragLeave
for (dragged_over, hit) in state.dragging_over.iter() {
let drag_leave_event = Pointer::new(
pointer_id,
location.clone(),
*dragged_over,
DragLeave {
button,
dragged: drag_target,
hit: hit.clone(),
},
);
commands.trigger_targets(drag_leave_event.clone(), *dragged_over);
event_writers.drag_leave_events.write(drag_leave_event);
}
}
// Finally, we can clear the state of everything relating to presses or drags.
state.pressing.clear();
state.dragging.clear();
state.dragging_over.clear();
}
// Moved
PointerAction::Move { delta } => {
if delta == Vec2::ZERO {
continue; // If delta is zero, the following events will not be triggered.
}
// Triggers during movement even if not over an entity
for button in PointerButton::iter() {
let state = pointer_state.get_mut(pointer_id, button);
// Emit DragEntry and DragStart the first time we move while pressing an entity
for (press_target, (location, _, hit)) in state.pressing.iter() {
if state.dragging.contains_key(press_target) {
continue; // This entity is already logged as being dragged
}
state.dragging.insert(
*press_target,
DragEntry {
start_pos: location.position,
latest_pos: location.position,
},
);
let drag_start_event = Pointer::new(
pointer_id,
location.clone(),
*press_target,
DragStart {
button,
hit: hit.clone(),
},
);
commands.trigger_targets(drag_start_event.clone(), *press_target);
event_writers.drag_start_events.write(drag_start_event);
}
// Emit Drag events to the entities we are dragging
for (drag_target, drag) in state.dragging.iter_mut() {
let delta = location.position - drag.latest_pos;
if delta == Vec2::ZERO {
continue; // No need to emit a Drag event if there is no movement
}
let drag_event = Pointer::new(
pointer_id,
location.clone(),
*drag_target,
Drag {
button,
distance: location.position - drag.start_pos,
delta,
},
);
commands.trigger_targets(drag_event.clone(), *drag_target);
event_writers.drag_events.write(drag_event);
// Update drag position
drag.latest_pos = location.position;
// Emit corresponding DragOver to the hovered entities
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
.filter(|(hovered_entity, _)| *hovered_entity != *drag_target)
{
let drag_over_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
DragOver {
button,
dragged: *drag_target,
hit: hit.clone(),
},
);
commands.trigger_targets(drag_over_event.clone(), hovered_entity);
event_writers.drag_over_events.write(drag_over_event);
}
}
}
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
{
// Emit Move events to the entities we are hovering
let move_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Move {
hit: hit.clone(),
delta,
},
);
commands.trigger_targets(move_event.clone(), hovered_entity);
event_writers.move_events.write(move_event);
}
}
PointerAction::Scroll { x, y, unit } => {
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
{
// Emit Scroll events to the entities we are hovering
let scroll_event = Pointer::new(
pointer_id,
location.clone(),
hovered_entity,
Scroll {
unit,
x,
y,
hit: hit.clone(),
},
);
commands.trigger_targets(scroll_event.clone(), hovered_entity);
event_writers.scroll_events.write(scroll_event);
}
}
// Canceled
PointerAction::Cancel => {
// Emit a Cancel to the hovered entity.
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
{
let cancel_event =
Pointer::new(pointer_id, location.clone(), hovered_entity, Cancel { hit });
commands.trigger_targets(cancel_event.clone(), hovered_entity);
event_writers.cancel_events.write(cancel_event);
}
// Clear the state for the canceled pointer
pointer_state.clear(pointer_id);
}
}
}
}

277
vendor/bevy_picking/src/hover.rs vendored Normal file
View File

@@ -0,0 +1,277 @@
//! Determines which entities are being hovered by which pointers.
//!
//! The most important type in this module is the [`HoverMap`], which maps pointers to the entities
//! they are hovering over.
use alloc::collections::BTreeMap;
use core::fmt::Debug;
use std::collections::HashSet;
use crate::{
backend::{self, HitData},
pointer::{PointerAction, PointerId, PointerInput, PointerInteraction, PointerPress},
Pickable,
};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::prelude::*;
use bevy_math::FloatOrd;
use bevy_platform::collections::HashMap;
use bevy_reflect::prelude::*;
type DepthSortedHits = Vec<(Entity, HitData)>;
/// Events returned from backends can be grouped with an order field. This allows picking to work
/// with multiple layers of rendered output to the same render target.
type PickLayer = FloatOrd;
/// Maps [`PickLayer`]s to the map of entities within that pick layer, sorted by depth.
type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;
/// Maps Pointers to a [`LayerMap`]. Note this is much more complex than the [`HoverMap`] because
/// this data structure is used to sort entities by layer then depth for every pointer.
type OverMap = HashMap<PointerId, LayerMap>;
/// The source of truth for all hover state. This is used to determine what events to send, and what
/// state components should be in.
///
/// Maps pointers to the entities they are hovering over.
///
/// "Hovering" refers to the *hover* state, which is not the same as whether or not a picking
/// backend is reporting hits between a pointer and an entity. A pointer is "hovering" an entity
/// only if the pointer is hitting the entity (as reported by a picking backend) *and* no entities
/// between it and the pointer block interactions.
///
/// For example, if a pointer is hitting a UI button and a 3d mesh, but the button is in front of
/// the mesh, the UI button will be hovered, but the mesh will not. Unless, the [`Pickable`]
/// component is present with [`should_block_lower`](Pickable::should_block_lower) set to `false`.
///
/// # Advanced Users
///
/// If you want to completely replace the provided picking events or state produced by this plugin,
/// you can use this resource to do that. All of the event systems for picking are built *on top of*
/// this authoritative hover state, and you can do the same. You can also use the
/// [`PreviousHoverMap`] as a robust way of determining changes in hover state from the previous
/// update.
#[derive(Debug, Deref, DerefMut, Default, Resource)]
pub struct HoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
/// The previous state of the hover map, used to track changes to hover state.
#[derive(Debug, Deref, DerefMut, Default, Resource)]
pub struct PreviousHoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
/// Coalesces all data from inputs and backends to generate a map of the currently hovered entities.
/// This is the final focusing step to determine which entity the pointer is hovering over.
pub fn generate_hovermap(
// Inputs
pickable: Query<&Pickable>,
pointers: Query<&PointerId>,
mut under_pointer: EventReader<backend::PointerHits>,
mut pointer_input: EventReader<PointerInput>,
// Local
mut over_map: Local<OverMap>,
// Output
mut hover_map: ResMut<HoverMap>,
mut previous_hover_map: ResMut<PreviousHoverMap>,
) {
reset_maps(
&mut hover_map,
&mut previous_hover_map,
&mut over_map,
&pointers,
);
build_over_map(&mut under_pointer, &mut over_map, &mut pointer_input);
build_hover_map(&pointers, pickable, &over_map, &mut hover_map);
}
/// Clear non-empty local maps, reusing allocated memory.
fn reset_maps(
hover_map: &mut HoverMap,
previous_hover_map: &mut PreviousHoverMap,
over_map: &mut OverMap,
pointers: &Query<&PointerId>,
) {
// Swap the previous and current hover maps. This results in the previous values being stored in
// `PreviousHoverMap`. Swapping is okay because we clear the `HoverMap` which now holds stale
// data. This process is done without any allocations.
core::mem::swap(&mut previous_hover_map.0, &mut hover_map.0);
for entity_set in hover_map.values_mut() {
entity_set.clear();
}
for layer_map in over_map.values_mut() {
layer_map.clear();
}
// Clear pointers from the maps if they have been removed.
let active_pointers: Vec<PointerId> = pointers.iter().copied().collect();
hover_map.retain(|pointer, _| active_pointers.contains(pointer));
over_map.retain(|pointer, _| active_pointers.contains(pointer));
}
/// Build an ordered map of entities that are under each pointer
fn build_over_map(
backend_events: &mut EventReader<backend::PointerHits>,
pointer_over_map: &mut Local<OverMap>,
pointer_input: &mut EventReader<PointerInput>,
) {
let cancelled_pointers: HashSet<PointerId> = pointer_input
.read()
.filter_map(|p| {
if let PointerAction::Cancel = p.action {
Some(p.pointer_id)
} else {
None
}
})
.collect();
for entities_under_pointer in backend_events
.read()
.filter(|e| !cancelled_pointers.contains(&e.pointer))
{
let pointer = entities_under_pointer.pointer;
let layer_map = pointer_over_map.entry(pointer).or_default();
for (entity, pick_data) in entities_under_pointer.picks.iter() {
let layer = entities_under_pointer.order;
let hits = layer_map.entry(FloatOrd(layer)).or_default();
hits.push((*entity, pick_data.clone()));
}
}
for layers in pointer_over_map.values_mut() {
for hits in layers.values_mut() {
hits.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
}
}
}
/// Build an unsorted set of hovered entities, accounting for depth, layer, and [`Pickable`]. Note
/// that unlike the pointer map, this uses [`Pickable`] to determine if lower entities receive hover
/// focus. Often, only a single entity per pointer will be hovered.
fn build_hover_map(
pointers: &Query<&PointerId>,
pickable: Query<&Pickable>,
over_map: &Local<OverMap>,
// Output
hover_map: &mut HoverMap,
) {
for pointer_id in pointers.iter() {
let pointer_entity_set = hover_map.entry(*pointer_id).or_default();
if let Some(layer_map) = over_map.get(pointer_id) {
// Note we reverse here to start from the highest layer first.
for (entity, pick_data) in layer_map.values().rev().flatten() {
if let Ok(pickable) = pickable.get(*entity) {
if pickable.is_hoverable {
pointer_entity_set.insert(*entity, pick_data.clone());
}
if pickable.should_block_lower {
break;
}
} else {
pointer_entity_set.insert(*entity, pick_data.clone()); // Emit events by default
break; // Entities block by default so we break out of the loop
}
}
}
}
}
/// A component that aggregates picking interaction state of this entity across all pointers.
///
/// Unlike bevy's `Interaction` component, this is an aggregate of the state of all pointers
/// interacting with this entity. Aggregation is done by taking the interaction with the highest
/// precedence.
///
/// For example, if we have an entity that is being hovered by one pointer, and pressed by another,
/// the entity will be considered pressed. If that entity is instead being hovered by both pointers,
/// it will be considered hovered.
#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
#[reflect(Component, Default, PartialEq, Debug, Clone)]
pub enum PickingInteraction {
/// The entity is being pressed down by a pointer.
Pressed = 2,
/// The entity is being hovered by a pointer.
Hovered = 1,
/// No pointers are interacting with this entity.
#[default]
None = 0,
}
/// Uses [`HoverMap`] changes to update [`PointerInteraction`] and [`PickingInteraction`] components.
pub fn update_interactions(
// Input
hover_map: Res<HoverMap>,
previous_hover_map: Res<PreviousHoverMap>,
// Outputs
mut commands: Commands,
mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,
mut interact: Query<&mut PickingInteraction>,
) {
// Clear all previous hover data from pointers and entities
for (pointer, _, mut pointer_interaction) in &mut pointers {
pointer_interaction.sorted_entities.clear();
if let Some(previously_hovered_entities) = previous_hover_map.get(pointer) {
for entity in previously_hovered_entities.keys() {
if let Ok(mut interaction) = interact.get_mut(*entity) {
*interaction = PickingInteraction::None;
}
}
}
}
// Create a map to hold the aggregated interaction for each entity. This is needed because we
// need to be able to insert the interaction component on entities if they do not exist. To do
// so we need to know the final aggregated interaction state to avoid the scenario where we set
// an entity to `Pressed`, then overwrite that with a lower precedent like `Hovered`.
let mut new_interaction_state = HashMap::<Entity, PickingInteraction>::default();
for (pointer, pointer_press, mut pointer_interaction) in &mut pointers {
if let Some(pointers_hovered_entities) = hover_map.get(pointer) {
// Insert a sorted list of hit entities into the pointer's interaction component.
let mut sorted_entities: Vec<_> = pointers_hovered_entities.clone().drain().collect();
sorted_entities.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
pointer_interaction.sorted_entities = sorted_entities;
for hovered_entity in pointers_hovered_entities.iter().map(|(entity, _)| entity) {
merge_interaction_states(pointer_press, hovered_entity, &mut new_interaction_state);
}
}
}
// Take the aggregated entity states and update or insert the component if missing.
for (hovered_entity, new_interaction) in new_interaction_state.drain() {
if let Ok(mut interaction) = interact.get_mut(hovered_entity) {
*interaction = new_interaction;
} else if let Ok(mut entity_commands) = commands.get_entity(hovered_entity) {
entity_commands.try_insert(new_interaction);
}
}
}
/// Merge the interaction state of this entity into the aggregated map.
fn merge_interaction_states(
pointer_press: &PointerPress,
hovered_entity: &Entity,
new_interaction_state: &mut HashMap<Entity, PickingInteraction>,
) {
let new_interaction = match pointer_press.is_any_pressed() {
true => PickingInteraction::Pressed,
false => PickingInteraction::Hovered,
};
if let Some(old_interaction) = new_interaction_state.get_mut(hovered_entity) {
// Only update if the new value has a higher precedence than the old value.
if *old_interaction != new_interaction
&& matches!(
(*old_interaction, new_interaction),
(PickingInteraction::Hovered, PickingInteraction::Pressed)
| (PickingInteraction::None, PickingInteraction::Pressed)
| (PickingInteraction::None, PickingInteraction::Hovered)
)
{
*old_interaction = new_interaction;
}
} else {
new_interaction_state.insert(*hovered_entity, new_interaction);
}
}

279
vendor/bevy_picking/src/input.rs vendored Normal file
View File

@@ -0,0 +1,279 @@
//! This module provides unsurprising default inputs to `bevy_picking` through [`PointerInput`].
//! The included systems are responsible for sending mouse and touch inputs to their
//! respective `Pointer`s.
//!
//! Because this has it's own plugin, it's easy to omit it, and provide your own inputs as
//! needed. Because `Pointer`s aren't coupled to the underlying input hardware, you can easily mock
//! inputs, and allow users full accessibility to map whatever inputs they need to pointer input.
//!
//! If, for example, you wanted to add support for VR input, all you need to do is spawn a pointer
//! entity with a custom [`PointerId`], and write a system
//! that updates its position. If you want this to work properly with the existing interaction events,
//! you need to be sure that you also write a [`PointerInput`] event stream.
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_input::{
mouse::MouseWheel,
prelude::*,
touch::{TouchInput, TouchPhase},
ButtonState,
};
use bevy_math::Vec2;
use bevy_platform::collections::{HashMap, HashSet};
use bevy_reflect::prelude::*;
use bevy_render::camera::RenderTarget;
use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
use tracing::debug;
use crate::pointer::{
Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
};
use crate::PickSet;
/// The picking input prelude.
///
/// This includes the most common types in this module, re-exported for your convenience.
pub mod prelude {
pub use crate::input::PointerInputPlugin;
}
/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
/// that you can replace with your own plugin as needed.
///
/// [`crate::PickingPlugin::is_input_enabled`] can be used to toggle whether
/// the core picking plugin processes the inputs sent by this, or other input plugins, in one place.
///
/// This plugin contains several settings, and is added to the world as a resource after initialization.
/// You can configure pointer input settings at runtime by accessing the resource.
#[derive(Copy, Clone, Resource, Debug, Reflect)]
#[reflect(Resource, Default, Clone)]
pub struct PointerInputPlugin {
/// Should touch inputs be updated?
pub is_touch_enabled: bool,
/// Should mouse inputs be updated?
pub is_mouse_enabled: bool,
}
impl PointerInputPlugin {
fn is_mouse_enabled(state: Res<Self>) -> bool {
state.is_mouse_enabled
}
fn is_touch_enabled(state: Res<Self>) -> bool {
state.is_touch_enabled
}
}
impl Default for PointerInputPlugin {
fn default() -> Self {
Self {
is_touch_enabled: true,
is_mouse_enabled: true,
}
}
}
impl Plugin for PointerInputPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(*self)
.add_systems(Startup, spawn_mouse_pointer)
.add_systems(
First,
(
mouse_pick_events.run_if(PointerInputPlugin::is_mouse_enabled),
touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled),
)
.chain()
.in_set(PickSet::Input),
)
.add_systems(
Last,
deactivate_touch_pointers.run_if(PointerInputPlugin::is_touch_enabled),
)
.register_type::<Self>()
.register_type::<PointerInputPlugin>();
}
}
/// Spawns the default mouse pointer.
pub fn spawn_mouse_pointer(mut commands: Commands) {
commands.spawn(PointerId::Mouse);
}
/// Sends mouse pointer events to be processed by the core plugin
pub fn mouse_pick_events(
// Input
mut window_events: EventReader<WindowEvent>,
primary_window: Query<Entity, With<PrimaryWindow>>,
// Locals
mut cursor_last: Local<Vec2>,
// Output
mut pointer_events: EventWriter<PointerInput>,
) {
for window_event in window_events.read() {
match window_event {
// Handle cursor movement events
WindowEvent::CursorMoved(event) => {
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(event.window))
.normalize(primary_window.single().ok())
{
Some(target) => target,
None => continue,
},
position: event.position,
};
pointer_events.write(PointerInput::new(
PointerId::Mouse,
location,
PointerAction::Move {
delta: event.position - *cursor_last,
},
));
*cursor_last = event.position;
}
// Handle mouse button press events
WindowEvent::MouseButtonInput(input) => {
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(input.window))
.normalize(primary_window.single().ok())
{
Some(target) => target,
None => continue,
},
position: *cursor_last,
};
let button = match input.button {
MouseButton::Left => PointerButton::Primary,
MouseButton::Right => PointerButton::Secondary,
MouseButton::Middle => PointerButton::Middle,
MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue,
};
let action = match input.state {
ButtonState::Pressed => PointerAction::Press(button),
ButtonState::Released => PointerAction::Release(button),
};
pointer_events.write(PointerInput::new(PointerId::Mouse, location, action));
}
WindowEvent::MouseWheel(event) => {
let MouseWheel { unit, x, y, window } = *event;
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(window))
.normalize(primary_window.single().ok())
{
Some(target) => target,
None => continue,
},
position: *cursor_last,
};
let action = PointerAction::Scroll { x, y, unit };
pointer_events.write(PointerInput::new(PointerId::Mouse, location, action));
}
_ => {}
}
}
}
/// Sends touch pointer events to be consumed by the core plugin
pub fn touch_pick_events(
// Input
mut window_events: EventReader<WindowEvent>,
primary_window: Query<Entity, With<PrimaryWindow>>,
// Locals
mut touch_cache: Local<HashMap<u64, TouchInput>>,
// Output
mut commands: Commands,
mut pointer_events: EventWriter<PointerInput>,
) {
for window_event in window_events.read() {
if let WindowEvent::TouchInput(touch) = window_event {
let pointer = PointerId::Touch(touch.id);
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(touch.window))
.normalize(primary_window.single().ok())
{
Some(target) => target,
None => continue,
},
position: touch.position,
};
match touch.phase {
TouchPhase::Started => {
debug!("Spawning pointer {:?}", pointer);
commands.spawn((pointer, PointerLocation::new(location.clone())));
pointer_events.write(PointerInput::new(
pointer,
location,
PointerAction::Press(PointerButton::Primary),
));
touch_cache.insert(touch.id, *touch);
}
TouchPhase::Moved => {
// Send a move event only if it isn't the same as the last one
if let Some(last_touch) = touch_cache.get(&touch.id) {
if last_touch == touch {
continue;
}
pointer_events.write(PointerInput::new(
pointer,
location,
PointerAction::Move {
delta: touch.position - last_touch.position,
},
));
}
touch_cache.insert(touch.id, *touch);
}
TouchPhase::Ended => {
pointer_events.write(PointerInput::new(
pointer,
location,
PointerAction::Release(PointerButton::Primary),
));
touch_cache.remove(&touch.id);
}
TouchPhase::Canceled => {
pointer_events.write(PointerInput::new(
pointer,
location,
PointerAction::Cancel,
));
touch_cache.remove(&touch.id);
}
}
}
}
}
/// Deactivates unused touch pointers.
///
/// Because each new touch gets assigned a new ID, we need to remove the pointers associated with
/// touches that are no longer active.
pub fn deactivate_touch_pointers(
mut commands: Commands,
mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
pointers: Query<(Entity, &PointerId)>,
mut touches: EventReader<TouchInput>,
) {
for touch in touches.read() {
if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase {
for (entity, pointer) in &pointers {
if pointer.get_touch_id() == Some(touch.id) {
despawn_list.insert((entity, *pointer));
}
}
}
}
// A hash set is used to prevent despawning the same entity twice.
for (entity, pointer) in despawn_list.drain() {
debug!("Despawning pointer {:?}", pointer);
commands.entity(entity).despawn();
}
}

433
vendor/bevy_picking/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,433 @@
//! This crate provides 'picking' capabilities for the Bevy game engine, allowing pointers to
//! interact with entities using hover, click, and drag events.
//!
//! ## Overview
//!
//! In the simplest case, this plugin allows you to click on things in the scene. However, it also
//! allows you to express more complex interactions, like detecting when a touch input drags a UI
//! element and drops it on a 3d mesh rendered to a different camera.
//!
//! Pointer events bubble up the entity hierarchy and can be used with observers, allowing you to
//! succinctly express rich interaction behaviors by attaching pointer callbacks to entities:
//!
//! ```rust
//! # use bevy_ecs::prelude::*;
//! # use bevy_picking::prelude::*;
//! # #[derive(Component)]
//! # struct MyComponent;
//! # let mut world = World::new();
//! world.spawn(MyComponent)
//! .observe(|mut trigger: Trigger<Pointer<Click>>| {
//! println!("I was just clicked!");
//! // Get the underlying pointer event data
//! let click_event: &Pointer<Click> = trigger.event();
//! // Stop the event from bubbling up the entity hierarchy
//! trigger.propagate(false);
//! });
//! ```
//!
//! At its core, this crate provides a robust abstraction for computing picking state regardless of
//! pointing devices, or what you are hit testing against. It is designed to work with any input,
//! including mouse, touch, pens, or virtual pointers controlled by gamepads.
//!
//! ## Expressive Events
//!
//! Although the events in this module (see [`events`]) can be listened to with normal
//! `EventReader`s, using observers is often more expressive, with less boilerplate. This is because
//! observers allow you to attach event handling logic to specific entities, as well as make use of
//! event bubbling.
//!
//! When events are generated, they bubble up the entity hierarchy starting from their target, until
//! they reach the root or bubbling is halted with a call to
//! [`Trigger::propagate`](bevy_ecs::observer::Trigger::propagate). See [`Observer`] for details.
//!
//! This allows you to run callbacks when any children of an entity are interacted with, and leads
//! to succinct, expressive code:
//!
//! ```
//! # use bevy_ecs::prelude::*;
//! # use bevy_transform::prelude::*;
//! # use bevy_picking::prelude::*;
//! # #[derive(Event)]
//! # struct Greeting;
//! fn setup(mut commands: Commands) {
//! commands.spawn(Transform::default())
//! // Spawn your entity here, e.g. a Mesh.
//! // When dragged, mutate the `Transform` component on the dragged target entity:
//! .observe(|trigger: Trigger<Pointer<Drag>>, mut transforms: Query<&mut Transform>| {
//! let mut transform = transforms.get_mut(trigger.target()).unwrap();
//! let drag = trigger.event();
//! transform.rotate_local_y(drag.delta.x / 50.0);
//! })
//! .observe(|trigger: Trigger<Pointer<Click>>, mut commands: Commands| {
//! println!("Entity {} goes BOOM!", trigger.target());
//! commands.entity(trigger.target()).despawn();
//! })
//! .observe(|trigger: Trigger<Pointer<Over>>, mut events: EventWriter<Greeting>| {
//! events.write(Greeting);
//! });
//! }
//! ```
//!
//! ## Modularity
//!
//! #### Mix and Match Hit Testing Backends
//!
//! The plugin attempts to handle all the hard parts for you, all you need to do is tell it when a
//! pointer is hitting any entities. Multiple backends can be used at the same time! [Use this
//! simple API to write your own backend](crate::backend) in about 100 lines of code.
//!
//! #### Input Agnostic
//!
//! Picking provides a generic Pointer abstraction, which is useful for reacting to many different
//! types of input devices. Pointers can be controlled with anything, whether it's the included
//! mouse or touch inputs, or a custom gamepad input system you write yourself to control a virtual
//! pointer.
//!
//! ## Robustness
//!
//! In addition to these features, this plugin also correctly handles multitouch, multiple windows,
//! multiple cameras, viewports, and render layers. Using this as a library allows you to write a
//! picking backend that can interoperate with any other picking backend.
//!
//! # Getting Started
//!
//! TODO: This section will need to be re-written once more backends are introduced.
//!
//! #### Next Steps
//!
//! To learn more, take a look at the examples in the
//! [examples](https://github.com/bevyengine/bevy/tree/main/examples/picking). You can read the next
//! section to understand how the plugin works.
//!
//! # The Picking Pipeline
//!
//! This plugin is designed to be extremely modular. To do so, it works in well-defined stages that
//! form a pipeline, where events are used to pass data between each stage.
//!
//! #### Pointers ([`pointer`](mod@pointer))
//!
//! The first stage of the pipeline is to gather inputs and update pointers. This stage is
//! ultimately responsible for generating [`PointerInput`](pointer::PointerInput) events. The
//! provided crate does this automatically for mouse, touch, and pen inputs. If you wanted to
//! implement your own pointer, controlled by some other input, you can do that here. The ordering
//! of events within the [`PointerInput`](pointer::PointerInput) stream is meaningful for events
//! with the same [`PointerId`](pointer::PointerId), but not between different pointers.
//!
//! Because pointer positions and presses are driven by these events, you can use them to mock
//! inputs for testing.
//!
//! After inputs are generated, they are then collected to update the current
//! [`PointerLocation`](pointer::PointerLocation) for each pointer.
//!
//! #### Backend ([`backend`])
//!
//! A picking backend only has one job: reading [`PointerLocation`](pointer::PointerLocation)
//! components, and producing [`PointerHits`](backend::PointerHits). You can find all documentation
//! and types needed to implement a backend at [`backend`].
//!
//! You will eventually need to choose which picking backend(s) you want to use. This crate does not
//! supply any backends, and expects you to select some from the other bevy crates or the
//! third-party ecosystem.
//!
//! It's important to understand that you can mix and match backends! For example, you might have a
//! backend for your UI, and one for the 3d scene, with each being specialized for their purpose.
//! Bevy provides some backends out of the box, but you can even write your own. It's been made as
//! easy as possible intentionally; the `bevy_mod_raycast` backend is 50 lines of code.
//!
//! #### Hover ([`hover`])
//!
//! The next step is to use the data from the backends, combine and sort the results, and determine
//! what each cursor is hovering over, producing a [`HoverMap`](`crate::hover::HoverMap`). Note that
//! just because a pointer is over an entity, it is not necessarily *hovering* that entity. Although
//! multiple backends may be reporting that a pointer is hitting an entity, the hover system needs
//! to determine which entities are actually being hovered by this pointer based on the pick depth,
//! order of the backend, and the optional [`Pickable`] component of the entity. In other
//! words, if one entity is in front of another, usually only the topmost one will be hovered.
//!
//! #### Events ([`events`])
//!
//! In the final step, the high-level pointer events are generated, such as events that trigger when
//! a pointer hovers or clicks an entity. These simple events are then used to generate more complex
//! events for dragging and dropping.
//!
//! Because it is completely agnostic to the earlier stages of the pipeline, you can easily extend
//! the plugin with arbitrary backends and input methods, yet still use all the high level features.
#![deny(missing_docs)]
extern crate alloc;
pub mod backend;
pub mod events;
pub mod hover;
pub mod input;
#[cfg(feature = "bevy_mesh_picking_backend")]
pub mod mesh_picking;
pub mod pointer;
pub mod window;
use bevy_app::{prelude::*, PluginGroupBuilder};
use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
/// The picking prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[cfg(feature = "bevy_mesh_picking_backend")]
#[doc(hidden)]
pub use crate::mesh_picking::{
ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastBackfaces, RayCastVisibility},
MeshPickingCamera, MeshPickingPlugin, MeshPickingSettings,
};
#[doc(hidden)]
pub use crate::{
events::*, input::PointerInputPlugin, pointer::PointerButton, DefaultPickingPlugins,
InteractionPlugin, Pickable, PickingPlugin,
};
}
/// An optional component that marks an entity as usable by a backend, and overrides default
/// picking behavior for an entity.
///
/// This allows you to make an entity non-hoverable, or allow items below it to be hovered.
///
/// See the documentation on the fields for more details.
#[derive(Component, Debug, Clone, Reflect, PartialEq, Eq)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct Pickable {
/// Should this entity block entities below it from being picked?
///
/// This is useful if you want picking to continue hitting entities below this one. Normally,
/// only the topmost entity under a pointer can be hovered, but this setting allows the pointer
/// to hover multiple entities, from nearest to farthest, stopping as soon as it hits an entity
/// that blocks lower entities.
///
/// Note that the word "lower" here refers to entities that have been reported as hit by any
/// picking backend, but are at a lower depth than the current one. This is different from the
/// concept of event bubbling, as it works irrespective of the entity hierarchy.
///
/// For example, if a pointer is over a UI element, as well as a 3d mesh, backends will report
/// hits for both of these entities. Additionally, the hits will be sorted by the camera order,
/// so if the UI is drawing on top of the 3d mesh, the UI will be "above" the mesh. When hovering
/// is computed, the UI element will be checked first to see if it this field is set to block
/// lower entities. If it does (default), the hovering system will stop there, and only the UI
/// element will be marked as hovered. However, if this field is set to `false`, both the UI
/// element *and* the mesh will be marked as hovered.
///
/// Entities without the [`Pickable`] component will block by default.
pub should_block_lower: bool,
/// If this is set to `false` and `should_block_lower` is set to true, this entity will block
/// lower entities from being interacted and at the same time will itself not emit any events.
///
/// Note that the word "lower" here refers to entities that have been reported as hit by any
/// picking backend, but are at a lower depth than the current one. This is different from the
/// concept of event bubbling, as it works irrespective of the entity hierarchy.
///
/// For example, if a pointer is over a UI element, and this field is set to `false`, it will
/// not be marked as hovered, and consequently will not emit events nor will any picking
/// components mark it as hovered. This can be combined with the other field
/// [`Self::should_block_lower`], which is orthogonal to this one.
///
/// Entities without the [`Pickable`] component are hoverable by default.
pub is_hoverable: bool,
}
impl Pickable {
/// This entity will not block entities beneath it, nor will it emit events.
///
/// If a backend reports this entity as being hit, the picking plugin will completely ignore it.
pub const IGNORE: Self = Self {
should_block_lower: false,
is_hoverable: false,
};
}
impl Default for Pickable {
fn default() -> Self {
Self {
should_block_lower: true,
is_hoverable: true,
}
}
}
/// Groups the stages of the picking process under shared labels.
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum PickSet {
/// Produces pointer input events. In the [`First`] schedule.
Input,
/// Runs after input events are generated but before commands are flushed. In the [`First`]
/// schedule.
PostInput,
/// Receives and processes pointer input events. In the [`PreUpdate`] schedule.
ProcessInput,
/// Reads inputs and produces [`backend::PointerHits`]s. In the [`PreUpdate`] schedule.
Backend,
/// Reads [`backend::PointerHits`]s, and updates the hovermap, selection, and highlighting states. In
/// the [`PreUpdate`] schedule.
Hover,
/// Runs after all the [`PickSet::Hover`] systems are done, before event listeners are triggered. In the
/// [`PreUpdate`] schedule.
PostHover,
/// Runs after all other picking sets. In the [`PreUpdate`] schedule.
Last,
}
/// One plugin that contains the [`PointerInputPlugin`](input::PointerInputPlugin), [`PickingPlugin`]
/// and the [`InteractionPlugin`], this is probably the plugin that will be most used.
///
/// Note: for any of these plugins to work, they require a picking backend to be active,
/// The picking backend is responsible to turn an input, into a [`crate::backend::PointerHits`]
/// that [`PickingPlugin`] and [`InteractionPlugin`] will refine into [`bevy_ecs::observer::Trigger`]s.
#[derive(Default)]
pub struct DefaultPickingPlugins;
impl PluginGroup for DefaultPickingPlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(input::PointerInputPlugin::default())
.add(PickingPlugin::default())
.add(InteractionPlugin)
}
}
/// This plugin sets up the core picking infrastructure. It receives input events, and provides the shared
/// types used by other picking plugins.
///
/// This plugin contains several settings, and is added to the world as a resource after initialization. You
/// can configure picking settings at runtime through the resource.
#[derive(Copy, Clone, Debug, Resource, Reflect)]
#[reflect(Resource, Default, Debug, Clone)]
pub struct PickingPlugin {
/// Enables and disables all picking features.
pub is_enabled: bool,
/// Enables and disables input collection.
pub is_input_enabled: bool,
/// Enables and disables updating interaction states of entities.
pub is_hover_enabled: bool,
/// Enables or disables picking for window entities.
pub is_window_picking_enabled: bool,
}
impl PickingPlugin {
/// Whether or not input collection systems should be running.
pub fn input_should_run(state: Res<Self>) -> bool {
state.is_input_enabled && state.is_enabled
}
/// Whether or not systems updating entities' [`PickingInteraction`](hover::PickingInteraction)
/// component should be running.
pub fn hover_should_run(state: Res<Self>) -> bool {
state.is_hover_enabled && state.is_enabled
}
/// Whether or not window entities should receive pick events.
pub fn window_picking_should_run(state: Res<Self>) -> bool {
state.is_window_picking_enabled && state.is_enabled
}
}
impl Default for PickingPlugin {
fn default() -> Self {
Self {
is_enabled: true,
is_input_enabled: true,
is_hover_enabled: true,
is_window_picking_enabled: true,
}
}
}
impl Plugin for PickingPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(*self)
.init_resource::<pointer::PointerMap>()
.init_resource::<backend::ray::RayMap>()
.add_event::<pointer::PointerInput>()
.add_event::<backend::PointerHits>()
// Rather than try to mark all current and future backends as ambiguous with each other,
// we allow them to send their hits in any order. These are later sorted, so submission
// order doesn't matter. See `PointerHits` docs for caveats.
.allow_ambiguous_resource::<Events<backend::PointerHits>>()
.add_systems(
PreUpdate,
(
pointer::update_pointer_map,
pointer::PointerInput::receive,
backend::ray::RayMap::repopulate.after(pointer::PointerInput::receive),
)
.in_set(PickSet::ProcessInput),
)
.add_systems(
PreUpdate,
window::update_window_hits
.run_if(Self::window_picking_should_run)
.in_set(PickSet::Backend),
)
.configure_sets(
First,
(PickSet::Input, PickSet::PostInput)
.after(bevy_time::TimeSystem)
.after(bevy_ecs::event::EventUpdates)
.chain(),
)
.configure_sets(
PreUpdate,
(
PickSet::ProcessInput.run_if(Self::input_should_run),
PickSet::Backend,
PickSet::Hover.run_if(Self::hover_should_run),
PickSet::PostHover,
PickSet::Last,
)
.chain(),
)
.register_type::<Self>()
.register_type::<Pickable>()
.register_type::<hover::PickingInteraction>()
.register_type::<pointer::PointerId>()
.register_type::<pointer::PointerLocation>()
.register_type::<pointer::PointerPress>()
.register_type::<pointer::PointerInteraction>()
.register_type::<backend::ray::RayId>();
}
}
/// Generates [`Pointer`](events::Pointer) events and handles event bubbling.
#[derive(Default)]
pub struct InteractionPlugin;
impl Plugin for InteractionPlugin {
fn build(&self, app: &mut App) {
use events::*;
use hover::{generate_hovermap, update_interactions};
app.init_resource::<hover::HoverMap>()
.init_resource::<hover::PreviousHoverMap>()
.init_resource::<PointerState>()
.add_event::<Pointer<Cancel>>()
.add_event::<Pointer<Click>>()
.add_event::<Pointer<Pressed>>()
.add_event::<Pointer<DragDrop>>()
.add_event::<Pointer<DragEnd>>()
.add_event::<Pointer<DragEnter>>()
.add_event::<Pointer<Drag>>()
.add_event::<Pointer<DragLeave>>()
.add_event::<Pointer<DragOver>>()
.add_event::<Pointer<DragStart>>()
.add_event::<Pointer<Move>>()
.add_event::<Pointer<Out>>()
.add_event::<Pointer<Over>>()
.add_event::<Pointer<Released>>()
.add_event::<Pointer<Scroll>>()
.add_systems(
PreUpdate,
(generate_hovermap, update_interactions, pointer_events)
.chain()
.in_set(PickSet::Hover),
);
}
}

View File

@@ -0,0 +1,137 @@
//! A [mesh ray casting](ray_cast) backend for [`bevy_picking`](crate).
//!
//! By default, all meshes are pickable. Picking can be disabled for individual entities
//! by adding [`Pickable::IGNORE`].
//!
//! To make mesh picking entirely opt-in, set [`MeshPickingSettings::require_markers`]
//! to `true` and add [`MeshPickingCamera`] and [`Pickable`] components to the desired camera and
//! target entities.
//!
//! To manually perform mesh ray casts independent of picking, use the [`MeshRayCast`] system parameter.
//!
//! ## Implementation Notes
//!
//! - The `position` reported in `HitData` is in world space. The `normal` is a vector pointing
//! away from the face, it is not guaranteed to be normalized for scaled meshes.
pub mod ray_cast;
use crate::{
backend::{ray::RayMap, HitData, PointerHits},
prelude::*,
PickSet,
};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_render::{prelude::*, view::RenderLayers};
use ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastVisibility, SimplifiedMesh};
/// An optional component that marks cameras that should be used in the [`MeshPickingPlugin`].
///
/// Only needed if [`MeshPickingSettings::require_markers`] is set to `true`, and ignored otherwise.
#[derive(Debug, Clone, Default, Component, Reflect)]
#[reflect(Debug, Default, Component)]
pub struct MeshPickingCamera;
/// Runtime settings for the [`MeshPickingPlugin`].
#[derive(Resource, Reflect)]
#[reflect(Resource, Default)]
pub struct MeshPickingSettings {
/// When set to `true` ray casting will only consider cameras marked with
/// [`MeshPickingCamera`] and entities marked with [`Pickable`]. `false` by default.
///
/// This setting is provided to give you fine-grained control over which cameras and entities
/// should be used by the mesh picking backend at runtime.
pub require_markers: bool,
/// Determines how mesh picking should consider [`Visibility`]. When set to [`RayCastVisibility::Any`],
/// ray casts can be performed against both visible and hidden entities.
///
/// Defaults to [`RayCastVisibility::VisibleInView`], only performing picking against visible entities
/// that are in the view of a camera.
pub ray_cast_visibility: RayCastVisibility,
}
impl Default for MeshPickingSettings {
fn default() -> Self {
Self {
require_markers: false,
ray_cast_visibility: RayCastVisibility::VisibleInView,
}
}
}
/// Adds the mesh picking backend to your app.
#[derive(Clone, Default)]
pub struct MeshPickingPlugin;
impl Plugin for MeshPickingPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<MeshPickingSettings>()
.register_type::<MeshPickingSettings>()
.register_type::<SimplifiedMesh>()
.add_systems(PreUpdate, update_hits.in_set(PickSet::Backend));
}
}
/// Casts rays into the scene using [`MeshPickingSettings`] and sends [`PointerHits`] events.
pub fn update_hits(
backend_settings: Res<MeshPickingSettings>,
ray_map: Res<RayMap>,
picking_cameras: Query<(&Camera, Has<MeshPickingCamera>, Option<&RenderLayers>)>,
pickables: Query<&Pickable>,
marked_targets: Query<&Pickable>,
layers: Query<&RenderLayers>,
mut ray_cast: MeshRayCast,
mut output: EventWriter<PointerHits>,
) {
for (&ray_id, &ray) in ray_map.iter() {
let Ok((camera, cam_can_pick, cam_layers)) = picking_cameras.get(ray_id.camera) else {
continue;
};
if backend_settings.require_markers && !cam_can_pick {
continue;
}
let cam_layers = cam_layers.to_owned().unwrap_or_default();
let settings = MeshRayCastSettings {
visibility: backend_settings.ray_cast_visibility,
filter: &|entity| {
let marker_requirement =
!backend_settings.require_markers || marked_targets.get(entity).is_ok();
// Other entities missing render layers are on the default layer 0
let entity_layers = layers.get(entity).cloned().unwrap_or_default();
let render_layers_match = cam_layers.intersects(&entity_layers);
let is_pickable = pickables.get(entity).ok().is_none_or(|p| p.is_hoverable);
marker_requirement && render_layers_match && is_pickable
},
early_exit_test: &|entity_hit| {
pickables
.get(entity_hit)
.is_ok_and(|pickable| pickable.should_block_lower)
},
};
let picks = ray_cast
.cast_ray(ray, &settings)
.iter()
.map(|(entity, hit)| {
let hit_data = HitData::new(
ray_id.camera,
hit.distance,
Some(hit.point),
Some(hit.normal),
);
(*entity, hit_data)
})
.collect::<Vec<_>>();
let order = camera.order as f32;
if !picks.is_empty() {
output.write(PointerHits::new(ray_id.pointer, picks, order));
}
}
}

View File

@@ -0,0 +1,486 @@
use bevy_math::{bounding::Aabb3d, Dir3, Mat4, Ray3d, Vec3, Vec3A};
use bevy_mesh::{Indices, Mesh, PrimitiveTopology};
use bevy_reflect::Reflect;
use super::Backfaces;
/// Hit data for an intersection between a ray and a mesh.
#[derive(Debug, Clone, Reflect)]
#[reflect(Clone)]
pub struct RayMeshHit {
/// The point of intersection in world space.
pub point: Vec3,
/// The normal vector of the triangle at the point of intersection. Not guaranteed to be normalized for scaled meshes.
pub normal: Vec3,
/// The barycentric coordinates of the intersection.
pub barycentric_coords: Vec3,
/// The distance from the ray origin to the intersection point.
pub distance: f32,
/// The vertices of the triangle that was hit.
pub triangle: Option<[Vec3; 3]>,
/// The index of the triangle that was hit.
pub triangle_index: Option<usize>,
}
/// Hit data for an intersection between a ray and a triangle.
#[derive(Default, Debug)]
pub struct RayTriangleHit {
pub distance: f32,
pub barycentric_coords: (f32, f32),
}
/// Casts a ray on a mesh, and returns the intersection.
pub(super) fn ray_intersection_over_mesh(
mesh: &Mesh,
transform: &Mat4,
ray: Ray3d,
culling: Backfaces,
) -> Option<RayMeshHit> {
if mesh.primitive_topology() != PrimitiveTopology::TriangleList {
return None; // ray_mesh_intersection assumes vertices are laid out in a triangle list
}
// Vertex positions are required
let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?.as_float3()?;
// Normals are optional
let normals = mesh
.attribute(Mesh::ATTRIBUTE_NORMAL)
.and_then(|normal_values| normal_values.as_float3());
match mesh.indices() {
Some(Indices::U16(indices)) => {
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling)
}
Some(Indices::U32(indices)) => {
ray_mesh_intersection(ray, transform, positions, normals, Some(indices), culling)
}
None => ray_mesh_intersection::<usize>(ray, transform, positions, normals, None, culling),
}
}
/// Checks if a ray intersects a mesh, and returns the nearest intersection if one exists.
pub fn ray_mesh_intersection<I: TryInto<usize> + Clone + Copy>(
ray: Ray3d,
mesh_transform: &Mat4,
positions: &[[f32; 3]],
vertex_normals: Option<&[[f32; 3]]>,
indices: Option<&[I]>,
backface_culling: Backfaces,
) -> Option<RayMeshHit> {
let world_to_mesh = mesh_transform.inverse();
let ray = Ray3d::new(
world_to_mesh.transform_point3(ray.origin),
Dir3::new(world_to_mesh.transform_vector3(*ray.direction)).ok()?,
);
let closest_hit = if let Some(indices) = indices {
// The index list must be a multiple of three. If not, the mesh is malformed and the raycast
// result might be nonsensical.
if indices.len() % 3 != 0 {
return None;
}
indices
.chunks_exact(3)
.enumerate()
.fold(
(f32::MAX, None),
|(closest_distance, closest_hit), (tri_idx, triangle)| {
let [Ok(a), Ok(b), Ok(c)] = [
triangle[0].try_into(),
triangle[1].try_into(),
triangle[2].try_into(),
] else {
return (closest_distance, closest_hit);
};
let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)]
{
[Some(a), Some(b), Some(c)] => {
[Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)]
}
_ => return (closest_distance, closest_hit),
};
match ray_triangle_intersection(&ray, &tri_vertices, backface_culling) {
Some(hit) if hit.distance >= 0. && hit.distance < closest_distance => {
(hit.distance, Some((tri_idx, hit)))
}
_ => (closest_distance, closest_hit),
}
},
)
.1
} else {
positions
.chunks_exact(3)
.enumerate()
.fold(
(f32::MAX, None),
|(closest_distance, closest_hit), (tri_idx, triangle)| {
let tri_vertices = [
Vec3::from(triangle[0]),
Vec3::from(triangle[1]),
Vec3::from(triangle[2]),
];
match ray_triangle_intersection(&ray, &tri_vertices, backface_culling) {
Some(hit) if hit.distance >= 0. && hit.distance < closest_distance => {
(hit.distance, Some((tri_idx, hit)))
}
_ => (closest_distance, closest_hit),
}
},
)
.1
};
closest_hit.and_then(|(tri_idx, hit)| {
let [a, b, c] = match indices {
Some(indices) => {
let triangle = indices.get((tri_idx * 3)..(tri_idx * 3 + 3))?;
let [Ok(a), Ok(b), Ok(c)] = [
triangle[0].try_into(),
triangle[1].try_into(),
triangle[2].try_into(),
] else {
return None;
};
[a, b, c]
}
None => [tri_idx * 3, tri_idx * 3 + 1, tri_idx * 3 + 2],
};
let tri_vertices = match [positions.get(a), positions.get(b), positions.get(c)] {
[Some(a), Some(b), Some(c)] => [Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)],
_ => return None,
};
let tri_normals = vertex_normals.and_then(|normals| {
let [Some(a), Some(b), Some(c)] = [normals.get(a), normals.get(b), normals.get(c)]
else {
return None;
};
Some([Vec3::from(*a), Vec3::from(*b), Vec3::from(*c)])
});
let point = ray.get_point(hit.distance);
let u = hit.barycentric_coords.0;
let v = hit.barycentric_coords.1;
let w = 1.0 - u - v;
let barycentric = Vec3::new(u, v, w);
let normal = if let Some(normals) = tri_normals {
normals[1] * u + normals[2] * v + normals[0] * w
} else {
(tri_vertices[1] - tri_vertices[0])
.cross(tri_vertices[2] - tri_vertices[0])
.normalize()
};
Some(RayMeshHit {
point: mesh_transform.transform_point3(point),
normal: mesh_transform.transform_vector3(normal),
barycentric_coords: barycentric,
distance: mesh_transform
.transform_vector3(ray.direction * hit.distance)
.length(),
triangle: Some(tri_vertices.map(|v| mesh_transform.transform_point3(v))),
triangle_index: Some(tri_idx),
})
})
}
/// Takes a ray and triangle and computes the intersection.
#[inline]
fn ray_triangle_intersection(
ray: &Ray3d,
triangle: &[Vec3; 3],
backface_culling: Backfaces,
) -> Option<RayTriangleHit> {
// Source: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection
let vector_v0_to_v1: Vec3 = triangle[1] - triangle[0];
let vector_v0_to_v2: Vec3 = triangle[2] - triangle[0];
let p_vec: Vec3 = ray.direction.cross(vector_v0_to_v2);
let determinant: f32 = vector_v0_to_v1.dot(p_vec);
match backface_culling {
Backfaces::Cull => {
// if the determinant is negative the triangle is back facing
// if the determinant is close to 0, the ray misses the triangle
// This test checks both cases
if determinant < f32::EPSILON {
return None;
}
}
Backfaces::Include => {
// ray and triangle are parallel if det is close to 0
if determinant.abs() < f32::EPSILON {
return None;
}
}
}
let determinant_inverse = 1.0 / determinant;
let t_vec = ray.origin - triangle[0];
let u = t_vec.dot(p_vec) * determinant_inverse;
if !(0.0..=1.0).contains(&u) {
return None;
}
let q_vec = t_vec.cross(vector_v0_to_v1);
let v = (*ray.direction).dot(q_vec) * determinant_inverse;
if v < 0.0 || u + v > 1.0 {
return None;
}
// The distance between ray origin and intersection is t.
let t: f32 = vector_v0_to_v2.dot(q_vec) * determinant_inverse;
Some(RayTriangleHit {
distance: t,
barycentric_coords: (u, v),
})
}
// TODO: It'd be nice to reuse `RayCast3d::aabb_intersection_at`, but it assumes a normalized ray.
// In our case, the ray is transformed to model space, which could involve scaling.
/// Checks if the ray intersects with the AABB of a mesh, returning the distance to the point of intersection.
/// The distance is zero if the ray starts inside the AABB.
pub fn ray_aabb_intersection_3d(ray: Ray3d, aabb: &Aabb3d, model_to_world: &Mat4) -> Option<f32> {
// Transform the ray to model space
let world_to_model = model_to_world.inverse();
let ray_direction: Vec3A = world_to_model.transform_vector3a((*ray.direction).into());
let ray_direction_recip = ray_direction.recip();
let ray_origin: Vec3A = world_to_model.transform_point3a(ray.origin.into());
// Check if the ray intersects the mesh's AABB. It's useful to work in model space
// because we can do an AABB intersection test, instead of an OBB intersection test.
// NOTE: This is largely copied from `RayCast3d::aabb_intersection_at`.
let positive = ray_direction.signum().cmpgt(Vec3A::ZERO);
let min = Vec3A::select(positive, aabb.min, aabb.max);
let max = Vec3A::select(positive, aabb.max, aabb.min);
// Calculate the minimum/maximum time for each axis based on how much the direction goes that
// way. These values can get arbitrarily large, or even become NaN, which is handled by the
// min/max operations below
let tmin = (min - ray_origin) * ray_direction_recip;
let tmax = (max - ray_origin) * ray_direction_recip;
// An axis that is not relevant to the ray direction will be NaN. When one of the arguments
// to min/max is NaN, the other argument is used.
// An axis for which the direction is the wrong way will return an arbitrarily large
// negative value.
let tmin = tmin.max_element().max(0.0);
let tmax = tmax.min_element();
if tmin <= tmax {
Some(tmin)
} else {
None
}
}
#[cfg(test)]
mod tests {
use bevy_math::Vec3;
use bevy_transform::components::GlobalTransform;
use super::*;
// Triangle vertices to be used in a left-hand coordinate system
const V0: [f32; 3] = [1.0, -1.0, 2.0];
const V1: [f32; 3] = [1.0, 2.0, -1.0];
const V2: [f32; 3] = [1.0, -1.0, -1.0];
#[test]
fn ray_cast_triangle_mt() {
let triangle = [V0.into(), V1.into(), V2.into()];
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let result = ray_triangle_intersection(&ray, &triangle, Backfaces::Include);
assert!(result.unwrap().distance - 1.0 <= f32::EPSILON);
}
#[test]
fn ray_cast_triangle_mt_culling() {
let triangle = [V2.into(), V1.into(), V0.into()];
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let result = ray_triangle_intersection(&ray, &triangle, Backfaces::Cull);
assert!(result.is_none());
}
#[test]
fn ray_mesh_intersection_simple() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let mesh_transform = GlobalTransform::IDENTITY.compute_matrix();
let positions = &[V0, V1, V2];
let vertex_normals = None;
let indices: Option<&[u16]> = None;
let backface_culling = Backfaces::Cull;
let result = ray_mesh_intersection(
ray,
&mesh_transform,
positions,
vertex_normals,
indices,
backface_culling,
);
assert!(result.is_some());
}
#[test]
fn ray_mesh_intersection_indices() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let mesh_transform = GlobalTransform::IDENTITY.compute_matrix();
let positions = &[V0, V1, V2];
let vertex_normals = None;
let indices: Option<&[u16]> = Some(&[0, 1, 2]);
let backface_culling = Backfaces::Cull;
let result = ray_mesh_intersection(
ray,
&mesh_transform,
positions,
vertex_normals,
indices,
backface_culling,
);
assert!(result.is_some());
}
#[test]
fn ray_mesh_intersection_indices_vertex_normals() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let mesh_transform = GlobalTransform::IDENTITY.compute_matrix();
let positions = &[V0, V1, V2];
let vertex_normals: Option<&[[f32; 3]]> =
Some(&[[-1., 0., 0.], [-1., 0., 0.], [-1., 0., 0.]]);
let indices: Option<&[u16]> = Some(&[0, 1, 2]);
let backface_culling = Backfaces::Cull;
let result = ray_mesh_intersection(
ray,
&mesh_transform,
positions,
vertex_normals,
indices,
backface_culling,
);
assert!(result.is_some());
}
#[test]
fn ray_mesh_intersection_vertex_normals() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let mesh_transform = GlobalTransform::IDENTITY.compute_matrix();
let positions = &[V0, V1, V2];
let vertex_normals: Option<&[[f32; 3]]> =
Some(&[[-1., 0., 0.], [-1., 0., 0.], [-1., 0., 0.]]);
let indices: Option<&[u16]> = None;
let backface_culling = Backfaces::Cull;
let result = ray_mesh_intersection(
ray,
&mesh_transform,
positions,
vertex_normals,
indices,
backface_culling,
);
assert!(result.is_some());
}
#[test]
fn ray_mesh_intersection_missing_vertex_normals() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let mesh_transform = GlobalTransform::IDENTITY.compute_matrix();
let positions = &[V0, V1, V2];
let vertex_normals: Option<&[[f32; 3]]> = Some(&[]);
let indices: Option<&[u16]> = None;
let backface_culling = Backfaces::Cull;
let result = ray_mesh_intersection(
ray,
&mesh_transform,
positions,
vertex_normals,
indices,
backface_culling,
);
assert!(result.is_some());
}
#[test]
fn ray_mesh_intersection_indices_missing_vertex_normals() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let mesh_transform = GlobalTransform::IDENTITY.compute_matrix();
let positions = &[V0, V1, V2];
let vertex_normals: Option<&[[f32; 3]]> = Some(&[]);
let indices: Option<&[u16]> = Some(&[0, 1, 2]);
let backface_culling = Backfaces::Cull;
let result = ray_mesh_intersection(
ray,
&mesh_transform,
positions,
vertex_normals,
indices,
backface_culling,
);
assert!(result.is_some());
}
#[test]
fn ray_mesh_intersection_not_enough_indices() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let mesh_transform = GlobalTransform::IDENTITY.compute_matrix();
let positions = &[V0, V1, V2];
let vertex_normals = None;
let indices: Option<&[u16]> = Some(&[0]);
let backface_culling = Backfaces::Cull;
let result = ray_mesh_intersection(
ray,
&mesh_transform,
positions,
vertex_normals,
indices,
backface_culling,
);
assert!(result.is_none());
}
#[test]
fn ray_mesh_intersection_bad_indices() {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
let mesh_transform = GlobalTransform::IDENTITY.compute_matrix();
let positions = &[V0, V1, V2];
let vertex_normals = None;
let indices: Option<&[u16]> = Some(&[0, 1, 3]);
let backface_culling = Backfaces::Cull;
let result = ray_mesh_intersection(
ray,
&mesh_transform,
positions,
vertex_normals,
indices,
backface_culling,
);
assert!(result.is_none());
}
}

View File

@@ -0,0 +1,313 @@
//! Ray casting for meshes.
//!
//! See the [`MeshRayCast`] system parameter for more information.
mod intersections;
use bevy_derive::{Deref, DerefMut};
use bevy_math::{bounding::Aabb3d, Ray3d};
use bevy_mesh::Mesh;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use intersections::*;
pub use intersections::{ray_aabb_intersection_3d, ray_mesh_intersection, RayMeshHit};
use bevy_asset::{Assets, Handle};
use bevy_ecs::{prelude::*, system::lifetimeless::Read, system::SystemParam};
use bevy_math::FloatOrd;
use bevy_render::{prelude::*, primitives::Aabb};
use bevy_transform::components::GlobalTransform;
use tracing::*;
/// How a ray cast should handle [`Visibility`].
#[derive(Clone, Copy, Reflect)]
#[reflect(Clone)]
pub enum RayCastVisibility {
/// Completely ignore visibility checks. Hidden items can still be ray casted against.
Any,
/// Only cast rays against entities that are visible in the hierarchy. See [`Visibility`].
Visible,
/// Only cast rays against entities that are visible in the hierarchy and visible to a camera or
/// light. See [`Visibility`].
VisibleInView,
}
/// Settings for a ray cast.
#[derive(Clone)]
pub struct MeshRayCastSettings<'a> {
/// Determines how ray casting should consider [`Visibility`].
pub visibility: RayCastVisibility,
/// A predicate that is applied for every entity that ray casts are performed against.
/// Only entities that return `true` will be considered.
pub filter: &'a dyn Fn(Entity) -> bool,
/// A function that is run every time a hit is found. Ray casting will continue to check for hits
/// along the ray as long as this returns `false`.
pub early_exit_test: &'a dyn Fn(Entity) -> bool,
}
impl<'a> MeshRayCastSettings<'a> {
/// Set the filter to apply to the ray cast.
pub fn with_filter(mut self, filter: &'a impl Fn(Entity) -> bool) -> Self {
self.filter = filter;
self
}
/// Set the early exit test to apply to the ray cast.
pub fn with_early_exit_test(mut self, early_exit_test: &'a impl Fn(Entity) -> bool) -> Self {
self.early_exit_test = early_exit_test;
self
}
/// Set the [`RayCastVisibility`] setting to apply to the ray cast.
pub fn with_visibility(mut self, visibility: RayCastVisibility) -> Self {
self.visibility = visibility;
self
}
/// This ray cast should exit as soon as the nearest hit is found.
pub fn always_early_exit(self) -> Self {
self.with_early_exit_test(&|_| true)
}
/// This ray cast should check all entities whose AABB intersects the ray and return all hits.
pub fn never_early_exit(self) -> Self {
self.with_early_exit_test(&|_| false)
}
}
impl<'a> Default for MeshRayCastSettings<'a> {
fn default() -> Self {
Self {
visibility: RayCastVisibility::VisibleInView,
filter: &|_| true,
early_exit_test: &|_| true,
}
}
}
/// Determines whether backfaces should be culled or included in ray intersection tests.
///
/// By default, backfaces are culled.
#[derive(Copy, Clone, Default, Reflect)]
#[reflect(Default, Clone)]
pub enum Backfaces {
/// Cull backfaces.
#[default]
Cull,
/// Include backfaces.
Include,
}
/// Disables backface culling for [ray casts](MeshRayCast) on this entity.
#[derive(Component, Copy, Clone, Default, Reflect)]
#[reflect(Component, Default, Clone)]
pub struct RayCastBackfaces;
/// A simplified mesh component that can be used for [ray casting](super::MeshRayCast).
///
/// Consider using this component for complex meshes that don't need perfectly accurate ray casting.
#[derive(Component, Clone, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component, Debug, Clone)]
pub struct SimplifiedMesh(pub Handle<Mesh>);
type MeshFilter = Or<(With<Mesh3d>, With<Mesh2d>, With<SimplifiedMesh>)>;
/// Add this ray casting [`SystemParam`] to your system to cast rays into the world with an
/// immediate-mode API. Call `cast_ray` to immediately perform a ray cast and get a result.
///
/// Under the hood, this is a collection of regular bevy queries, resources, and local parameters
/// that are added to your system.
///
/// ## Usage
///
/// The following system casts a ray into the world with the ray positioned at the origin, pointing in
/// the X-direction, and returns a list of intersections:
///
/// ```
/// # use bevy_math::prelude::*;
/// # use bevy_picking::prelude::*;
/// fn ray_cast_system(mut ray_cast: MeshRayCast) {
/// let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
/// let hits = ray_cast.cast_ray(ray, &MeshRayCastSettings::default());
/// }
/// ```
///
/// ## Configuration
///
/// You can specify the behavior of the ray cast using [`MeshRayCastSettings`]. This allows you to filter out
/// entities, configure early-out behavior, and set whether the [`Visibility`] of an entity should be
/// considered.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_picking::prelude::*;
/// # #[derive(Component)]
/// # struct Foo;
/// fn ray_cast_system(mut ray_cast: MeshRayCast, foo_query: Query<(), With<Foo>>) {
/// let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
///
/// // Only ray cast against entities with the `Foo` component.
/// let filter = |entity| foo_query.contains(entity);
///
/// // Never early-exit. Note that you can change behavior per-entity.
/// let early_exit_test = |_entity| false;
///
/// // Ignore the visibility of entities. This allows ray casting hidden entities.
/// let visibility = RayCastVisibility::Any;
///
/// let settings = MeshRayCastSettings::default()
/// .with_filter(&filter)
/// .with_early_exit_test(&early_exit_test)
/// .with_visibility(visibility);
///
/// // Cast the ray with the settings, returning a list of intersections.
/// let hits = ray_cast.cast_ray(ray, &settings);
/// }
/// ```
#[derive(SystemParam)]
pub struct MeshRayCast<'w, 's> {
#[doc(hidden)]
pub meshes: Res<'w, Assets<Mesh>>,
#[doc(hidden)]
pub hits: Local<'s, Vec<(FloatOrd, (Entity, RayMeshHit))>>,
#[doc(hidden)]
pub output: Local<'s, Vec<(Entity, RayMeshHit)>>,
#[doc(hidden)]
pub culled_list: Local<'s, Vec<(FloatOrd, Entity)>>,
#[doc(hidden)]
pub culling_query: Query<
'w,
's,
(
Read<InheritedVisibility>,
Read<ViewVisibility>,
Read<Aabb>,
Read<GlobalTransform>,
Entity,
),
MeshFilter,
>,
#[doc(hidden)]
pub mesh_query: Query<
'w,
's,
(
Option<Read<Mesh2d>>,
Option<Read<Mesh3d>>,
Option<Read<SimplifiedMesh>>,
Has<RayCastBackfaces>,
Read<GlobalTransform>,
),
MeshFilter,
>,
}
impl<'w, 's> MeshRayCast<'w, 's> {
/// Casts the `ray` into the world and returns a sorted list of intersections, nearest first.
pub fn cast_ray(
&mut self,
ray: Ray3d,
settings: &MeshRayCastSettings,
) -> &[(Entity, RayMeshHit)] {
let ray_cull = info_span!("ray culling");
let ray_cull_guard = ray_cull.enter();
self.hits.clear();
self.culled_list.clear();
self.output.clear();
// Check all entities to see if the ray intersects the AABB. Use this to build a short list
// of entities that are in the path of the ray.
let (aabb_hits_tx, aabb_hits_rx) = crossbeam_channel::unbounded::<(FloatOrd, Entity)>();
let visibility_setting = settings.visibility;
self.culling_query.par_iter().for_each(
|(inherited_visibility, view_visibility, aabb, transform, entity)| {
let should_ray_cast = match visibility_setting {
RayCastVisibility::Any => true,
RayCastVisibility::Visible => inherited_visibility.get(),
RayCastVisibility::VisibleInView => view_visibility.get(),
};
if should_ray_cast {
if let Some(distance) = ray_aabb_intersection_3d(
ray,
&Aabb3d::new(aabb.center, aabb.half_extents),
&transform.compute_matrix(),
) {
aabb_hits_tx.send((FloatOrd(distance), entity)).ok();
}
}
},
);
*self.culled_list = aabb_hits_rx.try_iter().collect();
// Sort by the distance along the ray.
self.culled_list.sort_by_key(|(aabb_near, _)| *aabb_near);
drop(ray_cull_guard);
// Perform ray casts against the culled entities.
let mut nearest_blocking_hit = FloatOrd(f32::INFINITY);
let ray_cast_guard = debug_span!("ray_cast");
self.culled_list
.iter()
.filter(|(_, entity)| (settings.filter)(*entity))
.for_each(|(aabb_near, entity)| {
// Get the mesh components and transform.
let Ok((mesh2d, mesh3d, simplified_mesh, has_backfaces, transform)) =
self.mesh_query.get(*entity)
else {
return;
};
// Get the underlying mesh handle. One of these will always be `Some` because of the query filters.
let Some(mesh_handle) = simplified_mesh
.map(|m| &m.0)
.or(mesh3d.map(|m| &m.0).or(mesh2d.map(|m| &m.0)))
else {
return;
};
// Is it even possible the mesh could be closer than the current best?
if *aabb_near > nearest_blocking_hit {
return;
}
// Does the mesh handle resolve?
let Some(mesh) = self.meshes.get(mesh_handle) else {
return;
};
// Backfaces of 2d meshes are never culled, unlike 3d meshes.
let backfaces = match (has_backfaces, mesh2d.is_some()) {
(false, false) => Backfaces::Cull,
_ => Backfaces::Include,
};
// Perform the actual ray cast.
let _ray_cast_guard = ray_cast_guard.enter();
let transform = transform.compute_matrix();
let intersection = ray_intersection_over_mesh(mesh, &transform, ray, backfaces);
if let Some(intersection) = intersection {
let distance = FloatOrd(intersection.distance);
if (settings.early_exit_test)(*entity) && distance < nearest_blocking_hit {
// The reason we don't just return here is because right now we are
// going through the AABBs in order, but that doesn't mean that an
// AABB that starts further away can't end up with a closer hit than
// an AABB that starts closer. We need to keep checking AABBs that
// could possibly contain a nearer hit.
nearest_blocking_hit = distance.min(nearest_blocking_hit);
}
self.hits.push((distance, (*entity, intersection)));
};
});
self.hits.retain(|(dist, _)| *dist <= nearest_blocking_hit);
self.hits.sort_by_key(|(k, _)| *k);
let hits = self.hits.iter().map(|(_, (e, i))| (*e, i.to_owned()));
self.output.extend(hits);
self.output.as_ref()
}
}

359
vendor/bevy_picking/src/pointer.rs vendored Normal file
View File

@@ -0,0 +1,359 @@
//! Types and systems for pointer inputs, such as position and buttons.
//!
//! The picking system is built around the concept of a 'Pointer', which is an
//! abstract representation of a user input with a specific screen location. The cursor
//! and touch input is provided under [`crate::input`], but you can also implement
//! your own custom pointers by supplying a unique ID.
//!
//! The purpose of this module is primarily to provide a common interface that can be
//! driven by lower-level input devices and consumed by higher-level interaction systems.
use bevy_ecs::prelude::*;
use bevy_input::mouse::MouseScrollUnit;
use bevy_math::Vec2;
use bevy_platform::collections::HashMap;
use bevy_reflect::prelude::*;
use bevy_render::camera::{Camera, NormalizedRenderTarget};
use bevy_window::PrimaryWindow;
use uuid::Uuid;
use core::{fmt::Debug, ops::Deref};
use crate::backend::HitData;
/// Identifies a unique pointer entity. `Mouse` and `Touch` pointers are automatically spawned.
///
/// This component is needed because pointers can be spawned and despawned, but they need to have a
/// stable ID that persists regardless of the Entity they are associated with.
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, Component, Reflect)]
#[require(PointerLocation, PointerPress, PointerInteraction)]
#[reflect(Component, Default, Debug, Hash, PartialEq, Clone)]
pub enum PointerId {
/// The mouse pointer.
#[default]
Mouse,
/// A touch input, usually numbered by window touch events from `winit`.
Touch(u64),
/// A custom, uniquely identified pointer. Useful for mocking inputs or implementing a software
/// controlled cursor.
#[reflect(ignore, clone)]
Custom(Uuid),
}
impl PointerId {
/// Returns true if the pointer is a touch input.
pub fn is_touch(&self) -> bool {
matches!(self, PointerId::Touch(_))
}
/// Returns true if the pointer is the mouse.
pub fn is_mouse(&self) -> bool {
matches!(self, PointerId::Mouse)
}
/// Returns true if the pointer is a custom input.
pub fn is_custom(&self) -> bool {
matches!(self, PointerId::Custom(_))
}
/// Returns the touch id if the pointer is a touch input.
pub fn get_touch_id(&self) -> Option<u64> {
if let PointerId::Touch(id) = self {
Some(*id)
} else {
None
}
}
}
/// Holds a list of entities this pointer is currently interacting with, sorted from nearest to
/// farthest.
#[derive(Debug, Default, Clone, Component, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct PointerInteraction {
pub(crate) sorted_entities: Vec<(Entity, HitData)>,
}
impl PointerInteraction {
/// Returns the nearest hit entity and data about that intersection.
pub fn get_nearest_hit(&self) -> Option<&(Entity, HitData)> {
self.sorted_entities.first()
}
}
impl Deref for PointerInteraction {
type Target = Vec<(Entity, HitData)>;
fn deref(&self) -> &Self::Target {
&self.sorted_entities
}
}
/// A resource that maps each [`PointerId`] to their [`Entity`] for easy lookups.
#[derive(Debug, Clone, Default, Resource)]
pub struct PointerMap {
inner: HashMap<PointerId, Entity>,
}
impl PointerMap {
/// Get the [`Entity`] of the supplied [`PointerId`].
pub fn get_entity(&self, pointer_id: PointerId) -> Option<Entity> {
self.inner.get(&pointer_id).copied()
}
}
/// Update the [`PointerMap`] resource with the current frame's data.
pub fn update_pointer_map(pointers: Query<(Entity, &PointerId)>, mut map: ResMut<PointerMap>) {
map.inner.clear();
for (entity, id) in &pointers {
map.inner.insert(*id, entity);
}
}
/// Tracks the state of the pointer's buttons in response to [`PointerInput`] events.
#[derive(Debug, Default, Clone, Component, Reflect, PartialEq, Eq)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct PointerPress {
primary: bool,
secondary: bool,
middle: bool,
}
impl PointerPress {
/// Returns true if the primary pointer button is pressed.
#[inline]
pub fn is_primary_pressed(&self) -> bool {
self.primary
}
/// Returns true if the secondary pointer button is pressed.
#[inline]
pub fn is_secondary_pressed(&self) -> bool {
self.secondary
}
/// Returns true if the middle (tertiary) pointer button is pressed.
#[inline]
pub fn is_middle_pressed(&self) -> bool {
self.middle
}
/// Returns true if any pointer button is pressed.
#[inline]
pub fn is_any_pressed(&self) -> bool {
self.primary || self.middle || self.secondary
}
}
/// The stage of the pointer button press event
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
#[reflect(Clone, PartialEq)]
pub enum PressDirection {
/// The pointer button was just pressed
Pressed,
/// The pointer button was just released
Released,
}
/// The button that was just pressed or released
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
#[reflect(Clone, PartialEq)]
pub enum PointerButton {
/// The primary pointer button
Primary,
/// The secondary pointer button
Secondary,
/// The tertiary pointer button
Middle,
}
impl PointerButton {
/// Iterator over all buttons that a pointer can have.
pub fn iter() -> impl Iterator<Item = PointerButton> {
[Self::Primary, Self::Secondary, Self::Middle].into_iter()
}
}
/// Component that tracks a pointer's current [`Location`].
#[derive(Debug, Default, Clone, Component, Reflect, PartialEq)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct PointerLocation {
/// The [`Location`] of the pointer. Note that a location is both the target, and the position
/// on the target.
#[reflect(ignore, clone)]
pub location: Option<Location>,
}
impl PointerLocation {
///Returns a [`PointerLocation`] associated with the given location
pub fn new(location: Location) -> Self {
Self {
location: Some(location),
}
}
/// Returns `Some(&`[`Location`]`)` if the pointer is active, or `None` if the pointer is
/// inactive.
pub fn location(&self) -> Option<&Location> {
self.location.as_ref()
}
}
/// The location of a pointer, including the current [`NormalizedRenderTarget`], and the x/y
/// position of the pointer on this render target.
///
/// Note that:
/// - a pointer can move freely between render targets
/// - a pointer is not associated with a [`Camera`] because multiple cameras can target the same
/// render target. It is up to picking backends to associate a Pointer's `Location` with a
/// specific `Camera`, if any.
#[derive(Debug, Clone, Component, Reflect, PartialEq)]
#[reflect(Component, Debug, PartialEq, Clone)]
pub struct Location {
/// The [`NormalizedRenderTarget`] associated with the pointer, usually a window.
pub target: NormalizedRenderTarget,
/// The position of the pointer in the `target`.
pub position: Vec2,
}
impl Location {
/// Returns `true` if this pointer's [`Location`] is within the [`Camera`]'s viewport.
///
/// Note this returns `false` if the location and camera have different render targets.
#[inline]
pub fn is_in_viewport(
&self,
camera: &Camera,
primary_window: &Query<Entity, With<PrimaryWindow>>,
) -> bool {
if camera
.target
.normalize(Some(match primary_window.single() {
Ok(w) => w,
Err(_) => return false,
}))
.as_ref()
!= Some(&self.target)
{
return false;
}
camera
.logical_viewport_rect()
.is_some_and(|rect| rect.contains(self.position))
}
}
/// Event sent to drive a pointer.
#[derive(Debug, Clone, Copy, Reflect)]
#[reflect(Clone)]
pub enum PointerAction {
/// Causes the pointer to press a button.
Press(PointerButton),
/// Causes the pointer to release a button.
Release(PointerButton),
/// Move the pointer.
Move {
/// How much the pointer moved from the previous position.
delta: Vec2,
},
/// Scroll the pointer
Scroll {
/// The mouse scroll unit.
unit: MouseScrollUnit,
/// The horizontal scroll value.
x: f32,
/// The vertical scroll value.
y: f32,
},
/// Cancel the pointer. Often used for touch events.
Cancel,
}
/// An input event effecting a pointer.
#[derive(Event, Debug, Clone, Reflect)]
#[reflect(Clone)]
pub struct PointerInput {
/// The id of the pointer.
pub pointer_id: PointerId,
/// The location of the pointer. For [`PointerAction::Move`], this is the location after the movement.
pub location: Location,
/// The action that the event describes.
pub action: PointerAction,
}
impl PointerInput {
/// Creates a new pointer input event.
///
/// Note that `location` refers to the position of the pointer *after* the event occurred.
pub fn new(pointer_id: PointerId, location: Location, action: PointerAction) -> PointerInput {
PointerInput {
pointer_id,
location,
action,
}
}
/// Returns true if the `target_button` of this pointer was just pressed.
#[inline]
pub fn button_just_pressed(&self, target_button: PointerButton) -> bool {
if let PointerAction::Press(button) = self.action {
button == target_button
} else {
false
}
}
/// Returns true if the `target_button` of this pointer was just released.
#[inline]
pub fn button_just_released(&self, target_button: PointerButton) -> bool {
if let PointerAction::Release(button) = self.action {
button == target_button
} else {
false
}
}
/// Updates pointer entities according to the input events.
pub fn receive(
mut events: EventReader<PointerInput>,
mut pointers: Query<(&PointerId, &mut PointerLocation, &mut PointerPress)>,
) {
for event in events.read() {
match event.action {
PointerAction::Press(button) => {
pointers
.iter_mut()
.for_each(|(pointer_id, _, mut pointer)| {
if *pointer_id == event.pointer_id {
match button {
PointerButton::Primary => pointer.primary = true,
PointerButton::Secondary => pointer.secondary = true,
PointerButton::Middle => pointer.middle = true,
}
}
});
}
PointerAction::Release(button) => {
pointers
.iter_mut()
.for_each(|(pointer_id, _, mut pointer)| {
if *pointer_id == event.pointer_id {
match button {
PointerButton::Primary => pointer.primary = false,
PointerButton::Secondary => pointer.secondary = false,
PointerButton::Middle => pointer.middle = false,
}
}
});
}
PointerAction::Move { .. } => {
pointers.iter_mut().for_each(|(id, mut pointer, _)| {
if *id == event.pointer_id {
pointer.location = Some(event.location.to_owned());
}
});
}
_ => {}
}
}
}
}

49
vendor/bevy_picking/src/window.rs vendored Normal file
View File

@@ -0,0 +1,49 @@
//! This module contains a basic backend that implements picking for window
//! entities.
//!
//! Pointers can exist on windows, images, and gpu texture views. With
//! [`update_window_hits`] enabled, when a pointer hovers over a window that
//! window will be inserted as a pointer hit, listed behind all other pointer
//! hits. This means that when the pointer isn't hovering any other entities,
//! the picking events will be routed to the window.
//!
//! ## Implementation Notes
//!
//! - This backend does not provide `position` or `normal` in `HitData`.
use core::f32;
use bevy_ecs::prelude::*;
use bevy_render::camera::NormalizedRenderTarget;
use crate::{
backend::{HitData, PointerHits},
pointer::{Location, PointerId, PointerLocation},
};
/// Generates pointer hit events for window entities.
///
/// A pointer is treated as hitting a window when it is located on that window. The order
/// of the hit event is negative infinity, meaning it should appear behind all other entities.
///
/// The depth of the hit will be listed as zero.
pub fn update_window_hits(
pointers: Query<(&PointerId, &PointerLocation)>,
mut output_events: EventWriter<PointerHits>,
) {
for (pointer_id, pointer_location) in pointers.iter() {
if let Some(Location {
target: NormalizedRenderTarget::Window(window_ref),
..
}) = pointer_location.location
{
let entity = window_ref.entity();
let hit_data = HitData::new(entity, 0.0, None, None);
output_events.write(PointerHits::new(
*pointer_id,
vec![(entity, hit_data)],
f32::NEG_INFINITY,
));
}
}
}