pub mod systemparams;
use bevy::prelude::*;
use rapier::parry::query::QueryDispatcher;
use std::collections::HashMap;
use std::sync::RwLock;
use rapier::prelude::{
Aabb, CCDSolver, ColliderHandle, ColliderSet, EventHandler, FeatureId, ImpulseJointHandle,
ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointHandle, MultibodyJointSet,
NarrowPhase, PhysicsHooks, PhysicsPipeline, QueryFilter as RapierQueryFilter, QueryPipeline,
QueryPipelineMut, Ray, Real, RigidBodyHandle, RigidBodySet, Shape,
};
use crate::geometry::{PointProjection, RayIntersection, ShapeCastHit};
use crate::math::{Rot, Vect};
use crate::pipeline::{CollisionEvent, ContactForceEvent, EventQueue};
use bevy::prelude::{Entity, GlobalTransform, Query};
use crate::control::{CharacterCollision, MoveShapeOptions, MoveShapeOutput};
use crate::dynamics::TransformInterpolation;
use crate::parry::query::details::ShapeCastOptions;
use crate::plugin::configuration::TimestepMode;
use crate::prelude::{CollisionGroups, QueryFilter, RapierRigidBodyHandle};
use rapier::control::CharacterAutostep;
use rapier::geometry::DefaultBroadPhase;
#[cfg(doc)]
use crate::prelude::{
systemparams::{RapierContext, ReadRapierContext},
ImpulseJoint, MultibodyJoint, RevoluteJoint, TypedJoint,
};
#[derive(Component, Default, Reflect, Clone)]
pub struct SimulationToRenderTime {
pub diff: f32,
}
#[derive(Component, Reflect, Debug, Clone, Copy)]
pub struct DefaultRapierContext;
#[derive(Component, Reflect, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RapierContextEntityLink(pub Entity);
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Component, Default, Debug, Clone)]
pub struct RapierContextColliders {
pub colliders: ColliderSet,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) entity2collider: HashMap<Entity, ColliderHandle>,
}
impl RapierContextColliders {
pub fn collider_parent(
&self,
rigidbody_set: &RapierRigidBodySet,
entity: Entity,
) -> Option<Entity> {
self.entity2collider
.get(&entity)
.and_then(|h| self.colliders.get(*h))
.and_then(|co| co.parent())
.and_then(|h| rigidbody_set.rigid_body_entity(h))
}
pub fn rigid_body_colliders<'a, 'b: 'a>(
&'a self,
entity: Entity,
rigidbody_set: &'b RapierRigidBodySet,
) -> impl Iterator<Item = Entity> + 'a {
rigidbody_set
.entity2body()
.get(&entity)
.and_then(|handle| rigidbody_set.bodies.get(*handle))
.map(|body| {
body.colliders()
.iter()
.filter_map(|handle| self.collider_entity(*handle))
})
.into_iter()
.flatten()
}
pub fn collider_entity(&self, handle: ColliderHandle) -> Option<Entity> {
RapierContextColliders::collider_entity_with_set(&self.colliders, handle)
}
pub(crate) fn collider_entity_with_set(
colliders: &ColliderSet,
handle: ColliderHandle,
) -> Option<Entity> {
colliders.get(handle).map(Self::entity_from_collider)
}
pub fn entity_from_collider(collider: &rapier::prelude::Collider) -> Entity {
Entity::from_bits(collider.user_data as u64)
}
pub fn entity2collider(&self) -> &HashMap<Entity, ColliderHandle> {
&self.entity2collider
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Component, Default, Debug, Clone)]
pub struct RapierContextJoints {
pub impulse_joints: ImpulseJointSet,
pub multibody_joints: MultibodyJointSet,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) entity2impulse_joint: HashMap<Entity, ImpulseJointHandle>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) entity2multibody_joint: HashMap<Entity, MultibodyJointHandle>,
}
impl RapierContextJoints {
pub fn entity2impulse_joint(&self) -> &HashMap<Entity, ImpulseJointHandle> {
&self.entity2impulse_joint
}
pub fn entity2multibody_joint(&self) -> &HashMap<Entity, MultibodyJointHandle> {
&self.entity2multibody_joint
}
}
#[derive(Copy, Clone)]
pub struct RapierQueryPipeline<'a> {
pub query_pipeline: QueryPipeline<'a>,
}
pub struct RapierQueryPipelineMut<'a> {
pub query_pipeline: QueryPipelineMut<'a>,
}
impl<'a> RapierQueryPipelineMut<'a> {
pub fn new_scoped<T>(
broad_phase: &DefaultBroadPhase,
colliders: &mut RapierContextColliders,
rigid_bodies: &mut RapierRigidBodySet,
filter: &QueryFilter<'_>,
dispatcher: &dyn QueryDispatcher,
scoped_fn: impl FnOnce(RapierQueryPipelineMut<'_>) -> T,
) -> T {
let mut rapier_filter = RapierQueryFilter {
flags: filter.flags,
groups: filter.groups.map(CollisionGroups::into),
exclude_collider: filter
.exclude_collider
.and_then(|c| colliders.entity2collider.get(&c).copied()),
exclude_rigid_body: filter
.exclude_rigid_body
.and_then(|b| rigid_bodies.entity2body.get(&b).copied()),
predicate: None,
};
if let Some(predicate) = filter.predicate {
let wrapped_predicate = to_rapier_query_filter_predicate(predicate);
rapier_filter.predicate = Some(&wrapped_predicate);
let query_pipeline = broad_phase.as_query_pipeline_mut(
dispatcher,
&mut rigid_bodies.bodies,
&mut colliders.colliders,
rapier_filter,
);
scoped_fn(RapierQueryPipelineMut { query_pipeline })
} else {
let query_pipeline = broad_phase.as_query_pipeline_mut(
dispatcher,
&mut rigid_bodies.bodies,
&mut colliders.colliders,
rapier_filter,
);
scoped_fn(RapierQueryPipelineMut { query_pipeline })
}
}
pub fn as_ref(&self) -> RapierQueryPipeline<'_> {
RapierQueryPipeline {
query_pipeline: self.query_pipeline.as_ref(),
}
}
}
pub fn to_rapier_query_filter_predicate(
predicate: &dyn Fn(Entity) -> bool,
) -> impl Fn(ColliderHandle, &rapier::prelude::Collider) -> bool + use<'_> {
|_: ColliderHandle, collider: &'_ rapier::geometry::Collider| -> bool {
let entity = crate::prelude::RapierContextColliders::entity_from_collider(collider);
(predicate)(entity)
}
}
impl<'a> RapierQueryPipeline<'a> {
pub fn new_scoped<T>(
broad_phase: &DefaultBroadPhase,
colliders: &RapierContextColliders,
rigid_bodies: &RapierRigidBodySet,
filter: &QueryFilter<'_>,
dispatcher: &dyn QueryDispatcher,
scoped_fn: impl FnOnce(RapierQueryPipeline<'_>) -> T,
) -> T {
let mut rapier_filter = RapierQueryFilter {
flags: filter.flags,
groups: filter.groups.map(CollisionGroups::into),
exclude_collider: filter
.exclude_collider
.and_then(|c| colliders.entity2collider.get(&c).copied()),
exclude_rigid_body: filter
.exclude_rigid_body
.and_then(|b| rigid_bodies.entity2body.get(&b).copied()),
predicate: None,
};
if let Some(predicate) = filter.predicate {
let wrapped_predicate = to_rapier_query_filter_predicate(predicate);
rapier_filter.predicate = Some(&wrapped_predicate);
let query_pipeline = broad_phase.as_query_pipeline(
dispatcher,
&rigid_bodies.bodies,
&colliders.colliders,
rapier_filter,
);
scoped_fn(RapierQueryPipeline { query_pipeline })
} else {
let query_pipeline = broad_phase.as_query_pipeline(
dispatcher,
&rigid_bodies.bodies,
&colliders.colliders,
rapier_filter,
);
scoped_fn(RapierQueryPipeline { query_pipeline })
}
}
pub fn collider_entity(&self, collider_handle: ColliderHandle) -> Entity {
RapierContextColliders::collider_entity_with_set(
self.query_pipeline.colliders,
collider_handle,
)
.unwrap()
}
pub fn cast_ray(
&self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
) -> Option<(Entity, Real)> {
let ray = Ray::new(ray_origin.into(), ray_dir.into());
let (h, toi) = self.query_pipeline.cast_ray(&ray, max_toi, solid)?;
Some((self.collider_entity(h), toi))
}
pub fn cast_ray_and_get_normal(
&self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
) -> Option<(Entity, RayIntersection)> {
let ray = Ray::new(ray_origin.into(), ray_dir.into());
let (h, result) = self
.query_pipeline
.cast_ray_and_get_normal(&ray, max_toi, solid)?;
Some((
self.collider_entity(h),
RayIntersection::from_rapier(result, ray_origin, ray_dir),
))
}
#[allow(clippy::too_many_arguments)]
pub fn intersect_ray(
&'a self,
ray_origin: Vect,
ray_dir: Vect,
max_toi: Real,
solid: bool,
) -> impl Iterator<Item = (Entity, RayIntersection)> + 'a {
let ray = Ray::new(ray_origin.into(), ray_dir.into());
self.query_pipeline.intersect_ray(ray, max_toi, solid).map(
move |(collider_handle, _, intersection)| {
(
self.collider_entity(collider_handle),
RayIntersection::from_rapier(intersection, ray_origin, ray_dir),
)
},
)
}
pub fn intersect_shape(
&'a self,
shape_pos: Vect,
shape_rot: Rot,
shape: &'a dyn Shape,
) -> impl Iterator<Item = Entity> + 'a {
let scaled_transform = (shape_pos, shape_rot).into();
self.query_pipeline
.intersect_shape(scaled_transform, shape)
.map(move |(collider_handle, _)| self.collider_entity(collider_handle))
}
pub fn project_point(
&self,
point: Vect,
max_dist: Real,
solid: bool,
) -> Option<(Entity, PointProjection)> {
let (h, result) = self
.query_pipeline
.project_point(&point.into(), max_dist, solid)?;
Some((
self.collider_entity(h),
PointProjection::from_rapier(result),
))
}
pub fn intersect_point(&'a self, point: Vect) -> impl Iterator<Item = Entity> + 'a {
self.query_pipeline
.intersect_point(point.into())
.map(move |(collider_handle, _)| self.collider_entity(collider_handle))
}
pub fn project_point_and_get_feature(
&self,
point: Vect,
) -> Option<(Entity, PointProjection, FeatureId)> {
let (h, proj, fid) = self
.query_pipeline
.project_point_and_get_feature(&point.into())?;
Some((
self.collider_entity(h),
PointProjection::from_rapier(proj),
fid,
))
}
pub fn intersect_aabb_conservative(
&'a self,
#[cfg(feature = "dim2")] aabb: bevy::math::bounding::Aabb2d,
#[cfg(feature = "dim3")] aabb: bevy::math::bounding::Aabb3d,
) -> impl Iterator<Item = Entity> + 'a {
let scaled_aabb = Aabb {
mins: aabb.min.into(),
maxs: aabb.max.into(),
};
self.query_pipeline
.intersect_aabb_conservative(scaled_aabb)
.map(move |(collider_handle, _)| self.collider_entity(collider_handle))
}
#[allow(clippy::too_many_arguments)]
pub fn cast_shape(
&'a self,
shape_pos: Vect,
shape_rot: Rot,
shape_vel: Vect,
shape: &dyn Shape,
options: ShapeCastOptions,
) -> Option<(Entity, ShapeCastHit)> {
let scaled_transform = (shape_pos, shape_rot).into();
let (h, result) =
self.query_pipeline
.cast_shape(&scaled_transform, &shape_vel.into(), shape, options)?;
Some((
self.collider_entity(h),
ShapeCastHit::from_rapier(result, options.compute_impact_geometry_on_penetration),
))
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Component, Default, Clone)]
pub struct RapierRigidBodySet {
pub bodies: RigidBodySet,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) entity2body: HashMap<Entity, RigidBodyHandle>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) last_body_transform_set: HashMap<RigidBodyHandle, GlobalTransform>,
}
impl RapierRigidBodySet {
pub fn entity2body(&self) -> &HashMap<Entity, RigidBodyHandle> {
&self.entity2body
}
pub fn rigid_body_entity(&self, handle: RigidBodyHandle) -> Option<Entity> {
self.bodies
.get(handle)
.map(|c| Entity::from_bits(c.user_data as u64))
}
pub fn propagate_modified_body_positions_to_colliders(
&self,
colliders: &mut RapierContextColliders,
) {
self.bodies
.propagate_modified_body_positions_to_colliders(&mut colliders.colliders);
}
pub fn impulse_revolute_joint_angle(
&self,
joints: &RapierContextJoints,
entity: Entity,
) -> Option<f32> {
let joint_handle = joints.entity2impulse_joint().get(&entity)?;
let impulse_joint = joints.impulse_joints.get(*joint_handle)?;
let revolute_joint = impulse_joint.data.as_revolute()?;
let rb1 = &self.bodies[impulse_joint.body1];
let rb2 = &self.bodies[impulse_joint.body2];
Some(revolute_joint.angle(rb1.rotation(), rb2.rotation()))
}
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Component)]
#[require(
RapierContextColliders,
RapierRigidBodySet,
RapierContextJoints,
SimulationToRenderTime
)]
pub struct RapierContextSimulation {
pub islands: IslandManager,
pub broad_phase: DefaultBroadPhase,
pub narrow_phase: NarrowPhase,
pub ccd_solver: CCDSolver,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub pipeline: PhysicsPipeline,
pub integration_parameters: IntegrationParameters,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) event_handler: Option<Box<dyn EventHandler>>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) deleted_colliders: HashMap<ColliderHandle, Entity>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) collision_events_to_send: Vec<CollisionEvent>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) contact_force_events_to_send: Vec<ContactForceEvent>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
pub(crate) character_collisions_collector: Vec<rapier::control::CharacterCollision>,
}
impl Default for RapierContextSimulation {
fn default() -> Self {
Self {
islands: IslandManager::new(),
broad_phase: DefaultBroadPhase::new(),
narrow_phase: NarrowPhase::new(),
ccd_solver: CCDSolver::new(),
pipeline: PhysicsPipeline::new(),
integration_parameters: IntegrationParameters::default(),
event_handler: None,
deleted_colliders: HashMap::default(),
collision_events_to_send: Vec::new(),
contact_force_events_to_send: Vec::new(),
character_collisions_collector: Vec::new(),
}
}
}
impl RapierContextSimulation {
#[allow(clippy::too_many_arguments)]
pub fn step_simulation(
&mut self,
colliders: &mut RapierContextColliders,
joints: &mut RapierContextJoints,
rigidbody_set: &mut RapierRigidBodySet,
gravity: Vect,
timestep_mode: TimestepMode,
events: Option<(
&MessageWriter<CollisionEvent>,
&MessageWriter<ContactForceEvent>,
)>,
hooks: &dyn PhysicsHooks,
time: &Time,
sim_to_render_time: &mut SimulationToRenderTime,
mut interpolation_query: Option<
&mut Query<(&RapierRigidBodyHandle, &mut TransformInterpolation)>,
>,
) {
let event_queue = if events.is_some() {
Some(EventQueue {
deleted_colliders: &self.deleted_colliders,
collision_events: RwLock::new(Vec::new()),
contact_force_events: RwLock::new(Vec::new()),
})
} else {
None
};
let event_handler = self
.event_handler
.as_deref()
.or_else(|| event_queue.as_ref().map(|q| q as &dyn EventHandler))
.unwrap_or(&() as &dyn EventHandler);
let mut executed_steps = 0;
match timestep_mode {
TimestepMode::Interpolated {
dt,
time_scale,
substeps,
} => {
self.integration_parameters.dt = dt;
sim_to_render_time.diff += time.delta_secs();
while sim_to_render_time.diff > 0.0 {
if sim_to_render_time.diff - dt <= 0.0 {
if let Some(interpolation_query) = interpolation_query.as_mut() {
for (handle, mut interpolation) in interpolation_query.iter_mut() {
if let Some(body) = rigidbody_set.bodies.get(handle.0) {
interpolation.start = Some(*body.position());
interpolation.end = None;
}
}
}
}
let mut substep_integration_parameters = self.integration_parameters;
substep_integration_parameters.dt = dt / (substeps as Real) * time_scale;
for _ in 0..substeps {
self.pipeline.step(
&gravity.into(),
&substep_integration_parameters,
&mut self.islands,
&mut self.broad_phase,
&mut self.narrow_phase,
&mut rigidbody_set.bodies,
&mut colliders.colliders,
&mut joints.impulse_joints,
&mut joints.multibody_joints,
&mut self.ccd_solver,
hooks,
event_handler,
);
executed_steps += 1;
}
sim_to_render_time.diff -= dt;
}
}
TimestepMode::Variable {
max_dt,
time_scale,
substeps,
} => {
self.integration_parameters.dt = (time.delta_secs() * time_scale).min(max_dt);
let mut substep_integration_parameters = self.integration_parameters;
substep_integration_parameters.dt /= substeps as Real;
for _ in 0..substeps {
self.pipeline.step(
&gravity.into(),
&substep_integration_parameters,
&mut self.islands,
&mut self.broad_phase,
&mut self.narrow_phase,
&mut rigidbody_set.bodies,
&mut colliders.colliders,
&mut joints.impulse_joints,
&mut joints.multibody_joints,
&mut self.ccd_solver,
hooks,
event_handler,
);
executed_steps += 1;
}
}
TimestepMode::Fixed { dt, substeps } => {
self.integration_parameters.dt = dt;
let mut substep_integration_parameters = self.integration_parameters;
substep_integration_parameters.dt = dt / (substeps as Real);
for _ in 0..substeps {
self.pipeline.step(
&gravity.into(),
&substep_integration_parameters,
&mut self.islands,
&mut self.broad_phase,
&mut self.narrow_phase,
&mut rigidbody_set.bodies,
&mut colliders.colliders,
&mut joints.impulse_joints,
&mut joints.multibody_joints,
&mut self.ccd_solver,
hooks,
event_handler,
);
executed_steps += 1;
}
}
}
if let Some(mut event_queue) = event_queue {
self.collision_events_to_send =
std::mem::take(event_queue.collision_events.get_mut().unwrap());
self.contact_force_events_to_send =
std::mem::take(event_queue.contact_force_events.get_mut().unwrap());
}
if executed_steps > 0 {
self.deleted_colliders.clear();
}
}
pub fn send_bevy_events(
&mut self,
collision_event_writer: &mut MessageWriter<CollisionEvent>,
contact_force_event_writer: &mut MessageWriter<ContactForceEvent>,
) {
for collision_event in self.collision_events_to_send.drain(..) {
collision_event_writer.write(collision_event);
}
for contact_force_event in self.contact_force_events_to_send.drain(..) {
contact_force_event_writer.write(contact_force_event);
}
}
#[allow(clippy::too_many_arguments)]
pub fn move_shape(
&mut self,
rapier_colliders: &RapierContextColliders,
rapier_query_pipeline: &mut RapierQueryPipelineMut<'_>,
movement: Vect,
shape: &dyn Shape,
shape_translation: Vect,
shape_rotation: Rot,
shape_mass: Real,
options: &MoveShapeOptions,
mut events: impl FnMut(CharacterCollision),
) -> MoveShapeOutput {
let up = options
.up
.try_into()
.expect("The up vector must be non-zero.");
let autostep = options.autostep.map(|autostep| CharacterAutostep {
max_height: autostep.max_height,
min_width: autostep.min_width,
include_dynamic_bodies: autostep.include_dynamic_bodies,
});
let controller = rapier::control::KinematicCharacterController {
up,
offset: options.offset,
slide: options.slide,
autostep,
max_slope_climb_angle: options.max_slope_climb_angle,
min_slope_slide_angle: options.min_slope_slide_angle,
snap_to_ground: options.snap_to_ground,
normal_nudge_factor: options.normal_nudge_factor,
};
self.character_collisions_collector.clear();
let dt = self.integration_parameters.dt;
let colliders = &rapier_colliders.colliders;
let collisions = &mut self.character_collisions_collector;
collisions.clear();
let result = controller.move_shape(
dt,
&rapier_query_pipeline.query_pipeline.as_ref(),
shape,
&(shape_translation, shape_rotation).into(),
movement.into(),
|c| {
if let Some(collision) = CharacterCollision::from_raw_with_set(colliders, &c, true)
{
events(collision);
}
collisions.push(c);
},
);
if options.apply_impulse_to_dynamic_bodies {
controller.solve_character_collision_impulses(
dt,
&mut rapier_query_pipeline.query_pipeline,
shape,
shape_mass,
collisions.iter(),
)
};
MoveShapeOutput {
effective_translation: result.translation.into(),
grounded: result.grounded,
is_sliding_down_slope: result.is_sliding_down_slope,
}
}
}