use bevy_asset::{Assets, Handle};
use bevy_ecs::{prelude::*, system::lifetimeless::Read, system::SystemParam};
use bevy_math::{FloatOrd, Ray3d};
use bevy_reflect::Reflect;
use bevy_render::{prelude::*, primitives::Aabb};
use bevy_transform::components::GlobalTransform;
use bevy_utils::tracing::*;
#[cfg(feature = "debug")]
use {
bevy_gizmos::gizmos::Gizmos,
bevy_math::{Quat, Vec3},
};
use crate::prelude::*;
#[derive(Clone, Copy, Reflect)]
pub enum RaycastVisibility {
Ignore,
MustBeVisible,
MustBeVisibleAndInView,
}
#[derive(Clone)]
pub struct RaycastSettings<'a> {
pub visibility: RaycastVisibility,
pub filter: &'a dyn Fn(Entity) -> bool,
pub early_exit_test: &'a dyn Fn(Entity) -> bool,
}
impl<'a> RaycastSettings<'a> {
pub fn with_filter(mut self, filter: &'a impl Fn(Entity) -> bool) -> Self {
self.filter = filter;
self
}
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
}
pub fn with_visibility(mut self, visibility: RaycastVisibility) -> Self {
self.visibility = visibility;
self
}
pub fn always_early_exit(self) -> Self {
self.with_early_exit_test(&|_| true)
}
pub fn never_early_exit(self) -> Self {
self.with_early_exit_test(&|_| false)
}
}
impl<'a> Default for RaycastSettings<'a> {
fn default() -> Self {
Self {
visibility: RaycastVisibility::MustBeVisibleAndInView,
filter: &|_| true,
early_exit_test: &|_| true,
}
}
}
#[cfg(feature = "2d")]
type MeshFilter = Or<(With<Handle<Mesh>>, With<bevy_sprite::Mesh2dHandle>)>;
#[cfg(not(feature = "2d"))]
type MeshFilter = With<Handle<Mesh>>;
#[derive(SystemParam)]
pub struct Raycast<'w, 's> {
#[doc(hidden)]
pub meshes: Res<'w, Assets<Mesh>>,
#[doc(hidden)]
pub hits: Local<'s, Vec<(FloatOrd, (Entity, IntersectionData))>>,
#[doc(hidden)]
pub output: Local<'s, Vec<(Entity, IntersectionData)>>,
#[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,
(
Read<Handle<Mesh>>,
Option<Read<SimplifiedMesh>>,
Option<Read<NoBackfaceCulling>>,
Read<GlobalTransform>,
),
>,
#[cfg(feature = "2d")]
#[doc(hidden)]
pub mesh2d_query: Query<
'w,
's,
(
Read<bevy_sprite::Mesh2dHandle>,
Option<Read<SimplifiedMesh>>,
Read<GlobalTransform>,
),
>,
}
impl<'w, 's> Raycast<'w, 's> {
#[cfg(feature = "debug")]
pub fn debug_cast_ray(
&mut self,
ray: Ray3d,
settings: &RaycastSettings,
gizmos: &mut Gizmos,
) -> &[(Entity, IntersectionData)] {
use bevy_color::palettes::css;
use bevy_math::Dir3;
let orientation = Quat::from_rotation_arc(Vec3::NEG_Z, *ray.direction);
gizmos.ray(ray.origin, *ray.direction, css::BLUE);
gizmos.sphere(ray.origin, orientation, 0.1, css::BLUE);
let hits = self.cast_ray(ray, settings);
for (is_first, intersection) in hits
.iter()
.map(|i| i.1.clone())
.enumerate()
.map(|(i, hit)| (i == 0, hit))
{
let color = match is_first {
true => css::GREEN,
false => css::PINK,
};
gizmos.ray(intersection.position(), intersection.normal(), color);
gizmos.circle(
intersection.position(),
Dir3::new_unchecked(intersection.normal().normalize()),
0.1,
color,
);
}
if let Some(hit) = hits.first() {
debug!("{:?}", hit);
}
hits
}
pub fn cast_ray(
&mut self,
ray: Ray3d,
settings: &RaycastSettings,
) -> &[(Entity, IntersectionData)] {
let ray_cull = info_span!("ray culling");
let ray_cull_guard = ray_cull.enter();
self.hits.clear();
self.culled_list.clear();
self.output.clear();
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_raycast = match visibility_setting {
RaycastVisibility::Ignore => true,
RaycastVisibility::MustBeVisible => inherited_visibility.get(),
RaycastVisibility::MustBeVisibleAndInView => view_visibility.get(),
};
if should_raycast {
if let Some([near, _]) = intersects_aabb(ray, aabb, &transform.compute_matrix())
.filter(|[_, far]| *far >= 0.0)
{
aabb_hits_tx.send((FloatOrd(near), entity)).ok();
}
}
},
);
*self.culled_list = aabb_hits_rx.try_iter().collect();
self.culled_list.sort_by_key(|(aabb_near, _)| *aabb_near);
drop(ray_cull_guard);
let mut nearest_blocking_hit = FloatOrd(f32::INFINITY);
let raycast_guard = debug_span!("raycast");
self.culled_list
.iter()
.filter(|(_, entity)| (settings.filter)(*entity))
.for_each(|(aabb_near, entity)| {
let mut raycast_mesh =
|mesh_handle: &Handle<Mesh>,
simplified_mesh: Option<&SimplifiedMesh>,
no_backface_culling: Option<&NoBackfaceCulling>,
transform: &GlobalTransform| {
if *aabb_near > nearest_blocking_hit {
return;
}
let mesh_handle = simplified_mesh.map(|m| &m.mesh).unwrap_or(mesh_handle);
let Some(mesh) = self.meshes.get(mesh_handle) else {
return;
};
let _raycast_guard = raycast_guard.enter();
let backfaces = match no_backface_culling {
Some(_) => Backfaces::Include,
None => Backfaces::Cull,
};
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
{
nearest_blocking_hit = distance.min(nearest_blocking_hit);
}
self.hits.push((distance, (*entity, intersection)));
};
};
if let Ok((mesh, simp_mesh, culling, transform)) = self.mesh_query.get(*entity) {
raycast_mesh(mesh, simp_mesh, culling, transform);
}
#[cfg(feature = "2d")]
if let Ok((mesh, simp_mesh, transform)) = self.mesh2d_query.get(*entity) {
raycast_mesh(&mesh.0, simp_mesh, Some(&NoBackfaceCulling), transform);
}
});
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 = hits.collect();
self.output.as_ref()
}
}