#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
mod pipeline;
mod query_filter;
mod ray_caster;
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
mod shape_caster;
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
mod system_param;
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
pub use pipeline::*;
pub use query_filter::*;
pub use ray_caster::*;
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
pub use shape_caster::*;
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
pub use system_param::*;
use crate::{prelude::*, prepare::PrepareSet};
use bevy::{ecs::intern::Interned, prelude::*};
pub struct SpatialQueryPlugin {
schedule: Interned<dyn ScheduleLabel>,
}
impl SpatialQueryPlugin {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
}
}
}
impl Default for SpatialQueryPlugin {
fn default() -> Self {
Self::new(PostUpdate)
}
}
impl Plugin for SpatialQueryPlugin {
fn build(&self, app: &mut App) {
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
app.init_resource::<SpatialQueryPipeline>();
app.add_systems(self.schedule, init_ray_hits.in_set(PrepareSet::PreInit));
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
app.add_systems(self.schedule, init_shape_hits.in_set(PrepareSet::PreInit));
let physics_schedule = app
.get_schedule_mut(PhysicsSchedule)
.expect("add PhysicsSchedule first");
physics_schedule.add_systems(
(
update_ray_caster_positions,
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
(
update_shape_caster_positions,
|mut spatial_query: SpatialQuery| spatial_query.update_pipeline(),
raycast,
shapecast,
)
.chain(),
)
.chain()
.in_set(PhysicsStepSet::SpatialQuery),
);
}
}
fn init_ray_hits(mut commands: Commands, rays: Query<(Entity, &RayCaster), Added<RayCaster>>) {
for (entity, ray) in &rays {
let max_hits = if ray.max_hits == u32::MAX {
10
} else {
ray.max_hits as usize
};
commands.entity(entity).try_insert(RayHits {
vector: Vec::with_capacity(max_hits),
count: 0,
});
}
}
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
fn init_shape_hits(
mut commands: Commands,
shape_casters: Query<(Entity, &ShapeCaster), Added<ShapeCaster>>,
) {
for (entity, shape_caster) in &shape_casters {
commands.entity(entity).try_insert(ShapeHits {
vector: Vec::with_capacity(shape_caster.max_hits.min(100_000) as usize),
count: 0,
});
}
}
type RayCasterPositionQueryComponents = (
&'static mut RayCaster,
Option<&'static Position>,
Option<&'static Rotation>,
Option<&'static Parent>,
Option<&'static GlobalTransform>,
);
#[allow(clippy::type_complexity)]
fn update_ray_caster_positions(
mut rays: Query<RayCasterPositionQueryComponents>,
parents: Query<
(
Option<&Position>,
Option<&Rotation>,
Option<&GlobalTransform>,
),
With<Children>,
>,
) {
for (mut ray, position, rotation, parent, transform) in &mut rays {
let origin = ray.origin;
let direction = ray.direction;
let global_position = position.copied().or(transform.map(Position::from));
let global_rotation = rotation.copied().or(transform.map(Rotation::from));
if let Some(global_position) = global_position {
ray.set_global_origin(global_position.0 + rotation.map_or(origin, |rot| rot * origin));
} else if parent.is_none() {
ray.set_global_origin(origin);
}
if let Some(global_rotation) = global_rotation {
let global_direction = global_rotation * ray.direction;
ray.set_global_direction(global_direction);
} else if parent.is_none() {
ray.set_global_direction(direction);
}
if let Some(Ok((parent_position, parent_rotation, parent_transform))) =
parent.map(|p| parents.get(p.get()))
{
let parent_position = parent_position
.copied()
.or(parent_transform.map(Position::from));
let parent_rotation = parent_rotation
.copied()
.or(parent_transform.map(Rotation::from));
if global_position.is_none() {
if let Some(position) = parent_position {
let rotation = global_rotation.unwrap_or(parent_rotation.unwrap_or_default());
ray.set_global_origin(position.0 + rotation * origin);
}
}
if global_rotation.is_none() {
if let Some(rotation) = parent_rotation {
let global_direction = rotation * ray.direction;
ray.set_global_direction(global_direction);
}
}
}
}
}
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
type ShapeCasterPositionQueryComponents = (
&'static mut ShapeCaster,
Option<&'static Position>,
Option<&'static Rotation>,
Option<&'static Parent>,
Option<&'static GlobalTransform>,
);
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
#[allow(clippy::type_complexity)]
fn update_shape_caster_positions(
mut shape_casters: Query<ShapeCasterPositionQueryComponents>,
parents: Query<
(
Option<&Position>,
Option<&Rotation>,
Option<&GlobalTransform>,
),
With<Children>,
>,
) {
for (mut shape_caster, position, rotation, parent, transform) in &mut shape_casters {
let origin = shape_caster.origin;
let shape_rotation = shape_caster.shape_rotation;
let direction = shape_caster.direction;
let global_position = position.copied().or(transform.map(Position::from));
let global_rotation = rotation.copied().or(transform.map(Rotation::from));
if let Some(global_position) = global_position {
shape_caster
.set_global_origin(global_position.0 + rotation.map_or(origin, |rot| rot * origin));
} else if parent.is_none() {
shape_caster.set_global_origin(origin);
}
if let Some(global_rotation) = global_rotation {
let global_direction = global_rotation * shape_caster.direction;
shape_caster.set_global_direction(global_direction);
#[cfg(feature = "2d")]
{
shape_caster
.set_global_shape_rotation(shape_rotation + global_rotation.as_radians());
}
#[cfg(feature = "3d")]
{
shape_caster.set_global_shape_rotation(shape_rotation * global_rotation.0);
}
} else if parent.is_none() {
shape_caster.set_global_direction(direction);
#[cfg(feature = "2d")]
{
shape_caster.set_global_shape_rotation(shape_rotation);
}
#[cfg(feature = "3d")]
{
shape_caster.set_global_shape_rotation(shape_rotation);
}
}
if let Some(Ok((parent_position, parent_rotation, parent_transform))) =
parent.map(|p| parents.get(p.get()))
{
let parent_position = parent_position
.copied()
.or(parent_transform.map(Position::from));
let parent_rotation = parent_rotation
.copied()
.or(parent_transform.map(Rotation::from));
if global_position.is_none() {
if let Some(position) = parent_position {
let rotation = global_rotation.unwrap_or(parent_rotation.unwrap_or_default());
shape_caster.set_global_origin(position.0 + rotation * origin);
}
}
if global_rotation.is_none() {
if let Some(rotation) = parent_rotation {
let global_direction = rotation * shape_caster.direction;
shape_caster.set_global_direction(global_direction);
#[cfg(feature = "2d")]
{
shape_caster
.set_global_shape_rotation(shape_rotation + rotation.as_radians());
}
#[cfg(feature = "3d")]
{
shape_caster.set_global_shape_rotation(shape_rotation * rotation.0);
}
}
}
}
}
}
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
fn raycast(mut rays: Query<(Entity, &RayCaster, &mut RayHits)>, spatial_query: SpatialQuery) {
for (entity, ray, mut hits) in &mut rays {
if ray.enabled {
ray.cast(entity, &mut hits, &spatial_query.query_pipeline);
} else if !hits.is_empty() {
hits.clear();
}
}
}
#[cfg(all(
feature = "default-collider",
any(feature = "parry-f32", feature = "parry-f64")
))]
fn shapecast(
mut shape_casters: Query<(Entity, &ShapeCaster, &mut ShapeHits)>,
spatial_query: SpatialQuery,
) {
for (entity, shape_caster, mut hits) in &mut shape_casters {
if shape_caster.enabled {
shape_caster.cast(entity, &mut hits, &spatial_query.query_pipeline);
} else if !hits.is_empty() {
hits.clear();
}
}
}