use super::BvhBuildError;
use super::bvh::{
Bvh, BvhNode, INDEX_MASK, LEAF_SIZE, MAX_BRANCH_LEFT, MORTON_LEVELS, MortonEntry,
};
use super::source::BvhSource;
use crate::{Aabb, Vec3};
use rayon::prelude::*;
const PARALLEL_KEY_THRESHOLD: usize = 1 << 16;
pub(super) fn scene_bounds<S: BvhSource + ?Sized>(source: &S, count: u32) -> Aabb {
let fold = |bounds: Aabb, index: u32| {
let bound = source.bound(index);
if valid_bound(&bound) {
bounds.union(&bound)
} else {
bounds
}
};
if (count as usize) < PARALLEL_KEY_THRESHOLD {
return (0..count).fold(Aabb::EMPTY, fold);
}
(0..count)
.into_par_iter()
.fold(|| Aabb::EMPTY, fold)
.reduce(|| Aabb::EMPTY, |left, right| left.union(&right))
}
pub(super) fn extend_keys<S: BvhSource + ?Sized>(
entries: &mut Vec<MortonEntry>,
source: &S,
count: u32,
scene_bounds: Aabb,
) {
let key = |index: u32| {
let bound = source.bound(index);
valid_bound(&bound).then(|| MortonEntry {
code: morton_code(bound.center(), scene_bounds),
source: index,
})
};
if (count as usize) < PARALLEL_KEY_THRESHOLD {
entries.extend((0..count).filter_map(key));
return;
}
entries.par_extend((0..count).into_par_iter().filter_map(key));
}
pub(super) fn build_range<S: BvhSource + ?Sized>(
entries: &[MortonEntry],
source: &S,
start: usize,
end: usize,
node: usize,
out: &mut Bvh,
) -> Result<BuildAggregate, BvhBuildError> {
if end - start <= LEAF_SIZE {
let first = checked_u32(
"BVH leaf primitive offset",
out.primitive_indices.len(),
INDEX_MASK,
)?;
let mut bounds = Aabb::EMPTY;
let mut max_radius = 0.0f32;
for entry in &entries[start..end] {
let bound = source.bound(entry.source);
bounds = bounds.union(&bound);
max_radius = max_radius.max(bound.half_extents().max_element());
out.primitive_indices.push(entry.source);
}
let count = checked_u32("BVH leaf primitive count", end - start, 7)?;
out.nodes[node] = BvhNode::leaf(bounds, first, count, max_radius);
return Ok(BuildAggregate { bounds, max_radius });
}
let split = split_range(entries, start, end);
let left = out.nodes.len();
out.nodes.extend([BvhNode::default(), BvhNode::default()]);
let left_bounds = build_range(entries, source, start, split, left, out)?;
let right_bounds = build_range(entries, source, split, end, left + 1, out)?;
let bounds = left_bounds.bounds.union(&right_bounds.bounds);
let max_radius = left_bounds.max_radius.max(right_bounds.max_radius);
let left = checked_u32("BVH left child", left, MAX_BRANCH_LEFT)?;
out.nodes[node] = BvhNode::branch(bounds, left, max_radius);
Ok(BuildAggregate { bounds, max_radius })
}
#[inline]
pub(super) fn checked_index(
resource: &'static str,
index: usize,
maximum: u32,
) -> Result<(), BvhBuildError> {
checked_u32(resource, index, maximum).map(|_| ())
}
#[inline]
pub(super) fn checked_u32(
resource: &'static str,
index: usize,
maximum: u32,
) -> Result<u32, BvhBuildError> {
let index_u64 = u64::try_from(index)
.into_iter()
.fold(u64::MAX, |_, value| value);
let converted = u32::try_from(index).map_err(|_| BvhBuildError {
resource,
index: index_u64,
maximum,
})?;
if converted > maximum {
return Err(BvhBuildError {
resource,
index: index_u64,
maximum,
});
}
Ok(converted)
}
#[derive(Clone, Copy)]
pub(super) struct BuildAggregate {
bounds: Aabb,
max_radius: f32,
}
#[inline]
fn split_range(entries: &[MortonEntry], start: usize, end: usize) -> usize {
let first = entries[start].code;
let last = entries[end - 1].code;
if first == last {
return start + (end - start) / 2;
}
let shared_prefix = (first ^ last).leading_zeros();
let mut low = start + 1;
let mut high = end - 1;
while low < high {
let middle = (low + high).div_ceil(2);
if (first ^ entries[middle].code).leading_zeros() > shared_prefix {
low = middle;
} else {
high = middle - 1;
}
}
low + 1
}
#[inline]
pub(super) fn valid_bound(bound: &Aabb) -> bool {
!bound.is_empty() && bound.min.is_finite() && bound.max.is_finite()
}
#[inline]
pub(super) fn morton_code(point: Vec3, bounds: Aabb) -> u64 {
let extent = (bounds.max - bounds.min).max(Vec3::splat(f32::EPSILON));
let unit = ((point - bounds.min) / extent).clamp(Vec3::ZERO, Vec3::ONE);
let x = crate::unit_to_grid(unit.x, MORTON_LEVELS);
let y = crate::unit_to_grid(unit.y, MORTON_LEVELS);
let z = crate::unit_to_grid(unit.z, MORTON_LEVELS);
expand_21(x) | (expand_21(y) << 1) | (expand_21(z) << 2)
}
#[inline]
pub(super) const fn expand_21(value: u32) -> u64 {
let mut value = (value as u64) & 0x001f_ffff;
value = (value | (value << 32)) & 0x001f_0000_0000_ffff;
value = (value | (value << 16)) & 0x001f_0000_ff00_00ff;
value = (value | (value << 8)) & 0x100f_00f0_0f00_f00f;
value = (value | (value << 4)) & 0x10c3_0c30_c30c_30c3;
value = (value | (value << 2)) & 0x1249_2492_4924_9249;
value
}