#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BroadProxy {
pub min: [f32; 3],
pub max: [f32; 3],
pub layer: u32,
pub mask: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct BroadPairs {
pub pairs: Vec<[u32; 2]>,
pub overflowed: Vec<u32>,
}
const HEADER_WORDS: usize = 10;
const PROXY_WORDS: usize = 8;
pub const GPU_BROAD_PHASE_LANES: u32 = 256;
pub const MAX_BROAD_PROXIES: usize = 1 << 16;
const MAX_CELL: f32 = (1 << 24) as f32;
fn cell_of(p: [f32; 3], inverse: f32) -> [i32; 3] {
p.map(|x| (x * inverse).floor() as i32)
}
fn bucket_of(cell: [i32; 3], buckets: u32) -> u32 {
let [x, y, z] = cell.map(|c| c as u32);
(x.wrapping_mul(73_856_093)
^ y.wrapping_mul(19_349_663)
^ z.wrapping_mul(83_492_791))
& (buckets - 1)
}
fn buckets_of(lo: [i32; 3], hi: [i32; 3], buckets: u32) -> Vec<u32> {
let mut seen = Vec::new();
for z in lo[2]..=hi[2] {
for y in lo[1]..=hi[1] {
for x in lo[0]..=hi[0] {
let bucket = bucket_of([x, y, z], buckets);
if !seen.contains(&bucket) {
seen.push(bucket);
}
}
}
}
seen
}
#[must_use]
pub fn pack(proxies: &[BroadProxy], capacity: u32) -> Option<Vec<u32>> {
if proxies.len() > MAX_BROAD_PROXIES {
return None;
}
let mut extent = 0.0_f32;
for proxy in proxies {
for axis in 0..3 {
let (lo, hi) = (proxy.min[axis], proxy.max[axis]);
if !lo.is_finite() || !hi.is_finite() || lo > hi {
return None;
}
extent = extent.max(hi - lo);
}
}
let cell = if extent > 0.0 { extent } else { 1.0 };
let inverse = 1.0 / cell;
let count = proxies.len();
let buckets = (count * 2).next_power_of_two().clamp(64, 1 << 16) as u32;
let mut items = 0;
for proxy in proxies {
let lo = cell_of(proxy.min, inverse);
let hi = cell_of(proxy.max, inverse);
let far = |c: [i32; 3]| c.iter().any(|&c| (c as f32).abs() >= MAX_CELL);
if far(lo) || far(hi) {
return None;
}
items += buckets_of(lo, hi, buckets).len();
}
let proxy_base = HEADER_WORDS;
let starts = proxy_base + count * PROXY_WORDS;
let cursors = starts + buckets as usize;
let item_base = cursors + buckets as usize;
let counts = item_base + items;
let rows = counts + count;
let total = (capacity as usize)
.checked_mul(count)
.and_then(|row_words| row_words.checked_add(rows))
.filter(|&total| total <= u32::MAX as usize)?;
let mut words = vec![0; total];
words[..HEADER_WORDS].copy_from_slice(&[
count as u32,
buckets,
capacity,
inverse.to_bits(),
proxy_base as u32,
starts as u32,
cursors as u32,
item_base as u32,
counts as u32,
rows as u32,
]);
for (i, proxy) in proxies.iter().enumerate() {
let at = proxy_base + i * PROXY_WORDS;
let [a, b, c] = proxy.min.map(f32::to_bits);
let [d, e, f] = proxy.max.map(f32::to_bits);
words[at..at + PROXY_WORDS].copy_from_slice(&[
a,
b,
c,
d,
e,
f,
proxy.layer,
proxy.mask,
]);
}
Some(words)
}
#[must_use]
pub fn unpack(words: &[u32]) -> BroadPairs {
let [count, capacity, counts, rows] =
[0, 2, 8, 9].map(|index| words[index] as usize);
let mut out = BroadPairs::default();
for i in 0..count {
let found = words[counts + i] as usize;
if found > capacity {
out.overflowed.push(i as u32);
}
let row = rows + i * capacity;
out.pairs.extend(
words[row..row + found.min(capacity)]
.iter()
.map(|&j| [i as u32, j]),
);
}
out
}
#[must_use]
pub fn pairs_on_cpu(proxies: &[BroadProxy], capacity: u32) -> BroadPairs {
let mut out = BroadPairs::default();
for (i, a) in proxies.iter().enumerate() {
let mut found = 0;
for (j, b) in proxies.iter().enumerate().skip(i + 1) {
let overlap = (0..3).all(|axis| {
a.min[axis] <= b.max[axis] && b.min[axis] <= a.max[axis]
});
if overlap && a.layer & b.mask != 0 && b.layer & a.mask != 0 {
if found < capacity {
out.pairs.push([i as u32, j as u32]);
}
found += 1;
}
}
if found > capacity {
out.overflowed.push(i as u32);
}
}
out
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub(crate) fn scattered(count: usize) -> Vec<BroadProxy> {
let mut state = 0x9E37_79B9_u32;
let mut next = || {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state as f32 / u32::MAX as f32
};
(0..count)
.map(|i| {
let size = match i % 7 {
0 => 1.5,
1 | 2 => 0.02,
3 => 0.0,
_ => 0.3,
} * (0.5 + next());
let centre = [0, 1, 2].map(|_| next() * 12.0 - 6.0);
BroadProxy {
min: centre.map(|c| c - size),
max: centre.map(|c| c + size),
layer: 1 << (i % 3),
mask: if i % 5 == 0 { 0b011 } else { u32::MAX },
}
})
.collect()
}
#[test]
fn the_reference_pairs_overlapping_boxes_whose_layers_meet() {
let at = |x: f32, layer, mask| BroadProxy {
min: [x, 0.0, 0.0],
max: [x + 1.0, 1.0, 1.0],
layer,
mask,
};
let proxies = [
at(0.0, 1, u32::MAX),
at(0.5, 1, u32::MAX),
at(1.5, 2, u32::MAX),
at(2.5, 1, 1),
at(9.0, 1, u32::MAX),
];
let out = pairs_on_cpu(&proxies, 8);
assert_eq!(out.pairs, vec![[0, 1], [1, 2]]);
assert!(out.overflowed.is_empty());
let capped = pairs_on_cpu(&proxies, 0);
assert!(capped.pairs.is_empty());
assert_eq!(capped.overflowed, vec![0, 1]);
}
#[test]
fn packing_refuses_bad_boxes_and_sizes_its_buffers() {
let good = scattered(100);
let words = pack(&good, 4).unwrap();
assert_eq!(words[0], 100);
assert!(words[1].is_power_of_two());
assert_eq!(unpack(&words), BroadPairs::default());
let mut inverted = good.clone();
inverted[3].min[1] = inverted[3].max[1] + 1.0;
assert!(pack(&inverted, 4).is_none());
let mut nan = good.clone();
nan[7].max[0] = f32::NAN;
assert!(pack(&nan, 4).is_none());
let mut far = good;
far[0].min = [1e30; 3];
far[0].max = [1e30; 3];
assert!(pack(&far, 4).is_none());
assert!(pack(&scattered(2), u32::MAX).is_none());
}
#[test]
fn a_proxy_enters_each_bucket_once() {
let buckets = buckets_of([-1, -1, -1], [1, 1, 1], 64);
let mut sorted = buckets.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), buckets.len());
}
}