use crate::math::{Rot, Vect};
use bevy::ecs::{query, system::SystemParam};
use bevy::prelude::*;
use rapier::prelude::Real;
pub(crate) const RAPIER_CONTEXT_EXPECT_ERROR: &str =
"RapierContextEntityLink.0 refers to an entity missing components from RapierContextSimulation.";
use crate::plugin::context::{
DefaultRapierContext, RapierContextColliders, RapierContextJoints, RapierContextSimulation,
RapierQueryPipeline, RapierRigidBodySet,
};
#[derive(SystemParam)]
pub struct ReadRapierContext<'w, 's, T: query::QueryFilter + 'static = With<DefaultRapierContext>> {
pub rapier_context: Query<
'w,
's,
(
&'static RapierContextSimulation,
&'static RapierContextColliders,
&'static RapierContextJoints,
&'static RapierRigidBodySet,
),
T,
>,
}
impl<'w, 's, T: query::QueryFilter + 'static> ReadRapierContext<'w, 's, T> {
pub fn single(&self) -> Result<RapierContext<'_>> {
let (simulation, colliders, joints, rigidbody_set) = self.rapier_context.single()?;
Ok(RapierContext {
simulation,
colliders,
joints,
rigidbody_set,
})
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize))]
#[derive(query::QueryData)]
pub struct RapierContext<'a> {
pub simulation: &'a RapierContextSimulation,
pub colliders: &'a RapierContextColliders,
pub joints: &'a RapierContextJoints,
pub rigidbody_set: &'a RapierRigidBodySet,
}
#[derive(SystemParam)]
pub struct WriteRapierContext<'w, 's, T: query::QueryFilter + 'static = With<DefaultRapierContext>>
{
pub rapier_context: Query<
'w,
's,
(
&'static mut RapierContextSimulation,
&'static mut RapierContextColliders,
&'static mut RapierContextJoints,
&'static mut RapierRigidBodySet,
),
T,
>,
}
impl<'w, 's, T: query::QueryFilter + 'static> WriteRapierContext<'w, 's, T> {
pub fn single(&self) -> Result<RapierContext<'_>> {
let (simulation, colliders, joints, rigidbody_set) = self.rapier_context.single()?;
Ok(RapierContext {
simulation,
colliders,
joints,
rigidbody_set,
})
}
pub fn single_mut(&mut self) -> Result<RapierContextMut<'_>> {
let (simulation, colliders, joints, rigidbody_set) = self.rapier_context.single_mut()?;
Ok(RapierContextMut {
simulation,
colliders,
joints,
rigidbody_set,
})
}
}
pub struct RapierContextMut<'a> {
pub simulation: Mut<'a, RapierContextSimulation>,
pub colliders: Mut<'a, RapierContextColliders>,
pub joints: Mut<'a, RapierContextJoints>,
pub rigidbody_set: Mut<'a, RapierRigidBodySet>,
}
mod simulation {
use crate::control::CharacterCollision;
use crate::control::MoveShapeOptions;
use crate::control::MoveShapeOutput;
use crate::plugin::context::SimulationToRenderTime;
use crate::plugin::ContactPairView;
use crate::plugin::TimestepMode;
use crate::prelude::CollisionEvent;
use crate::prelude::ContactForceEvent;
use crate::prelude::RapierQueryPipelineMut;
use crate::prelude::RapierRigidBodyHandle;
use crate::prelude::TransformInterpolation;
use rapier::prelude::PhysicsHooks;
use rapier::prelude::Shape;
use super::*;
impl RapierContext<'_> {
pub fn contact_pair(
&self,
collider1: Entity,
collider2: Entity,
) -> Option<ContactPairView<'_>> {
self.simulation
.contact_pair(self.colliders, self.rigidbody_set, collider1, collider2)
}
pub fn contact_pairs_with(
&self,
collider: Entity,
) -> impl Iterator<Item = ContactPairView<'_>> {
self.simulation
.contact_pairs_with(self.colliders, self.rigidbody_set, collider)
}
pub fn intersection_pair(&self, collider1: Entity, collider2: Entity) -> Option<bool> {
self.simulation
.intersection_pair(self.colliders, collider1, collider2)
}
pub fn intersection_pairs_with(
&self,
collider: Entity,
) -> impl Iterator<Item = (Entity, Entity, bool)> + '_ {
self.simulation
.intersection_pairs_with(self.colliders, collider)
}
}
impl RapierContextMut<'_> {
#[expect(clippy::too_many_arguments)]
pub fn step_simulation(
&mut self,
gravity: Vect,
timestep_mode: TimestepMode,
events: Option<(
&MessageWriter<CollisionEvent>,
&MessageWriter<ContactForceEvent>,
)>,
hooks: &dyn PhysicsHooks,
time: &Time,
sim_to_render_time: &mut SimulationToRenderTime,
interpolation_query: Option<
&mut Query<(&RapierRigidBodyHandle, &mut TransformInterpolation)>,
>,
) {
self.simulation.step_simulation(
&mut self.colliders,
&mut self.joints,
&mut self.rigidbody_set,
gravity,
timestep_mode,
events,
hooks,
time,
sim_to_render_time,
interpolation_query,
)
}
#[expect(clippy::too_many_arguments)]
pub fn move_shape(
&mut self,
query_pipeline_mut: &mut RapierQueryPipelineMut<'_>,
movement: Vect,
shape: &dyn Shape,
shape_translation: Vect,
shape_rotation: Rot,
shape_mass: Real,
options: &MoveShapeOptions,
events: impl FnMut(CharacterCollision),
) -> MoveShapeOutput {
self.simulation.move_shape(
&self.colliders,
query_pipeline_mut,
movement,
shape,
shape_translation,
shape_rotation,
shape_mass,
options,
events,
)
}
pub fn contact_pair(
&self,
collider1: Entity,
collider2: Entity,
) -> Option<ContactPairView<'_>> {
self.simulation
.contact_pair(&self.colliders, &self.rigidbody_set, collider1, collider2)
}
pub fn contact_pairs_with(
&self,
collider: Entity,
) -> impl Iterator<Item = ContactPairView<'_>> {
self.simulation
.contact_pairs_with(&self.colliders, &self.rigidbody_set, collider)
}
pub fn intersection_pair(&self, collider1: Entity, collider2: Entity) -> Option<bool> {
self.simulation
.intersection_pair(&self.colliders, collider1, collider2)
}
pub fn intersection_pairs_with(
&self,
collider: Entity,
) -> impl Iterator<Item = (Entity, Entity, bool)> + '_ {
self.simulation
.intersection_pairs_with(&self.colliders, collider)
}
}
}
mod query_pipeline {
use rapier::{
parry::query::{DefaultQueryDispatcher, ShapeCastOptions},
prelude::Shape,
};
use crate::prelude::{PointProjection, QueryFilter, RayIntersection, ShapeCastHit};
use super::*;
impl RapierContext<'_> {
pub fn with_query_pipeline<'a, T>(
&'a self,
filter: QueryFilter<'a>,
scoped_fn: impl FnOnce(RapierQueryPipeline<'_>) -> T,
) -> T {
crate::prelude::RapierQueryPipeline::new_scoped(
&self.simulation.broad_phase,
self.colliders,
self.rigidbody_set,
&filter,
&DefaultQueryDispatcher,
scoped_fn,
)
}
pub fn cast_ray(
&self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
filter: QueryFilter,
) -> Option<(Entity, Real)> {
self.with_query_pipeline(filter, |query_pipeline| {
query_pipeline.cast_ray(ray_origin, ray_dir, max_toi, solid)
})
}
pub fn cast_ray_and_get_normal(
&self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
filter: QueryFilter,
) -> Option<(Entity, RayIntersection)> {
self.with_query_pipeline(filter, |query_pipeline| {
query_pipeline.cast_ray_and_get_normal(ray_origin, ray_dir, max_toi, solid)
})
}
pub fn intersect_point(
&self,
point: Vect,
filter: QueryFilter,
mut callback: impl FnMut(Entity) -> bool,
) {
self.with_query_pipeline(filter, |query_pipeline| {
for e in query_pipeline.intersect_point(point) {
if !callback(e) {
break;
}
}
});
}
pub fn intersect_ray(
&self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
filter: QueryFilter,
mut callback: impl FnMut(Entity, RayIntersection) -> bool,
) {
self.with_query_pipeline(filter, |query_pipeline| {
for (e, intersection) in
query_pipeline.intersect_ray(ray_origin, ray_dir, max_toi, solid)
{
if !callback(e, intersection) {
break;
}
}
});
}
pub fn intersect_shape(
&self,
shape_pos: Vect,
shape_rot: Rot,
shape: &dyn Shape,
filter: QueryFilter,
mut callback: impl FnMut(Entity) -> bool,
) {
self.with_query_pipeline(filter, |query_pipeline| {
for e in query_pipeline.intersect_shape(shape_pos, shape_rot, shape) {
if !callback(e) {
break;
}
}
});
}
pub fn intersect_aabb_conservative(
&self,
#[cfg(feature = "dim2")] aabb: bevy::math::bounding::Aabb2d,
#[cfg(feature = "dim3")] aabb: bevy::math::bounding::Aabb3d,
filter: QueryFilter,
mut callback: impl FnMut(Entity) -> bool,
) {
self.with_query_pipeline(filter, |query_pipeline| {
for e in query_pipeline.intersect_aabb_conservative(aabb) {
if !callback(e) {
break;
}
}
});
}
pub fn cast_shape(
&self,
shape_pos: Vect,
shape_rot: Rot,
shape_vel: Vect,
shape: &dyn Shape,
options: ShapeCastOptions,
filter: QueryFilter,
) -> Option<(Entity, ShapeCastHit)> {
self.with_query_pipeline(filter, |query_pipeline| {
query_pipeline.cast_shape(shape_pos, shape_rot, shape_vel, shape, options)
})
}
pub fn project_point(
&self,
point: Vect,
max_dist: f32,
solid: bool,
filter: QueryFilter,
) -> Option<(Entity, PointProjection)> {
self.with_query_pipeline(filter, |query_pipeline| {
query_pipeline.project_point(point, max_dist, solid)
})
}
}
impl RapierContextMut<'_> {
pub fn cast_ray(
&self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
filter: QueryFilter,
) -> Option<(Entity, Real)> {
self.with_query_pipeline(filter, |query_pipeline| {
query_pipeline.cast_ray(ray_origin, ray_dir, max_toi, solid)
})
}
pub fn cast_ray_and_get_normal(
&self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
filter: QueryFilter,
) -> Option<(Entity, RayIntersection)> {
self.with_query_pipeline(filter, |query_pipeline| {
query_pipeline.cast_ray_and_get_normal(ray_origin, ray_dir, max_toi, solid)
})
}
pub fn intersect_point(
&self,
point: Vect,
filter: QueryFilter,
mut callback: impl FnMut(Entity) -> bool,
) {
self.with_query_pipeline(filter, |query_pipeline| {
for e in query_pipeline.intersect_point(point) {
if !callback(e) {
break;
}
}
});
}
pub fn intersect_ray(
&self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
filter: QueryFilter,
mut callback: impl FnMut(Entity, RayIntersection) -> bool,
) {
self.with_query_pipeline(filter, |query_pipeline| {
for (e, intersection) in
query_pipeline.intersect_ray(ray_origin, ray_dir, max_toi, solid)
{
if !callback(e, intersection) {
break;
}
}
});
}
pub fn intersect_shape(
&self,
shape_pos: Vect,
shape_rot: Rot,
shape: &dyn Shape,
filter: QueryFilter,
mut callback: impl FnMut(Entity) -> bool,
) {
self.with_query_pipeline(filter, |query_pipeline| {
for e in query_pipeline.intersect_shape(shape_pos, shape_rot, shape) {
if !callback(e) {
break;
}
}
});
}
pub fn intersect_aabb_conservative(
&self,
#[cfg(feature = "dim2")] aabb: bevy::math::bounding::Aabb2d,
#[cfg(feature = "dim3")] aabb: bevy::math::bounding::Aabb3d,
filter: QueryFilter,
mut callback: impl FnMut(Entity) -> bool,
) {
self.with_query_pipeline(filter, |query_pipeline| {
for e in query_pipeline.intersect_aabb_conservative(aabb) {
if !callback(e) {
break;
}
}
});
}
pub fn cast_shape(
&self,
shape_pos: Vect,
shape_rot: Rot,
shape_vel: Vect,
shape: &dyn Shape,
options: ShapeCastOptions,
filter: QueryFilter,
) -> Option<(Entity, ShapeCastHit)> {
self.with_query_pipeline(filter, |query_pipeline| {
query_pipeline.cast_shape(shape_pos, shape_rot, shape_vel, shape, options)
})
}
pub fn project_point(
&self,
point: Vect,
max_dist: f32,
solid: bool,
filter: QueryFilter,
) -> Option<(Entity, PointProjection)> {
self.with_query_pipeline(filter, |query_pipeline| {
query_pipeline.project_point(point, max_dist, solid)
})
}
}
impl RapierContextMut<'_> {
pub fn with_query_pipeline<'a, T>(
&'a self,
filter: QueryFilter<'a>,
scoped_fn: impl FnOnce(RapierQueryPipeline<'_>) -> T,
) -> T {
crate::prelude::RapierQueryPipeline::new_scoped(
&self.simulation.broad_phase,
&self.colliders,
&self.rigidbody_set,
&filter,
&DefaultQueryDispatcher,
scoped_fn,
)
}
}
}
mod rigidbody_set {
use std::collections::HashMap;
use super::*;
pub use rapier::prelude::RigidBodyHandle;
impl RapierContext<'_> {
pub fn entity2body(&self) -> &HashMap<Entity, RigidBodyHandle> {
self.rigidbody_set.entity2body()
}
pub fn rigid_body_entity(&self, handle: RigidBodyHandle) -> Option<Entity> {
self.rigidbody_set.rigid_body_entity(handle)
}
pub fn impulse_revolute_joint_angle(&self, entity: Entity) -> Option<f32> {
self.rigidbody_set
.impulse_revolute_joint_angle(self.joints, entity)
}
}
impl RapierContextMut<'_> {
pub fn propagate_modified_body_positions_to_colliders(&mut self) {
self.rigidbody_set
.propagate_modified_body_positions_to_colliders(&mut self.colliders)
}
pub fn entity2body(&self) -> &HashMap<Entity, RigidBodyHandle> {
self.rigidbody_set.entity2body()
}
pub fn rigid_body_entity(&self, handle: RigidBodyHandle) -> Option<Entity> {
self.rigidbody_set.rigid_body_entity(handle)
}
pub fn impulse_revolute_joint_angle(&self, entity: Entity) -> Option<f32> {
self.rigidbody_set
.impulse_revolute_joint_angle(&self.joints, entity)
}
}
}