diff --git a/ApplicationLibCode/CMakeLists.txt b/ApplicationLibCode/CMakeLists.txt index f8a671c80b..df7469fa81 100644 --- a/ApplicationLibCode/CMakeLists.txt +++ b/ApplicationLibCode/CMakeLists.txt @@ -307,6 +307,7 @@ endif() set(LINK_LIBRARIES Eigen3::Eigen Clipper2::Clipper2Z + nanoflann::nanoflann ${THIRD_PARTY_LIBRARIES} ${OPENGL_LIBRARIES} ${QT_LIBRARIES} diff --git a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigFaultDistanceCalculator.cpp b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigFaultDistanceCalculator.cpp index 4a8e46f36f..72ac58e379 100644 --- a/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigFaultDistanceCalculator.cpp +++ b/ApplicationLibCode/ReservoirDataModel/ResultCalculators/RigFaultDistanceCalculator.cpp @@ -23,10 +23,34 @@ #include "RigFault.h" #include "RigMainGrid.h" -#include "cvfBoundingBoxTree.h" +#include +#include #include +namespace +{ +//-------------------------------------------------------------------------------------------------- +/// Adaptor exposing the collected fault-face-center points to nanoflann without copying them. +/// cvf::Vec3d stores its three doubles contiguously, so ptr()[dim] is a direct coordinate lookup. +//-------------------------------------------------------------------------------------------------- +struct FaceCenterCloud +{ + const std::vector& points; + + inline size_t kdtree_get_point_count() const { return points.size(); } + inline double kdtree_get_pt( size_t idx, size_t dim ) const { return points[idx].ptr()[dim]; } + + template + bool kdtree_get_bbox( BBOX& ) const + { + return false; + } +}; + +using FaceCenterKdTree = nanoflann::KDTreeSingleIndexAdaptor, FaceCenterCloud, 3>; +} // namespace + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -61,25 +85,12 @@ void RigFaultDistanceCalculator::computeFaultDistances( const RigMainGrid* if ( faultFaceCenters.empty() ) return; - // Create bounding box tree for all face centers - cvf::BoundingBoxTree searchTree; - { - std::vector faceIndicesForBoundingBoxes; - std::vector faceBBs; - - size_t faceCenterIndex = 0; - for ( const auto& faultFaceCenter : faultFaceCenters ) - { - cvf::BoundingBox bb; - bb.add( faultFaceCenter ); - faceBBs.push_back( bb ); - faceIndicesForBoundingBoxes.push_back( faceCenterIndex++ ); - } - searchTree.buildTreeFromBoundingBoxes( faceBBs, &faceIndicesForBoundingBoxes ); - } - - const auto nodes = mainGrid->nodes(); - const auto mainGridBB = mainGrid->boundingBox(); + // Build a KD-tree over the fault face centers and query the nearest one for every active cell. + // This is an exact nearest-point search in O(log N) per cell, replacing the previous expanding + // bounding-box heuristic. + FaceCenterCloud cloud{ faultFaceCenters }; + FaceCenterKdTree kdTree( 3, cloud, nanoflann::KDTreeSingleIndexAdaptorParams( 10 /* max leaf size */ ) ); + kdTree.buildIndex(); #pragma omp parallel for for ( int activeIndex = 0; activeIndex < static_cast( activeCells.size() ); activeIndex++ ) @@ -90,45 +101,15 @@ void RigFaultDistanceCalculator::computeFaultDistances( const RigMainGrid* const RigCell& cell = mainGrid->cell( cellIdx.value() ); if ( cell.isInvalid() ) continue; - std::vector candidateFaceIndices; - { - cvf::BoundingBox bb; - const auto& cellIndices = cell.cornerIndices(); - for ( const auto& i : cellIndices ) - { - bb.add( nodes[i] ); - } + const cvf::Vec3d cellCenter = cell.center(); + const double queryPoint[] = { cellCenter.x(), cellCenter.y(), cellCenter.z() }; - searchTree.findIntersections( bb, &candidateFaceIndices ); + size_t nearestIndex = 0; + double nearestDistanceSquared = std::numeric_limits::infinity(); + nanoflann::KNNResultSet resultSet( 1 ); + resultSet.init( &nearestIndex, &nearestDistanceSquared ); + kdTree.findNeighbors( resultSet, queryPoint ); - bool bbIsBelowThreshold = true; - while ( candidateFaceIndices.empty() && bbIsBelowThreshold ) - { - if ( bb.extent().x() > mainGridBB.extent().x() * 2 ) - { - bbIsBelowThreshold = false; - break; - } - if ( bb.extent().y() > mainGridBB.extent().y() * 2 ) - { - bbIsBelowThreshold = false; - break; - } - - bb.expand( bb.extent().x() ); - searchTree.findIntersections( bb, &candidateFaceIndices ); - } - } - - // Find closest fault face - double shortestDistance = std::numeric_limits::infinity(); - - for ( const auto& faultFaceIndex : candidateFaceIndices ) - { - const cvf::Vec3d& faultFaceCenter = faultFaceCenters[faultFaceIndex]; - shortestDistance = std::min( cell.center().pointDistance( faultFaceCenter ), shortestDistance ); - } - - resultValues[activeIndex] = shortestDistance; + resultValues[activeIndex] = std::sqrt( nearestDistanceSquared ); } } diff --git a/CMakeLists.txt b/CMakeLists.txt index 293848f94d..77de3abd91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -758,6 +758,11 @@ find_package(Clipper2 CONFIG REQUIRED) # THIRD_PARTY_LIBRARIES which is used for setting folder properties on targets # we build +find_package(nanoflann CONFIG REQUIRED) + +# nanoflann::nanoflann is a header-only imported target, so like Clipper2 it is +# not added to THIRD_PARTY_LIBRARIES + # ############################################################################## # roffcpp # ############################################################################## diff --git a/vcpkg.json b/vcpkg.json index 64718c0e46..10fe4046cc 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -13,7 +13,8 @@ "type-lite", "fast-float", "spdlog", - "pugixml" + "pugixml", + "nanoflann" ], "overrides": [ {