BambuStudio/mcut/source/bvh.cpp

1149 lines
48 KiB
C++
Raw Normal View History

2024-12-20 06:44:50 +00:00
/**
* Copyright (c) 2021-2022 Floyd M. Chitalu.
* All rights reserved.
*
* NOTE: This file is licensed under GPL-3.0-or-later (default).
* A commercial license can be purchased from Floyd M. Chitalu.
*
* License details:
*
* (A) GNU General Public License ("GPL"); a copy of which you should have
* recieved with this file.
* - see also: <http://www.gnu.org/licenses/>
* (B) Commercial license.
* - email: floyd.m.chitalu@gmail.com
*
* The commercial license options is for users that wish to use MCUT in
* their products for comercial purposes but do not wish to release their
* software products under the GPL license.
*
* Author(s) : Floyd M. Chitalu
*/
#include <mcut/internal/bvh.h>
#include <mcut/internal/utils.h>
#include "mcut/internal/timer.h"
#include <cmath> // see: if it is possible to remove thsi header
#include <queue>
#ifdef _MSC_VER
#include <intrin.h>
#define __builtin_popcount __popcnt
// https://stackoverflow.com/questions/355967/how-to-use-msvc-intrinsics-to-get-the-equivalent-of-this-gcc-code
unsigned int __inline clz_(unsigned int value)
{
unsigned long leading_zero = 0;
if (_BitScanReverse(&leading_zero, value)) {
return 31 - leading_zero;
} else {
// Same remarks as above
return 32;
}
}
#endif
#ifndef CHAR_BIT
#define CHAR_BIT 8
#endif
#if defined(USE_OIBVH)
// count leading zeros in 32 bit bitfield
unsigned int clz(unsigned int x) // stub
{
#ifdef _MSC_VER
return clz_(x);
#else
return __builtin_clz(x); // only tested with gcc!!!
#endif
}
// next power of two from x
int next_power_of_two(int x)
{
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x++;
return x;
}
// check if "x" is a power of two
bool is_power_of_two(int x)
{
return (x != 0) && !(x & (x - 1));
}
// compute log-base-2 of "x"
int ilog2(unsigned int x)
{
return sizeof(unsigned int) * CHAR_BIT - clz(x) - 1;
}
// compute index (0...N-1) of the leaf level from the number of leaves
int get_leaf_level_from_real_leaf_count(const int t)
{
const int np2 = next_power_of_two(t); // todo
const int tLeafLev = ilog2(np2);
return tLeafLev;
}
// compute tree-level index from implicit index of a node
int get_level_from_implicit_idx(const int bvhNodeImplicitIndex)
{
return ilog2(bvhNodeImplicitIndex + 1);
}
// compute previous power of two
unsigned int flp2(unsigned int x) // prev pow2
{
x = x | (x >> 1);
x = x | (x >> 2);
x = x | (x >> 4);
x = x | (x >> 8);
x = x | (x >> 16);
return x - (x >> 1);
}
// compute size of of Oi-BVH give number of triangles
int get_ostensibly_implicit_bvh_size(const int t)
{
return 2 * t - 1 + __builtin_popcount(next_power_of_two(t) - t);
}
// compute left-most node on a given level
int get_level_leftmost_node(const int node_level)
{
return (1 << node_level) - 1;
}
// compute right-most leaf node in tree
int get_rightmost_real_leaf(const int bvhLeafLevelIndex, const int num_real_leaf_nodes_in_bvh)
{
return (get_level_leftmost_node(bvhLeafLevelIndex) + num_real_leaf_nodes_in_bvh) - 1;
}
// check if node is a "real node"
bool is_real_implicit_tree_node_id(const int bvhNodeImplicitIndex, const int num_real_leaf_nodes_in_bvh)
{
const int t = num_real_leaf_nodes_in_bvh;
// const int q = bvhNodeImplicitIndex; // queried node
const int li = get_leaf_level_from_real_leaf_count(t);
const int i = get_rightmost_real_leaf(li, t);
const int lq = get_level_from_implicit_idx(bvhNodeImplicitIndex);
const int p = (int)((1.0f / (1 << (li - lq))) + ((float)i / (1 << (li - lq))) - 1);
return bvhNodeImplicitIndex <= p || p == 0; // and p is not the root
}
// get the right most real node on a given tree level
int get_level_rightmost_real_node(
const int rightmostRealLeafNodeImplicitIndex,
const int bvhLeafLevelIndex,
const int ancestorLevelIndex)
{
using namespace std;
const int level_dist = (bvhLeafLevelIndex - ancestorLevelIndex);
const int implicit_index_of_ancestor = (int)((1.0f / (1 << level_dist)) + ((float)rightmostRealLeafNodeImplicitIndex / (1 << level_dist)) - 1);
return implicit_index_of_ancestor;
}
// compute implicit index of a node's ancestor
int get_node_ancestor(
const int nodeImplicitIndex,
const int nodeLevelIndex,
const int ancestorLevelIndex)
{
using namespace std;
const int levelDistance = nodeLevelIndex - ancestorLevelIndex;
return (int)((1.0f / (1 << levelDistance)) + ((float)nodeImplicitIndex / (1 << levelDistance)) - 1); /*trunc((1.0f / pow(bvhDegree, level_dist)) + (rightmostRealLeafNodeImplicitIndex / pow(bvhDegree, level_dist)) - 1)*/
}
// calculate linear memory index of a real node
int get_node_mem_index(
const int nodeImplicitIndex,
const int leftmostImplicitIndexOnNodeLevel,
const int bvh_data_base_offset,
const int rightmostRealNodeImplicitIndexOnNodeLevel)
{
return bvh_data_base_offset + get_ostensibly_implicit_bvh_size((rightmostRealNodeImplicitIndexOnNodeLevel - leftmostImplicitIndexOnNodeLevel) + 1) - 1 - (rightmostRealNodeImplicitIndexOnNodeLevel - nodeImplicitIndex);
}
// Expands a 10-bit integer into 30 bits by inserting 2 zeros after each bit.
unsigned int expandBits(unsigned int v)
{
v = (v * 0x00010001u) & 0xFF0000FFu;
v = (v * 0x00000101u) & 0x0F00F00Fu;
v = (v * 0x00000011u) & 0xC30C30C3u;
v = (v * 0x00000005u) & 0x49249249u;
return v;
};
// Calculates a 30-bit Morton code for the given 3D point located within the unit cube [0,1].
unsigned int morton3D(float x, float y, float z)
{
x = std::fmin(std::fmax(x * 1024.0f, 0.0f), 1023.0f);
y = std::fmin(std::fmax(y * 1024.0f, 0.0f), 1023.0f);
z = std::fmin(std::fmax(z * 1024.0f, 0.0f), 1023.0f);
unsigned int xx = expandBits((unsigned int)x);
unsigned int yy = expandBits((unsigned int)y);
unsigned int zz = expandBits((unsigned int)z);
return (xx * 4 + yy * 2 + zz);
};
void build_oibvh(
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
thread_pool& pool,
#endif
const hmesh_t& mesh,
std::vector<bounding_box_t<vec3>>& bvhAABBs,
std::vector<fd_t>& bvhLeafNodeFaces,
std::vector<bounding_box_t<vec3>>& face_bboxes,
const double& slightEnlargmentEps)
{
SCOPED_TIMER(__FUNCTION__);
const int meshFaceCount = mesh.number_of_faces();
const int bvhNodeCount = get_ostensibly_implicit_bvh_size(meshFaceCount);
// compute mesh-face bounding boxes and their centers
// ::::::::::::::::::::::::::::::::::::::::::::::::::
face_bboxes.resize(meshFaceCount); //, bounding_box_t<vec3>());
std::vector<vec3> face_bbox_centers(meshFaceCount, vec3());
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
{
auto fn_compute_face_bbox_data = [&](face_array_iterator_t block_start_, face_array_iterator_t block_end_) {
for (face_array_iterator_t f = block_start_; f != block_end_; ++f) {
const int faceIdx = static_cast<int>(*f);
std::vector<vd_t> vertices_on_face = mesh.get_vertices_around_face(*f);
// for each vertex on face
for (std::vector<vd_t>::const_iterator v = vertices_on_face.cbegin(); v != vertices_on_face.cend(); ++v) {
const vec3 coords = mesh.vertex(*v);
face_bboxes[faceIdx].expand(coords);
}
bounding_box_t<vec3>& bbox = face_bboxes[faceIdx];
if (slightEnlargmentEps > double(0.0)) {
bbox.enlarge(slightEnlargmentEps);
}
// calculate bbox center
face_bbox_centers[*f] = (bbox.minimum() + bbox.maximum()) / 2;
}
};
parallel_for(
pool,
mesh.faces_begin(),
mesh.faces_end(),
fn_compute_face_bbox_data);
}
#else
// for each face in mesh
for (face_array_iterator_t f = mesh.faces_begin(); f != mesh.faces_end(); ++f) {
const int faceIdx = static_cast<int>(*f);
const std::vector<vd_t> vertices_on_face = mesh.get_vertices_around_face(*f);
// for each vertex on face
for (std::vector<vd_t>::const_iterator v = vertices_on_face.cbegin(); v != vertices_on_face.cend(); ++v) {
const vec3 coords = mesh.vertex(*v);
face_bboxes[faceIdx].expand(coords);
}
bounding_box_t<vec3>& bbox = face_bboxes[faceIdx];
if (slightEnlargmentEps > double(0.0)) {
bbox.enlarge(slightEnlargmentEps);
}
// calculate bbox center
face_bbox_centers[*f] = (bbox.minimum() + bbox.maximum()) / 2;
}
#endif
// compute mesh bounding box
// :::::::::::::::::::::::::
bvhAABBs.resize(bvhNodeCount);
bounding_box_t<vec3>& meshBbox = bvhAABBs.front(); // root bounding box
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
{
std::mutex bbox_expansion_mtx;
auto fn_compute_mesh_bbox = [&](vertex_array_iterator_t block_start_, vertex_array_iterator_t block_end_) {
bounding_box_t<vec3> meshBbox_local;
for (vertex_array_iterator_t v = block_start_; v != block_end_; ++v) {
const vec3& coords = mesh.vertex(*v);
meshBbox_local.expand(coords);
}
std::lock_guard<std::mutex> lock(bbox_expansion_mtx);
meshBbox.expand(meshBbox_local);
};
parallel_for(
pool,
mesh.vertices_begin(),
mesh.vertices_end(),
fn_compute_mesh_bbox);
}
#else
// for each vertex in mesh
for (vertex_array_iterator_t v = mesh.vertices_begin(); v != mesh.vertices_end(); ++v) {
const vec3& coords = mesh.vertex(*v);
meshBbox.expand(coords);
}
#endif
// compute morton codes
// ::::::::::::::::::::
std::vector<std::pair<fd_t, uint32_t>> bvhLeafNodeDescriptors(meshFaceCount, std::pair<fd_t, uint32_t>());
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
{
auto fn_compute_morton_codes = [&](face_array_iterator_t block_start_, face_array_iterator_t block_end_) {
for (face_array_iterator_t f = block_start_; f != block_end_; ++f) {
const uint32_t faceIdx = static_cast<uint32_t>(*f);
const vec3& face_aabb_centre = SAFE_ACCESS(face_bbox_centers, faceIdx);
const vec3 offset = face_aabb_centre - meshBbox.minimum();
const vec3 dims = meshBbox.maximum() - meshBbox.minimum();
const unsigned int mortion_code = morton3D(
static_cast<float>(offset.x() / dims.x()),
static_cast<float>(offset.y() / dims.y()),
static_cast<float>(offset.z() / dims.z()));
const uint32_t idx = (uint32_t)std::distance(mesh.faces_begin(), f); // NOTE: mesh.faces_begin() may not be the actual beginning internally
bvhLeafNodeDescriptors[idx].first = *f;
bvhLeafNodeDescriptors[idx].second = mortion_code;
}
};
parallel_for(
pool,
mesh.faces_begin(),
mesh.faces_end(),
fn_compute_morton_codes);
}
#else
for (face_array_iterator_t f = mesh.faces_begin(); f != mesh.faces_end(); ++f) {
const uint32_t faceIdx = static_cast<uint32_t>(*f);
const vec3& face_aabb_centre = SAFE_ACCESS(face_bbox_centers, faceIdx);
const vec3 offset = face_aabb_centre - meshBbox.minimum();
const vec3 dims = meshBbox.maximum() - meshBbox.minimum();
const unsigned int mortion_code = morton3D(
static_cast<float>(offset.x() / dims.x()),
static_cast<float>(offset.y() / dims.y()),
static_cast<float>(offset.z() / dims.z()));
const uint32_t idx = (uint32_t)std::distance(mesh.faces_begin(), f); // NOTE: mesh.faces_begin() may not be the actual beginning internally
bvhLeafNodeDescriptors[idx].first = *f;
bvhLeafNodeDescriptors[idx].second = mortion_code;
}
#endif
// sort faces according to morton codes
// TODO: make parallel
std::sort(
bvhLeafNodeDescriptors.begin(),
bvhLeafNodeDescriptors.end(),
[](const std::pair<fd_t, uint32_t>& a, const std::pair<fd_t, uint32_t>& b) {
return a.second < b.second;
});
bvhLeafNodeFaces.resize(meshFaceCount);
const int leaf_level_index = get_leaf_level_from_real_leaf_count(meshFaceCount);
const int leftmost_real_node_on_leaf_level = get_level_leftmost_node(leaf_level_index);
const int rightmost_real_leaf = get_rightmost_real_leaf(leaf_level_index, meshFaceCount);
const int rightmost_real_node_on_leaf_level = get_level_rightmost_real_node(rightmost_real_leaf, leaf_level_index, leaf_level_index);
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
{
auto fn_save_leaf_node_info = [&](std::vector<std::pair<fd_t, uint32_t>>::const_iterator block_start_, std::vector<std::pair<fd_t, uint32_t>>::const_iterator block_end_) {
for (std::vector<std::pair<fd_t, uint32_t>>::const_iterator it = block_start_; it != block_end_; ++it) {
const uint32_t index_on_leaf_level = (uint32_t)std::distance(bvhLeafNodeDescriptors.cbegin(), it);
bvhLeafNodeFaces[index_on_leaf_level] = it->first;
const int implicit_idx = leftmost_real_node_on_leaf_level + index_on_leaf_level;
const int memory_idx = get_node_mem_index(
implicit_idx,
leftmost_real_node_on_leaf_level,
0,
rightmost_real_node_on_leaf_level);
const bounding_box_t<vec3>& face_bbox = face_bboxes[(uint32_t)it->first];
bvhAABBs[memory_idx] = face_bbox;
}
};
parallel_for(
pool,
bvhLeafNodeDescriptors.cbegin(),
bvhLeafNodeDescriptors.cend(),
fn_save_leaf_node_info);
}
#else
// save sorted leaf node bvhAABBs and their corrresponding face id
for (std::vector<std::pair<fd_t, uint32_t>>::const_iterator it = bvhLeafNodeDescriptors.cbegin(); it != bvhLeafNodeDescriptors.cend(); ++it) {
const uint32_t index_on_leaf_level = (uint32_t)std::distance(bvhLeafNodeDescriptors.cbegin(), it);
bvhLeafNodeFaces[index_on_leaf_level] = it->first;
const int implicit_idx = leftmost_real_node_on_leaf_level + index_on_leaf_level;
const int memory_idx = get_node_mem_index(
implicit_idx,
leftmost_real_node_on_leaf_level,
0,
rightmost_real_node_on_leaf_level);
const bounding_box_t<vec3>& face_bbox = face_bboxes[(uint32_t)it->first];
bvhAABBs[memory_idx] = face_bbox;
}
#endif
// construct internal-node bounding boxes
// ::::::::::::::::::::::::::::::::::::::
// for each level in the oi-bvh tree (starting from the penultimate level)
for (int level_index = leaf_level_index - 1; level_index >= 0; --level_index) {
const int rightmost_real_node_on_level = get_level_rightmost_real_node(rightmost_real_leaf, leaf_level_index, level_index);
const int leftmost_real_node_on_level = get_level_leftmost_node(level_index);
const int number_of_real_nodes_on_level = (rightmost_real_node_on_level - leftmost_real_node_on_level) + 1;
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
{
// allows us to pretend that we can use an iterator over the nodes
std::vector<uint8_t> level_nodes_placeholder(number_of_real_nodes_on_level);
auto fn_compute_bvh_level_nodes = [&](std::vector<uint8_t>::const_iterator block_start_, std::vector<uint8_t>::const_iterator block_end_) {
uint32_t base_offset = std::distance(level_nodes_placeholder.cbegin(), block_start_);
uint32_t counter = 0;
for (std::vector<uint8_t>::const_iterator it = block_start_; it != block_end_; ++it) {
const int level_node_idx_iter = base_offset + (counter++);
const int node_implicit_idx = leftmost_real_node_on_level + level_node_idx_iter;
const int left_child_implicit_idx = (node_implicit_idx * 2) + 1;
const int right_child_implicit_idx = (node_implicit_idx * 2) + 2;
const bool is_penultimate_level = (level_index == (leaf_level_index - 1));
const int rightmost_real_node_on_child_level = get_level_rightmost_real_node(rightmost_real_leaf, leaf_level_index, level_index + 1);
const int leftmost_real_node_on_child_level = get_level_leftmost_node(level_index + 1);
const bool right_child_exists = (right_child_implicit_idx <= rightmost_real_node_on_child_level);
bounding_box_t<vec3> node_bbox;
if (is_penultimate_level) { // both children are leaves
const int left_child_index_on_level = left_child_implicit_idx - leftmost_real_node_on_child_level;
const fd_t& left_child_face = SAFE_ACCESS(bvhLeafNodeFaces, left_child_index_on_level);
const bounding_box_t<vec3>& left_child_bbox = SAFE_ACCESS(face_bboxes, left_child_face);
node_bbox.expand(left_child_bbox);
if (right_child_exists) {
const int right_child_index_on_level = right_child_implicit_idx - leftmost_real_node_on_child_level;
const fd_t& right_child_face = SAFE_ACCESS(bvhLeafNodeFaces, right_child_index_on_level);
const bounding_box_t<vec3>& right_child_bbox = SAFE_ACCESS(face_bboxes, right_child_face);
node_bbox.expand(right_child_bbox);
}
} else { // remaining internal node levels
const int left_child_memory_idx = get_node_mem_index(
left_child_implicit_idx,
leftmost_real_node_on_child_level,
0,
rightmost_real_node_on_child_level);
const bounding_box_t<vec3>& left_child_bbox = SAFE_ACCESS(bvhAABBs, left_child_memory_idx);
node_bbox.expand(left_child_bbox);
if (right_child_exists) {
const int right_child_memory_idx = get_node_mem_index(
right_child_implicit_idx,
leftmost_real_node_on_child_level,
0,
rightmost_real_node_on_child_level);
const bounding_box_t<vec3>& right_child_bbox = SAFE_ACCESS(bvhAABBs, right_child_memory_idx);
node_bbox.expand(right_child_bbox);
}
}
const int node_memory_idx = get_node_mem_index(
node_implicit_idx,
leftmost_real_node_on_level,
0,
rightmost_real_node_on_level);
SAFE_ACCESS(bvhAABBs, node_memory_idx) = node_bbox;
}
};
parallel_for(
pool,
level_nodes_placeholder.cbegin(),
level_nodes_placeholder.cend(),
fn_compute_bvh_level_nodes);
}
#else
// for each node on the current level
for (int level_node_idx_iter = 0; level_node_idx_iter < number_of_real_nodes_on_level; ++level_node_idx_iter) {
const int node_implicit_idx = leftmost_real_node_on_level + level_node_idx_iter;
const int left_child_implicit_idx = (node_implicit_idx * 2) + 1;
const int right_child_implicit_idx = (node_implicit_idx * 2) + 2;
const bool is_penultimate_level = (level_index == (leaf_level_index - 1));
const int rightmost_real_node_on_child_level = get_level_rightmost_real_node(rightmost_real_leaf, leaf_level_index, level_index + 1);
const int leftmost_real_node_on_child_level = get_level_leftmost_node(level_index + 1);
const bool right_child_exists = (right_child_implicit_idx <= rightmost_real_node_on_child_level);
bounding_box_t<vec3> node_bbox;
if (is_penultimate_level) { // both children are leaves
const int left_child_index_on_level = left_child_implicit_idx - leftmost_real_node_on_child_level;
const fd_t& left_child_face = SAFE_ACCESS(bvhLeafNodeFaces, left_child_index_on_level);
const bounding_box_t<vec3>& left_child_bbox = SAFE_ACCESS(face_bboxes, left_child_face);
node_bbox.expand(left_child_bbox);
if (right_child_exists) {
const int right_child_index_on_level = right_child_implicit_idx - leftmost_real_node_on_child_level;
const fd_t& right_child_face = SAFE_ACCESS(bvhLeafNodeFaces, right_child_index_on_level);
const bounding_box_t<vec3>& right_child_bbox = SAFE_ACCESS(face_bboxes, right_child_face);
node_bbox.expand(right_child_bbox);
}
} else { // remaining internal node levels
const int left_child_memory_idx = get_node_mem_index(
left_child_implicit_idx,
leftmost_real_node_on_child_level,
0,
rightmost_real_node_on_child_level);
const bounding_box_t<vec3>& left_child_bbox = SAFE_ACCESS(bvhAABBs, left_child_memory_idx);
node_bbox.expand(left_child_bbox);
if (right_child_exists) {
const int right_child_memory_idx = get_node_mem_index(
right_child_implicit_idx,
leftmost_real_node_on_child_level,
0,
rightmost_real_node_on_child_level);
const bounding_box_t<vec3>& right_child_bbox = SAFE_ACCESS(bvhAABBs, right_child_memory_idx);
node_bbox.expand(right_child_bbox);
}
}
const int node_memory_idx = get_node_mem_index(
node_implicit_idx,
leftmost_real_node_on_level,
0,
rightmost_real_node_on_level);
SAFE_ACCESS(bvhAABBs, node_memory_idx) = node_bbox;
} // for each real node on level
#endif
} // for each internal level
}
void intersectOIBVHs(
std::map<fd_t, std::vector<fd_t>>& ps_face_to_potentially_intersecting_others,
const std::vector<bounding_box_t<vec3>>& srcMeshBvhAABBs,
const std::vector<fd_t>& srcMeshBvhLeafNodeFaces,
const std::vector<bounding_box_t<vec3>>& cutMeshBvhAABBs,
const std::vector<fd_t>& cutMeshBvhLeafNodeFaces)
{
TIMESTACK_PUSH(__FUNCTION__);
// simultaneuosly traverse both BVHs to find intersecting pairs
std::queue<node_pair_t> traversalQueue;
traversalQueue.push({ 0, 0 }); // left = sm BVH; right = cm BVH
const int numSrcMeshFaces = (int)srcMeshBvhLeafNodeFaces.size();
MCUT_ASSERT(numSrcMeshFaces >= 1);
const int numCutMeshFaces = (int)cutMeshBvhLeafNodeFaces.size();
MCUT_ASSERT(numCutMeshFaces >= 1);
const int sm_bvh_leaf_level_idx = get_leaf_level_from_real_leaf_count(numSrcMeshFaces);
const int cs_bvh_leaf_level_idx = get_leaf_level_from_real_leaf_count(numCutMeshFaces);
const int sm_bvh_rightmost_real_leaf = get_rightmost_real_leaf(sm_bvh_leaf_level_idx, numSrcMeshFaces);
const int cs_bvh_rightmost_real_leaf = get_rightmost_real_leaf(cs_bvh_leaf_level_idx, numCutMeshFaces);
do {
node_pair_t ct_front_node = traversalQueue.front();
bounding_box_t<vec3> sm_bvh_node_bbox;
bounding_box_t<vec3> cs_bvh_node_bbox;
// sm
const int sm_bvh_node_implicit_idx = ct_front_node.m_left;
const int sm_bvh_node_level_idx = get_level_from_implicit_idx(sm_bvh_node_implicit_idx);
const bool sm_bvh_node_is_leaf = sm_bvh_node_level_idx == sm_bvh_leaf_level_idx;
const int sm_bvh_node_level_leftmost_node = get_level_leftmost_node(sm_bvh_node_level_idx);
fd_t sm_node_face = hmesh_t::null_face();
const int sm_bvh_node_level_rightmost_node = get_level_rightmost_real_node(sm_bvh_rightmost_real_leaf, sm_bvh_leaf_level_idx, sm_bvh_node_level_idx);
const int sm_bvh_node_mem_idx = get_node_mem_index(
sm_bvh_node_implicit_idx,
sm_bvh_node_level_leftmost_node,
0,
sm_bvh_node_level_rightmost_node);
sm_bvh_node_bbox = SAFE_ACCESS(srcMeshBvhAABBs, sm_bvh_node_mem_idx);
if (sm_bvh_node_is_leaf) {
const int sm_bvh_node_idx_on_level = sm_bvh_node_implicit_idx - sm_bvh_node_level_leftmost_node;
sm_node_face = SAFE_ACCESS(srcMeshBvhLeafNodeFaces, sm_bvh_node_idx_on_level);
}
// cs
const int cs_bvh_node_implicit_idx = ct_front_node.m_right;
const int cs_bvh_node_level_idx = get_level_from_implicit_idx(cs_bvh_node_implicit_idx);
const int cs_bvh_node_level_leftmost_node = get_level_leftmost_node(cs_bvh_node_level_idx);
const bool cs_bvh_node_is_leaf = cs_bvh_node_level_idx == cs_bvh_leaf_level_idx;
fd_t cs_node_face = hmesh_t::null_face();
const int cs_bvh_node_level_rightmost_node = get_level_rightmost_real_node(cs_bvh_rightmost_real_leaf, cs_bvh_leaf_level_idx, cs_bvh_node_level_idx);
const int cs_bvh_node_mem_idx = get_node_mem_index(
cs_bvh_node_implicit_idx,
cs_bvh_node_level_leftmost_node,
0,
cs_bvh_node_level_rightmost_node);
cs_bvh_node_bbox = SAFE_ACCESS(cutMeshBvhAABBs, cs_bvh_node_mem_idx);
if (cs_bvh_node_is_leaf) {
const int cs_bvh_node_idx_on_level = cs_bvh_node_implicit_idx - cs_bvh_node_level_leftmost_node;
cs_node_face = SAFE_ACCESS(cutMeshBvhLeafNodeFaces, cs_bvh_node_idx_on_level);
}
const bool haveOverlap = intersect_bounding_boxes(sm_bvh_node_bbox, cs_bvh_node_bbox);
if (haveOverlap) {
if (cs_bvh_node_is_leaf && sm_bvh_node_is_leaf) {
MCUT_ASSERT(cs_node_face != hmesh_t::null_face());
MCUT_ASSERT(sm_node_face != hmesh_t::null_face());
fd_t cs_node_face_offsetted = fd_t(cs_node_face + numSrcMeshFaces);
ps_face_to_potentially_intersecting_others[sm_node_face].push_back(cs_node_face_offsetted);
ps_face_to_potentially_intersecting_others[cs_node_face_offsetted].push_back(sm_node_face);
} else if (sm_bvh_node_is_leaf && !cs_bvh_node_is_leaf) {
MCUT_ASSERT(cs_node_face == hmesh_t::null_face());
MCUT_ASSERT(sm_node_face != hmesh_t::null_face());
const int cs_bvh_node_left_child_implicit_idx = (cs_bvh_node_implicit_idx * 2) + 1;
const int cs_bvh_node_right_child_implicit_idx = (cs_bvh_node_implicit_idx * 2) + 2;
const int rightmost_real_node_on_child_level = get_level_rightmost_real_node(cs_bvh_rightmost_real_leaf, cs_bvh_leaf_level_idx, cs_bvh_node_level_idx + 1);
const bool right_child_is_real = cs_bvh_node_right_child_implicit_idx <= rightmost_real_node_on_child_level;
traversalQueue.push({ sm_bvh_node_implicit_idx, cs_bvh_node_left_child_implicit_idx });
if (right_child_is_real) {
traversalQueue.push({ sm_bvh_node_implicit_idx, cs_bvh_node_right_child_implicit_idx });
}
} else if (!sm_bvh_node_is_leaf && cs_bvh_node_is_leaf) {
MCUT_ASSERT(cs_node_face != hmesh_t::null_face());
MCUT_ASSERT(sm_node_face == hmesh_t::null_face());
const int sm_bvh_node_left_child_implicit_idx = (sm_bvh_node_implicit_idx * 2) + 1;
const int sm_bvh_node_right_child_implicit_idx = (sm_bvh_node_implicit_idx * 2) + 2;
const int rightmost_real_node_on_child_level = get_level_rightmost_real_node(sm_bvh_rightmost_real_leaf, sm_bvh_leaf_level_idx, sm_bvh_node_level_idx + 1);
const bool right_child_is_real = sm_bvh_node_right_child_implicit_idx <= rightmost_real_node_on_child_level;
traversalQueue.push({ sm_bvh_node_left_child_implicit_idx, cs_bvh_node_implicit_idx });
if (right_child_is_real) {
traversalQueue.push({ sm_bvh_node_right_child_implicit_idx, cs_bvh_node_implicit_idx });
}
} else { // both nodes are internal
MCUT_ASSERT(cs_node_face == hmesh_t::null_face());
MCUT_ASSERT(sm_node_face == hmesh_t::null_face());
const int sm_bvh_node_left_child_implicit_idx = (sm_bvh_node_implicit_idx * 2) + 1;
const int sm_bvh_node_right_child_implicit_idx = (sm_bvh_node_implicit_idx * 2) + 2;
const int cs_bvh_node_left_child_implicit_idx = (cs_bvh_node_implicit_idx * 2) + 1;
const int cs_bvh_node_right_child_implicit_idx = (cs_bvh_node_implicit_idx * 2) + 2;
const int sm_rightmost_real_node_on_child_level = get_level_rightmost_real_node(sm_bvh_rightmost_real_leaf, sm_bvh_leaf_level_idx, sm_bvh_node_level_idx + 1);
const bool sm_right_child_is_real = sm_bvh_node_right_child_implicit_idx <= sm_rightmost_real_node_on_child_level;
const int cs_rightmost_real_node_on_child_level = get_level_rightmost_real_node(cs_bvh_rightmost_real_leaf, cs_bvh_leaf_level_idx, cs_bvh_node_level_idx + 1);
const bool cs_right_child_is_real = cs_bvh_node_right_child_implicit_idx <= cs_rightmost_real_node_on_child_level;
traversalQueue.push({ sm_bvh_node_left_child_implicit_idx, cs_bvh_node_left_child_implicit_idx });
if (cs_right_child_is_real) {
traversalQueue.push({ sm_bvh_node_left_child_implicit_idx, cs_bvh_node_right_child_implicit_idx });
}
if (sm_right_child_is_real) {
traversalQueue.push({ sm_bvh_node_right_child_implicit_idx, cs_bvh_node_left_child_implicit_idx });
if (cs_right_child_is_real) {
traversalQueue.push({ sm_bvh_node_right_child_implicit_idx, cs_bvh_node_right_child_implicit_idx });
}
}
}
}
traversalQueue.pop(); // rm ct_front_node
} while (!traversalQueue.empty());
TIMESTACK_POP();
}
#else
BoundingVolumeHierarchy::BoundingVolumeHierarchy()
{
}
BoundingVolumeHierarchy::~BoundingVolumeHierarchy() { }
// three stages to BVH construction
void BoundingVolumeHierarchy::buildTree(const hmesh_t& mesh_,
const fixed_precision_number_t& enlargementEps_,
uint32_t mp_,
const SplitMethod& sm_)
{
SCOPED_TIMER(__FUNCTION__);
mesh = &(mesh_); ///
MCUT_ASSERT(mesh->number_of_faces() >= 1);
maxPrimsInNode = (std::min(255u, mp_)); //
splitMethod = (sm_); //
enlargementEps = (enlargementEps_);
buildData.clear();
primitives.clear();
primitiveOrderedBBoxes.clear();
nodes.clear();
// First, bounding information about each primitive is computed and stored in an array
// that will be used during tree construction.
// initialize buildData array for primitives
buildData.reserve(mesh->number_of_faces());
primitiveOrderedBBoxes.resize(mesh->number_of_faces());
primitives.resize(mesh->number_of_faces());
// TODO make this parallel
// for each face in mesh
for (face_array_iterator_t f = mesh->faces_begin(); f != mesh->faces_end(); ++f) {
const int i = static_cast<int>(*f);
primitives[i] = *f;
const std::vector<vd_t> vertices_on_face = mesh->get_vertices_around_face(*f);
bounding_box_t<vec3> bbox;
// for each vertex on face
for (std::vector<vd_t>::const_iterator v = vertices_on_face.cbegin(); v != vertices_on_face.cend(); ++v) {
const vec3 coords = mesh->vertex(*v);
bbox.expand(coords);
}
if (enlargementEps > 0.0) {
bbox.enlarge(enlargementEps);
}
primitiveOrderedBBoxes[i] = bbox;
buildData.push_back(BVHPrimitiveInfo(i, primitiveOrderedBBoxes[i]));
}
// Next, the tree is built via a procedure that splits the primitives into subsets and
// recursively builds BVHs for the subsets. The result is a binary tree where each
// interior node holds pointers to its children and each leaf node holds references to
// one or more primitives.
uint32_t totalNodes = 0;
std::vector<fd_t> orderedPrims;
// orderedPrims.reserve(mesh->number_of_faces());
std::shared_ptr<BVHBuildNode> root = recursiveBuild(
buildData,
0,
/*primitives.size()*/ mesh->number_of_faces(),
&totalNodes,
orderedPrims);
primitives.swap(orderedPrims);
// Finally, this tree is converted to a more compact (and thus more efficient) pointerless
// representation for use during rendering
nodes.resize(totalNodes);
for (uint32_t i = 0; i < totalNodes; ++i) {
// new (&nodes[i]) LinearBVHNode;
nodes[i] = std::make_shared<LinearBVHNode>();
}
uint32_t offset = 0;
flattenBVHTree(root, &offset);
}
const BBox& BoundingVolumeHierarchy::GetPrimitiveBBox(int primitiveIndex) const
{
MCUT_ASSERT(primitiveIndex < mesh->number_of_faces());
return primitiveOrderedBBoxes[primitiveIndex];
}
uint32_t BoundingVolumeHierarchy::flattenBVHTree(std::shared_ptr<BVHBuildNode> node, uint32_t* offset)
{
MCUT_ASSERT(*offset < nodes.size());
std::shared_ptr<LinearBVHNode> linearNode = nodes[*offset];
linearNode->bounds = node->bounds;
uint32_t myOffset = (*offset)++;
if (node->nPrimitives > 0) {
linearNode->primitivesOffset = node->firstPrimOffset;
linearNode->nPrimitives = node->nPrimitives;
} else {
// Creater interior flattened BVH node
linearNode->axis = node->splitAxis;
linearNode->nPrimitives = 0;
flattenBVHTree(node->children[0], offset);
linearNode->secondChildOffset = flattenBVHTree(node->children[1],
offset);
}
return myOffset;
}
std::shared_ptr<BVHBuildNode> BoundingVolumeHierarchy::recursiveBuild(
std::vector<BVHPrimitiveInfo>& buildData,
uint32_t start,
uint32_t end,
uint32_t* totalNodes,
std::vector<fd_t>& orderedPrims)
{
(*totalNodes)++;
std::shared_ptr<BVHBuildNode> node = std::make_shared<BVHBuildNode>();
// Compute bounds of all primitives in BVH node
uint32_t nPrimitives = end - start;
MCUT_ASSERT((nPrimitives - 1) < (uint32_t)mesh->number_of_faces());
BBox bbox;
for (uint32_t i = start; i < end; ++i) {
MCUT_ASSERT(i < buildData.size());
bbox = Union(bbox, buildData[i].bounds);
}
if (nPrimitives == 1) {
// Create leaf BVHBuildNode
uint32_t firstPrimOffset = orderedPrims.size();
for (uint32_t i = start; i < end; ++i) {
MCUT_ASSERT(i < buildData.size());
uint32_t primNum = buildData[i].primitiveNumber;
orderedPrims.push_back(primitives[primNum]);
}
node->InitLeaf(firstPrimOffset, nPrimitives, bbox);
} else {
// Compute bound of primitive centroids, choose split dimension dim
BBox centroidBounds;
for (uint32_t i = start; i < end; ++i) {
MCUT_ASSERT(i < buildData.size());
centroidBounds = Union(centroidBounds, buildData[i].centroid);
}
int dim = centroidBounds.MaximumExtent();
MCUT_ASSERT(dim < 3);
//
// Partition primitives into two sets and build children
//
uint32_t mid = (start + end) / 2;
switch (this->splitMethod) {
case SplitMethod::SPLIT_MIDDLE: {
// Partition primitives through nodes midpoint
fixed_precision_number_t pmid = (centroidBounds.minimum()[dim] + centroidBounds.maximum()[dim]) * .5;
#if 1
MCUT_ASSERT(start < buildData.size());
BVHPrimitiveInfo* midPtr = std::partition(&buildData[start],
&buildData[end - 1] + 1,
/*CompareToMid(dim, pmid)*/
[dim, pmid](const BVHPrimitiveInfo& pi) {
return pi.centroid[dim] < pmid;
});
mid = midPtr - &buildData[0];
#else
std::vector<BVHPrimitiveInfo>::iterator midPtr = std::partition(buildData.begin() + start,
buildData.end(),
CompareToMid(dim, pmid));
mid = std::distance(buildData.begin(), midPtr);
#endif
} break;
case SplitMethod::SPLIT_EQUAL_COUNTS: {
// Partition primitives into equally-sized subsets
mid = (start + end) / 2;
std::nth_element(&buildData[start], &buildData[mid],
&buildData[end - 1] + 1, ComparePoints(dim));
} break;
case SplitMethod::SPLIT_SAH: {
// Partition primitives using approximate SAH
if (nPrimitives <= 4) {
// Partition primitives into equally-sized subsets
mid = (start + end) / 2;
std::nth_element(&buildData[start], &buildData[mid],
&buildData[end - 1] + 1, ComparePoints(dim));
} else {
// Allocate BucketInfo for SAH partition buckets
const int nBuckets = 12;
BucketInfo buckets[nBuckets];
// Initialize BucketInfo for SAH partition buckets
for (uint32_t i = start; i < end; ++i) {
int b = nBuckets * ((buildData[i].centroid[dim] - centroidBounds.minimum()[dim]) / (centroidBounds.maximum()[dim] - centroidBounds.minimum()[dim]));
if (b == nBuckets)
b = nBuckets - 1;
buckets[b].count++;
buckets[b].bounds = Union(buckets[b].bounds, buildData[i].bounds);
}
// Compute costs for splitting after each bucket
fixed_precision_number_t cost[nBuckets - 1];
for (int i = 0; i < nBuckets - 1; ++i) {
BBox b0, b1;
int count0 = 0, count1 = 0;
for (int j = 0; j <= i; ++j) {
b0 = Union(b0, buckets[j].bounds);
count0 += buckets[j].count;
}
for (int j = i + 1; j < nBuckets; ++j) {
b1 = Union(b1, buckets[j].bounds);
count1 += buckets[j].count;
}
cost[i] = .125f + (count0 * b0.SurfaceArea() + count1 * b1.SurfaceArea()) / bbox.SurfaceArea();
}
// Find bucket to split at that minimizes SAH metric
float minCost = cost[0];
uint32_t minCostSplit = 0;
for (int i = 1; i < nBuckets - 1; ++i) {
if (cost[i] < minCost) {
minCost = cost[i];
minCostSplit = i;
}
}
// Either create leaf or split primitives at selected SAH bucket
if (nPrimitives > (uint32_t)maxPrimsInNode || minCost < nPrimitives) {
const BVHPrimitiveInfo* pmid = std::partition(&buildData[start],
&buildData[end - 1] + 1,
CompareToBucket(minCostSplit, nBuckets, dim, centroidBounds));
mid = pmid - &buildData[0];
} else {
// Create leaf BVHBuildNode
uint32_t firstPrimOffset = orderedPrims.size();
for (uint32_t i = start; i < end; ++i) {
uint32_t primNum = buildData[i].primitiveNumber;
orderedPrims.push_back(primitives[primNum]);
}
node->InitLeaf(firstPrimOffset, nPrimitives, bbox);
}
}
} break;
default:
fprintf(stderr, "[MCUT]: error, unknown split method\n");
break;
}
mid = (start + end) / 2;
if (centroidBounds.maximum()[dim] == centroidBounds.minimum()[dim]) {
// Create leaf BVHBuildNode
int32_t firstPrimOffset = orderedPrims.size();
for (uint32_t i = start; i < end; ++i) {
uint32_t primNum = buildData[i].primitiveNumber;
orderedPrims.push_back(primitives[primNum]);
}
node->InitLeaf(firstPrimOffset, nPrimitives, bbox);
return node;
}
std::shared_ptr<BVHBuildNode> leftSubTree = recursiveBuild(buildData, start, mid,
totalNodes, orderedPrims);
std::shared_ptr<BVHBuildNode> rightSubTree = recursiveBuild(buildData, mid, end,
totalNodes, orderedPrims);
// Partition primitives based on splitMethod〉
node->InitInterior(dim,
leftSubTree,
rightSubTree);
}
return node;
}
int BoundingVolumeHierarchy::GetNodeCount() const
{
return (int)nodes.size();
}
const std::shared_ptr<LinearBVHNode>& BoundingVolumeHierarchy::GetNode(int idx) const
{
return nodes[idx];
}
const fd_t& BoundingVolumeHierarchy::GetPrimitive(int index) const
{
MCUT_ASSERT(index < (int)primitives.size());
return primitives[index];
}
void BoundingVolumeHierarchy::intersectBVHTrees(
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
thread_pool& scheduler,
#endif
std::map<fd_t, std::vector<fd_t>>& symmetric_intersecting_pairs,
const BoundingVolumeHierarchy& bvhA,
const BoundingVolumeHierarchy& bvhB,
const uint32_t primitiveOffsetA,
const uint32_t primitiveOffsetB)
{
SCOPED_TIMER(__FUNCTION__);
MCUT_ASSERT(bvhA.GetNodeCount() > 0);
MCUT_ASSERT(bvhB.GetNodeCount() > 0);
auto fn_intersectBVHTrees = [&bvhA, &bvhB, &primitiveOffsetA, &primitiveOffsetB](
std::vector<std::pair<int, int>>& worklist_,
std::map<fd_t, std::vector<fd_t>>& symmetric_intersecting_pairs_,
const uint32_t maxWorklistSize) {
// Simultaneous DFS traversal
while (worklist_.size() > 0 && worklist_.size() < maxWorklistSize) {
// maxTodoSz = std::max(maxTodoSz, (int)worklist_.size());
// std::cout << "worklist_.size()="<<worklist_.size()<<std::endl;
std::pair<int, int> cur = worklist_.back();
// TODO: try to keep an additional counter that allows us to minimize pushing and popping
// Might require a wrapper class over std::vector "lazy vector"
worklist_.pop_back();
const uint32_t nodeAIndex = cur.first;
const uint32_t nodeBIndex = cur.second;
const std::shared_ptr<LinearBVHNode> nodeA = bvhA.GetNode(nodeAIndex);
const std::shared_ptr<LinearBVHNode> nodeB = bvhB.GetNode(nodeBIndex);
if (!intersect_bounding_boxes(nodeA->bounds, nodeB->bounds)) {
continue;
}
bool nodeAIsLeaf = nodeA->nPrimitives > 0;
bool nodeBIsLeaf = nodeB->nPrimitives > 0;
if (nodeAIsLeaf) {
if (nodeBIsLeaf) {
for (int i = 0; i < nodeA->nPrimitives; ++i) {
const fd_t faceA = bvhA.GetPrimitive((uint32_t)(nodeA->primitivesOffset + i));
const fd_t faceAOffsetted(primitiveOffsetA + faceA);
for (int j = 0; j < nodeB->nPrimitives; ++j) {
const fd_t faceB = bvhB.GetPrimitive((uint32_t)(nodeB->primitivesOffset + j));
const fd_t faceBOffsetted(primitiveOffsetB + faceB);
symmetric_intersecting_pairs_[faceAOffsetted].push_back(faceBOffsetted);
symmetric_intersecting_pairs_[faceBOffsetted].push_back(faceAOffsetted);
}
}
} else {
const uint32_t nodeBLeftChild = nodeBIndex + 1;
const uint32_t nodeBRightChild = nodeB->secondChildOffset;
worklist_.emplace_back(nodeAIndex, nodeBLeftChild);
worklist_.emplace_back(nodeAIndex, nodeBRightChild);
}
} else {
if (nodeBIsLeaf) {
const uint32_t nodeALeftChild = nodeAIndex + 1;
const uint32_t nodeARightChild = nodeA->secondChildOffset;
worklist_.emplace_back(nodeALeftChild, nodeBIndex);
worklist_.emplace_back(nodeARightChild, nodeBIndex);
} else {
const uint32_t nodeALeftChild = nodeAIndex + 1;
const uint32_t nodeARightChild = nodeA->secondChildOffset;
const uint32_t nodeBLeftChild = nodeBIndex + 1;
const uint32_t nodeBRightChild = nodeB->secondChildOffset;
worklist_.emplace_back(nodeALeftChild, nodeBLeftChild);
worklist_.emplace_back(nodeALeftChild, nodeBRightChild);
worklist_.emplace_back(nodeARightChild, nodeBLeftChild);
worklist_.emplace_back(nodeARightChild, nodeBRightChild);
}
}
}
};
// start with pair of root nodes
std::vector<std::pair<int, int>> todo(1, std::make_pair(0, 0));
#if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
{
// master thread intersects the BVHs until the number of node pairs
// reaches a threshold (or workload was small enough that traversal
// is finished)
const uint32_t threshold = scheduler.get_num_threads();
fn_intersectBVHTrees(todo, symmetric_intersecting_pairs, threshold);
uint32_t remainingWorkloadCount = (uint32_t)todo.size(); // how much work do we still have left
if (remainingWorkloadCount > 0) { // do parallel traversal by distributing blocks of node-pairs across worker threads
// NOTE: we do not manage load-balancing (too complex for the perf gain)
typedef std::vector<std::pair<int, int>>::const_iterator InputStorageIteratorType;
typedef std::map<fd_t, std::vector<fd_t>> OutputStorageType; // symmetric_intersecting_pairs (local)
auto fn_intersect = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) -> OutputStorageType {
OutputStorageType symmetric_intersecting_pairs_local;
std::vector<std::pair<int, int>> todo_local(block_start_, block_end_);
fn_intersectBVHTrees(
todo_local,
symmetric_intersecting_pairs_local,
// traverse until leaves
std::numeric_limits<uint32_t>::max());
return symmetric_intersecting_pairs_local;
};
std::vector<std::future<OutputStorageType>> futures;
OutputStorageType partial_res;
parallel_for(
scheduler,
todo.cbegin(),
todo.cend(),
(1 << 1),
fn_intersect,
partial_res, // output of master thread
futures);
symmetric_intersecting_pairs.insert(partial_res.cbegin(), partial_res.cend());
for (int i = 0; i < (int)futures.size(); ++i) {
std::future<OutputStorageType>& f = futures[i];
MCUT_ASSERT(f.valid());
OutputStorageType future_res = f.get();
symmetric_intersecting_pairs.insert(future_res.cbegin(), future_res.cend());
}
}
}
#else
fn_intersectBVHTrees(todo, symmetric_intersecting_pairs, std::numeric_limits<uint32_t>::max());
#endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL)
}
#endif