use crate::alloc_prelude::*;
use crate::dynamics::{IntegrationParameters, IslandManager, RigidBodySet};
use crate::geometry::{
BroadPhaseBvh, Collider, ColliderHandle, ColliderSet, CollisionEvent, NarrowPhase,
};
use crate::math::Real;
use crate::parry::bounding_volume::Aabb;
use crate::pipeline::{EventHandler, PhysicsHooks, QueryFilter};
use crate::prelude::{ActiveEvents, CollisionEventFlags};
use parry::query::sweep_toi::Sweep;
use super::sweeps::{
BodyContinuousResult, CcdTargets, PseudoHitMode, collect_fixed_targets, is_bullet,
map_bodies_parallel, sweep_fast_body,
};
#[derive(Clone, Default)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct CCDSolver {
#[cfg_attr(feature = "serde-serialize", serde(skip))]
fixed_targets_cache: Option<FixedTargetsCache>,
}
type FixedTargetsCache = (Real, Option<Vec<(ColliderHandle, Aabb)>>);
impl CCDSolver {
pub fn new() -> Self {
Self::default()
}
pub fn update_ccd_active_flags(
&self,
islands: &IslandManager,
bodies: &mut RigidBodySet,
dt: Real,
include_forces: bool,
) -> bool {
let mut ccd_active = false;
for handle in islands.active_bodies() {
let rb = bodies.index_mut_internal(handle);
if rb.is_dynamic() {
let moving_fast = if include_forces {
rb.ccd.is_moving_fast(
dt,
&rb.ccd_vels,
Some(&rb.forces),
rb.mprops.max_extent(),
)
} else {
rb.ccd.is_moving_fast_with_next_position(
dt,
&rb.ccd_vels,
&rb.pos,
rb.mprops.local_mprops.local_com,
rb.mprops.max_extent(),
)
};
rb.ccd.ccd_active = moving_fast;
ccd_active = ccd_active || moving_fast;
}
}
ccd_active
}
#[profiling::function]
#[allow(clippy::too_many_arguments)]
pub fn find_first_impact(
&mut self,
dt: Real, params: &IntegrationParameters,
islands: &IslandManager,
bodies: &RigidBodySet,
colliders: &ColliderSet,
broad_phase: &mut BroadPhaseBvh,
narrow_phase: &NarrowPhase,
hooks: &dyn PhysicsHooks,
) -> Option<Real> {
let query_pipeline = broad_phase.as_query_pipeline(
narrow_phase.query_dispatcher(),
bodies,
colliders,
QueryFilter::default(),
);
let (bvh, dispatcher) = (query_pipeline.bvh, query_pipeline.dispatcher);
let linear_slop = params.allowed_linear_error();
let fast_bodies: Vec<_> = islands
.active_bodies()
.filter(|h| bodies[*h].ccd.ccd_active)
.collect();
let fractions = map_bodies_parallel(&fast_bodies, hooks, |handle, hooks| {
let rb1 = &bodies[handle];
let predicted_body_pos =
rb1.pos
.integrate_forces_and_velocities(dt, &rb1.forces, &rb1.vels, &rb1.mprops);
sweep_fast_body(
handle,
bodies,
colliders,
predicted_body_pos,
CcdTargets::FullBvh(bvh),
dispatcher,
hooks,
dt,
linear_slop,
PseudoHitMode::Ignore,
)
.fraction
});
let min_fraction = fractions.into_iter().fold(1.0, Real::min);
(min_fraction < 1.0).then_some(min_fraction * dt)
}
#[profiling::function]
#[allow(clippy::too_many_arguments)]
pub fn solve_continuous(
&mut self,
params: &IntegrationParameters,
islands: &IslandManager,
bodies: &mut RigidBodySet,
colliders: &ColliderSet,
broad_phase: &mut BroadPhaseBvh,
narrow_phase: &NarrowPhase,
hooks: &dyn PhysicsHooks,
events: &dyn EventHandler,
scene_changed: bool,
) {
let dt = params.dt;
let linear_slop = params.allowed_linear_error();
let (non_bullets, bullets): (Vec<_>, Vec<_>) = islands
.active_bodies()
.filter(|h| bodies[*h].ccd.ccd_active)
.partition(|h| !is_bullet(&bodies[*h]));
let mut all_results = Vec::new();
{
let query_pipeline = broad_phase.as_query_pipeline(
narrow_phase.query_dispatcher(),
bodies,
colliders,
QueryFilter::default(),
);
let (bvh, dispatcher) = (query_pipeline.bvh, query_pipeline.dispatcher);
let prediction = params.prediction_distance();
let cache_valid = !scene_changed
&& self
.fixed_targets_cache
.as_ref()
.is_some_and(|(p, _)| *p == prediction);
if !cache_valid {
self.fixed_targets_cache = Some((
prediction,
collect_fixed_targets(bodies, colliders, prediction),
));
}
let targets = match &self.fixed_targets_cache.as_ref().unwrap().1 {
Some(fixed) => CcdTargets::FixedList(fixed),
None => CcdTargets::FullBvh(bvh),
};
let results = map_bodies_parallel(&non_bullets, hooks, |handle, hooks| {
sweep_fast_body(
handle,
bodies,
colliders,
bodies[handle].pos.next_position,
targets,
dispatcher,
hooks,
dt,
linear_slop,
PseudoHitMode::Record,
)
});
all_results.extend(results);
}
Self::apply_clamps(bodies, &all_results);
if !bullets.is_empty() {
let bullet_results = {
let query_pipeline = broad_phase.as_query_pipeline(
narrow_phase.query_dispatcher(),
bodies,
colliders,
QueryFilter::default(),
);
let (bvh, dispatcher) = (query_pipeline.bvh, query_pipeline.dispatcher);
map_bodies_parallel(&bullets, hooks, |handle, hooks| {
sweep_fast_body(
handle,
bodies,
colliders,
bodies[handle].pos.next_position,
CcdTargets::FullBvh(bvh),
dispatcher,
hooks,
dt,
linear_slop,
PseudoHitMode::Record,
)
})
};
Self::apply_clamps(bodies, &bullet_results);
all_results.extend(bullet_results);
}
for result in &all_results {
for hit in &result.pseudo_hits {
if hit.fraction >= result.fraction {
continue;
}
let co1 = &colliders[hit.ch1];
let co2 = &colliders[hit.ch2];
if !co1.is_sensor() && !co2.is_sensor() {
continue;
}
let next_pose = |co: &Collider| match co.parent.as_ref() {
Some(parent) => bodies[parent.handle].pos.next_position * parent.pos_wrt_parent,
None => co.pos.0,
};
let prev_pos12 = co1.pos.inv_mul(&co2.pos);
let next_pos12 = next_pose(co1).inv_mul(&next_pose(co2));
let dispatcher = narrow_phase.query_dispatcher();
let intersect_before = dispatcher
.intersection_test(&prev_pos12, co1.shape.as_ref(), co2.shape.as_ref())
.unwrap_or(false);
let intersect_after = dispatcher
.intersection_test(&next_pos12, co1.shape.as_ref(), co2.shape.as_ref())
.unwrap_or(false);
if !intersect_before
&& !intersect_after
&& (co1.flags.active_events | co2.flags.active_events)
.contains(ActiveEvents::COLLISION_EVENTS)
{
events.handle_collision_event(
bodies,
colliders,
CollisionEvent::Started(hit.ch1, hit.ch2, CollisionEventFlags::SENSOR),
None,
);
events.handle_collision_event(
bodies,
colliders,
CollisionEvent::Stopped(hit.ch1, hit.ch2, CollisionEventFlags::SENSOR),
None,
);
}
}
}
}
fn apply_clamps(bodies: &mut RigidBodySet, results: &[BodyContinuousResult]) {
for result in results {
if result.fraction < 1.0 {
let rb = bodies.index_mut_internal(result.handle);
let sweep = Sweep::from_poses(
&rb.pos.position,
&rb.pos.next_position,
rb.mprops.local_mprops.local_com,
);
rb.pos.next_position = sweep.transform_at(result.fraction);
}
}
}
}