use alloc::vec::Vec;
use crate::LayerMask;
use crate::fanout::Fanout;
use super::aabb::Aabb;
const MIN_FANOUT_PROXIES: usize = 192;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Role {
Static,
Driven,
Dynamic,
Sensor,
}
impl Role {
fn responds(self) -> bool {
self == Role::Dynamic
}
fn crosses(self) -> bool {
self != Role::Static
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Kind {
Contact,
Sensor,
}
fn reportable(a: Role, b: Role) -> Option<Kind> {
if a == Role::Sensor || b == Role::Sensor {
return (a.crosses() && b.crosses()).then_some(Kind::Sensor);
}
(a.responds() || b.responds()).then_some(Kind::Contact)
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Proxy {
pub(crate) bounds: Aabb,
pub(crate) mask: LayerMask,
pub(crate) role: Role,
}
pub(crate) type Pair = (u32, u32);
#[derive(Debug, Clone, Copy)]
pub(crate) struct Pairs<'a> {
pub(crate) contacts: &'a [Pair],
pub(crate) sensors: &'a [Pair],
}
#[derive(Debug, Default)]
struct Scan {
from: usize,
to: usize,
contacts: Vec<Pair>,
sensors: Vec<Pair>,
}
pub(crate) struct SweepPrune {
proxies: Vec<Proxy>,
order: Vec<u32>,
axis: usize,
pairs: Vec<Pair>,
sensor_pairs: Vec<Pair>,
max_extent: f32,
dirty: bool,
scans: Vec<Scan>,
}
impl SweepPrune {
pub(crate) fn with_capacity(capacity: usize) -> Self {
let empty = Proxy {
bounds: Aabb::EMPTY,
mask: LayerMask {
memberships: 0,
filter: 0,
},
role: Role::Static,
};
SweepPrune {
proxies: alloc::vec![empty; capacity],
order: Vec::with_capacity(capacity),
axis: 0,
pairs: Vec::with_capacity(capacity * 4),
sensor_pairs: Vec::with_capacity(capacity),
max_extent: 0.0,
dirty: true,
scans: Vec::new(),
}
}
pub(crate) fn reserve_workers(&mut self, workers: usize, capacity: usize) {
self.scans.clear();
if workers < 2 {
return;
}
let share = (capacity * 4).div_ceil(workers);
self.scans.resize_with(workers, || Scan {
from: 0,
to: 0,
contacts: Vec::with_capacity(share),
sensors: Vec::with_capacity(capacity.div_ceil(workers)),
});
}
pub(crate) fn insert(&mut self, slot: u32) {
debug_assert!(!self.order.contains(&slot), "slot {slot} added twice");
self.order.push(slot);
self.dirty = true;
}
pub(crate) fn remove(&mut self, slot: u32) {
if let Some(at) = self.order.iter().position(|&s| s == slot) {
self.order.remove(at);
self.dirty = true;
}
}
pub(crate) fn set_proxy(&mut self, slot: u32, proxy: Proxy) {
self.proxies[slot as usize] = proxy;
self.dirty = true;
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.order.len()
}
pub(crate) fn pair_count(&self) -> usize {
self.pairs.len() + self.sensor_pairs.len()
}
pub(crate) fn axis(&self) -> usize {
self.axis
}
pub(crate) fn proxy(&self, slot: u32) -> &Proxy {
&self.proxies[slot as usize]
}
pub(crate) fn slab_window(&self, low: f32, high: f32) -> &[u32] {
if self.dirty {
return &self.order;
}
let axis = self.axis;
let key = |&slot: &u32| self.proxies[slot as usize].bounds.min.get(axis);
let start = self
.order
.partition_point(|s| key(s) < low - self.max_extent);
let end = self.order.partition_point(|s| key(s) <= high);
if end <= start {
return &[];
}
&self.order[start..end]
}
pub(crate) fn reserved_bytes(&self) -> u64 {
let scans: usize = self
.scans
.iter()
.map(|scan| (scan.contacts.capacity() + scan.sensors.capacity()) * size_of::<Pair>())
.sum();
(self.proxies.capacity() * size_of::<Proxy>()
+ self.order.capacity() * size_of::<u32>()
+ (self.pairs.capacity() + self.sensor_pairs.capacity()) * size_of::<Pair>()
+ scans) as u64
}
pub(crate) fn sweep(&mut self, fanout: &impl Fanout, workers: usize) -> Pairs<'_> {
if self.dirty {
self.choose_axis();
self.sort();
self.collect_pairs(fanout, workers);
self.dirty = false;
}
Pairs {
contacts: &self.pairs,
sensors: &self.sensor_pairs,
}
}
fn choose_axis(&mut self) {
let count = self.order.len();
if count < 2 {
return;
}
let inv = 1.0 / count as f32;
let mut sum = [0.0f32; 3];
let mut sum_sq = [0.0f32; 3];
for &slot in &self.order {
let center = self.proxies[slot as usize].bounds.center();
for (axis, (s, sq)) in sum.iter_mut().zip(sum_sq.iter_mut()).enumerate() {
let v = center.get(axis);
*s += v;
*sq += v * v;
}
}
let mut best = 0;
let mut best_variance = f32::NEG_INFINITY;
for axis in 0..3 {
let mean = sum[axis] * inv;
let variance = sum_sq[axis] * inv - mean * mean;
if variance > best_variance {
best_variance = variance;
best = axis;
}
}
if best != self.axis {
self.axis = best;
let axis = self.axis;
let proxies = &self.proxies;
self.order.sort_unstable_by(|&x, &y| {
let kx = proxies[x as usize].bounds.min.get(axis);
let ky = proxies[y as usize].bounds.min.get(axis);
kx.total_cmp(&ky).then(x.cmp(&y))
});
}
}
fn sort(&mut self) {
let axis = self.axis;
self.max_extent = self.order.iter().fold(0.0f32, |widest, &slot| {
let bounds = self.proxies[slot as usize].bounds;
widest.max(bounds.max.get(axis) - bounds.min.get(axis))
});
let key = |proxies: &[Proxy], slot: u32| proxies[slot as usize].bounds.min.get(axis);
for i in 1..self.order.len() {
let slot = self.order[i];
let k = key(&self.proxies, slot);
let mut j = i;
while j > 0 {
let prev = self.order[j - 1];
let kp = key(&self.proxies, prev);
if kp < k || (kp == k && prev <= slot) {
break;
}
self.order[j] = prev;
j -= 1;
}
self.order[j] = slot;
}
}
fn collect_pairs(&mut self, fanout: &impl Fanout, workers: usize) {
self.pairs.clear();
self.sensor_pairs.clear();
let count = self.order.len();
let workers = workers.min(self.scans.len());
if workers < 2 || count < MIN_FANOUT_PROXIES {
let SweepPrune {
proxies,
order,
axis,
pairs,
sensor_pairs,
..
} = self;
scan_range(proxies, order, *axis, 0, count, pairs, sensor_pairs);
} else {
let SweepPrune {
proxies,
order,
axis,
pairs,
sensor_pairs,
scans,
..
} = self;
let axis = *axis;
let share = count.div_ceil(workers);
let scans = &mut scans[..workers];
for (index, scan) in scans.iter_mut().enumerate() {
scan.from = (index * share).min(count);
scan.to = ((index + 1) * share).min(count);
}
fanout.for_each(scans, |scan| {
scan.contacts.clear();
scan.sensors.clear();
scan_range(
proxies,
order,
axis,
scan.from,
scan.to,
&mut scan.contacts,
&mut scan.sensors,
);
});
for scan in scans.iter() {
pairs.extend_from_slice(&scan.contacts);
sensor_pairs.extend_from_slice(&scan.sensors);
}
}
self.pairs.sort_unstable();
self.sensor_pairs.sort_unstable();
}
}
fn scan_range(
proxies: &[Proxy],
order: &[u32],
axis: usize,
from: usize,
to: usize,
contacts: &mut Vec<Pair>,
sensors: &mut Vec<Pair>,
) {
for i in from..to {
let a = order[i];
let pa = proxies[a as usize];
let reach = pa.bounds.max.get(axis);
for &b in &order[i + 1..] {
let pb = proxies[b as usize];
if pb.bounds.min.get(axis) > reach {
break;
}
let Some(kind) = reportable(pa.role, pb.role) else {
continue;
};
if !pa.mask.interacts_with(pb.mask) || !pa.bounds.overlaps(pb.bounds) {
continue;
}
let pair = if a < b { (a, b) } else { (b, a) };
match kind {
Kind::Contact => contacts.push(pair),
Kind::Sensor => sensors.push(pair),
}
}
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use super::*;
use crate::sim::math::{Vec3, vec3};
fn proxy(center: Vec3, half: f32, role: Role) -> Proxy {
Proxy {
bounds: Aabb::from_center_half_extents(center, Vec3::splat(half)),
mask: LayerMask::ALL,
role,
}
}
fn role(responds: bool) -> Role {
if responds {
Role::Dynamic
} else {
Role::Static
}
}
fn build(centers: &[(Vec3, bool)]) -> SweepPrune {
let mut sap = SweepPrune::with_capacity(centers.len().max(1));
for (slot, (center, responds)) in centers.iter().enumerate() {
sap.insert(slot as u32);
sap.set_proxy(slot as u32, proxy(*center, 0.5, role(*responds)));
}
sap
}
fn touching(roles: &[Role]) -> SweepPrune {
let mut sap = SweepPrune::with_capacity(roles.len().max(1));
for (slot, &role) in roles.iter().enumerate() {
sap.insert(slot as u32);
sap.set_proxy(
slot as u32,
proxy(vec3(slot as f32 * 0.5, 0.0, 0.0), 0.5, role),
);
}
sap
}
#[test]
fn overlapping_boxes_pair_and_distant_ones_do_not() {
let mut sap = build(&[
(Vec3::ZERO, true),
(vec3(0.5, 0.0, 0.0), true),
(vec3(10.0, 0.0, 0.0), true),
]);
assert_eq!(sap.sweep(&crate::Inline, 1).contacts, [(0, 1)]);
assert_eq!(sap.len(), 3);
}
#[test]
fn a_pair_of_immovable_bodies_is_never_reported() {
let mut sap = build(&[(Vec3::ZERO, false), (vec3(0.5, 0.0, 0.0), false)]);
assert!(sap.sweep(&crate::Inline, 1).contacts.is_empty());
let mut mixed = build(&[(Vec3::ZERO, false), (vec3(0.5, 0.0, 0.0), true)]);
assert_eq!(mixed.sweep(&crate::Inline, 1).contacts, [(0, 1)]);
}
#[test]
fn a_one_way_filter_still_blocks_the_pair() {
let mut sap = build(&[(Vec3::ZERO, true), (vec3(0.5, 0.0, 0.0), true)]);
sap.set_proxy(
1,
Proxy {
mask: LayerMask {
memberships: u32::MAX,
filter: 0,
},
..proxy(vec3(0.5, 0.0, 0.0), 0.5, Role::Dynamic)
},
);
assert!(sap.sweep(&crate::Inline, 1).contacts.is_empty());
}
#[test]
fn pairs_come_out_ordered_by_slot_whatever_the_layout() {
let mut sap = build(&[
(vec3(0.0, 0.0, 2.0), true),
(vec3(0.0, 0.0, 1.0), true),
(vec3(0.0, 0.0, 0.0), true),
(vec3(0.0, 0.0, 1.5), true),
]);
let pairs = sap.sweep(&crate::Inline, 1).contacts.to_vec();
let mut sorted = pairs.clone();
sorted.sort_unstable();
assert_eq!(pairs, sorted);
assert!(pairs.iter().all(|(a, b)| a < b));
assert!(pairs.contains(&(0, 3)), "{pairs:?}");
assert!(pairs.contains(&(1, 3)), "{pairs:?}");
assert!(!pairs.contains(&(0, 2)), "{pairs:?}");
}
#[test]
fn the_pair_set_does_not_depend_on_the_chosen_axis() {
let along_x: Vec<(Vec3, bool)> = (0..8)
.map(|i| (vec3(i as f32 * 0.75, 0.0, 0.0), true))
.collect();
let along_y: Vec<(Vec3, bool)> = (0..8)
.map(|i| (vec3(0.0, i as f32 * 0.75, 0.0), true))
.collect();
let mut sx = build(&along_x);
let mut sy = build(&along_y);
assert_eq!(
sx.sweep(&crate::Inline, 1).contacts,
sy.sweep(&crate::Inline, 1).contacts
);
assert_eq!(sx.axis, 0);
assert_eq!(sy.axis, 1);
}
#[test]
fn re_sorting_after_motion_finds_the_same_pairs_as_a_fresh_sweep() {
let mut sap = build(&[
(vec3(0.0, 0.0, 0.0), true),
(vec3(3.0, 0.0, 0.0), true),
(vec3(6.0, 0.0, 0.0), true),
]);
sap.sweep(&crate::Inline, 1);
sap.set_proxy(2, proxy(vec3(-3.2, 0.0, 0.0), 0.5, Role::Dynamic));
sap.set_proxy(0, proxy(vec3(-2.8, 0.0, 0.0), 0.5, Role::Dynamic));
let moved = sap.sweep(&crate::Inline, 1).contacts.to_vec();
let mut fresh = build(&[
(vec3(-2.8, 0.0, 0.0), true),
(vec3(3.0, 0.0, 0.0), true),
(vec3(-3.2, 0.0, 0.0), true),
]);
assert_eq!(moved, fresh.sweep(&crate::Inline, 1).contacts);
assert_eq!(moved, [(0, 2)]);
}
#[test]
fn removing_a_body_drops_its_pairs_and_leaves_the_rest() {
let mut sap = build(&[
(Vec3::ZERO, true),
(vec3(0.6, 0.0, 0.0), true),
(vec3(1.2, 0.0, 0.0), true),
]);
assert_eq!(sap.sweep(&crate::Inline, 1).contacts, [(0, 1), (1, 2)]);
sap.remove(1);
assert_eq!(sap.len(), 2);
assert!(
sap.sweep(&crate::Inline, 1).contacts.is_empty(),
"the ends never reached each other"
);
}
#[test]
fn an_empty_or_single_body_sweep_reports_nothing() {
let mut empty = SweepPrune::with_capacity(4);
assert!(empty.sweep(&crate::Inline, 1).contacts.is_empty());
let mut one = build(&[(Vec3::ZERO, true)]);
assert!(one.sweep(&crate::Inline, 1).contacts.is_empty());
}
#[test]
fn a_sweep_with_nothing_moved_reuses_its_answer() {
let mut sap = build(&[(Vec3::ZERO, true), (vec3(0.5, 0.0, 0.0), true)]);
assert_eq!(sap.sweep(&crate::Inline, 1).contacts, [(0, 1)]);
assert!(!sap.dirty);
sap.pairs.clear();
assert!(sap.sweep(&crate::Inline, 1).contacts.is_empty());
sap.set_proxy(1, proxy(vec3(0.5, 0.0, 0.0), 0.5, Role::Dynamic));
assert_eq!(sap.sweep(&crate::Inline, 1).contacts, [(0, 1)]);
}
#[test]
fn a_query_window_holds_the_reachable_proxies_and_drops_the_rest() {
let centers: Vec<(Vec3, bool)> = (0..10)
.map(|i| (vec3(i as f32 * 2.0, 0.0, 0.0), true))
.collect();
let mut sap = build(¢ers);
sap.sweep(&crate::Inline, 1);
assert_eq!(sap.axis(), 0);
let window = sap.slab_window(3.6, 6.4);
assert!(window.contains(&2) && window.contains(&3), "{window:?}");
assert!(!window.contains(&0) && !window.contains(&9), "{window:?}");
assert!(window.len() < sap.len(), "the window must prune");
assert_eq!(sap.slab_window(-100.0, 100.0).len(), sap.len());
assert!(sap.slab_window(-100.0, -50.0).is_empty());
assert!(sap.slab_window(50.0, 100.0).is_empty());
}
#[test]
fn a_wide_proxy_stays_in_the_window_of_a_query_far_ahead_of_it() {
let mut sap = SweepPrune::with_capacity(3);
for slot in 0..3 {
sap.insert(slot);
}
sap.set_proxy(0, proxy(vec3(0.0, 0.0, 0.0), 20.0, Role::Dynamic));
sap.set_proxy(1, proxy(vec3(10.0, 0.0, 0.0), 0.5, Role::Dynamic));
sap.set_proxy(2, proxy(vec3(30.0, 0.0, 0.0), 0.5, Role::Dynamic));
sap.sweep(&crate::Inline, 1);
let window = sap.slab_window(9.0, 11.0);
assert!(
window.contains(&0),
"the wide proxy reaches here: {window:?}"
);
assert!(window.contains(&1), "{window:?}");
assert!(!window.contains(&2), "{window:?}");
}
#[test]
fn a_stale_order_widens_the_window_to_everything() {
let mut sap = build(&[
(vec3(0.0, 0.0, 0.0), true),
(vec3(4.0, 0.0, 0.0), true),
(vec3(8.0, 0.0, 0.0), true),
]);
sap.sweep(&crate::Inline, 1);
assert!(sap.slab_window(-1.0, 1.0).len() < 3);
sap.set_proxy(2, proxy(vec3(-9.0, 0.0, 0.0), 0.5, Role::Dynamic));
assert_eq!(sap.slab_window(-1.0, 1.0).len(), 3);
}
#[test]
fn a_sensor_pairs_with_what_can_cross_it_and_not_with_geometry() {
let mut sap = touching(&[Role::Sensor, Role::Static]);
let swept = sap.sweep(&crate::Inline, 1);
assert!(swept.contacts.is_empty());
assert!(swept.sensors.is_empty(), "a wall crosses nothing");
for other in [Role::Dynamic, Role::Driven, Role::Sensor] {
let mut sap = touching(&[Role::Sensor, other]);
let swept = sap.sweep(&crate::Inline, 1);
assert_eq!(swept.sensors, [(0, 1)], "{other:?} must be detected");
assert!(
swept.contacts.is_empty(),
"{other:?} must not collide with a region"
);
}
}
#[test]
fn the_sensor_rule_leaves_the_contact_rule_alone() {
for pair in [
[Role::Driven, Role::Static],
[Role::Driven, Role::Driven],
[Role::Static, Role::Static],
] {
let mut sap = touching(&pair);
let swept = sap.sweep(&crate::Inline, 1);
assert!(swept.contacts.is_empty(), "{pair:?}");
assert!(swept.sensors.is_empty(), "{pair:?}");
}
let mut sap = touching(&[Role::Driven, Role::Dynamic]);
assert_eq!(sap.sweep(&crate::Inline, 1).contacts, [(0, 1)]);
}
#[test]
fn both_lists_leave_the_sweep_sorted_by_slot() {
let mut sap = touching(&[
Role::Dynamic,
Role::Sensor,
Role::Dynamic,
Role::Sensor,
Role::Dynamic,
]);
let swept = sap.sweep(&crate::Inline, 1);
assert!(swept.contacts.windows(2).all(|w| w[0] < w[1]));
assert!(swept.sensors.windows(2).all(|w| w[0] < w[1]));
assert!(swept.sensors.contains(&(1, 2)), "{:?}", swept.sensors);
assert!(swept.sensors.contains(&(2, 3)), "{:?}", swept.sensors);
assert!(!swept.contacts.contains(&(1, 2)), "{:?}", swept.contacts);
}
#[test]
fn a_layer_filter_hides_a_sensor_pair_too() {
let mut sap = touching(&[Role::Sensor, Role::Dynamic]);
assert_eq!(sap.sweep(&crate::Inline, 1).sensors, [(0, 1)]);
sap.set_proxy(
1,
Proxy {
mask: LayerMask {
memberships: 0b10,
filter: 0b10,
},
..proxy(vec3(0.5, 0.0, 0.0), 0.5, Role::Dynamic)
},
);
sap.set_proxy(
0,
Proxy {
mask: LayerMask {
memberships: 0b01,
filter: 0b01,
},
..proxy(Vec3::ZERO, 0.5, Role::Sensor)
},
);
assert!(sap.sweep(&crate::Inline, 1).sensors.is_empty());
}
#[test]
fn sweeping_reuses_its_pair_buffer() {
let centers: Vec<(Vec3, bool)> = (0..16)
.map(|i| (vec3(i as f32 * 0.4, 0.0, 0.0), true))
.collect();
let mut sap = build(¢ers);
sap.sweep(&crate::Inline, 1);
let capacity = sap.pairs.capacity();
for slot in 0..8 {
sap.set_proxy(
slot,
proxy(vec3(slot as f32 * 0.4, 0.0, 0.0), 0.5, Role::Dynamic),
);
sap.sweep(&crate::Inline, 1);
}
assert_eq!(sap.pairs.capacity(), capacity);
}
}