/**************************************************************************** * Copyright (c) 2017-2023 by the ArborX authors * * All rights reserved. * * * * This file is part of the ArborX library. ArborX is * * distributed under a BSD 3-clause license. For the licensing terms see * * the LICENSE file in the top-level directory. * * * * SPDX-License-Identifier: BSD-3-Clause * ****************************************************************************/ #ifndef ARBORX_LINEAR_BVH_HPP #define ARBORX_LINEAR_BVH_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ArborX { namespace Experimental { struct PerThread {}; } // namespace Experimental namespace Details { struct HappyTreeFriends; } // namespace Details template >>, typename GeometryTraits::coordinate_type_t< std::decay_t>>>> class BoundingVolumeHierarchy { public: using memory_space = MemorySpace; static_assert(Kokkos::is_memory_space::value); using size_type = typename MemorySpace::size_type; using bounding_volume_type = BoundingVolume; using value_type = Value; BoundingVolumeHierarchy() = default; // build an empty tree template BoundingVolumeHierarchy( ExecutionSpace const &space, Values const &values, IndexableGetter const &indexable_getter = IndexableGetter(), SpaceFillingCurve const &curve = SpaceFillingCurve()); KOKKOS_FUNCTION size_type size() const noexcept { return _size; } KOKKOS_FUNCTION bool empty() const noexcept { return size() == 0; } KOKKOS_FUNCTION bounding_volume_type bounds() const noexcept { return _bounds; } template void query(ExecutionSpace const &space, Predicates const &predicates, Callback const &callback, Experimental::TraversalPolicy const &policy = Experimental::TraversalPolicy()) const; template std::enable_if_t>> query(ExecutionSpace const &space, UserPredicates const &user_predicates, CallbackOrView &&callback_or_view, View &&view, Args &&...args) const { Kokkos::Profiling::ScopedRegion guard("ArborX::BVH::query_crs"); Details::CrsGraphWrapperImpl:: check_valid_callback_if_first_argument_is_not_a_view( callback_or_view, user_predicates, view); using Predicates = Details::AccessValues; using Tag = typename Predicates::value_type::Tag; // Automatically add LegacyDefaultCallback if // 1. A user does not provide a callback // 2. The index is constructed on PairValueIndex // 3. The output value_type is an integral type constexpr bool use_convenient_shortcut = []() { if constexpr (!Kokkos::is_view_v>) return false; else if constexpr (!Details::is_pair_value_index_v) return false; else return std::is_integral_v< typename std::decay_t::value_type>; }(); if constexpr (use_convenient_shortcut) { // Simplified way to get APIv1 result using APIv2 interface Details::CrsGraphWrapperImpl::queryDispatch( Tag{}, *this, space, Predicates{user_predicates}, Details::LegacyDefaultCallback{}, // inject legacy callback arg std::forward(callback_or_view), std::forward(view), std::forward(args)...); return; } else { Details::CrsGraphWrapperImpl::queryDispatch( Tag{}, *this, space, Predicates{user_predicates}, std::forward(callback_or_view), std::forward(view), std::forward(args)...); } } template KOKKOS_FUNCTION void query(Experimental::PerThread, Predicate const &predicate, Callback const &callback) const { ArborX::Details::TreeTraversal tree_traversal(*this, callback); tree_traversal(predicate); } KOKKOS_FUNCTION auto const &indexable_get() const { return _indexable_getter; } private: friend struct Details::HappyTreeFriends; using indexable_type = std::decay_t>; using leaf_node_type = Details::LeafNode; using internal_node_type = Details::InternalNode; size_type _size{0}; bounding_volume_type _bounds; Kokkos::View _leaf_nodes; Kokkos::View _internal_nodes; IndexableGetter _indexable_getter; }; // The partial template specialization parameters *must* match the default ones template class BoundingVolumeHierarchy> : public BoundingVolumeHierarchy, Details::DefaultIndexableGetter, Box> { using base_type = BoundingVolumeHierarchy, Details::DefaultIndexableGetter, Box>; public: using bounding_volume_type = typename base_type::bounding_volume_type; BoundingVolumeHierarchy() = default; // build an empty tree template BoundingVolumeHierarchy(ExecutionSpace const &space, Primitives const &primitives, SpaceFillingCurve const &curve = SpaceFillingCurve()) : base_type( space, // Validate the primitives before calling the base constructor (Details::check_valid_access_traits(PrimitivesTag{}, primitives), Details::LegacyValues{ primitives}), Details::DefaultIndexableGetter(), curve) {} template void query(ExecutionSpace const &space, Predicates const &predicates, Callback const &callback, Experimental::TraversalPolicy const &policy = Experimental::TraversalPolicy()) const { Details::check_valid_callback(callback, predicates); base_type::query(space, predicates, Details::LegacyCallbackWrapper{callback}, policy); } template std::enable_if_t>> query(ExecutionSpace const &space, Predicates const &predicates, View &&view, Args &&...args) const { base_type::query(space, predicates, Details::LegacyDefaultCallback{}, std::forward(view), std::forward(args)...); } template std::enable_if_t>> query(ExecutionSpace const &space, Predicates const &predicates, Callback &&callback, OutputView &&out, OffsetView &&offset, Args &&...args) const { if constexpr (!Details::is_tagged_post_callback< std::decay_t>::value) { Details::check_valid_callback(callback, predicates, out); base_type::query(space, predicates, Details::LegacyCallbackWrapper>{ std::forward(callback)}, std::forward(out), std::forward(offset), std::forward(args)...); } else { Kokkos::Profiling::ScopedRegion guard("ArborX::BVH::query_crs"); Kokkos::View indices( "ArborX::CrsGraphWrapper::query::indices", 0); base_type::query(space, predicates, Details::LegacyDefaultCallback{}, indices, std::forward(offset), std::forward(args)...); callback(predicates, std::forward(offset), indices, std::forward(out)); } } template KOKKOS_FUNCTION void query(Experimental::PerThread tag, Predicate const &predicate, Callback const &callback) const { base_type::query(tag, predicate, callback); } }; template >>, typename GeometryTraits::coordinate_type_t< std::decay_t>>>> using BVH = BoundingVolumeHierarchy; template template BoundingVolumeHierarchy:: BoundingVolumeHierarchy(ExecutionSpace const &space, UserValues const &user_values, IndexableGetter const &indexable_getter, SpaceFillingCurve const &curve) : _size(AccessTraits::size(user_values)) , _leaf_nodes(Kokkos::view_alloc(space, Kokkos::WithoutInitializing, "ArborX::BVH::leaf_nodes"), _size) , _internal_nodes(Kokkos::view_alloc(space, Kokkos::WithoutInitializing, "ArborX::BVH::internal_nodes"), _size > 1 ? _size - 1 : 0) , _indexable_getter(indexable_getter) { static_assert(Details::KokkosExt::is_accessible_from::value); // FIXME redo with RangeTraits Details::check_valid_access_traits( PrimitivesTag{}, user_values, Details::DoNotCheckGetReturnType()); using Values = Details::AccessValues; Values values{user_values}; // NOLINT static_assert( Details::KokkosExt::is_accessible_from::value, "Values must be accessible from the execution space"); constexpr int DIM = GeometryTraits::dimension_v; Details::check_valid_space_filling_curve(curve); Kokkos::Profiling::ScopedRegion guard("ArborX::BVH::BVH"); if (empty()) { return; } if (size() == 1) { Details::TreeConstruction::initializeSingleLeafTree( space, values, _indexable_getter, _leaf_nodes, _bounds); return; } Details::Indexables indexables{values, indexable_getter}; Kokkos::Profiling::pushRegion( "ArborX::BVH::BVH::calculate_scene_bounding_box"); // determine the bounding box of the scene ExperimentalHyperGeometry::Box< DIM, typename GeometryTraits::coordinate_type_t> bbox{}; Details::TreeConstruction::calculateBoundingBoxOfTheScene(space, indexables, bbox); Kokkos::Profiling::popRegion(); Kokkos::Profiling::pushRegion("ArborX::BVH::BVH::compute_linear_ordering"); // Map indexables from multidimensional domain to one-dimensional interval using LinearOrderingValueType = std::invoke_result_t; Kokkos::View linear_ordering_indices( Kokkos::view_alloc(space, Kokkos::WithoutInitializing, "ArborX::BVH::BVH::linear_ordering"), size()); Details::TreeConstruction::projectOntoSpaceFillingCurve( space, indexables, curve, bbox, linear_ordering_indices); Kokkos::Profiling::popRegion(); Kokkos::Profiling::pushRegion("ArborX::BVH::BVH::sort_linearized_order"); // Compute the ordering of the indexables along the space-filling curve auto permutation_indices = Details::sortObjects(space, linear_ordering_indices); Kokkos::Profiling::popRegion(); Kokkos::Profiling::pushRegion("ArborX::BVH::BVH::generate_hierarchy"); // Generate bounding volume hierarchy Details::TreeConstruction::generateHierarchy( space, values, _indexable_getter, permutation_indices, linear_ordering_indices, _leaf_nodes, _internal_nodes, _bounds); Kokkos::Profiling::popRegion(); } template template void BoundingVolumeHierarchy< MemorySpace, Value, IndexableGetter, BoundingVolume>::query(ExecutionSpace const &space, UserPredicates const &user_predicates, Callback const &callback, Experimental::TraversalPolicy const &policy) const { static_assert(Details::KokkosExt::is_accessible_from::value); Details::check_valid_access_traits(PredicatesTag{}, user_predicates); Details::check_valid_callback(callback, user_predicates); using Predicates = Details::AccessValues; static_assert( Details::KokkosExt::is_accessible_from::value, "Predicates must be accessible from the execution space"); Predicates predicates{user_predicates}; // NOLINT using Tag = typename Predicates::value_type::Tag; std::string profiling_prefix = "ArborX::BVH::query::"; if constexpr (std::is_same_v) { profiling_prefix += "spatial"; } else if constexpr (std::is_same_v) { profiling_prefix += "nearest"; } else if constexpr (std::is_same_v) { profiling_prefix += "ordered_spatial"; } else { static_assert(std::is_void_v, "ArborX implementation bug"); } Kokkos::Profiling::pushRegion(profiling_prefix); if (policy._sort_predicates) { Kokkos::Profiling::pushRegion(profiling_prefix + "::compute_permutation"); using DeviceType = Kokkos::Device; ExperimentalHyperGeometry::Box< GeometryTraits::dimension_v, typename GeometryTraits::coordinate_type_t> scene_bounding_box{}; using namespace Details; expand(scene_bounding_box, bounds()); auto permute = Details::BatchedQueries:: sortPredicatesAlongSpaceFillingCurve(space, Experimental::Morton32(), scene_bounding_box, predicates); Kokkos::Profiling::popRegion(); using PermutedPredicates = Details::PermutedData; Details::traverse(space, *this, PermutedPredicates{predicates, permute}, callback); } else { Details::traverse(space, *this, predicates, callback); } Kokkos::Profiling::popRegion(); } } // namespace ArborX #endif