Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-28 09:20:59

0001 // Copyright (c) 2026 OPEN CASCADE SAS
0002 //
0003 // This file is part of Open CASCADE Technology software library.
0004 //
0005 // This library is free software; you can redistribute it and/or modify it under
0006 // the terms of the GNU Lesser General Public License version 2.1 as published
0007 // by the Free Software Foundation, with special exception defined in the file
0008 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
0009 // distribution for complete text of the license and disclaimer of any warranty.
0010 //
0011 // Alternatively, this file may be used under the terms of Open CASCADE
0012 // commercial license or contractual agreement.
0013 
0014 #ifndef NCollection_KDTree_HeaderFile
0015 #define NCollection_KDTree_HeaderFile
0016 
0017 #include <NCollection_Array1.hxx>
0018 #include <NCollection_DynamicArray.hxx>
0019 
0020 #include <algorithm>
0021 #include <cmath>
0022 #include <cstddef>
0023 #include <limits>
0024 #include <type_traits>
0025 
0026 //! @brief Static KD-Tree for efficient point set queries.
0027 //!
0028 //! NCollection_KDTree is a balanced KD-Tree built once from a set of points,
0029 //! supporting efficient nearest-neighbor, k-nearest, range (sphere), and
0030 //! box (AABB) queries.
0031 //!
0032 //! Key features:
0033 //! - O(N log N) construction via median-split (std::nth_element)
0034 //! - O(log N) nearest-neighbor search with bounding box pruning
0035 //! - O(N^(1-1/d) + k) range and box search
0036 //! - Cache-friendly permutation-based layout (no node allocations)
0037 //! - Optional per-point radii for sphere-aware queries (compile-time, zero overhead when unused)
0038 //! - Works with any point type providing Coord(int) with 1-based indexing
0039 //!
0040 //! Best suited for:
0041 //! - Static point clouds (build once, query many times)
0042 //! - Nearest-neighbor and k-nearest queries
0043 //! - Spatial range filtering
0044 //! - Sphere containment and weighted nearest queries (when HasRadii = true)
0045 //!
0046 //! Required interface for ThePointType (besides copy constructor and operator =):
0047 //! @code
0048 //!   class MyPointType
0049 //!   {
0050 //!   public:
0051 //!     double Coord(int theIndex) const;
0052 //!     // Returns coordinate along theIndex axis (1-based, from 1 to TheDimension)
0053 //!   };
0054 //! @endcode
0055 //!
0056 //! Works out-of-the-box with gp_Pnt, gp_Pnt2d, gp_XYZ, gp_XY.
0057 //!
0058 //! @tparam ThePointType  Point type providing Coord(int) with 1-based indexing
0059 //! @tparam TheDimension  Spatial dimension (compile-time constant, typically 2 or 3)
0060 //! @tparam HasRadii      When true, enables per-point radii for ContainingSearch
0061 //!                       and NearestWeighted queries (default: false)
0062 template <class ThePointType, int TheDimension, bool HasRadii = false>
0063 class NCollection_KDTree
0064 {
0065 public:
0066   //! Empty constructor. Creates an empty tree.
0067   NCollection_KDTree()
0068       : mySize(0)
0069   {
0070   }
0071 
0072   //! Build the tree from a C array of points (no radii).
0073   //! Only available when HasRadii is false.
0074   //! @param[in] thePoints pointer to contiguous array of points
0075   //! @param[in] theCount  number of points
0076   template <bool R = HasRadii, typename = std::enable_if_t<!R>>
0077   void Build(const ThePointType* thePoints, size_t theCount)
0078   {
0079     if (theCount == 0)
0080     {
0081       Clear();
0082       return;
0083     }
0084     mySize = theCount;
0085     myPoints.Resize(1, static_cast<int>(theCount), false);
0086     myIndices.Resize(1, static_cast<int>(theCount), false);
0087     for (size_t i = 0; i < theCount; ++i)
0088     {
0089       myPoints.SetValue(static_cast<int>(i + 1), thePoints[i]);
0090       myIndices.SetValue(static_cast<int>(i + 1), i + 1);
0091     }
0092     buildRecursive(1, static_cast<int>(mySize), 0);
0093   }
0094 
0095   //! Build the tree from an NCollection_Array1 (no radii).
0096   //! Only available when HasRadii is false.
0097   //! @param[in] thePoints array of points (any lower bound)
0098   template <bool R = HasRadii, typename = std::enable_if_t<!R>>
0099   void Build(const NCollection_Array1<ThePointType>& thePoints)
0100   {
0101     const int aCount = thePoints.Length();
0102     if (aCount == 0)
0103     {
0104       Clear();
0105       return;
0106     }
0107     mySize = static_cast<size_t>(aCount);
0108     myPoints.Resize(1, aCount, false);
0109     myIndices.Resize(1, aCount, false);
0110     for (int i = 0; i < aCount; ++i)
0111     {
0112       myPoints.SetValue(i + 1, thePoints.Value(thePoints.Lower() + i));
0113       myIndices.SetValue(i + 1, static_cast<size_t>(i + 1));
0114     }
0115     buildRecursive(1, static_cast<int>(mySize), 0);
0116   }
0117 
0118   //! Build the tree from a C array of points with per-point radii.
0119   //! Only available when HasRadii is true.
0120   //! @param[in] thePoints pointer to contiguous array of points
0121   //! @param[in] theRadii  pointer to contiguous array of radii (one per point)
0122   //! @param[in] theCount  number of points
0123   template <bool R = HasRadii, typename = std::enable_if_t<R>>
0124   void Build(const ThePointType* thePoints, const double* theRadii, size_t theCount)
0125   {
0126     if (theCount == 0)
0127     {
0128       Clear();
0129       return;
0130     }
0131     mySize       = theCount;
0132     const int aN = static_cast<int>(theCount);
0133     myPoints.Resize(1, aN, false);
0134     myIndices.Resize(1, aN, false);
0135     myRadii.Resize(1, aN, false);
0136     myMaxRadius.Resize(1, aN, false);
0137     for (size_t i = 0; i < theCount; ++i)
0138     {
0139       const int anI = static_cast<int>(i + 1);
0140       myPoints.SetValue(anI, thePoints[i]);
0141       myIndices.SetValue(anI, i + 1);
0142       myRadii.SetValue(anI, theRadii[i]);
0143     }
0144     buildRecursive(1, aN, 0);
0145   }
0146 
0147   //! Build the tree from NCollection_Array1 of points with radii.
0148   //! Only available when HasRadii is true.
0149   //! @param[in] thePoints array of points (any lower bound)
0150   //! @param[in] theRadii  array of radii (same length as thePoints)
0151   template <bool R = HasRadii, typename = std::enable_if_t<R>>
0152   void Build(const NCollection_Array1<ThePointType>& thePoints,
0153              const NCollection_Array1<double>&       theRadii)
0154   {
0155     const int aCount = thePoints.Length();
0156     if (aCount == 0)
0157     {
0158       Clear();
0159       return;
0160     }
0161     mySize = static_cast<size_t>(aCount);
0162     myPoints.Resize(1, aCount, false);
0163     myIndices.Resize(1, aCount, false);
0164     myRadii.Resize(1, aCount, false);
0165     myMaxRadius.Resize(1, aCount, false);
0166     for (int i = 0; i < aCount; ++i)
0167     {
0168       myPoints.SetValue(i + 1, thePoints.Value(thePoints.Lower() + i));
0169       myIndices.SetValue(i + 1, static_cast<size_t>(i + 1));
0170       myRadii.SetValue(i + 1, theRadii.Value(theRadii.Lower() + i));
0171     }
0172     buildRecursive(1, static_cast<int>(mySize), 0);
0173   }
0174 
0175   //! Returns true if the tree contains no points.
0176   bool IsEmpty() const { return mySize == 0; }
0177 
0178   //! Returns the number of points in the tree.
0179   size_t Size() const { return mySize; }
0180 
0181   //! Clears the tree.
0182   void Clear()
0183   {
0184     mySize    = 0;
0185     myPoints  = NCollection_Array1<ThePointType>();
0186     myIndices = NCollection_Array1<size_t>();
0187     if constexpr (HasRadii)
0188     {
0189       myRadii     = NCollection_Array1<double>();
0190       myMaxRadius = NCollection_Array1<double>();
0191     }
0192   }
0193 
0194   //! Returns the point at the given 1-based original index.
0195   //! @param[in] theIndex 1-based point index
0196   //! @return const reference to the point
0197   const ThePointType& Point(size_t theIndex) const
0198   {
0199     return myPoints.Value(static_cast<int>(theIndex));
0200   }
0201 
0202   //! Returns the radius of the point at the given 1-based original index.
0203   //! Only available when HasRadii is true.
0204   //! @param[in] theIndex 1-based point index
0205   //! @return radius of the point
0206   template <bool R = HasRadii, typename = std::enable_if_t<R>>
0207   double Radius(size_t theIndex) const
0208   {
0209     return myRadii.Value(static_cast<int>(theIndex));
0210   }
0211 
0212   //! Finds the nearest point to theQuery.
0213   //! @param[in] theQuery query point
0214   //! @return 1-based index of nearest point, 0 if tree is empty
0215   size_t NearestPoint(const ThePointType& theQuery) const
0216   {
0217     double aDummy = 0.0;
0218     return NearestPoint(theQuery, aDummy);
0219   }
0220 
0221   //! Finds the nearest point to theQuery and returns the squared distance.
0222   //! @param[in]  theQuery      query point
0223   //! @param[out] theSqDistance  squared distance to the nearest point
0224   //! @return 1-based index of nearest point, 0 if tree is empty
0225   size_t NearestPoint(const ThePointType& theQuery, double& theSqDistance) const
0226   {
0227     if (IsEmpty())
0228     {
0229       theSqDistance = 0.0;
0230       return 0;
0231     }
0232     size_t aBestIndex  = 0;
0233     double aBestSqDist = std::numeric_limits<double>::max();
0234     double aBoundsMin[TheDimension];
0235     double aBoundsMax[TheDimension];
0236     initBounds(aBoundsMin, aBoundsMax);
0237     nearestRecursive(theQuery,
0238                      1,
0239                      static_cast<int>(mySize),
0240                      0,
0241                      aBestIndex,
0242                      aBestSqDist,
0243                      aBoundsMin,
0244                      aBoundsMax);
0245     theSqDistance = aBestSqDist;
0246     return aBestIndex;
0247   }
0248 
0249   //! Finds all nearest points to theQuery that share the same minimum distance.
0250   //! Useful when multiple points are equidistant from the query.
0251   //! First finds the nearest distance via tree traversal, then collects all points
0252   //! at that distance using a range search with theTolerance for floating-point comparison.
0253   //! @param[in]  theQuery      query point
0254   //! @param[in]  theTolerance  absolute tolerance for distance equality (default 0.0)
0255   //! @param[out] theSqDistance squared distance to the nearest points
0256   //! @return array of 1-based indices of all equidistant nearest points (empty if tree is empty)
0257   NCollection_DynamicArray<size_t> NearestPoints(const ThePointType& theQuery,
0258                                                  double              theTolerance,
0259                                                  double&             theSqDistance) const
0260   {
0261     NCollection_DynamicArray<size_t> aResult;
0262     if (IsEmpty())
0263     {
0264       theSqDistance = 0.0;
0265       return aResult;
0266     }
0267     // Find the nearest distance first
0268     size_t aBestIndex  = 0;
0269     double aBestSqDist = std::numeric_limits<double>::max();
0270     double aBoundsMin[TheDimension];
0271     double aBoundsMax[TheDimension];
0272     initBounds(aBoundsMin, aBoundsMax);
0273     nearestRecursive(theQuery,
0274                      1,
0275                      static_cast<int>(mySize),
0276                      0,
0277                      aBestIndex,
0278                      aBestSqDist,
0279                      aBoundsMin,
0280                      aBoundsMax);
0281     theSqDistance = aBestSqDist;
0282     // Collect all points at this distance (within tolerance)
0283     const double aBestDist       = std::sqrt(aBestSqDist);
0284     const double aSearchRadius   = aBestDist + theTolerance;
0285     const double aSearchRadiusSq = aSearchRadius * aSearchRadius;
0286     const double aMinSqDist      = (aBestDist - theTolerance > 0.0)
0287                                      ? (aBestDist - theTolerance) * (aBestDist - theTolerance)
0288                                      : 0.0;
0289     // Use range search to get candidates, then filter by exact distance tolerance
0290     NCollection_DynamicArray<size_t> aCandidates;
0291     rangeSearchRecursive(theQuery, aSearchRadiusSq, 1, static_cast<int>(mySize), 0, aCandidates);
0292     for (size_t i = 0; i < aCandidates.Size(); ++i)
0293     {
0294       const size_t anIdx   = aCandidates[i];
0295       const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
0296       if (aSqDist >= aMinSqDist && aSqDist <= aSearchRadiusSq)
0297       {
0298         aResult.Append(anIdx);
0299       }
0300     }
0301     return aResult;
0302   }
0303 
0304   //! Finds the K nearest points to theQuery.
0305   //! Results are sorted by distance (closest first).
0306   //! @param[in]  theQuery       query point
0307   //! @param[in]  theK           number of nearest points to find
0308   //! @param[out] theIndices     1-based indices of the found points (resized internally)
0309   //! @param[out] theSqDistances squared distances of the found points (resized internally)
0310   //! @return actual count of points found (may be less than theK)
0311   size_t KNearestPoints(const ThePointType&         theQuery,
0312                         size_t                      theK,
0313                         NCollection_Array1<size_t>& theIndices,
0314                         NCollection_Array1<double>& theSqDistances) const
0315   {
0316     if (IsEmpty() || theK == 0)
0317     {
0318       theIndices     = NCollection_Array1<size_t>();
0319       theSqDistances = NCollection_Array1<double>();
0320       return 0;
0321     }
0322     const size_t anActualK = std::min(theK, mySize);
0323     // Max-heap: pairs of (sqDist, index)
0324     NCollection_Array1<std::pair<double, size_t>> aHeap(1, static_cast<int>(anActualK));
0325     size_t                                        aHeapSize = 0;
0326     double                                        aBoundsMin[TheDimension];
0327     double                                        aBoundsMax[TheDimension];
0328     initBounds(aBoundsMin, aBoundsMax);
0329     kNearestRecursive(theQuery,
0330                       1,
0331                       static_cast<int>(mySize),
0332                       0,
0333                       aHeap,
0334                       aHeapSize,
0335                       anActualK,
0336                       aBoundsMin,
0337                       aBoundsMax);
0338     // Extract from heap sorted closest-first
0339     const int aCount = static_cast<int>(aHeapSize);
0340     theIndices.Resize(1, aCount, false);
0341     theSqDistances.Resize(1, aCount, false);
0342     // Heap is max-heap, so extract in reverse (largest first) then reverse
0343     for (int i = aCount; i >= 1; --i)
0344     {
0345       theIndices.SetValue(i, aHeap.Value(1).second);
0346       theSqDistances.SetValue(i, aHeap.Value(1).first);
0347       // Pop max from heap
0348       aHeap.SetValue(1, aHeap.Value(static_cast<int>(aHeapSize)));
0349       --aHeapSize;
0350       heapSiftDown(aHeap, 1, static_cast<int>(aHeapSize));
0351     }
0352     return static_cast<size_t>(aCount);
0353   }
0354 
0355   //! Finds all points within theRadius of theQuery (sphere search).
0356   //! @param[in] theQuery  query point
0357   //! @param[in] theRadius search radius
0358   //! @return array of 1-based indices of found points
0359   NCollection_DynamicArray<size_t> RangeSearch(const ThePointType& theQuery, double theRadius) const
0360   {
0361     NCollection_DynamicArray<size_t> aResult;
0362     if (IsEmpty() || theRadius < 0.0)
0363     {
0364       return aResult;
0365     }
0366     const double aRadiusSq = theRadius * theRadius;
0367     rangeSearchRecursive(theQuery, aRadiusSq, 1, static_cast<int>(mySize), 0, aResult);
0368     return aResult;
0369   }
0370 
0371   //! Calls theFunctor for each point within theRadius of theQuery.
0372   //! Zero-allocation alternative to RangeSearch - avoids DynamicArray overhead.
0373   //! Indices passed to theFunctor are 1-based (same convention as RangeSearch).
0374   //! @tparam Functor callable with signature void(size_t theIndex)
0375   //! @param[in] theQuery   query point
0376   //! @param[in] theRadius  search radius
0377   //! @param[in] theFunctor callback invoked for each found 1-based index
0378   template <typename Functor>
0379   void ForEachInRange(const ThePointType& theQuery, double theRadius, Functor theFunctor) const
0380   {
0381     if (IsEmpty() || theRadius < 0.0)
0382     {
0383       return;
0384     }
0385     const double aRadiusSq = theRadius * theRadius;
0386     forEachInRangeRecursive(theQuery, aRadiusSq, 1, static_cast<int>(mySize), 0, theFunctor);
0387   }
0388 
0389   //! Finds all points within the axis-aligned bounding box [theMin, theMax].
0390   //! @param[in] theMin minimum corner of the box
0391   //! @param[in] theMax maximum corner of the box
0392   //! @return array of 1-based indices of found points
0393   NCollection_DynamicArray<size_t> BoxSearch(const ThePointType& theMin,
0394                                              const ThePointType& theMax) const
0395   {
0396     NCollection_DynamicArray<size_t> aResult;
0397     if (IsEmpty())
0398     {
0399       return aResult;
0400     }
0401     boxSearchRecursive(theMin, theMax, 1, static_cast<int>(mySize), 0, aResult);
0402     return aResult;
0403   }
0404 
0405   //! Finds all points whose sphere contains theQuery.
0406   //! Point i "contains" theQuery if dist(theQuery, point_i) <= radius_i.
0407   //! Only available when HasRadii is true.
0408   //! @param[in] theQuery query point
0409   //! @return array of 1-based indices of containing points
0410   template <bool R = HasRadii, typename = std::enable_if_t<R>>
0411   NCollection_DynamicArray<size_t> ContainingSearch(const ThePointType& theQuery) const
0412   {
0413     NCollection_DynamicArray<size_t> aResult;
0414     if (IsEmpty())
0415     {
0416       return aResult;
0417     }
0418     double aBoundsMin[TheDimension];
0419     double aBoundsMax[TheDimension];
0420     initBounds(aBoundsMin, aBoundsMax);
0421     containingSearchRecursive(theQuery,
0422                               1,
0423                               static_cast<int>(mySize),
0424                               0,
0425                               aResult,
0426                               aBoundsMin,
0427                               aBoundsMax);
0428     return aResult;
0429   }
0430 
0431   //! Finds the point whose sphere surface is closest to theQuery.
0432   //! Minimizes gap distance = dist(theQuery, point_i) - radius_i.
0433   //! Negative gap means theQuery is inside the sphere.
0434   //! Only available when HasRadii is true.
0435   //! @param[in] theQuery query point
0436   //! @return 1-based index of nearest-weighted point, 0 if tree is empty
0437   template <bool R = HasRadii, typename = std::enable_if_t<R>>
0438   size_t NearestWeighted(const ThePointType& theQuery) const
0439   {
0440     double aDummy = 0.0;
0441     return NearestWeighted(theQuery, aDummy);
0442   }
0443 
0444   //! Finds the point whose sphere surface is closest to theQuery.
0445   //! Minimizes gap distance = dist(theQuery, point_i) - radius_i.
0446   //! Negative gap means theQuery is inside the sphere.
0447   //! Only available when HasRadii is true.
0448   //! @param[in]  theQuery       query point
0449   //! @param[out] theGapDistance  gap distance to the nearest sphere surface (negative = inside)
0450   //! @return 1-based index of nearest-weighted point, 0 if tree is empty
0451   template <bool R = HasRadii, typename = std::enable_if_t<R>>
0452   size_t NearestWeighted(const ThePointType& theQuery, double& theGapDistance) const
0453   {
0454     if (IsEmpty())
0455     {
0456       theGapDistance = 0.0;
0457       return 0;
0458     }
0459     size_t aBestIndex = 0;
0460     double aBestGap   = std::numeric_limits<double>::max();
0461     double aBoundsMin[TheDimension];
0462     double aBoundsMax[TheDimension];
0463     initBounds(aBoundsMin, aBoundsMax);
0464     nearestWeightedRecursive(theQuery,
0465                              1,
0466                              static_cast<int>(mySize),
0467                              0,
0468                              aBestIndex,
0469                              aBestGap,
0470                              aBoundsMin,
0471                              aBoundsMax);
0472     theGapDistance = aBestGap;
0473     return aBestIndex;
0474   }
0475 
0476   //! Copy constructor.
0477   NCollection_KDTree(const NCollection_KDTree& theOther) = default;
0478 
0479   //! Move constructor.
0480   NCollection_KDTree(NCollection_KDTree&& theOther) noexcept
0481       : myPoints(std::move(theOther.myPoints)),
0482         myIndices(std::move(theOther.myIndices)),
0483         myRadii(std::move(theOther.myRadii)),
0484         myMaxRadius(std::move(theOther.myMaxRadius)),
0485         mySize(theOther.mySize)
0486   {
0487     theOther.mySize = 0;
0488   }
0489 
0490   //! Copy assignment.
0491   NCollection_KDTree& operator=(const NCollection_KDTree& theOther)
0492   {
0493     if (this != &theOther)
0494     {
0495       myPoints    = theOther.myPoints;
0496       myIndices   = theOther.myIndices;
0497       myRadii     = theOther.myRadii;
0498       myMaxRadius = theOther.myMaxRadius;
0499       mySize      = theOther.mySize;
0500     }
0501     return *this;
0502   }
0503 
0504   //! Move assignment.
0505   NCollection_KDTree& operator=(NCollection_KDTree&& theOther) noexcept
0506   {
0507     if (this != &theOther)
0508     {
0509       myPoints        = std::move(theOther.myPoints);
0510       myIndices       = std::move(theOther.myIndices);
0511       myRadii         = std::move(theOther.myRadii);
0512       myMaxRadius     = std::move(theOther.myMaxRadius);
0513       mySize          = theOther.mySize;
0514       theOther.mySize = 0;
0515     }
0516     return *this;
0517   }
0518 
0519 private:
0520   //! Squared distance between two points.
0521   static double squareDistance(const ThePointType& theP1, const ThePointType& theP2)
0522   {
0523     double aSqDist = 0.0;
0524     for (int i = 1; i <= TheDimension; ++i)
0525     {
0526       const double aDiff = theP1.Coord(i) - theP2.Coord(i);
0527       aSqDist += aDiff * aDiff;
0528     }
0529     return aSqDist;
0530   }
0531 
0532   //! Checks whether a point is inside a box [theMin, theMax].
0533   static bool isInsideBox(const ThePointType& thePoint,
0534                           const ThePointType& theMin,
0535                           const ThePointType& theMax)
0536   {
0537     for (int i = 1; i <= TheDimension; ++i)
0538     {
0539       const double aCoord = thePoint.Coord(i);
0540       if (aCoord < theMin.Coord(i) || aCoord > theMax.Coord(i))
0541       {
0542         return false;
0543       }
0544     }
0545     return true;
0546   }
0547 
0548   //! Leaf bucket size: subtrees with this many points or fewer
0549   //! are scanned linearly instead of recursing further.
0550   static constexpr int THE_LEAF_SIZE = 32;
0551 
0552   //! Minimum squared distance from a point to an axis-aligned bounding box.
0553   static double sqDistToBox(const ThePointType& theQuery,
0554                             const double        theBoundsMin[],
0555                             const double        theBoundsMax[])
0556   {
0557     double aSqDist = 0.0;
0558     for (int i = 0; i < TheDimension; ++i)
0559     {
0560       const double aCoord = theQuery.Coord(i + 1);
0561       if (aCoord < theBoundsMin[i])
0562       {
0563         const double aDiff = theBoundsMin[i] - aCoord;
0564         aSqDist += aDiff * aDiff;
0565       }
0566       else if (aCoord > theBoundsMax[i])
0567       {
0568         const double aDiff = aCoord - theBoundsMax[i];
0569         aSqDist += aDiff * aDiff;
0570       }
0571     }
0572     return aSqDist;
0573   }
0574 
0575   //! Initializes bounding box arrays to [-max, +max] in all dimensions.
0576   static void initBounds(double theBoundsMin[], double theBoundsMax[])
0577   {
0578     for (int i = 0; i < TheDimension; ++i)
0579     {
0580       theBoundsMin[i] = -std::numeric_limits<double>::max();
0581       theBoundsMax[i] = std::numeric_limits<double>::max();
0582     }
0583   }
0584 
0585   //! Sift-down for a max-heap on the range [1..theSize].
0586   static void heapSiftDown(NCollection_Array1<std::pair<double, size_t>>& theHeap,
0587                            int                                            theIdx,
0588                            int                                            theSize)
0589   {
0590     while (true)
0591     {
0592       int       aLargest = theIdx;
0593       const int aLeft    = 2 * theIdx;
0594       const int aRight   = 2 * theIdx + 1;
0595       if (aLeft <= theSize && theHeap.Value(aLeft).first > theHeap.Value(aLargest).first)
0596       {
0597         aLargest = aLeft;
0598       }
0599       if (aRight <= theSize && theHeap.Value(aRight).first > theHeap.Value(aLargest).first)
0600       {
0601         aLargest = aRight;
0602       }
0603       if (aLargest == theIdx)
0604       {
0605         break;
0606       }
0607       std::swap(theHeap.ChangeValue(theIdx), theHeap.ChangeValue(aLargest));
0608       theIdx = aLargest;
0609     }
0610   }
0611 
0612   //! Sift-up for a max-heap on the range [1..theSize].
0613   static void heapSiftUp(NCollection_Array1<std::pair<double, size_t>>& theHeap, int theIdx)
0614   {
0615     while (theIdx > 1)
0616     {
0617       const int aParent = theIdx / 2;
0618       if (theHeap.Value(theIdx).first <= theHeap.Value(aParent).first)
0619       {
0620         break;
0621       }
0622       std::swap(theHeap.ChangeValue(theIdx), theHeap.ChangeValue(aParent));
0623       theIdx = aParent;
0624     }
0625   }
0626 
0627   //! Recursive build: partitions myIndices[theLo..theHi] into a balanced KD-Tree.
0628   //! @return maximum radius in the subtree (0.0 when HasRadii is false)
0629   double buildRecursive(int theLo, int theHi, int theDepth)
0630   {
0631     if (theLo > theHi)
0632     {
0633       return 0.0;
0634     }
0635     if (theLo == theHi)
0636     {
0637       if constexpr (HasRadii)
0638       {
0639         const double aR = myRadii.Value(static_cast<int>(myIndices.Value(theLo)));
0640         myMaxRadius.SetValue(theLo, aR);
0641         return aR;
0642       }
0643       return 0.0;
0644     }
0645     const int theAxis = theDepth % TheDimension;
0646     const int theMid  = (theLo + theHi) / 2;
0647     // Use std::nth_element on the index range to place the median
0648     std::nth_element(&myIndices.ChangeValue(theLo),
0649                      &myIndices.ChangeValue(theMid),
0650                      &myIndices.ChangeValue(theHi) + 1,
0651                      [this, theAxis](size_t a, size_t b) {
0652                        return myPoints.Value(static_cast<int>(a)).Coord(theAxis + 1)
0653                               < myPoints.Value(static_cast<int>(b)).Coord(theAxis + 1);
0654                      });
0655     if constexpr (HasRadii)
0656     {
0657       const double aLeftMax  = buildRecursive(theLo, theMid - 1, theDepth + 1);
0658       const double aRightMax = buildRecursive(theMid + 1, theHi, theDepth + 1);
0659       const double aNodeR    = myRadii.Value(static_cast<int>(myIndices.Value(theMid)));
0660       const double aMaxR     = std::max({aNodeR, aLeftMax, aRightMax});
0661       myMaxRadius.SetValue(theMid, aMaxR);
0662       return aMaxR;
0663     }
0664     else
0665     {
0666       buildRecursive(theLo, theMid - 1, theDepth + 1);
0667       buildRecursive(theMid + 1, theHi, theDepth + 1);
0668       return 0.0;
0669     }
0670   }
0671 
0672   //! Recursive nearest-neighbor search with bounding box pruning and leaf buckets.
0673   void nearestRecursive(const ThePointType& theQuery,
0674                         int                 theLo,
0675                         int                 theHi,
0676                         int                 theDepth,
0677                         size_t&             theBestIndex,
0678                         double&             theBestSqDist,
0679                         double              theBoundsMin[],
0680                         double              theBoundsMax[]) const
0681   {
0682     if (theLo > theHi)
0683     {
0684       return;
0685     }
0686     // Bounding box pruning: skip subtree if it cannot contain a closer point
0687     if (sqDistToBox(theQuery, theBoundsMin, theBoundsMax) >= theBestSqDist)
0688     {
0689       return;
0690     }
0691     // Leaf bucket: linear scan for small subtrees
0692     if (theHi - theLo + 1 <= THE_LEAF_SIZE)
0693     {
0694       for (int i = theLo; i <= theHi; ++i)
0695       {
0696         const size_t anIdx   = myIndices.Value(i);
0697         const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
0698         if (aSqDist < theBestSqDist)
0699         {
0700           theBestSqDist = aSqDist;
0701           theBestIndex  = anIdx;
0702         }
0703       }
0704       return;
0705     }
0706     const int           theMid     = (theLo + theHi) / 2;
0707     const size_t        aNodeIndex = myIndices.Value(theMid);
0708     const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
0709     const double        aSqDist    = squareDistance(theQuery, aNodePoint);
0710     if (aSqDist < theBestSqDist)
0711     {
0712       theBestSqDist = aSqDist;
0713       theBestIndex  = aNodeIndex;
0714     }
0715     const int    theAxis   = theDepth % TheDimension;
0716     const double aSplitVal = aNodePoint.Coord(theAxis + 1);
0717     const double aDiff     = theQuery.Coord(theAxis + 1) - aSplitVal;
0718     // Visit closer subtree first, then farther subtree.
0719     // The bounding box check at the top of recursion handles pruning.
0720     if (aDiff <= 0.0)
0721     {
0722       const double aSavedMax = theBoundsMax[theAxis];
0723       theBoundsMax[theAxis]  = aSplitVal;
0724       nearestRecursive(theQuery,
0725                        theLo,
0726                        theMid - 1,
0727                        theDepth + 1,
0728                        theBestIndex,
0729                        theBestSqDist,
0730                        theBoundsMin,
0731                        theBoundsMax);
0732       theBoundsMax[theAxis]  = aSavedMax;
0733       const double aSavedMin = theBoundsMin[theAxis];
0734       theBoundsMin[theAxis]  = aSplitVal;
0735       nearestRecursive(theQuery,
0736                        theMid + 1,
0737                        theHi,
0738                        theDepth + 1,
0739                        theBestIndex,
0740                        theBestSqDist,
0741                        theBoundsMin,
0742                        theBoundsMax);
0743       theBoundsMin[theAxis] = aSavedMin;
0744     }
0745     else
0746     {
0747       const double aSavedMin = theBoundsMin[theAxis];
0748       theBoundsMin[theAxis]  = aSplitVal;
0749       nearestRecursive(theQuery,
0750                        theMid + 1,
0751                        theHi,
0752                        theDepth + 1,
0753                        theBestIndex,
0754                        theBestSqDist,
0755                        theBoundsMin,
0756                        theBoundsMax);
0757       theBoundsMin[theAxis]  = aSavedMin;
0758       const double aSavedMax = theBoundsMax[theAxis];
0759       theBoundsMax[theAxis]  = aSplitVal;
0760       nearestRecursive(theQuery,
0761                        theLo,
0762                        theMid - 1,
0763                        theDepth + 1,
0764                        theBestIndex,
0765                        theBestSqDist,
0766                        theBoundsMin,
0767                        theBoundsMax);
0768       theBoundsMax[theAxis] = aSavedMax;
0769     }
0770   }
0771 
0772   //! Recursive k-nearest search with bounding box pruning, max-heap, and leaf buckets.
0773   void kNearestRecursive(const ThePointType&                            theQuery,
0774                          int                                            theLo,
0775                          int                                            theHi,
0776                          int                                            theDepth,
0777                          NCollection_Array1<std::pair<double, size_t>>& theHeap,
0778                          size_t&                                        theHeapSize,
0779                          size_t                                         theK,
0780                          double                                         theBoundsMin[],
0781                          double                                         theBoundsMax[]) const
0782   {
0783     if (theLo > theHi)
0784     {
0785       return;
0786     }
0787     // Bounding box pruning: skip subtree if it cannot improve the K-th best
0788     if (theHeapSize == theK
0789         && sqDistToBox(theQuery, theBoundsMin, theBoundsMax) >= theHeap.Value(1).first)
0790     {
0791       return;
0792     }
0793     // Leaf bucket: linear scan for small subtrees
0794     if (theHi - theLo + 1 <= THE_LEAF_SIZE)
0795     {
0796       for (int i = theLo; i <= theHi; ++i)
0797       {
0798         const size_t anIdx   = myIndices.Value(i);
0799         const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
0800         if (theHeapSize < theK)
0801         {
0802           ++theHeapSize;
0803           theHeap.SetValue(static_cast<int>(theHeapSize), {aSqDist, anIdx});
0804           heapSiftUp(theHeap, static_cast<int>(theHeapSize));
0805         }
0806         else if (aSqDist < theHeap.Value(1).first)
0807         {
0808           theHeap.SetValue(1, {aSqDist, anIdx});
0809           heapSiftDown(theHeap, 1, static_cast<int>(theHeapSize));
0810         }
0811       }
0812       return;
0813     }
0814     const int           theMid     = (theLo + theHi) / 2;
0815     const size_t        aNodeIndex = myIndices.Value(theMid);
0816     const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
0817     const double        aSqDist    = squareDistance(theQuery, aNodePoint);
0818     if (theHeapSize < theK)
0819     {
0820       ++theHeapSize;
0821       theHeap.SetValue(static_cast<int>(theHeapSize), {aSqDist, aNodeIndex});
0822       heapSiftUp(theHeap, static_cast<int>(theHeapSize));
0823     }
0824     else if (aSqDist < theHeap.Value(1).first)
0825     {
0826       theHeap.SetValue(1, {aSqDist, aNodeIndex});
0827       heapSiftDown(theHeap, 1, static_cast<int>(theHeapSize));
0828     }
0829     const int    theAxis   = theDepth % TheDimension;
0830     const double aSplitVal = aNodePoint.Coord(theAxis + 1);
0831     const double aDiff     = theQuery.Coord(theAxis + 1) - aSplitVal;
0832     // Visit closer subtree first, bounding box check at the top handles pruning
0833     if (aDiff <= 0.0)
0834     {
0835       const double aSavedMax = theBoundsMax[theAxis];
0836       theBoundsMax[theAxis]  = aSplitVal;
0837       kNearestRecursive(theQuery,
0838                         theLo,
0839                         theMid - 1,
0840                         theDepth + 1,
0841                         theHeap,
0842                         theHeapSize,
0843                         theK,
0844                         theBoundsMin,
0845                         theBoundsMax);
0846       theBoundsMax[theAxis]  = aSavedMax;
0847       const double aSavedMin = theBoundsMin[theAxis];
0848       theBoundsMin[theAxis]  = aSplitVal;
0849       kNearestRecursive(theQuery,
0850                         theMid + 1,
0851                         theHi,
0852                         theDepth + 1,
0853                         theHeap,
0854                         theHeapSize,
0855                         theK,
0856                         theBoundsMin,
0857                         theBoundsMax);
0858       theBoundsMin[theAxis] = aSavedMin;
0859     }
0860     else
0861     {
0862       const double aSavedMin = theBoundsMin[theAxis];
0863       theBoundsMin[theAxis]  = aSplitVal;
0864       kNearestRecursive(theQuery,
0865                         theMid + 1,
0866                         theHi,
0867                         theDepth + 1,
0868                         theHeap,
0869                         theHeapSize,
0870                         theK,
0871                         theBoundsMin,
0872                         theBoundsMax);
0873       theBoundsMin[theAxis]  = aSavedMin;
0874       const double aSavedMax = theBoundsMax[theAxis];
0875       theBoundsMax[theAxis]  = aSplitVal;
0876       kNearestRecursive(theQuery,
0877                         theLo,
0878                         theMid - 1,
0879                         theDepth + 1,
0880                         theHeap,
0881                         theHeapSize,
0882                         theK,
0883                         theBoundsMin,
0884                         theBoundsMax);
0885       theBoundsMax[theAxis] = aSavedMax;
0886     }
0887   }
0888 
0889   //! Recursive range search (sphere) with leaf buckets.
0890   void rangeSearchRecursive(const ThePointType&               theQuery,
0891                             double                            theRadiusSq,
0892                             int                               theLo,
0893                             int                               theHi,
0894                             int                               theDepth,
0895                             NCollection_DynamicArray<size_t>& theIndices) const
0896   {
0897     if (theLo > theHi)
0898     {
0899       return;
0900     }
0901     // Leaf bucket: linear scan for small subtrees
0902     if (theHi - theLo + 1 <= THE_LEAF_SIZE)
0903     {
0904       for (int i = theLo; i <= theHi; ++i)
0905       {
0906         const size_t anIdx   = myIndices.Value(i);
0907         const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
0908         if (aSqDist <= theRadiusSq)
0909         {
0910           theIndices.Append(anIdx);
0911         }
0912       }
0913       return;
0914     }
0915     const int           theMid     = (theLo + theHi) / 2;
0916     const size_t        aNodeIndex = myIndices.Value(theMid);
0917     const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
0918     const double        aSqDist    = squareDistance(theQuery, aNodePoint);
0919     if (aSqDist <= theRadiusSq)
0920     {
0921       theIndices.Append(aNodeIndex);
0922     }
0923     if (theLo == theHi)
0924     {
0925       return;
0926     }
0927     const int    theAxis = theDepth % TheDimension;
0928     const double aDiff   = theQuery.Coord(theAxis + 1) - aNodePoint.Coord(theAxis + 1);
0929     // Always visit the near subtree
0930     if (aDiff <= 0.0)
0931     {
0932       rangeSearchRecursive(theQuery, theRadiusSq, theLo, theMid - 1, theDepth + 1, theIndices);
0933       if (aDiff * aDiff <= theRadiusSq)
0934       {
0935         rangeSearchRecursive(theQuery, theRadiusSq, theMid + 1, theHi, theDepth + 1, theIndices);
0936       }
0937     }
0938     else
0939     {
0940       rangeSearchRecursive(theQuery, theRadiusSq, theMid + 1, theHi, theDepth + 1, theIndices);
0941       if (aDiff * aDiff <= theRadiusSq)
0942       {
0943         rangeSearchRecursive(theQuery, theRadiusSq, theLo, theMid - 1, theDepth + 1, theIndices);
0944       }
0945     }
0946   }
0947 
0948   //! Recursive range search (sphere) with callback - zero-allocation variant.
0949   template <typename Functor>
0950   void forEachInRangeRecursive(const ThePointType& theQuery,
0951                                double              theRadiusSq,
0952                                int                 theLo,
0953                                int                 theHi,
0954                                int                 theDepth,
0955                                Functor&            theFunctor) const
0956   {
0957     if (theLo > theHi)
0958     {
0959       return;
0960     }
0961     if (theHi - theLo + 1 <= THE_LEAF_SIZE)
0962     {
0963       for (int i = theLo; i <= theHi; ++i)
0964       {
0965         const size_t anIdx   = myIndices.Value(i);
0966         const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
0967         if (aSqDist <= theRadiusSq)
0968         {
0969           theFunctor(anIdx);
0970         }
0971       }
0972       return;
0973     }
0974     const int           aMid       = (theLo + theHi) / 2;
0975     const size_t        aNodeIndex = myIndices.Value(aMid);
0976     const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
0977     const double        aSqDist    = squareDistance(theQuery, aNodePoint);
0978     if (aSqDist <= theRadiusSq)
0979     {
0980       theFunctor(aNodeIndex);
0981     }
0982     if (theLo == theHi)
0983     {
0984       return;
0985     }
0986     const int    aAxis = theDepth % TheDimension;
0987     const double aDiff = theQuery.Coord(aAxis + 1) - aNodePoint.Coord(aAxis + 1);
0988     if (aDiff <= 0.0)
0989     {
0990       forEachInRangeRecursive(theQuery, theRadiusSq, theLo, aMid - 1, theDepth + 1, theFunctor);
0991       if (aDiff * aDiff <= theRadiusSq)
0992       {
0993         forEachInRangeRecursive(theQuery, theRadiusSq, aMid + 1, theHi, theDepth + 1, theFunctor);
0994       }
0995     }
0996     else
0997     {
0998       forEachInRangeRecursive(theQuery, theRadiusSq, aMid + 1, theHi, theDepth + 1, theFunctor);
0999       if (aDiff * aDiff <= theRadiusSq)
1000       {
1001         forEachInRangeRecursive(theQuery, theRadiusSq, theLo, aMid - 1, theDepth + 1, theFunctor);
1002       }
1003     }
1004   }
1005 
1006   //! Recursive box search (AABB) with leaf buckets.
1007   void boxSearchRecursive(const ThePointType&               theMin,
1008                           const ThePointType&               theMax,
1009                           int                               theLo,
1010                           int                               theHi,
1011                           int                               theDepth,
1012                           NCollection_DynamicArray<size_t>& theIndices) const
1013   {
1014     if (theLo > theHi)
1015     {
1016       return;
1017     }
1018     // Leaf bucket: linear scan for small subtrees
1019     if (theHi - theLo + 1 <= THE_LEAF_SIZE)
1020     {
1021       for (int i = theLo; i <= theHi; ++i)
1022       {
1023         const size_t anIdx = myIndices.Value(i);
1024         if (isInsideBox(myPoints.Value(static_cast<int>(anIdx)), theMin, theMax))
1025         {
1026           theIndices.Append(anIdx);
1027         }
1028       }
1029       return;
1030     }
1031     const int           theMid     = (theLo + theHi) / 2;
1032     const size_t        aNodeIndex = myIndices.Value(theMid);
1033     const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
1034     if (isInsideBox(aNodePoint, theMin, theMax))
1035     {
1036       theIndices.Append(aNodeIndex);
1037     }
1038     if (theLo == theHi)
1039     {
1040       return;
1041     }
1042     const int    theAxis = theDepth % TheDimension;
1043     const double aCoord  = aNodePoint.Coord(theAxis + 1);
1044     if (aCoord >= theMin.Coord(theAxis + 1))
1045     {
1046       boxSearchRecursive(theMin, theMax, theLo, theMid - 1, theDepth + 1, theIndices);
1047     }
1048     if (aCoord <= theMax.Coord(theAxis + 1))
1049     {
1050       boxSearchRecursive(theMin, theMax, theMid + 1, theHi, theDepth + 1, theIndices);
1051     }
1052   }
1053 
1054   //! Recursive containing search: finds all spheres containing theQuery.
1055   //! Uses maxRadius pruning to skip subtrees where no sphere can reach the query.
1056   void containingSearchRecursive(const ThePointType&               theQuery,
1057                                  int                               theLo,
1058                                  int                               theHi,
1059                                  int                               theDepth,
1060                                  NCollection_DynamicArray<size_t>& theIndices,
1061                                  double                            theBoundsMin[],
1062                                  double                            theBoundsMax[]) const
1063   {
1064     if (theLo > theHi)
1065     {
1066       return;
1067     }
1068     // Prune: if min distance to subtree box > max radius in subtree, no sphere contains query
1069     const int    theMid = (theLo + theHi) / 2;
1070     const double aMaxR  = myMaxRadius.Value(theMid);
1071     if (sqDistToBox(theQuery, theBoundsMin, theBoundsMax) > aMaxR * aMaxR)
1072     {
1073       return;
1074     }
1075     // Leaf bucket: linear scan for small subtrees
1076     if (theHi - theLo + 1 <= THE_LEAF_SIZE)
1077     {
1078       for (int i = theLo; i <= theHi; ++i)
1079       {
1080         const size_t anIdx   = myIndices.Value(i);
1081         const double aR      = myRadii.Value(static_cast<int>(anIdx));
1082         const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
1083         if (aSqDist <= aR * aR)
1084         {
1085           theIndices.Append(anIdx);
1086         }
1087       }
1088       return;
1089     }
1090     // Check current node
1091     const size_t        aNodeIndex = myIndices.Value(theMid);
1092     const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
1093     const double        aNodeR     = myRadii.Value(static_cast<int>(aNodeIndex));
1094     if (squareDistance(theQuery, aNodePoint) <= aNodeR * aNodeR)
1095     {
1096       theIndices.Append(aNodeIndex);
1097     }
1098     // Recurse both subtrees with bounds tracking
1099     const int    theAxis   = theDepth % TheDimension;
1100     const double aSplitVal = aNodePoint.Coord(theAxis + 1);
1101     const double aSavedMax = theBoundsMax[theAxis];
1102     theBoundsMax[theAxis]  = aSplitVal;
1103     containingSearchRecursive(theQuery,
1104                               theLo,
1105                               theMid - 1,
1106                               theDepth + 1,
1107                               theIndices,
1108                               theBoundsMin,
1109                               theBoundsMax);
1110     theBoundsMax[theAxis]  = aSavedMax;
1111     const double aSavedMin = theBoundsMin[theAxis];
1112     theBoundsMin[theAxis]  = aSplitVal;
1113     containingSearchRecursive(theQuery,
1114                               theMid + 1,
1115                               theHi,
1116                               theDepth + 1,
1117                               theIndices,
1118                               theBoundsMin,
1119                               theBoundsMax);
1120     theBoundsMin[theAxis] = aSavedMin;
1121   }
1122 
1123   //! Recursive nearest-weighted search: finds point minimizing dist - radius.
1124   //! Uses bounding box + maxRadius pruning to skip subtrees.
1125   void nearestWeightedRecursive(const ThePointType& theQuery,
1126                                 int                 theLo,
1127                                 int                 theHi,
1128                                 int                 theDepth,
1129                                 size_t&             theBestIndex,
1130                                 double&             theBestGap,
1131                                 double              theBoundsMin[],
1132                                 double              theBoundsMax[]) const
1133   {
1134     if (theLo > theHi)
1135     {
1136       return;
1137     }
1138     // Prune: minimum possible gap = sqrt(sqDistToBox) - maxRadius.
1139     // To avoid sqrt: if (bestGap + maxRadius) >= 0, then sqDistToBox >= (bestGap + maxRadius)^2.
1140     const int    theMid          = (theLo + theHi) / 2;
1141     const double aMaxR           = myMaxRadius.Value(theMid);
1142     const double aSqDistToBox    = sqDistToBox(theQuery, theBoundsMin, theBoundsMax);
1143     const double aPruneThreshold = theBestGap + aMaxR;
1144     if (aPruneThreshold >= 0.0 && aSqDistToBox >= aPruneThreshold * aPruneThreshold)
1145     {
1146       return;
1147     }
1148     // Leaf bucket: linear scan for small subtrees
1149     if (theHi - theLo + 1 <= THE_LEAF_SIZE)
1150     {
1151       for (int i = theLo; i <= theHi; ++i)
1152       {
1153         const size_t anIdx = myIndices.Value(i);
1154         const double aDist =
1155           std::sqrt(squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx))));
1156         const double aGap = aDist - myRadii.Value(static_cast<int>(anIdx));
1157         if (aGap < theBestGap)
1158         {
1159           theBestGap   = aGap;
1160           theBestIndex = anIdx;
1161         }
1162       }
1163       return;
1164     }
1165     // Check current node
1166     const size_t        aNodeIndex = myIndices.Value(theMid);
1167     const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
1168     const double        aDist      = std::sqrt(squareDistance(theQuery, aNodePoint));
1169     const double        aGap       = aDist - myRadii.Value(static_cast<int>(aNodeIndex));
1170     if (aGap < theBestGap)
1171     {
1172       theBestGap   = aGap;
1173       theBestIndex = aNodeIndex;
1174     }
1175     // Recurse with bounds tracking, visit closer subtree first
1176     const int    theAxis   = theDepth % TheDimension;
1177     const double aSplitVal = aNodePoint.Coord(theAxis + 1);
1178     const double aDiff     = theQuery.Coord(theAxis + 1) - aSplitVal;
1179     if (aDiff <= 0.0)
1180     {
1181       const double aSavedMax = theBoundsMax[theAxis];
1182       theBoundsMax[theAxis]  = aSplitVal;
1183       nearestWeightedRecursive(theQuery,
1184                                theLo,
1185                                theMid - 1,
1186                                theDepth + 1,
1187                                theBestIndex,
1188                                theBestGap,
1189                                theBoundsMin,
1190                                theBoundsMax);
1191       theBoundsMax[theAxis]  = aSavedMax;
1192       const double aSavedMin = theBoundsMin[theAxis];
1193       theBoundsMin[theAxis]  = aSplitVal;
1194       nearestWeightedRecursive(theQuery,
1195                                theMid + 1,
1196                                theHi,
1197                                theDepth + 1,
1198                                theBestIndex,
1199                                theBestGap,
1200                                theBoundsMin,
1201                                theBoundsMax);
1202       theBoundsMin[theAxis] = aSavedMin;
1203     }
1204     else
1205     {
1206       const double aSavedMin = theBoundsMin[theAxis];
1207       theBoundsMin[theAxis]  = aSplitVal;
1208       nearestWeightedRecursive(theQuery,
1209                                theMid + 1,
1210                                theHi,
1211                                theDepth + 1,
1212                                theBestIndex,
1213                                theBestGap,
1214                                theBoundsMin,
1215                                theBoundsMax);
1216       theBoundsMin[theAxis]  = aSavedMin;
1217       const double aSavedMax = theBoundsMax[theAxis];
1218       theBoundsMax[theAxis]  = aSplitVal;
1219       nearestWeightedRecursive(theQuery,
1220                                theLo,
1221                                theMid - 1,
1222                                theDepth + 1,
1223                                theBestIndex,
1224                                theBestGap,
1225                                theBoundsMin,
1226                                theBoundsMax);
1227       theBoundsMax[theAxis] = aSavedMax;
1228     }
1229   }
1230 
1231 private:
1232   NCollection_Array1<ThePointType> myPoints; //!< Copy of input points (1-based)
1233   NCollection_Array1<size_t> myIndices; //!< Permutation array encoding tree structure (1-based)
1234   NCollection_Array1<double> myRadii;   //!< Per-point radii (1-based, only used when HasRadii)
1235   NCollection_Array1<double>
1236          myMaxRadius; //!< Max radius in subtree rooted at [mid] (only used when HasRadii)
1237   size_t mySize;      //!< Number of points
1238 };
1239 
1240 #endif