Loading files
// Copyright (c) 2026 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#ifndef NCollection_KDTree_HeaderFile
#define NCollection_KDTree_HeaderFile
#include <NCollection_Array1.hxx>
#include <NCollection_DynamicArray.hxx>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <limits>
#include <type_traits>
//! @brief Static KD-Tree for efficient point set queries.
//!
//! NCollection_KDTree is a balanced KD-Tree built once from a set of points,
//! supporting efficient nearest-neighbor, k-nearest, range (sphere), and
//! box (AABB) queries.
//!
//! Key features:
//! - O(N log N) construction via median-split (std::nth_element)
//! - O(log N) nearest-neighbor search with bounding box pruning
//! - O(N^(1-1/d) + k) range and box search
//! - Cache-friendly permutation-based layout (no node allocations)
//! - Optional per-point radii for sphere-aware queries (compile-time, zero overhead when unused)
//! - Works with any point type providing Coord(int) with 1-based indexing
//!
//! Best suited for:
//! - Static point clouds (build once, query many times)
//! - Nearest-neighbor and k-nearest queries
//! - Spatial range filtering
//! - Sphere containment and weighted nearest queries (when HasRadii = true)
//!
//! Required interface for ThePointType (besides copy constructor and operator =):
//! @code
//! class MyPointType
//! {
//! public:
//! double Coord(int theIndex) const;
//! // Returns coordinate along theIndex axis (1-based, from 1 to TheDimension)
//! };
//! @endcode
//!
//! Works out-of-the-box with gp_Pnt, gp_Pnt2d, gp_XYZ, gp_XY.
//!
//! @tparam ThePointType Point type providing Coord(int) with 1-based indexing
//! @tparam TheDimension Spatial dimension (compile-time constant, typically 2 or 3)
//! @tparam HasRadii When true, enables per-point radii for ContainingSearch
//! and NearestWeighted queries (default: false)
template <class ThePointType, int TheDimension, bool HasRadii = false>
class NCollection_KDTree
{
public:
//! Empty constructor. Creates an empty tree.
NCollection_KDTree()
: mySize(0)
{
}
//! Build the tree from a C array of points (no radii).
//! Only available when HasRadii is false.
//! @param[in] thePoints pointer to contiguous array of points
//! @param[in] theCount number of points
template <bool R = HasRadii, typename = std::enable_if_t<!R>>
void Build(const ThePointType* thePoints, size_t theCount)
{
if (theCount == 0)
{
Clear();
return;
}
mySize = theCount;
myPoints.Resize(1, static_cast<int>(theCount), false);
myIndices.Resize(1, static_cast<int>(theCount), false);
for (size_t i = 0; i < theCount; ++i)
{
myPoints.SetValue(static_cast<int>(i + 1), thePoints[i]);
myIndices.SetValue(static_cast<int>(i + 1), i + 1);
}
buildRecursive(1, static_cast<int>(mySize), 0);
}
//! Build the tree from an NCollection_Array1 (no radii).
//! Only available when HasRadii is false.
//! @param[in] thePoints array of points (any lower bound)
template <bool R = HasRadii, typename = std::enable_if_t<!R>>
void Build(const NCollection_Array1<ThePointType>& thePoints)
{
const int aCount = thePoints.Length();
if (aCount == 0)
{
Clear();
return;
}
mySize = static_cast<size_t>(aCount);
myPoints.Resize(1, aCount, false);
myIndices.Resize(1, aCount, false);
for (int i = 0; i < aCount; ++i)
{
myPoints.SetValue(i + 1, thePoints.Value(thePoints.Lower() + i));
myIndices.SetValue(i + 1, static_cast<size_t>(i + 1));
}
buildRecursive(1, static_cast<int>(mySize), 0);
}
//! Build the tree from a C array of points with per-point radii.
//! Only available when HasRadii is true.
//! @param[in] thePoints pointer to contiguous array of points
//! @param[in] theRadii pointer to contiguous array of radii (one per point)
//! @param[in] theCount number of points
template <bool R = HasRadii, typename = std::enable_if_t<R>>
void Build(const ThePointType* thePoints, const double* theRadii, size_t theCount)
{
if (theCount == 0)
{
Clear();
return;
}
mySize = theCount;
const int aN = static_cast<int>(theCount);
myPoints.Resize(1, aN, false);
myIndices.Resize(1, aN, false);
myRadii.Resize(1, aN, false);
myMaxRadius.Resize(1, aN, false);
for (size_t i = 0; i < theCount; ++i)
{
const int anI = static_cast<int>(i + 1);
myPoints.SetValue(anI, thePoints[i]);
myIndices.SetValue(anI, i + 1);
myRadii.SetValue(anI, theRadii[i]);
}
buildRecursive(1, aN, 0);
}
//! Build the tree from NCollection_Array1 of points with radii.
//! Only available when HasRadii is true.
//! @param[in] thePoints array of points (any lower bound)
//! @param[in] theRadii array of radii (same length as thePoints)
template <bool R = HasRadii, typename = std::enable_if_t<R>>
void Build(const NCollection_Array1<ThePointType>& thePoints,
const NCollection_Array1<double>& theRadii)
{
const int aCount = thePoints.Length();
if (aCount == 0)
{
Clear();
return;
}
mySize = static_cast<size_t>(aCount);
myPoints.Resize(1, aCount, false);
myIndices.Resize(1, aCount, false);
myRadii.Resize(1, aCount, false);
myMaxRadius.Resize(1, aCount, false);
for (int i = 0; i < aCount; ++i)
{
myPoints.SetValue(i + 1, thePoints.Value(thePoints.Lower() + i));
myIndices.SetValue(i + 1, static_cast<size_t>(i + 1));
myRadii.SetValue(i + 1, theRadii.Value(theRadii.Lower() + i));
}
buildRecursive(1, static_cast<int>(mySize), 0);
}
//! Returns true if the tree contains no points.
bool IsEmpty() const { return mySize == 0; }
//! Returns the number of points in the tree.
size_t Size() const { return mySize; }
//! Clears the tree.
void Clear()
{
mySize = 0;
myPoints = NCollection_Array1<ThePointType>();
myIndices = NCollection_Array1<size_t>();
if constexpr (HasRadii)
{
myRadii = NCollection_Array1<double>();
myMaxRadius = NCollection_Array1<double>();
}
}
//! Returns the point at the given 1-based original index.
//! @param[in] theIndex 1-based point index
//! @return const reference to the point
const ThePointType& Point(size_t theIndex) const
{
return myPoints.Value(static_cast<int>(theIndex));
}
//! Returns the radius of the point at the given 1-based original index.
//! Only available when HasRadii is true.
//! @param[in] theIndex 1-based point index
//! @return radius of the point
template <bool R = HasRadii, typename = std::enable_if_t<R>>
double Radius(size_t theIndex) const
{
return myRadii.Value(static_cast<int>(theIndex));
}
//! Finds the nearest point to theQuery.
//! @param[in] theQuery query point
//! @return 1-based index of nearest point, 0 if tree is empty
size_t NearestPoint(const ThePointType& theQuery) const
{
double aDummy = 0.0;
return NearestPoint(theQuery, aDummy);
}
//! Finds the nearest point to theQuery and returns the squared distance.
//! @param[in] theQuery query point
//! @param[out] theSqDistance squared distance to the nearest point
//! @return 1-based index of nearest point, 0 if tree is empty
size_t NearestPoint(const ThePointType& theQuery, double& theSqDistance) const
{
if (IsEmpty())
{
theSqDistance = 0.0;
return 0;
}
size_t aBestIndex = 0;
double aBestSqDist = std::numeric_limits<double>::max();
double aBoundsMin[TheDimension];
double aBoundsMax[TheDimension];
initBounds(aBoundsMin, aBoundsMax);
nearestRecursive(theQuery,
1,
static_cast<int>(mySize),
0,
aBestIndex,
aBestSqDist,
aBoundsMin,
aBoundsMax);
theSqDistance = aBestSqDist;
return aBestIndex;
}
//! Finds all nearest points to theQuery that share the same minimum distance.
//! Useful when multiple points are equidistant from the query.
//! First finds the nearest distance via tree traversal, then collects all points
//! at that distance using a range search with theTolerance for floating-point comparison.
//! @param[in] theQuery query point
//! @param[in] theTolerance absolute tolerance for distance equality (default 0.0)
//! @param[out] theSqDistance squared distance to the nearest points
//! @return array of 1-based indices of all equidistant nearest points (empty if tree is empty)
NCollection_DynamicArray<size_t> NearestPoints(const ThePointType& theQuery,
double theTolerance,
double& theSqDistance) const
{
NCollection_DynamicArray<size_t> aResult;
if (IsEmpty())
{
theSqDistance = 0.0;
return aResult;
}
// Find the nearest distance first
size_t aBestIndex = 0;
double aBestSqDist = std::numeric_limits<double>::max();
double aBoundsMin[TheDimension];
double aBoundsMax[TheDimension];
initBounds(aBoundsMin, aBoundsMax);
nearestRecursive(theQuery,
1,
static_cast<int>(mySize),
0,
aBestIndex,
aBestSqDist,
aBoundsMin,
aBoundsMax);
theSqDistance = aBestSqDist;
// Collect all points at this distance (within tolerance)
const double aBestDist = std::sqrt(aBestSqDist);
const double aSearchRadius = aBestDist + theTolerance;
const double aSearchRadiusSq = aSearchRadius * aSearchRadius;
const double aMinSqDist = (aBestDist - theTolerance > 0.0)
? (aBestDist - theTolerance) * (aBestDist - theTolerance)
: 0.0;
// Use range search to get candidates, then filter by exact distance tolerance
NCollection_DynamicArray<size_t> aCandidates;
rangeSearchRecursive(theQuery, aSearchRadiusSq, 1, static_cast<int>(mySize), 0, aCandidates);
for (size_t i = 0; i < aCandidates.Size(); ++i)
{
const size_t anIdx = aCandidates[i];
const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
if (aSqDist >= aMinSqDist && aSqDist <= aSearchRadiusSq)
{
aResult.Append(anIdx);
}
}
return aResult;
}
//! Finds the K nearest points to theQuery.
//! Results are sorted by distance (closest first).
//! @param[in] theQuery query point
//! @param[in] theK number of nearest points to find
//! @param[out] theIndices 1-based indices of the found points (resized internally)
//! @param[out] theSqDistances squared distances of the found points (resized internally)
//! @return actual count of points found (may be less than theK)
size_t KNearestPoints(const ThePointType& theQuery,
size_t theK,
NCollection_Array1<size_t>& theIndices,
NCollection_Array1<double>& theSqDistances) const
{
if (IsEmpty() || theK == 0)
{
theIndices = NCollection_Array1<size_t>();
theSqDistances = NCollection_Array1<double>();
return 0;
}
const size_t anActualK = std::min(theK, mySize);
// Max-heap: pairs of (sqDist, index)
NCollection_Array1<std::pair<double, size_t>> aHeap(1, static_cast<int>(anActualK));
size_t aHeapSize = 0;
double aBoundsMin[TheDimension];
double aBoundsMax[TheDimension];
initBounds(aBoundsMin, aBoundsMax);
kNearestRecursive(theQuery,
1,
static_cast<int>(mySize),
0,
aHeap,
aHeapSize,
anActualK,
aBoundsMin,
aBoundsMax);
// Extract from heap sorted closest-first
const int aCount = static_cast<int>(aHeapSize);
theIndices.Resize(1, aCount, false);
theSqDistances.Resize(1, aCount, false);
// Heap is max-heap, so extract in reverse (largest first) then reverse
for (int i = aCount; i >= 1; --i)
{
theIndices.SetValue(i, aHeap.Value(1).second);
theSqDistances.SetValue(i, aHeap.Value(1).first);
// Pop max from heap
aHeap.SetValue(1, aHeap.Value(static_cast<int>(aHeapSize)));
--aHeapSize;
heapSiftDown(aHeap, 1, static_cast<int>(aHeapSize));
}
return static_cast<size_t>(aCount);
}
//! Finds all points within theRadius of theQuery (sphere search).
//! @param[in] theQuery query point
//! @param[in] theRadius search radius
//! @return array of 1-based indices of found points
NCollection_DynamicArray<size_t> RangeSearch(const ThePointType& theQuery, double theRadius) const
{
NCollection_DynamicArray<size_t> aResult;
if (IsEmpty() || theRadius < 0.0)
{
return aResult;
}
const double aRadiusSq = theRadius * theRadius;
rangeSearchRecursive(theQuery, aRadiusSq, 1, static_cast<int>(mySize), 0, aResult);
return aResult;
}
//! Calls theFunctor for each point within theRadius of theQuery.
//! Zero-allocation alternative to RangeSearch - avoids DynamicArray overhead.
//! Indices passed to theFunctor are 1-based (same convention as RangeSearch).
//! @tparam Functor callable with signature void(size_t theIndex)
//! @param[in] theQuery query point
//! @param[in] theRadius search radius
//! @param[in] theFunctor callback invoked for each found 1-based index
template <typename Functor>
void ForEachInRange(const ThePointType& theQuery, double theRadius, Functor theFunctor) const
{
if (IsEmpty() || theRadius < 0.0)
{
return;
}
const double aRadiusSq = theRadius * theRadius;
forEachInRangeRecursive(theQuery, aRadiusSq, 1, static_cast<int>(mySize), 0, theFunctor);
}
//! Finds all points within the axis-aligned bounding box [theMin, theMax].
//! @param[in] theMin minimum corner of the box
//! @param[in] theMax maximum corner of the box
//! @return array of 1-based indices of found points
NCollection_DynamicArray<size_t> BoxSearch(const ThePointType& theMin,
const ThePointType& theMax) const
{
NCollection_DynamicArray<size_t> aResult;
if (IsEmpty())
{
return aResult;
}
boxSearchRecursive(theMin, theMax, 1, static_cast<int>(mySize), 0, aResult);
return aResult;
}
//! Finds all points whose sphere contains theQuery.
//! Point i "contains" theQuery if dist(theQuery, point_i) <= radius_i.
//! Only available when HasRadii is true.
//! @param[in] theQuery query point
//! @return array of 1-based indices of containing points
template <bool R = HasRadii, typename = std::enable_if_t<R>>
NCollection_DynamicArray<size_t> ContainingSearch(const ThePointType& theQuery) const
{
NCollection_DynamicArray<size_t> aResult;
if (IsEmpty())
{
return aResult;
}
double aBoundsMin[TheDimension];
double aBoundsMax[TheDimension];
initBounds(aBoundsMin, aBoundsMax);
containingSearchRecursive(theQuery,
1,
static_cast<int>(mySize),
0,
aResult,
aBoundsMin,
aBoundsMax);
return aResult;
}
//! Finds the point whose sphere surface is closest to theQuery.
//! Minimizes gap distance = dist(theQuery, point_i) - radius_i.
//! Negative gap means theQuery is inside the sphere.
//! Only available when HasRadii is true.
//! @param[in] theQuery query point
//! @return 1-based index of nearest-weighted point, 0 if tree is empty
template <bool R = HasRadii, typename = std::enable_if_t<R>>
size_t NearestWeighted(const ThePointType& theQuery) const
{
double aDummy = 0.0;
return NearestWeighted(theQuery, aDummy);
}
//! Finds the point whose sphere surface is closest to theQuery.
//! Minimizes gap distance = dist(theQuery, point_i) - radius_i.
//! Negative gap means theQuery is inside the sphere.
//! Only available when HasRadii is true.
//! @param[in] theQuery query point
//! @param[out] theGapDistance gap distance to the nearest sphere surface (negative = inside)
//! @return 1-based index of nearest-weighted point, 0 if tree is empty
template <bool R = HasRadii, typename = std::enable_if_t<R>>
size_t NearestWeighted(const ThePointType& theQuery, double& theGapDistance) const
{
if (IsEmpty())
{
theGapDistance = 0.0;
return 0;
}
size_t aBestIndex = 0;
double aBestGap = std::numeric_limits<double>::max();
double aBoundsMin[TheDimension];
double aBoundsMax[TheDimension];
initBounds(aBoundsMin, aBoundsMax);
nearestWeightedRecursive(theQuery,
1,
static_cast<int>(mySize),
0,
aBestIndex,
aBestGap,
aBoundsMin,
aBoundsMax);
theGapDistance = aBestGap;
return aBestIndex;
}
//! Copy constructor.
NCollection_KDTree(const NCollection_KDTree& theOther) = default;
//! Move constructor.
NCollection_KDTree(NCollection_KDTree&& theOther) noexcept
: myPoints(std::move(theOther.myPoints)),
myIndices(std::move(theOther.myIndices)),
myRadii(std::move(theOther.myRadii)),
myMaxRadius(std::move(theOther.myMaxRadius)),
mySize(theOther.mySize)
{
theOther.mySize = 0;
}
//! Copy assignment.
NCollection_KDTree& operator=(const NCollection_KDTree& theOther)
{
if (this != &theOther)
{
myPoints = theOther.myPoints;
myIndices = theOther.myIndices;
myRadii = theOther.myRadii;
myMaxRadius = theOther.myMaxRadius;
mySize = theOther.mySize;
}
return *this;
}
//! Move assignment.
NCollection_KDTree& operator=(NCollection_KDTree&& theOther) noexcept
{
if (this != &theOther)
{
myPoints = std::move(theOther.myPoints);
myIndices = std::move(theOther.myIndices);
myRadii = std::move(theOther.myRadii);
myMaxRadius = std::move(theOther.myMaxRadius);
mySize = theOther.mySize;
theOther.mySize = 0;
}
return *this;
}
private:
//! Squared distance between two points.
static double squareDistance(const ThePointType& theP1, const ThePointType& theP2)
{
double aSqDist = 0.0;
for (int i = 1; i <= TheDimension; ++i)
{
const double aDiff = theP1.Coord(i) - theP2.Coord(i);
aSqDist += aDiff * aDiff;
}
return aSqDist;
}
//! Checks whether a point is inside a box [theMin, theMax].
static bool isInsideBox(const ThePointType& thePoint,
const ThePointType& theMin,
const ThePointType& theMax)
{
for (int i = 1; i <= TheDimension; ++i)
{
const double aCoord = thePoint.Coord(i);
if (aCoord < theMin.Coord(i) || aCoord > theMax.Coord(i))
{
return false;
}
}
return true;
}
//! Leaf bucket size: subtrees with this many points or fewer
//! are scanned linearly instead of recursing further.
static constexpr int THE_LEAF_SIZE = 32;
//! Minimum squared distance from a point to an axis-aligned bounding box.
static double sqDistToBox(const ThePointType& theQuery,
const double theBoundsMin[],
const double theBoundsMax[])
{
double aSqDist = 0.0;
for (int i = 0; i < TheDimension; ++i)
{
const double aCoord = theQuery.Coord(i + 1);
if (aCoord < theBoundsMin[i])
{
const double aDiff = theBoundsMin[i] - aCoord;
aSqDist += aDiff * aDiff;
}
else if (aCoord > theBoundsMax[i])
{
const double aDiff = aCoord - theBoundsMax[i];
aSqDist += aDiff * aDiff;
}
}
return aSqDist;
}
//! Initializes bounding box arrays to [-max, +max] in all dimensions.
static void initBounds(double theBoundsMin[], double theBoundsMax[])
{
for (int i = 0; i < TheDimension; ++i)
{
theBoundsMin[i] = -std::numeric_limits<double>::max();
theBoundsMax[i] = std::numeric_limits<double>::max();
}
}
//! Sift-down for a max-heap on the range [1..theSize].
static void heapSiftDown(NCollection_Array1<std::pair<double, size_t>>& theHeap,
int theIdx,
int theSize)
{
while (true)
{
int aLargest = theIdx;
const int aLeft = 2 * theIdx;
const int aRight = 2 * theIdx + 1;
if (aLeft <= theSize && theHeap.Value(aLeft).first > theHeap.Value(aLargest).first)
{
aLargest = aLeft;
}
if (aRight <= theSize && theHeap.Value(aRight).first > theHeap.Value(aLargest).first)
{
aLargest = aRight;
}
if (aLargest == theIdx)
{
break;
}
std::swap(theHeap.ChangeValue(theIdx), theHeap.ChangeValue(aLargest));
theIdx = aLargest;
}
}
//! Sift-up for a max-heap on the range [1..theSize].
static void heapSiftUp(NCollection_Array1<std::pair<double, size_t>>& theHeap, int theIdx)
{
while (theIdx > 1)
{
const int aParent = theIdx / 2;
if (theHeap.Value(theIdx).first <= theHeap.Value(aParent).first)
{
break;
}
std::swap(theHeap.ChangeValue(theIdx), theHeap.ChangeValue(aParent));
theIdx = aParent;
}
}
//! Recursive build: partitions myIndices[theLo..theHi] into a balanced KD-Tree.
//! @return maximum radius in the subtree (0.0 when HasRadii is false)
double buildRecursive(int theLo, int theHi, int theDepth)
{
if (theLo > theHi)
{
return 0.0;
}
if (theLo == theHi)
{
if constexpr (HasRadii)
{
const double aR = myRadii.Value(static_cast<int>(myIndices.Value(theLo)));
myMaxRadius.SetValue(theLo, aR);
return aR;
}
return 0.0;
}
const int theAxis = theDepth % TheDimension;
const int theMid = (theLo + theHi) / 2;
// Use std::nth_element on the index range to place the median
std::nth_element(&myIndices.ChangeValue(theLo),
&myIndices.ChangeValue(theMid),
&myIndices.ChangeValue(theHi) + 1,
[this, theAxis](size_t a, size_t b) {
return myPoints.Value(static_cast<int>(a)).Coord(theAxis + 1)
< myPoints.Value(static_cast<int>(b)).Coord(theAxis + 1);
});
if constexpr (HasRadii)
{
const double aLeftMax = buildRecursive(theLo, theMid - 1, theDepth + 1);
const double aRightMax = buildRecursive(theMid + 1, theHi, theDepth + 1);
const double aNodeR = myRadii.Value(static_cast<int>(myIndices.Value(theMid)));
const double aMaxR = std::max({aNodeR, aLeftMax, aRightMax});
myMaxRadius.SetValue(theMid, aMaxR);
return aMaxR;
}
else
{
buildRecursive(theLo, theMid - 1, theDepth + 1);
buildRecursive(theMid + 1, theHi, theDepth + 1);
return 0.0;
}
}
//! Recursive nearest-neighbor search with bounding box pruning and leaf buckets.
void nearestRecursive(const ThePointType& theQuery,
int theLo,
int theHi,
int theDepth,
size_t& theBestIndex,
double& theBestSqDist,
double theBoundsMin[],
double theBoundsMax[]) const
{
if (theLo > theHi)
{
return;
}
// Bounding box pruning: skip subtree if it cannot contain a closer point
if (sqDistToBox(theQuery, theBoundsMin, theBoundsMax) >= theBestSqDist)
{
return;
}
// Leaf bucket: linear scan for small subtrees
if (theHi - theLo + 1 <= THE_LEAF_SIZE)
{
for (int i = theLo; i <= theHi; ++i)
{
const size_t anIdx = myIndices.Value(i);
const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
if (aSqDist < theBestSqDist)
{
theBestSqDist = aSqDist;
theBestIndex = anIdx;
}
}
return;
}
const int theMid = (theLo + theHi) / 2;
const size_t aNodeIndex = myIndices.Value(theMid);
const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
const double aSqDist = squareDistance(theQuery, aNodePoint);
if (aSqDist < theBestSqDist)
{
theBestSqDist = aSqDist;
theBestIndex = aNodeIndex;
}
const int theAxis = theDepth % TheDimension;
const double aSplitVal = aNodePoint.Coord(theAxis + 1);
const double aDiff = theQuery.Coord(theAxis + 1) - aSplitVal;
// Visit closer subtree first, then farther subtree.
// The bounding box check at the top of recursion handles pruning.
if (aDiff <= 0.0)
{
const double aSavedMax = theBoundsMax[theAxis];
theBoundsMax[theAxis] = aSplitVal;
nearestRecursive(theQuery,
theLo,
theMid - 1,
theDepth + 1,
theBestIndex,
theBestSqDist,
theBoundsMin,
theBoundsMax);
theBoundsMax[theAxis] = aSavedMax;
const double aSavedMin = theBoundsMin[theAxis];
theBoundsMin[theAxis] = aSplitVal;
nearestRecursive(theQuery,
theMid + 1,
theHi,
theDepth + 1,
theBestIndex,
theBestSqDist,
theBoundsMin,
theBoundsMax);
theBoundsMin[theAxis] = aSavedMin;
}
else
{
const double aSavedMin = theBoundsMin[theAxis];
theBoundsMin[theAxis] = aSplitVal;
nearestRecursive(theQuery,
theMid + 1,
theHi,
theDepth + 1,
theBestIndex,
theBestSqDist,
theBoundsMin,
theBoundsMax);
theBoundsMin[theAxis] = aSavedMin;
const double aSavedMax = theBoundsMax[theAxis];
theBoundsMax[theAxis] = aSplitVal;
nearestRecursive(theQuery,
theLo,
theMid - 1,
theDepth + 1,
theBestIndex,
theBestSqDist,
theBoundsMin,
theBoundsMax);
theBoundsMax[theAxis] = aSavedMax;
}
}
//! Recursive k-nearest search with bounding box pruning, max-heap, and leaf buckets.
void kNearestRecursive(const ThePointType& theQuery,
int theLo,
int theHi,
int theDepth,
NCollection_Array1<std::pair<double, size_t>>& theHeap,
size_t& theHeapSize,
size_t theK,
double theBoundsMin[],
double theBoundsMax[]) const
{
if (theLo > theHi)
{
return;
}
// Bounding box pruning: skip subtree if it cannot improve the K-th best
if (theHeapSize == theK
&& sqDistToBox(theQuery, theBoundsMin, theBoundsMax) >= theHeap.Value(1).first)
{
return;
}
// Leaf bucket: linear scan for small subtrees
if (theHi - theLo + 1 <= THE_LEAF_SIZE)
{
for (int i = theLo; i <= theHi; ++i)
{
const size_t anIdx = myIndices.Value(i);
const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
if (theHeapSize < theK)
{
++theHeapSize;
theHeap.SetValue(static_cast<int>(theHeapSize), {aSqDist, anIdx});
heapSiftUp(theHeap, static_cast<int>(theHeapSize));
}
else if (aSqDist < theHeap.Value(1).first)
{
theHeap.SetValue(1, {aSqDist, anIdx});
heapSiftDown(theHeap, 1, static_cast<int>(theHeapSize));
}
}
return;
}
const int theMid = (theLo + theHi) / 2;
const size_t aNodeIndex = myIndices.Value(theMid);
const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
const double aSqDist = squareDistance(theQuery, aNodePoint);
if (theHeapSize < theK)
{
++theHeapSize;
theHeap.SetValue(static_cast<int>(theHeapSize), {aSqDist, aNodeIndex});
heapSiftUp(theHeap, static_cast<int>(theHeapSize));
}
else if (aSqDist < theHeap.Value(1).first)
{
theHeap.SetValue(1, {aSqDist, aNodeIndex});
heapSiftDown(theHeap, 1, static_cast<int>(theHeapSize));
}
const int theAxis = theDepth % TheDimension;
const double aSplitVal = aNodePoint.Coord(theAxis + 1);
const double aDiff = theQuery.Coord(theAxis + 1) - aSplitVal;
// Visit closer subtree first, bounding box check at the top handles pruning
if (aDiff <= 0.0)
{
const double aSavedMax = theBoundsMax[theAxis];
theBoundsMax[theAxis] = aSplitVal;
kNearestRecursive(theQuery,
theLo,
theMid - 1,
theDepth + 1,
theHeap,
theHeapSize,
theK,
theBoundsMin,
theBoundsMax);
theBoundsMax[theAxis] = aSavedMax;
const double aSavedMin = theBoundsMin[theAxis];
theBoundsMin[theAxis] = aSplitVal;
kNearestRecursive(theQuery,
theMid + 1,
theHi,
theDepth + 1,
theHeap,
theHeapSize,
theK,
theBoundsMin,
theBoundsMax);
theBoundsMin[theAxis] = aSavedMin;
}
else
{
const double aSavedMin = theBoundsMin[theAxis];
theBoundsMin[theAxis] = aSplitVal;
kNearestRecursive(theQuery,
theMid + 1,
theHi,
theDepth + 1,
theHeap,
theHeapSize,
theK,
theBoundsMin,
theBoundsMax);
theBoundsMin[theAxis] = aSavedMin;
const double aSavedMax = theBoundsMax[theAxis];
theBoundsMax[theAxis] = aSplitVal;
kNearestRecursive(theQuery,
theLo,
theMid - 1,
theDepth + 1,
theHeap,
theHeapSize,
theK,
theBoundsMin,
theBoundsMax);
theBoundsMax[theAxis] = aSavedMax;
}
}
//! Recursive range search (sphere) with leaf buckets.
void rangeSearchRecursive(const ThePointType& theQuery,
double theRadiusSq,
int theLo,
int theHi,
int theDepth,
NCollection_DynamicArray<size_t>& theIndices) const
{
if (theLo > theHi)
{
return;
}
// Leaf bucket: linear scan for small subtrees
if (theHi - theLo + 1 <= THE_LEAF_SIZE)
{
for (int i = theLo; i <= theHi; ++i)
{
const size_t anIdx = myIndices.Value(i);
const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
if (aSqDist <= theRadiusSq)
{
theIndices.Append(anIdx);
}
}
return;
}
const int theMid = (theLo + theHi) / 2;
const size_t aNodeIndex = myIndices.Value(theMid);
const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
const double aSqDist = squareDistance(theQuery, aNodePoint);
if (aSqDist <= theRadiusSq)
{
theIndices.Append(aNodeIndex);
}
if (theLo == theHi)
{
return;
}
const int theAxis = theDepth % TheDimension;
const double aDiff = theQuery.Coord(theAxis + 1) - aNodePoint.Coord(theAxis + 1);
// Always visit the near subtree
if (aDiff <= 0.0)
{
rangeSearchRecursive(theQuery, theRadiusSq, theLo, theMid - 1, theDepth + 1, theIndices);
if (aDiff * aDiff <= theRadiusSq)
{
rangeSearchRecursive(theQuery, theRadiusSq, theMid + 1, theHi, theDepth + 1, theIndices);
}
}
else
{
rangeSearchRecursive(theQuery, theRadiusSq, theMid + 1, theHi, theDepth + 1, theIndices);
if (aDiff * aDiff <= theRadiusSq)
{
rangeSearchRecursive(theQuery, theRadiusSq, theLo, theMid - 1, theDepth + 1, theIndices);
}
}
}
//! Recursive range search (sphere) with callback - zero-allocation variant.
template <typename Functor>
void forEachInRangeRecursive(const ThePointType& theQuery,
double theRadiusSq,
int theLo,
int theHi,
int theDepth,
Functor& theFunctor) const
{
if (theLo > theHi)
{
return;
}
if (theHi - theLo + 1 <= THE_LEAF_SIZE)
{
for (int i = theLo; i <= theHi; ++i)
{
const size_t anIdx = myIndices.Value(i);
const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
if (aSqDist <= theRadiusSq)
{
theFunctor(anIdx);
}
}
return;
}
const int aMid = (theLo + theHi) / 2;
const size_t aNodeIndex = myIndices.Value(aMid);
const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
const double aSqDist = squareDistance(theQuery, aNodePoint);
if (aSqDist <= theRadiusSq)
{
theFunctor(aNodeIndex);
}
if (theLo == theHi)
{
return;
}
const int aAxis = theDepth % TheDimension;
const double aDiff = theQuery.Coord(aAxis + 1) - aNodePoint.Coord(aAxis + 1);
if (aDiff <= 0.0)
{
forEachInRangeRecursive(theQuery, theRadiusSq, theLo, aMid - 1, theDepth + 1, theFunctor);
if (aDiff * aDiff <= theRadiusSq)
{
forEachInRangeRecursive(theQuery, theRadiusSq, aMid + 1, theHi, theDepth + 1, theFunctor);
}
}
else
{
forEachInRangeRecursive(theQuery, theRadiusSq, aMid + 1, theHi, theDepth + 1, theFunctor);
if (aDiff * aDiff <= theRadiusSq)
{
forEachInRangeRecursive(theQuery, theRadiusSq, theLo, aMid - 1, theDepth + 1, theFunctor);
}
}
}
//! Recursive box search (AABB) with leaf buckets.
void boxSearchRecursive(const ThePointType& theMin,
const ThePointType& theMax,
int theLo,
int theHi,
int theDepth,
NCollection_DynamicArray<size_t>& theIndices) const
{
if (theLo > theHi)
{
return;
}
// Leaf bucket: linear scan for small subtrees
if (theHi - theLo + 1 <= THE_LEAF_SIZE)
{
for (int i = theLo; i <= theHi; ++i)
{
const size_t anIdx = myIndices.Value(i);
if (isInsideBox(myPoints.Value(static_cast<int>(anIdx)), theMin, theMax))
{
theIndices.Append(anIdx);
}
}
return;
}
const int theMid = (theLo + theHi) / 2;
const size_t aNodeIndex = myIndices.Value(theMid);
const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
if (isInsideBox(aNodePoint, theMin, theMax))
{
theIndices.Append(aNodeIndex);
}
if (theLo == theHi)
{
return;
}
const int theAxis = theDepth % TheDimension;
const double aCoord = aNodePoint.Coord(theAxis + 1);
if (aCoord >= theMin.Coord(theAxis + 1))
{
boxSearchRecursive(theMin, theMax, theLo, theMid - 1, theDepth + 1, theIndices);
}
if (aCoord <= theMax.Coord(theAxis + 1))
{
boxSearchRecursive(theMin, theMax, theMid + 1, theHi, theDepth + 1, theIndices);
}
}
//! Recursive containing search: finds all spheres containing theQuery.
//! Uses maxRadius pruning to skip subtrees where no sphere can reach the query.
void containingSearchRecursive(const ThePointType& theQuery,
int theLo,
int theHi,
int theDepth,
NCollection_DynamicArray<size_t>& theIndices,
double theBoundsMin[],
double theBoundsMax[]) const
{
if (theLo > theHi)
{
return;
}
// Prune: if min distance to subtree box > max radius in subtree, no sphere contains query
const int theMid = (theLo + theHi) / 2;
const double aMaxR = myMaxRadius.Value(theMid);
if (sqDistToBox(theQuery, theBoundsMin, theBoundsMax) > aMaxR * aMaxR)
{
return;
}
// Leaf bucket: linear scan for small subtrees
if (theHi - theLo + 1 <= THE_LEAF_SIZE)
{
for (int i = theLo; i <= theHi; ++i)
{
const size_t anIdx = myIndices.Value(i);
const double aR = myRadii.Value(static_cast<int>(anIdx));
const double aSqDist = squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx)));
if (aSqDist <= aR * aR)
{
theIndices.Append(anIdx);
}
}
return;
}
// Check current node
const size_t aNodeIndex = myIndices.Value(theMid);
const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
const double aNodeR = myRadii.Value(static_cast<int>(aNodeIndex));
if (squareDistance(theQuery, aNodePoint) <= aNodeR * aNodeR)
{
theIndices.Append(aNodeIndex);
}
// Recurse both subtrees with bounds tracking
const int theAxis = theDepth % TheDimension;
const double aSplitVal = aNodePoint.Coord(theAxis + 1);
const double aSavedMax = theBoundsMax[theAxis];
theBoundsMax[theAxis] = aSplitVal;
containingSearchRecursive(theQuery,
theLo,
theMid - 1,
theDepth + 1,
theIndices,
theBoundsMin,
theBoundsMax);
theBoundsMax[theAxis] = aSavedMax;
const double aSavedMin = theBoundsMin[theAxis];
theBoundsMin[theAxis] = aSplitVal;
containingSearchRecursive(theQuery,
theMid + 1,
theHi,
theDepth + 1,
theIndices,
theBoundsMin,
theBoundsMax);
theBoundsMin[theAxis] = aSavedMin;
}
//! Recursive nearest-weighted search: finds point minimizing dist - radius.
//! Uses bounding box + maxRadius pruning to skip subtrees.
void nearestWeightedRecursive(const ThePointType& theQuery,
int theLo,
int theHi,
int theDepth,
size_t& theBestIndex,
double& theBestGap,
double theBoundsMin[],
double theBoundsMax[]) const
{
if (theLo > theHi)
{
return;
}
// Prune: minimum possible gap = sqrt(sqDistToBox) - maxRadius.
// To avoid sqrt: if (bestGap + maxRadius) >= 0, then sqDistToBox >= (bestGap + maxRadius)^2.
const int theMid = (theLo + theHi) / 2;
const double aMaxR = myMaxRadius.Value(theMid);
const double aSqDistToBox = sqDistToBox(theQuery, theBoundsMin, theBoundsMax);
const double aPruneThreshold = theBestGap + aMaxR;
if (aPruneThreshold >= 0.0 && aSqDistToBox >= aPruneThreshold * aPruneThreshold)
{
return;
}
// Leaf bucket: linear scan for small subtrees
if (theHi - theLo + 1 <= THE_LEAF_SIZE)
{
for (int i = theLo; i <= theHi; ++i)
{
const size_t anIdx = myIndices.Value(i);
const double aDist =
std::sqrt(squareDistance(theQuery, myPoints.Value(static_cast<int>(anIdx))));
const double aGap = aDist - myRadii.Value(static_cast<int>(anIdx));
if (aGap < theBestGap)
{
theBestGap = aGap;
theBestIndex = anIdx;
}
}
return;
}
// Check current node
const size_t aNodeIndex = myIndices.Value(theMid);
const ThePointType& aNodePoint = myPoints.Value(static_cast<int>(aNodeIndex));
const double aDist = std::sqrt(squareDistance(theQuery, aNodePoint));
const double aGap = aDist - myRadii.Value(static_cast<int>(aNodeIndex));
if (aGap < theBestGap)
{
theBestGap = aGap;
theBestIndex = aNodeIndex;
}
// Recurse with bounds tracking, visit closer subtree first
const int theAxis = theDepth % TheDimension;
const double aSplitVal = aNodePoint.Coord(theAxis + 1);
const double aDiff = theQuery.Coord(theAxis + 1) - aSplitVal;
if (aDiff <= 0.0)
{
const double aSavedMax = theBoundsMax[theAxis];
theBoundsMax[theAxis] = aSplitVal;
nearestWeightedRecursive(theQuery,
theLo,
theMid - 1,
theDepth + 1,
theBestIndex,
theBestGap,
theBoundsMin,
theBoundsMax);
theBoundsMax[theAxis] = aSavedMax;
const double aSavedMin = theBoundsMin[theAxis];
theBoundsMin[theAxis] = aSplitVal;
nearestWeightedRecursive(theQuery,
theMid + 1,
theHi,
theDepth + 1,
theBestIndex,
theBestGap,
theBoundsMin,
theBoundsMax);
theBoundsMin[theAxis] = aSavedMin;
}
else
{
const double aSavedMin = theBoundsMin[theAxis];
theBoundsMin[theAxis] = aSplitVal;
nearestWeightedRecursive(theQuery,
theMid + 1,
theHi,
theDepth + 1,
theBestIndex,
theBestGap,
theBoundsMin,
theBoundsMax);
theBoundsMin[theAxis] = aSavedMin;
const double aSavedMax = theBoundsMax[theAxis];
theBoundsMax[theAxis] = aSplitVal;
nearestWeightedRecursive(theQuery,
theLo,
theMid - 1,
theDepth + 1,
theBestIndex,
theBestGap,
theBoundsMin,
theBoundsMax);
theBoundsMax[theAxis] = aSavedMax;
}
}
private:
NCollection_Array1<ThePointType> myPoints; //!< Copy of input points (1-based)
NCollection_Array1<size_t> myIndices; //!< Permutation array encoding tree structure (1-based)
NCollection_Array1<double> myRadii; //!< Per-point radii (1-based, only used when HasRadii)
NCollection_Array1<double>
myMaxRadius; //!< Max radius in subtree rooted at [mid] (only used when HasRadii)
size_t mySize; //!< Number of points
};
#endif
3d097a032This file is pinned to the selected release. Download the original to open it in your design tool.