mod bounding;
mod debug;
mod primitives;
mod raycast;
pub use crate::bounding::{update_bound_sphere, BoundVol, BoundingSphere};
pub use crate::debug::*;
pub use crate::primitives::*;
use crate::raycast::*;
use bevy::{
prelude::*,
render::{
camera::Camera,
mesh::{Indices, Mesh, VertexAttributeValues},
pipeline::PrimitiveTopology,
},
};
use std::marker::PhantomData;
pub struct DefaultRaycastingPlugin<T: 'static + Send + Sync>(pub PhantomData<T>);
impl<T: 'static + Send + Sync> Plugin for DefaultRaycastingPlugin<T> {
fn build(&self, app: &mut AppBuilder) {
app.init_resource::<PluginState<T>>()
.add_system_to_stage(
CoreStage::PostUpdate,
build_rays::<T>.system().label(RaycastSystem::BuildRays),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_raycast::<T>
.system()
.label(RaycastSystem::UpdateRaycast)
.after(RaycastSystem::BuildRays),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_debug_cursor::<T>
.system()
.label(RaycastSystem::UpdateDebugCursor)
.after(RaycastSystem::UpdateRaycast),
);
}
}
impl<T: 'static + Send + Sync> Default for DefaultRaycastingPlugin<T> {
fn default() -> Self {
DefaultRaycastingPlugin(PhantomData::<T>)
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum RaycastSystem {
BuildRays,
UpdateRaycast,
UpdateDebugCursor,
}
pub struct PluginState<T> {
pub enabled: bool,
_marker: PhantomData<T>,
}
impl<T> Default for PluginState<T> {
fn default() -> Self {
PluginState {
enabled: true,
_marker: PhantomData::<T>::default(),
}
}
}
#[derive(Debug)]
pub struct RayCastMesh<T> {
_marker: PhantomData<T>,
}
impl<T> Default for RayCastMesh<T> {
fn default() -> Self {
RayCastMesh {
_marker: PhantomData::default(),
}
}
}
pub struct RayCastSource<T> {
pub cast_method: RayCastMethod,
ray: Option<Ray3d>,
intersections: Vec<(Entity, Intersection)>,
_marker: PhantomData<T>,
}
impl<T> Default for RayCastSource<T> {
fn default() -> Self {
RayCastSource {
cast_method: RayCastMethod::Screenspace(Vec2::ZERO),
ray: None,
intersections: Vec::new(),
_marker: PhantomData::default(),
}
}
}
impl<T> RayCastSource<T> {
pub fn new() -> RayCastSource<T> {
RayCastSource::default()
}
pub fn with_ray_screenspace(
&self,
cursor_pos_screen: Vec2,
windows: &Res<Windows>,
camera: &Camera,
camera_transform: &GlobalTransform,
) -> Self {
RayCastSource {
cast_method: RayCastMethod::Screenspace(cursor_pos_screen),
ray: Ray3d::from_screenspace(cursor_pos_screen, windows, camera, camera_transform),
intersections: self.intersections.clone(),
_marker: self._marker,
}
}
pub fn with_ray_transform(&self, transform: Mat4) -> Self {
RayCastSource {
cast_method: RayCastMethod::Transform,
ray: Some(Ray3d::from_transform(transform)),
intersections: self.intersections.clone(),
_marker: self._marker,
}
}
pub fn new_screenspace(
cursor_pos_screen: Vec2,
windows: &Res<Windows>,
camera: &Camera,
camera_transform: &GlobalTransform,
) -> Self {
RayCastSource::new().with_ray_screenspace(
cursor_pos_screen,
windows,
camera,
camera_transform,
)
}
pub fn new_transform(transform: Mat4) -> Self {
RayCastSource::new().with_ray_transform(transform)
}
pub fn new_transform_empty() -> Self {
RayCastSource {
cast_method: RayCastMethod::Transform,
..Default::default()
}
}
pub fn intersect_list(&self) -> Option<&Vec<(Entity, Intersection)>> {
if self.intersections.is_empty() {
None
} else {
Some(&self.intersections)
}
}
pub fn intersect_top(&self) -> Option<(Entity, Intersection)> {
if self.intersections.is_empty() {
None
} else {
self.intersections.first().copied()
}
}
pub fn intersect_primitive(&self, shape: Primitive3d) -> Option<Intersection> {
let ray = self.ray?;
match shape {
Primitive3d::Plane {
point: plane_origin,
normal: plane_normal,
} => {
let denominator = ray.direction().dot(plane_normal);
if denominator > f32::EPSILON {
let point_to_point = plane_origin - ray.origin();
let intersect_dist = plane_normal.dot(point_to_point) / denominator;
let intersect_position = ray.direction() * intersect_dist + ray.origin();
Some(Intersection::new(
Ray3d::new(intersect_position, plane_normal),
intersect_dist,
None,
))
} else {
None
}
}
}
}
pub fn ray(&self) -> Option<Ray3d> {
self.ray
}
}
pub enum RayCastMethod {
Screenspace(Vec2),
Transform,
}
#[allow(clippy::type_complexity)]
pub fn build_rays<T: 'static + Send + Sync>(
windows: Res<Windows>,
mut pick_source_query: Query<(
&mut RayCastSource<T>,
Option<&GlobalTransform>,
Option<&Camera>,
)>,
) {
for (mut pick_source, transform, camera) in &mut pick_source_query.iter_mut() {
pick_source.ray = match &mut pick_source.cast_method {
RayCastMethod::Screenspace(cursor_pos_screen) => {
let camera = match camera {
Some(camera) => camera,
None => {
error!(
"The PickingSource is a CameraScreenSpace but has no associated Camera component"
);
return;
}
};
let camera_transform = match transform {
Some(transform) => transform,
None => {
error!(
"The PickingSource is a CameraScreenSpace but has no associated GlobalTransform component"
);
return;
}
};
Ray3d::from_screenspace(*cursor_pos_screen, &windows, camera, camera_transform)
}
RayCastMethod::Transform => {
let transform = match transform {
Some(matrix) => matrix,
None => {
error!(
"The PickingSource is a Transform but has no associated GlobalTransform component"
);
return
}
}
.compute_matrix();
Some(Ray3d::from_transform(transform))
}
};
}
}
#[allow(clippy::type_complexity)]
pub fn update_raycast<T: 'static + Send + Sync>(
state: Res<PluginState<T>>,
meshes: Res<Assets<Mesh>>,
mut pick_source_query: Query<&mut RayCastSource<T>>,
culling_query: Query<
(&Visible, Option<&BoundVol>, &GlobalTransform, Entity),
With<RayCastMesh<T>>,
>,
mesh_query: Query<(&Handle<Mesh>, &GlobalTransform, Entity), With<RayCastMesh<T>>>,
) {
if !state.enabled {
return;
}
for mut pick_source in pick_source_query.iter_mut() {
if let Some(ray) = pick_source.ray {
pick_source.intersections.clear();
let ray_cull = info_span!("ray culling");
let raycast = info_span!("raycast");
let culled_list: Vec<Entity> = {
let _ray_cull_guard = ray_cull.enter();
culling_query
.iter()
.map(|(visibility, bound_vol, transform, entity)| {
let visible = visibility.is_visible;
let bound_hit = if let Some(bound_vol) = bound_vol {
if let Some(sphere) = &bound_vol.sphere {
let scaled_radius: f32 =
1.01 * sphere.radius() * transform.scale.max_element();
let translated_origin =
sphere.origin() * transform.scale + transform.translation;
let det = (ray.direction().dot(ray.origin() - translated_origin))
.powi(2)
- (Vec3::length_squared(ray.origin() - translated_origin)
- scaled_radius.powi(2));
det >= 0.0
} else {
true
}
} else {
true
};
if visible && bound_hit {
Some(entity)
} else {
None
}
})
.filter_map(|value| value)
.collect()
};
let mut picks = mesh_query
.iter()
.filter(|(_mesh_handle, _transform, entity)| culled_list.contains(&entity))
.filter_map(|(mesh_handle, transform, entity)| {
let _raycast_guard = raycast.enter();
if let Some(mesh) = meshes.get(mesh_handle) {
if mesh.primitive_topology() != PrimitiveTopology::TriangleList {
error!("bevy_mod_picking only supports TriangleList mesh topology");
}
let vertex_positions: &Vec<[f32; 3]> =
match mesh.attribute(Mesh::ATTRIBUTE_POSITION) {
None => panic!("Mesh does not contain vertex positions"),
Some(vertex_values) => match &vertex_values {
VertexAttributeValues::Float3(positions) => positions,
_ => panic!("Unexpected vertex types in ATTRIBUTE_POSITION"),
},
};
if let Some(indices) = &mesh.indices() {
let mesh_to_world = transform.compute_matrix();
let new_intersection = match indices {
Indices::U16(vector) => ray_mesh_intersection(
&mesh_to_world,
vertex_positions,
&ray,
&vector.iter().map(|x| *x as u32).collect(),
),
Indices::U32(vector) => ray_mesh_intersection(
&mesh_to_world,
vertex_positions,
&ray,
vector,
),
};
if let Some(new_intersection) = new_intersection {
Some((entity, new_intersection))
} else {
None
}
} else {
panic!(
"No index matrix found in mesh {:?}\n{:?}",
mesh_handle, mesh
);
}
} else {
None
}
})
.collect::<Vec<(Entity, Intersection)>>();
picks.sort_by(|a, b| {
a.1.distance()
.partial_cmp(&b.1.distance())
.unwrap_or(std::cmp::Ordering::Equal)
});
pick_source.intersections = picks;
}
}
}
#[allow(clippy::ptr_arg)]
fn ray_mesh_intersection(
mesh_to_world: &Mat4,
vertex_positions: &[[f32; 3]],
pick_ray: &Ray3d,
indices: &Vec<u32>,
) -> Option<Intersection> {
let mut min_pick_distance_squared = f32::MAX;
let mut pick_intersection = None;
if indices.len() % 3 == 0 {
for index in indices.chunks(3) {
let mut world_vertices: [Vec3; 3] = [Vec3::ZERO, Vec3::ZERO, Vec3::ZERO];
for i in 0..3 {
let vertex_index = index[i] as usize;
world_vertices[i] =
mesh_to_world.project_point3(Vec3::from(vertex_positions[vertex_index]));
}
if world_vertices
.iter()
.map(|vert| (*vert - pick_ray.origin()).length_squared().abs())
.fold(f32::INFINITY, |a, b| a.min(b))
> min_pick_distance_squared
{
continue;
}
let world_triangle = Triangle::from(world_vertices);
if let Some(intersection) =
ray_triangle_intersection(pick_ray, &world_triangle, RaycastAlgorithm::default())
{
let distance: f32 = (intersection.origin() - pick_ray.origin())
.length_squared()
.abs();
if distance < min_pick_distance_squared {
min_pick_distance_squared = distance;
pick_intersection = Some(Intersection::new(
intersection,
distance,
Some(world_triangle),
));
}
}
}
}
pick_intersection
}