214 lines
6.9 KiB
Rust
214 lines
6.9 KiB
Rust
use crate::core::geometry::{Bounds3f, Point3f, Ray, Vector3f};
|
|
use crate::core::primitive::{Primitive, PrimitiveTrait};
|
|
use crate::core::shape::ShapeIntersection;
|
|
use crate::{gvec, Float, GVec, Ptr};
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum SplitMethod {
|
|
SAH,
|
|
Hlbvh,
|
|
Middle,
|
|
EqualCounts,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Default, Debug, Clone, Copy)]
|
|
pub struct LinearBVHNode {
|
|
pub bounds: Bounds3f,
|
|
pub primitives_offset: usize,
|
|
pub n_primitives: u16,
|
|
pub axis: u8,
|
|
pub pad: u8,
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Clone)]
|
|
pub struct BVHAggregate {
|
|
pub node_count: u32,
|
|
pub max_prims_in_node: u32,
|
|
pub split_method: SplitMethod,
|
|
pub primitives: GVec<Primitive>,
|
|
pub nodes: GVec<LinearBVHNode>,
|
|
}
|
|
|
|
impl BVHAggregate {
|
|
pub fn empty() -> Self {
|
|
Self {
|
|
node_count: 0,
|
|
max_prims_in_node: 0,
|
|
split_method: SplitMethod::SAH,
|
|
primitives: gvec(),
|
|
nodes: gvec(),
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn node(&self, i: usize) -> &LinearBVHNode {
|
|
unsafe { self.nodes.get_unchecked(i) }
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn primitive(&self, i: usize) -> &Primitive {
|
|
unsafe { self.primitives.get_unchecked(i) }
|
|
}
|
|
}
|
|
|
|
impl PrimitiveTrait for BVHAggregate {
|
|
fn bounds(&self) -> Bounds3f {
|
|
if self.nodes.is_empty() || self.node_count == 0 {
|
|
Bounds3f::default()
|
|
} else {
|
|
self.node(0).bounds
|
|
}
|
|
}
|
|
|
|
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
|
if self.nodes.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let mut best_si: Option<ShapeIntersection> = None;
|
|
|
|
let mut hit_t = t_max.unwrap_or(Float::INFINITY);
|
|
|
|
let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z());
|
|
let dir_is_neg = [
|
|
if inv_dir.x() < 0.0 { 1 } else { 0 },
|
|
if inv_dir.y() < 0.0 { 1 } else { 0 },
|
|
if inv_dir.z() < 0.0 { 1 } else { 0 },
|
|
];
|
|
|
|
let mut to_visit_offset = 0;
|
|
let mut current_node_index = 0;
|
|
let mut nodes_to_visit = [0usize; 64];
|
|
|
|
loop {
|
|
let node = &self.nodes[current_node_index];
|
|
|
|
// Check ray against BVH node bounds using the current closest hit_t
|
|
if node
|
|
.bounds
|
|
.intersect_p(r.o, hit_t, inv_dir, &dir_is_neg)
|
|
.is_some()
|
|
{
|
|
if node.n_primitives > 0 {
|
|
// Intersect ray with all primitives in this leaf
|
|
for i in 0..node.n_primitives {
|
|
let prim_idx = node.primitives_offset + i as usize;
|
|
let prim = &self.primitives[prim_idx];
|
|
|
|
if let Some(si) = prim.intersect(r, Some(hit_t)) {
|
|
hit_t = si.t_hit();
|
|
best_si = Some(si);
|
|
}
|
|
}
|
|
|
|
if to_visit_offset == 0 {
|
|
break;
|
|
}
|
|
to_visit_offset -= 1;
|
|
current_node_index = nodes_to_visit[to_visit_offset];
|
|
} else {
|
|
// Check the sign of the ray direction against the split axis
|
|
if dir_is_neg[node.axis as usize] == 1 {
|
|
// Ray is negative (Right -> Left).
|
|
// Near child is Second Child (stored in primitives_offset).
|
|
// Far child is First Child (current + 1).
|
|
|
|
// Push Far
|
|
nodes_to_visit[to_visit_offset] = current_node_index + 1;
|
|
to_visit_offset += 1;
|
|
|
|
// Visit Near immediately
|
|
current_node_index = node.primitives_offset;
|
|
} else {
|
|
// Ray is positive (Left -> Right).
|
|
// Push Far
|
|
nodes_to_visit[to_visit_offset] = node.primitives_offset;
|
|
to_visit_offset += 1;
|
|
|
|
current_node_index += 1;
|
|
}
|
|
}
|
|
} else {
|
|
// The ray missed the AABB of this node. Pop stack to try the next node.
|
|
if to_visit_offset == 0 {
|
|
break;
|
|
}
|
|
to_visit_offset -= 1;
|
|
current_node_index = nodes_to_visit[to_visit_offset];
|
|
}
|
|
}
|
|
|
|
best_si
|
|
}
|
|
|
|
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
|
|
if self.nodes.is_empty() {
|
|
return false;
|
|
}
|
|
|
|
let t_max = t_max.unwrap_or(Float::INFINITY);
|
|
|
|
let inv_dir = Vector3f::new(1.0 / r.d.x(), 1.0 / r.d.y(), 1.0 / r.d.z());
|
|
let dir_is_neg = [
|
|
if inv_dir.x() < 0.0 { 1 } else { 0 },
|
|
if inv_dir.y() < 0.0 { 1 } else { 0 },
|
|
if inv_dir.z() < 0.0 { 1 } else { 0 },
|
|
];
|
|
|
|
let mut to_visit_offset = 0;
|
|
let mut current_node_index = 0;
|
|
let mut nodes_to_visit = [0usize; 64];
|
|
|
|
loop {
|
|
let node = &self.nodes[current_node_index];
|
|
|
|
// Check AABB
|
|
if node
|
|
.bounds
|
|
.intersect_p(r.o, t_max, inv_dir, &dir_is_neg)
|
|
.is_some()
|
|
{
|
|
if node.n_primitives > 0 {
|
|
for i in 0..node.n_primitives {
|
|
let prim_idx = node.primitives_offset + i as usize;
|
|
let prim = &self.primitives[prim_idx];
|
|
|
|
if prim.intersect_p(r, Some(t_max)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// No intersection in this leaf, try next node in stack
|
|
if to_visit_offset == 0 {
|
|
break;
|
|
}
|
|
to_visit_offset -= 1;
|
|
current_node_index = nodes_to_visit[to_visit_offset];
|
|
} else {
|
|
// Standard front-to-back traversal order helps find an occlusion
|
|
// closer to the origin faster, potentially saving work.
|
|
|
|
if dir_is_neg[node.axis as usize] == 1 {
|
|
nodes_to_visit[to_visit_offset] = current_node_index + 1;
|
|
to_visit_offset += 1;
|
|
current_node_index = node.primitives_offset;
|
|
} else {
|
|
nodes_to_visit[to_visit_offset] = node.primitives_offset;
|
|
to_visit_offset += 1;
|
|
current_node_index += 1;
|
|
}
|
|
}
|
|
} else {
|
|
if to_visit_offset == 0 {
|
|
break;
|
|
}
|
|
to_visit_offset -= 1;
|
|
current_node_index = nodes_to_visit[to_visit_offset];
|
|
}
|
|
}
|
|
false
|
|
}
|
|
}
|