use super::{BroadPhaseBvh, BvhOptimizationStrategy};
use crate::alloc_prelude::*;
use crate::dynamics::{IntegrationParameters, RigidBodySet, RigidBodyType};
use crate::geometry::Collider;
use crate::geometry::{
Aabb, BroadPhasePairEvent, ColliderChanges, ColliderHandle, ColliderPair, ColliderSet,
};
use crate::math::Real;
use parry::partitioning::BvhLeafUpdateStatus;
impl BroadPhaseBvh {
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;
if self.deferred_optimize_pending {
self.deferred_optimize_pending = false;
super::run_bvh_optimize(&mut self.tree, &mut self.workspace);
}
for handle in removed_colliders {
self.tree.remove(handle.into_raw_parts().0);
}
let first_pass = self.tree.is_empty();
self.updated_colliders.clear();
self.curr_updated_leaves.clear();
for handle in self.pending_set_aabb.drain(..) {
if colliders.contains(handle) {
self.updated_colliders.push(handle);
self.curr_updated_leaves.push(handle.into_raw_parts().0);
}
}
let mut forced_reinsertion = false;
for handle in modified_colliders {
if let Some(co) = colliders.get(*handle) {
let leaf_index = handle.into_raw_parts().0;
if co.is_enabled()
&& co.changes.intersects(
ColliderChanges::PARENT | ColliderChanges::PARENT_EFFECTIVE_DOMINANCE,
)
&& self.tree.leaf_node(leaf_index).is_some()
{
self.tree.remove(leaf_index);
forced_reinsertion = true;
}
}
}
let mut update_scratch = core::mem::take(&mut self.update_scratch);
update_scratch.clear();
let compute_update = |modified: &ColliderHandle| -> Option<(ColliderHandle, Aabb, Real)> {
let collider = colliders.get(*modified)?;
if !collider.is_enabled()
|| !(collider.changes.needs_broad_phase_update()
|| collider
.changes
.contains(ColliderChanges::PARENT_EFFECTIVE_DOMINANCE))
{
return None;
}
let aabb = collider.compute_broad_phase_aabb(params, bodies);
if !(aabb.mins.is_finite() && aabb.maxs.is_finite()) {
return None;
}
let change_detection_skin = self.change_detection_skin(params, &aabb);
Some((*modified, aabb, change_detection_skin))
};
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
let precomputed: Vec<Vec<_>> = modified_colliders
.par_chunks(1024)
.map(|chunk| chunk.iter().filter_map(compute_update).collect())
.collect();
update_scratch.extend(precomputed.into_iter().flatten());
}
#[cfg(not(feature = "parallel"))]
update_scratch.extend(modified_colliders.iter().filter_map(compute_update));
let leaf_count = self.tree.leaf_count() as usize;
let use_reinsert =
self.reinsert_leaf_updates && update_scratch.len() * 16 < leaf_count && !first_pass;
#[cfg(feature = "parallel")]
let parallel_leaf_updates = !use_reinsert;
#[cfg(feature = "parallel")]
if parallel_leaf_updates {
self.update_batch_scratch.clear();
self.update_batch_scratch.extend(
update_scratch
.iter()
.map(|(handle, aabb, skin)| (*aabb, handle.into_raw_parts().0, *skin)),
);
self.tree.insert_or_update_batch_partially_parallel(
&self.update_batch_scratch,
&mut self.update_batch_statuses,
);
for ((modified, _, _), status) in
update_scratch.iter().zip(self.update_batch_statuses.iter())
{
let leaf_index = modified.into_raw_parts().0;
match status {
BvhLeafUpdateStatus::Unchanged => {}
BvhLeafUpdateStatus::UpdatedInPlace | BvhLeafUpdateStatus::Inserted => {
if *status == BvhLeafUpdateStatus::UpdatedInPlace {
self.changes_since_optimize =
self.changes_since_optimize.saturating_add(1);
}
self.updated_colliders.push(*modified);
self.curr_updated_leaves.push(leaf_index);
}
}
}
}
#[cfg(feature = "parallel")]
let sequential_leaf_updates = !parallel_leaf_updates;
#[cfg(not(feature = "parallel"))]
let sequential_leaf_updates = true;
#[allow(clippy::collapsible_if)]
if sequential_leaf_updates {
let mut deferred_inserts: Vec<usize> = Vec::new();
for (i, (modified, aabb, change_detection_skin)) in update_scratch.iter().enumerate() {
let leaf_index = modified.into_raw_parts().0;
let status = if use_reinsert {
self.tree.reinsert_or_update_if_present(
*aabb,
leaf_index,
*change_detection_skin,
)
} else {
self.tree
.update_partially_if_present(*aabb, leaf_index, *change_detection_skin)
};
let Some(status) = status else {
deferred_inserts.push(i);
continue;
};
match status {
BvhLeafUpdateStatus::Unchanged => {}
BvhLeafUpdateStatus::UpdatedInPlace | BvhLeafUpdateStatus::Inserted => {
if !use_reinsert && status == BvhLeafUpdateStatus::UpdatedInPlace {
self.changes_since_optimize =
self.changes_since_optimize.saturating_add(1);
}
self.updated_colliders.push(*modified);
self.curr_updated_leaves.push(leaf_index);
}
}
}
for i in deferred_inserts {
let (modified, aabb, change_detection_skin) = &update_scratch[i];
let leaf_index = modified.into_raw_parts().0;
let status =
self.tree
.insert_or_update_partially(*aabb, leaf_index, *change_detection_skin);
debug_assert_eq!(status, BvhLeafUpdateStatus::Inserted);
self.updated_colliders.push(*modified);
self.curr_updated_leaves.push(leaf_index);
}
}
self.update_scratch = update_scratch;
let num_updated = self.updated_colliders.len();
self.reinsert_leaf_updates = num_updated * 16 < self.tree.leaf_count() as usize;
self.changes_since_optimize = self
.changes_since_optimize
.saturating_add(removed_colliders.len() as u32);
let run_optimizer = self.changes_since_optimize > 0
&& (num_updated * 16 >= leaf_count
|| (self.frame_index % 8 == 0
&& self.changes_since_optimize as usize * 64 >= leaf_count))
&& self.optimization_strategy == BvhOptimizationStrategy::SubtreeOptimizer;
let must_full_refit = first_pass || !removed_colliders.is_empty() || forced_reinsertion;
let defer_optimize = run_optimizer && !must_full_refit;
if run_optimizer {
self.changes_since_optimize = 0;
}
if run_optimizer && !defer_optimize {
self.tree.optimize_incremental(&mut self.workspace);
}
let partial_refit_too_expensive =
(num_updated + self.prev_updated_leaves.len()) * 16 >= self.tree.leaf_count() as usize;
let full_refit =
must_full_refit || (run_optimizer && !defer_optimize) || partial_refit_too_expensive;
if full_refit {
#[cfg(feature = "parallel")]
self.tree.refit_parallel(&mut self.workspace);
#[cfg(not(feature = "parallel"))]
self.tree.refit(&mut self.workspace);
} else {
self.tree
.refit_partial(&self.prev_updated_leaves, &self.curr_updated_leaves);
}
core::mem::swap(&mut self.prev_updated_leaves, &mut self.curr_updated_leaves);
self.deferred_optimize_pending |= defer_optimize;
#[cfg(feature = "parallel")]
let candidates = self
.tree
.traverse_bvtt_single_tree_parallel::<{ Self::CHANGE_DETECTION_ENABLED }>();
#[cfg(not(feature = "parallel"))]
let candidates = {
let mut candidates = core::mem::take(&mut self.candidates_scratch);
candidates.clear();
self.tree
.traverse_bvtt_single_tree::<{ Self::CHANGE_DETECTION_ENABLED }>(
&mut self.workspace,
&mut |co1, co2| candidates.push((co1, co2)),
);
candidates
};
{
let filter_new =
|&(co1, co2): &(u32, u32)| -> Option<(ColliderHandle, ColliderHandle)> {
debug_assert_ne!(co1, co2);
let (mut collider1, mut handle1) = colliders.get_unknown_gen(co1)?;
let (mut collider2, mut handle2) = colliders.get_unknown_gen(co2)?;
if co1 > co2 {
core::mem::swap(&mut handle1, &mut handle2);
core::mem::swap(&mut collider1, &mut collider2);
}
if self.pairs.contains_key(&(handle1, handle2)) {
return None;
}
let rb_type = |co: &Collider| {
co.parent
.and_then(|p| bodies.get(p.handle))
.map(|rb| rb.body_type)
.unwrap_or(RigidBodyType::Fixed)
};
let rb_type1 = rb_type(collider1);
let rb_type2 = rb_type(collider2);
if !collider1
.flags
.active_collision_types
.test(rb_type1, rb_type2)
&& !collider2
.flags
.active_collision_types
.test(rb_type1, rb_type2)
{
return None;
}
Some((handle1, handle2))
};
#[cfg(feature = "parallel")]
let new_pairs: Vec<(ColliderHandle, ColliderHandle)> = {
use rayon::prelude::*;
candidates
.par_chunks(512)
.flat_map_iter(|chunk| chunk.iter().filter_map(filter_new))
.collect()
};
#[cfg(not(feature = "parallel"))]
let new_pairs: Vec<(ColliderHandle, ColliderHandle)> =
candidates.iter().filter_map(filter_new).collect();
for (handle1, handle2) in new_pairs {
let prev = self.pairs.insert((handle1, handle2), self.frame_index);
debug_assert!(prev.is_none());
self.pair_adjacency
.ensure_element_exist(handle1.0, Vec::new())
.push(handle2);
self.pair_adjacency
.ensure_element_exist(handle2.0, Vec::new())
.push(handle1);
events.push(BroadPhasePairEvent::AddPair(ColliderPair::new(
handle1, handle2,
)));
}
}
#[cfg(not(feature = "parallel"))]
{
self.candidates_scratch = candidates;
}
self.stale_pairs.clear();
for handle in removed_colliders {
if let Some(mut others) = self.pair_adjacency.remove(handle.0, Vec::new()) {
for other in others.drain(..) {
self.stale_pairs.push((*handle, other, false));
}
}
}
self.updated_mask.clear();
self.updated_mask.resize(
self.updated_colliders
.iter()
.map(|h| h.into_raw_parts().0 as usize + 1)
.max()
.unwrap_or(0),
false,
);
for handle in &self.updated_colliders {
self.updated_mask[handle.into_raw_parts().0 as usize] = true;
}
{
let tree = &self.tree;
let pair_adjacency = &self.pair_adjacency;
let updated_mask = &self.updated_mask;
let scan =
|handle: &ColliderHandle, out: &mut Vec<(ColliderHandle, ColliderHandle, bool)>| {
let Some(others) = pair_adjacency.get(handle.0) else {
return;
};
let node_self = tree.leaf_node(handle.into_raw_parts().0);
let self_index = handle.into_raw_parts().0;
for other in others {
let other_index = other.into_raw_parts().0;
if self_index > other_index
&& updated_mask
.get(other_index as usize)
.copied()
.unwrap_or(false)
{
continue;
}
let (h0, h1) = if self_index > other_index {
(*other, *handle)
} else {
(*handle, *other)
};
let Some(node0) = node_self else {
out.push((h0, h1, false));
continue;
};
let Some(node1) = tree.leaf_node(other_index) else {
out.push((h0, h1, false));
continue;
};
if (!Self::CHANGE_DETECTION_ENABLED
|| node0.is_changed()
|| node1.is_changed())
&& !node0.intersects(node1)
{
out.push((h0, h1, true));
}
}
};
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
let mut stale_pairs = core::mem::take(&mut self.stale_pairs);
stale_pairs.par_extend(self.updated_colliders.par_chunks(256).flat_map_iter(
|chunk| {
let mut out = Vec::new();
for handle in chunk {
scan(handle, &mut out);
}
out
},
));
self.stale_pairs = stale_pairs;
}
#[cfg(not(feature = "parallel"))]
{
let mut stale_pairs = core::mem::take(&mut self.stale_pairs);
for handle in &self.updated_colliders {
scan(handle, &mut stale_pairs);
}
self.stale_pairs = stale_pairs;
}
}
self.stale_pairs
.sort_unstable_by_key(|&(h0, h1, emit_event)| {
let a = h0.into_raw_parts().0;
let b = h1.into_raw_parts().0;
(a.min(b), a.max(b), emit_event)
});
for i in 0..self.stale_pairs.len() {
let (h0, h1, emit_event) = self.stale_pairs[i];
let (h0, h1) = if h0.into_raw_parts().0 > h1.into_raw_parts().0 {
(h1, h0)
} else {
(h0, h1)
};
if crate::utils::hashmap_remove(&mut self.pairs, &(h0, h1)).is_some() {
for (ha, hb) in [(h0, h1), (h1, h0)] {
if let Some(others) = self.pair_adjacency.get_mut(ha.0) {
if let Some(pos) = others.iter().position(|h| *h == hb) {
others.swap_remove(pos);
}
}
}
if emit_event {
events.push(BroadPhasePairEvent::DeletePair(ColliderPair::new(h0, h1)));
}
}
}
}
}