#include "bvh.h" #include #include namespace hhb { namespace core { // Calculate triangle bounding box Bounds BVH::compute_bounds(const Triangle* triangle) const { Bounds bounds; bounds.expand(triangle->vertex1); bounds.expand(triangle->vertex2); bounds.expand(triangle->vertex3); return bounds; } // SAH split algorithm uint32_t BVH::split_sah(std::vector& triangles, size_t start, size_t end, Bounds& bounds, uint8_t& split_axis) { // Calculate best split axis float centroid[3]; bounds.center(centroid); // Calculate extent for each axis float extent[3]; for (int i = 0; i < 3; ++i) { extent[i] = bounds.max[i] - bounds.min[i]; } // Select longest axis as split axis split_axis = 0; if (extent[1] > extent[split_axis]) split_axis = 1; if (extent[2] > extent[split_axis]) split_axis = 2; if (extent[split_axis] <= 1e-12f) { return static_cast(start + (end - start) / 2); } // Sort triangles std::sort(triangles.begin() + start, triangles.begin() + end, [split_axis](const Triangle* a, const Triangle* b) { Bounds bounds_a, bounds_b; bounds_a.expand(a->vertex1); bounds_a.expand(a->vertex2); bounds_a.expand(a->vertex3); bounds_b.expand(b->vertex1); bounds_b.expand(b->vertex2); bounds_b.expand(b->vertex3); float centroid_a[3], centroid_b[3]; bounds_a.center(centroid_a); bounds_b.center(centroid_b); return centroid_a[split_axis] < centroid_b[split_axis]; }); // Calculate best split point const size_t num_triangles = end - start; const size_t num_buckets = 16; struct BucketInfo { size_t count = 0; Bounds bounds; } buckets[num_buckets]; // Statistics for each bucket for (size_t i = start; i < end; ++i) { const Triangle* triangle = triangles[i]; Bounds tri_bounds = compute_bounds(triangle); float centroid[3]; tri_bounds.center(centroid); float t = (centroid[split_axis] - bounds.min[split_axis]) / extent[split_axis]; size_t bucket_index = (std::min)(static_cast(t * num_buckets), num_buckets - 1); buckets[bucket_index].count++; buckets[bucket_index].bounds.expand(tri_bounds); } // Calculate cost for each possible split point float costs[num_buckets - 1]; for (size_t i = 0; i < num_buckets - 1; ++i) { Bounds left_bounds, right_bounds; size_t left_count = 0, right_count = 0; for (size_t j = 0; j <= i; ++j) { left_bounds.expand(buckets[j].bounds); left_count += buckets[j].count; } for (size_t j = i + 1; j < num_buckets; ++j) { right_bounds.expand(buckets[j].bounds); right_count += buckets[j].count; } float left_sa = left_bounds.surface_area(); float right_sa = right_bounds.surface_area(); float total_sa = bounds.surface_area(); costs[i] = TRAVERSAL_COST + (left_count * left_sa + right_count * right_sa) * SAH_COST / total_sa; } // Find minimum cost split point float min_cost = costs[0]; size_t min_bucket = 0; for (size_t i = 1; i < num_buckets - 1; ++i) { if (costs[i] < min_cost) { min_cost = costs[i]; min_bucket = i; } } // Find corresponding triangle index size_t split_index = start; for (size_t i = start; i < end; ++i) { const Triangle* triangle = triangles[i]; Bounds tri_bounds = compute_bounds(triangle); float centroid[3]; tri_bounds.center(centroid); float t = (centroid[split_axis] - bounds.min[split_axis]) / extent[split_axis]; size_t bucket_index = (std::min)(static_cast(t * num_buckets), num_buckets - 1); if (bucket_index > min_bucket) { split_index = i; break; } } // Ensure split point is valid if (split_index == start || split_index == end) { split_index = start + num_triangles / 2; } return static_cast(split_index); } // BVH build recursive function uint32_t BVH::build_recursive(std::vector& triangles, size_t start, size_t end, int current_depth) { // Update tree depth if (current_depth > tree_depth_) { tree_depth_ = current_depth; } // Create new node uint32_t node_index = static_cast(nodes_.size()); nodes_.emplace_back(); // Calculate bounding box for current triangles Bounds bounds; for (size_t i = start; i < end; ++i) { bounds.expand(compute_bounds(triangles[i])); } nodes_[node_index].bounds = bounds; size_t num_triangles = end - start; if (num_triangles <= MAX_PRIMITIVES_PER_LEAF) { // Create leaf node nodes_[node_index].is_leaf = 1; nodes_[node_index].left = static_cast(triangles_.size()); nodes_[node_index].right = static_cast(num_triangles); // Store triangle pointers for (size_t i = start; i < end; ++i) { triangles_.push_back(triangles[i]); } } else { // Create internal node nodes_[node_index].is_leaf = 0; uint8_t split_axis; uint32_t split_index = split_sah(triangles, start, end, bounds, split_axis); nodes_[node_index].split_axis = split_axis; // Recursively build left and right subtrees uint32_t left = build_recursive(triangles, start, split_index, current_depth + 1); uint32_t right = build_recursive(triangles, split_index, end, current_depth + 1); nodes_[node_index].left = left; nodes_[node_index].right = right; } return node_index; } // Build BVH void BVH::build(std::vector& triangles) { nodes_.clear(); triangles_.clear(); tree_depth_ = 0; if (!triangles.empty()) { build_recursive(triangles, 0, triangles.size(), 0); } } // Ray-bounding box intersection bool BVH::intersect_bounds(const float* ray_origin, const float* ray_direction, const Bounds& bounds, float& t_min, float& t_max) const { for (int i = 0; i < 3; ++i) { float inv_dir = 1.0f / ray_direction[i]; float t0 = (bounds.min[i] - ray_origin[i]) * inv_dir; float t1 = (bounds.max[i] - ray_origin[i]) * inv_dir; if (inv_dir < 0.0f) { std::swap(t0, t1); } t_min = (std::max)(t_min, t0); t_max = (std::min)(t_max, t1); if (t_min > t_max) { return false; } } return true; } // Ray-triangle intersection bool BVH::intersect_triangle(const float* ray_origin, const float* ray_direction, const Triangle* triangle, float& t_hit) const { const float* v0 = triangle->vertex1; const float* v1 = triangle->vertex2; const float* v2 = triangle->vertex3; // Calculate edge vectors float e1[3] = {v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]}; float e2[3] = {v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]}; // Calculate normal vector float pvec[3] = { ray_direction[1] * e2[2] - ray_direction[2] * e2[1], ray_direction[2] * e2[0] - ray_direction[0] * e2[2], ray_direction[0] * e2[1] - ray_direction[1] * e2[0] }; float det = e1[0] * pvec[0] + e1[1] * pvec[1] + e1[2] * pvec[2]; if (det < 1e-8f) { return false; } float inv_det = 1.0f / det; float tvec[3] = { ray_origin[0] - v0[0], ray_origin[1] - v0[1], ray_origin[2] - v0[2] }; float u = (tvec[0] * pvec[0] + tvec[1] * pvec[1] + tvec[2] * pvec[2]) * inv_det; if (u < 0.0f || u > 1.0f) { return false; } float qvec[3] = { tvec[1] * e1[2] - tvec[2] * e1[1], tvec[2] * e1[0] - tvec[0] * e1[2], tvec[0] * e1[1] - tvec[1] * e1[0] }; float v = (ray_direction[0] * qvec[0] + ray_direction[1] * qvec[1] + ray_direction[2] * qvec[2]) * inv_det; if (v < 0.0f || u + v > 1.0f) { return false; } t_hit = (e2[0] * qvec[0] + e2[1] * qvec[1] + e2[2] * qvec[2]) * inv_det; if (t_hit < 0.0f) { return false; } return true; } // Ray-BVH intersection bool BVH::intersect(const float* ray_origin, const float* ray_direction, float& t_hit, Triangle*& hit_triangle) const { if (nodes_.empty()) { return false; } t_hit = 1e38f; hit_triangle = nullptr; // Use pre-allocated stack space to avoid recursion static constexpr size_t MAX_STACK_SIZE = 64; uint32_t stack[MAX_STACK_SIZE]; int stack_ptr = 0; stack[stack_ptr++] = 0; while (stack_ptr > 0) { uint32_t node_index = stack[--stack_ptr]; const BVHNode& node = nodes_[node_index]; // Ray-bounding box intersection float t_min = 0.0f, t_max = 1e38f; if (!intersect_bounds(ray_origin, ray_direction, node.bounds, t_min, t_max)) { continue; } if (node.is_leaf) { // Traverse triangles in leaf node uint32_t start = node.left; uint32_t count = node.right; for (uint32_t i = 0; i < count; ++i) { Triangle* triangle = triangles_[start + i]; float t; if (intersect_triangle(ray_origin, ray_direction, triangle, t) && t < t_hit) { t_hit = t; hit_triangle = triangle; } } } else { // Push children to stack in distance order float t_min_left = 0.0f, t_max_left = 1e38f; float t_min_right = 0.0f, t_max_right = 1e38f; intersect_bounds(ray_origin, ray_direction, nodes_[node.left].bounds, t_min_left, t_max_left); intersect_bounds(ray_origin, ray_direction, nodes_[node.right].bounds, t_min_right, t_max_right); if (t_min_left < t_min_right) { stack[stack_ptr++] = node.right; stack[stack_ptr++] = node.left; } else { stack[stack_ptr++] = node.left; stack[stack_ptr++] = node.right; } } } return hit_triangle != nullptr; } } // namespace core } // namespace hhb