use crate::alloc_prelude::*;
use crate::data::Coarena;
use crate::dynamics::IntegrationParameters;
use crate::geometry::{Aabb, ColliderHandle};
use crate::math::Real;
use parry::partitioning::{Bvh, BvhLeafUpdateStatus, BvhWorkspace};
use parry::utils::hashmap::HashMap;
mod update;
#[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 = "serialize_pairs",
deserialize_with = "crate::utils::serde::deserialize_from_vec_tuple"
)
)]
pairs: HashMap<(ColliderHandle, ColliderHandle), u32>,
pair_adjacency: Coarena<Vec<ColliderHandle>>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
updated_colliders: Vec<ColliderHandle>,
#[cfg(not(feature = "parallel"))]
#[cfg_attr(feature = "serde-serialize", serde(skip))]
candidates_scratch: Vec<(u32, u32)>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
updated_mask: Vec<bool>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
stale_pairs: Vec<(ColliderHandle, ColliderHandle, bool)>,
prev_updated_leaves: Vec<u32>,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
curr_updated_leaves: Vec<u32>,
pending_set_aabb: Vec<ColliderHandle>,
changes_since_optimize: u32,
reinsert_leaf_updates: bool,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
update_scratch: Vec<(ColliderHandle, Aabb, Real)>,
#[cfg(feature = "parallel")]
#[cfg_attr(feature = "serde-serialize", serde(skip))]
update_batch_scratch: Vec<(Aabb, u32, Real)>,
#[cfg(feature = "parallel")]
#[cfg_attr(feature = "serde-serialize", serde(skip))]
update_batch_statuses: Vec<BvhLeafUpdateStatus>,
frame_index: u32,
optimization_strategy: BvhOptimizationStrategy,
#[cfg_attr(feature = "serde-serialize", serde(default))]
pub adaptive_change_detection_margin: bool,
#[cfg_attr(feature = "serde-serialize", serde(skip))]
deferred_optimize_pending: bool,
}
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
#[derive(Default, PartialEq, Eq, Copy, Clone)]
pub enum BvhOptimizationStrategy {
#[default]
SubtreeOptimizer,
None,
}
pub(crate) fn run_bvh_optimize(tree: &mut Bvh, workspace: &mut BvhWorkspace) {
tree.optimize_incremental(workspace);
#[cfg(feature = "parallel")]
tree.refit_without_resolve_parallel(workspace);
#[cfg(not(feature = "parallel"))]
tree.refit_without_resolve(workspace);
}
pub(crate) struct DeferredBvhOptimize {
tree: Bvh,
workspace: BvhWorkspace,
}
impl DeferredBvhOptimize {
pub(crate) fn run(&mut self) {
run_bvh_optimize(&mut self.tree, &mut self.workspace);
}
}
#[cfg(feature = "serde-serialize")]
fn serialize_pairs<S: serde::Serializer>(
pairs: &HashMap<(ColliderHandle, ColliderHandle), u32>,
s: S,
) -> Result<S::Ok, S::Error> {
crate::utils::serde::serialize_sorted_to_vec_tuple(
pairs,
|(a, b)| (a.into_raw_parts(), b.into_raw_parts()),
s,
)
}
impl BroadPhaseBvh {
const CHANGE_DETECTION_ENABLED: bool = true;
const CHANGE_DETECTION_FACTOR: Real = 4.0e-2;
const ADAPTIVE_CHANGE_DETECTION_CAP: Real = 0.25;
pub fn new() -> Self {
Self::default()
}
fn change_detection_skin(&self, params: &IntegrationParameters, aabb: &Aabb) -> Real {
if !Self::CHANGE_DETECTION_ENABLED {
0.0
} else if self.adaptive_change_detection_margin {
let min_extent = aabb.extents().min_element();
(min_extent * 0.125).clamp(
Self::CHANGE_DETECTION_FACTOR * params.length_unit,
Self::ADAPTIVE_CHANGE_DETECTION_CAP * params.length_unit,
)
} else {
Self::CHANGE_DETECTION_FACTOR * params.length_unit
}
}
pub fn with_optimization_strategy(optimization_strategy: BvhOptimizationStrategy) -> Self {
Self {
optimization_strategy,
..Default::default()
}
}
pub(crate) fn take_deferred_optimize(&mut self) -> Option<DeferredBvhOptimize> {
self.deferred_optimize_pending.then(|| {
self.deferred_optimize_pending = false;
DeferredBvhOptimize {
tree: core::mem::replace(&mut self.tree, Bvh::new()),
workspace: core::mem::take(&mut self.workspace),
}
})
}
pub(crate) fn finish_deferred_optimize(&mut self, task: DeferredBvhOptimize) {
self.tree = task.tree;
self.workspace = task.workspace;
}
pub fn set_aabb(&mut self, params: &IntegrationParameters, handle: ColliderHandle, aabb: Aabb) {
let change_detection_skin = self.change_detection_skin(params, &aabb);
let leaf_index = handle.into_raw_parts().0;
let status = if self.reinsert_leaf_updates {
self.tree.reinsert_or_update_with_change_detection(
aabb,
leaf_index,
change_detection_skin,
)
} else {
self.tree
.insert_with_change_detection(aabb, leaf_index, change_detection_skin)
};
match status {
BvhLeafUpdateStatus::Unchanged => {}
BvhLeafUpdateStatus::UpdatedInPlace | BvhLeafUpdateStatus::Inserted => {
if !self.reinsert_leaf_updates && status == BvhLeafUpdateStatus::UpdatedInPlace {
self.changes_since_optimize = self.changes_since_optimize.saturating_add(1);
}
self.pending_set_aabb.push(handle);
}
}
}
}
#[cfg(test)]
#[cfg(all(feature = "dim3", feature = "f32"))]
mod test {
#[allow(unused_imports)]
use crate::alloc_prelude::*;
use crate::math::Vector;
use crate::prelude::{
CCDSolver, ColliderBuilder, ColliderSet, DefaultBroadPhase, ImpulseJointSet,
IntegrationParameters, IslandManager, MultibodyJointSet, NarrowPhase, PhysicsPipeline,
RigidBodyBuilder, RigidBodySet,
};
#[test]
fn adaptive_change_detection_margin_smoke() {
let mut final_ys = [0.0; 2];
for (i, adaptive) in [false, true].into_iter().enumerate() {
let mut bodies = RigidBodySet::new();
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut pipeline = PhysicsPipeline::new();
let mut islands = IslandManager::new();
let mut broad_phase = DefaultBroadPhase::new();
broad_phase.adaptive_change_detection_margin = adaptive;
let mut narrow_phase = NarrowPhase::new();
let mut ccd = CCDSolver::new();
colliders.insert(ColliderBuilder::cuboid(10.0, 0.5, 10.0));
let ball =
bodies.insert(RigidBodyBuilder::dynamic().translation(Vector::new(0.0, 4.0, 0.0)));
colliders.insert_with_parent(ColliderBuilder::ball(0.5), ball, &mut bodies);
let params = IntegrationParameters::default();
for _ in 0..200 {
pipeline.step(
Vector::new(0.0, -9.81, 0.0),
¶ms,
&mut islands,
&mut broad_phase,
&mut narrow_phase,
&mut bodies,
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
&mut ccd,
&(),
&(),
);
}
final_ys[i] = bodies[ball].translation().y;
}
for y in final_ys {
assert!(
(y - 1.0).abs() < 0.02,
"ball did not rest on the floor: y = {y}"
);
}
}
}