use math::{dot, normalize, Base, Vector3};
use smallvec::SmallVec;
use crate::physics::{
dynabit::{body::PhysicsBody, contact::Pair},
Body,
};
pub struct Intersection {
pub(crate) normal: Vector3,
pub(crate) depth: f32,
pub(crate) point_on_a: Vector3,
pub(crate) point_on_b: Vector3,
}
pub struct PseudoBody {
id: usize,
value: f32,
is_min: bool,
}
impl Eq for PseudoBody {}
impl PartialEq for PseudoBody {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == std::cmp::Ordering::Equal
}
}
impl Ord for PseudoBody {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value
.total_cmp(&other.value)
.then_with(|| other.is_min.cmp(&self.is_min))
.then_with(|| self.id.cmp(&other.id))
}
}
impl PartialOrd for PseudoBody {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
pub fn sort_bodies_bounds<'a>(bodies: impl Iterator<Item = (usize, &'a PhysicsBody)>, dt: f32) -> SmallVec<[PseudoBody; 32]> {
let axis = normalize(Vector3::one());
let mut pseudo_bodies = SmallVec::with_capacity(bodies.size_hint().0 * 2);
for (i, body) in bodies {
let mut bounds = body.bounds(); let future_bounds = bounds + body.linear_velocity * dt;
bounds.expand(&future_bounds);
let epsilon = 0.01f32;
let bounds = bounds.expanded_by(Vector3::one() * epsilon);
pseudo_bodies.push(PseudoBody {
id: i,
value: dot(axis, bounds.min()),
is_min: true,
});
pseudo_bodies.push(PseudoBody {
id: i,
value: dot(axis, bounds.max()),
is_min: false,
});
}
pseudo_bodies.sort();
pseudo_bodies
}
pub fn build_pairs(pseudo_bodies: &[PseudoBody]) -> SmallVec<[Pair; 32]> {
let mut pairs = SmallVec::new();
let mut active = SmallVec::<[usize; 32]>::new();
for endpoint in pseudo_bodies {
if endpoint.is_min {
for &active_id in &active {
pairs.push(Pair::new(active_id, endpoint.id));
}
active.push(endpoint.id);
} else if let Some(index) = active.iter().position(|id| *id == endpoint.id) {
active.remove(index);
}
}
pairs
}
pub fn sweep_and_prune_1d<'a>(bodies: impl Iterator<Item = (usize, &'a PhysicsBody)>, dt: f32) -> SmallVec<[Pair; 32]> {
let e = sort_bodies_bounds(bodies, dt);
build_pairs(&e)
}
pub fn broadphase<'a>(bodies: impl Iterator<Item = (usize, &'a PhysicsBody)>, dt: f32) -> SmallVec<[Pair; 32]> {
sweep_and_prune_1d(bodies, dt)
}
#[cfg(test)]
mod tests {
use math::Quaternion;
use super::*;
use crate::{
core::factory::Factory,
physics::{body::BodyTypes, collider::Shapes},
};
fn endpoint(id: usize, value: f32, is_min: bool) -> PseudoBody {
PseudoBody { id, value, is_min }
}
fn sorted_endpoints(endpoints: impl IntoIterator<Item = PseudoBody>) -> Vec<PseudoBody> {
let mut endpoints = endpoints.into_iter().collect::<Vec<_>>();
endpoints.sort();
endpoints
}
fn canonical_pairs(pairs: impl IntoIterator<Item = Pair>) -> Vec<(usize, usize)> {
let mut pairs = pairs
.into_iter()
.map(|pair| (pair.a.min(pair.b), pair.a.max(pair.b)))
.collect::<Vec<_>>();
pairs.sort_unstable();
pairs
}
fn body(position: Vector3, linear_velocity: Vector3) -> PhysicsBody {
PhysicsBody {
body_type: BodyTypes::Dynamic,
collision_shape: Shapes::Sphere { radius: 0.5 },
position,
orientation: Quaternion::identity(),
acceleration: Vector3::new(0.0, 0.0, 0.0),
linear_velocity,
angular_velocity: Vector3::new(0.0, 0.0, 0.0),
inv_mass: 1.0,
center_of_mass: Vector3::new(0.0, 0.0, 0.0),
elasticity: 0.0,
friction: 1.0,
handle: Factory::<()>::new().create(()),
}
}
#[test]
fn endpoint_order_is_total_and_opens_touching_intervals_before_closing() {
let endpoints = sorted_endpoints([
endpoint(2, 1.0, false),
endpoint(1, 1.0, false),
endpoint(2, 1.0, true),
endpoint(1, 1.0, true),
]);
assert_eq!(
endpoints
.iter()
.map(|endpoint| (endpoint.id, endpoint.is_min))
.collect::<Vec<_>>(),
[(1, true), (2, true), (1, false), (2, false)]
);
}
#[test]
fn sweep_generates_all_pairs_among_three_simultaneously_active_bodies() {
let endpoints = sorted_endpoints([
endpoint(0, 0.0, true),
endpoint(0, 3.0, false),
endpoint(1, 1.0, true),
endpoint(1, 4.0, false),
endpoint(2, 2.0, true),
endpoint(2, 5.0, false),
]);
assert_eq!(canonical_pairs(build_pairs(&endpoints)), [(0, 1), (0, 2), (1, 2)]);
}
#[test]
fn sweep_distinguishes_disjoint_nested_and_touching_intervals() {
let disjoint = sorted_endpoints([
endpoint(0, 0.0, true),
endpoint(0, 1.0, false),
endpoint(1, 2.0, true),
endpoint(1, 3.0, false),
]);
assert!(build_pairs(&disjoint).is_empty());
let nested = sorted_endpoints([
endpoint(0, 0.0, true),
endpoint(0, 3.0, false),
endpoint(1, 1.0, true),
endpoint(1, 2.0, false),
]);
assert_eq!(canonical_pairs(build_pairs(&nested)), [(0, 1)]);
let touching = sorted_endpoints([
endpoint(0, 0.0, true),
endpoint(0, 1.0, false),
endpoint(1, 1.0, true),
endpoint(1, 2.0, false),
]);
assert_eq!(canonical_pairs(build_pairs(&touching)), [(0, 1)]);
}
#[test]
fn closing_an_early_interval_preserves_pair_generation_order() {
let endpoints = sorted_endpoints([
endpoint(0, 0.0, true),
endpoint(1, 1.0, true),
endpoint(2, 2.0, true),
endpoint(0, 3.0, false),
endpoint(3, 4.0, true),
endpoint(1, 5.0, false),
endpoint(2, 6.0, false),
endpoint(3, 7.0, false),
]);
let pairs = build_pairs(&endpoints)
.into_iter()
.map(|pair| (pair.a, pair.b))
.collect::<Vec<_>>();
assert_eq!(pairs, [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)]);
}
#[test]
fn swept_bounds_keep_fast_crossing_bodies_in_the_candidate_set() {
let moving = body(Vector3::new(-5.0, -5.0, -5.0), Vector3::new(10.0, 10.0, 10.0));
let stationary = body(Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 0.0, 0.0));
let bodies = [moving, stationary];
assert_eq!(canonical_pairs(broadphase(bodies.iter().enumerate(), 1.0)), [(0, 1)]);
}
}