465 lines
11 KiB
C++
465 lines
11 KiB
C++
/**
|
|
* @file bvh.cpp
|
|
* @brief Implementation of BVH class (optimized version)
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <are/acceleration/bvh.h>
|
|
#include <are/core/logger.h>
|
|
#include <are/core/profiler.h>
|
|
|
|
namespace are {
|
|
|
|
BVH::BVH()
|
|
: root_index_(0) {
|
|
}
|
|
|
|
BVH::~BVH() {
|
|
clear();
|
|
}
|
|
|
|
bool BVH::build(const std::vector<Triangle> &triangles, const BVHBuildConfig &config) {
|
|
ARE_PROFILE_FUNCTION();
|
|
|
|
if (triangles.empty()) {
|
|
ARE_LOG_WARN("BVH: Cannot build from empty triangle list");
|
|
return false;
|
|
}
|
|
|
|
// Clear existing data
|
|
clear();
|
|
|
|
// Copy triangles
|
|
triangles_ = triangles;
|
|
|
|
// Build BVH
|
|
BVHBuilder builder(config);
|
|
root_index_ = builder.build(triangles_, nodes_, primitive_indices_);
|
|
|
|
// Get statistics
|
|
size_t node_count, leaf_count;
|
|
int max_depth;
|
|
builder.get_stats(node_count, leaf_count, max_depth);
|
|
|
|
ARE_LOG_INFO("BVH: Built successfully");
|
|
ARE_LOG_INFO(" Triangles: " + std::to_string(triangles_.size()));
|
|
ARE_LOG_INFO(" Nodes: " + std::to_string(node_count));
|
|
ARE_LOG_INFO(" Leaves: " + std::to_string(leaf_count));
|
|
ARE_LOG_INFO(" Max depth: " + std::to_string(max_depth));
|
|
ARE_LOG_INFO(" Memory: " + std::to_string(get_memory_usage() / 1024) + " KB");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BVH::intersect(const Ray &ray, HitRecord &hit) const {
|
|
// Note: No profiling here - this is a hot path
|
|
|
|
if (!is_built()) {
|
|
return false;
|
|
}
|
|
|
|
// Use iterative traversal with stack for better performance
|
|
return intersect_iterative(ray, hit);
|
|
}
|
|
|
|
bool BVH::intersect_any(const Ray &ray, Real t_max) const {
|
|
// Note: No profiling here - this is a hot path
|
|
|
|
if (!is_built()) {
|
|
return false;
|
|
}
|
|
|
|
return intersect_any_iterative(ray, t_max);
|
|
}
|
|
|
|
size_t BVH::get_memory_usage() const {
|
|
size_t total = 0;
|
|
total += nodes_.size() * sizeof(BVHNode);
|
|
total += primitive_indices_.size() * sizeof(uint32_t);
|
|
total += triangles_.size() * sizeof(Triangle);
|
|
return total;
|
|
}
|
|
|
|
void BVH::clear() {
|
|
nodes_.clear();
|
|
primitive_indices_.clear();
|
|
triangles_.clear();
|
|
root_index_ = 0;
|
|
}
|
|
|
|
bool BVH::intersect_iterative(const Ray &ray, HitRecord &hit) const {
|
|
// Precompute inverse direction for faster AABB tests
|
|
Vec3 inv_dir(
|
|
1.0f / ray.direction_.x,
|
|
1.0f / ray.direction_.y,
|
|
1.0f / ray.direction_.z);
|
|
|
|
// Stack-based traversal (64 levels is enough for most scenes)
|
|
uint32_t stack[64];
|
|
int stack_ptr = 0;
|
|
stack[stack_ptr++] = root_index_;
|
|
|
|
bool hit_anything = false;
|
|
Real closest_t = ray.t_max_;
|
|
|
|
while (stack_ptr > 0) {
|
|
uint32_t node_index = stack[--stack_ptr];
|
|
|
|
if (node_index >= nodes_.size()) {
|
|
continue;
|
|
}
|
|
|
|
const BVHNode &node = nodes_[node_index];
|
|
|
|
// Fast AABB test with precomputed inverse direction
|
|
Real t_min, t_max;
|
|
if (!intersect_aabb_fast(node.bounds_, ray, inv_dir, closest_t, t_min, t_max)) {
|
|
continue;
|
|
}
|
|
|
|
if (node.is_leaf()) {
|
|
// Test all primitives in leaf
|
|
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
|
|
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
|
|
|
|
if (prim_idx >= triangles_.size()) {
|
|
continue;
|
|
}
|
|
|
|
const Triangle &triangle = triangles_[prim_idx];
|
|
|
|
HitRecord temp_hit;
|
|
if (intersect_triangle_fast(triangle, ray, closest_t, temp_hit)) {
|
|
closest_t = temp_hit.t_;
|
|
hit = temp_hit;
|
|
hit.triangle_index_ = prim_idx;
|
|
hit_anything = true;
|
|
}
|
|
}
|
|
} else {
|
|
// Push children to stack (far child first, so near child is processed first)
|
|
if (node.left_child_ >= nodes_.size() || node.right_child_ >= nodes_.size()) {
|
|
continue;
|
|
}
|
|
|
|
const BVHNode &left = nodes_[node.left_child_];
|
|
const BVHNode &right = nodes_[node.right_child_];
|
|
|
|
Real t_left_min, t_left_max;
|
|
Real t_right_min, t_right_max;
|
|
|
|
bool hit_left = intersect_aabb_fast(left.bounds_, ray, inv_dir, closest_t,
|
|
t_left_min, t_left_max);
|
|
bool hit_right = intersect_aabb_fast(right.bounds_, ray, inv_dir, closest_t,
|
|
t_right_min, t_right_max);
|
|
|
|
if (hit_left && hit_right) {
|
|
// Push far child first (so near child is popped first)
|
|
if (t_left_min < t_right_min) {
|
|
if (stack_ptr < 64)
|
|
stack[stack_ptr++] = node.right_child_;
|
|
if (stack_ptr < 64)
|
|
stack[stack_ptr++] = node.left_child_;
|
|
} else {
|
|
if (stack_ptr < 64)
|
|
stack[stack_ptr++] = node.left_child_;
|
|
if (stack_ptr < 64)
|
|
stack[stack_ptr++] = node.right_child_;
|
|
}
|
|
} else if (hit_left) {
|
|
if (stack_ptr < 64)
|
|
stack[stack_ptr++] = node.left_child_;
|
|
} else if (hit_right) {
|
|
if (stack_ptr < 64)
|
|
stack[stack_ptr++] = node.right_child_;
|
|
}
|
|
}
|
|
}
|
|
|
|
return hit_anything;
|
|
}
|
|
|
|
bool BVH::intersect_any_iterative(const Ray &ray, Real t_max) const {
|
|
// Precompute inverse direction
|
|
Vec3 inv_dir(
|
|
1.0f / ray.direction_.x,
|
|
1.0f / ray.direction_.y,
|
|
1.0f / ray.direction_.z);
|
|
|
|
// Stack-based traversal
|
|
uint32_t stack[64];
|
|
int stack_ptr = 0;
|
|
stack[stack_ptr++] = root_index_;
|
|
|
|
while (stack_ptr > 0) {
|
|
uint32_t node_index = stack[--stack_ptr];
|
|
|
|
if (node_index >= nodes_.size()) {
|
|
continue;
|
|
}
|
|
|
|
const BVHNode &node = nodes_[node_index];
|
|
|
|
// Fast AABB test
|
|
Real t_min, t_max_box;
|
|
if (!intersect_aabb_fast(node.bounds_, ray, inv_dir, t_max, t_min, t_max_box)) {
|
|
continue;
|
|
}
|
|
|
|
if (node.is_leaf()) {
|
|
// Test all primitives in leaf
|
|
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
|
|
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
|
|
|
|
if (prim_idx >= triangles_.size()) {
|
|
continue;
|
|
}
|
|
|
|
const Triangle &triangle = triangles_[prim_idx];
|
|
|
|
if (triangle.intersect_fast(ray, t_max)) {
|
|
return true; // Early exit on first hit
|
|
}
|
|
}
|
|
} else {
|
|
// Push both children
|
|
if (node.left_child_ < nodes_.size() && stack_ptr < 64) {
|
|
stack[stack_ptr++] = node.left_child_;
|
|
}
|
|
if (node.right_child_ < nodes_.size() && stack_ptr < 64) {
|
|
stack[stack_ptr++] = node.right_child_;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline bool BVH::intersect_aabb_fast(const AABB &bounds, const Ray &ray,
|
|
const Vec3 &inv_dir, Real t_max,
|
|
Real &t_min_out, Real &t_max_out) const {
|
|
// Optimized slab method with precomputed inverse direction
|
|
Real t_min = ray.t_min_;
|
|
Real t_max_local = t_max;
|
|
|
|
// X axis
|
|
{
|
|
Real t0 = (bounds.min_.x - ray.origin_.x) * inv_dir.x;
|
|
Real t1 = (bounds.max_.x - ray.origin_.x) * inv_dir.x;
|
|
|
|
if (inv_dir.x < 0.0f) {
|
|
Real temp = t0;
|
|
t0 = t1;
|
|
t1 = temp;
|
|
}
|
|
|
|
t_min = std::max(t_min, t0);
|
|
t_max_local = std::min(t_max_local, t1);
|
|
|
|
if (t_max_local < t_min) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Y axis
|
|
{
|
|
Real t0 = (bounds.min_.y - ray.origin_.y) * inv_dir.y;
|
|
Real t1 = (bounds.max_.y - ray.origin_.y) * inv_dir.y;
|
|
|
|
if (inv_dir.y < 0.0f) {
|
|
Real temp = t0;
|
|
t0 = t1;
|
|
t1 = temp;
|
|
}
|
|
|
|
t_min = std::max(t_min, t0);
|
|
t_max_local = std::min(t_max_local, t1);
|
|
|
|
if (t_max_local < t_min) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Z axis
|
|
{
|
|
Real t0 = (bounds.min_.z - ray.origin_.z) * inv_dir.z;
|
|
Real t1 = (bounds.max_.z - ray.origin_.z) * inv_dir.z;
|
|
|
|
if (inv_dir.z < 0.0f) {
|
|
Real temp = t0;
|
|
t0 = t1;
|
|
t1 = temp;
|
|
}
|
|
|
|
t_min = std::max(t_min, t0);
|
|
t_max_local = std::min(t_max_local, t1);
|
|
|
|
if (t_max_local < t_min) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
t_min_out = t_min;
|
|
t_max_out = t_max_local;
|
|
return true;
|
|
}
|
|
|
|
inline bool BVH::intersect_triangle_fast(const Triangle &triangle, const Ray &ray,
|
|
Real t_max, HitRecord &hit) const {
|
|
// Möller-Trumbore algorithm (inlined for performance)
|
|
const Vec3 &v0 = triangle.v0_.position_;
|
|
const Vec3 &v1 = triangle.v1_.position_;
|
|
const Vec3 &v2 = triangle.v2_.position_;
|
|
|
|
const Vec3 edge1 = v1 - v0;
|
|
const Vec3 edge2 = v2 - v0;
|
|
|
|
const Vec3 h = glm::cross(ray.direction_, edge2);
|
|
const Real a = glm::dot(edge1, h);
|
|
|
|
// Check if ray is parallel to triangle
|
|
if (a > -are_epsilon && a < are_epsilon) {
|
|
return false;
|
|
}
|
|
|
|
const Real f = 1.0f / a;
|
|
const Vec3 s = ray.origin_ - v0;
|
|
const Real u = f * glm::dot(s, h);
|
|
|
|
if (u < 0.0f || u > 1.0f) {
|
|
return false;
|
|
}
|
|
|
|
const Vec3 q = glm::cross(s, edge1);
|
|
const Real v = f * glm::dot(ray.direction_, q);
|
|
|
|
if (v < 0.0f || u + v > 1.0f) {
|
|
return false;
|
|
}
|
|
|
|
const Real t = f * glm::dot(edge2, q);
|
|
|
|
if (t < ray.t_min_ || t >= t_max) {
|
|
return false;
|
|
}
|
|
|
|
// Fill hit record
|
|
const Real w = 1.0f - u - v;
|
|
|
|
hit.t_ = t;
|
|
hit.position_ = ray.origin_ + ray.direction_ * t;
|
|
hit.material_ = triangle.material_;
|
|
|
|
// Interpolate vertex attributes
|
|
hit.normal_ = glm::normalize(
|
|
w * triangle.v0_.normal_ + u * triangle.v1_.normal_ + v * triangle.v2_.normal_);
|
|
|
|
hit.texcoord_ = w * triangle.v0_.texcoord_ + u * triangle.v1_.texcoord_ + v * triangle.v2_.texcoord_;
|
|
|
|
hit.tangent_ = glm::normalize(
|
|
w * triangle.v0_.tangent_ + u * triangle.v1_.tangent_ + v * triangle.v2_.tangent_);
|
|
|
|
// Determine front face
|
|
hit.set_face_normal(ray.direction_, hit.normal_);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Keep recursive versions for reference/debugging
|
|
bool BVH::intersect_recursive(uint32_t node_index, const Ray &ray, HitRecord &hit) const {
|
|
if (node_index >= nodes_.size()) {
|
|
return false;
|
|
}
|
|
|
|
const BVHNode &node = nodes_[node_index];
|
|
|
|
Real t_min, t_max;
|
|
if (!node.bounds_.intersect_ray(ray, t_min, t_max)) {
|
|
return false;
|
|
}
|
|
|
|
if (t_min > hit.t_) {
|
|
return false;
|
|
}
|
|
|
|
bool hit_anything = false;
|
|
|
|
if (node.is_leaf()) {
|
|
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
|
|
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
|
|
|
|
if (prim_idx >= triangles_.size()) {
|
|
continue;
|
|
}
|
|
|
|
const Triangle &triangle = triangles_[prim_idx];
|
|
HitRecord temp_hit;
|
|
|
|
if (triangle.intersect(ray, temp_hit) && temp_hit.t_ < hit.t_) {
|
|
hit = temp_hit;
|
|
hit.triangle_index_ = prim_idx;
|
|
hit_anything = true;
|
|
}
|
|
}
|
|
} else {
|
|
Real t_left_min, t_left_max;
|
|
Real t_right_min, t_right_max;
|
|
|
|
bool hit_left = nodes_[node.left_child_].bounds_.intersect_ray(ray, t_left_min, t_left_max);
|
|
bool hit_right = nodes_[node.right_child_].bounds_.intersect_ray(ray, t_right_min, t_right_max);
|
|
|
|
// Traverse closer child first
|
|
if (hit_left && hit_right) {
|
|
if (t_left_min < t_right_min) {
|
|
hit_anything |= intersect_recursive(node.left_child_, ray, hit);
|
|
hit_anything |= intersect_recursive(node.right_child_, ray, hit);
|
|
} else {
|
|
hit_anything |= intersect_recursive(node.right_child_, ray, hit);
|
|
hit_anything |= intersect_recursive(node.left_child_, ray, hit);
|
|
}
|
|
} else if (hit_left) {
|
|
hit_anything |= intersect_recursive(node.left_child_, ray, hit);
|
|
} else if (hit_right) {
|
|
hit_anything |= intersect_recursive(node.right_child_, ray, hit);
|
|
}
|
|
}
|
|
|
|
return hit_anything;
|
|
}
|
|
|
|
bool BVH::intersect_any_recursive(uint32_t node_index, const Ray &ray, Real t_max) const {
|
|
if (node_index >= nodes_.size()) {
|
|
return false;
|
|
}
|
|
|
|
const BVHNode &node = nodes_[node_index];
|
|
|
|
Real t_min, t_max_box;
|
|
if (!node.bounds_.intersect_ray(ray, t_min, t_max_box)) {
|
|
return false;
|
|
}
|
|
|
|
if (t_min > t_max) {
|
|
return false;
|
|
}
|
|
|
|
if (node.is_leaf()) {
|
|
for (uint32_t i = 0; i < node.primitive_count_; ++i) {
|
|
uint32_t prim_idx = primitive_indices_[node.first_primitive_ + i];
|
|
|
|
if (prim_idx >= triangles_.size()) {
|
|
continue;
|
|
}
|
|
|
|
if (triangles_[prim_idx].intersect_fast(ray, t_max)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
return intersect_any_recursive(node.left_child_, ray, t_max) || intersect_any_recursive(node.right_child_, ray, t_max);
|
|
}
|
|
}
|
|
|
|
} // namespace are
|