/** * 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: * (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 #include #include "mcut/internal/timer.h" #include // see: if it is possible to remove thsi header #include #ifdef _MSC_VER #include #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>& bvhAABBs, std::vector& bvhLeafNodeFaces, std::vector>& 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()); std::vector 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(*f); std::vector vertices_on_face = mesh.get_vertices_around_face(*f); // for each vertex on face for (std::vector::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& 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(*f); const std::vector vertices_on_face = mesh.get_vertices_around_face(*f); // for each vertex on face for (std::vector::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& 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& 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 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 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> bvhLeafNodeDescriptors(meshFaceCount, std::pair()); #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(*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(offset.x() / dims.x()), static_cast(offset.y() / dims.y()), static_cast(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(*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(offset.x() / dims.x()), static_cast(offset.y() / dims.y()), static_cast(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& a, const std::pair& 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>::const_iterator block_start_, std::vector>::const_iterator block_end_) { for (std::vector>::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& 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>::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& 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 level_nodes_placeholder(number_of_real_nodes_on_level); auto fn_compute_bvh_level_nodes = [&](std::vector::const_iterator block_start_, std::vector::const_iterator block_end_) { uint32_t base_offset = std::distance(level_nodes_placeholder.cbegin(), block_start_); uint32_t counter = 0; for (std::vector::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 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& 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& 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& 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& 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 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& 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& 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& 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& 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>& ps_face_to_potentially_intersecting_others, const std::vector>& srcMeshBvhAABBs, const std::vector& srcMeshBvhLeafNodeFaces, const std::vector>& cutMeshBvhAABBs, const std::vector& cutMeshBvhLeafNodeFaces) { TIMESTACK_PUSH(__FUNCTION__); // simultaneuosly traverse both BVHs to find intersecting pairs std::queue 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 sm_bvh_node_bbox; bounding_box_t 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(*f); primitives[i] = *f; const std::vector vertices_on_face = mesh->get_vertices_around_face(*f); bounding_box_t bbox; // for each vertex on face for (std::vector::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 orderedPrims; // orderedPrims.reserve(mesh->number_of_faces()); std::shared_ptr 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(); } 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 node, uint32_t* offset) { MCUT_ASSERT(*offset < nodes.size()); std::shared_ptr 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 BoundingVolumeHierarchy::recursiveBuild( std::vector& buildData, uint32_t start, uint32_t end, uint32_t* totalNodes, std::vector& orderedPrims) { (*totalNodes)++; std::shared_ptr node = std::make_shared(); // 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 node’s 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::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 leftSubTree = recursiveBuild(buildData, start, mid, totalNodes, orderedPrims); std::shared_ptr 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& 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>& 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>& worklist_, std::map>& 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()="< 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 nodeA = bvhA.GetNode(nodeAIndex); const std::shared_ptr 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> 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>::const_iterator InputStorageIteratorType; typedef std::map> OutputStorageType; // symmetric_intersecting_pairs (local) auto fn_intersect = [&](InputStorageIteratorType block_start_, InputStorageIteratorType block_end_) -> OutputStorageType { OutputStorageType symmetric_intersecting_pairs_local; std::vector> todo_local(block_start_, block_end_); fn_intersectBVHTrees( todo_local, symmetric_intersecting_pairs_local, // traverse until leaves std::numeric_limits::max()); return symmetric_intersecting_pairs_local; }; std::vector> 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& 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::max()); #endif // #if defined(MCUT_WITH_COMPUTE_HELPER_THREADPOOL) } #endif