mod common;
use std::{
collections::HashSet,
sync::{
atomic::{AtomicUsize, Ordering::Relaxed},
Arc, Mutex,
},
thread::ThreadId,
};
use embree3::{Bounds, Collision, Device, Geometry, GeometryKind, Scene};
fn user_boxes(device: &Device, n: u32) -> Geometry<'static> {
let mut g = device.create_geometry(GeometryKind::USER).unwrap();
g.set_primitive_count(n);
g.set_bounds_function::<_, ()>(move |b: &mut Bounds, prim, _time, _user: Option<&()>| {
let f = prim as f32;
b.lower_x = f;
b.lower_y = 0.0;
b.lower_z = 0.0;
b.upper_x = f + 1.5;
b.upper_y = 1.0;
b.upper_z = 1.0;
});
g.commit()
}
fn user_box(device: &Device, lo: [f32; 3], hi: [f32; 3]) -> Geometry<'static> {
let mut g = device.create_geometry(GeometryKind::USER).unwrap();
g.set_primitive_count(1);
g.set_bounds_function::<_, ()>(move |b: &mut Bounds, _prim, _time, _user: Option<&()>| {
b.lower_x = lo[0];
b.lower_y = lo[1];
b.lower_z = lo[2];
b.upper_x = hi[0];
b.upper_y = hi[1];
b.upper_z = hi[2];
});
g.commit()
}
fn committed_scene(device: &Device, geom: &Geometry<'static>) -> Scene<'static> {
let mut s = device.create_scene().unwrap();
s.attach_geometry(geom);
s.commit();
s
}
#[test]
fn overlapping_user_boxes_report_a_collision_pair() {
let device = common::device();
let g0 = user_box(&device, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let g1 = user_box(&device, [0.5, 0.5, 0.5], [1.5, 1.5, 1.5]); let s0 = committed_scene(&device, &g0);
let s1 = committed_scene(&device, &g1);
let pairs = Arc::new(Mutex::new(Vec::<(u32, u32, u32, u32)>::new()));
let sink = pairs.clone();
unsafe {
s0.collide(&s1, move |cs: &mut [Collision]| {
let mut v = sink.lock().unwrap();
v.extend(
cs.iter()
.map(|c| (c.geomID0, c.primID0, c.geomID1, c.primID1)),
);
})
.expect("collide preconditions hold (same device, non-empty, user geometries)");
}
let v = pairs.lock().unwrap();
assert!(
!v.is_empty(),
"overlapping user boxes must report at least one broad-phase pair"
);
assert!(
v.iter()
.any(|&(g0, p0, g1, p1)| g0 == 0 && p0 == 0 && g1 == 0 && p1 == 0),
"the single prim of each scene should be reported as a candidate pair, got {v:?}"
);
}
#[test]
fn broad_phase_is_conservative_user_does_narrow_phase() {
let device = common::device();
let g0 = user_box(&device, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let g1 = user_box(&device, [10.0, 10.0, 10.0], [11.0, 11.0, 11.0]); let s0 = committed_scene(&device, &g0);
let s1 = committed_scene(&device, &g1);
let candidates = Arc::new(AtomicUsize::new(0));
let c = candidates.clone();
unsafe {
s0.collide(&s1, move |cs: &mut [Collision]| {
c.fetch_add(cs.len(), Relaxed);
})
.expect("collide preconditions hold");
}
assert!(
candidates.load(Relaxed) >= 1,
"broad phase should surface the candidate pair even for disjoint AABBs (the user \
narrow-phases); embree reported none"
);
}
#[test]
fn many_primitives_collide_soundly_under_concurrency() {
let device = common::device();
let g0 = user_boxes(&device, 4000);
let g1 = user_boxes(&device, 4000);
let s0 = committed_scene(&device, &g0);
let s1 = committed_scene(&device, &g1);
let count = Arc::new(AtomicUsize::new(0));
let threads = Arc::new(Mutex::new(HashSet::<ThreadId>::new()));
let (c, t) = (count.clone(), threads.clone());
unsafe {
s0.collide(&s1, move |cs: &mut [Collision]| {
c.fetch_add(cs.len(), Relaxed);
t.lock().unwrap().insert(std::thread::current().id());
})
.expect("collide preconditions hold");
}
assert!(
count.load(Relaxed) > 0,
"overlapping multi-primitive scenes must report candidate pairs"
);
eprintln!(
"collide ran callbacks on {} distinct thread(s)",
threads.lock().unwrap().len()
);
}
#[test]
fn self_collision_omits_identical_primitive_pairs() {
let device = common::device();
let g0 = user_box(&device, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let g1 = user_box(&device, [0.5, 0.5, 0.5], [1.5, 1.5, 1.5]); let mut scene = device.create_scene().unwrap();
scene.attach_geometry(&g0); scene.attach_geometry(&g1); scene.commit();
let pairs = Arc::new(Mutex::new(Vec::<(u32, u32, u32, u32)>::new()));
let sink = pairs.clone();
unsafe {
scene
.collide(&scene, move |cs: &mut [Collision]| {
let mut v = sink.lock().unwrap();
v.extend(
cs.iter()
.map(|c| (c.geomID0, c.primID0, c.geomID1, c.primID1)),
);
})
.expect("collide preconditions hold");
}
let v = pairs.lock().unwrap();
assert!(
!v.is_empty(),
"two overlapping geometries in one scene should self-collide"
);
assert!(
v.iter().all(|&(g0, p0, g1, p1)| !(g0 == g1 && p0 == p1)),
"embree must omit (primitive, same primitive) self-pairs, got {v:?}"
);
}
#[test]
fn collide_rejects_invalid_inputs_before_calling_embree() {
let device = common::device();
let g = user_box(&device, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let populated = committed_scene(&device, &g);
let mut empty = device.create_scene().unwrap();
empty.commit();
let r = unsafe { populated.collide(&empty, |_cs: &mut [Collision]| {}) };
assert!(r.is_err(), "collide must reject an empty `other` scene");
let r = unsafe { empty.collide(&populated, |_cs: &mut [Collision]| {}) };
assert!(r.is_err(), "collide must reject an empty `self` scene");
let tri = common::unit_triangle(&device).commit();
let tri_scene = committed_scene(&device, &tri);
let r = unsafe { populated.collide(&tri_scene, |_cs: &mut [Collision]| {}) };
assert!(r.is_err(), "collide must reject a non-user geometry");
let device2 = common::device();
let g2 = user_box(&device2, [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
let other_device_scene = committed_scene(&device2, &g2);
let r = unsafe { populated.collide(&other_device_scene, |_cs: &mut [Collision]| {}) };
assert!(
r.is_err(),
"collide must reject scenes created on different devices"
);
}