use crate::prelude::*;
use bevy::{
ecs::{
entity::{EntityMapper, MapEntities},
lifecycle::HookContext,
world::DeferredWorld,
},
prelude::*,
};
#[cfg_attr(feature = "2d", doc = " Collider::circle(0.5),")]
#[cfg_attr(feature = "3d", doc = " Collider::sphere(0.5),")]
#[derive(Component, Clone, Debug, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Component)]
#[component(on_add = on_add_shape_caster)]
#[require(ShapeHits)]
pub struct ShapeCaster {
pub enabled: bool,
#[reflect(ignore)]
pub shape: Collider,
pub origin: Vector,
global_origin: Vector,
#[cfg(feature = "2d")]
pub shape_rotation: Scalar,
#[cfg(feature = "3d")]
pub shape_rotation: Quaternion,
#[cfg(feature = "2d")]
global_shape_rotation: Scalar,
#[cfg(feature = "3d")]
global_shape_rotation: Quaternion,
pub direction: Dir,
global_direction: Dir,
pub max_hits: u32,
#[doc(alias = "max_time_of_impact")]
pub max_distance: Scalar,
pub target_distance: Scalar,
pub compute_contact_on_penetration: bool,
pub ignore_origin_penetration: bool,
pub ignore_self: bool,
pub query_filter: SpatialQueryFilter,
}
impl Default for ShapeCaster {
fn default() -> Self {
Self {
enabled: true,
#[cfg(feature = "2d")]
shape: Collider::circle(0.0),
#[cfg(feature = "3d")]
shape: Collider::sphere(0.0),
origin: Vector::ZERO,
global_origin: Vector::ZERO,
#[cfg(feature = "2d")]
shape_rotation: 0.0,
#[cfg(feature = "3d")]
shape_rotation: Quaternion::IDENTITY,
#[cfg(feature = "2d")]
global_shape_rotation: 0.0,
#[cfg(feature = "3d")]
global_shape_rotation: Quaternion::IDENTITY,
direction: Dir::X,
global_direction: Dir::X,
max_hits: 1,
max_distance: Scalar::MAX,
target_distance: 0.0,
compute_contact_on_penetration: true,
ignore_origin_penetration: false,
ignore_self: true,
query_filter: SpatialQueryFilter::default(),
}
}
}
impl ShapeCaster {
#[cfg(feature = "2d")]
pub fn new(
shape: impl Into<Collider>,
origin: Vector,
shape_rotation: Scalar,
direction: Dir,
) -> Self {
Self {
shape: shape.into(),
origin,
shape_rotation,
direction,
..default()
}
}
#[cfg(feature = "3d")]
pub fn new(
shape: impl Into<Collider>,
origin: Vector,
shape_rotation: Quaternion,
direction: Dir,
) -> Self {
Self {
shape: shape.into(),
origin,
shape_rotation,
direction,
..default()
}
}
pub fn with_origin(mut self, origin: Vector) -> Self {
self.origin = origin;
self
}
pub fn with_direction(mut self, direction: Dir) -> Self {
self.direction = direction;
self
}
pub fn with_target_distance(mut self, target_distance: Scalar) -> Self {
self.target_distance = target_distance;
self
}
pub fn with_compute_contact_on_penetration(mut self, compute_contact: bool) -> Self {
self.compute_contact_on_penetration = compute_contact;
self
}
pub fn with_ignore_origin_penetration(mut self, ignore: bool) -> Self {
self.ignore_origin_penetration = ignore;
self
}
pub fn with_ignore_self(mut self, ignore: bool) -> Self {
self.ignore_self = ignore;
self
}
pub fn with_max_distance(mut self, max_distance: Scalar) -> Self {
self.max_distance = max_distance;
self
}
pub fn with_max_hits(mut self, max_hits: u32) -> Self {
self.max_hits = max_hits;
self
}
pub fn with_query_filter(mut self, query_filter: SpatialQueryFilter) -> Self {
self.query_filter = query_filter;
self
}
pub fn enable(&mut self) {
self.enabled = true;
}
pub fn disable(&mut self) {
self.enabled = false;
}
pub fn global_origin(&self) -> Vector {
self.global_origin
}
#[cfg(feature = "2d")]
pub fn global_shape_rotation(&self) -> Scalar {
self.global_shape_rotation
}
#[cfg(feature = "3d")]
pub fn global_shape_rotation(&self) -> Quaternion {
self.global_shape_rotation
}
pub fn global_direction(&self) -> Dir {
self.global_direction
}
pub(crate) fn set_global_origin(&mut self, global_origin: Vector) {
self.global_origin = global_origin;
}
#[cfg(feature = "2d")]
pub(crate) fn set_global_shape_rotation(&mut self, global_rotation: Scalar) {
self.global_shape_rotation = global_rotation;
}
#[cfg(feature = "3d")]
pub(crate) fn set_global_shape_rotation(&mut self, global_rotation: Quaternion) {
self.global_shape_rotation = global_rotation;
}
pub(crate) fn set_global_direction(&mut self, global_direction: Dir) {
self.global_direction = global_direction;
}
pub(crate) fn cast(
&mut self,
caster_entity: Entity,
hits: &mut ShapeHits,
spatial_query: &SpatialQuery,
) {
if self.ignore_self {
self.query_filter.excluded_entities.insert(caster_entity);
} else {
self.query_filter.excluded_entities.remove(&caster_entity);
}
hits.clear();
let config = ShapeCastConfig {
max_distance: self.max_distance,
target_distance: self.target_distance,
compute_contact_on_penetration: self.compute_contact_on_penetration,
ignore_origin_penetration: self.ignore_origin_penetration,
};
if self.max_hits == 1 {
let first_hit = spatial_query.cast_shape(
&self.shape,
self.global_origin,
self.global_shape_rotation,
self.global_direction,
&config,
&self.query_filter,
);
if let Some(hit) = first_hit {
hits.push(hit);
}
} else {
hits.extend(spatial_query.shape_hits(
&self.shape,
self.global_origin,
self.global_shape_rotation,
self.global_direction,
self.max_hits,
&config,
&self.query_filter,
));
}
}
}
fn on_add_shape_caster(mut world: DeferredWorld, ctx: HookContext) {
let shape_caster = world.get::<ShapeCaster>(ctx.entity).unwrap();
let max_hits = if shape_caster.max_hits == u32::MAX {
10
} else {
shape_caster.max_hits as usize
};
world.get_mut::<ShapeHits>(ctx.entity).unwrap().0 = Vec::with_capacity(max_hits);
}
#[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 ShapeCastConfig {
#[doc(alias = "max_time_of_impact")]
pub max_distance: Scalar,
pub target_distance: Scalar,
pub compute_contact_on_penetration: bool,
pub ignore_origin_penetration: bool,
}
impl Default for ShapeCastConfig {
fn default() -> Self {
Self::DEFAULT
}
}
impl ShapeCastConfig {
pub const DEFAULT: Self = Self {
max_distance: Scalar::MAX,
target_distance: 0.0,
compute_contact_on_penetration: true,
ignore_origin_penetration: false,
};
#[inline]
pub const fn from_max_distance(max_distance: Scalar) -> Self {
Self {
max_distance,
target_distance: 0.0,
compute_contact_on_penetration: true,
ignore_origin_penetration: false,
}
}
#[inline]
pub const fn from_target_distance(target_distance: Scalar) -> Self {
Self {
max_distance: Scalar::MAX,
target_distance,
compute_contact_on_penetration: true,
ignore_origin_penetration: false,
}
}
#[inline]
pub const fn with_max_distance(mut self, max_distance: Scalar) -> Self {
self.max_distance = max_distance;
self
}
#[inline]
pub const fn with_target_distance(mut self, target_distance: Scalar) -> Self {
self.target_distance = target_distance;
self
}
}
#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, Default, PartialEq)]
pub struct ShapeHits(pub Vec<ShapeHitData>);
impl ShapeHits {
pub fn iter_sorted(&self) -> alloc::vec::IntoIter<ShapeHitData> {
let mut vector = self.as_slice().to_vec();
vector.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap());
vector.into_iter()
}
}
impl IntoIterator for ShapeHits {
type Item = ShapeHitData;
type IntoIter = alloc::vec::IntoIter<ShapeHitData>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a ShapeHits {
type Item = &'a ShapeHitData;
type IntoIter = core::slice::Iter<'a, ShapeHitData>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'a> IntoIterator for &'a mut ShapeHits {
type Item = &'a mut ShapeHitData;
type IntoIter = core::slice::IterMut<'a, ShapeHitData>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}
impl MapEntities for ShapeHits {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
for hit in self {
hit.map_entities(entity_mapper);
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, PartialEq)]
pub struct ShapeHitData {
pub entity: Entity,
#[doc(alias = "time_of_impact")]
pub distance: Scalar,
pub point1: Vector,
pub point2: Vector,
pub normal1: Vector,
pub normal2: Vector,
}
impl MapEntities for ShapeHitData {
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
self.entity = entity_mapper.get_mapped(self.entity);
}
}