File indexing completed on 2025-09-17 09:13:47
0001 #ifndef BVH_V2_TOP_DOWN_SAH_BUILDER_H
0002 #define BVH_V2_TOP_DOWN_SAH_BUILDER_H
0003
0004 #include "bvh/v2/bvh.h"
0005 #include "bvh/v2/vec.h"
0006 #include "bvh/v2/bbox.h"
0007 #include "bvh/v2/split_heuristic.h"
0008 #include <stack>
0009 #if __has_include(<span>)
0010 #include <span>
0011 #else
0012
0013 #include "ROOT/span.hxx"
0014 #endif
0015 #include <algorithm>
0016 #include <optional>
0017 #include <numeric>
0018 #include <cassert>
0019
0020 namespace bvh::v2 {
0021
0022
0023 template <typename Node>
0024 class TopDownSahBuilder {
0025 protected:
0026 using Scalar = typename Node::Scalar;
0027 using Vec = bvh::v2::Vec<Scalar, Node::dimension>;
0028 using BBox = bvh::v2::BBox<Scalar, Node::dimension>;
0029
0030 public:
0031 struct Config {
0032
0033 SplitHeuristic<Scalar> sah;
0034
0035
0036
0037
0038 size_t min_leaf_size = 1;
0039
0040
0041
0042
0043 size_t max_leaf_size = 8;
0044 };
0045
0046 protected:
0047 struct WorkItem {
0048 size_t node_id;
0049 size_t begin;
0050 size_t end;
0051
0052 BVH_ALWAYS_INLINE size_t size() const { return end - begin; }
0053 };
0054
0055 std::span<const BBox> bboxes_;
0056 std::span<const Vec> centers_;
0057 const Config& config_;
0058
0059 BVH_ALWAYS_INLINE TopDownSahBuilder(
0060 std::span<const BBox> bboxes,
0061 std::span<const Vec> centers,
0062 const Config& config)
0063 : bboxes_(bboxes)
0064 , centers_(centers)
0065 , config_(config)
0066 {
0067 assert(bboxes.size() == centers.size());
0068 assert(config.min_leaf_size <= config.max_leaf_size);
0069 }
0070
0071 virtual std::vector<size_t>& get_prim_ids() = 0;
0072 virtual std::optional<size_t> try_split(const BBox& bbox, size_t begin, size_t end) = 0;
0073
0074 BVH_ALWAYS_INLINE const std::vector<size_t>& get_prim_ids() const {
0075 return const_cast<TopDownSahBuilder*>(this)->get_prim_ids();
0076 }
0077
0078 Bvh<Node> build() {
0079 const auto prim_count = bboxes_.size();
0080
0081 Bvh<Node> bvh;
0082 bvh.nodes.reserve((2 * prim_count) / config_.min_leaf_size);
0083 bvh.nodes.emplace_back();
0084 bvh.nodes.back().set_bbox(compute_bbox(0, prim_count));
0085
0086 std::stack<WorkItem> stack;
0087 stack.push(WorkItem { 0, 0, prim_count });
0088 while (!stack.empty()) {
0089 auto item = stack.top();
0090 stack.pop();
0091
0092 auto& node = bvh.nodes[item.node_id];
0093 if (item.size() > config_.min_leaf_size) {
0094 if (auto split_pos = try_split(node.get_bbox(), item.begin, item.end)) {
0095 auto first_child = bvh.nodes.size();
0096 node.index = Node::Index::make_inner(first_child);
0097
0098 bvh.nodes.resize(first_child + 2);
0099
0100 auto first_bbox = compute_bbox(item.begin, *split_pos);
0101 auto second_bbox = compute_bbox(*split_pos, item.end);
0102 auto first_range = std::make_pair(item.begin, *split_pos);
0103 auto second_range = std::make_pair(*split_pos, item.end);
0104
0105
0106
0107
0108
0109 if (first_bbox.get_half_area() < second_bbox.get_half_area()) {
0110 std::swap(first_bbox, second_bbox);
0111 std::swap(first_range, second_range);
0112 }
0113
0114 auto first_item = WorkItem { first_child + 0, first_range.first, first_range.second };
0115 auto second_item = WorkItem { first_child + 1, second_range.first, second_range.second };
0116 bvh.nodes[first_child + 0].set_bbox(first_bbox);
0117 bvh.nodes[first_child + 1].set_bbox(second_bbox);
0118
0119
0120 if (first_item.size() < second_item.size())
0121 std::swap(first_item, second_item);
0122
0123 stack.push(first_item);
0124 stack.push(second_item);
0125 continue;
0126 }
0127 }
0128
0129 node.index = Node::Index::make_leaf(item.begin, item.size());
0130 }
0131
0132 bvh.prim_ids = std::move(get_prim_ids());
0133 bvh.nodes.shrink_to_fit();
0134 return bvh;
0135 }
0136
0137 BVH_ALWAYS_INLINE BBox compute_bbox(size_t begin, size_t end) const {
0138 const auto& prim_ids = get_prim_ids();
0139 auto bbox = BBox::make_empty();
0140 for (size_t i = begin; i < end; ++i)
0141 bbox.extend(bboxes_[prim_ids[i]]);
0142 return bbox;
0143 }
0144 };
0145
0146 }
0147
0148 #endif