use super::{
PAIR_HINT_COUNT_MASK, PAIR_HINT_DYN_BIT, clear_filtered_pair, pair_qualified_manifold_count,
single_manifold_bucket_drift,
};
#[cfg(not(feature = "parallel"))]
use crate::alloc_prelude::*;
use crate::dynamics::{
CoefficientCombineRule, ImpulseJointSet, MultibodyJointSet, RigidBodyDominance, RigidBodySet,
RigidBodyType,
};
use crate::geometry::{
BoundingVolume, ColliderChanges, ColliderSet, ContactData, ContactManifoldData, ContactPair,
SolverContact, SolverFlags,
};
use crate::math::{MAX_MANIFOLD_POINTS, Real};
use crate::pipeline::{ActiveHooks, ContactModificationContext, PairFilterContext, PhysicsHooks};
use parry::query::PersistentQueryDispatcher;
use parry::utils::PoseOpt;
pub(super) struct HintsPtr(pub(super) *mut u16);
unsafe impl Sync for HintsPtr {}
pub(super) type PairTransition = (
u32,
Option<crate::dynamics::RigidBodyHandle>,
Option<crate::dynamics::RigidBodyHandle>,
bool,
);
pub(super) const OUTCOME_SKIPPED: u8 = 0;
pub(super) const OUTCOME_RECYCLED: u8 = 1;
pub(super) const OUTCOME_FULL: u8 = 2;
pub(super) const OUTCOME_RECYCLED_REQUALIFIED: u8 = 3;
pub(super) const OUTCOME_FULL_CLEAN: u8 = 4;
pub(super) const OUTCOME_FULL_COMPOSITE: u8 = 5;
pub(super) const OUTCOME_CLEARED_IN_GRAPH: u8 = 6;
#[allow(clippy::too_many_arguments)]
pub(super) fn process_pair(
edge: &mut crate::data::graph::Edge<ContactPair>,
edge_id: u32,
prediction_distance: Real,
dt: Real,
contact_clustering: bool,
contact_recycle_distance: Real,
bodies: &RigidBodySet,
colliders: &ColliderSet,
impulse_joints: &ImpulseJointSet,
multibody_joints: &MultibodyJointSet,
hooks: &dyn PhysicsHooks,
query_dispatcher: &dyn PersistentQueryDispatcher<ContactManifoldData, ContactData>,
awake_body_mask: &[bool],
hints_ptr: &HintsPtr,
#[cfg(not(feature = "parallel"))] transitions: &mut Vec<PairTransition>,
#[cfg(feature = "parallel")] snd: &std::sync::mpsc::Sender<PairTransition>,
) -> u8 {
let pair = &mut edge.weight;
let co1 = &colliders[pair.collider1];
let co2 = &colliders[pair.collider2];
let body_awake = |co: &crate::geometry::Collider| {
co.parent.as_ref().is_some_and(|p| {
awake_body_mask
.get(p.handle.into_raw_parts().0 as usize)
.copied()
.unwrap_or(false)
})
};
if !co1.changes.needs_narrow_phase_update()
&& !co2.changes.needs_narrow_phase_update()
&& !body_awake(co1)
&& !body_awake(co2)
{
return OUTCOME_SKIPPED;
}
if contact_recycle_distance > 0.0 {
if let Some(state) = &pair.recycle_state {
let recycle_safe = ColliderChanges::IN_MODIFIED_SET
| ColliderChanges::POSITION
| ColliderChanges::LOCAL_MASS_PROPERTIES;
let hooks_involved = !(co1.flags.active_hooks | co2.flags.active_hooks).is_empty();
if ((co1.changes | co2.changes) & !recycle_safe).is_empty() && !hooks_involved {
let pos12 = co1.pos.inv_mul(&co2.pos);
let drift = crate::geometry::contact_pair::relative_pose_drift(
&state.pos12,
&pos12,
state.max_extent,
);
let rot_cos =
crate::geometry::contact_pair::relative_rot_cos(&state.rot1, &co1.pos.rotation)
.min(crate::geometry::contact_pair::relative_rot_cos(
&state.rot2,
&co2.pos.rotation,
));
if drift <= state.max_drift && rot_cos > 0.98 {
let mut requalified = false;
let hint = unsafe { &mut *hints_ptr.0.add(edge_id as usize) };
if *hint & PAIR_HINT_COUNT_MASK == 0 {
let dyn_awake = |co: &crate::geometry::Collider| {
co.parent.is_some_and(|p| {
let rb = &bodies[p.handle];
rb.body_type.is_dynamic() && !rb.activation.sleeping
})
};
let is_dyn = dyn_awake(co1) || dyn_awake(co2);
*hint = pair_qualified_manifold_count(pair)
| ((is_dyn as u16) * PAIR_HINT_DYN_BIT);
requalified =
*hint & PAIR_HINT_DYN_BIT != 0 && *hint & PAIR_HINT_COUNT_MASK != 0;
}
return if requalified {
OUTCOME_RECYCLED_REQUALIFIED
} else {
OUTCOME_RECYCLED
};
}
}
}
}
let had_any_active_contact = pair.has_any_active_contact();
let rb_handle1 = co1.parent.map(|p| p.handle);
let rb_handle2 = co2.parent.map(|p| p.handle);
let mut outcome = OUTCOME_SKIPPED;
'emit_events: {
if rb_handle1 == rb_handle2 && co1.parent.is_some() {
if clear_filtered_pair(pair) {
outcome = OUTCOME_CLEARED_IN_GRAPH;
}
break 'emit_events;
}
let rb1 = co1.parent.map(|co_parent1| &bodies[co_parent1.handle]);
let rb2 = co2.parent.map(|co_parent2| &bodies[co_parent2.handle]);
let rb_type1 = rb1.map(|rb| rb.body_type).unwrap_or(RigidBodyType::Fixed);
let rb_type2 = rb2.map(|rb| rb.body_type).unwrap_or(RigidBodyType::Fixed);
if let (Some(co_parent1), Some(co_parent2)) = (&co1.parent, &co2.parent) {
for (_, joint) in impulse_joints.joints_between(co_parent1.handle, co_parent2.handle) {
if !joint.data.contacts_enabled {
if clear_filtered_pair(pair) {
outcome = OUTCOME_CLEARED_IN_GRAPH;
}
break 'emit_events;
}
}
let link1 = multibody_joints.rigid_body_link(co_parent1.handle);
let link2 = multibody_joints.rigid_body_link(co_parent2.handle);
if let (Some(link1), Some(link2)) = (link1, link2) {
if link1.multibody == link2.multibody {
if let Some(mb) = multibody_joints.get_multibody(link1.multibody) {
if !mb.self_contacts_enabled() {
if clear_filtered_pair(pair) {
outcome = OUTCOME_CLEARED_IN_GRAPH;
}
break 'emit_events;
}
}
if let Some((_, _, mb_link)) =
multibody_joints.joint_between(co_parent1.handle, co_parent2.handle)
{
if !mb_link.joint.data.contacts_enabled {
if clear_filtered_pair(pair) {
outcome = OUTCOME_CLEARED_IN_GRAPH;
}
break 'emit_events;
}
}
}
}
}
if !co1.flags.active_collision_types.test(rb_type1, rb_type2)
&& !co2.flags.active_collision_types.test(rb_type1, rb_type2)
{
if clear_filtered_pair(pair) {
outcome = OUTCOME_CLEARED_IN_GRAPH;
}
break 'emit_events;
}
if !co1.flags.collision_groups.test(co2.flags.collision_groups) {
if clear_filtered_pair(pair) {
outcome = OUTCOME_CLEARED_IN_GRAPH;
}
break 'emit_events;
}
let active_hooks = co1.flags.active_hooks | co2.flags.active_hooks;
let mut solver_flags = if active_hooks.contains(ActiveHooks::FILTER_CONTACT_PAIRS) {
let context = PairFilterContext {
bodies,
colliders,
rigid_body1: rb_handle1,
rigid_body2: rb_handle2,
collider1: pair.collider1,
collider2: pair.collider2,
};
if let Some(solver_flags) = hooks.filter_contact_pair(&context) {
solver_flags
} else {
if clear_filtered_pair(pair) {
outcome = OUTCOME_CLEARED_IN_GRAPH;
}
break 'emit_events;
}
} else {
SolverFlags::default()
};
if !co1.flags.solver_groups.test(co2.flags.solver_groups) {
solver_flags.remove(SolverFlags::COMPUTE_IMPULSES);
}
if co1.changes.contains(ColliderChanges::SHAPE)
|| co2.changes.contains(ColliderChanges::SHAPE)
{
pair.workspace = None;
}
let pos12 = co1.pos.inv_mul(&co2.pos);
let contact_skin_sum = co1.contact_skin() + co2.contact_skin();
let soft_ccd_prediction1 = rb1.map(|rb| rb.soft_ccd_prediction()).unwrap_or(0.0);
let soft_ccd_prediction2 = rb2.map(|rb| rb.soft_ccd_prediction()).unwrap_or(0.0);
let effective_prediction_distance = if soft_ccd_prediction1 > 0.0
|| soft_ccd_prediction2 > 0.0
{
let aabb1 = co1.compute_collision_aabb(0.0);
let aabb2 = co2.compute_collision_aabb(0.0);
let inv_dt = crate::utils::inv(dt);
let linvel1 = rb1
.map(|rb| rb.linvel().clamp_length_max(soft_ccd_prediction1 * inv_dt))
.unwrap_or_default();
let linvel2 = rb2
.map(|rb| rb.linvel().clamp_length_max(soft_ccd_prediction2 * inv_dt))
.unwrap_or_default();
if !aabb1.intersects(&aabb2) && !aabb1.intersects_moving_aabb(&aabb2, linvel2 - linvel1)
{
if clear_filtered_pair(pair) {
outcome = OUTCOME_CLEARED_IN_GRAPH;
}
break 'emit_events;
}
prediction_distance.max(dt * (linvel1 - linvel2).length()) + contact_skin_sum
} else {
prediction_distance + contact_skin_sum
};
outcome = OUTCOME_FULL;
let _ = query_dispatcher.contact_manifolds(
&pos12,
&*co1.shape,
&*co2.shape,
effective_prediction_distance,
&mut pair.manifolds,
&mut pair.workspace,
);
let friction = CoefficientCombineRule::combine(
co1.material.friction,
co2.material.friction,
co1.material.friction_combine_rule,
co2.material.friction_combine_rule,
);
let restitution = CoefficientCombineRule::combine(
co1.material.restitution,
co2.material.restitution,
co1.material.restitution_combine_rule,
co2.material.restitution_combine_rule,
);
let zero = RigidBodyDominance(0); let dominance1 = rb1.map(|rb| rb.dominance).unwrap_or(zero);
let dominance2 = rb2.map(|rb| rb.dominance).unwrap_or(zero);
#[cfg(feature = "dim3")]
let use_clusters = contact_clustering && pair.manifolds.len() > 1;
#[cfg(not(feature = "dim3"))]
let use_clusters = {
let _ = contact_clustering;
false
};
#[cfg(feature = "dim3")]
if use_clusters {
core::mem::swap(&mut pair.solver_clusters, &mut pair.solver_clusters_prev);
crate::geometry::contact_clustering::cluster_manifolds_for_solver(
&pair.manifolds,
&pair.solver_clusters_prev,
&mut pair.solver_clusters,
prediction_distance,
);
for manifold in &mut pair.manifolds {
let world_pos1 = manifold.subshape_pos1().prepend_to(&co1.pos);
manifold.data.solver_contacts.clear();
manifold.data.rigid_body1 = rb_handle1;
manifold.data.rigid_body2 = rb_handle2;
manifold.data.solver_flags = solver_flags;
manifold.data.friction = friction;
manifold.data.restitution = restitution;
manifold.data.relative_dominance =
dominance1.effective_group(&rb_type1) - dominance2.effective_group(&rb_type2);
manifold.data.normal = world_pos1.rotation * manifold.local_n1;
}
} else if !pair.solver_clusters.is_empty() {
crate::geometry::contact_clustering::carry_warmstart_data(
&pair.solver_clusters,
&mut pair.manifolds,
prediction_distance,
);
pair.solver_clusters.clear();
pair.solver_clusters_prev.clear();
}
let solver_manifolds = if use_clusters {
&mut pair.solver_clusters
} else {
&mut pair.manifolds
};
for manifold in solver_manifolds {
let world_pos1 = manifold.subshape_pos1().prepend_to(&co1.pos);
let world_pos2 = manifold.subshape_pos2().prepend_to(&co2.pos);
manifold.data.solver_contacts.clear();
manifold.data.rigid_body1 = rb_handle1;
manifold.data.rigid_body2 = rb_handle2;
manifold.data.solver_flags = solver_flags;
manifold.data.friction = friction;
manifold.data.restitution = restitution;
manifold.data.relative_dominance =
dominance1.effective_group(&rb_type1) - dominance2.effective_group(&rb_type2);
manifold.data.normal = world_pos1.rotation * manifold.local_n1;
#[allow(unused_mut)] let mut selected = [0, 1, 2, 3];
#[allow(unused_mut)] let mut num_selected = MAX_MANIFOLD_POINTS.min(manifold.points.len());
#[cfg(feature = "dim3")]
crate::geometry::manifold_reduction::reduce_manifold_naive(
manifold,
&mut selected,
&mut num_selected,
prediction_distance,
);
#[cfg(all(feature = "dim3", feature = "block-solver"))]
if num_selected == 4 {
let p = |i: usize| manifold.points[selected[i]].local_p1;
let d2 = |a, b| (p(a) - p(b)).length_squared();
let far = if d2(0, 1) >= d2(0, 2) && d2(0, 1) >= d2(0, 3) {
1
} else if d2(0, 2) >= d2(0, 3) {
2
} else {
3
};
selected.swap(1, far);
}
#[cfg(all(feature = "dim3", not(feature = "block-solver")))]
if num_selected > 1 {
use crate::utils::OrthonormalBasis;
let basis = manifold.local_n1.orthonormal_basis();
let mut keyed: [(Real, Real, usize); MAX_MANIFOLD_POINTS] =
[(0.0, 0.0, 0); MAX_MANIFOLD_POINTS];
for (i, sel) in selected[..num_selected].iter().enumerate() {
let p = manifold.points[*sel].local_p1;
keyed[i] = (p.dot(basis[0]), p.dot(basis[1]), *sel);
}
for i in 1..num_selected {
let k = keyed[i];
let mut j = i;
while j > 0
&& (keyed[j - 1].0 > k.0 || (keyed[j - 1].0 == k.0 && keyed[j - 1].1 > k.1))
{
keyed[j] = keyed[j - 1];
j -= 1;
}
keyed[j] = k;
}
for (i, k) in keyed[..num_selected].iter().enumerate() {
selected[i] = k.2;
}
}
for contact_id in &selected[..num_selected] {
let contact = &manifold.points[*contact_id];
let effective_contact_dist = contact.dist - co1.contact_skin() - co2.contact_skin();
let keep_solver_contact = effective_contact_dist < prediction_distance || {
let world_pt1 = world_pos1 * contact.local_p1;
let world_pt2 = world_pos2 * contact.local_p2;
let vel1 = rb1
.map(|rb| rb.velocity_at_point(world_pt1))
.unwrap_or_default();
let vel2 = rb2
.map(|rb| rb.velocity_at_point(world_pt2))
.unwrap_or_default();
effective_contact_dist + (vel2 - vel1).dot(manifold.data.normal) * dt
< prediction_distance
};
if keep_solver_contact {
let world_pt1 = world_pos1 * contact.local_p1;
let world_pt2 = world_pos2 * contact.local_p2;
let is_new =
(contact.data.impulse == 0.0) as crate::geometry::contact_pair::ContactId;
let solver_contact = SolverContact {
contact_id: [*contact_id as crate::geometry::contact_pair::ContactId
| (is_new * crate::geometry::contact_pair::NEW_CONTACT_BIT)],
anchor1: world_pt1,
anchor2: world_pt2,
dist: effective_contact_dist,
tangent_velocity: Default::default(),
#[cfg(feature = "dim3")]
padding: Default::default(),
};
manifold.data.solver_contacts.push(solver_contact);
}
}
if active_hooks.contains(ActiveHooks::MODIFY_SOLVER_CONTACTS) {
let mut modifiable_solver_contacts =
core::mem::take(&mut manifold.data.solver_contacts);
let mut modifiable_user_data = manifold.data.user_data;
let mut modifiable_normal = manifold.data.normal;
let mut modifiable_friction = manifold.data.friction;
let mut modifiable_restitution = manifold.data.restitution;
let mut context = ContactModificationContext {
bodies,
colliders,
rigid_body1: rb_handle1,
rigid_body2: rb_handle2,
collider1: pair.collider1,
collider2: pair.collider2,
manifold,
solver_contacts: &mut modifiable_solver_contacts,
normal: &mut modifiable_normal,
friction: &mut modifiable_friction,
restitution: &mut modifiable_restitution,
user_data: &mut modifiable_user_data,
};
hooks.modify_solver_contacts(&mut context);
manifold.data.solver_contacts = modifiable_solver_contacts;
manifold.data.normal = modifiable_normal;
manifold.data.friction = modifiable_friction;
manifold.data.restitution = modifiable_restitution;
manifold.data.user_data = modifiable_user_data;
}
{
let normal = manifold.data.normal;
let rel_dom = manifold.data.relative_dominance;
let com_pose = |rb: &&crate::dynamics::RigidBody| {
rb.pos
.position
.prepend_translation(rb.mprops.local_mprops.local_com)
};
let com_pose1 = rb1.as_ref().filter(|_| rel_dom <= 0).map(com_pose);
let com_pose2 = rb2.as_ref().filter(|_| rel_dom >= 0).map(com_pose);
let manifold_points = &mut manifold.points;
for sc in &mut manifold.data.solver_contacts {
let shift = (sc.anchor2 - sc.anchor1).dot(normal) - sc.dist;
let p1 = sc.anchor1 + normal * shift;
let point = (p1 + sc.anchor2) * 0.5;
let cid = (sc.contact_id[0] & !crate::geometry::contact_pair::NEW_CONTACT_BIT)
as usize;
let pt_data = &mut manifold_points[cid].data;
pt_data.solver_dp1 = match &com_pose1 {
Some(pose) => point - pose.translation,
None => point,
};
pt_data.solver_dp2 = match &com_pose2 {
Some(pose) => point - pose.translation,
None => point,
};
sc.anchor1 = match &com_pose1 {
Some(pose) => pose.inverse_transform_point(p1),
None => p1,
};
if let Some(pose) = &com_pose2 {
sc.anchor2 = pose.inverse_transform_point(sc.anchor2);
}
}
}
}
if contact_recycle_distance > 0.0 {
let shapes_changed =
(co1.changes | co2.changes).contains(crate::geometry::ColliderChanges::SHAPE);
let max_extent = match &pair.recycle_state {
Some(state) if !shapes_changed => state.max_extent,
_ => {
let origin_radius = |co: &crate::geometry::Collider| {
let aabb = co.shape.compute_local_aabb();
aabb.mins.length().max(aabb.maxs.length())
};
origin_radius(co1).max(origin_radius(co2))
}
};
let max_drift = if pair.has_any_active_contact() {
contact_recycle_distance
} else {
contact_recycle_distance.min(prediction_distance)
};
pair.recycle_state = Some(crate::geometry::ContactRecycleState {
pos12,
rot1: co1.pos.rotation,
rot2: co2.pos.rotation,
max_extent,
max_drift,
});
}
}
let has_any_active_contact = pair.has_any_active_contact();
if has_any_active_contact != had_any_active_contact {
let transition = (edge_id, rb_handle1, rb_handle2, has_any_active_contact);
#[cfg(not(feature = "parallel"))]
transitions.push(transition);
#[cfg(feature = "parallel")]
let _ = snd.send(transition);
}
let mut membership_changed = true;
{
let dyn_awake = |co: &crate::geometry::Collider| {
co.parent.is_some_and(|p| {
let rb = &bodies[p.handle];
rb.body_type.is_dynamic() && !rb.activation.sleeping
})
};
let is_dyn = dyn_awake(co1) || dyn_awake(co2);
let new_hint = pair_qualified_manifold_count(pair) | ((is_dyn as u16) * PAIR_HINT_DYN_BIT);
let old_hint = unsafe { *hints_ptr.0.add(edge_id as usize) };
unsafe {
*hints_ptr.0.add(edge_id as usize) = new_hint;
}
if outcome == OUTCOME_FULL && old_hint == new_hint {
let selectable =
new_hint & PAIR_HINT_DYN_BIT != 0 && new_hint & PAIR_HINT_COUNT_MASK != 0;
membership_changed = match pair.solver_manifolds().len() {
0 => false,
1 => single_manifold_bucket_drift(pair, selectable),
_ => selectable,
};
}
}
if outcome == OUTCOME_FULL && pair.workspace.is_some() {
return OUTCOME_FULL_COMPOSITE;
}
if outcome == OUTCOME_FULL && !membership_changed {
return OUTCOME_FULL_CLEAN;
}
outcome
}