mod intersections;
use bevy_derive::{Deref, DerefMut};
use bevy_camera::{
primitives::Aabb,
visibility::{InheritedVisibility, ViewVisibility},
};
use bevy_math::{bounding::Aabb3d, Ray3d};
use bevy_mesh::{Mesh, Mesh2d, Mesh3d};
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_transform::components::GlobalTransform;
use tracing::*;
#[derive(Clone, Copy, Reflect)]
#[reflect(Clone)]
pub enum RayCastVisibility {
Any,
Visible,
VisibleInView,
}
#[derive(Clone)]
pub struct MeshRayCastSettings<'a> {
pub visibility: RayCastVisibility,
pub filter: &'a dyn Fn(Entity) -> bool,
pub early_exit_test: &'a dyn Fn(Entity) -> bool,
}
impl<'a> MeshRayCastSettings<'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 MeshRayCastSettings<'a> {
fn default() -> Self {
Self {
visibility: RayCastVisibility::VisibleInView,
filter: &|_| true,
early_exit_test: &|_| true,
}
}
}
#[derive(Copy, Clone, Default, Reflect)]
#[reflect(Default, Clone)]
pub enum Backfaces {
#[default]
Cull,
Include,
}
#[derive(Component, Copy, Clone, Default, Reflect)]
#[reflect(Component, Default, Clone)]
pub struct RayCastBackfaces;
#[derive(Component, FromTemplate, Clone, Debug, Deref, DerefMut, Reflect)]
#[reflect(Component, Debug, Clone)]
pub struct SimplifiedMesh(pub Handle<Mesh>);
type MeshFilter = Or<(With<Mesh3d>, With<Mesh2d>, With<SimplifiedMesh>)>;
#[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> {
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();
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
&& let Some(distance) = ray_aabb_intersection_3d(
ray,
&Aabb3d::new(aabb.center, aabb.half_extents),
&transform.affine(),
)
{
aabb_hits_tx.send((FloatOrd(distance), 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 ray_cast_guard = debug_span!("ray_cast");
self.culled_list
.iter()
.filter(|(_, entity)| (settings.filter)(*entity))
.for_each(|(aabb_near, entity)| {
let Ok((mesh2d, mesh3d, simplified_mesh, has_backfaces, transform)) =
self.mesh_query.get(*entity)
else {
return;
};
let Some(mesh_handle) = simplified_mesh
.map(|m| &m.0)
.or(mesh3d.map(|m| &m.0).or(mesh2d.map(|m| &m.0)))
else {
return;
};
if *aabb_near > nearest_blocking_hit {
return;
}
let Some(mesh) = self.meshes.get(mesh_handle) else {
return;
};
let backfaces = match (has_backfaces, mesh2d.is_some()) {
(false, false) => Backfaces::Cull,
_ => Backfaces::Include,
};
let _ray_cast_guard = ray_cast_guard.enter();
let transform = transform.affine();
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)));
};
});
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()
}
}