use crate::dynamics::{IntegrationParameters, RigidBodySet};
use crate::geometry::{Aabb, BroadPhasePairEvent, ColliderHandle, ColliderPair, ColliderSet};
use crate::math::Real;
use parry::partitioning::{Bvh, BvhWorkspace};
use parry::utils::hashmap::{Entry, HashMap};
#[derive(Default, Clone)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct BroadPhaseBvh {
pub(crate) tree: Bvh,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
workspace: BvhWorkspace,
#[cfg_attr(
feature = "serde-serialize",
serde(
serialize_with = "crate::utils::serde::serialize_to_vec_tuple",
deserialize_with = "crate::utils::serde::deserialize_from_vec_tuple"
)
)]
pairs: HashMap<(ColliderHandle, ColliderHandle), u32>,
frame_index: u32,
optimization_strategy: BvhOptimizationStrategy,
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Default, PartialEq, Eq, Copy, Clone)]
pub enum BvhOptimizationStrategy {
#[default]
SubtreeOptimizer,
None,
}
const ENABLE_TREE_VALIDITY_CHECK: bool = false;
impl BroadPhaseBvh {
const CHANGE_DETECTION_ENABLED: bool = true;
const CHANGE_DETECTION_FACTOR: Real = 1.0e-2;
pub fn new() -> Self {
Self::default()
}
pub fn with_optimization_strategy(optimization_strategy: BvhOptimizationStrategy) -> Self {
Self {
optimization_strategy,
..Default::default()
}
}
pub fn update(
&mut self,
params: &IntegrationParameters,
colliders: &ColliderSet,
bodies: &RigidBodySet,
modified_colliders: &[ColliderHandle],
removed_colliders: &[ColliderHandle],
events: &mut Vec<BroadPhasePairEvent>,
) {
self.frame_index = self.frame_index.overflowing_add(1).0;
for handle in removed_colliders {
self.tree.remove(handle.into_raw_parts().0);
}
let first_pass = self.tree.is_empty();
for modified in modified_colliders {
if let Some(collider) = colliders.get(*modified) {
if !collider.is_enabled() || !collider.changes.needs_broad_phase_update() {
continue;
}
let aabb = collider.compute_broad_phase_aabb(params, bodies);
let change_detection_skin = if Self::CHANGE_DETECTION_ENABLED {
Self::CHANGE_DETECTION_FACTOR * params.length_unit
} else {
0.0
};
self.tree.insert_or_update_partially(
aabb,
modified.into_raw_parts().0,
change_detection_skin,
);
}
}
if ENABLE_TREE_VALIDITY_CHECK {
if first_pass {
self.tree.assert_well_formed();
}
self.tree.assert_well_formed_topology_only();
}
match self.optimization_strategy {
BvhOptimizationStrategy::SubtreeOptimizer => {
self.tree.optimize_incremental(&mut self.workspace);
}
BvhOptimizationStrategy::None => {}
};
self.tree.refit(&mut self.workspace);
if ENABLE_TREE_VALIDITY_CHECK {
self.tree.assert_well_formed();
}
let mut pairs_collector = |co1: u32, co2: u32| {
assert_ne!(co1, co2);
let Some((_, mut handle1)) = colliders.get_unknown_gen(co1) else {
return;
};
let Some((_, mut handle2)) = colliders.get_unknown_gen(co2) else {
return;
};
if co1 > co2 {
std::mem::swap(&mut handle1, &mut handle2);
}
match self.pairs.entry((handle1, handle2)) {
Entry::Occupied(e) => *e.into_mut() = self.frame_index,
Entry::Vacant(e) => {
e.insert(self.frame_index);
events.push(BroadPhasePairEvent::AddPair(ColliderPair::new(
handle1, handle2,
)));
}
}
};
self.tree
.traverse_bvtt_single_tree::<{ Self::CHANGE_DETECTION_ENABLED }>(
&mut self.workspace,
&mut pairs_collector,
);
self.pairs.retain(|(h0, h1), timestamp| {
if *timestamp != self.frame_index {
if !colliders.contains(*h0) || !colliders.contains(*h1) {
return false;
}
let Some(node0) = self.tree.leaf_node(h0.into_raw_parts().0) else {
return false;
};
let Some(node1) = self.tree.leaf_node(h1.into_raw_parts().0) else {
return false;
};
if (!Self::CHANGE_DETECTION_ENABLED || node0.is_changed() || node1.is_changed())
&& !node0.intersects(node1)
{
events.push(BroadPhasePairEvent::DeletePair(ColliderPair::new(*h0, *h1)));
false
} else {
true
}
} else {
true
}
});
}
pub fn set_aabb(&mut self, params: &IntegrationParameters, handle: ColliderHandle, aabb: Aabb) {
let change_detection_skin = if Self::CHANGE_DETECTION_ENABLED {
Self::CHANGE_DETECTION_FACTOR * params.length_unit
} else {
0.0
};
self.tree.insert_with_change_detection(
aabb,
handle.into_raw_parts().0,
change_detection_skin,
);
}
}