Antkeeper  0.0.1
bvh.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Christopher J. Howard
3  *
4  * This file is part of Antkeeper source code.
5  *
6  * Antkeeper source code is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Antkeeper source code is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <engine/geom/bvh/bvh.hpp>
23 #include <engine/debug/log.hpp>
24 #include <algorithm>
25 #include <numeric>
26 
27 namespace geom {
28 
29 bvh::bvh(std::span<const bvh_primitive> primitives)
30 {
31  build(primitives);
32 }
33 
34 bvh::bvh(const brep_mesh& mesh)
35 {
36  build(mesh);
37 }
38 
39 void bvh::build(std::span<const bvh_primitive> primitives)
40 {
41  if (primitives.empty())
42  {
43  clear();
44  }
45  else
46  {
47  // Allocate and fill primitive index array
48  m_primitive_indices.resize(primitives.size());
49  std::iota(m_primitive_indices.begin(), m_primitive_indices.end(), 0);
50 
51  // Allocate nodes
52  m_nodes.resize(primitives.size() << 1);
53 
54  // Init root node
55  m_node_count = 1;
56  auto& root = m_nodes.front();
57  root.size = static_cast<std::uint32_t>(primitives.size());
58  root.offset = 0;
59  update_bounds(root, primitives);
60 
61  // Recursively build BVH
62  subdivide(root, primitives);
63  }
64 }
65 
66 void bvh::build(const brep_mesh& mesh)
67 {
68  // Get mesh vertex positions attribute
69  const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
70 
71  // Allocate BVH primitives for mesh faces
72  std::vector<bvh_primitive> primitives(mesh.faces().size());
73 
74  // Calculate bounding boxes
75  for (brep_face* face: mesh.faces())
76  {
77  auto& primitive = primitives[face->index()];
78  primitive.centroid = {};
79  primitive.bounds = {math::fvec3::infinity(), -math::fvec3::infinity()};
80 
81  for (brep_loop* loop: face->loops())
82  {
83  const auto& vertex_position = vertex_positions[loop->vertex()->index()];
84 
85  primitive.centroid += vertex_position;
86  primitive.bounds.extend(vertex_position);
87  }
88 
89  primitive.centroid /= static_cast<float>(face->loops().size());
90  }
91 
92  // Build BVH from the bounding boxes of the mesh faces
93  build(primitives);
94 }
95 
96 void bvh::clear()
97 {
98  m_primitive_indices.clear();
99  m_nodes.clear();
100  m_node_count = 0;
101 }
102 
103 void bvh::update_bounds(bvh_node& node, const std::span<const bvh_primitive>& primitives)
104 {
106  for (std::uint32_t i = 0; i < node.size; ++i)
107  {
108  const auto& primitive = primitives[m_primitive_indices[node.offset + i]];
109  node.bounds.extend(primitive.bounds);
110  };
111 }
112 
113 void bvh::subdivide(bvh_node& node, const std::span<const bvh_primitive>& primitives)
114 {
115  if (node.size <= 2)
116  {
117  return;
118  }
119 
120  // Determine index of split axis
121  const auto extents = node.bounds.size();
122  std::uint8_t split_axis = 0;
123  if (extents.y() > extents.x())
124  {
125  split_axis = 1;
126  }
127  if (extents.z() > extents[split_axis])
128  {
129  split_axis = 2;
130  }
131 
132  // Determine split coordinate
133  const float split_coord = node.bounds.min[split_axis] + extents[split_axis] * 0.5f;
134 
135  std::uint32_t i = node.offset;
136  std::uint32_t j = (node.size) ? i + node.size - 1 : i;
137  while (i <= j)
138  {
139  const auto& primitive = primitives[m_primitive_indices[i]];
140 
141  if (primitive.centroid[split_axis] < split_coord)
142  // if (primitive.bounds.center()[split_axis] < split_coord)
143  {
144  ++i;
145  }
146  else
147  {
148  std::swap(m_primitive_indices[i], m_primitive_indices[j]);
149  if (!j)
150  {
151  break;
152  }
153  --j;
154  }
155  }
156 
157  const std::uint32_t left_size = i - node.offset;
158  if (!left_size || left_size == node.size)
159  {
160  return;
161  }
162 
163  const std::uint32_t left_index = m_node_count++;
164  auto& left_child = m_nodes[left_index];
165  left_child.offset = node.offset;
166  left_child.size = left_size;
167  update_bounds(left_child, primitives);
168 
169  const std::uint32_t right_index = m_node_count++;
170  auto& right_child = m_nodes[right_index];
171  right_child.offset = i;
172  right_child.size = node.size - left_size;
173  update_bounds(right_child, primitives);
174 
175  node.offset = left_index;
176  node.size = 0;
177  subdivide(left_child, primitives);
178  subdivide(right_child, primitives);
179 }
180 
181 void bvh::visit(const bvh_node& node, const geom::ray<float, 3>& ray, const visitor_type& f) const
182 {
183  if (!geom::intersection(ray, node.bounds))
184  {
185  return;
186  }
187 
188  if (node.is_leaf())
189  {
190  // Visit leaf node primitives
191  for (std::uint32_t i = 0; i < node.size; ++i)
192  {
193  f(m_primitive_indices[node.offset + i]);
194  }
195  }
196  else
197  {
198  // Recursively visit node children
199  visit(m_nodes[node.offset], ray, f);
200  visit(m_nodes[node.offset + 1], ray, f);
201  }
202 }
203 
204 } // namespace geom
Portion of a shell bounded by loops.
Definition: brep-face.hpp:244
Connected boundary of a single face.
Definition: brep-loop.hpp:38
Boundary representation (B-rep) of a mesh.
Definition: brep-mesh.hpp:34
void clear()
Clears the BVH.
Definition: bvh.cpp:96
void build(std::span< const bvh_primitive > primitives)
Constructs a BVH from a set of primitives.
Definition: bvh.cpp:39
constexpr bvh() noexcept=default
Constructs an empty BVH.
void visit(const geom::ray< float, 3 > &ray, const visitor_type &f) const
Visits the primitive indices of all BVH nodes that intersect a ray.
Definition: bvh.hpp:83
Geometric algorithms.
constexpr std::optional< T > intersection(const ray< T, N > &ray, const hyperplane< T, N > &hyperplane) noexcept
Ray-hyperplane intersection test.
Single node in a bounding volume hierarchy.
Definition: bvh-node.hpp:32
std::uint32_t offset
Offset to the first child node (non-leaf) or primitive (leaf).
Definition: bvh-node.hpp:46
std::uint32_t size
Number of primitives in the node.
Definition: bvh-node.hpp:43
geom::box< float > bounds
Node bounds.
Definition: bvh-node.hpp:40
Half of a line proceeding from an initial point.
Definition: ray.hpp:38
n-dimensional vector.
Definition: vector.hpp:44
static constexpr vector infinity() noexcept
Returns a vector of infinities, where every element is equal to infinity.
Definition: vector.hpp:342