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,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()
}
}