use bevy::{
ecs::system::{lifetimeless::Read, SystemParam},
prelude::*,
reflect::TypePath,
render::primitives::Aabb,
sprite::Mesh2dHandle,
utils::FloatOrd,
};
use crate::{
ray_intersection_over_mesh, Backfaces, IntersectionData, NoBackfaceCulling, Ray3d, RaycastMesh,
RaycastSource, SimplifiedMesh,
};
#[derive(Clone, Copy, Reflect)]
pub enum RaycastVisibility {
Ignore,
MustBeVisible,
MustBeVisibleAndInView,
}
#[derive(Clone, Reflect)]
pub struct RaycastSettings {
pub visibility: RaycastVisibility,
pub should_early_exit: bool,
}
impl<T: TypePath> From<&RaycastSource<T>> for RaycastSettings {
fn from(value: &RaycastSource<T>) -> Self {
Self {
visibility: value.visibility,
should_early_exit: value.should_early_exit,
}
}
}
impl Default for RaycastSettings {
fn default() -> Self {
Self {
visibility: RaycastVisibility::MustBeVisibleAndInView,
should_early_exit: true,
}
}
}
#[derive(SystemParam)]
pub struct Raycast<'w, 's, T: TypePath + Send + Sync> {
pub meshes: Res<'w, Assets<Mesh>>,
pub hits: Local<'s, Vec<(FloatOrd, (Entity, IntersectionData))>>,
pub output: Local<'s, Vec<(Entity, IntersectionData)>>,
pub culled_list: Local<'s, Vec<(FloatOrd, Entity)>>,
pub culling_query: Query<
'w,
's,
(
Read<ComputedVisibility>,
Read<Aabb>,
Read<GlobalTransform>,
Entity,
),
With<RaycastMesh<T>>,
>,
pub mesh_query: Query<
'w,
's,
(
Read<Handle<Mesh>>,
Option<Read<SimplifiedMesh>>,
Option<Read<NoBackfaceCulling>>,
Read<GlobalTransform>,
),
With<RaycastMesh<T>>,
>,
#[cfg(feature = "2d")]
pub mesh2d_query: Query<
'w,
's,
(
Read<Mesh2dHandle>,
Option<Read<SimplifiedMesh>>,
Read<GlobalTransform>,
),
With<RaycastMesh<T>>,
>,
}
impl<'w, 's, T: TypePath + Send + Sync> Raycast<'w, 's, T> {
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)>();
self.culling_query
.par_iter()
.for_each(|(visibility, aabb, transform, entity)| {
let should_raycast = match settings.visibility {
RaycastVisibility::Ignore => true,
RaycastVisibility::MustBeVisible => visibility.is_visible_in_hierarchy(),
RaycastVisibility::MustBeVisibleAndInView => visibility.is_visible_in_view(),
};
if should_raycast {
if let Some([near, _]) = ray
.intersects_aabb(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_hit = FloatOrd(f32::INFINITY);
let raycast_guard = info_span!("raycast");
self.culled_list.iter().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 settings.should_early_exit && *aabb_near > nearest_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.should_early_exit && distance < nearest_hit {
nearest_hit = distance.min(nearest_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.sort_by_key(|(k, _)| *k);
if settings.should_early_exit && self.hits.len() > 1 {
self.hits.drain(1..);
}
let hits = self.hits.iter().map(|(_, (e, i))| (*e, i.to_owned()));
*self.output = hits.collect();
self.output.as_ref()
}
}