CCD contacts result in very strong, instantaneous, impulses. So it is preferable to attenuate their contribution to subsequent timesteps to avoid overshooting.
151 lines
4.1 KiB
Rust
151 lines
4.1 KiB
Rust
use crate::dynamics::{IntegrationParameters, RigidBody, RigidBodyHandle};
|
|
use crate::geometry::{Collider, ColliderHandle};
|
|
use crate::math::Real;
|
|
use parry::query::{NonlinearRigidMotion, QueryDispatcher};
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct TOIEntry {
|
|
pub toi: Real,
|
|
pub c1: ColliderHandle,
|
|
pub b1: RigidBodyHandle,
|
|
pub c2: ColliderHandle,
|
|
pub b2: RigidBodyHandle,
|
|
pub is_intersection_test: bool,
|
|
pub timestamp: usize,
|
|
}
|
|
|
|
impl TOIEntry {
|
|
fn new(
|
|
toi: Real,
|
|
c1: ColliderHandle,
|
|
b1: RigidBodyHandle,
|
|
c2: ColliderHandle,
|
|
b2: RigidBodyHandle,
|
|
is_intersection_test: bool,
|
|
timestamp: usize,
|
|
) -> Self {
|
|
Self {
|
|
toi,
|
|
c1,
|
|
b1,
|
|
c2,
|
|
b2,
|
|
is_intersection_test,
|
|
timestamp,
|
|
}
|
|
}
|
|
|
|
pub fn try_from_colliders<QD: ?Sized + QueryDispatcher>(
|
|
params: &IntegrationParameters,
|
|
query_dispatcher: &QD,
|
|
ch1: ColliderHandle,
|
|
ch2: ColliderHandle,
|
|
c1: &Collider,
|
|
c2: &Collider,
|
|
b1: &RigidBody,
|
|
b2: &RigidBody,
|
|
frozen1: Option<Real>,
|
|
frozen2: Option<Real>,
|
|
start_time: Real,
|
|
end_time: Real,
|
|
) -> Option<Self> {
|
|
assert!(start_time <= end_time);
|
|
|
|
let linvel1 = frozen1.is_none() as u32 as Real * b1.linvel;
|
|
let linvel2 = frozen2.is_none() as u32 as Real * b2.linvel;
|
|
|
|
let vel12 = linvel2 - linvel1;
|
|
let thickness = c1.shape().ccd_thickness() + c2.shape().ccd_thickness();
|
|
|
|
if params.dt * vel12.norm() < thickness {
|
|
return None;
|
|
}
|
|
|
|
let is_intersection_test = c1.is_sensor() || c2.is_sensor();
|
|
|
|
// Compute the TOI.
|
|
let mut motion1 = Self::body_motion(params.dt, b1);
|
|
let mut motion2 = Self::body_motion(params.dt, b2);
|
|
|
|
if let Some(t) = frozen1 {
|
|
motion1.freeze(t);
|
|
}
|
|
|
|
if let Some(t) = frozen2 {
|
|
motion2.freeze(t);
|
|
}
|
|
|
|
let motion_c1 = motion1.prepend(*c1.position_wrt_parent());
|
|
let motion_c2 = motion2.prepend(*c2.position_wrt_parent());
|
|
|
|
// println!("start_time: {}", start_time);
|
|
|
|
// If this is just an intersection test (i.e. with sensors)
|
|
// then we can stop the TOI search immediately if it starts with
|
|
// a penetration because we don't care about the whether the velocity
|
|
// at the impact is a separating velocity or not.
|
|
// If the TOI search involves two non-sensor colliders then
|
|
// we don't want to stop the TOI search at the first penetration
|
|
// because the colliders may be in a separating trajectory.
|
|
let stop_at_penetration = is_intersection_test;
|
|
|
|
let res_toi = query_dispatcher
|
|
.nonlinear_time_of_impact(
|
|
&motion_c1,
|
|
c1.shape(),
|
|
&motion_c2,
|
|
c2.shape(),
|
|
start_time,
|
|
end_time,
|
|
stop_at_penetration,
|
|
)
|
|
.ok();
|
|
|
|
let toi = res_toi??;
|
|
|
|
Some(Self::new(
|
|
toi.toi,
|
|
ch1,
|
|
c1.parent(),
|
|
ch2,
|
|
c2.parent(),
|
|
is_intersection_test,
|
|
0,
|
|
))
|
|
}
|
|
|
|
fn body_motion(dt: Real, body: &RigidBody) -> NonlinearRigidMotion {
|
|
if body.should_resolve_ccd(dt) {
|
|
NonlinearRigidMotion::new(
|
|
0.0,
|
|
body.position,
|
|
body.mass_properties.local_com,
|
|
body.linvel,
|
|
body.angvel,
|
|
)
|
|
} else {
|
|
NonlinearRigidMotion::constant_position(body.next_position)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for TOIEntry {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
(-self.toi).partial_cmp(&(-other.toi))
|
|
}
|
|
}
|
|
|
|
impl Ord for TOIEntry {
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
self.partial_cmp(other).unwrap()
|
|
}
|
|
}
|
|
|
|
impl PartialEq for TOIEntry {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.toi == other.toi
|
|
}
|
|
}
|
|
|
|
impl Eq for TOIEntry {}
|