use crate::{collider_tree::ColliderTrees, collision::collider::contact_query, prelude::*};
use bevy::{ecs::system::SystemParam, prelude::*};
use parry::query::ShapeCastOptions;
#[derive(SystemParam)]
pub struct SpatialQuery<'w, 's> {
colliders: Query<'w, 's, (&'static Position, &'static Rotation, &'static Collider)>,
aabbs: Query<'w, 's, &'static ColliderAabb>,
collider_trees: Res<'w, ColliderTrees>,
}
impl SpatialQuery<'_, '_> {
pub fn cast_ray(
&self,
origin: Vector,
direction: Dir,
max_distance: Scalar,
solid: bool,
filter: &SpatialQueryFilter,
) -> Option<RayHitData> {
self.cast_ray_predicate(origin, direction, max_distance, solid, filter, &|_| true)
}
pub fn cast_ray_predicate(
&self,
origin: Vector,
direction: Dir,
mut max_distance: Scalar,
solid: bool,
filter: &SpatialQueryFilter,
predicate: &dyn Fn(Entity) -> bool,
) -> Option<RayHitData> {
let ray = Ray::new(origin.f32(), direction);
let mut closest_hit: Option<RayHitData> = None;
self.collider_trees.iter_trees().for_each(|tree| {
tree.ray_traverse_closest(ray, max_distance, |proxy_id| {
let proxy = tree.get_proxy(proxy_id).unwrap();
if !filter.test(proxy.collider, proxy.layers) || !predicate(proxy.collider) {
return Scalar::MAX;
}
let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
return Scalar::MAX;
};
let Some((distance, normal)) = collider.cast_ray(
position.0,
*rotation,
origin,
direction.adjust_precision(),
max_distance,
solid,
) else {
return Scalar::MAX;
};
if distance < max_distance {
max_distance = distance;
closest_hit = Some(RayHitData {
entity: proxy.collider,
normal,
distance,
});
}
distance
});
});
closest_hit
}
pub fn ray_hits(
&self,
origin: Vector,
direction: Dir,
max_distance: Scalar,
max_hits: u32,
solid: bool,
filter: &SpatialQueryFilter,
) -> Vec<RayHitData> {
let mut hits = Vec::new();
self.ray_hits_callback(origin, direction, max_distance, solid, filter, |hit| {
if hits.len() < max_hits as usize {
hits.push(hit);
true
} else {
false
}
});
hits
}
pub fn ray_hits_callback(
&self,
origin: Vector,
direction: Dir,
max_distance: Scalar,
solid: bool,
filter: &SpatialQueryFilter,
mut callback: impl FnMut(RayHitData) -> bool,
) {
let ray = Ray::new(origin.f32(), direction);
self.collider_trees.iter_trees().for_each(|tree| {
tree.ray_traverse_all(ray, max_distance, |proxy_id| {
let proxy = tree.get_proxy(proxy_id).unwrap();
if !filter.test(proxy.collider, proxy.layers) {
return true;
}
let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
return true;
};
let Some((distance, normal)) = collider.cast_ray(
position.0,
*rotation,
origin,
direction.adjust_precision(),
max_distance,
solid,
) else {
return true;
};
callback(RayHitData {
entity: proxy.collider,
normal,
distance,
})
});
});
}
#[allow(clippy::too_many_arguments)]
pub fn cast_shape(
&self,
shape: &Collider,
origin: Vector,
shape_rotation: RotationValue,
direction: Dir,
config: &ShapeCastConfig,
filter: &SpatialQueryFilter,
) -> Option<ShapeHitData> {
self.cast_shape_predicate(
shape,
origin,
shape_rotation,
direction,
config,
filter,
&|_| true,
)
}
pub fn cast_shape_predicate(
&self,
shape: &Collider,
origin: Vector,
shape_rotation: RotationValue,
direction: Dir,
config: &ShapeCastConfig,
filter: &SpatialQueryFilter,
predicate: &dyn Fn(Entity) -> bool,
) -> Option<ShapeHitData> {
let mut closest_distance = config.max_distance;
let mut closest_hit: Option<ShapeHitData> = None;
let aabb = obvhs::aabb::Aabb::from(shape.aabb(origin, shape_rotation));
self.collider_trees.iter_trees().for_each(|tree| {
tree.sweep_traverse_closest(
aabb,
direction,
closest_distance,
config.target_distance,
|proxy_id| {
let proxy = tree.get_proxy(proxy_id).unwrap();
if !filter.test(proxy.collider, proxy.layers) || !predicate(proxy.collider) {
return Scalar::MAX;
}
let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider)
else {
return Scalar::MAX;
};
let pose1 = make_pose(position.0, *rotation);
let pose2 = make_pose(origin, shape_rotation);
let Ok(Some(hit)) = parry::query::cast_shapes(
&pose1,
Vector::ZERO,
collider.shape_scaled().as_ref(),
&pose2,
direction.adjust_precision(),
shape.shape_scaled().as_ref(),
ShapeCastOptions {
max_time_of_impact: config.max_distance,
target_distance: config.target_distance,
stop_at_penetration: !config.ignore_origin_penetration,
compute_impact_geometry_on_penetration: config
.compute_contact_on_penetration,
},
) else {
return Scalar::MAX;
};
if hit.time_of_impact < closest_distance {
closest_distance = hit.time_of_impact;
closest_hit = Some(ShapeHitData {
entity: proxy.collider,
point1: pose1 * hit.witness1,
point2: pose2 * hit.witness2
+ direction.adjust_precision() * hit.time_of_impact,
normal1: pose1.rotation * hit.normal1,
normal2: pose2.rotation * hit.normal2,
distance: hit.time_of_impact,
});
}
hit.time_of_impact
},
);
});
closest_hit
}
#[allow(clippy::too_many_arguments)]
pub fn shape_hits(
&self,
shape: &Collider,
origin: Vector,
shape_rotation: RotationValue,
direction: Dir,
max_hits: u32,
config: &ShapeCastConfig,
filter: &SpatialQueryFilter,
) -> Vec<ShapeHitData> {
let mut hits = Vec::new();
self.shape_hits_callback(
shape,
origin,
shape_rotation,
direction,
config,
filter,
|hit| {
if hits.len() < max_hits as usize {
hits.push(hit);
true
} else {
false
}
},
);
hits
}
#[allow(clippy::too_many_arguments)]
pub fn shape_hits_callback(
&self,
shape: &Collider,
origin: Vector,
shape_rotation: RotationValue,
direction: Dir,
config: &ShapeCastConfig,
filter: &SpatialQueryFilter,
mut callback: impl FnMut(ShapeHitData) -> bool,
) {
let aabb = obvhs::aabb::Aabb::from(shape.aabb(origin, shape_rotation));
self.collider_trees.iter_trees().for_each(|tree| {
tree.sweep_traverse_all(
aabb,
direction,
config.max_distance,
config.target_distance,
|proxy_id| {
let proxy = tree.get_proxy(proxy_id).unwrap();
if !filter.test(proxy.collider, proxy.layers) {
return true;
}
let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider)
else {
return true;
};
let pose1 = make_pose(position.0, *rotation);
let pose2 = make_pose(origin, shape_rotation);
let Ok(Some(hit)) = parry::query::cast_shapes(
&pose1,
Vector::ZERO,
collider.shape_scaled().as_ref(),
&pose2,
direction.adjust_precision(),
shape.shape_scaled().as_ref(),
ShapeCastOptions {
max_time_of_impact: config.max_distance,
target_distance: config.target_distance,
stop_at_penetration: !config.ignore_origin_penetration,
compute_impact_geometry_on_penetration: config
.compute_contact_on_penetration,
},
) else {
return true;
};
callback(ShapeHitData {
entity: proxy.collider,
point1: position.0 + rotation * hit.witness1,
point2: pose2 * hit.witness2
+ direction.adjust_precision() * hit.time_of_impact,
normal1: pose1.rotation * hit.normal1,
normal2: pose2.rotation * hit.normal2,
distance: hit.time_of_impact,
})
},
);
});
}
pub fn project_point(
&self,
point: Vector,
solid: bool,
filter: &SpatialQueryFilter,
) -> Option<PointProjection> {
self.project_point_predicate(point, solid, filter, &|_| true)
}
pub fn project_point_predicate(
&self,
point: Vector,
solid: bool,
filter: &SpatialQueryFilter,
predicate: &dyn Fn(Entity) -> bool,
) -> Option<PointProjection> {
let mut closest_distance_squared = Scalar::INFINITY;
let mut closest_projection: Option<PointProjection> = None;
self.collider_trees.iter_trees().for_each(|tree| {
tree.squared_distance_traverse_closest(point, Scalar::INFINITY, |proxy_id| {
let proxy = tree.get_proxy(proxy_id).unwrap();
if !filter.test(proxy.collider, proxy.layers) || !predicate(proxy.collider) {
return Scalar::INFINITY;
}
let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
return Scalar::INFINITY;
};
let (projection, is_inside) =
collider.project_point(position.0, *rotation, point, solid);
let distance_squared = (projection - point).length_squared();
if distance_squared < closest_distance_squared {
closest_distance_squared = distance_squared;
closest_projection = Some(PointProjection {
entity: proxy.collider,
point: projection,
is_inside,
});
}
distance_squared
});
});
closest_projection
}
pub fn point_intersections(&self, point: Vector, filter: &SpatialQueryFilter) -> Vec<Entity> {
let mut intersections = vec![];
self.point_intersections_callback(point, filter, |entity| {
intersections.push(entity);
true
});
intersections
}
pub fn point_intersections_callback(
&self,
point: Vector,
filter: &SpatialQueryFilter,
mut callback: impl FnMut(Entity) -> bool,
) {
self.collider_trees.iter_trees().for_each(|tree| {
tree.point_traverse(point, |proxy_id| {
let proxy = tree.get_proxy(proxy_id).unwrap();
if !filter.test(proxy.collider, proxy.layers) {
return true;
}
let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
return true;
};
if collider.contains_point(position.0, *rotation, point) {
callback(proxy.collider)
} else {
true
}
});
});
}
pub fn aabb_intersections_with_aabb(&self, aabb: ColliderAabb) -> Vec<Entity> {
let mut intersections = vec![];
self.aabb_intersections_with_aabb_callback(aabb, |entity| {
intersections.push(entity);
true
});
intersections
}
pub fn aabb_intersections_with_aabb_callback(
&self,
aabb: ColliderAabb,
mut callback: impl FnMut(Entity) -> bool,
) {
self.collider_trees.iter_trees().for_each(|tree| {
tree.aabb_traverse(obvhs::aabb::Aabb::from(aabb), |proxy_id| {
let proxy = tree.get_proxy(proxy_id).unwrap();
let Ok(proxy_aabb) = self.aabbs.get(proxy.collider) else {
return true;
};
if proxy_aabb.intersects(&aabb) {
callback(proxy.collider)
} else {
true
}
});
});
}
pub fn shape_intersections(
&self,
shape: &Collider,
shape_position: Vector,
shape_rotation: RotationValue,
filter: &SpatialQueryFilter,
) -> Vec<Entity> {
let mut intersections = vec![];
self.shape_intersections_callback(
shape,
shape_position,
shape_rotation,
filter,
|entity| {
intersections.push(entity);
true
},
);
intersections
}
pub fn shape_intersections_callback(
&self,
shape: &Collider,
shape_position: Vector,
shape_rotation: RotationValue,
filter: &SpatialQueryFilter,
mut callback: impl FnMut(Entity) -> bool,
) {
let aabb = obvhs::aabb::Aabb::from(shape.aabb(shape_position, shape_rotation));
self.collider_trees.iter_trees().for_each(|tree| {
tree.aabb_traverse(aabb, |proxy_id| {
let proxy = tree.get_proxy(proxy_id).unwrap();
if !filter.test(proxy.collider, proxy.layers) {
return true;
}
let Ok((position, rotation, collider)) = self.colliders.get(proxy.collider) else {
return true;
};
if contact_query::intersection_test(
collider,
position.0,
*rotation,
shape,
shape_position,
shape_rotation,
)
.is_ok_and(|intersects| intersects)
{
callback(proxy.collider)
} else {
true
}
});
});
}
}
#[derive(Clone, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub struct PointProjection {
pub entity: Entity,
pub point: Vector,
pub is_inside: bool,
}