use rapier3d::prelude as rp;
use super::{CollisionGroups, Physics3D, from_rp, pose_to_rp, to_rp};
use crate::{Entity, Quat, Vec3};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Hit3D {
pub entity: Entity,
pub point: Vec3,
pub normal: Vec3,
pub distance: f32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct QueryFilter3D {
pub exclude: Option<Entity>,
pub groups: CollisionGroups,
pub sensors: bool,
}
impl QueryFilter3D {
#[must_use]
pub fn exclude(self, entity: Entity) -> Self {
Self {
exclude: Some(entity),
..self
}
}
#[must_use]
pub fn groups(self, groups: CollisionGroups) -> Self {
Self { groups, ..self }
}
#[must_use]
pub fn with_sensors(self) -> Self {
Self { sensors: true, ..self }
}
}
impl Physics3D {
fn rp_filter(&self, f: QueryFilter3D) -> rp::QueryFilter<'static> {
let mut filter = rp::QueryFilter::default().groups(f.groups.to_rp());
if !f.sensors {
filter = filter.exclude_sensors();
}
if let Some(link) = f.exclude.and_then(|e| self.link(e)) {
filter = filter.exclude_rigid_body(link.body);
}
filter
}
pub fn cast_ray(&self, origin: Vec3, direction: Vec3, max_distance: f32, filter: QueryFilter3D) -> Option<Hit3D> {
let dir = direction.normalize_or_zero();
if dir == Vec3::ZERO || !origin.is_finite() {
return None;
}
let ray = rp::Ray::new(to_rp(origin), to_rp(dir));
let (handle, hit) = self
.world
.cast_ray_and_get_normal(&ray, max_distance, true, self.rp_filter(filter))?;
Some(Hit3D {
entity: self.entity_of(handle)?,
point: origin + dir * hit.time_of_impact,
normal: from_rp(hit.normal),
distance: hit.time_of_impact,
})
}
pub fn cast_ray_all(
&self,
origin: Vec3,
direction: Vec3,
max_distance: f32,
filter: QueryFilter3D,
out: &mut Vec<Hit3D>,
) {
out.clear();
let dir = direction.normalize_or_zero();
if dir == Vec3::ZERO || !origin.is_finite() {
return;
}
let ray = rp::Ray::new(to_rp(origin), to_rp(dir));
for (handle, _, hit) in self
.world
.intersect_ray(ray, max_distance, true, self.rp_filter(filter))
{
if let Some(entity) = self.entity_of(handle) {
out.push(Hit3D {
entity,
point: origin + dir * hit.time_of_impact,
normal: from_rp(hit.normal),
distance: hit.time_of_impact,
});
}
}
out.sort_unstable_by(|a, b| a.distance.total_cmp(&b.distance));
}
fn cast(
&self,
shape: &dyn rp::Shape,
pose: rp::Pose,
direction: Vec3,
max_distance: f32,
filter: QueryFilter3D,
) -> Option<Hit3D> {
let dir = direction.normalize_or_zero();
if dir == Vec3::ZERO {
return None;
}
let options = rapier3d::parry::query::ShapeCastOptions {
max_time_of_impact: max_distance,
target_distance: 0.0,
stop_at_penetration: true,
compute_impact_geometry_on_penetration: true,
};
let (handle, hit) = self
.world
.cast_shape(&pose, to_rp(dir), shape, options, self.rp_filter(filter))?;
Some(Hit3D {
entity: self.entity_of(handle)?,
point: from_rp(hit.witness1),
normal: from_rp(hit.normal1),
distance: hit.time_of_impact,
})
}
pub fn cast_sphere(
&self,
origin: Vec3,
radius: f32,
direction: Vec3,
max_distance: f32,
filter: QueryFilter3D,
) -> Option<Hit3D> {
let ball = rp::Ball::new(radius.max(f32::EPSILON));
self.cast(
&ball,
pose_to_rp(origin, Quat::IDENTITY),
direction,
max_distance,
filter,
)
}
pub fn cast_capsule(
&self,
origin: Vec3,
height: f32,
radius: f32,
direction: Vec3,
max_distance: f32,
filter: QueryFilter3D,
) -> Option<Hit3D> {
let capsule = rp::Capsule::new_y((height * 0.5).max(0.0), radius.max(f32::EPSILON));
self.cast(
&capsule,
pose_to_rp(origin, Quat::IDENTITY),
direction,
max_distance,
filter,
)
}
fn overlap(&self, shape: &dyn rp::Shape, pose: rp::Pose, filter: QueryFilter3D, out: &mut Vec<Entity>) {
out.clear();
for (handle, _) in self.world.intersect_shape(pose, shape, self.rp_filter(filter)) {
out.extend(self.entity_of(handle));
}
}
pub fn overlap_sphere(&self, center: Vec3, radius: f32, filter: QueryFilter3D, out: &mut Vec<Entity>) {
let ball = rp::Ball::new(radius.max(f32::EPSILON));
self.overlap(&ball, pose_to_rp(center, Quat::IDENTITY), filter, out);
}
pub fn overlap_box(&self, center: Vec3, size: Vec3, rotation: Quat, filter: QueryFilter3D, out: &mut Vec<Entity>) {
let h = (size * 0.5).max(Vec3::splat(f32::EPSILON));
let cuboid = rp::Cuboid::new(rp::Vector::new(h.x, h.y, h.z));
self.overlap(&cuboid, pose_to_rp(center, rotation), filter, out);
}
pub fn overlap_capsule(
&self,
center: Vec3,
height: f32,
radius: f32,
filter: QueryFilter3D,
out: &mut Vec<Entity>,
) {
let capsule = rp::Capsule::new_y((height * 0.5).max(0.0), radius.max(f32::EPSILON));
self.overlap(&capsule, pose_to_rp(center, Quat::IDENTITY), filter, out);
}
}