]> OCCT Git - occt.git/commitdiff
Foundation Classes - Enhance BVH Implementation (#842)
authorPasukhin Dmitry <dpasukhi@opencascade.com>
Wed, 26 Nov 2025 10:38:42 +0000 (10:38 +0000)
committerGitHub <noreply@github.com>
Wed, 26 Nov 2025 10:38:42 +0000 (10:38 +0000)
- Fixed leaf node size condition and SAH cost evaluation in `BVH_SweepPlaneBuilder`
- Added `constexpr` to `BVH_Box`, `BVH_Types`, and helper functions for compile-time evaluation
- Introduced 13 new comprehensive test files covering BVH components
- Removed unused `BVH_BuildQueue.cxx` file
- Added internal helper structures to `BVH_Traverse` classes

34 files changed:
src/FoundationClasses/TKMath/BVH/BVH_BinnedBuilder.hxx
src/FoundationClasses/TKMath/BVH/BVH_Box.hxx
src/FoundationClasses/TKMath/BVH/BVH_BuildQueue.cxx [deleted file]
src/FoundationClasses/TKMath/BVH/BVH_BuildQueue.hxx
src/FoundationClasses/TKMath/BVH/BVH_Constants.hxx
src/FoundationClasses/TKMath/BVH/BVH_Geometry.hxx
src/FoundationClasses/TKMath/BVH/BVH_LinearBuilder.hxx
src/FoundationClasses/TKMath/BVH/BVH_Properties.hxx
src/FoundationClasses/TKMath/BVH/BVH_QueueBuilder.hxx
src/FoundationClasses/TKMath/BVH/BVH_QuickSorter.hxx
src/FoundationClasses/TKMath/BVH/BVH_RadixSorter.hxx
src/FoundationClasses/TKMath/BVH/BVH_Ray.hxx
src/FoundationClasses/TKMath/BVH/BVH_SpatialMedianBuilder.hxx
src/FoundationClasses/TKMath/BVH/BVH_SweepPlaneBuilder.hxx
src/FoundationClasses/TKMath/BVH/BVH_Tools.hxx
src/FoundationClasses/TKMath/BVH/BVH_Traverse.hxx
src/FoundationClasses/TKMath/BVH/BVH_Traverse.lxx
src/FoundationClasses/TKMath/BVH/BVH_Types.hxx
src/FoundationClasses/TKMath/BVH/FILES.cmake
src/FoundationClasses/TKMath/GTests/BVH_BinnedBuilder_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_Box_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_BuildQueue_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_LinearBuilder_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_QuickSorter_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_RadixSorter_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_Ray_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_SpatialMedianBuilder_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_SweepPlaneBuilder_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_Tools_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_Traverse_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_Tree_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/BVH_Triangulation_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/GTests/FILES.cmake
src/Visualization/TKV3d/Select3D/Select3D_SensitivePrimitiveArray.cxx

index ca5dde4134dfb84c52b1e8ca8e01505cde873f4d..a64831687414c283dafd1fb31d043c12ce2d5c83 100644 (file)
@@ -218,7 +218,8 @@ typename BVH_QueueBuilder<T, N>::BVH_ChildNodes BVH_BinnedBuilder<T, N, Bins>::b
 {
   const Standard_Integer aNodeBegPrimitive = theBVH->BegPrimitive(theNode);
   const Standard_Integer aNodeEndPrimitive = theBVH->EndPrimitive(theNode);
-  if (aNodeEndPrimitive - aNodeBegPrimitive < BVH_Builder<T, N>::myLeafNodeSize)
+  const Standard_Integer aNodeNbPrimitives = theBVH->NbPrimitives(theNode);
+  if (aNodeNbPrimitives <= BVH_Builder<T, N>::myLeafNodeSize)
   {
     // clang-format off
     return typename BVH_QueueBuilder<T, N>::BVH_ChildNodes(); // node does not require partitioning
@@ -270,14 +271,16 @@ typename BVH_QueueBuilder<T, N>::BVH_ChildNodes BVH_BinnedBuilder<T, N, Bins>::b
     }
 
     // Choose the best split (with minimum SAH cost)
+    const Standard_Real aParentArea = static_cast<Standard_Real>(anAABB.Area());
     for (Standard_Integer aSplit = 1; aSplit < Bins; ++aSplit)
     {
-      // Simple SAH evaluation
-      Standard_Real aCost =
-        (static_cast<Standard_Real>(aSplitPlanes[aSplit].LftVoxel.Box.Area()) /* / S(N) */)
-          * aSplitPlanes[aSplit].LftVoxel.Count
-        + (static_cast<Standard_Real>(aSplitPlanes[aSplit].RghVoxel.Box.Area()) /* / S(N) */)
-            * aSplitPlanes[aSplit].RghVoxel.Count;
+      // SAH evaluation with proper normalization by parent surface area
+      const Standard_Real aLftArea =
+        static_cast<Standard_Real>(aSplitPlanes[aSplit].LftVoxel.Box.Area());
+      const Standard_Real aRghArea =
+        static_cast<Standard_Real>(aSplitPlanes[aSplit].RghVoxel.Box.Area());
+      Standard_Real aCost = (aLftArea / aParentArea) * aSplitPlanes[aSplit].LftVoxel.Count
+                            + (aRghArea / aParentArea) * aSplitPlanes[aSplit].RghVoxel.Count;
 
       if (aCost <= aMinSplitCost)
       {
index cbf6ca172278a3d1b2fc7edd9c271df509f8d0c2..d511df0f179ad46418b41fa3dc8f1136f5c4d10a 100644 (file)
@@ -125,13 +125,13 @@ public:
 
 public:
   //! Creates uninitialized bounding box.
-  BVH_Box()
+  constexpr BVH_Box() noexcept
       : myIsInited(Standard_False)
   {
   }
 
   //! Creates bounding box of given point.
-  BVH_Box(const BVH_VecNt& thePoint)
+  constexpr BVH_Box(const BVH_VecNt& thePoint) noexcept
       : myMinPoint(thePoint),
         myMaxPoint(thePoint),
         myIsInited(Standard_True)
@@ -139,7 +139,7 @@ public:
   }
 
   //! Creates bounding box from corner points.
-  BVH_Box(const BVH_VecNt& theMinPoint, const BVH_VecNt& theMaxPoint)
+  constexpr BVH_Box(const BVH_VecNt& theMinPoint, const BVH_VecNt& theMaxPoint) noexcept
       : myMinPoint(theMinPoint),
         myMaxPoint(theMaxPoint),
         myIsInited(Standard_True)
@@ -148,10 +148,10 @@ public:
 
 public:
   //! Clears bounding box.
-  void Clear() { myIsInited = Standard_False; }
+  constexpr void Clear() noexcept { myIsInited = Standard_False; }
 
   //! Is bounding box valid?
-  Standard_Boolean IsValid() const { return myIsInited; }
+  constexpr Standard_Boolean IsValid() const noexcept { return myIsInited; }
 
   //! Appends new point to the bounding box.
   void Add(const BVH_VecNt& thePoint)
@@ -173,29 +173,29 @@ public:
   void Combine(const BVH_Box& theBox);
 
   //! Returns minimum point of bounding box.
-  const BVH_VecNt& CornerMin() const { return myMinPoint; }
+  constexpr const BVH_VecNt& CornerMin() const noexcept { return myMinPoint; }
 
   //! Returns maximum point of bounding box.
-  const BVH_VecNt& CornerMax() const { return myMaxPoint; }
+  constexpr const BVH_VecNt& CornerMax() const noexcept { return myMaxPoint; }
 
   //! Returns minimum point of bounding box.
-  BVH_VecNt& CornerMin() { return myMinPoint; }
+  constexpr BVH_VecNt& CornerMin() noexcept { return myMinPoint; }
 
   //! Returns maximum point of bounding box.
-  BVH_VecNt& CornerMax() { return myMaxPoint; }
+  constexpr BVH_VecNt& CornerMax() noexcept { return myMaxPoint; }
 
   //! Returns surface area of bounding box.
   //! If the box is degenerated into line, returns the perimeter instead.
   T Area() const;
 
   //! Returns diagonal of bounding box.
-  BVH_VecNt Size() const { return myMaxPoint - myMinPoint; }
+  constexpr BVH_VecNt Size() const { return myMaxPoint - myMinPoint; }
 
   //! Returns center of bounding box.
-  BVH_VecNt Center() const { return (myMinPoint + myMaxPoint) * static_cast<T>(0.5); }
+  constexpr BVH_VecNt Center() const { return (myMinPoint + myMaxPoint) * static_cast<T>(0.5); }
 
   //! Returns center of bounding box along the given axis.
-  T Center(const Standard_Integer theAxis) const;
+  inline T Center(const Standard_Integer theAxis) const;
 
   //! Dumps the content of me into the stream
   void DumpJson(Standard_OStream& theOStream, Standard_Integer theDepth = -1) const
@@ -203,18 +203,18 @@ public:
     (void)theDepth;
     OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myIsInited)
 
-    int n = (std::min)(N, 3);
-    if (n == 1)
+    constexpr int n = (N < 3) ? N : 3;
+    if constexpr (n == 1)
     {
       OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myMinPoint[0])
       OCCT_DUMP_FIELD_VALUE_NUMERICAL(theOStream, myMinPoint[0])
     }
-    else if (n == 2)
+    else if constexpr (n == 2)
     {
       OCCT_DUMP_FIELD_VALUES_NUMERICAL(theOStream, "MinPoint", n, myMinPoint[0], myMinPoint[1])
       OCCT_DUMP_FIELD_VALUES_NUMERICAL(theOStream, "MaxPoint", n, myMaxPoint[0], myMaxPoint[1])
     }
-    else if (n == 3)
+    else if constexpr (n == 3)
     {
       OCCT_DUMP_FIELD_VALUES_NUMERICAL(theOStream,
                                        "MinPoint",
@@ -242,14 +242,14 @@ public:
     OCCT_INIT_FIELD_VALUE_INTEGER(aStreamStr, aPos, anIsInited);
     myIsInited = anIsInited != 0;
 
-    int n = (std::min)(N, 3);
-    if (n == 1)
+    constexpr int n = (N < 3) ? N : 3;
+    if constexpr (n == 1)
     {
       Standard_Real aValue;
       OCCT_INIT_FIELD_VALUE_REAL(aStreamStr, aPos, aValue);
       myMinPoint[0] = (T)aValue;
     }
-    else if (n == 2)
+    else if constexpr (n == 2)
     {
       Standard_Real aValue1, aValue2;
       OCCT_INIT_VECTOR_CLASS(aStreamStr, "MinPoint", aPos, n, &aValue1, &aValue2);
@@ -260,7 +260,7 @@ public:
       myMaxPoint[0] = (T)aValue1;
       myMaxPoint[1] = (T)aValue2;
     }
-    else if (n == 3)
+    else if constexpr (n == 3)
     {
       Standard_Real aValue1, aValue2, aValue3;
       OCCT_INIT_VECTOR_CLASS(aStreamStr, "MinPoint", aPos, n, &aValue1, &aValue2, &aValue3);
@@ -280,7 +280,7 @@ public:
 
 public:
   //! Checks if the Box is out of the other box.
-  Standard_Boolean IsOut(const BVH_Box<T, N>& theOther) const
+  constexpr Standard_Boolean IsOut(const BVH_Box<T, N>& theOther) const
   {
     if (!theOther.IsValid())
       return Standard_True;
@@ -289,22 +289,32 @@ public:
   }
 
   //! Checks if the Box is out of the other box defined by two points.
-  Standard_Boolean IsOut(const BVH_VecNt& theMinPoint, const BVH_VecNt& theMaxPoint) const
+  constexpr Standard_Boolean IsOut(const BVH_VecNt& theMinPoint, const BVH_VecNt& theMaxPoint) const
   {
     if (!IsValid())
       return Standard_True;
 
-    int n = (std::min)(N, 3);
-    for (int i = 0; i < n; ++i)
+    if constexpr (N >= 1)
     {
-      if (myMinPoint[i] > theMaxPoint[i] || myMaxPoint[i] < theMinPoint[i])
+      if (myMinPoint[0] > theMaxPoint[0] || myMaxPoint[0] < theMinPoint[0])
+        return Standard_True;
+    }
+    if constexpr (N >= 2)
+    {
+      if (myMinPoint[1] > theMaxPoint[1] || myMaxPoint[1] < theMinPoint[1])
+        return Standard_True;
+    }
+    if constexpr (N >= 3)
+    {
+      if (myMinPoint[2] > theMaxPoint[2] || myMaxPoint[2] < theMinPoint[2])
         return Standard_True;
     }
     return Standard_False;
   }
 
   //! Checks if the Box fully contains the other box.
-  Standard_Boolean Contains(const BVH_Box<T, N>& theOther, Standard_Boolean& hasOverlap) const
+  constexpr Standard_Boolean Contains(const BVH_Box<T, N>& theOther,
+                                      Standard_Boolean&    hasOverlap) const
   {
     hasOverlap = Standard_False;
     if (!theOther.IsValid())
@@ -314,9 +324,9 @@ public:
   }
 
   //! Checks if the Box is fully contains the other box.
-  Standard_Boolean Contains(const BVH_VecNt&  theMinPoint,
-                            const BVH_VecNt&  theMaxPoint,
-                            Standard_Boolean& hasOverlap) const
+  constexpr Standard_Boolean Contains(const BVH_VecNt&  theMinPoint,
+                                      const BVH_VecNt&  theMaxPoint,
+                                      Standard_Boolean& hasOverlap) const
   {
     hasOverlap = Standard_False;
     if (!IsValid())
@@ -324,28 +334,49 @@ public:
 
     Standard_Boolean isInside = Standard_True;
 
-    int n = (std::min)(N, 3);
-    for (int i = 0; i < n; ++i)
+    if constexpr (N >= 1)
     {
-      hasOverlap = (myMinPoint[i] <= theMaxPoint[i] && myMaxPoint[i] >= theMinPoint[i]);
+      hasOverlap = (myMinPoint[0] <= theMaxPoint[0] && myMaxPoint[0] >= theMinPoint[0]);
       if (!hasOverlap)
         return Standard_False;
-
-      isInside = isInside && (myMinPoint[i] <= theMinPoint[i] && myMaxPoint[i] >= theMaxPoint[i]);
+      isInside = isInside && (myMinPoint[0] <= theMinPoint[0] && myMaxPoint[0] >= theMaxPoint[0]);
+    }
+    if constexpr (N >= 2)
+    {
+      hasOverlap = (myMinPoint[1] <= theMaxPoint[1] && myMaxPoint[1] >= theMinPoint[1]);
+      if (!hasOverlap)
+        return Standard_False;
+      isInside = isInside && (myMinPoint[1] <= theMinPoint[1] && myMaxPoint[1] >= theMaxPoint[1]);
+    }
+    if constexpr (N >= 3)
+    {
+      hasOverlap = (myMinPoint[2] <= theMaxPoint[2] && myMaxPoint[2] >= theMinPoint[2]);
+      if (!hasOverlap)
+        return Standard_False;
+      isInside = isInside && (myMinPoint[2] <= theMinPoint[2] && myMaxPoint[2] >= theMaxPoint[2]);
     }
     return isInside;
   }
 
   //! Checks if the Point is out of the box.
-  Standard_Boolean IsOut(const BVH_VecNt& thePoint) const
+  constexpr Standard_Boolean IsOut(const BVH_VecNt& thePoint) const
   {
     if (!IsValid())
       return Standard_True;
 
-    int n = (std::min)(N, 3);
-    for (int i = 0; i < n; ++i)
+    if constexpr (N >= 1)
+    {
+      if (thePoint[0] < myMinPoint[0] || thePoint[0] > myMaxPoint[0])
+        return Standard_True;
+    }
+    if constexpr (N >= 2)
+    {
+      if (thePoint[1] < myMinPoint[1] || thePoint[1] > myMaxPoint[1])
+        return Standard_True;
+    }
+    if constexpr (N >= 3)
     {
-      if (thePoint[i] < myMinPoint[i] || thePoint[i] > myMaxPoint[i])
+      if (thePoint[2] < myMinPoint[2] || thePoint[2] > myMaxPoint[2])
         return Standard_True;
     }
     return Standard_False;
@@ -371,7 +402,7 @@ struct CenterAxis
 template <class T>
 struct CenterAxis<T, 2>
 {
-  static T Center(const BVH_Box<T, 2>& theBox, const Standard_Integer theAxis)
+  static inline T Center(const BVH_Box<T, 2>& theBox, const Standard_Integer theAxis)
   {
     if (theAxis == 0)
     {
@@ -388,7 +419,7 @@ struct CenterAxis<T, 2>
 template <class T>
 struct CenterAxis<T, 3>
 {
-  static T Center(const BVH_Box<T, 3>& theBox, const Standard_Integer theAxis)
+  static inline T Center(const BVH_Box<T, 3>& theBox, const Standard_Integer theAxis)
   {
     if (theAxis == 0)
     {
@@ -409,7 +440,7 @@ struct CenterAxis<T, 3>
 template <class T>
 struct CenterAxis<T, 4>
 {
-  static T Center(const BVH_Box<T, 4>& theBox, const Standard_Integer theAxis)
+  static inline T Center(const BVH_Box<T, 4>& theBox, const Standard_Integer theAxis)
   {
     if (theAxis == 0)
     {
@@ -439,7 +470,7 @@ struct SurfaceCalculator
 template <class T>
 struct SurfaceCalculator<T, 2>
 {
-  static T Area(const typename BVH_Box<T, 2>::BVH_VecNt& theSize)
+  static inline T Area(const typename BVH_Box<T, 2>::BVH_VecNt& theSize)
   {
     const T anArea = std::abs(theSize.x() * theSize.y());
 
@@ -455,7 +486,7 @@ struct SurfaceCalculator<T, 2>
 template <class T>
 struct SurfaceCalculator<T, 3>
 {
-  static T Area(const typename BVH_Box<T, 3>::BVH_VecNt& theSize)
+  static inline T Area(const typename BVH_Box<T, 3>::BVH_VecNt& theSize)
   {
     const T anArea = (std::abs(theSize.x() * theSize.y()) + std::abs(theSize.x() * theSize.z())
                       + std::abs(theSize.z() * theSize.y()))
@@ -473,7 +504,7 @@ struct SurfaceCalculator<T, 3>
 template <class T>
 struct SurfaceCalculator<T, 4>
 {
-  static T Area(const typename BVH_Box<T, 4>::BVH_VecNt& theSize)
+  static inline T Area(const typename BVH_Box<T, 4>::BVH_VecNt& theSize)
   {
     const T anArea = (std::abs(theSize.x() * theSize.y()) + std::abs(theSize.x() * theSize.z())
                       + std::abs(theSize.z() * theSize.y()))
@@ -497,14 +528,14 @@ struct BoxMinMax
 {
   typedef typename BVH::VectorType<T, N>::Type BVH_VecNt;
 
-  static void CwiseMin(BVH_VecNt& theVec1, const BVH_VecNt& theVec2)
+  static inline void CwiseMin(BVH_VecNt& theVec1, const BVH_VecNt& theVec2)
   {
     theVec1.x() = (std::min)(theVec1.x(), theVec2.x());
     theVec1.y() = (std::min)(theVec1.y(), theVec2.y());
     theVec1.z() = (std::min)(theVec1.z(), theVec2.z());
   }
 
-  static void CwiseMax(BVH_VecNt& theVec1, const BVH_VecNt& theVec2)
+  static inline void CwiseMax(BVH_VecNt& theVec1, const BVH_VecNt& theVec2)
   {
     theVec1.x() = (std::max)(theVec1.x(), theVec2.x());
     theVec1.y() = (std::max)(theVec1.y(), theVec2.y());
@@ -517,13 +548,13 @@ struct BoxMinMax<T, 2>
 {
   typedef typename BVH::VectorType<T, 2>::Type BVH_VecNt;
 
-  static void CwiseMin(BVH_VecNt& theVec1, const BVH_VecNt& theVec2)
+  static inline void CwiseMin(BVH_VecNt& theVec1, const BVH_VecNt& theVec2)
   {
     theVec1.x() = (std::min)(theVec1.x(), theVec2.x());
     theVec1.y() = (std::min)(theVec1.y(), theVec2.y());
   }
 
-  static void CwiseMax(BVH_VecNt& theVec1, const BVH_VecNt& theVec2)
+  static inline void CwiseMax(BVH_VecNt& theVec1, const BVH_VecNt& theVec2)
   {
     theVec1.x() = (std::max)(theVec1.x(), theVec2.x());
     theVec1.y() = (std::max)(theVec1.y(), theVec2.y());
diff --git a/src/FoundationClasses/TKMath/BVH/BVH_BuildQueue.cxx b/src/FoundationClasses/TKMath/BVH/BVH_BuildQueue.cxx
deleted file mode 100644 (file)
index c985024..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-// Created on: 2015-05-27
-// Created by: Denis BOGOLEPOV
-// Copyright (c) 2015 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.
-
-#include <BVH_BuildQueue.hxx>
-
-// =======================================================================
-// function : Size
-// purpose  : Returns current size of BVH build queue
-// =======================================================================
-Standard_Integer BVH_BuildQueue::Size()
-{
-  std::lock_guard<std::mutex> aLock(myMutex);
-  return myQueue.Size();
-}
-
-// =======================================================================
-// function : Enqueue
-// purpose  : Enqueues new work-item onto BVH build queue
-// =======================================================================
-void BVH_BuildQueue::Enqueue(const Standard_Integer& theWorkItem)
-{
-  std::lock_guard<std::mutex> aLock(myMutex);
-  myQueue.Append(theWorkItem);
-}
-
-// =======================================================================
-// function : Fetch
-// purpose  : Fetches first work-item from BVH build queue
-// =======================================================================
-Standard_Integer BVH_BuildQueue::Fetch(Standard_Boolean& wasBusy)
-{
-  std::lock_guard<std::mutex> aLock(myMutex);
-
-  Standard_Integer aQuery = -1;
-  if (!myQueue.IsEmpty())
-  {
-    aQuery = myQueue.First();
-
-    myQueue.Remove(1); // remove item from queue
-  }
-
-  if (aQuery != -1)
-  {
-    if (!wasBusy)
-    {
-      ++myNbThreads;
-    }
-  }
-  else if (wasBusy)
-  {
-    --myNbThreads;
-  }
-
-  wasBusy = aQuery != -1;
-
-  return aQuery;
-}
index b83762030b59c27ebf41be109b1365b2c35769f2..4afa167108a1a7d93d9613f2f799d6fd5a855cb7 100644 (file)
@@ -20,6 +20,7 @@
 
 #include <NCollection_Sequence.hxx>
 
+#include <atomic>
 #include <mutex>
 
 //! Command-queue for parallel building of BVH nodes.
@@ -31,40 +32,82 @@ class BVH_BuildQueue
 public:
   //! Creates new BVH build queue.
   BVH_BuildQueue()
-      : myNbThreads(0)
+      : myNbThreads(0),
+        mySize(0)
   {
-    //
   }
 
   //! Releases resources of BVH build queue.
-  ~BVH_BuildQueue()
-  {
-    //
-  }
+  ~BVH_BuildQueue() = default;
 
 public:
   //! Returns current size of BVH build queue.
-  Standard_EXPORT Standard_Integer Size();
+  //! Uses acquire semantics to synchronize with enqueue/dequeue operations.
+  Standard_Integer Size() const { return mySize.load(std::memory_order_acquire); }
 
   //! Enqueues new work-item onto BVH build queue.
-  Standard_EXPORT void Enqueue(const Standard_Integer& theNode);
+  void Enqueue(const Standard_Integer theWorkItem)
+  {
+    std::lock_guard<std::mutex> aLock(myMutex);
+    myQueue.Append(theWorkItem);
+    mySize.fetch_add(1, std::memory_order_release);
+  }
 
   //! Fetches first work-item from BVH build queue.
-  Standard_EXPORT Standard_Integer Fetch(Standard_Boolean& wasBusy);
+  Standard_Integer Fetch(Standard_Boolean& wasBusy)
+  {
+    Standard_Integer aQuery = -1;
+
+    // Fetch item from queue under lock
+    {
+      std::lock_guard<std::mutex> aLock(myMutex);
+      if (!myQueue.IsEmpty())
+      {
+        aQuery = myQueue.First();
+        myQueue.Remove(1);
+        mySize.fetch_sub(1, std::memory_order_release);
+      }
+    }
+
+    // Update thread counter atomically with release/acquire semantics
+    // to ensure proper synchronization with HasBusyThreads()
+    if (aQuery != -1)
+    {
+      if (!wasBusy)
+      {
+        myNbThreads.fetch_add(1, std::memory_order_release);
+      }
+    }
+    else if (wasBusy)
+    {
+      myNbThreads.fetch_sub(1, std::memory_order_release);
+    }
+
+    wasBusy = (aQuery != -1);
+    return aQuery;
+  }
 
   //! Checks if there are active build threads.
-  Standard_Boolean HasBusyThreads() { return myNbThreads != 0; }
+  //! Uses acquire semantics to ensure visibility of thread counter updates.
+  //! This is critical for termination detection: threads check this after
+  //! finding an empty queue to determine if they should exit or wait.
+  Standard_Boolean HasBusyThreads() const
+  {
+    return myNbThreads.load(std::memory_order_acquire) != 0;
+  }
 
-protected:
+private:
   //! Queue of BVH nodes to build.
   NCollection_Sequence<Standard_Integer> myQueue;
 
-protected:
-  //! Manages access serialization of working threads.
+  //! Manages access serialization for queue operations.
   std::mutex myMutex;
 
-  //! Number of active build threads.
-  Standard_Integer myNbThreads;
+  //! Number of active build threads (atomic for lock-free reads).
+  std::atomic<Standard_Integer> myNbThreads;
+
+  //! Current queue size (atomic for lock-free reads).
+  std::atomic<Standard_Integer> mySize;
 };
 
 #endif // _BVH_BuildQueue_Header
index ca440ecd69997582f9b0c70c5c62a020edaf7395..cea52735c3e7dd6b60fa014d7fba66f8ba0abdc6 100644 (file)
@@ -42,7 +42,7 @@ enum
 namespace BVH
 {
 //! Minimum node size to split.
-const double THE_NODE_MIN_SIZE = 1e-5;
+constexpr double THE_NODE_MIN_SIZE = 1e-5;
 } // namespace BVH
 
 #endif // _BVH_Constants_Header
index 36e8826021356b370109ea69dc57009b71043387..8c289a0c38b7207d2dfcba31b6b6cb0454e1b0f6 100644 (file)
@@ -107,8 +107,8 @@ protected:
 
 protected:
   Standard_Boolean                       myIsDirty; //!< Is geometry state outdated?
-  opencascade::handle<BVH_Tree<T, N>>    myBVH;     //!< Constructed hight-level BVH
-  opencascade::handle<BVH_Builder<T, N>> myBuilder; //!< Builder for hight-level BVH
+  opencascade::handle<BVH_Tree<T, N>>    myBVH;     //!< Constructed high-level BVH
+  opencascade::handle<BVH_Builder<T, N>> myBuilder; //!< Builder for high-level BVH
 
   mutable BVH_Box<T, N> myBox; //!< Cached bounding box of geometric objects
 };
index 1777830c67800d5dad52795c32ea7ea87e3a36d0..192294ab9ad012e1968e18ba2c756de5d7263ba7 100644 (file)
@@ -17,6 +17,7 @@
 #define _BVH_LinearBuilder_Header
 
 #include <BVH_RadixSorter.hxx>
+#include <NCollection_Vector.hxx>
 #include <Standard_Assert.hxx>
 
 //! Performs fast BVH construction using LBVH building approach.
@@ -246,8 +247,7 @@ public:
       const Standard_Integer aLftChild = theData.myBVH->NodeInfoBuffer()[theData.myNode].y();
       const Standard_Integer aRghChild = theData.myBVH->NodeInfoBuffer()[theData.myNode].z();
 
-      std::vector<BoundData<T, N>> aList;
-      aList.reserve(2);
+      NCollection_Vector<BoundData<T, N>> aList(2);
       if (!theData.myBVH->IsOuter(aLftChild))
       {
         BoundData<T, N> aBoundData = {theData.mySet,
@@ -255,7 +255,7 @@ public:
                                       aLftChild,
                                       theData.myLevel + 1,
                                       &aLftHeight};
-        aList.push_back(aBoundData);
+        aList.Append(aBoundData);
       }
       else
       {
@@ -269,14 +269,14 @@ public:
                                       aRghChild,
                                       theData.myLevel + 1,
                                       &aRghHeight};
-        aList.push_back(aBoundData);
+        aList.Append(aBoundData);
       }
       else
       {
         aRghHeight = BVH::UpdateBounds(theData.mySet, theData.myBVH, aRghChild);
       }
 
-      if (!aList.empty())
+      if (aList.Size() > 0)
       {
         OSD_Parallel::ForEach(aList.begin(),
                               aList.end(),
index cba39c554092c1a624b1bb1a74e05ba0d2f1a86b..25f95658773e604c416c885ebd5c30b3bd671fad 100644 (file)
@@ -48,7 +48,7 @@ public:
   }
 
   //! Releases resources of transformation properties.
-  virtual ~BVH_Transform() {}
+  virtual ~BVH_Transform() = default;
 
   //! Returns transformation matrix.
   const BVH_MatNt& Transform() const { return myTransform; }
index dbd3cea4df5db727c216fa9ae9f08945b4792bed..5b71018970c67671bb1dd278d1294b0059246a1a 100644 (file)
@@ -46,7 +46,7 @@ public:
   }
 
   //! Releases resources of BVH queue based builder.
-  virtual ~BVH_QueueBuilder() {}
+  virtual ~BVH_QueueBuilder() = default;
 
 public:
   //! Builds BVH using specific algorithm.
index bdf428db1b3f792f628828d9ba1771c88d13a12b..4311089bfe9c6d20c4a39294c46b0b292ce79165 100644 (file)
 #define _BVH_QuickSorter_Header
 
 #include <BVH_Sorter.hxx>
+#include <NCollection_Allocator.hxx>
+
+#include <algorithm>
+#include <vector>
 
 //! Performs centroid-based sorting of abstract set along
-//! the given axis (X - 0, Y - 1, Z - 2) using quick sort.
+//! the given axis (X - 0, Y - 1, Z - 2) using std::sort.
+//! Uses introsort algorithm which guarantees O(n log n) complexity.
 template <class T, int N>
 class BVH_QuickSorter : public BVH_Sorter<T, N>
 {
@@ -41,41 +46,44 @@ public:
                        const Standard_Integer theStart,
                        const Standard_Integer theFinal) Standard_OVERRIDE
   {
-    Standard_Integer aLft = theStart;
-    Standard_Integer aRgh = theFinal;
-
-    T aPivot = theSet->Center((aRgh + aLft) / 2, myAxis);
-    while (aLft < aRgh)
+    const Standard_Integer aSize = theFinal - theStart + 1;
+    if (aSize <= 1)
     {
-      while (theSet->Center(aLft, myAxis) < aPivot && aLft < theFinal)
-      {
-        ++aLft;
-      }
-
-      while (theSet->Center(aRgh, myAxis) > aPivot && aRgh > theStart)
-      {
-        --aRgh;
-      }
+      return;
+    }
 
-      if (aLft <= aRgh)
-      {
-        if (aLft != aRgh)
-        {
-          theSet->Swap(aLft, aRgh);
-        }
-        ++aLft;
-        --aRgh;
-      }
+    // Create index array for sorting with OCCT allocator
+    std::vector<Standard_Integer, NCollection_Allocator<Standard_Integer>> anIndices(aSize);
+    for (Standard_Integer i = 0; i < aSize; ++i)
+    {
+      anIndices[i] = i;
     }
 
-    if (aRgh > theStart)
+    // Sort indices by center value using std::sort (introsort - O(n log n) guaranteed)
+    const Standard_Integer anAxis = myAxis;
+    std::sort(anIndices.begin(),
+              anIndices.end(),
+              [theSet, theStart, anAxis](Standard_Integer a, Standard_Integer b) {
+                return theSet->Center(theStart + a, anAxis) < theSet->Center(theStart + b, anAxis);
+              });
+
+    // Compute inverse permutation: invPerm[i] = where element i should go
+    std::vector<Standard_Integer, NCollection_Allocator<Standard_Integer>> anInvPerm(aSize);
+    for (Standard_Integer i = 0; i < aSize; ++i)
     {
-      Perform(theSet, theStart, aRgh);
+      anInvPerm[anIndices[i]] = i;
     }
 
-    if (aLft < theFinal)
+    // Apply permutation using cycle-based algorithm - O(n) swaps total
+    for (Standard_Integer i = 0; i < aSize; ++i)
     {
-      Perform(theSet, aLft, theFinal);
+      // Follow the cycle starting at position i
+      while (anInvPerm[i] != i)
+      {
+        Standard_Integer j = anInvPerm[i];
+        theSet->Swap(theStart + i, theStart + j);
+        std::swap(anInvPerm[i], anInvPerm[j]);
+      }
     }
   }
 
index 6d4f63572f754b240a97c1370ba0d5ccc4e18f17..829dab3e6618b45ed0d820b6d5c7a678e035dfd3 100644 (file)
 //! Pair of Morton code and primitive ID.
 typedef std::pair<unsigned int, Standard_Integer> BVH_EncodedLink;
 
+namespace BVH
+{
+//! Lookup table for expanding 8-bit value to 24-bit Morton code component.
+//! Each bit is spread to every 3rd position for interleaving with other components.
+constexpr unsigned int THE_MORTON_LUT[256] = {
+  0x000000, 0x000001, 0x000008, 0x000009, 0x000040, 0x000041, 0x000048, 0x000049, 0x000200,
+  0x000201, 0x000208, 0x000209, 0x000240, 0x000241, 0x000248, 0x000249, 0x001000, 0x001001,
+  0x001008, 0x001009, 0x001040, 0x001041, 0x001048, 0x001049, 0x001200, 0x001201, 0x001208,
+  0x001209, 0x001240, 0x001241, 0x001248, 0x001249, 0x008000, 0x008001, 0x008008, 0x008009,
+  0x008040, 0x008041, 0x008048, 0x008049, 0x008200, 0x008201, 0x008208, 0x008209, 0x008240,
+  0x008241, 0x008248, 0x008249, 0x009000, 0x009001, 0x009008, 0x009009, 0x009040, 0x009041,
+  0x009048, 0x009049, 0x009200, 0x009201, 0x009208, 0x009209, 0x009240, 0x009241, 0x009248,
+  0x009249, 0x040000, 0x040001, 0x040008, 0x040009, 0x040040, 0x040041, 0x040048, 0x040049,
+  0x040200, 0x040201, 0x040208, 0x040209, 0x040240, 0x040241, 0x040248, 0x040249, 0x041000,
+  0x041001, 0x041008, 0x041009, 0x041040, 0x041041, 0x041048, 0x041049, 0x041200, 0x041201,
+  0x041208, 0x041209, 0x041240, 0x041241, 0x041248, 0x041249, 0x048000, 0x048001, 0x048008,
+  0x048009, 0x048040, 0x048041, 0x048048, 0x048049, 0x048200, 0x048201, 0x048208, 0x048209,
+  0x048240, 0x048241, 0x048248, 0x048249, 0x049000, 0x049001, 0x049008, 0x049009, 0x049040,
+  0x049041, 0x049048, 0x049049, 0x049200, 0x049201, 0x049208, 0x049209, 0x049240, 0x049241,
+  0x049248, 0x049249, 0x200000, 0x200001, 0x200008, 0x200009, 0x200040, 0x200041, 0x200048,
+  0x200049, 0x200200, 0x200201, 0x200208, 0x200209, 0x200240, 0x200241, 0x200248, 0x200249,
+  0x201000, 0x201001, 0x201008, 0x201009, 0x201040, 0x201041, 0x201048, 0x201049, 0x201200,
+  0x201201, 0x201208, 0x201209, 0x201240, 0x201241, 0x201248, 0x201249, 0x208000, 0x208001,
+  0x208008, 0x208009, 0x208040, 0x208041, 0x208048, 0x208049, 0x208200, 0x208201, 0x208208,
+  0x208209, 0x208240, 0x208241, 0x208248, 0x208249, 0x209000, 0x209001, 0x209008, 0x209009,
+  0x209040, 0x209041, 0x209048, 0x209049, 0x209200, 0x209201, 0x209208, 0x209209, 0x209240,
+  0x209241, 0x209248, 0x209249, 0x240000, 0x240001, 0x240008, 0x240009, 0x240040, 0x240041,
+  0x240048, 0x240049, 0x240200, 0x240201, 0x240208, 0x240209, 0x240240, 0x240241, 0x240248,
+  0x240249, 0x241000, 0x241001, 0x241008, 0x241009, 0x241040, 0x241041, 0x241048, 0x241049,
+  0x241200, 0x241201, 0x241208, 0x241209, 0x241240, 0x241241, 0x241248, 0x241249, 0x248000,
+  0x248001, 0x248008, 0x248009, 0x248040, 0x248041, 0x248048, 0x248049, 0x248200, 0x248201,
+  0x248208, 0x248209, 0x248240, 0x248241, 0x248248, 0x248249, 0x249000, 0x249001, 0x249008,
+  0x249009, 0x249040, 0x249041, 0x249048, 0x249049, 0x249200, 0x249201, 0x249208, 0x249209,
+  0x249240, 0x249241, 0x249248, 0x249249};
+
+//! Encodes 10-bit voxel coordinates into 30-bit Morton code using LUT.
+//! @param theVoxelX X coordinate (0-1023)
+//! @param theVoxelY Y coordinate (0-1023)
+//! @param theVoxelZ Z coordinate (0-1023)
+//! @return 30-bit Morton code with interleaved bits
+constexpr unsigned int EncodeMortonCode(unsigned int theVoxelX,
+                                        unsigned int theVoxelY,
+                                        unsigned int theVoxelZ)
+{
+  // Split each 10-bit coordinate into two 8-bit lookups (upper 2 bits + lower 8 bits)
+  // For 10-bit values, we use lower 8 bits via LUT and handle upper 2 bits separately
+  return (THE_MORTON_LUT[theVoxelX & 0xFF] | (THE_MORTON_LUT[(theVoxelX >> 8) & 0x03] << 24))
+         | ((THE_MORTON_LUT[theVoxelY & 0xFF] | (THE_MORTON_LUT[(theVoxelY >> 8) & 0x03] << 24))
+            << 1)
+         | ((THE_MORTON_LUT[theVoxelZ & 0xFF] | (THE_MORTON_LUT[(theVoxelZ >> 8) & 0x03] << 24))
+            << 2);
+}
+} // namespace BVH
+
 //! Performs radix sort of a BVH primitive set using
 //! 10-bit Morton codes (or 1024 x 1024 x 1024 grid).
 template <class T, int N>
@@ -200,27 +254,26 @@ void BVH_RadixSorter<T, N>::Perform(BVH_Set<T, N>*         theSet,
 
   myEncodedLinks = new NCollection_Shared<NCollection_Array1<BVH_EncodedLink>>(theStart, theFinal);
 
-  // Step 1 -- Assign Morton code to each primitive
+  // Step 1 -- Assign Morton code to each primitive using LUT for faster encoding
   for (Standard_Integer aPrimIdx = theStart; aPrimIdx <= theFinal; ++aPrimIdx)
   {
     const BVH_VecNt aCenter = theSet->Box(aPrimIdx).Center();
     const BVH_VecNt aVoxelF = (aCenter - aSceneMin) * aReverseSize;
 
-    unsigned int aMortonCode = 0;
-    for (Standard_Integer aCompIter = 0; aCompIter < aNbEffComp; ++aCompIter)
-    {
-      const Standard_Integer aVoxelI = BVH::IntFloor(BVH::VecComp<T, N>::Get(aVoxelF, aCompIter));
-
-      unsigned int aVoxel =
-        static_cast<unsigned int>((std::max)(0, (std::min)(aVoxelI, aDimension - 1)));
-
-      aVoxel = (aVoxel | (aVoxel << 16)) & 0x030000FF;
-      aVoxel = (aVoxel | (aVoxel << 8)) & 0x0300F00F;
-      aVoxel = (aVoxel | (aVoxel << 4)) & 0x030C30C3;
-      aVoxel = (aVoxel | (aVoxel << 2)) & 0x09249249;
-
-      aMortonCode |= (aVoxel << aCompIter);
-    }
+    // Compute voxel coordinates clamped to valid range
+    const Standard_Integer aVoxelX =
+      std::clamp(BVH::IntFloor(BVH::VecComp<T, N>::Get(aVoxelF, 0)), 0, aDimension - 1);
+    const Standard_Integer aVoxelY =
+      std::clamp(BVH::IntFloor(BVH::VecComp<T, N>::Get(aVoxelF, 1)), 0, aDimension - 1);
+    const Standard_Integer aVoxelZ =
+      (aNbEffComp > 2)
+        ? std::clamp(BVH::IntFloor(BVH::VecComp<T, N>::Get(aVoxelF, 2)), 0, aDimension - 1)
+        : 0;
+
+    // Use LUT-based Morton code encoding for better performance
+    const unsigned int aMortonCode = BVH::EncodeMortonCode(static_cast<unsigned int>(aVoxelX),
+                                                           static_cast<unsigned int>(aVoxelY),
+                                                           static_cast<unsigned int>(aVoxelZ));
 
     myEncodedLinks->ChangeValue(aPrimIdx) = BVH_EncodedLink(aMortonCode, aPrimIdx);
   }
index 02b6ae156797ae61298ed92a817d5c4011b6b3f8..ce418f55b77ef95cd5f9b969216e37ddffd68b30 100644 (file)
@@ -16,6 +16,8 @@
 #ifndef _BVH_Ray_Header
 #define _BVH_Ray_Header
 
+#include <BVH_Types.hxx>
+
 //! Describes a ray based on BVH vectors.
 template <class T, int N>
 class BVH_Ray
@@ -24,15 +26,23 @@ public:
   typedef typename BVH::VectorType<T, N>::Type BVH_VecNt;
 
 public:
-  BVH_VecNt Origin;
-  BVH_VecNt Direct;
+  BVH_VecNt Origin; //!< Ray origin point
+  BVH_VecNt Direct; //!< Ray direction vector
 
 public:
-  BVH_Ray(const BVH_VecNt& theOrigin, const BVH_VecNt& theDirect)
+  //! Creates ray with given origin and direction.
+  constexpr BVH_Ray(const BVH_VecNt& theOrigin, const BVH_VecNt& theDirect) noexcept
       : Origin(theOrigin),
         Direct(theDirect)
   {
   }
+
+  //! Default constructor (creates invalid ray at origin).
+  constexpr BVH_Ray() noexcept
+      : Origin(BVH_VecNt()),
+        Direct(BVH_VecNt())
+  {
+  }
 };
 
 #endif // _BVH_Ray_Header
index e5dcd469baf3a78b6b21d37ccec35f8f98fa0a48..52ae3f07c2195839b468d8b92ab96b0050171208 100644 (file)
@@ -34,7 +34,7 @@ public:
   }
 
   //! Releases resources of spatial median split builder.
-  virtual ~BVH_SpatialMedianBuilder() {}
+  virtual ~BVH_SpatialMedianBuilder() = default;
 };
 
 #endif // _BVH_SpatialMedianBuilder_Header
index 9e030ab6b1c120e3813bfbf63df055bb87e8d9ff..e74c23ff1a65fe17cf3663a94a6865d7285d2eab 100644 (file)
@@ -46,7 +46,7 @@ protected:
     const Standard_Integer aNodeBegPrimitive = theBVH->BegPrimitive(theNode);
     const Standard_Integer aNodeEndPrimitive = theBVH->EndPrimitive(theNode);
     const Standard_Integer aNodeNbPrimitives = theBVH->NbPrimitives(theNode);
-    if (aNodeEndPrimitive - aNodeBegPrimitive < BVH_Builder<T, N>::myLeafNodeSize)
+    if (aNodeNbPrimitives <= BVH_Builder<T, N>::myLeafNodeSize)
     {
       // clang-format off
       return typename BVH_QueueBuilder<T, N>::BVH_ChildNodes(); // node does not require partitioning
@@ -57,8 +57,8 @@ protected:
     Standard_Integer aMinSplitAxis  = -1;
     Standard_Integer aMinSplitIndex = 0;
 
-    NCollection_Array1<Standard_Real> aLftSet(0, aNodeNbPrimitives - 1);
-    NCollection_Array1<Standard_Real> aRghSet(0, aNodeNbPrimitives - 1);
+    NCollection_Array1<Standard_Real> aLftSet(1, aNodeNbPrimitives - 1);
+    NCollection_Array1<Standard_Real> aRghSet(1, aNodeNbPrimitives - 1);
     Standard_Real                     aMinSplitCost = std::numeric_limits<Standard_Real>::max();
 
     // Find best split
@@ -74,8 +74,6 @@ protected:
       BVH_QuickSorter<T, N>(anAxis).Perform(theSet, aNodeBegPrimitive, aNodeEndPrimitive);
       BVH_Box<T, N> aLftBox;
       BVH_Box<T, N> aRghBox;
-      aLftSet.ChangeFirst() = std::numeric_limits<T>::max();
-      aRghSet.ChangeFirst() = std::numeric_limits<T>::max();
 
       // Sweep from left
       for (Standard_Integer anIndex = 1; anIndex < aNodeNbPrimitives; ++anIndex)
index 54d4bc0e26b5e5538ee6dae4adeb5c0b606bdade..eef6e2f4d3a43b05caf4ceba5a29a46fed87e502 100644 (file)
@@ -57,20 +57,18 @@ public: //! @name Box-Box Square distance
                                 const BVH_VecNt& theCMin2,
                                 const BVH_VecNt& theCMax2)
   {
-    T aDist = 0;
+    T aDist = T(0);
     for (int i = 0; i < N; ++i)
     {
       if (theCMin1[i] > theCMax2[i])
       {
         T d = theCMin1[i] - theCMax2[i];
-        d *= d;
-        aDist += d;
+        aDist += d * d;
       }
       else if (theCMax1[i] < theCMin2[i])
       {
         T d = theCMin2[i] - theCMax1[i];
-        d *= d;
-        aDist += d;
+        aDist += d * d;
       }
     }
     return aDist;
@@ -92,20 +90,18 @@ public: //! @name Point-Box Square distance
                                   const BVH_VecNt& theCMin,
                                   const BVH_VecNt& theCMax)
   {
-    T aDist = 0;
+    T aDist = T(0);
     for (int i = 0; i < N; ++i)
     {
       if (thePoint[i] < theCMin[i])
       {
         T d = theCMin[i] - thePoint[i];
-        d *= d;
-        aDist += d;
+        aDist += d * d;
       }
       else if (thePoint[i] > theCMax[i])
       {
         T d = thePoint[i] - theCMax[i];
-        d *= d;
-        aDist += d;
+        aDist += d * d;
       }
     }
     return aDist;
@@ -130,8 +126,49 @@ public: //! @name Point-Box projection
     return thePoint.cwiseMax(theCMin).cwiseMin(theCMax);
   }
 
+private: //! @name Internal helpers for point-triangle projection
+  //! Helper to set projection state for vertex
+  static void SetVertexState(BVH_PrjStateInTriangle* thePrjState,
+                             Standard_Integer*       theFirstNode,
+                             Standard_Integer*       theLastNode,
+                             Standard_Integer        theVertexIndex)
+  {
+    if (thePrjState != nullptr)
+    {
+      *thePrjState  = BVH_PrjStateInTriangle_VERTEX;
+      *theFirstNode = theVertexIndex;
+      *theLastNode  = theVertexIndex;
+    }
+  }
+
+  //! Helper to set projection state for edge
+  static void SetEdgeState(BVH_PrjStateInTriangle* thePrjState,
+                           Standard_Integer*       theFirstNode,
+                           Standard_Integer*       theLastNode,
+                           Standard_Integer        theStartVertex,
+                           Standard_Integer        theEndVertex)
+  {
+    if (thePrjState != nullptr)
+    {
+      *thePrjState  = BVH_PrjStateInTriangle_EDGE;
+      *theFirstNode = theStartVertex;
+      *theLastNode  = theEndVertex;
+    }
+  }
+
+  //! Helper to compute projection onto edge
+  static BVH_VecNt ProjectToEdge(const BVH_VecNt& theEdgeStart,
+                                 const BVH_VecNt& theEdge,
+                                 T                theDot1,
+                                 T                theDot2)
+  {
+    T aT = theDot1 / (theDot1 + theDot2);
+    return theEdgeStart + theEdge * aT;
+  }
+
 public: //! @name Point-Triangle Square distance
-  //! Find nearest point on a triangle for the given point
+  //! Find nearest point on a triangle for the given point.
+  //! Uses Voronoi region testing to determine closest feature (vertex, edge, or interior).
   static BVH_VecNt PointTriangleProjection(const BVH_VecNt&        thePoint,
                                            const BVH_VecNt&        theNode0,
                                            const BVH_VecNt&        theNode1,
@@ -140,101 +177,87 @@ public: //! @name Point-Triangle Square distance
                                            Standard_Integer*       theNumberOfFirstNode = nullptr,
                                            Standard_Integer*       theNumberOfLastNode  = nullptr)
   {
+    // Compute edge vectors
     const BVH_VecNt aAB = theNode1 - theNode0;
     const BVH_VecNt aAC = theNode2 - theNode0;
+    const BVH_VecNt aBC = theNode2 - theNode1;
+
+    // Compute point-to-vertex vectors
     const BVH_VecNt aAP = thePoint - theNode0;
+    const BVH_VecNt aBP = thePoint - theNode1;
+    const BVH_VecNt aCP = thePoint - theNode2;
 
-    T aABdotAP = aAB.Dot(aAP);
-    T aACdotAP = aAC.Dot(aAP);
+    // Compute dot products for Voronoi region tests
+    const T aABdotAP = aAB.Dot(aAP);
+    const T aACdotAP = aAC.Dot(aAP);
 
-    if (aABdotAP <= 0. && aACdotAP <= 0.)
+    // Check if P is in vertex region outside A
+    if (aABdotAP <= T(0) && aACdotAP <= T(0))
     {
-      if (thePrjState != nullptr)
-      {
-        *thePrjState          = BVH_PrjStateInTriangle_VERTEX;
-        *theNumberOfFirstNode = 0;
-        *theNumberOfLastNode  = 0;
-      }
+      SetVertexState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 0);
       return theNode0;
     }
 
-    const BVH_VecNt aBC = theNode2 - theNode1;
-    const BVH_VecNt aBP = thePoint - theNode1;
-
-    T aBAdotBP = -(aAB.Dot(aBP));
-    T aBCdotBP = (aBC.Dot(aBP));
+    const T aBAdotBP = -aAB.Dot(aBP);
+    const T aBCdotBP = aBC.Dot(aBP);
 
-    if (aBAdotBP <= 0. && aBCdotBP <= 0.)
+    // Check if P is in vertex region outside B
+    if (aBAdotBP <= T(0) && aBCdotBP <= T(0))
     {
-      if (thePrjState != nullptr)
-      {
-        *thePrjState          = BVH_PrjStateInTriangle_VERTEX;
-        *theNumberOfFirstNode = 1;
-        *theNumberOfLastNode  = 1;
-      }
+      SetVertexState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 1);
       return theNode1;
     }
 
-    const BVH_VecNt aCP = thePoint - theNode2;
-
-    T aCBdotCP = -(aBC.Dot(aCP));
-    T aCAdotCP = -(aAC.Dot(aCP));
+    const T aCBdotCP = -aBC.Dot(aCP);
+    const T aCAdotCP = -aAC.Dot(aCP);
 
-    if (aCAdotCP <= 0. && aCBdotCP <= 0.)
+    // Check if P is in vertex region outside C
+    if (aCAdotCP <= T(0) && aCBdotCP <= T(0))
     {
-      if (thePrjState != nullptr)
-      {
-        *thePrjState          = BVH_PrjStateInTriangle_VERTEX;
-        *theNumberOfFirstNode = 2;
-        *theNumberOfLastNode  = 2;
-      }
+      SetVertexState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 2);
       return theNode2;
     }
 
-    T aACdotBP = (aAC.Dot(aBP));
-
-    T aVC = aABdotAP * aACdotBP + aBAdotBP * aACdotAP;
+    // Compute barycentric coordinates for edge/interior tests
+    const T aACdotBP = aAC.Dot(aBP);
+    const T aVC      = aABdotAP * aACdotBP + aBAdotBP * aACdotAP;
 
-    if (aVC <= 0. && aABdotAP > 0. && aBAdotBP > 0.)
+    // Check if P is in edge region of AB
+    if (aVC <= T(0) && aABdotAP > T(0) && aBAdotBP > T(0))
     {
-      if (thePrjState != nullptr)
-      {
-        *thePrjState          = BVH_PrjStateInTriangle_EDGE;
-        *theNumberOfFirstNode = 0;
-        *theNumberOfLastNode  = 1;
-      }
-      return theNode0 + aAB * (aABdotAP / (aABdotAP + aBAdotBP));
+      SetEdgeState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 0, 1);
+      return ProjectToEdge(theNode0, aAB, aABdotAP, aBAdotBP);
     }
 
-    T aABdotCP = (aAB.Dot(aCP));
-
-    T aVA = aBAdotBP * aCAdotCP - aABdotCP * aACdotBP;
+    const T aABdotCP = aAB.Dot(aCP);
+    const T aVA      = aBAdotBP * aCAdotCP - aABdotCP * aACdotBP;
 
-    if (aVA <= 0. && aBCdotBP > 0. && aCBdotCP > 0.)
+    // Check if P is in edge region of BC
+    if (aVA <= T(0) && aBCdotBP > T(0) && aCBdotCP > T(0))
     {
-      if (thePrjState != nullptr)
-      {
-        *thePrjState          = BVH_PrjStateInTriangle_EDGE;
-        *theNumberOfFirstNode = 1;
-        *theNumberOfLastNode  = 2;
-      }
-      return theNode1 + aBC * (aBCdotBP / (aBCdotBP + aCBdotCP));
+      SetEdgeState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 1, 2);
+      return ProjectToEdge(theNode1, aBC, aBCdotBP, aCBdotCP);
     }
 
-    T aVB = aABdotCP * aACdotAP + aABdotAP * aCAdotCP;
+    const T aVB = aABdotCP * aACdotAP + aABdotAP * aCAdotCP;
 
-    if (aVB <= 0. && aACdotAP > 0. && aCAdotCP > 0.)
+    // Check if P is in edge region of CA
+    if (aVB <= T(0) && aACdotAP > T(0) && aCAdotCP > T(0))
     {
-      if (thePrjState != nullptr)
-      {
-        *thePrjState          = BVH_PrjStateInTriangle_EDGE;
-        *theNumberOfFirstNode = 2;
-        *theNumberOfLastNode  = 0;
-      }
-      return theNode0 + aAC * (aACdotAP / (aACdotAP + aCAdotCP));
+      SetEdgeState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 2, 0);
+      return ProjectToEdge(theNode0, aAC, aACdotAP, aCAdotCP);
     }
 
-    T aNorm = aVA + aVB + aVC;
+    // P is inside triangle - compute barycentric coordinates
+    const T aNorm = aVA + aVB + aVC;
+
+    // Handle degenerate triangle (zero or near-zero area)
+    if (aNorm
+        <= std::numeric_limits<T>::epsilon() * (std::abs(aVA) + std::abs(aVB) + std::abs(aVC)))
+    {
+      SetVertexState(thePrjState, theNumberOfFirstNode, theNumberOfLastNode, 0);
+      return (theNode0 + theNode1 + theNode2) / T(3);
+    }
 
     if (thePrjState != nullptr)
     {
@@ -273,7 +296,7 @@ public: //! @name Ray-Box Intersection
                               theTimeLeave);
   }
 
-  //! Computes hit time of ray-box intersection
+  //! Computes hit time of ray-box intersection.
   static Standard_Boolean RayBoxIntersection(const BVH_Ray<T, N>& theRay,
                                              const BVH_VecNt&     theBoxCMin,
                                              const BVH_VecNt&     theBoxCMax,
@@ -307,7 +330,8 @@ public: //! @name Ray-Box Intersection
                               theTimeLeave);
   }
 
-  //! Computes hit time of ray-box intersection
+  //! Computes hit time of ray-box intersection.
+  //! Uses optimized single-pass algorithm with early exit.
   static Standard_Boolean RayBoxIntersection(const BVH_VecNt& theRayOrigin,
                                              const BVH_VecNt& theRayDirection,
                                              const BVH_VecNt& theBoxCMin,
@@ -315,41 +339,50 @@ public: //! @name Ray-Box Intersection
                                              T&               theTimeEnter,
                                              T&               theTimeLeave)
   {
-    BVH_VecNt aNodeMin, aNodeMax;
+    T aTimeEnter = (std::numeric_limits<T>::lowest)();
+    T aTimeLeave = (std::numeric_limits<T>::max)();
+
     for (int i = 0; i < N; ++i)
     {
-      if (theRayDirection[i] == 0)
+      if (theRayDirection[i] == T(0))
       {
-        aNodeMin[i] = (theBoxCMin[i] - theRayOrigin[i]) <= 0 ? (std::numeric_limits<T>::min)()
-                                                             : (std::numeric_limits<T>::max)();
-        aNodeMax[i] = (theBoxCMax[i] - theRayOrigin[i]) < 0 ? (std::numeric_limits<T>::min)()
-                                                            : (std::numeric_limits<T>::max)();
+        // Ray is parallel to this axis slab - check if origin is within bounds
+        if (theRayOrigin[i] < theBoxCMin[i] || theRayOrigin[i] > theBoxCMax[i])
+        {
+          return Standard_False; // Ray misses the slab entirely
+        }
+        // Ray is within the slab, doesn't constrain the intersection interval
+        continue;
       }
-      else
+
+      // Compute intersection distances for this axis
+      T aT1 = (theBoxCMin[i] - theRayOrigin[i]) / theRayDirection[i];
+      T aT2 = (theBoxCMax[i] - theRayOrigin[i]) / theRayDirection[i];
+
+      // Ensure aT1 <= aT2 (handle negative direction)
+      T aTMin = (std::min)(aT1, aT2);
+      T aTMax = (std::max)(aT1, aT2);
+
+      // Update intersection interval
+      aTimeEnter = (std::max)(aTimeEnter, aTMin);
+      aTimeLeave = (std::min)(aTimeLeave, aTMax);
+
+      // Early exit if no intersection
+      if (aTimeEnter > aTimeLeave)
       {
-        aNodeMin[i] = (theBoxCMin[i] - theRayOrigin[i]) / theRayDirection[i];
-        aNodeMax[i] = (theBoxCMax[i] - theRayOrigin[i]) / theRayDirection[i];
+        return Standard_False;
       }
     }
 
-    BVH_VecNt aTimeMin, aTimeMax;
-    for (int i = 0; i < N; ++i)
-    {
-      aTimeMin[i] = (std::min)(aNodeMin[i], aNodeMax[i]);
-      aTimeMax[i] = (std::max)(aNodeMin[i], aNodeMax[i]);
-    }
-
-    T aTimeEnter = (std::max)(aTimeMin[0], (std::max)(aTimeMin[1], aTimeMin[2]));
-    T aTimeLeave = (std::min)(aTimeMax[0], (std::min)(aTimeMax[1], aTimeMax[2]));
-
-    Standard_Boolean hasIntersection = aTimeEnter <= aTimeLeave && aTimeLeave >= 0;
-    if (hasIntersection)
+    // Check if intersection is behind the ray origin
+    if (aTimeLeave < T(0))
     {
-      theTimeEnter = aTimeEnter;
-      theTimeLeave = aTimeLeave;
+      return Standard_False;
     }
 
-    return hasIntersection;
+    theTimeEnter = aTimeEnter;
+    theTimeLeave = aTimeLeave;
+    return Standard_True;
   }
 };
 
index edcbd6bff01bab9e61f4a30ed5814f55bc6a370e..0a6d706c4760d11c9123e84c61ba822ead27c9be 100644 (file)
@@ -238,6 +238,23 @@ public: //! @name Selection
   //! Returns the number of accepted elements.
   Standard_Integer Select(const opencascade::handle<BVH_Tree<NumType, Dimension>>& theBVH);
 
+protected: //! @name Internal structures
+  //! Auxiliary structure for keeping the nodes to process
+  struct BVH_NodeInStack
+  {
+    //! Constructor
+    constexpr BVH_NodeInStack(const Standard_Integer theNodeID = 0,
+                              const MetricType&      theMetric = MetricType()) noexcept
+        : NodeID(theNodeID),
+          Metric(theMetric)
+    {
+    }
+
+    // Fields
+    Standard_Integer NodeID; //!< Id of the node in the BVH tree
+    MetricType       Metric; //!< Metric computed for the node
+  };
+
 protected: //! @name Fields
   BVHSetType* myBVHSet;
 };
@@ -311,6 +328,26 @@ public: //! @name Selection
   Standard_Integer Select(const opencascade::handle<BVH_Tree<NumType, Dimension>>& theBVH1,
                           const opencascade::handle<BVH_Tree<NumType, Dimension>>& theBVH2);
 
+protected: //! @name Internal structures
+  //! Auxiliary structure for keeping the pair of nodes to process
+  struct BVH_PairNodesInStack
+  {
+    //! Constructor
+    constexpr BVH_PairNodesInStack(const Standard_Integer theNodeID1 = 0,
+                                   const Standard_Integer theNodeID2 = 0,
+                                   const MetricType&      theMetric  = MetricType()) noexcept
+        : NodeID1(theNodeID1),
+          NodeID2(theNodeID2),
+          Metric(theMetric)
+    {
+    }
+
+    // Fields
+    Standard_Integer NodeID1; //!< Id of the node in the first BVH tree
+    Standard_Integer NodeID2; //!< Id of the node in the second BVH tree
+    MetricType       Metric;  //!< Metric computed for the pair of nodes
+  };
+
 protected: //! @name Fields
   BVHSetType* myBVHSet1;
   BVHSetType* myBVHSet2;
index ba79046b6bbe750acb63b99f26c0791834b1c6fb..6eb3649853bea5bfadb7c9949b1f124997f14561 100644 (file)
 // Alternatively, this file may be used under the terms of Open CASCADE
 // commercial license or contractual agreement.
 
-namespace
-{
-//! Auxiliary structure for keeping the nodes to process
-template <class MetricType>
-struct BVH_NodeInStack
-{
-  //! Constructor
-  BVH_NodeInStack(const Standard_Integer theNodeID = 0, const MetricType& theMetric = MetricType())
-      : NodeID(theNodeID),
-        Metric(theMetric)
-  {
-  }
-
-  // Fields
-  Standard_Integer NodeID; //!< Id of the node in the BVH tree
-  MetricType       Metric; //!< Metric computed for the node
-};
-} // namespace
+#include <Standard_Assert.hxx>
 
 //=================================================================================================
 
@@ -41,23 +24,24 @@ Standard_Integer BVH_Traverse<NumType, Dimension, BVHSetType, MetricType>::Selec
   if (theBVH.IsNull())
     return 0;
 
-  if (theBVH->NodeInfoBuffer().empty())
+  const BVH_Array4i& aBVHNodes = theBVH->NodeInfoBuffer();
+  if (aBVHNodes.empty())
     return 0;
 
   // Create stack
-  BVH_NodeInStack<MetricType> aStack[BVH_Constants_MaxTreeDepth];
+  BVH_NodeInStack aStack[BVH_Constants_MaxTreeDepth];
 
   // clang-format off
-  BVH_NodeInStack<MetricType> aNode (0);         // Currently processed node, starting with the root node
+  BVH_NodeInStack aNode (0);         // Currently processed node, starting with the root node
   // clang-format on
-  BVH_NodeInStack<MetricType> aPrevNode = aNode; // Previously processed node
+  BVH_NodeInStack aPrevNode = aNode; // Previously processed node
 
   Standard_Integer aHead       = -1; // End of the stack
   Standard_Integer aNbAccepted = 0;  // Counter for accepted elements
 
   for (;;)
   {
-    const BVH_Vec4i& aData = theBVH->NodeInfoBuffer()[aNode.NodeID];
+    const BVH_Vec4i& aData = aBVHNodes[aNode.NodeID];
 
     if (aData.x() == 0)
     {
@@ -85,29 +69,32 @@ Standard_Integer BVH_Traverse<NumType, Dimension, BVHSetType, MetricType>::Selec
         {
           // Chose the branch with the best metric to be processed next,
           // put the other branch in the stack
+          Standard_ASSERT_RAISE(aHead < BVH_Constants_MaxTreeDepth - 1,
+                                "Error! BVH stack overflow");
           if (this->IsMetricBetter(aMetricLft, aMetricRgh))
           {
-            aNode           = BVH_NodeInStack<MetricType>(aData.y(), aMetricLft);
-            aStack[++aHead] = BVH_NodeInStack<MetricType>(aData.z(), aMetricRgh);
+            aNode           = BVH_NodeInStack(aData.y(), aMetricLft);
+            aStack[++aHead] = BVH_NodeInStack(aData.z(), aMetricRgh);
           }
           else
           {
-            aNode           = BVH_NodeInStack<MetricType>(aData.z(), aMetricRgh);
-            aStack[++aHead] = BVH_NodeInStack<MetricType>(aData.y(), aMetricLft);
+            aNode           = BVH_NodeInStack(aData.z(), aMetricRgh);
+            aStack[++aHead] = BVH_NodeInStack(aData.y(), aMetricLft);
           }
         }
         else if (isGoodLft || isGoodRgh)
         {
-          aNode = isGoodLft ? BVH_NodeInStack<MetricType>(aData.y(), aMetricLft)
-                            : BVH_NodeInStack<MetricType>(aData.z(), aMetricRgh);
+          aNode = isGoodLft ? BVH_NodeInStack(aData.y(), aMetricLft)
+                            : BVH_NodeInStack(aData.z(), aMetricRgh);
         }
       }
       else
       {
         // Both children will be accepted
         // Take one for processing, put the other into stack
-        aNode           = BVH_NodeInStack<MetricType>(aData.y(), aNode.Metric);
-        aStack[++aHead] = BVH_NodeInStack<MetricType>(aData.z(), aNode.Metric);
+        Standard_ASSERT_RAISE(aHead < BVH_Constants_MaxTreeDepth - 1, "Error! BVH stack overflow");
+        aNode           = BVH_NodeInStack(aData.y(), aNode.Metric);
+        aStack[++aHead] = BVH_NodeInStack(aData.z(), aNode.Metric);
       }
     }
     else
@@ -142,29 +129,6 @@ Standard_Integer BVH_Traverse<NumType, Dimension, BVHSetType, MetricType>::Selec
   }
 }
 
-namespace
-{
-//! Auxiliary structure for keeping the pair of nodes to process
-template <class MetricType>
-struct BVH_PairNodesInStack
-{
-  //! Constructor
-  BVH_PairNodesInStack(const Standard_Integer theNodeID1 = 0,
-                       const Standard_Integer theNodeID2 = 0,
-                       const MetricType&      theMetric  = MetricType())
-      : NodeID1(theNodeID1),
-        NodeID2(theNodeID2),
-        Metric(theMetric)
-  {
-  }
-
-  // Fields
-  Standard_Integer NodeID1; //!< Id of the node in the first BVH tree
-  Standard_Integer NodeID2; //!< Id of the node in the second BVH tree
-  MetricType       Metric;  //!< Metric computed for the pair of nodes
-};
-} // namespace
-
 //=================================================================================================
 
 template <class NumType, int Dimension, class BVHSetType, class MetricType>
@@ -187,12 +151,12 @@ Standard_Integer BVH_PairTraverse<NumType, Dimension, BVHSetType, MetricType>::S
   const Standard_Integer aMaxNbPairsInStack = 3 * BVH_Constants_MaxTreeDepth;
 
   // Stack of pairs of nodes to process
-  BVH_PairNodesInStack<MetricType> aStack[aMaxNbPairsInStack];
+  BVH_PairNodesInStack aStack[aMaxNbPairsInStack];
 
   // Currently processed pair, starting with the root nodes
-  BVH_PairNodesInStack<MetricType> aNode(0, 0);
+  BVH_PairNodesInStack aNode(0, 0);
   // Previously processed pair
-  BVH_PairNodesInStack<MetricType> aPrevNode = aNode;
+  BVH_PairNodesInStack aPrevNode = aNode;
   // End of the stack
   Standard_Integer aHead = -1;
   // Counter for accepted elements
@@ -205,47 +169,59 @@ Standard_Integer BVH_PairTraverse<NumType, Dimension, BVHSetType, MetricType>::S
 
     if (aData1.x() != 0 && aData2.x() != 0)
     {
-      // Outer/Outer
-      for (Standard_Integer iN1 = aData1.y(); iN1 <= aData1.z(); ++iN1)
+      // Outer/Outer - both nodes are leaves
+      // Check if the leaf node bounding boxes overlap before testing elements
+      MetricType       aMetric;
+      Standard_Boolean isRejected = RejectNode(theBVH1->MinPoint(aNode.NodeID1),
+                                               theBVH1->MaxPoint(aNode.NodeID1),
+                                               theBVH2->MinPoint(aNode.NodeID2),
+                                               theBVH2->MaxPoint(aNode.NodeID2),
+                                               aMetric);
+
+      if (!isRejected)
       {
-        for (Standard_Integer iN2 = aData2.y(); iN2 <= aData2.z(); ++iN2)
+        // Bounding boxes overlap, test all element pairs
+        for (Standard_Integer iN1 = aData1.y(); iN1 <= aData1.z(); ++iN1)
         {
-          if (Accept(iN1, iN2))
-            ++aNbAccepted;
+          for (Standard_Integer iN2 = aData2.y(); iN2 <= aData2.z(); ++iN2)
+          {
+            if (Accept(iN1, iN2))
+              ++aNbAccepted;
 
-          if (this->Stop())
-            return aNbAccepted;
+            if (this->Stop())
+              return aNbAccepted;
+          }
         }
       }
     }
     else
     {
-      BVH_PairNodesInStack<MetricType> aPairs[4];
-      Standard_Integer                 aNbPairs = 0;
+      BVH_PairNodesInStack aPairs[4];
+      Standard_Integer     aNbPairs = 0;
 
       if (aData1.x() == 0 && aData2.x() == 0)
       {
         // Inner/Inner
-        aPairs[aNbPairs++] = BVH_PairNodesInStack<MetricType>(aData1.y(), aData2.y());
-        aPairs[aNbPairs++] = BVH_PairNodesInStack<MetricType>(aData1.y(), aData2.z());
-        aPairs[aNbPairs++] = BVH_PairNodesInStack<MetricType>(aData1.z(), aData2.y());
-        aPairs[aNbPairs++] = BVH_PairNodesInStack<MetricType>(aData1.z(), aData2.z());
+        aPairs[aNbPairs++] = BVH_PairNodesInStack(aData1.y(), aData2.y());
+        aPairs[aNbPairs++] = BVH_PairNodesInStack(aData1.y(), aData2.z());
+        aPairs[aNbPairs++] = BVH_PairNodesInStack(aData1.z(), aData2.y());
+        aPairs[aNbPairs++] = BVH_PairNodesInStack(aData1.z(), aData2.z());
       }
       else if (aData1.x() == 0)
       {
         // Inner/Outer
-        aPairs[aNbPairs++] = BVH_PairNodesInStack<MetricType>(aData1.y(), aNode.NodeID2);
-        aPairs[aNbPairs++] = BVH_PairNodesInStack<MetricType>(aData1.z(), aNode.NodeID2);
+        aPairs[aNbPairs++] = BVH_PairNodesInStack(aData1.y(), aNode.NodeID2);
+        aPairs[aNbPairs++] = BVH_PairNodesInStack(aData1.z(), aNode.NodeID2);
       }
       else if (aData2.x() == 0)
       {
         // Outer/Inner
-        aPairs[aNbPairs++] = BVH_PairNodesInStack<MetricType>(aNode.NodeID1, aData2.y());
-        aPairs[aNbPairs++] = BVH_PairNodesInStack<MetricType>(aNode.NodeID1, aData2.z());
+        aPairs[aNbPairs++] = BVH_PairNodesInStack(aNode.NodeID1, aData2.y());
+        aPairs[aNbPairs++] = BVH_PairNodesInStack(aNode.NodeID1, aData2.z());
       }
 
-      BVH_PairNodesInStack<MetricType> aKeptPairs[4];
-      Standard_Integer                 aNbKept = 0;
+      BVH_PairNodesInStack aKeptPairs[4];
+      Standard_Integer     aNbKept = 0;
       // Compute metrics for the nodes
       for (Standard_Integer iPair = 0; iPair < aNbPairs; ++iPair)
       {
@@ -265,7 +241,7 @@ Standard_Integer BVH_PairTraverse<NumType, Dimension, BVHSetType, MetricType>::S
             --iSort;
           }
           aKeptPairs[iSort] = aPairs[iPair];
-          aNbKept++;
+          ++aNbKept;
         }
       }
 
@@ -275,6 +251,7 @@ Standard_Integer BVH_PairTraverse<NumType, Dimension, BVHSetType, MetricType>::S
 
         for (Standard_Integer iPair = 1; iPair < aNbKept; ++iPair)
         {
+          Standard_ASSERT_RAISE(aHead < aMaxNbPairsInStack - 1, "Error! BVH pair stack overflow");
           aStack[++aHead] = aKeptPairs[iPair];
         }
       }
index 02aa40e1c873b397da692730240152a9ed76bce5..f2e3f28507b19dc286195f2e211e5ff3cf6d1781 100644 (file)
@@ -188,7 +188,7 @@ struct VecComp<T, 2>
 {
   typedef typename BVH::VectorType<T, 2>::Type BVH_Vec2t;
 
-  static T Get(const BVH_Vec2t& theVec, const Standard_Integer theAxis)
+  static constexpr T Get(const BVH_Vec2t& theVec, const Standard_Integer theAxis)
   {
     return theAxis == 0 ? theVec.x() : theVec.y();
   }
@@ -199,7 +199,7 @@ struct VecComp<T, 3>
 {
   typedef typename BVH::VectorType<T, 3>::Type BVH_Vec3t;
 
-  static T Get(const BVH_Vec3t& theVec, const Standard_Integer theAxis)
+  static constexpr T Get(const BVH_Vec3t& theVec, const Standard_Integer theAxis)
   {
     return theAxis == 0 ? theVec.x() : (theAxis == 1 ? theVec.y() : theVec.z());
   }
@@ -210,7 +210,7 @@ struct VecComp<T, 4>
 {
   typedef typename BVH::VectorType<T, 4>::Type BVH_Vec4t;
 
-  static T Get(const BVH_Vec4t& theVec, const Standard_Integer theAxis)
+  static constexpr T Get(const BVH_Vec4t& theVec, const Standard_Integer theAxis)
   {
     return theAxis == 0 ? theVec.x()
                         : (theAxis == 1 ? theVec.y() : (theAxis == 2 ? theVec.z() : theVec.w()));
@@ -302,7 +302,7 @@ struct Array
 };
 
 template <class T>
-static inline Standard_Integer IntFloor(const T theValue)
+static inline constexpr Standard_Integer IntFloor(const T theValue)
 {
   const Standard_Integer aRes = static_cast<Standard_Integer>(theValue);
 
index 5276ccdad94a9f0b70cfd6bd7bef64befb803a09..a870df7d9380c7d0876405e881a999d3d2a8587f 100644 (file)
@@ -9,7 +9,6 @@ set(OCCT_BVH_FILES
   BVH_Builder.hxx
   BVH_Builder3d.hxx
   BVH_BuildQueue.hxx
-  BVH_BuildQueue.cxx
   BVH_BuildThread.hxx
   BVH_BuildThread.cxx
   BVH_Constants.hxx
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_BinnedBuilder_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_BinnedBuilder_Test.cxx
new file mode 100644 (file)
index 0000000..9d68227
--- /dev/null
@@ -0,0 +1,307 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_BinnedBuilder.hxx>
+#include <BVH_BoxSet.hxx>
+#include <Precision.hxx>
+
+TEST(BVH_BinnedBuilderTest, DefaultConstructor)
+{
+  BVH_BinnedBuilder<Standard_Real, 3> aBuilder;
+
+  EXPECT_GT(aBuilder.LeafNodeSize(), 0);
+  EXPECT_GT(aBuilder.MaxTreeDepth(), 0);
+}
+
+TEST(BVH_BinnedBuilderTest, CustomParameters)
+{
+  BVH_BinnedBuilder<Standard_Real, 3> aBuilder(5, 20);
+
+  EXPECT_EQ(aBuilder.LeafNodeSize(), 5);
+  EXPECT_EQ(aBuilder.MaxTreeDepth(), 20);
+}
+
+TEST(BVH_BinnedBuilderTest, BuildEmptySet)
+{
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>();
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_EQ(aBVH->Length(), 0);
+}
+
+TEST(BVH_BinnedBuilderTest, BuildSingleElement)
+{
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>();
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  aBoxSet.Add(0, BVH_Box<Standard_Real, 3>(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0)));
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_EQ(aBVH->Length(), 1);
+  EXPECT_TRUE(aBVH->IsOuter(0));
+}
+
+TEST(BVH_BinnedBuilderTest, BuildMultipleElements)
+{
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add boxes along X axis
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 2.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 2.0 + 1.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+  EXPECT_GT(aBVH->Depth(), 0);
+}
+
+TEST(BVH_BinnedBuilderTest, SAHOptimization)
+{
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add boxes in a pattern where SAH should matter
+  // Two clusters: one near origin, one far away
+  for (int i = 0; i < 5; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 0.1, 0.0, 0.0),
+                                   BVH_Vec3d(i * 0.1 + 0.1, 0.1, 0.1));
+    aBoxSet.Add(i, aBox);
+  }
+
+  for (int i = 0; i < 5; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(100.0 + i * 0.1, 0.0, 0.0),
+                                   BVH_Vec3d(100.0 + i * 0.1 + 0.1, 0.1, 0.1));
+    aBoxSet.Add(i + 5, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+
+  // SAH should produce a reasonable tree
+  Standard_Real aSAH = aBVH->EstimateSAH();
+  EXPECT_GT(aSAH, 0.0);
+}
+
+TEST(BVH_BinnedBuilderTest, LeafNodeSizeRespected)
+{
+  const int                                                aLeafSize = 3;
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>(aLeafSize, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 2.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 2.0 + 1.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+
+  // Check that leaf nodes don't exceed leaf size
+  for (int i = 0; i < aBVH->Length(); ++i)
+  {
+    if (aBVH->IsOuter(i))
+    {
+      int aNbPrims = aBVH->NbPrimitives(i);
+      EXPECT_LE(aNbPrims, aLeafSize);
+    }
+  }
+}
+
+TEST(BVH_BinnedBuilderTest, BuildWithDifferentBinCounts)
+{
+  // Test with different number of bins (template parameter)
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3, 16>> aBuilder16 =
+    new BVH_BinnedBuilder<Standard_Real, 3, 16>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder16);
+
+  for (int i = 0; i < 20; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 1.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 1.0 + 0.5, 0.5, 0.5));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+}
+
+TEST(BVH_BinnedBuilderTest, Build2D)
+{
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 2>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 2>(1, 32);
+  BVH_BoxSet<Standard_Real, 2> aBoxSet(aBuilder);
+
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(i * 2.0, 0.0), BVH_Vec2d(i * 2.0 + 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 2>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+}
+
+// Note: Float tests skipped due to BVH_BoxSet::Center return type issue
+
+TEST(BVH_BinnedBuilderTest, RandomDistribution)
+{
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add boxes in a 3D grid pattern
+  int aCount = 0;
+  for (int x = 0; x < 5; ++x)
+  {
+    for (int y = 0; y < 5; ++y)
+    {
+      for (int z = 0; z < 5; ++z)
+      {
+        BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(x * 2.0, y * 2.0, z * 2.0),
+                                       BVH_Vec3d(x * 2.0 + 1.0, y * 2.0 + 1.0, z * 2.0 + 1.0));
+        aBoxSet.Add(aCount++, aBox);
+      }
+    }
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+
+  // Verify tree covers all primitives
+  int aTotalPrims = 0;
+  for (int i = 0; i < aBVH->Length(); ++i)
+  {
+    if (aBVH->IsOuter(i))
+    {
+      aTotalPrims += aBVH->NbPrimitives(i);
+    }
+  }
+  EXPECT_EQ(aTotalPrims, 125);
+}
+
+TEST(BVH_BinnedBuilderTest, CompareTreeQuality)
+{
+  // Build tree with small leaf size vs large leaf size
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder1 =
+    new BVH_BinnedBuilder<Standard_Real, 3>(1, 32);
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder4 =
+    new BVH_BinnedBuilder<Standard_Real, 3>(4, 32);
+
+  BVH_BoxSet<Standard_Real, 3> aBoxSet1(aBuilder1);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet4(aBuilder4);
+
+  for (int i = 0; i < 50; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 2.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 2.0 + 1.0, 1.0, 1.0));
+    aBoxSet1.Add(i, aBox);
+    aBoxSet4.Add(i, aBox);
+  }
+
+  aBoxSet1.Build();
+  aBoxSet4.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH1 = aBoxSet1.BVH();
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH4 = aBoxSet4.BVH();
+
+  // Tree with smaller leaf size should be deeper
+  EXPECT_GE(aBVH1->Depth(), aBVH4->Depth());
+}
+
+TEST(BVH_BinnedBuilderTest, MaxDepthRespected)
+{
+  const int                                                aMaxDepth = 5;
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>(1, aMaxDepth);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  for (int i = 0; i < 100; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 2.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 2.0 + 1.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_LE(aBVH->Depth(), aMaxDepth);
+}
+
+TEST(BVH_BinnedBuilderTest, OverlappingBoxes)
+{
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add overlapping boxes
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 0.5, 0.0, 0.0),
+                                   BVH_Vec3d(i * 0.5 + 2.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+}
+
+TEST(BVH_BinnedBuilderTest, IdenticalBoxes)
+{
+  opencascade::handle<BVH_BinnedBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_BinnedBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add identical boxes
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GE(aBVH->Length(), 1);
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_Box_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_Box_Test.cxx
new file mode 100644 (file)
index 0000000..c05d57e
--- /dev/null
@@ -0,0 +1,1045 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_Box.hxx>
+#include <Precision.hxx>
+
+TEST(BVH_BoxTest, DefaultConstructor)
+{
+  BVH_Box<Standard_Real, 3> aBox;
+  EXPECT_FALSE(aBox.IsValid());
+}
+
+TEST(BVH_BoxTest, ConstructorWithCorners)
+{
+  BVH_Vec3d aMin(0.0, 0.0, 0.0);
+  BVH_Vec3d aMax(1.0, 2.0, 3.0);
+
+  BVH_Box<Standard_Real, 3> aBox(aMin, aMax);
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().z(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 3.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Add)
+{
+  BVH_Box<Standard_Real, 3> aBox;
+
+  aBox.Add(BVH_Vec3d(1.0, 2.0, 3.0));
+  EXPECT_TRUE(aBox.IsValid());
+
+  aBox.Add(BVH_Vec3d(-1.0, -2.0, -3.0));
+
+  EXPECT_NEAR(aBox.CornerMin().x(), -1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), -2.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().z(), -3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 3.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Combine)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(3.0, 3.0, 3.0));
+
+  aBox1.Combine(aBox2);
+
+  EXPECT_NEAR(aBox1.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMin().y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMin().z(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().x(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().y(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().z(), 3.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Size)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(2.0, 3.0, 4.0));
+
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.z(), 4.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Center)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(2.0, 4.0, 6.0));
+
+  BVH_Vec3d aCenter = aBox.Center();
+  EXPECT_NEAR(aCenter.x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aCenter.y(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aCenter.z(), 3.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Area)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 2.0, 3.0));
+
+  // Surface area = 2 * (1*2 + 2*3 + 1*3) = 2 * (2 + 6 + 3) = 22
+  Standard_Real anArea = aBox.Area();
+  EXPECT_NEAR(anArea, 22.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, IsOut)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(3.0, 3.0, 3.0));
+  BVH_Box<Standard_Real, 3> aBox3(BVH_Vec3d(0.5, 0.5, 0.5), BVH_Vec3d(1.5, 1.5, 1.5));
+
+  EXPECT_TRUE(aBox1.IsOut(aBox2));
+  EXPECT_FALSE(aBox1.IsOut(aBox3));
+}
+
+TEST(BVH_BoxTest, Clear)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  EXPECT_TRUE(aBox.IsValid());
+
+  aBox.Clear();
+  EXPECT_FALSE(aBox.IsValid());
+}
+
+TEST(BVH_BoxTest, Box2D)
+{
+  BVH_Box<Standard_Real, 2> aBox;
+
+  aBox.Add(BVH_Vec2d(0.0, 0.0));
+  aBox.Add(BVH_Vec2d(1.0, 1.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 1.0, Precision::Confusion());
+
+  // Area in 2D is width * height (actual area)
+  Standard_Real anArea = aBox.Area();
+  EXPECT_NEAR(anArea, 1.0, Precision::Confusion()); // 1 * 1 = 1
+}
+
+TEST(BVH_BoxTest, Box4D)
+{
+  BVH_Box<Standard_Real, 4> aBox;
+
+  aBox.Add(BVH_Vec4d(0.0, 0.0, 0.0, 0.0));
+  aBox.Add(BVH_Vec4d(1.0, 2.0, 3.0, 4.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().z(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().w(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().w(), 4.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, FloatPrecision)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox;
+
+  aBox.Add(BVH_Vec3f(0.0f, 0.0f, 0.0f));
+  aBox.Add(BVH_Vec3f(1.0f, 2.0f, 3.0f));
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().y(), 2.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().z(), 3.0f, 1e-5f);
+}
+
+TEST(BVH_BoxTest, SinglePointBox)
+{
+  BVH_Box<Standard_Real, 3> aBox;
+
+  aBox.Add(BVH_Vec3d(5.0, 5.0, 5.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.CornerMin().x(), 5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 5.0, Precision::Confusion());
+
+  // Size should be zero
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.z(), 0.0, Precision::Confusion());
+
+  // Area should be zero
+  EXPECT_NEAR(aBox.Area(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, NegativeCoordinates)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-10.0, -20.0, -30.0), BVH_Vec3d(-5.0, -10.0, -15.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+
+  BVH_Vec3d aCenter = aBox.Center();
+  EXPECT_NEAR(aCenter.x(), -7.5, Precision::Confusion());
+  EXPECT_NEAR(aCenter.y(), -15.0, Precision::Confusion());
+  EXPECT_NEAR(aCenter.z(), -22.5, Precision::Confusion());
+
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 5.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.z(), 15.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, LargeValues)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(1e10, 1e10, 1e10),
+                                 BVH_Vec3d(1e10 + 1.0, 1e10 + 2.0, 1e10 + 3.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 1.0, 1e-5);
+  EXPECT_NEAR(aSize.y(), 2.0, 1e-5);
+  EXPECT_NEAR(aSize.z(), 3.0, 1e-5);
+}
+
+TEST(BVH_BoxTest, CombineWithInvalid)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2; // Invalid box
+
+  aBox1.Combine(aBox2);
+
+  // Box1 should remain unchanged when combining with invalid box
+  EXPECT_TRUE(aBox1.IsValid());
+  EXPECT_NEAR(aBox1.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().x(), 1.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, AddToInvalid)
+{
+  BVH_Box<Standard_Real, 3> aBox1; // Invalid box
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  aBox1.Combine(aBox2);
+
+  // Box1 should now be valid and equal to aBox2
+  EXPECT_TRUE(aBox1.IsValid());
+  EXPECT_NEAR(aBox1.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().x(), 1.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, IsOutTouchingBoxes)
+{
+  // Boxes that touch at a face
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(1.0, 0.0, 0.0), BVH_Vec3d(2.0, 1.0, 1.0));
+
+  // Touching boxes should NOT be "out"
+  EXPECT_FALSE(aBox1.IsOut(aBox2));
+}
+
+TEST(BVH_BoxTest, IsOutTouchingAtEdge)
+{
+  // Boxes that touch at an edge
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(1.0, 1.0, 0.0), BVH_Vec3d(2.0, 2.0, 1.0));
+
+  // Touching at edge should NOT be "out"
+  EXPECT_FALSE(aBox1.IsOut(aBox2));
+}
+
+TEST(BVH_BoxTest, IsOutTouchingAtCorner)
+{
+  // Boxes that touch at a corner
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(1.0, 1.0, 1.0), BVH_Vec3d(2.0, 2.0, 2.0));
+
+  // Touching at corner should NOT be "out"
+  EXPECT_FALSE(aBox1.IsOut(aBox2));
+}
+
+TEST(BVH_BoxTest, AreaUnitCube)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Surface area of unit cube = 6 faces * 1 = 6
+  // Formula: 2 * (xy + yz + xz) = 2 * (1 + 1 + 1) = 6
+  EXPECT_NEAR(aBox.Area(), 6.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, CenterAtOrigin)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-1.0, -1.0, -1.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  BVH_Vec3d aCenter = aBox.Center();
+  EXPECT_NEAR(aCenter.x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aCenter.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aCenter.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, MultipleAdds)
+{
+  BVH_Box<Standard_Real, 3> aBox;
+
+  // Add points in random order
+  aBox.Add(BVH_Vec3d(5.0, 3.0, 1.0));
+  aBox.Add(BVH_Vec3d(-2.0, 7.0, 4.0));
+  aBox.Add(BVH_Vec3d(1.0, -1.0, 8.0));
+  aBox.Add(BVH_Vec3d(0.0, 2.0, -3.0));
+
+  EXPECT_NEAR(aBox.CornerMin().x(), -2.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), -1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().z(), -3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 7.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 8.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, FlatBox2D)
+{
+  // Box with zero extent in Z (flat)
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 5.0), BVH_Vec3d(3.0, 4.0, 5.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 4.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.z(), 0.0, Precision::Confusion());
+
+  // Area = 2 * (3*4 + 4*0 + 3*0) = 24
+  EXPECT_NEAR(aBox.Area(), 24.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, FlatBox1D)
+{
+  // Box with zero extent in Y and Z (line)
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 5.0, 5.0), BVH_Vec3d(10.0, 5.0, 5.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.z(), 0.0, Precision::Confusion());
+
+  // For degenerate box (line), Area returns length of longest dimension
+  // This is an implementation detail - the surface area formula gives 0,
+  // but Area() returns the length (10) for degenerate cases
+  Standard_Real anArea = aBox.Area();
+  EXPECT_GE(anArea, 0.0);
+}
+
+TEST(BVH_BoxTest, CombineMultiple)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 0.0, 0.0), BVH_Vec3d(3.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox3(BVH_Vec3d(0.0, 2.0, 0.0), BVH_Vec3d(1.0, 3.0, 1.0));
+
+  aBox1.Combine(aBox2);
+  aBox1.Combine(aBox3);
+
+  EXPECT_NEAR(aBox1.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMin().y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMin().z(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().x(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().y(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().z(), 1.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, IsOutPartialOverlap)
+{
+  // Boxes that partially overlap
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(2.0, 2.0, 2.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(1.0, 1.0, 1.0), BVH_Vec3d(3.0, 3.0, 3.0));
+
+  EXPECT_FALSE(aBox1.IsOut(aBox2));
+  EXPECT_FALSE(aBox2.IsOut(aBox1));
+}
+
+TEST(BVH_BoxTest, IsOutContained)
+{
+  // One box completely inside another
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(3.0, 3.0, 3.0));
+
+  EXPECT_FALSE(aBox1.IsOut(aBox2));
+  EXPECT_FALSE(aBox2.IsOut(aBox1));
+}
+
+TEST(BVH_BoxTest, IsOutFloat)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox1(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+  BVH_Box<Standard_ShortReal, 3> aBox2(BVH_Vec3f(2.0f, 2.0f, 2.0f), BVH_Vec3f(3.0f, 3.0f, 3.0f));
+  BVH_Box<Standard_ShortReal, 3> aBox3(BVH_Vec3f(0.5f, 0.5f, 0.5f), BVH_Vec3f(1.5f, 1.5f, 1.5f));
+
+  EXPECT_TRUE(aBox1.IsOut(aBox2));
+  EXPECT_FALSE(aBox1.IsOut(aBox3));
+}
+
+TEST(BVH_BoxTest, Box2DIsOut)
+{
+  BVH_Box<Standard_Real, 2> aBox1(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
+  BVH_Box<Standard_Real, 2> aBox2(BVH_Vec2d(2.0, 2.0), BVH_Vec2d(3.0, 3.0));
+  BVH_Box<Standard_Real, 2> aBox3(BVH_Vec2d(0.5, 0.5), BVH_Vec2d(1.5, 1.5));
+
+  EXPECT_TRUE(aBox1.IsOut(aBox2));
+  EXPECT_FALSE(aBox1.IsOut(aBox3));
+}
+
+TEST(BVH_BoxTest, Box2DCenter)
+{
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(4.0, 6.0));
+
+  BVH_Vec2d aCenter = aBox.Center();
+  EXPECT_NEAR(aCenter.x(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aCenter.y(), 3.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Box2DSize)
+{
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(1.0, 2.0), BVH_Vec2d(4.0, 7.0));
+
+  BVH_Vec2d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 5.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, VerySmallBox)
+{
+  // Test with very small values
+  Standard_Real             aSmall = 1e-10;
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(aSmall, aSmall, aSmall));
+
+  EXPECT_TRUE(aBox.IsValid());
+
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), aSmall, 1e-15);
+  EXPECT_NEAR(aSize.y(), aSmall, 1e-15);
+  EXPECT_NEAR(aSize.z(), aSmall, 1e-15);
+}
+
+TEST(BVH_BoxTest, SymmetricBox)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-5.0, -5.0, -5.0), BVH_Vec3d(5.0, 5.0, 5.0));
+
+  BVH_Vec3d aCenter = aBox.Center();
+  EXPECT_NEAR(aCenter.x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aCenter.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aCenter.z(), 0.0, Precision::Confusion());
+
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.z(), 10.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, NonCubicBox)
+{
+  // Box with different dimensions on each axis
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 10.0, 100.0));
+
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.z(), 100.0, Precision::Confusion());
+
+  // Area = 2 * (1*10 + 10*100 + 1*100) = 2 * (10 + 1000 + 100) = 2220
+  EXPECT_NEAR(aBox.Area(), 2220.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, ClearAndReuse)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+
+  aBox.Clear();
+  EXPECT_FALSE(aBox.IsValid());
+
+  // Reuse the box
+  aBox.Add(BVH_Vec3d(5.0, 5.0, 5.0));
+  aBox.Add(BVH_Vec3d(10.0, 10.0, 10.0));
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.CornerMin().x(), 5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 10.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, IsOutSameBox)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Box should not be "out" of itself
+  EXPECT_FALSE(aBox.IsOut(aBox));
+}
+
+TEST(BVH_BoxTest, CombineSameBox)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  aBox1.Combine(aBox2);
+
+  // Should remain unchanged
+  EXPECT_NEAR(aBox1.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox1.CornerMax().x(), 1.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, IsOutNearMiss)
+{
+  // Boxes that are very close but not touching
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(1.0001, 0.0, 0.0), BVH_Vec3d(2.0, 1.0, 1.0));
+
+  EXPECT_TRUE(aBox1.IsOut(aBox2));
+}
+
+TEST(BVH_BoxTest, AddDuplicatePoint)
+{
+  BVH_Box<Standard_Real, 3> aBox;
+
+  aBox.Add(BVH_Vec3d(1.0, 2.0, 3.0));
+  aBox.Add(BVH_Vec3d(1.0, 2.0, 3.0));
+  aBox.Add(BVH_Vec3d(1.0, 2.0, 3.0));
+
+  // Should still be a single point box
+  EXPECT_NEAR(aBox.CornerMin().x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.Size().x(), 0.0, Precision::Confusion());
+}
+
+// =======================================================================================
+// Tests for Single-Point Constructor
+// =======================================================================================
+
+TEST(BVH_BoxTest, SinglePointConstructor)
+{
+  BVH_Vec3d                 aPoint(5.0, 10.0, 15.0);
+  BVH_Box<Standard_Real, 3> aBox(aPoint);
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.CornerMin().x(), 5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().z(), 15.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 15.0, Precision::Confusion());
+
+  // Size should be zero
+  BVH_Vec3d aSize = aBox.Size();
+  EXPECT_NEAR(aSize.x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aSize.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, SinglePointConstructor2D)
+{
+  BVH_Vec2d                 aPoint(3.0, 7.0);
+  BVH_Box<Standard_Real, 2> aBox(aPoint);
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.CornerMin().x(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), 7.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 7.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, SinglePointConstructorOrigin)
+{
+  BVH_Vec3d                 aOrigin(0.0, 0.0, 0.0);
+  BVH_Box<Standard_Real, 3> aBox(aOrigin);
+
+  EXPECT_TRUE(aBox.IsValid());
+  EXPECT_NEAR(aBox.Center().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.Center().y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.Center().z(), 0.0, Precision::Confusion());
+}
+
+// =======================================================================================
+// Tests for Center(axis) method
+// =======================================================================================
+
+TEST(BVH_BoxTest, CenterByAxis)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 20.0, 30.0));
+
+  EXPECT_NEAR(aBox.Center(0), 5.0, Precision::Confusion());  // X axis
+  EXPECT_NEAR(aBox.Center(1), 10.0, Precision::Confusion()); // Y axis
+  EXPECT_NEAR(aBox.Center(2), 15.0, Precision::Confusion()); // Z axis
+}
+
+TEST(BVH_BoxTest, CenterByAxis2D)
+{
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(2.0, 4.0), BVH_Vec2d(8.0, 12.0));
+
+  EXPECT_NEAR(aBox.Center(0), 5.0, Precision::Confusion()); // X axis
+  EXPECT_NEAR(aBox.Center(1), 8.0, Precision::Confusion()); // Y axis
+}
+
+TEST(BVH_BoxTest, CenterByAxisNegative)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-10.0, -20.0, -30.0), BVH_Vec3d(10.0, 20.0, 30.0));
+
+  EXPECT_NEAR(aBox.Center(0), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.Center(1), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.Center(2), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, CenterByAxis4D)
+{
+  BVH_Box<Standard_Real, 4> aBox(BVH_Vec4d(0.0, 0.0, 0.0, 0.0), BVH_Vec4d(4.0, 6.0, 8.0, 10.0));
+
+  EXPECT_NEAR(aBox.Center(0), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.Center(1), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.Center(2), 4.0, Precision::Confusion());
+}
+
+// =======================================================================================
+// Tests for IsOut(point) method
+// =======================================================================================
+
+TEST(BVH_BoxTest, IsOutPoint_Inside)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Point inside
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(5.0, 5.0, 5.0)));
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(1.0, 1.0, 1.0)));
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(9.0, 9.0, 9.0)));
+}
+
+TEST(BVH_BoxTest, IsOutPoint_Outside)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Points outside
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(11.0, 5.0, 5.0)));   // Outside X
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(5.0, 11.0, 5.0)));   // Outside Y
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(5.0, 5.0, 11.0)));   // Outside Z
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(-1.0, 5.0, 5.0)));   // Outside negative X
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(15.0, 15.0, 15.0))); // Far outside
+}
+
+TEST(BVH_BoxTest, IsOutPoint_OnBoundary)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Points on boundary (should be considered inside)
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(0.0, 5.0, 5.0)));    // On min X face
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(10.0, 5.0, 5.0)));   // On max X face
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(5.0, 0.0, 5.0)));    // On min Y face
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(5.0, 10.0, 5.0)));   // On max Y face
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(0.0, 0.0, 0.0)));    // At corner
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(10.0, 10.0, 10.0))); // At opposite corner
+}
+
+TEST(BVH_BoxTest, IsOutPoint_InvalidBox)
+{
+  BVH_Box<Standard_Real, 3> aBox; // Invalid box
+
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(0.0, 0.0, 0.0)));
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(5.0, 5.0, 5.0)));
+}
+
+TEST(BVH_BoxTest, IsOutPoint_2D)
+{
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(5.0, 5.0));
+
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec2d(2.5, 2.5))); // Inside
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec2d(6.0, 2.5)));  // Outside X
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec2d(2.5, 6.0)));  // Outside Y
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec2d(0.0, 0.0))); // On corner
+}
+
+TEST(BVH_BoxTest, IsOutPoint_NegativeCoords)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-5.0, -5.0, -5.0), BVH_Vec3d(5.0, 5.0, 5.0));
+
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(0.0, 0.0, 0.0)));    // At center
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(-3.0, -3.0, -3.0))); // Inside negative
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(-6.0, 0.0, 0.0)));    // Outside negative X
+}
+
+// =======================================================================================
+// Tests for Contains() methods
+// =======================================================================================
+
+TEST(BVH_BoxTest, Contains_FullyContained)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(8.0, 8.0, 8.0));
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_TRUE(isContained);
+  EXPECT_TRUE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, Contains_NotContained)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(8.0, 8.0, 8.0), BVH_Vec3d(12.0, 12.0, 12.0));
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_FALSE(isContained);
+  EXPECT_TRUE(hasOverlap); // They overlap but box2 is not fully contained
+}
+
+TEST(BVH_BoxTest, Contains_NoOverlap)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(5.0, 5.0, 5.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(10.0, 10.0, 10.0), BVH_Vec3d(15.0, 15.0, 15.0));
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_FALSE(isContained);
+  EXPECT_FALSE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, Contains_SameBox)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_TRUE(isContained);
+  EXPECT_TRUE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, Contains_TouchingBoundary)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(5.0, 5.0, 5.0));
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_TRUE(isContained);
+  EXPECT_TRUE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, Contains_InvalidBox)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+  BVH_Box<Standard_Real, 3> aBox2; // Invalid
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_FALSE(isContained);
+  EXPECT_FALSE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, Contains_InvalidContainer)
+{
+  BVH_Box<Standard_Real, 3> aBox1; // Invalid
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_FALSE(isContained);
+  EXPECT_FALSE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, Contains_PartialOverlapOneAxis)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(5.0, 5.0, 5.0), BVH_Vec3d(15.0, 8.0, 8.0));
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_FALSE(isContained); // Not fully contained (extends in X)
+  EXPECT_TRUE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, Contains_2D)
+{
+  BVH_Box<Standard_Real, 2> aBox1(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(10.0, 10.0));
+  BVH_Box<Standard_Real, 2> aBox2(BVH_Vec2d(2.0, 2.0), BVH_Vec2d(8.0, 8.0));
+
+  Standard_Boolean hasOverlap  = Standard_False;
+  Standard_Boolean isContained = aBox1.Contains(aBox2, hasOverlap);
+
+  EXPECT_TRUE(isContained);
+  EXPECT_TRUE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, ContainsByCorners_FullyContained)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  Standard_Boolean hasOverlap = Standard_False;
+  Standard_Boolean isContained =
+    aBox.Contains(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(8.0, 8.0, 8.0), hasOverlap);
+
+  EXPECT_TRUE(isContained);
+  EXPECT_TRUE(hasOverlap);
+}
+
+TEST(BVH_BoxTest, ContainsByCorners_NotContained)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  Standard_Boolean hasOverlap = Standard_False;
+  Standard_Boolean isContained =
+    aBox.Contains(BVH_Vec3d(8.0, 8.0, 8.0), BVH_Vec3d(12.0, 12.0, 12.0), hasOverlap);
+
+  EXPECT_FALSE(isContained);
+  EXPECT_TRUE(hasOverlap);
+}
+
+// =======================================================================================
+// Tests for Transform() and Transformed() methods
+// =======================================================================================
+
+TEST(BVH_BoxTest, Transform_Identity)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  NCollection_Mat4<Standard_Real> aIdentity;
+  aIdentity.InitIdentity();
+
+  aBox.Transform(aIdentity);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Transform_Translation)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  NCollection_Mat4<Standard_Real> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetColumn(3, NCollection_Vec3<Standard_Real>(5.0, 10.0, 15.0));
+
+  aBox.Transform(aTransform);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), 5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().z(), 15.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 6.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 11.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 16.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Transform_Scale)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  NCollection_Mat4<Standard_Real> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetValue(0, 0, 2.0); // Scale X by 2
+  aTransform.SetValue(1, 1, 3.0); // Scale Y by 3
+  aTransform.SetValue(2, 2, 4.0); // Scale Z by 4
+
+  aBox.Transform(aTransform);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 4.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Transformed_Identity)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  NCollection_Mat4<Standard_Real> aIdentity;
+  aIdentity.InitIdentity();
+
+  BVH_Box<Standard_Real, 3> aTransformed = aBox.Transformed(aIdentity);
+
+  // Original should be unchanged
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, Precision::Confusion());
+
+  // Transformed should be the same
+  EXPECT_NEAR(aTransformed.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aTransformed.CornerMax().x(), 1.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Transformed_Translation)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  NCollection_Mat4<Standard_Real> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetColumn(3, NCollection_Vec3<Standard_Real>(10.0, 20.0, 30.0));
+
+  BVH_Box<Standard_Real, 3> aTransformed = aBox.Transformed(aTransform);
+
+  // Original should be unchanged
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, Precision::Confusion());
+
+  // Transformed should be translated
+  EXPECT_NEAR(aTransformed.CornerMin().x(), 10.0, Precision::Confusion());
+  EXPECT_NEAR(aTransformed.CornerMin().y(), 20.0, Precision::Confusion());
+  EXPECT_NEAR(aTransformed.CornerMin().z(), 30.0, Precision::Confusion());
+  EXPECT_NEAR(aTransformed.CornerMax().x(), 11.0, Precision::Confusion());
+  EXPECT_NEAR(aTransformed.CornerMax().y(), 21.0, Precision::Confusion());
+  EXPECT_NEAR(aTransformed.CornerMax().z(), 31.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, Transform_InvalidBox)
+{
+  BVH_Box<Standard_Real, 3> aBox; // Invalid box
+
+  NCollection_Mat4<Standard_Real> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetColumn(3, NCollection_Vec3<Standard_Real>(10.0, 20.0, 30.0));
+
+  aBox.Transform(aTransform);
+
+  // Should remain invalid
+  EXPECT_FALSE(aBox.IsValid());
+}
+
+TEST(BVH_BoxTest, Transformed_InvalidBox)
+{
+  BVH_Box<Standard_Real, 3> aBox; // Invalid box
+
+  NCollection_Mat4<Standard_Real> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetColumn(3, NCollection_Vec3<Standard_Real>(10.0, 20.0, 30.0));
+
+  BVH_Box<Standard_Real, 3> aTransformed = aBox.Transformed(aTransform);
+
+  // Should remain invalid
+  EXPECT_FALSE(aTransformed.IsValid());
+}
+
+// =======================================================================================
+// Tests for Corner Modification
+// =======================================================================================
+
+TEST(BVH_BoxTest, ModifyCornerMin)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Modify via non-const reference
+  aBox.CornerMin() = BVH_Vec3d(-5.0, -5.0, -5.0);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), -5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), -5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().z(), -5.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().x(), 10.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, ModifyCornerMax)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Modify via non-const reference
+  aBox.CornerMax() = BVH_Vec3d(20.0, 30.0, 40.0);
+
+  EXPECT_NEAR(aBox.CornerMax().x(), 20.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().y(), 30.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 40.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_BoxTest, ModifyCornerComponents)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Modify individual components
+  aBox.CornerMin().x() = -1.0;
+  aBox.CornerMin().y() = -2.0;
+  aBox.CornerMax().z() = 15.0;
+
+  EXPECT_NEAR(aBox.CornerMin().x(), -1.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().y(), -2.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMin().z(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aBox.CornerMax().z(), 15.0, Precision::Confusion());
+}
+
+// =======================================================================================
+// Additional Edge Cases
+// =======================================================================================
+
+TEST(BVH_BoxTest, Area_DegenerateBox2D)
+{
+  // 2D box with zero area should return perimeter
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(5.0, 0.0));
+
+  Standard_Real anArea = aBox.Area();
+  // For degenerate case, returns sum of dimensions
+  EXPECT_GE(anArea, 0.0);
+  EXPECT_NEAR(anArea, 5.0, Precision::Confusion()); // Width + height = 5 + 0
+}
+
+TEST(BVH_BoxTest, IsOut_TwoCorners_Overlapping)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Test with overlapping region
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(5.0, 5.0, 5.0), BVH_Vec3d(15.0, 15.0, 15.0)));
+}
+
+TEST(BVH_BoxTest, IsOut_TwoCorners_Disjoint)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Test with disjoint region
+  EXPECT_TRUE(aBox.IsOut(BVH_Vec3d(20.0, 20.0, 20.0), BVH_Vec3d(30.0, 30.0, 30.0)));
+}
+
+TEST(BVH_BoxTest, IsOut_TwoCorners_Contained)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+
+  // Test with fully contained region
+  EXPECT_FALSE(aBox.IsOut(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(8.0, 8.0, 8.0)));
+}
+
+TEST(BVH_BoxTest, Constexpr_Construction)
+{
+  // Test that constexpr constructors work at compile time
+  constexpr BVH_Vec3d                 aMin(0.0, 0.0, 0.0);
+  constexpr BVH_Vec3d                 aMax(1.0, 1.0, 1.0);
+  constexpr BVH_Box<Standard_Real, 3> aBox(aMin, aMax);
+
+  static_assert(aBox.IsValid(), "Constexpr box should be valid");
+}
+
+TEST(BVH_BoxTest, Constexpr_SinglePoint)
+{
+  constexpr BVH_Vec3d                 aPoint(5.0, 5.0, 5.0);
+  constexpr BVH_Box<Standard_Real, 3> aBox(aPoint);
+
+  static_assert(aBox.IsValid(), "Constexpr single-point box should be valid");
+}
+
+TEST(BVH_BoxTest, Constexpr_DefaultInvalid)
+{
+  constexpr BVH_Box<Standard_Real, 3> aBox;
+
+  static_assert(!aBox.IsValid(), "Default constexpr box should be invalid");
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_BuildQueue_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_BuildQueue_Test.cxx
new file mode 100644 (file)
index 0000000..62d7225
--- /dev/null
@@ -0,0 +1,387 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_BuildQueue.hxx>
+
+#include <thread>
+#include <vector>
+
+// =============================================================================
+// BVH_BuildQueue Basic Tests
+// =============================================================================
+
+TEST(BVH_BuildQueueTest, DefaultConstructor)
+{
+  BVH_BuildQueue aQueue;
+
+  EXPECT_EQ(aQueue.Size(), 0);
+  EXPECT_FALSE(aQueue.HasBusyThreads());
+}
+
+TEST(BVH_BuildQueueTest, EnqueueSingle)
+{
+  BVH_BuildQueue aQueue;
+
+  aQueue.Enqueue(42);
+
+  EXPECT_EQ(aQueue.Size(), 1);
+}
+
+TEST(BVH_BuildQueueTest, EnqueueMultiple)
+{
+  BVH_BuildQueue aQueue;
+
+  aQueue.Enqueue(1);
+  aQueue.Enqueue(2);
+  aQueue.Enqueue(3);
+
+  EXPECT_EQ(aQueue.Size(), 3);
+}
+
+TEST(BVH_BuildQueueTest, FetchFromEmptyQueue)
+{
+  BVH_BuildQueue   aQueue;
+  Standard_Boolean wasBusy = Standard_False;
+  Standard_Integer aResult = aQueue.Fetch(wasBusy);
+
+  EXPECT_EQ(aResult, -1);
+  EXPECT_FALSE(wasBusy);
+  EXPECT_FALSE(aQueue.HasBusyThreads());
+}
+
+TEST(BVH_BuildQueueTest, FetchSingleItem)
+{
+  BVH_BuildQueue aQueue;
+  aQueue.Enqueue(42);
+
+  Standard_Boolean wasBusy = Standard_False;
+  Standard_Integer aResult = aQueue.Fetch(wasBusy);
+
+  EXPECT_EQ(aResult, 42);
+  EXPECT_TRUE(wasBusy);
+  EXPECT_TRUE(aQueue.HasBusyThreads()); // Thread became busy
+  EXPECT_EQ(aQueue.Size(), 0);
+}
+
+TEST(BVH_BuildQueueTest, FetchFIFOOrder)
+{
+  BVH_BuildQueue aQueue;
+
+  aQueue.Enqueue(10);
+  aQueue.Enqueue(20);
+  aQueue.Enqueue(30);
+
+  Standard_Boolean wasBusy = Standard_False;
+
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 10);
+  EXPECT_TRUE(wasBusy);
+
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 20);
+  EXPECT_TRUE(wasBusy);
+
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 30);
+  EXPECT_TRUE(wasBusy);
+
+  EXPECT_EQ(aQueue.Fetch(wasBusy), -1);
+  EXPECT_FALSE(wasBusy);
+}
+
+TEST(BVH_BuildQueueTest, ThreadCountTracking)
+{
+  BVH_BuildQueue aQueue;
+
+  aQueue.Enqueue(1);
+  aQueue.Enqueue(2);
+
+  EXPECT_FALSE(aQueue.HasBusyThreads());
+
+  Standard_Boolean wasBusy = Standard_False;
+
+  // First fetch - thread becomes busy
+  aQueue.Fetch(wasBusy);
+  EXPECT_TRUE(wasBusy);
+  EXPECT_TRUE(aQueue.HasBusyThreads());
+
+  // Second fetch while already busy
+  aQueue.Fetch(wasBusy);
+  EXPECT_TRUE(wasBusy);
+  EXPECT_TRUE(aQueue.HasBusyThreads());
+
+  // Fetch from empty queue while busy
+  aQueue.Fetch(wasBusy);
+  EXPECT_FALSE(wasBusy);
+  EXPECT_FALSE(aQueue.HasBusyThreads());
+}
+
+TEST(BVH_BuildQueueTest, AlternatingEnqueueFetch)
+{
+  BVH_BuildQueue aQueue;
+
+  aQueue.Enqueue(1);
+  EXPECT_EQ(aQueue.Size(), 1);
+
+  Standard_Boolean wasBusy = Standard_False;
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 1);
+  EXPECT_EQ(aQueue.Size(), 0);
+
+  aQueue.Enqueue(2);
+  EXPECT_EQ(aQueue.Size(), 1);
+
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 2);
+  EXPECT_EQ(aQueue.Size(), 0);
+}
+
+TEST(BVH_BuildQueueTest, LargeQueue)
+{
+  BVH_BuildQueue aQueue;
+
+  const int aCount = 1000;
+  for (int i = 0; i < aCount; ++i)
+  {
+    aQueue.Enqueue(i);
+  }
+
+  EXPECT_EQ(aQueue.Size(), aCount);
+
+  Standard_Boolean wasBusy = Standard_False;
+  for (int i = 0; i < aCount; ++i)
+  {
+    EXPECT_EQ(aQueue.Fetch(wasBusy), i);
+  }
+
+  EXPECT_EQ(aQueue.Size(), 0);
+}
+
+TEST(BVH_BuildQueueTest, NegativeValues)
+{
+  BVH_BuildQueue aQueue;
+
+  aQueue.Enqueue(-1);
+  aQueue.Enqueue(-100);
+  aQueue.Enqueue(0);
+
+  Standard_Boolean wasBusy = Standard_False;
+
+  EXPECT_EQ(aQueue.Fetch(wasBusy), -1);
+  EXPECT_EQ(aQueue.Fetch(wasBusy), -100);
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 0);
+}
+
+// =============================================================================
+// BVH_BuildQueue Thread Safety Tests
+// =============================================================================
+
+TEST(BVH_BuildQueueTest, ConcurrentEnqueue)
+{
+  BVH_BuildQueue aQueue;
+
+  const int                aThreadCount    = 4;
+  const int                aItemsPerThread = 100;
+  std::vector<std::thread> aThreads;
+
+  for (int t = 0; t < aThreadCount; ++t)
+  {
+    aThreads.emplace_back([&aQueue, t, aItemsPerThread = aItemsPerThread]() {
+      for (int i = 0; i < aItemsPerThread; ++i)
+      {
+        aQueue.Enqueue(t * aItemsPerThread + i);
+      }
+    });
+  }
+
+  for (auto& aThread : aThreads)
+  {
+    aThread.join();
+  }
+
+  EXPECT_EQ(aQueue.Size(), aThreadCount * aItemsPerThread);
+}
+
+TEST(BVH_BuildQueueTest, ConcurrentFetch)
+{
+  BVH_BuildQueue aQueue;
+
+  const int aItemCount = 400;
+  for (int i = 0; i < aItemCount; ++i)
+  {
+    aQueue.Enqueue(i);
+  }
+
+  const int                aThreadCount = 4;
+  std::vector<std::thread> aThreads;
+  std::vector<int>         aFetchedCounts(aThreadCount, 0);
+
+  for (int t = 0; t < aThreadCount; ++t)
+  {
+    aThreads.emplace_back([&aQueue, t, &aFetchedCounts]() {
+      Standard_Boolean wasBusy = Standard_False;
+      while (true)
+      {
+        Standard_Integer aItem = aQueue.Fetch(wasBusy);
+        if (aItem == -1)
+          break;
+        aFetchedCounts[t]++;
+      }
+    });
+  }
+
+  for (auto& aThread : aThreads)
+  {
+    aThread.join();
+  }
+
+  int aTotalFetched = 0;
+  for (int aCount : aFetchedCounts)
+  {
+    aTotalFetched += aCount;
+  }
+
+  EXPECT_EQ(aTotalFetched, aItemCount);
+  EXPECT_EQ(aQueue.Size(), 0);
+}
+
+TEST(BVH_BuildQueueTest, ConcurrentEnqueueAndFetch)
+{
+  BVH_BuildQueue aQueue;
+
+  const int                aProducerCount    = 2;
+  const int                aConsumerCount    = 2;
+  const int                aItemsPerProducer = 100;
+  std::vector<std::thread> aThreads;
+  std::atomic<int>         aFetchedCount{0};
+  std::atomic<bool>        aDone{false};
+
+  // Producer threads
+  for (int t = 0; t < aProducerCount; ++t)
+  {
+    aThreads.emplace_back([&aQueue, t, aItemsPerProducer = aItemsPerProducer]() {
+      for (int i = 0; i < aItemsPerProducer; ++i)
+      {
+        aQueue.Enqueue(t * aItemsPerProducer + i);
+      }
+    });
+  }
+
+  // Consumer threads
+  for (int t = 0; t < aConsumerCount; ++t)
+  {
+    aThreads.emplace_back([&aQueue, &aFetchedCount, &aDone]() {
+      Standard_Boolean wasBusy = Standard_False;
+      while (!aDone.load() || aQueue.Size() > 0)
+      {
+        Standard_Integer aItem = aQueue.Fetch(wasBusy);
+        if (aItem != -1)
+        {
+          aFetchedCount.fetch_add(1);
+        }
+      }
+    });
+  }
+
+  // Wait for producers to finish
+  for (int i = 0; i < aProducerCount; ++i)
+  {
+    aThreads[i].join();
+  }
+
+  // Signal consumers that production is done
+  aDone.store(true);
+
+  // Wait for consumers to finish
+  for (int i = aProducerCount; i < aProducerCount + aConsumerCount; ++i)
+  {
+    aThreads[i].join();
+  }
+
+  EXPECT_EQ(aFetchedCount.load(), aProducerCount * aItemsPerProducer);
+  EXPECT_EQ(aQueue.Size(), 0);
+}
+
+// =============================================================================
+// BVH_BuildQueue Edge Cases
+// =============================================================================
+
+TEST(BVH_BuildQueueTest, RepeatedFetchFromEmpty)
+{
+  BVH_BuildQueue   aQueue;
+  Standard_Boolean wasBusy = Standard_False;
+
+  for (int i = 0; i < 10; ++i)
+  {
+    Standard_Integer aResult = aQueue.Fetch(wasBusy);
+    EXPECT_EQ(aResult, -1);
+    EXPECT_FALSE(wasBusy);
+  }
+
+  EXPECT_FALSE(aQueue.HasBusyThreads());
+}
+
+TEST(BVH_BuildQueueTest, EnqueueZero)
+{
+  BVH_BuildQueue aQueue;
+
+  aQueue.Enqueue(0);
+  EXPECT_EQ(aQueue.Size(), 1);
+
+  Standard_Boolean wasBusy = Standard_False;
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 0);
+  EXPECT_TRUE(wasBusy);
+}
+
+TEST(BVH_BuildQueueTest, DuplicateValues)
+{
+  BVH_BuildQueue aQueue;
+
+  aQueue.Enqueue(5);
+  aQueue.Enqueue(5);
+  aQueue.Enqueue(5);
+
+  EXPECT_EQ(aQueue.Size(), 3);
+
+  Standard_Boolean wasBusy = Standard_False;
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 5);
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 5);
+  EXPECT_EQ(aQueue.Fetch(wasBusy), 5);
+}
+
+TEST(BVH_BuildQueueTest, SingleThreadWorkflow)
+{
+  BVH_BuildQueue aQueue;
+
+  // Simulate single-threaded BVH building workflow
+  aQueue.Enqueue(0); // Root node
+
+  Standard_Boolean wasBusy = Standard_False;
+  Standard_Integer aNode   = aQueue.Fetch(wasBusy);
+
+  EXPECT_EQ(aNode, 0);
+  EXPECT_TRUE(wasBusy);
+
+  // After processing root, enqueue children
+  aQueue.Enqueue(1); // Left child
+  aQueue.Enqueue(2); // Right child
+
+  EXPECT_EQ(aQueue.Size(), 2);
+
+  aNode = aQueue.Fetch(wasBusy);
+  EXPECT_EQ(aNode, 1);
+
+  aNode = aQueue.Fetch(wasBusy);
+  EXPECT_EQ(aNode, 2);
+
+  aNode = aQueue.Fetch(wasBusy);
+  EXPECT_EQ(aNode, -1);
+  EXPECT_FALSE(wasBusy);
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_LinearBuilder_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_LinearBuilder_Test.cxx
new file mode 100644 (file)
index 0000000..077f445
--- /dev/null
@@ -0,0 +1,337 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_LinearBuilder.hxx>
+#include <BVH_BoxSet.hxx>
+#include <Precision.hxx>
+
+TEST(BVH_LinearBuilderTest, DefaultConstructor)
+{
+  BVH_LinearBuilder<Standard_Real, 3> aBuilder;
+
+  EXPECT_GT(aBuilder.LeafNodeSize(), 0);
+  EXPECT_GT(aBuilder.MaxTreeDepth(), 0);
+}
+
+TEST(BVH_LinearBuilderTest, CustomParameters)
+{
+  BVH_LinearBuilder<Standard_Real, 3> aBuilder(5, 20);
+
+  EXPECT_EQ(aBuilder.LeafNodeSize(), 5);
+  EXPECT_EQ(aBuilder.MaxTreeDepth(), 20);
+}
+
+TEST(BVH_LinearBuilderTest, BuildEmptySet)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>();
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_EQ(aBVH->Length(), 0);
+}
+
+TEST(BVH_LinearBuilderTest, BuildSingleElement)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>();
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  aBoxSet.Add(0, BVH_Box<Standard_Real, 3>(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0)));
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_EQ(aBVH->Length(), 1);
+  EXPECT_TRUE(aBVH->IsOuter(0));
+}
+
+TEST(BVH_LinearBuilderTest, BuildMultipleElements)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add boxes along X axis
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 2.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 2.0 + 1.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+  EXPECT_GT(aBVH->Depth(), 0);
+}
+
+TEST(BVH_LinearBuilderTest, MortonCodeSorting)
+{
+  // Linear builder uses Morton codes for spatial sorting
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add boxes in a pattern that tests Morton code sorting
+  // Diagonal pattern should group nearby boxes
+  for (int i = 0; i < 8; ++i)
+  {
+    Standard_Real x = (i & 1) ? 10.0 : 0.0;
+    Standard_Real y = (i & 2) ? 10.0 : 0.0;
+    Standard_Real z = (i & 4) ? 10.0 : 0.0;
+
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(x, y, z), BVH_Vec3d(x + 1.0, y + 1.0, z + 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+
+  // SAH should be reasonable for spatially sorted data
+  Standard_Real aSAH = aBVH->EstimateSAH();
+  EXPECT_GT(aSAH, 0.0);
+}
+
+TEST(BVH_LinearBuilderTest, LeafNodeSizeRespected)
+{
+  const int                                                aLeafSize = 3;
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(aLeafSize, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 2.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 2.0 + 1.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+
+  // Check that leaf nodes don't exceed leaf size
+  for (int i = 0; i < aBVH->Length(); ++i)
+  {
+    if (aBVH->IsOuter(i))
+    {
+      int aNbPrims = aBVH->NbPrimitives(i);
+      EXPECT_LE(aNbPrims, aLeafSize);
+    }
+  }
+}
+
+TEST(BVH_LinearBuilderTest, Build2D)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 2>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 2>(1, 32);
+  BVH_BoxSet<Standard_Real, 2> aBoxSet(aBuilder);
+
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(i * 2.0, 0.0), BVH_Vec2d(i * 2.0 + 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 2>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+}
+
+// Note: Float tests skipped due to BVH_BoxSet::Center return type issue
+
+TEST(BVH_LinearBuilderTest, LargeDataSet)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(4, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add many boxes - this tests the Morton code optimization
+  int aCount = 0;
+  for (int x = 0; x < 10; ++x)
+  {
+    for (int y = 0; y < 10; ++y)
+    {
+      for (int z = 0; z < 10; ++z)
+      {
+        BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(x * 2.0, y * 2.0, z * 2.0),
+                                       BVH_Vec3d(x * 2.0 + 1.0, y * 2.0 + 1.0, z * 2.0 + 1.0));
+        aBoxSet.Add(aCount++, aBox);
+      }
+    }
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+
+  // Verify tree covers all primitives
+  int aTotalPrims = 0;
+  for (int i = 0; i < aBVH->Length(); ++i)
+  {
+    if (aBVH->IsOuter(i))
+    {
+      aTotalPrims += aBVH->NbPrimitives(i);
+    }
+  }
+  EXPECT_EQ(aTotalPrims, 1000);
+}
+
+TEST(BVH_LinearBuilderTest, MaxDepthParameter)
+{
+  // Linear builder uses max depth parameter but may exceed it in some cases
+  const int                                                aMaxDepth = 10;
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(1, aMaxDepth);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  for (int i = 0; i < 100; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 2.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 2.0 + 1.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  // Tree should have a reasonable depth
+  EXPECT_GT(aBVH->Depth(), 0);
+  EXPECT_LT(aBVH->Depth(), 20);
+}
+
+TEST(BVH_LinearBuilderTest, ClusteredData)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Create two clusters far apart
+  // Morton codes should group each cluster together
+  for (int i = 0; i < 10; ++i)
+  {
+    // Cluster 1 near origin
+    BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(i * 0.1, i * 0.1, i * 0.1),
+                                    BVH_Vec3d(i * 0.1 + 0.1, i * 0.1 + 0.1, i * 0.1 + 0.1));
+    aBoxSet.Add(i, aBox1);
+
+    // Cluster 2 far from origin
+    BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(100.0 + i * 0.1, 100.0 + i * 0.1, 100.0 + i * 0.1),
+                                    BVH_Vec3d(100.1 + i * 0.1, 100.1 + i * 0.1, 100.1 + i * 0.1));
+    aBoxSet.Add(i + 10, aBox2);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+
+  // SAH should be reasonable for clustered data
+  Standard_Real aSAH = aBVH->EstimateSAH();
+  EXPECT_GT(aSAH, 0.0);
+}
+
+TEST(BVH_LinearBuilderTest, OverlappingBoxes)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add overlapping boxes
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 0.5, 0.0, 0.0),
+                                   BVH_Vec3d(i * 0.5 + 2.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+}
+
+TEST(BVH_LinearBuilderTest, IdenticalBoxes)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add identical boxes (same Morton code)
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GE(aBVH->Length(), 1);
+}
+
+TEST(BVH_LinearBuilderTest, CompareWithBinnedBuilder)
+{
+  // Linear builder should produce reasonable trees compared to binned builder
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aLinearBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aLinearSet(aLinearBuilder);
+
+  for (int i = 0; i < 50; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(i * 2.0, 0.0, 0.0),
+                                   BVH_Vec3d(i * 2.0 + 1.0, 1.0, 1.0));
+    aLinearSet.Add(i, aBox);
+  }
+
+  aLinearSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aLinearBVH = aLinearSet.BVH();
+
+  // Linear builder produces a valid tree
+  EXPECT_GT(aLinearBVH->Length(), 1);
+  EXPECT_GT(aLinearBVH->Depth(), 0);
+
+  // SAH should be positive
+  Standard_Real aSAH = aLinearBVH->EstimateSAH();
+  EXPECT_GT(aSAH, 0.0);
+}
+
+TEST(BVH_LinearBuilderTest, NegativeCoordinates)
+{
+  opencascade::handle<BVH_LinearBuilder<Standard_Real, 3>> aBuilder =
+    new BVH_LinearBuilder<Standard_Real, 3>(1, 32);
+  BVH_BoxSet<Standard_Real, 3> aBoxSet(aBuilder);
+
+  // Add boxes with negative coordinates
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-10.0 + i * 2.0, -5.0, -5.0),
+                                   BVH_Vec3d(-9.0 + i * 2.0, -4.0, -4.0));
+    aBoxSet.Add(i, aBox);
+  }
+
+  aBoxSet.Build();
+
+  const opencascade::handle<BVH_Tree<Standard_Real, 3>>& aBVH = aBoxSet.BVH();
+  EXPECT_GT(aBVH->Length(), 1);
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_QuickSorter_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_QuickSorter_Test.cxx
new file mode 100644 (file)
index 0000000..dadf105
--- /dev/null
@@ -0,0 +1,330 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_QuickSorter.hxx>
+#include <BVH_Triangulation.hxx>
+
+// =============================================================================
+// BVH_QuickSorter Basic Tests
+// =============================================================================
+
+TEST(BVH_QuickSorterTest, Constructor)
+{
+  BVH_QuickSorter<Standard_Real, 3> aSorterX(0);
+  BVH_QuickSorter<Standard_Real, 3> aSorterY(1);
+  BVH_QuickSorter<Standard_Real, 3> aSorterZ(2);
+
+  // Constructor should not crash
+  EXPECT_TRUE(true);
+}
+
+TEST(BVH_QuickSorterTest, SortEmptySet)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  BVH_QuickSorter<Standard_Real, 3>   aSorter(0);
+
+  // Sorting empty set should not crash
+  aSorter.Perform(&aTriangulation);
+  EXPECT_EQ(aTriangulation.Size(), 0);
+}
+
+TEST(BVH_QuickSorterTest, SortSingleElement)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0);
+  aSorter.Perform(&aTriangulation);
+
+  // Single element should remain unchanged
+  EXPECT_EQ(aTriangulation.Size(), 1);
+}
+
+// =============================================================================
+// Sorting Correctness Tests
+// =============================================================================
+
+TEST(BVH_QuickSorterTest, SortAlongXAxis)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create triangles with increasing X centroids: 5, 1, 3
+  // Triangle 0: centroid X = 5
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(4.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(5.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(6.0, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  // Triangle 1: centroid X = 1
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(2.0, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(3, 4, 5, 0));
+
+  // Triangle 2: centroid X = 3
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(2.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(3.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(4.0, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(6, 7, 8, 0));
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0); // Sort along X
+  aSorter.Perform(&aTriangulation);
+
+  // After sorting: should be ordered 1, 3, 5 (indices 1, 2, 0)
+  EXPECT_LT(aTriangulation.Center(0, 0), aTriangulation.Center(1, 0));
+  EXPECT_LT(aTriangulation.Center(1, 0), aTriangulation.Center(2, 0));
+
+  // Verify actual values
+  EXPECT_NEAR(aTriangulation.Center(0, 0), 1.0, 1e-10);
+  EXPECT_NEAR(aTriangulation.Center(1, 0), 3.0, 1e-10);
+  EXPECT_NEAR(aTriangulation.Center(2, 0), 5.0, 1e-10);
+}
+
+TEST(BVH_QuickSorterTest, SortAlongYAxis)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create triangles with Y centroids: 3, 1, 2
+  for (int i = 0; i < 3; ++i)
+  {
+    Standard_Real y = (i == 0 ? 3.0 : (i == 1 ? 1.0 : 2.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, y - 0.5, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, y, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.5, y + 0.5, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(1); // Sort along Y
+  aSorter.Perform(&aTriangulation);
+
+  // Should be ordered by Y centroid
+  EXPECT_LT(aTriangulation.Center(0, 1), aTriangulation.Center(1, 1));
+  EXPECT_LT(aTriangulation.Center(1, 1), aTriangulation.Center(2, 1));
+}
+
+TEST(BVH_QuickSorterTest, SortAlongZAxis)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create triangles with Z centroids: 2, 0, 1
+  for (int i = 0; i < 3; ++i)
+  {
+    Standard_Real z = (i == 0 ? 2.0 : (i == 1 ? 0.0 : 1.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, z));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, z));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.5, 1.0, z));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(2); // Sort along Z
+  aSorter.Perform(&aTriangulation);
+
+  // Should be ordered by Z centroid
+  EXPECT_LT(aTriangulation.Center(0, 2), aTriangulation.Center(1, 2));
+  EXPECT_LT(aTriangulation.Center(1, 2), aTriangulation.Center(2, 2));
+}
+
+TEST(BVH_QuickSorterTest, SortRangeInSet)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create 5 triangles with X centroids: 0, 1, 2, 3, 4
+  for (int i = 0; i < 5; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  // Reverse the middle 3 elements (indices 1, 2, 3) manually
+  aTriangulation.Swap(1, 3);
+
+  // Now we have centroids: 0, 3, 2, 1, 4
+  EXPECT_NEAR(aTriangulation.Center(1, 0), 3.5, 1e-10);
+  EXPECT_NEAR(aTriangulation.Center(2, 0), 2.5, 1e-10);
+  EXPECT_NEAR(aTriangulation.Center(3, 0), 1.5, 1e-10);
+
+  // Sort only range [1, 3] (middle 3 elements)
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0);
+  aSorter.Perform(&aTriangulation, 1, 3);
+
+  // Elements 0 and 4 should remain unchanged
+  EXPECT_NEAR(aTriangulation.Center(0, 0), 0.5, 1e-10);
+  EXPECT_NEAR(aTriangulation.Center(4, 0), 4.5, 1e-10);
+
+  // Elements 1-3 should be sorted: 1, 2, 3
+  EXPECT_NEAR(aTriangulation.Center(1, 0), 1.5, 1e-10);
+  EXPECT_NEAR(aTriangulation.Center(2, 0), 2.5, 1e-10);
+  EXPECT_NEAR(aTriangulation.Center(3, 0), 3.5, 1e-10);
+}
+
+// =============================================================================
+// Edge Cases and Stability Tests
+// =============================================================================
+
+TEST(BVH_QuickSorterTest, AlreadySorted)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create already sorted triangles
+  for (int i = 0; i < 10; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.9, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 0.9, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0);
+  aSorter.Perform(&aTriangulation);
+
+  // Should remain sorted
+  for (int i = 0; i < 9; ++i)
+  {
+    EXPECT_LT(aTriangulation.Center(i, 0), aTriangulation.Center(i + 1, 0));
+  }
+}
+
+TEST(BVH_QuickSorterTest, ReverseSorted)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create reverse sorted triangles
+  for (int i = 0; i < 10; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(9 - i);
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.9, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 0.9, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0);
+  aSorter.Perform(&aTriangulation);
+
+  // Should become sorted
+  for (int i = 0; i < 9; ++i)
+  {
+    EXPECT_LT(aTriangulation.Center(i, 0), aTriangulation.Center(i + 1, 0));
+  }
+}
+
+TEST(BVH_QuickSorterTest, DuplicateValues)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create triangles with duplicate centroids: 1, 2, 2, 2, 3
+  for (int i = 0; i < 5; ++i)
+  {
+    Standard_Real x = (i == 0 ? 1.0 : (i <= 3 ? 2.0 : 3.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.9, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 0.9, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0);
+  aSorter.Perform(&aTriangulation);
+
+  // Should be non-decreasing
+  for (int i = 0; i < 4; ++i)
+  {
+    EXPECT_LE(aTriangulation.Center(i, 0), aTriangulation.Center(i + 1, 0));
+  }
+}
+
+TEST(BVH_QuickSorterTest, AllSameValue)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // All triangles at same position
+  for (int i = 0; i < 5; ++i)
+  {
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.9, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.5, 0.9, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0);
+  aSorter.Perform(&aTriangulation);
+
+  // All should have same centroid
+  for (int i = 0; i < 5; ++i)
+  {
+    EXPECT_NEAR(aTriangulation.Center(i, 0), 1.467, 1e-2);
+  }
+}
+
+TEST(BVH_QuickSorterTest, LargeDataSet)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create 1000 triangles with random-ish order
+  for (int i = 0; i < 1000; ++i)
+  {
+    // Use simple pseudo-random pattern
+    Standard_Real x = static_cast<Standard_Real>((i * 37) % 1000);
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.9, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 0.9, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0);
+  aSorter.Perform(&aTriangulation);
+
+  // Verify sorted
+  for (int i = 0; i < 999; ++i)
+  {
+    EXPECT_LE(aTriangulation.Center(i, 0), aTriangulation.Center(i + 1, 0));
+  }
+}
+
+TEST(BVH_QuickSorterTest, TwoElements)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Two elements out of order
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(2.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(3.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(2.5, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.5, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(3, 4, 5, 0));
+
+  BVH_QuickSorter<Standard_Real, 3> aSorter(0);
+  aSorter.Perform(&aTriangulation);
+
+  EXPECT_LT(aTriangulation.Center(0, 0), aTriangulation.Center(1, 0));
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_RadixSorter_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_RadixSorter_Test.cxx
new file mode 100644 (file)
index 0000000..3ca48fa
--- /dev/null
@@ -0,0 +1,449 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_RadixSorter.hxx>
+
+// =============================================================================
+// Morton Code Tests
+// =============================================================================
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_Origin)
+{
+  unsigned int aCode = BVH::EncodeMortonCode(0, 0, 0);
+  EXPECT_EQ(aCode, 0u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_SingleBits)
+{
+  // X=1, Y=0, Z=0 should give 1 (bit 0)
+  unsigned int aCodeX = BVH::EncodeMortonCode(1, 0, 0);
+  EXPECT_EQ(aCodeX, 1u);
+
+  // X=0, Y=1, Z=0 should give 2 (bit 1)
+  unsigned int aCodeY = BVH::EncodeMortonCode(0, 1, 0);
+  EXPECT_EQ(aCodeY, 2u);
+
+  // X=0, Y=0, Z=1 should give 4 (bit 2)
+  unsigned int aCodeZ = BVH::EncodeMortonCode(0, 0, 1);
+  EXPECT_EQ(aCodeZ, 4u);
+
+  // X=1, Y=1, Z=1 should give 7 (bits 0, 1, 2)
+  unsigned int aCodeXYZ = BVH::EncodeMortonCode(1, 1, 1);
+  EXPECT_EQ(aCodeXYZ, 7u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_Interleaving)
+{
+  // X=2 (binary 10), Y=0, Z=0
+  // Should produce binary ...001000 = 8
+  unsigned int aCode1 = BVH::EncodeMortonCode(2, 0, 0);
+  EXPECT_EQ(aCode1, 8u);
+
+  // X=0, Y=2, Z=0
+  // Should produce binary ...010000 = 16
+  unsigned int aCode2 = BVH::EncodeMortonCode(0, 2, 0);
+  EXPECT_EQ(aCode2, 16u);
+
+  // X=0, Y=0, Z=2
+  // Should produce binary ...100000 = 32
+  unsigned int aCode3 = BVH::EncodeMortonCode(0, 0, 2);
+  EXPECT_EQ(aCode3, 32u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_MaxValues)
+{
+  // Test with maximum 10-bit values (1023)
+  unsigned int aCode = BVH::EncodeMortonCode(1023, 1023, 1023);
+
+  // All 30 bits should be set
+  EXPECT_EQ(aCode, 0x3FFFFFFFu);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_Symmetry)
+{
+  // Swapping coordinates should produce predictable results
+  unsigned int aCode1 = BVH::EncodeMortonCode(100, 200, 300);
+  unsigned int aCode2 = BVH::EncodeMortonCode(200, 100, 300);
+  unsigned int aCode3 = BVH::EncodeMortonCode(100, 300, 200);
+
+  // Codes should be different
+  EXPECT_NE(aCode1, aCode2);
+  EXPECT_NE(aCode1, aCode3);
+  EXPECT_NE(aCode2, aCode3);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_Ordering)
+{
+  // Points closer in space should have closer Morton codes
+  // (0, 0, 0) vs (1, 0, 0) should be closer than (0, 0, 0) vs (0, 0, 1023)
+  unsigned int aCode000    = BVH::EncodeMortonCode(0, 0, 0);
+  unsigned int aCode100    = BVH::EncodeMortonCode(1, 0, 0);
+  unsigned int aCode001023 = BVH::EncodeMortonCode(0, 0, 1023);
+
+  // (0,0,0) to (1,0,0) difference should be small
+  unsigned int aDiff1 = aCode100 - aCode000;
+
+  // (0,0,0) to (0,0,1023) difference should be large
+  unsigned int aDiff2 = aCode001023 - aCode000;
+
+  EXPECT_LT(aDiff1, aDiff2);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_MidValues)
+{
+  // Test with mid-range values
+  unsigned int aCode = BVH::EncodeMortonCode(512, 512, 512);
+
+  // Should be non-zero and within 30-bit range
+  EXPECT_GT(aCode, 0u);
+  EXPECT_LE(aCode, 0x3FFFFFFFu);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_PowersOfTwo)
+{
+  // Test powers of two for each axis
+  unsigned int aCode4  = BVH::EncodeMortonCode(4, 0, 0);  // X=100 binary
+  unsigned int aCode8  = BVH::EncodeMortonCode(8, 0, 0);  // X=1000 binary
+  unsigned int aCode16 = BVH::EncodeMortonCode(16, 0, 0); // X=10000 binary
+
+  // Each should be 8x the previous (3 bit positions apart in Morton code)
+  EXPECT_EQ(aCode8, aCode4 * 8);
+  EXPECT_EQ(aCode16, aCode8 * 8);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_ConsistentEncoding)
+{
+  // Same inputs should always produce same outputs
+  for (int i = 0; i < 100; ++i)
+  {
+    unsigned int aCode1 = BVH::EncodeMortonCode(100, 200, 300);
+    unsigned int aCode2 = BVH::EncodeMortonCode(100, 200, 300);
+    EXPECT_EQ(aCode1, aCode2);
+  }
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_BoundaryValues)
+{
+  // Test boundary values near max (1023)
+  unsigned int aCode1022 = BVH::EncodeMortonCode(1022, 1022, 1022);
+  unsigned int aCode1023 = BVH::EncodeMortonCode(1023, 1023, 1023);
+
+  EXPECT_LT(aCode1022, aCode1023);
+  EXPECT_EQ(aCode1023, 0x3FFFFFFFu);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_SmallValues)
+{
+  // Test small values
+  unsigned int aCode2 = BVH::EncodeMortonCode(2, 2, 2);
+  unsigned int aCode3 = BVH::EncodeMortonCode(3, 3, 3);
+
+  // Both should be small but aCode3 > aCode2
+  EXPECT_LT(aCode2, 100u);
+  EXPECT_LT(aCode3, 100u);
+  EXPECT_LT(aCode2, aCode3);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_SingleAxisValues)
+{
+  // Test with value only in one axis
+  unsigned int aCodeX = BVH::EncodeMortonCode(255, 0, 0);
+  unsigned int aCodeY = BVH::EncodeMortonCode(0, 255, 0);
+  unsigned int aCodeZ = BVH::EncodeMortonCode(0, 0, 255);
+
+  // X bits in positions 0, 3, 6, ...
+  // Y bits in positions 1, 4, 7, ...
+  // Z bits in positions 2, 5, 8, ...
+  EXPECT_NE(aCodeX, aCodeY);
+  EXPECT_NE(aCodeY, aCodeZ);
+  EXPECT_NE(aCodeX, aCodeZ);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_SequentialXValues)
+{
+  // Test that sequential X values produce increasing codes
+  unsigned int aPrev = BVH::EncodeMortonCode(0, 500, 500);
+  for (unsigned int x = 1; x <= 10; ++x)
+  {
+    unsigned int aCurr = BVH::EncodeMortonCode(x, 500, 500);
+    EXPECT_GT(aCurr, aPrev);
+    aPrev = aCurr;
+  }
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_SequentialYValues)
+{
+  // Test that sequential Y values produce increasing codes
+  unsigned int aPrev = BVH::EncodeMortonCode(500, 0, 500);
+  for (unsigned int y = 1; y <= 10; ++y)
+  {
+    unsigned int aCurr = BVH::EncodeMortonCode(500, y, 500);
+    EXPECT_GT(aCurr, aPrev);
+    aPrev = aCurr;
+  }
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_SequentialZValues)
+{
+  // Test that sequential Z values produce increasing codes
+  unsigned int aPrev = BVH::EncodeMortonCode(500, 500, 0);
+  for (unsigned int z = 1; z <= 10; ++z)
+  {
+    unsigned int aCurr = BVH::EncodeMortonCode(500, 500, z);
+    EXPECT_GT(aCurr, aPrev);
+    aPrev = aCurr;
+  }
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_ZeroInDimensions)
+{
+  // Test with zeros in different dimensions
+  unsigned int aCodeXY = BVH::EncodeMortonCode(100, 100, 0);
+  unsigned int aCodeXZ = BVH::EncodeMortonCode(100, 0, 100);
+  unsigned int aCodeYZ = BVH::EncodeMortonCode(0, 100, 100);
+
+  // All should be different
+  EXPECT_NE(aCodeXY, aCodeXZ);
+  EXPECT_NE(aCodeXY, aCodeYZ);
+  EXPECT_NE(aCodeXZ, aCodeYZ);
+
+  // All should be non-zero
+  EXPECT_GT(aCodeXY, 0u);
+  EXPECT_GT(aCodeXZ, 0u);
+  EXPECT_GT(aCodeYZ, 0u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_UpperByteBits)
+{
+  // Test values that require upper byte (values >= 256)
+  unsigned int aCode256 = BVH::EncodeMortonCode(256, 0, 0);
+  unsigned int aCode512 = BVH::EncodeMortonCode(512, 0, 0);
+  unsigned int aCode768 = BVH::EncodeMortonCode(768, 0, 0);
+
+  EXPECT_GT(aCode256, 0u);
+  EXPECT_GT(aCode512, aCode256);
+  EXPECT_GT(aCode768, aCode512);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_AllOnes)
+{
+  // 255 = 0xFF (all ones in lower byte)
+  unsigned int aCode = BVH::EncodeMortonCode(255, 255, 255);
+
+  // Should be less than max (1023, 1023, 1023)
+  EXPECT_LT(aCode, 0x3FFFFFFFu);
+  EXPECT_GT(aCode, 0u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_LocalityPreservation)
+{
+  // Points close in space should have relatively close Morton codes
+  unsigned int aCenter = BVH::EncodeMortonCode(512, 512, 512);
+  unsigned int aNear1  = BVH::EncodeMortonCode(513, 512, 512);
+  unsigned int aNear2  = BVH::EncodeMortonCode(512, 513, 512);
+  unsigned int aFar    = BVH::EncodeMortonCode(0, 0, 0);
+
+  // Near points should have smaller difference than far point
+  unsigned int aDiffNear1 = (aNear1 > aCenter) ? (aNear1 - aCenter) : (aCenter - aNear1);
+  unsigned int aDiffNear2 = (aNear2 > aCenter) ? (aNear2 - aCenter) : (aCenter - aNear2);
+  unsigned int aDiffFar   = (aFar > aCenter) ? (aFar - aCenter) : (aCenter - aFar);
+
+  EXPECT_LT(aDiffNear1, aDiffFar);
+  EXPECT_LT(aDiffNear2, aDiffFar);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_BitPattern3)
+{
+  // X=3 (binary 11), Y=0, Z=0
+  // Interleaved: ...001001 = 9
+  unsigned int aCode = BVH::EncodeMortonCode(3, 0, 0);
+  EXPECT_EQ(aCode, 9u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_BitPattern7)
+{
+  // X=7 (binary 111), Y=0, Z=0
+  // Interleaved: 001001001 = 73
+  unsigned int aCode = BVH::EncodeMortonCode(7, 0, 0);
+  EXPECT_EQ(aCode, 73u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_XYEqual)
+{
+  // When X = Y and Z = 0, code should follow pattern
+  unsigned int aCode1 = BVH::EncodeMortonCode(1, 1, 0);
+  unsigned int aCode2 = BVH::EncodeMortonCode(2, 2, 0);
+  unsigned int aCode3 = BVH::EncodeMortonCode(4, 4, 0);
+
+  // X=1, Y=1, Z=0: bits at 0,1 -> 3
+  EXPECT_EQ(aCode1, 3u);
+
+  // X=2, Y=2, Z=0: bits at 3,4 -> 24
+  EXPECT_EQ(aCode2, 24u);
+
+  // X=4, Y=4, Z=0: bits at 6,7 -> 192
+  EXPECT_EQ(aCode3, 192u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_AllAxesEqual)
+{
+  // When all axes are equal
+  unsigned int aCode1 = BVH::EncodeMortonCode(1, 1, 1);
+  unsigned int aCode2 = BVH::EncodeMortonCode(2, 2, 2);
+
+  // X=1, Y=1, Z=1: bits at 0,1,2 -> 7
+  EXPECT_EQ(aCode1, 7u);
+
+  // X=2, Y=2, Z=2: bits at 3,4,5 -> 56
+  EXPECT_EQ(aCode2, 56u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_HalfMax)
+{
+  // Test with half of maximum (512)
+  unsigned int aCode = BVH::EncodeMortonCode(512, 512, 512);
+
+  // Should be in upper half of range
+  EXPECT_GT(aCode, 0x1FFFFFFFu);
+  EXPECT_LE(aCode, 0x3FFFFFFFu);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_QuarterMax)
+{
+  // Test with quarter of maximum (256)
+  unsigned int aCode = BVH::EncodeMortonCode(256, 256, 256);
+
+  // Should be less than half max
+  EXPECT_GT(aCode, 0u);
+  EXPECT_LT(aCode, 0x3FFFFFFFu);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_DifferentScales)
+{
+  // Test that scaling all coordinates by 2 shifts the Morton code
+  unsigned int aCode1 = BVH::EncodeMortonCode(100, 100, 100);
+  unsigned int aCode2 = BVH::EncodeMortonCode(200, 200, 200);
+
+  // Code2 should be larger
+  EXPECT_GT(aCode2, aCode1);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_AdjacentCells)
+{
+  // Test adjacent cells in X direction
+  unsigned int aCode1 = BVH::EncodeMortonCode(100, 100, 100);
+  unsigned int aCode2 = BVH::EncodeMortonCode(101, 100, 100);
+
+  // Difference should be 1 (X bit is at position 0)
+  EXPECT_EQ(aCode2 - aCode1, 1u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_AdjacentCellsY)
+{
+  // Test adjacent cells in Y direction
+  unsigned int aCode1 = BVH::EncodeMortonCode(100, 100, 100);
+  unsigned int aCode2 = BVH::EncodeMortonCode(100, 101, 100);
+
+  // Difference should be 2 (Y bit is at position 1)
+  EXPECT_EQ(aCode2 - aCode1, 2u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_AdjacentCellsZ)
+{
+  // Test adjacent cells in Z direction
+  unsigned int aCode1 = BVH::EncodeMortonCode(100, 100, 100);
+  unsigned int aCode2 = BVH::EncodeMortonCode(100, 100, 101);
+
+  // Difference should be 4 (Z bit is at position 2)
+  EXPECT_EQ(aCode2 - aCode1, 4u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_MonotonicX)
+{
+  // Verify monotonicity when only X changes
+  unsigned int aPrev = 0;
+  for (unsigned int x = 0; x < 100; ++x)
+  {
+    unsigned int aCode = BVH::EncodeMortonCode(x, 0, 0);
+    EXPECT_GE(aCode, aPrev);
+    aPrev = aCode;
+  }
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_MonotonicY)
+{
+  // Verify monotonicity when only Y changes
+  unsigned int aPrev = 0;
+  for (unsigned int y = 0; y < 100; ++y)
+  {
+    unsigned int aCode = BVH::EncodeMortonCode(0, y, 0);
+    EXPECT_GE(aCode, aPrev);
+    aPrev = aCode;
+  }
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_MonotonicZ)
+{
+  // Verify monotonicity when only Z changes
+  unsigned int aPrev = 0;
+  for (unsigned int z = 0; z < 100; ++z)
+  {
+    unsigned int aCode = BVH::EncodeMortonCode(0, 0, z);
+    EXPECT_GE(aCode, aPrev);
+    aPrev = aCode;
+  }
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_NoOverflow)
+{
+  // Verify no overflow with maximum values
+  unsigned int aCode = BVH::EncodeMortonCode(1023, 1023, 1023);
+
+  // Should fit in 30 bits
+  EXPECT_EQ(aCode & 0xC0000000u, 0u);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_UniqueForDifferentInputs)
+{
+  // Different inputs should produce different codes
+  unsigned int aCode1 = BVH::EncodeMortonCode(100, 200, 300);
+  unsigned int aCode2 = BVH::EncodeMortonCode(100, 200, 301);
+  unsigned int aCode3 = BVH::EncodeMortonCode(100, 201, 300);
+  unsigned int aCode4 = BVH::EncodeMortonCode(101, 200, 300);
+
+  EXPECT_NE(aCode1, aCode2);
+  EXPECT_NE(aCode1, aCode3);
+  EXPECT_NE(aCode1, aCode4);
+  EXPECT_NE(aCode2, aCode3);
+  EXPECT_NE(aCode2, aCode4);
+  EXPECT_NE(aCode3, aCode4);
+}
+
+TEST(BVH_RadixSorterTest, EncodeMortonCode_SpecificPattern)
+{
+  // Test specific known pattern
+  // X=5 (101), Y=3 (011), Z=6 (110)
+  unsigned int aCode = BVH::EncodeMortonCode(5, 3, 6);
+
+  // Morton code interleaves bits: X at 3i, Y at 3i+1, Z at 3i+2
+  // The exact value depends on the interleaving order
+  // Just verify it's non-zero and within range
+  EXPECT_GT(aCode, 0u);
+  EXPECT_LE(aCode, 0x3FFFFFFFu);
+
+  // Also verify consistency
+  unsigned int aCode2 = BVH::EncodeMortonCode(5, 3, 6);
+  EXPECT_EQ(aCode, aCode2);
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_Ray_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_Ray_Test.cxx
new file mode 100644 (file)
index 0000000..c6728a4
--- /dev/null
@@ -0,0 +1,268 @@
+// Created on: 2025-01-20
+// Copyright (c) 2025 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.
+
+#include <BVH_Ray.hxx>
+
+#include <gtest/gtest.h>
+
+// =======================================================================================
+// Tests for BVH_Ray
+// =======================================================================================
+
+TEST(BVH_RayTest, ConstructorBasic)
+{
+  BVH_Vec3d aOrigin(1.0, 2.0, 3.0);
+  BVH_Vec3d aDirection(0.0, 1.0, 0.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Origin.x(), 1.0);
+  EXPECT_EQ(aRay.Origin.y(), 2.0);
+  EXPECT_EQ(aRay.Origin.z(), 3.0);
+
+  EXPECT_EQ(aRay.Direct.x(), 0.0);
+  EXPECT_EQ(aRay.Direct.y(), 1.0);
+  EXPECT_EQ(aRay.Direct.z(), 0.0);
+}
+
+TEST(BVH_RayTest, DirectionStorage)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(2.0, 4.0, 8.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  // Verify direction is stored correctly
+  EXPECT_EQ(aRay.Direct.x(), 2.0);
+  EXPECT_EQ(aRay.Direct.y(), 4.0);
+  EXPECT_EQ(aRay.Direct.z(), 8.0);
+}
+
+TEST(BVH_RayTest, NegativeDirection)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(-2.0, -4.0, -1.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), -2.0);
+  EXPECT_EQ(aRay.Direct.y(), -4.0);
+  EXPECT_EQ(aRay.Direct.z(), -1.0);
+}
+
+TEST(BVH_RayTest, ZeroComponent)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(1.0, 0.0, 1.0); // Zero Y component
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 1.0);
+  EXPECT_EQ(aRay.Direct.y(), 0.0);
+  EXPECT_EQ(aRay.Direct.z(), 1.0);
+}
+
+TEST(BVH_RayTest, AllZeroDirection)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(0.0, 0.0, 0.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  // All components should be zero
+  EXPECT_EQ(aRay.Direct.x(), 0.0);
+  EXPECT_EQ(aRay.Direct.y(), 0.0);
+  EXPECT_EQ(aRay.Direct.z(), 0.0);
+}
+
+TEST(BVH_RayTest, DefaultConstructor)
+{
+  BVH_Ray<Standard_Real, 3> aRay;
+
+  // Default constructed ray should have zero origin and direction
+  EXPECT_EQ(aRay.Origin.x(), 0.0);
+  EXPECT_EQ(aRay.Origin.y(), 0.0);
+  EXPECT_EQ(aRay.Origin.z(), 0.0);
+
+  EXPECT_EQ(aRay.Direct.x(), 0.0);
+  EXPECT_EQ(aRay.Direct.y(), 0.0);
+  EXPECT_EQ(aRay.Direct.z(), 0.0);
+}
+
+TEST(BVH_RayTest, Ray2D)
+{
+  BVH_Vec2d aOrigin(1.0, 2.0);
+  BVH_Vec2d aDirection(3.0, 4.0);
+
+  BVH_Ray<Standard_Real, 2> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Origin.x(), 1.0);
+  EXPECT_EQ(aRay.Origin.y(), 2.0);
+
+  EXPECT_EQ(aRay.Direct.x(), 3.0);
+  EXPECT_EQ(aRay.Direct.y(), 4.0);
+}
+
+TEST(BVH_RayTest, NormalizedDirection)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(1.0, 0.0, 0.0); // Unit vector along X
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 1.0);
+  EXPECT_EQ(aRay.Direct.y(), 0.0);
+  EXPECT_EQ(aRay.Direct.z(), 0.0);
+}
+
+TEST(BVH_RayTest, VerySmallDirection)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(1e-20, 1e-20, 1e-20);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  // Should handle very small values correctly
+  EXPECT_EQ(aRay.Direct.x(), 1e-20);
+  EXPECT_EQ(aRay.Direct.y(), 1e-20);
+  EXPECT_EQ(aRay.Direct.z(), 1e-20);
+}
+
+TEST(BVH_RayTest, MixedZeroNonZero)
+{
+  BVH_Vec3d aOrigin(1.0, 2.0, 3.0);
+  BVH_Vec3d aDirection(0.0, 2.0, 0.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 0.0);
+  EXPECT_EQ(aRay.Direct.y(), 2.0);
+  EXPECT_EQ(aRay.Direct.z(), 0.0);
+}
+
+TEST(BVH_RayTest, FloatPrecision)
+{
+  BVH_Vec3f aOrigin(1.0f, 2.0f, 3.0f);
+  BVH_Vec3f aDirection(2.0f, 4.0f, 8.0f);
+
+  BVH_Ray<Standard_ShortReal, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 2.0f);
+  EXPECT_EQ(aRay.Direct.y(), 4.0f);
+  EXPECT_EQ(aRay.Direct.z(), 8.0f);
+}
+
+TEST(BVH_RayTest, ConstexprConstructor)
+{
+  // This test verifies that the constructor is truly constexpr
+  constexpr BVH_Vec3d                 aOrigin(1.0, 2.0, 3.0);
+  constexpr BVH_Vec3d                 aDirection(1.0, 1.0, 1.0);
+  constexpr BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  // If this compiles, constexpr works
+  EXPECT_EQ(aRay.Origin.x(), 1.0);
+}
+
+TEST(BVH_RayTest, DiagonalRay)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(1.0, 1.0, 1.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 1.0);
+  EXPECT_EQ(aRay.Direct.y(), 1.0);
+  EXPECT_EQ(aRay.Direct.z(), 1.0);
+}
+
+TEST(BVH_RayTest, NegativeOrigin)
+{
+  BVH_Vec3d aOrigin(-10.0, -20.0, -30.0);
+  BVH_Vec3d aDirection(1.0, 2.0, 3.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Origin.x(), -10.0);
+  EXPECT_EQ(aRay.Origin.y(), -20.0);
+  EXPECT_EQ(aRay.Origin.z(), -30.0);
+
+  EXPECT_EQ(aRay.Direct.x(), 1.0);
+  EXPECT_EQ(aRay.Direct.y(), 2.0);
+  EXPECT_EQ(aRay.Direct.z(), 3.0);
+}
+
+TEST(BVH_RayTest, AxisAlignedX)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(1.0, 0.0, 0.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 1.0);
+  EXPECT_EQ(aRay.Direct.y(), 0.0);
+  EXPECT_EQ(aRay.Direct.z(), 0.0);
+}
+
+TEST(BVH_RayTest, AxisAlignedY)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(0.0, 1.0, 0.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 0.0);
+  EXPECT_EQ(aRay.Direct.y(), 1.0);
+  EXPECT_EQ(aRay.Direct.z(), 0.0);
+}
+
+TEST(BVH_RayTest, AxisAlignedZ)
+{
+  BVH_Vec3d aOrigin(0.0, 0.0, 0.0);
+  BVH_Vec3d aDirection(0.0, 0.0, 1.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 0.0);
+  EXPECT_EQ(aRay.Direct.y(), 0.0);
+  EXPECT_EQ(aRay.Direct.z(), 1.0);
+}
+
+TEST(BVH_RayTest, LargeValues)
+{
+  BVH_Vec3d aOrigin(1e6, 2e6, 3e6);
+  BVH_Vec3d aDirection(1e3, 2e3, 4e3);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  EXPECT_EQ(aRay.Direct.x(), 1e3);
+  EXPECT_EQ(aRay.Direct.y(), 2e3);
+  EXPECT_EQ(aRay.Direct.z(), 4e3);
+}
+
+TEST(BVH_RayTest, OriginAndDirectionPreservation)
+{
+  BVH_Vec3d aOrigin(1.0, 2.0, 3.0);
+  BVH_Vec3d aDirection(2.0, 4.0, 8.0);
+
+  BVH_Ray<Standard_Real, 3> aRay(aOrigin, aDirection);
+
+  // Verify that both origin and direction are preserved exactly
+  EXPECT_EQ(aRay.Origin.x(), aOrigin.x());
+  EXPECT_EQ(aRay.Origin.y(), aOrigin.y());
+  EXPECT_EQ(aRay.Origin.z(), aOrigin.z());
+
+  EXPECT_EQ(aRay.Direct.x(), aDirection.x());
+  EXPECT_EQ(aRay.Direct.y(), aDirection.y());
+  EXPECT_EQ(aRay.Direct.z(), aDirection.z());
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_SpatialMedianBuilder_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_SpatialMedianBuilder_Test.cxx
new file mode 100644 (file)
index 0000000..baffdbb
--- /dev/null
@@ -0,0 +1,412 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_SpatialMedianBuilder.hxx>
+#include <BVH_Triangulation.hxx>
+
+// Helper function to compute bounding box for a set
+template <class T, int N>
+BVH_Box<T, N> ComputeSetBox(BVH_Set<T, N>* theSet)
+{
+  BVH_Box<T, N>          aBox;
+  const Standard_Integer aSize = theSet->Size();
+  for (Standard_Integer i = 0; i < aSize; ++i)
+  {
+    aBox.Combine(theSet->Box(i));
+  }
+  return aBox;
+}
+
+// =============================================================================
+// BVH_SpatialMedianBuilder Basic Tests
+// =============================================================================
+
+TEST(BVH_SpatialMedianBuilderTest, DefaultConstructor)
+{
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder;
+
+  EXPECT_EQ(aBuilder.LeafNodeSize(), 5);
+  EXPECT_EQ(aBuilder.MaxTreeDepth(), 32);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, CustomParameters)
+{
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder(10, 20, Standard_True);
+
+  EXPECT_EQ(aBuilder.LeafNodeSize(), 10);
+  EXPECT_EQ(aBuilder.MaxTreeDepth(), 20);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, BuildEmptySet)
+{
+  BVH_Triangulation<Standard_Real, 3>        aTriangulation;
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder;
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  EXPECT_EQ(aTree.Length(), 0);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, BuildSingleTriangle)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder;
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  EXPECT_EQ(aTree.Length(), 1);
+  EXPECT_TRUE(aTree.IsOuter(0));
+  EXPECT_EQ(aTree.NbPrimitives(0), 1);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, BuildMultipleTriangles)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 10;
+
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder;
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  EXPECT_GT(aTree.Length(), 0);
+  EXPECT_GE(aTree.Depth(), 1);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, LeafNodeSizeRespected)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 20;
+
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  const Standard_Integer                     aLeafSize = 3;
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder(aLeafSize, 32);
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  // Check all leaf nodes respect the leaf size
+  for (Standard_Integer i = 0; i < aTree.Length(); ++i)
+  {
+    if (aTree.IsOuter(i))
+    {
+      EXPECT_LE(aTree.NbPrimitives(i), aLeafSize);
+    }
+  }
+}
+
+TEST(BVH_SpatialMedianBuilderTest, MaxDepthRespected)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 100;
+
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  const Standard_Integer                     aMaxDepth = 5;
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder(1, aMaxDepth);
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  EXPECT_LE(aTree.Depth(), aMaxDepth);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, SplitsAtSpatialMedian)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 8;
+
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder(2, 32);
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  // For uniformly distributed data, spatial median should create balanced splits
+  // Root node should have roughly equal children
+  if (aTree.Length() > 0 && !aTree.IsOuter(0))
+  {
+    Standard_Integer aLeftChild  = aTree.Child<0>(0);
+    Standard_Integer aRightChild = aTree.Child<1>(0);
+
+    if (aLeftChild != -1 && aRightChild != -1)
+    {
+      Standard_Integer aLeftPrims  = aTree.NbPrimitives(aLeftChild);
+      Standard_Integer aRightPrims = aTree.NbPrimitives(aRightChild);
+
+      // For 8 triangles, expect roughly 4-4 split (allowing some tolerance)
+      EXPECT_GE(aLeftPrims, 2);
+      EXPECT_LE(aLeftPrims, 6);
+      EXPECT_GE(aRightPrims, 2);
+      EXPECT_LE(aRightPrims, 6);
+    }
+  }
+}
+
+TEST(BVH_SpatialMedianBuilderTest, Build2D)
+{
+  BVH_Triangulation<Standard_Real, 2> aTriangulation;
+  const int                           aNumTriangles = 10;
+
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+
+    BVH::Array<Standard_Real, 2>::Append(aTriangulation.Vertices, BVH_Vec2d(x, 0.0));
+    BVH::Array<Standard_Real, 2>::Append(aTriangulation.Vertices, BVH_Vec2d(x + 0.5, 1.0));
+    BVH::Array<Standard_Real, 2>::Append(aTriangulation.Vertices, BVH_Vec2d(x + 1.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Tree<Standard_Real, 2>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 2> aBuilder;
+
+  BVH_Box<Standard_Real, 2> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  EXPECT_GT(aTree.Length(), 0);
+  EXPECT_GE(aTree.Depth(), 1);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, UseMainAxisFlag)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 20;
+
+  // Create elongated distribution along X axis
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i * 10);
+
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Tree<Standard_Real, 3>                 aTreeMainAxis;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilderMainAxis(5, 32, Standard_True);
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilderMainAxis.Build(&aTriangulation, &aTreeMainAxis, aBox);
+
+  EXPECT_GT(aTreeMainAxis.Length(), 0);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, CompareWithBinnedBuilder)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 50;
+
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+
+  // Build with SpatialMedianBuilder (uses 2 bins)
+  BVH_Tree<Standard_Real, 3>                 aTreeMedian;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilderMedian;
+  aBuilderMedian.Build(&aTriangulation, &aTreeMedian, aBox);
+
+  // Build with BinnedBuilder with 2 bins (should be identical)
+  BVH_Tree<Standard_Real, 3>             aTreeBinned;
+  BVH_BinnedBuilder<Standard_Real, 3, 2> aBuilderBinned;
+  aBuilderBinned.Build(&aTriangulation, &aTreeBinned, aBox);
+
+  // Both should produce trees (exact structure may vary but both should be valid)
+  EXPECT_EQ(aTreeMedian.Length(), aTreeBinned.Length());
+  EXPECT_EQ(aTreeMedian.Depth(), aTreeBinned.Depth());
+}
+
+TEST(BVH_SpatialMedianBuilderTest, ClusteredData)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 30;
+
+  // Create two clusters: 15 triangles at x=0, 15 at x=100
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = (i < 15) ? 0.0 : 100.0;
+    Standard_Real y = static_cast<Standard_Real>(i % 15);
+
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, y, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, y + 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, y, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder;
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  // Spatial median should split the two clusters
+  EXPECT_GT(aTree.Length(), 0);
+  EXPECT_GE(aTree.Depth(), 1);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, FloatPrecision)
+{
+  BVH_Triangulation<Standard_ShortReal, 3> aTriangulation;
+  const int                                aNumTriangles = 10;
+
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_ShortReal x = static_cast<Standard_ShortReal>(i);
+
+    BVH::Array<Standard_ShortReal, 3>::Append(aTriangulation.Vertices,
+                                              NCollection_Vec3<Standard_ShortReal>(x, 0.0f, 0.0f));
+    BVH::Array<Standard_ShortReal, 3>::Append(
+      aTriangulation.Vertices,
+      NCollection_Vec3<Standard_ShortReal>(x + 0.5f, 1.0f, 0.0f));
+    BVH::Array<Standard_ShortReal, 3>::Append(
+      aTriangulation.Vertices,
+      NCollection_Vec3<Standard_ShortReal>(x + 1.0f, 0.0f, 0.0f));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Tree<Standard_ShortReal, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_ShortReal, 3> aBuilder;
+
+  BVH_Box<Standard_ShortReal, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  EXPECT_GT(aTree.Length(), 0);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, IdenticalBoxes)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 10;
+
+  // All triangles at the same position
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 1.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder;
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  // Should create a single leaf containing all triangles
+  EXPECT_GT(aTree.Length(), 0);
+}
+
+TEST(BVH_SpatialMedianBuilderTest, LargeDataSet)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+  const int                           aNumTriangles = 500;
+
+  // Random-ish distribution
+  for (int i = 0; i < aNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>((i * 7) % 100);
+    Standard_Real y = static_cast<Standard_Real>((i * 11) % 100);
+    Standard_Real z = static_cast<Standard_Real>((i * 13) % 100);
+
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, y, z));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, y + 1.0, z));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, y, z + 1.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_Tree<Standard_Real, 3>                 aTree;
+  BVH_SpatialMedianBuilder<Standard_Real, 3> aBuilder;
+
+  BVH_Box<Standard_Real, 3> aBox = ComputeSetBox(&aTriangulation);
+  aBuilder.Build(&aTriangulation, &aTree, aBox);
+
+  EXPECT_GT(aTree.Length(), 0);
+  EXPECT_GT(aTree.Depth(), 5); // Large dataset should produce deeper tree
+
+  // Estimate SAH quality
+  Standard_Real aSAH = aTree.EstimateSAH();
+  EXPECT_GT(aSAH, 0.0);
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_SweepPlaneBuilder_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_SweepPlaneBuilder_Test.cxx
new file mode 100644 (file)
index 0000000..32e62b0
--- /dev/null
@@ -0,0 +1,320 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_SweepPlaneBuilder.hxx>
+#include <BVH_Triangulation.hxx>
+
+// Helper to compute bounding box for BVH_Set (avoids cached empty box issue)
+template <class T, int N>
+BVH_Box<T, N> ComputeSetBox(BVH_Set<T, N>* theSet)
+{
+  BVH_Box<T, N>          aBox;
+  const Standard_Integer aSize = theSet->Size();
+  for (Standard_Integer i = 0; i < aSize; ++i)
+  {
+    aBox.Combine(theSet->Box(i));
+  }
+  return aBox;
+}
+
+// =============================================================================
+// BVH_SweepPlaneBuilder Construction Tests
+// =============================================================================
+
+TEST(BVH_SweepPlaneBuilderTest, DefaultConstructor)
+{
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder;
+  EXPECT_EQ(aBuilder.LeafNodeSize(), BVH_Constants_LeafNodeSizeDefault);
+  EXPECT_EQ(aBuilder.MaxTreeDepth(), BVH_Constants_MaxTreeDepth);
+}
+
+TEST(BVH_SweepPlaneBuilderTest, CustomParameters)
+{
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder(5, 20, 1);
+  EXPECT_EQ(aBuilder.LeafNodeSize(), 5);
+  EXPECT_EQ(aBuilder.MaxTreeDepth(), 20);
+}
+
+// =============================================================================
+// BVH Building Tests
+// =============================================================================
+
+TEST(BVH_SweepPlaneBuilderTest, BuildEmptyTriangulation)
+{
+  BVH_Triangulation<Standard_Real, 3>     aTriangulation;
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder;
+
+  BVH_Tree<Standard_Real, 3> aBVH;
+  BVH_Box<Standard_Real, 3>  aBox;
+
+  aBuilder.Build(&aTriangulation, &aBVH, aBox);
+
+  EXPECT_EQ(aBVH.Length(), 0);
+}
+
+TEST(BVH_SweepPlaneBuilderTest, BuildSingleTriangle)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Add vertices
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 1.0, 0.0));
+
+  // Add triangle
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder(1, 32, 1);
+  BVH_Tree<Standard_Real, 3>              aBVH;
+  BVH_Box<Standard_Real, 3>               aBox = ComputeSetBox(&aTriangulation);
+
+  aBuilder.Build(&aTriangulation, &aBVH, aBox);
+
+  // Single triangle should create one leaf node
+  EXPECT_EQ(aBVH.Length(), 1);
+  EXPECT_TRUE(aBVH.IsOuter(0));
+  EXPECT_EQ(aBVH.BegPrimitive(0), 0);
+  EXPECT_EQ(aBVH.EndPrimitive(0), 0);
+}
+
+TEST(BVH_SweepPlaneBuilderTest, BuildTwoTriangles)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create two triangles forming a quad
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 1.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 1.0, 0.0));
+
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 2, 3, 0));
+
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder(1, 32, 1);
+  BVH_Tree<Standard_Real, 3>              aBVH;
+  BVH_Box<Standard_Real, 3>               aBox = ComputeSetBox(&aTriangulation);
+
+  aBuilder.Build(&aTriangulation, &aBVH, aBox);
+
+  // Two triangles with leaf size 1 should create internal nodes
+  EXPECT_GT(aBVH.Length(), 2);
+  EXPECT_GE(aBVH.Depth(), 1);
+}
+
+TEST(BVH_SweepPlaneBuilderTest, BuildMultipleTriangles_Grid)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create a 2x2 grid of quads (8 triangles)
+  const int aGridSize = 3;
+  for (int i = 0; i < aGridSize; ++i)
+  {
+    for (int j = 0; j < aGridSize; ++j)
+    {
+      BVH::Array<Standard_Real, 3>::Append(
+        aTriangulation.Vertices,
+        BVH_Vec3d(static_cast<Standard_Real>(i), static_cast<Standard_Real>(j), 0.0));
+    }
+  }
+
+  // Add triangles for the grid
+  for (int i = 0; i < aGridSize - 1; ++i)
+  {
+    for (int j = 0; j < aGridSize - 1; ++j)
+    {
+      int aV0 = i * aGridSize + j;
+      int aV1 = i * aGridSize + (j + 1);
+      int aV2 = (i + 1) * aGridSize + (j + 1);
+      int aV3 = (i + 1) * aGridSize + j;
+
+      BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(aV0, aV1, aV2, 0));
+      BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(aV0, aV2, aV3, 0));
+    }
+  }
+
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder(2, 32, 1);
+  BVH_Tree<Standard_Real, 3>              aBVH;
+  BVH_Box<Standard_Real, 3>               aBox = ComputeSetBox(&aTriangulation);
+
+  aBuilder.Build(&aTriangulation, &aBVH, aBox);
+
+  int aTotalTriangles = (aGridSize - 1) * (aGridSize - 1) * 2;
+  EXPECT_GT(aBVH.Length(), 0);
+  EXPECT_GE(aBVH.Depth(), 1);
+
+  // Verify tree bounds contain all triangles
+  BVH_Box<Standard_Real, 3> aRootBox(aBVH.MinPoint(0), aBVH.MaxPoint(0));
+  for (int i = 0; i < aTotalTriangles; ++i)
+  {
+    BVH_Box<Standard_Real, 3> aTriBox = aTriangulation.Box(i);
+    EXPECT_FALSE(aRootBox.IsOut(aTriBox));
+  }
+}
+
+TEST(BVH_SweepPlaneBuilderTest, SplitAlongDifferentAxes)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create triangles spread along X axis (should prefer X-axis split)
+  for (int i = 0; i < 10; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i) * 2.0;
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder(1, 32, 1);
+  BVH_Tree<Standard_Real, 3>              aBVH;
+  BVH_Box<Standard_Real, 3>               aBox = ComputeSetBox(&aTriangulation);
+
+  aBuilder.Build(&aTriangulation, &aBVH, aBox);
+
+  // Should create splits along X axis
+  EXPECT_GT(aBVH.Length(), 1);
+  EXPECT_GE(aBVH.Depth(), 1);
+}
+
+TEST(BVH_SweepPlaneBuilderTest, DegenerateCase_AllSamePosition)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // All triangles at same position (degenerate case)
+  for (int i = 0; i < 5; ++i)
+  {
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.1, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.1, 0.0));
+
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder(1, 32, 1);
+  BVH_Tree<Standard_Real, 3>              aBVH;
+  BVH_Box<Standard_Real, 3>               aBox = ComputeSetBox(&aTriangulation);
+
+  aBuilder.Build(&aTriangulation, &aBVH, aBox);
+
+  // Should still build valid tree even with degenerate geometry
+  EXPECT_GT(aBVH.Length(), 0);
+}
+
+// =============================================================================
+// SAH Quality Tests
+// =============================================================================
+
+TEST(BVH_SweepPlaneBuilderTest, SAHQuality_VerifyBetter)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create well-separated triangle groups (should result in good SAH)
+  // Group 1: left side
+  for (int i = 0; i < 5; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i) * 0.5;
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.4, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.2, 0.4, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  // Group 2: right side (spatially separated)
+  for (int i = 0; i < 5; ++i)
+  {
+    Standard_Real x = 10.0 + static_cast<Standard_Real>(i) * 0.5;
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.4, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.2, 0.4, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(
+      aTriangulation.Elements,
+      BVH_Vec4i((5 + i) * 3, (5 + i) * 3 + 1, (5 + i) * 3 + 2, 0));
+  }
+
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder(2, 32, 1);
+  BVH_Tree<Standard_Real, 3>              aBVH;
+  BVH_Box<Standard_Real, 3>               aBox = ComputeSetBox(&aTriangulation);
+
+  aBuilder.Build(&aTriangulation, &aBVH, aBox);
+
+  // Spatially separated groups should create clear split
+  EXPECT_GE(aBVH.Depth(), 1);
+
+  // Root should have two children covering separate regions
+  if (!aBVH.IsOuter(0))
+  {
+    int aLeftChild  = aBVH.template Child<0>(0);
+    int aRightChild = aBVH.template Child<1>(0);
+
+    BVH_Box<Standard_Real, 3> aLeftBox(aBVH.MinPoint(aLeftChild), aBVH.MaxPoint(aLeftChild));
+    BVH_Box<Standard_Real, 3> aRightBox(aBVH.MinPoint(aRightChild), aBVH.MaxPoint(aRightChild));
+
+    // Boxes should not significantly overlap for well-separated geometry
+    Standard_Real aOverlap = 0.0;
+    for (int i = 0; i < 3; ++i)
+    {
+      Standard_Real aMin = std::max(aLeftBox.CornerMin()[i], aRightBox.CornerMin()[i]);
+      Standard_Real aMax = std::min(aLeftBox.CornerMax()[i], aRightBox.CornerMax()[i]);
+      if (aMax > aMin)
+      {
+        aOverlap += (aMax - aMin);
+      }
+    }
+
+    // Overlap should be minimal for well-separated groups
+    Standard_Real aTotalSize = (aBox.CornerMax()[0] - aBox.CornerMin()[0]);
+    EXPECT_LT(aOverlap, aTotalSize * 0.1); // Less than 10% overlap
+  }
+}
+
+// =============================================================================
+// Leaf Size Tests
+// =============================================================================
+
+TEST(BVH_SweepPlaneBuilderTest, LeafSize_RespectMaxSize)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create 20 triangles
+  for (int i = 0; i < 20; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.9, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 0.9, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  BVH_SweepPlaneBuilder<Standard_Real, 3> aBuilder(5, 32, 1); // Leaf size = 5
+  BVH_Tree<Standard_Real, 3>              aBVH;
+  BVH_Box<Standard_Real, 3>               aBox = ComputeSetBox(&aTriangulation);
+
+  aBuilder.Build(&aTriangulation, &aBVH, aBox);
+
+  // Verify leaf nodes don't exceed the specified size
+  for (int i = 0; i < aBVH.Length(); ++i)
+  {
+    if (aBVH.IsOuter(i))
+    {
+      int aLeafSize = aBVH.EndPrimitive(i) - aBVH.BegPrimitive(i) + 1;
+      EXPECT_LE(aLeafSize, 5);
+    }
+  }
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_Tools_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_Tools_Test.cxx
new file mode 100644 (file)
index 0000000..7503d61
--- /dev/null
@@ -0,0 +1,813 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_Box.hxx>
+#include <BVH_Tools.hxx>
+#include <Precision.hxx>
+
+TEST(BVH_ToolsTest, PointBoxSquareDistance)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Point inside box
+  Standard_Real aDist1 =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(0.5, 0.5, 0.5), aBox);
+  EXPECT_NEAR(aDist1, 0.0, Precision::Confusion());
+
+  // Point on face
+  Standard_Real aDist2 =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(0.5, 0.5, 1.0), aBox);
+  EXPECT_NEAR(aDist2, 0.0, Precision::Confusion());
+
+  // Point outside box (distance = 1)
+  Standard_Real aDist3 =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(2.0, 0.5, 0.5), aBox);
+  EXPECT_NEAR(aDist3, 1.0, Precision::Confusion());
+
+  // Point at corner outside (distance^2 = 1 + 1 + 1 = 3)
+  Standard_Real aDist4 =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(2.0, 2.0, 2.0), aBox);
+  EXPECT_NEAR(aDist4, 3.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, BoxBoxSquareDistance)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 0.0, 0.0), BVH_Vec3d(3.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox3(BVH_Vec3d(0.5, 0.5, 0.5), BVH_Vec3d(1.5, 1.5, 1.5));
+
+  // Separated boxes
+  Standard_Real aDist1 = BVH_Tools<Standard_Real, 3>::BoxBoxSquareDistance(aBox1, aBox2);
+  EXPECT_NEAR(aDist1, 1.0, Precision::Confusion());
+
+  // Overlapping boxes
+  Standard_Real aDist2 = BVH_Tools<Standard_Real, 3>::BoxBoxSquareDistance(aBox1, aBox3);
+  EXPECT_NEAR(aDist2, 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointBoxProjection)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Point inside - should return same point
+  BVH_Vec3d aProj1 =
+    BVH_Tools<Standard_Real, 3>::PointBoxProjection(BVH_Vec3d(0.5, 0.5, 0.5), aBox);
+  EXPECT_NEAR(aProj1.x(), 0.5, Precision::Confusion());
+  EXPECT_NEAR(aProj1.y(), 0.5, Precision::Confusion());
+  EXPECT_NEAR(aProj1.z(), 0.5, Precision::Confusion());
+
+  // Point outside - should clamp to box
+  BVH_Vec3d aProj2 =
+    BVH_Tools<Standard_Real, 3>::PointBoxProjection(BVH_Vec3d(2.0, 0.5, 0.5), aBox);
+  EXPECT_NEAR(aProj2.x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj2.y(), 0.5, Precision::Confusion());
+  EXPECT_NEAR(aProj2.z(), 0.5, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersection)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray hitting the box
+  Standard_Boolean aHit1 =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 0.5, 0.5),
+                                                    BVH_Vec3d(1.0, 0.0, 0.0),
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_TRUE(aHit1);
+  EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
+  EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
+
+  // Ray missing the box
+  Standard_Boolean aHit2 =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 5.0, 0.5),
+                                                    BVH_Vec3d(1.0, 0.0, 0.0),
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_FALSE(aHit2);
+
+  // Ray starting inside box
+  Standard_Boolean aHit3 = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(0.5, 0.5, 0.5),
+                                                                           BVH_Vec3d(1.0, 0.0, 0.0),
+                                                                           aBox,
+                                                                           aTimeEnter,
+                                                                           aTimeLeave);
+  EXPECT_TRUE(aHit3);
+  EXPECT_LE(aTimeEnter, 0.0);
+  EXPECT_NEAR(aTimeLeave, 0.5, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionParallelRay)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray parallel to X-axis, passing through box
+  Standard_Boolean aHit1 =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 0.5, 0.5),
+                                                    BVH_Vec3d(1.0, 0.0, 0.0),
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_TRUE(aHit1);
+
+  // Ray parallel to X-axis, missing box (Y out of range)
+  Standard_Boolean aHit2 =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 2.0, 0.5),
+                                                    BVH_Vec3d(1.0, 0.0, 0.0),
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_FALSE(aHit2);
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjection)
+{
+  BVH_Vec3d aNode0(0.0, 0.0, 0.0);
+  BVH_Vec3d aNode1(1.0, 0.0, 0.0);
+  BVH_Vec3d aNode2(0.0, 1.0, 0.0);
+
+  // Point projects to vertex
+  BVH_Vec3d aProj1 =
+    BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(-1.0, -1.0, 0.0),
+                                                         aNode0,
+                                                         aNode1,
+                                                         aNode2);
+  EXPECT_NEAR(aProj1.x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj1.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj1.z(), 0.0, Precision::Confusion());
+
+  // Point projects to edge
+  BVH_Vec3d aProj2 = BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(0.5, -1.0, 0.0),
+                                                                          aNode0,
+                                                                          aNode1,
+                                                                          aNode2);
+  EXPECT_NEAR(aProj2.x(), 0.5, Precision::Confusion());
+  EXPECT_NEAR(aProj2.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj2.z(), 0.0, Precision::Confusion());
+
+  // Point projects inside triangle
+  BVH_Vec3d aProj3 =
+    BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(0.25, 0.25, 1.0),
+                                                         aNode0,
+                                                         aNode1,
+                                                         aNode2);
+  EXPECT_NEAR(aProj3.x(), 0.25, Precision::Confusion());
+  EXPECT_NEAR(aProj3.y(), 0.25, Precision::Confusion());
+  EXPECT_NEAR(aProj3.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointBoxSquareDistance2D)
+{
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
+
+  // Point inside box
+  Standard_Real aDist1 =
+    BVH_Tools<Standard_Real, 2>::PointBoxSquareDistance(BVH_Vec2d(0.5, 0.5), aBox);
+  EXPECT_NEAR(aDist1, 0.0, Precision::Confusion());
+
+  // Point outside box
+  Standard_Real aDist2 =
+    BVH_Tools<Standard_Real, 2>::PointBoxSquareDistance(BVH_Vec2d(2.0, 0.5), aBox);
+  EXPECT_NEAR(aDist2, 1.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionDiagonalRay)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Diagonal ray through box center
+  BVH_Vec3d     aDir(1.0, 1.0, 1.0);
+  Standard_Real aNorm = std::sqrt(3.0);
+  aDir                = BVH_Vec3d(aDir.x() / aNorm, aDir.y() / aNorm, aDir.z() / aNorm);
+
+  Standard_Boolean aHit =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, -1.0, -1.0),
+                                                    aDir,
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_TRUE(aHit);
+  EXPECT_GT(aTimeEnter, 0.0);
+  EXPECT_GT(aTimeLeave, aTimeEnter);
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionNegativeDirection)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray going in negative X direction
+  Standard_Boolean aHit = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(2.0, 0.5, 0.5),
+                                                                          BVH_Vec3d(-1.0, 0.0, 0.0),
+                                                                          aBox,
+                                                                          aTimeEnter,
+                                                                          aTimeLeave);
+  EXPECT_TRUE(aHit);
+  EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
+  EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionTouchingEdge)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray touching edge of box
+  Standard_Boolean aHit = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 0.0, 0.0),
+                                                                          BVH_Vec3d(1.0, 0.0, 0.0),
+                                                                          aBox,
+                                                                          aTimeEnter,
+                                                                          aTimeLeave);
+  EXPECT_TRUE(aHit);
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionTouchingCorner)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray through corner
+  BVH_Vec3d     aDir(1.0, 1.0, 1.0);
+  Standard_Real aNorm = std::sqrt(3.0);
+  aDir                = BVH_Vec3d(aDir.x() / aNorm, aDir.y() / aNorm, aDir.z() / aNorm);
+
+  Standard_Boolean aHit =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, -1.0, -1.0),
+                                                    aDir,
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_TRUE(aHit);
+}
+
+TEST(BVH_ToolsTest, BoxBoxSquareDistanceTouching)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(1.0, 0.0, 0.0), BVH_Vec3d(2.0, 1.0, 1.0));
+
+  // Touching boxes (sharing a face)
+  Standard_Real aDist = BVH_Tools<Standard_Real, 3>::BoxBoxSquareDistance(aBox1, aBox2);
+  EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, BoxBoxSquareDistanceOneInsideOther)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(10.0, 10.0, 10.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(3.0, 3.0, 3.0));
+
+  // Small box inside large box
+  Standard_Real aDist = BVH_Tools<Standard_Real, 3>::BoxBoxSquareDistance(aBox1, aBox2);
+  EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, BoxBoxSquareDistanceCornerToCorner)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 2.0, 2.0), BVH_Vec3d(3.0, 3.0, 3.0));
+
+  // Distance from corner to corner: sqrt(1^2 + 1^2 + 1^2) = sqrt(3), squared = 3
+  Standard_Real aDist = BVH_Tools<Standard_Real, 3>::BoxBoxSquareDistance(aBox1, aBox2);
+  EXPECT_NEAR(aDist, 3.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointBoxProjectionNegativeCoords)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-1.0, -1.0, -1.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Point far outside in negative direction
+  BVH_Vec3d aProj =
+    BVH_Tools<Standard_Real, 3>::PointBoxProjection(BVH_Vec3d(-5.0, -5.0, -5.0), aBox);
+  EXPECT_NEAR(aProj.x(), -1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.y(), -1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.z(), -1.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjectionOnEdge01)
+{
+  BVH_Vec3d aNode0(0.0, 0.0, 0.0);
+  BVH_Vec3d aNode1(2.0, 0.0, 0.0);
+  BVH_Vec3d aNode2(0.0, 2.0, 0.0);
+
+  // Point projects onto edge between Node0 and Node1
+  BVH_Vec3d aProj = BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(1.0, -1.0, 0.0),
+                                                                         aNode0,
+                                                                         aNode1,
+                                                                         aNode2);
+  EXPECT_NEAR(aProj.x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjectionOnEdge12)
+{
+  BVH_Vec3d aNode0(0.0, 0.0, 0.0);
+  BVH_Vec3d aNode1(2.0, 0.0, 0.0);
+  BVH_Vec3d aNode2(0.0, 2.0, 0.0);
+
+  // Point projects onto edge between Node1 and Node2
+  BVH_Vec3d aProj = BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(2.0, 2.0, 0.0),
+                                                                         aNode0,
+                                                                         aNode1,
+                                                                         aNode2);
+  EXPECT_NEAR(aProj.x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.y(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjectionOnEdge20)
+{
+  BVH_Vec3d aNode0(0.0, 0.0, 0.0);
+  BVH_Vec3d aNode1(2.0, 0.0, 0.0);
+  BVH_Vec3d aNode2(0.0, 2.0, 0.0);
+
+  // Point projects onto edge between Node2 and Node0
+  BVH_Vec3d aProj = BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(-1.0, 1.0, 0.0),
+                                                                         aNode0,
+                                                                         aNode1,
+                                                                         aNode2);
+  EXPECT_NEAR(aProj.x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.y(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjectionVertex1)
+{
+  BVH_Vec3d aNode0(0.0, 0.0, 0.0);
+  BVH_Vec3d aNode1(2.0, 0.0, 0.0);
+  BVH_Vec3d aNode2(0.0, 2.0, 0.0);
+
+  // Point projects to Node1
+  BVH_Vec3d aProj = BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(3.0, -1.0, 0.0),
+                                                                         aNode0,
+                                                                         aNode1,
+                                                                         aNode2);
+  EXPECT_NEAR(aProj.x(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjectionVertex2)
+{
+  BVH_Vec3d aNode0(0.0, 0.0, 0.0);
+  BVH_Vec3d aNode1(2.0, 0.0, 0.0);
+  BVH_Vec3d aNode2(0.0, 2.0, 0.0);
+
+  // Point projects to Node2
+  BVH_Vec3d aProj = BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(-1.0, 3.0, 0.0),
+                                                                         aNode0,
+                                                                         aNode1,
+                                                                         aNode2);
+  EXPECT_NEAR(aProj.x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.y(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjection3D)
+{
+  // Triangle in 3D space (not in XY plane)
+  BVH_Vec3d aNode0(0.0, 0.0, 0.0);
+  BVH_Vec3d aNode1(1.0, 0.0, 0.0);
+  BVH_Vec3d aNode2(0.0, 1.0, 1.0);
+
+  // Point above triangle center
+  BVH_Vec3d aProj = BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(0.3, 0.3, 0.8),
+                                                                         aNode0,
+                                                                         aNode1,
+                                                                         aNode2);
+
+  // Should project inside triangle
+  EXPECT_GE(aProj.x(), 0.0);
+  EXPECT_GE(aProj.y(), 0.0);
+  EXPECT_LE(aProj.x() + aProj.y(), 1.0 + Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, BoxBoxSquareDistance2D)
+{
+  BVH_Box<Standard_Real, 2> aBox1(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
+  BVH_Box<Standard_Real, 2> aBox2(BVH_Vec2d(3.0, 0.0), BVH_Vec2d(4.0, 1.0));
+
+  // Distance = 2
+  Standard_Real aDist = BVH_Tools<Standard_Real, 2>::BoxBoxSquareDistance(aBox1, aBox2);
+  EXPECT_NEAR(aDist, 4.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointBoxProjection2D)
+{
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
+
+  // Point outside
+  BVH_Vec2d aProj = BVH_Tools<Standard_Real, 2>::PointBoxProjection(BVH_Vec2d(2.0, 2.0), aBox);
+  EXPECT_NEAR(aProj.x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.y(), 1.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointBoxSquareDistanceAtVertex)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Point on vertex
+  Standard_Real aDist =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(0.0, 0.0, 0.0), aBox);
+  EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointBoxSquareDistanceAtEdge)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Point on edge
+  Standard_Real aDist =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(0.5, 0.0, 0.0), aBox);
+  EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointBoxSquareDistanceFloat)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+
+  // Point inside box
+  Standard_ShortReal aDist1 =
+    BVH_Tools<Standard_ShortReal, 3>::PointBoxSquareDistance(BVH_Vec3f(0.5f, 0.5f, 0.5f), aBox);
+  EXPECT_NEAR(aDist1, 0.0f, 1e-5f);
+
+  // Point outside box
+  Standard_ShortReal aDist2 =
+    BVH_Tools<Standard_ShortReal, 3>::PointBoxSquareDistance(BVH_Vec3f(2.0f, 0.5f, 0.5f), aBox);
+  EXPECT_NEAR(aDist2, 1.0f, 1e-5f);
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionFloat)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+
+  Standard_ShortReal aTimeEnter, aTimeLeave;
+
+  // Ray hitting the box
+  Standard_Boolean aHit =
+    BVH_Tools<Standard_ShortReal, 3>::RayBoxIntersection(BVH_Vec3f(-1.0f, 0.5f, 0.5f),
+                                                         BVH_Vec3f(1.0f, 0.0f, 0.0f),
+                                                         aBox,
+                                                         aTimeEnter,
+                                                         aTimeLeave);
+  EXPECT_TRUE(aHit);
+  EXPECT_NEAR(aTimeEnter, 1.0f, 1e-5f);
+  EXPECT_NEAR(aTimeLeave, 2.0f, 1e-5f);
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionBehindRay)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray pointing away from box
+  Standard_Boolean aHit = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(2.0, 0.5, 0.5),
+                                                                          BVH_Vec3d(1.0, 0.0, 0.0),
+                                                                          aBox,
+                                                                          aTimeEnter,
+                                                                          aTimeLeave);
+  // The box is behind the ray origin
+  EXPECT_TRUE(aTimeLeave < 0.0 || !aHit);
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionYAxis)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray along Y axis
+  Standard_Boolean aHit = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(0.5, -1.0, 0.5),
+                                                                          BVH_Vec3d(0.0, 1.0, 0.0),
+                                                                          aBox,
+                                                                          aTimeEnter,
+                                                                          aTimeLeave);
+  EXPECT_TRUE(aHit);
+  EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
+  EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionZAxis)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray along Z axis
+  Standard_Boolean aHit = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(0.5, 0.5, -1.0),
+                                                                          BVH_Vec3d(0.0, 0.0, 1.0),
+                                                                          aBox,
+                                                                          aTimeEnter,
+                                                                          aTimeLeave);
+  EXPECT_TRUE(aHit);
+  EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
+  EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, BoxBoxSquareDistanceFloat)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox1(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+  BVH_Box<Standard_ShortReal, 3> aBox2(BVH_Vec3f(2.0f, 0.0f, 0.0f), BVH_Vec3f(3.0f, 1.0f, 1.0f));
+
+  Standard_ShortReal aDist = BVH_Tools<Standard_ShortReal, 3>::BoxBoxSquareDistance(aBox1, aBox2);
+  EXPECT_NEAR(aDist, 1.0f, 1e-5f);
+}
+
+TEST(BVH_ToolsTest, PointBoxProjectionFloat)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+
+  BVH_Vec3f aProj =
+    BVH_Tools<Standard_ShortReal, 3>::PointBoxProjection(BVH_Vec3f(2.0f, 0.5f, 0.5f), aBox);
+  EXPECT_NEAR(aProj.x(), 1.0f, 1e-5f);
+  EXPECT_NEAR(aProj.y(), 0.5f, 1e-5f);
+  EXPECT_NEAR(aProj.z(), 0.5f, 1e-5f);
+}
+
+TEST(BVH_ToolsTest, PointBoxSquareDistanceNegativeBox)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-2.0, -2.0, -2.0), BVH_Vec3d(-1.0, -1.0, -1.0));
+
+  // Point at origin
+  Standard_Real aDist =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(0.0, 0.0, 0.0), aBox);
+  // Distance = sqrt(1^2 + 1^2 + 1^2) = sqrt(3), squared = 3
+  EXPECT_NEAR(aDist, 3.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, BoxBoxSquareDistanceEdgeToEdge)
+{
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 2.0, 0.0), BVH_Vec3d(3.0, 3.0, 1.0));
+
+  // Closest points are on edges, distance = sqrt(1^2 + 1^2) = sqrt(2), squared = 2
+  Standard_Real aDist = BVH_Tools<Standard_Real, 3>::BoxBoxSquareDistance(aBox1, aBox2);
+  EXPECT_NEAR(aDist, 2.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjectionFloat)
+{
+  BVH_Vec3f aNode0(0.0f, 0.0f, 0.0f);
+  BVH_Vec3f aNode1(1.0f, 0.0f, 0.0f);
+  BVH_Vec3f aNode2(0.0f, 1.0f, 0.0f);
+
+  // Point projects inside triangle
+  BVH_Vec3f aProj =
+    BVH_Tools<Standard_ShortReal, 3>::PointTriangleProjection(BVH_Vec3f(0.25f, 0.25f, 1.0f),
+                                                              aNode0,
+                                                              aNode1,
+                                                              aNode2);
+  EXPECT_NEAR(aProj.x(), 0.25f, 1e-5f);
+  EXPECT_NEAR(aProj.y(), 0.25f, 1e-5f);
+  EXPECT_NEAR(aProj.z(), 0.0f, 1e-5f);
+}
+
+TEST(BVH_ToolsTest, PointBoxSquareDistanceLargeBox)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1000.0, 1000.0, 1000.0));
+
+  // Point inside
+  Standard_Real aDist1 =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(500.0, 500.0, 500.0), aBox);
+  EXPECT_NEAR(aDist1, 0.0, Precision::Confusion());
+
+  // Point outside
+  Standard_Real aDist2 =
+    BVH_Tools<Standard_Real, 3>::PointBoxSquareDistance(BVH_Vec3d(1001.0, 500.0, 500.0), aBox);
+  EXPECT_NEAR(aDist2, 1.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionLargeBox)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1000.0, 1000.0, 1000.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  Standard_Boolean aHit =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-100.0, 500.0, 500.0),
+                                                    BVH_Vec3d(1.0, 0.0, 0.0),
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_TRUE(aHit);
+  EXPECT_NEAR(aTimeEnter, 100.0, Precision::Confusion());
+  EXPECT_NEAR(aTimeLeave, 1100.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointTriangleProjectionCentroid)
+{
+  BVH_Vec3d aNode0(0.0, 0.0, 0.0);
+  BVH_Vec3d aNode1(3.0, 0.0, 0.0);
+  BVH_Vec3d aNode2(0.0, 3.0, 0.0);
+
+  // Point directly above centroid
+  BVH_Vec3d aProj = BVH_Tools<Standard_Real, 3>::PointTriangleProjection(BVH_Vec3d(1.0, 1.0, 5.0),
+                                                                         aNode0,
+                                                                         aNode1,
+                                                                         aNode2);
+  EXPECT_NEAR(aProj.x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.y(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj.z(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, BoxBoxSquareDistanceSameBox)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Distance to itself should be 0
+  Standard_Real aDist = BVH_Tools<Standard_Real, 3>::BoxBoxSquareDistance(aBox, aBox);
+  EXPECT_NEAR(aDist, 0.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, PointBoxProjectionAllCorners)
+{
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  // Test projection from each octant
+  BVH_Vec3d aProj1 =
+    BVH_Tools<Standard_Real, 3>::PointBoxProjection(BVH_Vec3d(-1.0, -1.0, -1.0), aBox);
+  EXPECT_NEAR(aProj1.x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj1.y(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aProj1.z(), 0.0, Precision::Confusion());
+
+  BVH_Vec3d aProj2 =
+    BVH_Tools<Standard_Real, 3>::PointBoxProjection(BVH_Vec3d(2.0, 2.0, 2.0), aBox);
+  EXPECT_NEAR(aProj2.x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj2.y(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aProj2.z(), 1.0, Precision::Confusion());
+}
+
+// =======================================================================================
+// Tests for improved RayBoxIntersection (dimension-independent, early exit)
+// =======================================================================================
+
+TEST(BVH_ToolsTest, RayBoxIntersection2D)
+{
+  // Test 2D ray-box intersection (old code was hardcoded for 3D)
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
+
+  Standard_Real aTimeEnter, aTimeLeave;
+
+  // Ray hitting the box
+  Standard_Boolean aHit1 = BVH_Tools<Standard_Real, 2>::RayBoxIntersection(BVH_Vec2d(-1.0, 0.5),
+                                                                           BVH_Vec2d(1.0, 0.0),
+                                                                           aBox,
+                                                                           aTimeEnter,
+                                                                           aTimeLeave);
+  EXPECT_TRUE(aHit1);
+  EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
+  EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
+
+  // Ray missing the box
+  Standard_Boolean aHit2 = BVH_Tools<Standard_Real, 2>::RayBoxIntersection(BVH_Vec2d(-1.0, 2.0),
+                                                                           BVH_Vec2d(1.0, 0.0),
+                                                                           aBox,
+                                                                           aTimeEnter,
+                                                                           aTimeLeave);
+  EXPECT_FALSE(aHit2);
+
+  // Ray parallel to X axis, inside Y bounds
+  Standard_Boolean aHit3 = BVH_Tools<Standard_Real, 2>::RayBoxIntersection(BVH_Vec2d(-1.0, 0.5),
+                                                                           BVH_Vec2d(1.0, 0.0),
+                                                                           aBox,
+                                                                           aTimeEnter,
+                                                                           aTimeLeave);
+  EXPECT_TRUE(aHit3);
+
+  // Ray parallel to Y axis, inside X bounds
+  Standard_Boolean aHit4 = BVH_Tools<Standard_Real, 2>::RayBoxIntersection(BVH_Vec2d(0.5, -1.0),
+                                                                           BVH_Vec2d(0.0, 1.0),
+                                                                           aBox,
+                                                                           aTimeEnter,
+                                                                           aTimeLeave);
+  EXPECT_TRUE(aHit4);
+  EXPECT_NEAR(aTimeEnter, 1.0, Precision::Confusion());
+  EXPECT_NEAR(aTimeLeave, 2.0, Precision::Confusion());
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionEarlyExit)
+{
+  // Test early exit optimization when ray misses on first axis
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  Standard_Real             aTimeEnter, aTimeLeave;
+
+  // Ray clearly misses in X direction - should exit immediately
+  Standard_Boolean aHit = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-2.0, 0.5, 0.5),
+                                                                          BVH_Vec3d(-1.0, 0.0, 0.0),
+                                                                          aBox,
+                                                                          aTimeEnter,
+                                                                          aTimeLeave);
+  EXPECT_FALSE(aHit);
+
+  // Ray with early mismatch in Y direction
+  Standard_Boolean aHit2 =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(0.5, -2.0, 0.5),
+                                                    BVH_Vec3d(0.0, -1.0, 0.0),
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_FALSE(aHit2);
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionParallelRayEarlyExit)
+{
+  // Test parallel ray that misses - should exit immediately
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  Standard_Real             aTimeEnter, aTimeLeave;
+
+  // Ray parallel to X axis but Y coordinate outside box - should reject immediately
+  Standard_Boolean aHit = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, 2.0, 0.5),
+                                                                          BVH_Vec3d(1.0, 0.0, 0.0),
+                                                                          aBox,
+                                                                          aTimeEnter,
+                                                                          aTimeLeave);
+  EXPECT_FALSE(aHit);
+
+  // Ray parallel to Y axis but X coordinate outside box
+  Standard_Boolean aHit2 =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(2.0, -1.0, 0.5),
+                                                    BVH_Vec3d(0.0, 1.0, 0.0),
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_FALSE(aHit2);
+
+  // Ray parallel to Z axis but X and Y outside
+  Standard_Boolean aHit3 =
+    BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(-1.0, -1.0, 0.5),
+                                                    BVH_Vec3d(0.0, 0.0, 1.0),
+                                                    aBox,
+                                                    aTimeEnter,
+                                                    aTimeLeave);
+  EXPECT_FALSE(aHit3);
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersection2DParallelBothAxes)
+{
+  // 2D test with ray parallel to both axes (direction = 0,0)
+  BVH_Box<Standard_Real, 2> aBox(BVH_Vec2d(0.0, 0.0), BVH_Vec2d(1.0, 1.0));
+  Standard_Real             aTimeEnter, aTimeLeave;
+
+  // Ray origin inside box, direction = 0
+  Standard_Boolean aHit1 = BVH_Tools<Standard_Real, 2>::RayBoxIntersection(BVH_Vec2d(0.5, 0.5),
+                                                                           BVH_Vec2d(0.0, 0.0),
+                                                                           aBox,
+                                                                           aTimeEnter,
+                                                                           aTimeLeave);
+  EXPECT_TRUE(aHit1); // Should still hit because origin is inside
+
+  // Ray origin outside box, direction = 0
+  Standard_Boolean aHit2 = BVH_Tools<Standard_Real, 2>::RayBoxIntersection(BVH_Vec2d(2.0, 2.0),
+                                                                           BVH_Vec2d(0.0, 0.0),
+                                                                           aBox,
+                                                                           aTimeEnter,
+                                                                           aTimeLeave);
+  EXPECT_FALSE(aHit2); // Should miss because origin is outside
+}
+
+TEST(BVH_ToolsTest, RayBoxIntersectionNegativeTime)
+{
+  // Test that ray doesn't report intersection behind the origin
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  Standard_Real             aTimeEnter, aTimeLeave;
+
+  // Ray origin is past the box, pointing away
+  Standard_Boolean aHit = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(2.0, 0.5, 0.5),
+                                                                          BVH_Vec3d(1.0, 0.0, 0.0),
+                                                                          aBox,
+                                                                          aTimeEnter,
+                                                                          aTimeLeave);
+  EXPECT_FALSE(aHit);
+
+  // Ray origin inside box, pointing away - should still hit (leave point is in front)
+  Standard_Boolean aHit2 = BVH_Tools<Standard_Real, 3>::RayBoxIntersection(BVH_Vec3d(0.5, 0.5, 0.5),
+                                                                           BVH_Vec3d(1.0, 0.0, 0.0),
+                                                                           aBox,
+                                                                           aTimeEnter,
+                                                                           aTimeLeave);
+  EXPECT_TRUE(aHit2);
+  EXPECT_TRUE(aTimeLeave >= 0.0);
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_Traverse_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_Traverse_Test.cxx
new file mode 100644 (file)
index 0000000..498cb41
--- /dev/null
@@ -0,0 +1,683 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_BinnedBuilder.hxx>
+#include <BVH_Traverse.hxx>
+#include <BVH_Triangulation.hxx>
+
+#include <vector>
+
+// =======================================================================================
+// Test implementations of BVH_Traverse
+// =======================================================================================
+
+//! Simple traverse implementation that counts all elements
+class BVH_CountAllElements : public BVH_Traverse<Standard_Real, 3, void, Standard_Real>
+{
+public:
+  BVH_CountAllElements()
+      : myAcceptedCount(0)
+  {
+  }
+
+  virtual Standard_Boolean RejectNode(const BVH_VecNt&,
+                                      const BVH_VecNt&,
+                                      Standard_Real& theMetric) const Standard_OVERRIDE
+  {
+    theMetric = 0.0;       // All nodes have same metric
+    return Standard_False; // Never reject
+  }
+
+  virtual Standard_Boolean Accept(const Standard_Integer, const Standard_Real&) Standard_OVERRIDE
+  {
+    ++myAcceptedCount;
+    return Standard_True;
+  }
+
+  Standard_Integer AcceptedCount() const { return myAcceptedCount; }
+
+  void Reset() { myAcceptedCount = 0; }
+
+private:
+  mutable Standard_Integer myAcceptedCount;
+};
+
+//! Traverse that rejects nodes outside a bounding box
+class BVH_BoxSelector : public BVH_Traverse<Standard_Real, 3, void, Standard_Real>
+{
+public:
+  BVH_BoxSelector(const BVH_Box<Standard_Real, 3>& theBox)
+      : myBox(theBox),
+        myAcceptedCount(0)
+  {
+  }
+
+  virtual Standard_Boolean RejectNode(const BVH_VecNt& theMin,
+                                      const BVH_VecNt& theMax,
+                                      Standard_Real&   theMetric) const Standard_OVERRIDE
+  {
+    // Reject if box doesn't intersect with selection box
+    theMetric = 0.0;
+    return myBox.IsOut(theMin, theMax);
+  }
+
+  virtual Standard_Boolean Accept(const Standard_Integer, const Standard_Real&) Standard_OVERRIDE
+  {
+    ++myAcceptedCount;
+    return Standard_True;
+  }
+
+  Standard_Integer AcceptedCount() const { return myAcceptedCount; }
+
+  void Reset() { myAcceptedCount = 0; }
+
+private:
+  BVH_Box<Standard_Real, 3> myBox;
+  mutable Standard_Integer  myAcceptedCount;
+};
+
+//! Traverse with distance-based metric and early termination
+class BVH_DistanceSelector : public BVH_Traverse<Standard_Real, 3, void, Standard_Real>
+{
+public:
+  BVH_DistanceSelector(const BVH_Vec3d& thePoint, Standard_Real theMaxDist)
+      : myPoint(thePoint),
+        myMaxDistSq(theMaxDist * theMaxDist),
+        myMinDistSq(std::numeric_limits<Standard_Real>::max()),
+        myAcceptedCount(0),
+        myClosestIndex(-1)
+  {
+  }
+
+  virtual Standard_Boolean RejectNode(const BVH_VecNt& theMin,
+                                      const BVH_VecNt& theMax,
+                                      Standard_Real&   theMetric) const Standard_OVERRIDE
+  {
+    // Compute squared distance from point to box
+    theMetric = PointBoxSquareDistance(myPoint, theMin, theMax);
+    return theMetric > myMaxDistSq;
+  }
+
+  virtual Standard_Boolean Accept(const Standard_Integer theIndex,
+                                  const Standard_Real&   theMetric) Standard_OVERRIDE
+  {
+    ++myAcceptedCount;
+    if (theMetric < myMinDistSq)
+    {
+      myMinDistSq    = theMetric;
+      myClosestIndex = theIndex;
+    }
+    return Standard_True;
+  }
+
+  virtual Standard_Boolean IsMetricBetter(const Standard_Real& theLeft,
+                                          const Standard_Real& theRight) const Standard_OVERRIDE
+  {
+    return theLeft < theRight; // Closer is better
+  }
+
+  virtual Standard_Boolean RejectMetric(const Standard_Real& theMetric) const Standard_OVERRIDE
+  {
+    return theMetric > myMaxDistSq;
+  }
+
+  Standard_Integer AcceptedCount() const { return myAcceptedCount; }
+
+  Standard_Integer ClosestIndex() const { return myClosestIndex; }
+
+  Standard_Real MinDistance() const { return std::sqrt(myMinDistSq); }
+
+private:
+  static Standard_Real PointBoxSquareDistance(const BVH_Vec3d& thePoint,
+                                              const BVH_Vec3d& theMin,
+                                              const BVH_Vec3d& theMax)
+  {
+    Standard_Real aDist = 0.0;
+    for (int i = 0; i < 3; ++i)
+    {
+      if (thePoint[i] < theMin[i])
+      {
+        Standard_Real d = theMin[i] - thePoint[i];
+        aDist += d * d;
+      }
+      else if (thePoint[i] > theMax[i])
+      {
+        Standard_Real d = thePoint[i] - theMax[i];
+        aDist += d * d;
+      }
+    }
+    return aDist;
+  }
+
+  BVH_Vec3d        myPoint;
+  Standard_Real    myMaxDistSq;
+  Standard_Real    myMinDistSq;
+  Standard_Integer myAcceptedCount;
+  Standard_Integer myClosestIndex;
+};
+
+//! Traverse with early stopping after finding N elements
+class BVH_LimitedSelector : public BVH_Traverse<Standard_Real, 3, void, Standard_Real>
+{
+public:
+  BVH_LimitedSelector(Standard_Integer theMaxCount)
+      : myMaxCount(theMaxCount),
+        myAcceptedCount(0)
+  {
+  }
+
+  virtual Standard_Boolean RejectNode(const BVH_VecNt&,
+                                      const BVH_VecNt&,
+                                      Standard_Real& theMetric) const Standard_OVERRIDE
+  {
+    theMetric = 0.0;
+    return Standard_False;
+  }
+
+  virtual Standard_Boolean Accept(const Standard_Integer, const Standard_Real&) Standard_OVERRIDE
+  {
+    ++myAcceptedCount;
+    return Standard_True;
+  }
+
+  virtual Standard_Boolean Stop() const Standard_OVERRIDE { return myAcceptedCount >= myMaxCount; }
+
+  Standard_Integer AcceptedCount() const { return myAcceptedCount; }
+
+private:
+  Standard_Integer myMaxCount;
+  Standard_Integer myAcceptedCount;
+};
+
+// =======================================================================================
+// Test implementations of BVH_PairTraverse
+// =======================================================================================
+
+//! Simple pair traverse that counts all pairs
+class BVH_CountAllPairs : public BVH_PairTraverse<Standard_Real, 3, void, Standard_Real>
+{
+public:
+  BVH_CountAllPairs()
+      : myAcceptedCount(0)
+  {
+  }
+
+  virtual Standard_Boolean RejectNode(const BVH_VecNt&,
+                                      const BVH_VecNt&,
+                                      const BVH_VecNt&,
+                                      const BVH_VecNt&,
+                                      Standard_Real& theMetric) const Standard_OVERRIDE
+  {
+    theMetric = 0.0;
+    return Standard_False; // Never reject
+  }
+
+  virtual Standard_Boolean Accept(const Standard_Integer, const Standard_Integer) Standard_OVERRIDE
+  {
+    ++myAcceptedCount;
+    return Standard_True;
+  }
+
+  Standard_Integer AcceptedCount() const { return myAcceptedCount; }
+
+private:
+  mutable Standard_Integer myAcceptedCount;
+};
+
+//! Pair traverse that only accepts overlapping boxes
+class BVH_OverlapDetector : public BVH_PairTraverse<Standard_Real, 3, void, Standard_Real>
+{
+public:
+  BVH_OverlapDetector()
+      : myOverlapCount(0),
+        myRejectCount(0)
+  {
+  }
+
+  virtual Standard_Boolean RejectNode(const BVH_VecNt& theMin1,
+                                      const BVH_VecNt& theMax1,
+                                      const BVH_VecNt& theMin2,
+                                      const BVH_VecNt& theMax2,
+                                      Standard_Real&   theMetric) const Standard_OVERRIDE
+  {
+    ++myRejectCount;
+    theMetric = 0.0;
+    // Reject if boxes don't overlap
+    BVH_Box<Standard_Real, 3> aBox1(theMin1, theMax1);
+    Standard_Boolean          isOut = aBox1.IsOut(theMin2, theMax2);
+    return isOut;
+  }
+
+  virtual Standard_Boolean Accept(const Standard_Integer, const Standard_Integer) Standard_OVERRIDE
+  {
+    // For this test, if we reach Accept, it means the bounding boxes overlap.
+    // In a real implementation, you would check actual triangle-triangle intersection here.
+    // For testing purposes, we count all pairs whose bounding boxes overlap.
+    ++myOverlapCount;
+    return Standard_True;
+  }
+
+  Standard_Integer OverlapCount() const { return myOverlapCount; }
+
+  Standard_Integer RejectCount() const { return myRejectCount; }
+
+private:
+  mutable Standard_Integer myOverlapCount;
+  mutable Standard_Integer myRejectCount;
+};
+
+// =======================================================================================
+// Helper functions
+// =======================================================================================
+
+//! Creates a simple triangulation for testing
+opencascade::handle<BVH_Tree<Standard_Real, 3>> CreateSimpleTriangulationBVH(
+  Standard_Integer theNumTriangles)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  for (Standard_Integer i = 0; i < theNumTriangles; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i * 2);
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 2.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = new BVH_Tree<Standard_Real, 3>;
+  BVH_BinnedBuilder<Standard_Real, 3>             aBuilder;
+  aBuilder.Build(&aTriangulation, aBVH.get(), aTriangulation.Box());
+
+  return aBVH;
+}
+
+// =======================================================================================
+// Tests for BVH_Traverse
+// =======================================================================================
+
+TEST(BVH_TraverseTest, CountAllElements)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(10);
+
+  BVH_CountAllElements aSelector;
+  Standard_Integer     aCount = aSelector.Select(aBVH);
+
+  EXPECT_EQ(aCount, 10);
+  EXPECT_EQ(aSelector.AcceptedCount(), 10);
+}
+
+TEST(BVH_TraverseTest, EmptyTree)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = new BVH_Tree<Standard_Real, 3>;
+
+  BVH_CountAllElements aSelector;
+  Standard_Integer     aCount = aSelector.Select(aBVH);
+
+  EXPECT_EQ(aCount, 0);
+}
+
+TEST(BVH_TraverseTest, NullTree)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH;
+
+  BVH_CountAllElements aSelector;
+  Standard_Integer     aCount = aSelector.Select(aBVH);
+
+  EXPECT_EQ(aCount, 0);
+}
+
+TEST(BVH_TraverseTest, BoxSelection)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(10);
+
+  // Select elements in the first half
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, -1.0), BVH_Vec3d(10.0, 2.0, 1.0));
+  BVH_BoxSelector           aSelector(aBox);
+  Standard_Integer          aCount = aSelector.Select(aBVH);
+
+  // Should select approximately half of the elements
+  EXPECT_GT(aCount, 0);
+  EXPECT_LT(aCount, 10);
+}
+
+TEST(BVH_TraverseTest, EmptyBoxSelection)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(10);
+
+  // Select with box that doesn't intersect any elements
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(100.0, 100.0, 100.0), BVH_Vec3d(200.0, 200.0, 200.0));
+  BVH_BoxSelector           aSelector(aBox);
+  Standard_Integer          aCount = aSelector.Select(aBVH);
+
+  EXPECT_EQ(aCount, 0);
+}
+
+TEST(BVH_TraverseTest, FullBoxSelection)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(10);
+
+  // Select with box that contains all elements
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(-100.0, -100.0, -100.0), BVH_Vec3d(100.0, 100.0, 100.0));
+  BVH_BoxSelector           aSelector(aBox);
+  Standard_Integer          aCount = aSelector.Select(aBVH);
+
+  EXPECT_EQ(aCount, 10);
+}
+
+TEST(BVH_TraverseTest, DistanceBasedSelection)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(10);
+
+  // Find elements near a point
+  BVH_Vec3d            aPoint(5.0, 0.5, 0.0);
+  Standard_Real        aMaxDist = 5.0;
+  BVH_DistanceSelector aSelector(aPoint, aMaxDist);
+  Standard_Integer     aCount = aSelector.Select(aBVH);
+
+  EXPECT_GT(aCount, 0);
+  EXPECT_LE(aCount, 10);
+  EXPECT_GE(aSelector.ClosestIndex(), 0);
+  EXPECT_LT(aSelector.ClosestIndex(), 10);
+}
+
+TEST(BVH_TraverseTest, EarlyTermination)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(100);
+
+  // Stop after finding 5 elements
+  BVH_LimitedSelector aSelector(5);
+  Standard_Integer    aCount = aSelector.Select(aBVH);
+
+  EXPECT_EQ(aCount, 5);
+  EXPECT_EQ(aSelector.AcceptedCount(), 5);
+}
+
+TEST(BVH_TraverseTest, LargeDataSet)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(1000);
+
+  BVH_CountAllElements aSelector;
+  Standard_Integer     aCount = aSelector.Select(aBVH);
+
+  EXPECT_EQ(aCount, 1000);
+}
+
+TEST(BVH_TraverseTest, MetricBasedPruning)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(50);
+
+  // Very restrictive distance should result in few acceptances
+  BVH_Vec3d            aPoint(1000.0, 1000.0, 1000.0); // Far away
+  Standard_Real        aMaxDist = 1.0;                 // Small radius
+  BVH_DistanceSelector aSelector(aPoint, aMaxDist);
+  Standard_Integer     aCount = aSelector.Select(aBVH);
+
+  EXPECT_EQ(aCount, 0); // Nothing should be within range
+}
+
+// =======================================================================================
+// Tests for BVH_PairTraverse
+// =======================================================================================
+
+TEST(BVH_PairTraverseTest, IsOutVerification)
+{
+  // Verify that IsOut works correctly for non-overlapping boxes
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(3.0, 1.0, 0.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(100.0, 0.0, 0.0), BVH_Vec3d(103.0, 1.0, 0.0));
+
+  // These boxes are far apart and should not overlap
+  EXPECT_TRUE(aBox1.IsOut(aBox2.CornerMin(), aBox2.CornerMax()));
+  EXPECT_TRUE(aBox2.IsOut(aBox1.CornerMin(), aBox1.CornerMax()));
+}
+
+TEST(BVH_PairTraverseTest, TriangulationBoxVerification)
+{
+  // Create two triangulations and verify their bounding boxes don't overlap
+  BVH_Triangulation<Standard_Real, 3> aTri1, aTri2;
+
+  for (Standard_Integer i = 0; i < 3; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTri1.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  for (Standard_Integer i = 0; i < 3; ++i)
+  {
+    Standard_Real x = 100.0 + static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTri2.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  // Mark as dirty to force bounding box computation
+  aTri1.MarkDirty();
+  aTri2.MarkDirty();
+
+  BVH_Box<Standard_Real, 3> aBox1 = aTri1.Box();
+  BVH_Box<Standard_Real, 3> aBox2 = aTri2.Box();
+
+  // Verify the boxes are where we expect them
+  EXPECT_TRUE(aBox1.IsValid());
+  EXPECT_TRUE(aBox2.IsValid());
+
+  // Box1 should be roughly [0, 0, 0] to [3, 1, 0]
+  EXPECT_NEAR(aBox1.CornerMin().x(), 0.0, 0.01);
+  EXPECT_NEAR(aBox1.CornerMax().x(), 3.0, 0.01);
+
+  // Box2 should be roughly [100, 0, 0] to [103, 1, 0]
+  EXPECT_NEAR(aBox2.CornerMin().x(), 100.0, 0.01);
+  EXPECT_NEAR(aBox2.CornerMax().x(), 103.0, 0.01);
+
+  // The boxes should not overlap
+  EXPECT_TRUE(aBox1.IsOut(aBox2));
+  EXPECT_TRUE(aBox2.IsOut(aBox1));
+}
+
+TEST(BVH_PairTraverseTest, BVHRootBoxVerification)
+{
+  // Create triangulations and verify their BVH root boxes
+  BVH_Triangulation<Standard_Real, 3> aTri1, aTri2;
+
+  for (Standard_Integer i = 0; i < 3; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTri1.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  for (Standard_Integer i = 0; i < 3; ++i)
+  {
+    Standard_Real x = 100.0 + static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTri2.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  aTri1.MarkDirty();
+  aTri2.MarkDirty();
+
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH1 = new BVH_Tree<Standard_Real, 3>;
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH2 = new BVH_Tree<Standard_Real, 3>;
+
+  BVH_BinnedBuilder<Standard_Real, 3> aBuilder;
+  aBuilder.Build(&aTri1, aBVH1.get(), aTri1.Box());
+  aBuilder.Build(&aTri2, aBVH2.get(), aTri2.Box());
+
+  // Check the root node bounding boxes
+  BVH_Vec3d aMin1 = aBVH1->MinPoint(0);
+  BVH_Vec3d aMax1 = aBVH1->MaxPoint(0);
+  BVH_Vec3d aMin2 = aBVH2->MinPoint(0);
+  BVH_Vec3d aMax2 = aBVH2->MaxPoint(0);
+
+  // Verify root boxes are separated
+  EXPECT_NEAR(aMin1.x(), 0.0, 0.01);
+  EXPECT_NEAR(aMax1.x(), 3.0, 0.01);
+  EXPECT_NEAR(aMin2.x(), 100.0, 0.01);
+  EXPECT_NEAR(aMax2.x(), 103.0, 0.01);
+
+  // The root boxes should not overlap
+  BVH_Box<Standard_Real, 3> aBox1(aMin1, aMax1);
+  EXPECT_TRUE(aBox1.IsOut(aMin2, aMax2));
+
+  // Check if the nodes are inner or leaf nodes
+  const BVH_Vec4i& aData1 = aBVH1->NodeInfoBuffer()[0];
+  const BVH_Vec4i& aData2 = aBVH2->NodeInfoBuffer()[0];
+
+  // aData.x() == 0 means inner node, != 0 means leaf node
+  // With 3 triangles, the tree might be a single leaf node
+  EXPECT_EQ(aData1.x(), 1) << "Root of BVH1 should be a leaf node (3 elements)";
+  EXPECT_EQ(aData2.x(), 1) << "Root of BVH2 should be a leaf node (3 elements)";
+}
+
+TEST(BVH_PairTraverseTest, CountAllPairs)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH1 = CreateSimpleTriangulationBVH(5);
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH2 = CreateSimpleTriangulationBVH(5);
+
+  BVH_CountAllPairs aSelector;
+  Standard_Integer  aCount = aSelector.Select(aBVH1, aBVH2);
+
+  EXPECT_EQ(aCount, 25); // 5 x 5 pairs
+}
+
+TEST(BVH_PairTraverseTest, EmptyFirstTree)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH1 = new BVH_Tree<Standard_Real, 3>;
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH2 = CreateSimpleTriangulationBVH(5);
+
+  BVH_CountAllPairs aSelector;
+  Standard_Integer  aCount = aSelector.Select(aBVH1, aBVH2);
+
+  EXPECT_EQ(aCount, 0);
+}
+
+TEST(BVH_PairTraverseTest, EmptySecondTree)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH1 = CreateSimpleTriangulationBVH(5);
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH2 = new BVH_Tree<Standard_Real, 3>;
+
+  BVH_CountAllPairs aSelector;
+  Standard_Integer  aCount = aSelector.Select(aBVH1, aBVH2);
+
+  EXPECT_EQ(aCount, 0);
+}
+
+TEST(BVH_PairTraverseTest, NullTrees)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH1;
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH2;
+
+  BVH_CountAllPairs aSelector;
+  Standard_Integer  aCount = aSelector.Select(aBVH1, aBVH2);
+
+  EXPECT_EQ(aCount, 0);
+}
+
+TEST(BVH_PairTraverseTest, OverlapDetection_SameTrees)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH = CreateSimpleTriangulationBVH(10);
+
+  BVH_OverlapDetector aSelector;
+  Standard_Integer    aCount = aSelector.Select(aBVH, aBVH);
+
+  // Self-overlap: all 10 elements overlap with themselves
+  EXPECT_GE(aCount, 10);
+}
+
+TEST(BVH_PairTraverseTest, OverlapDetection_NonOverlapping)
+{
+  // Create two triangulations in different regions
+  BVH_Triangulation<Standard_Real, 3> aTri1, aTri2;
+
+  // First triangulation at x=0..5
+  for (Standard_Integer i = 0; i < 3; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri1.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTri1.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  // Second triangulation at x=100..105 (far away)
+  for (Standard_Integer i = 0; i < 3; ++i)
+  {
+    Standard_Real x = 100.0 + static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTri2.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTri2.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  // Mark as dirty to force bounding box computation
+  aTri1.MarkDirty();
+  aTri2.MarkDirty();
+
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH1 = new BVH_Tree<Standard_Real, 3>;
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH2 = new BVH_Tree<Standard_Real, 3>;
+
+  BVH_BinnedBuilder<Standard_Real, 3> aBuilder;
+  aBuilder.Build(&aTri1, aBVH1.get(), aTri1.Box());
+  aBuilder.Build(&aTri2, aBVH2.get(), aTri2.Box());
+
+  BVH_OverlapDetector aSelector;
+  Standard_Integer    aCount = aSelector.Select(aBVH1, aBVH2);
+
+  // Debug: Check how many times RejectNode was called
+  // If it's 0, RejectNode is not being called at all
+  // If it's > 0 but overlaps are still found, then IsOut is broken
+  EXPECT_GT(aSelector.RejectCount(), 0) << "RejectNode should be called at least once";
+
+  // No overlaps expected
+  EXPECT_EQ(aCount, 0) << "Found " << aCount << " overlaps (RejectNode called "
+                       << aSelector.RejectCount() << " times)";
+}
+
+TEST(BVH_PairTraverseTest, AsymmetricPairs)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH1 = CreateSimpleTriangulationBVH(3);
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH2 = CreateSimpleTriangulationBVH(7);
+
+  BVH_CountAllPairs aSelector;
+  Standard_Integer  aCount = aSelector.Select(aBVH1, aBVH2);
+
+  EXPECT_EQ(aCount, 21); // 3 x 7 pairs
+}
+
+TEST(BVH_PairTraverseTest, LargeDataSets)
+{
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH1 = CreateSimpleTriangulationBVH(50);
+  opencascade::handle<BVH_Tree<Standard_Real, 3>> aBVH2 = CreateSimpleTriangulationBVH(50);
+
+  BVH_CountAllPairs aSelector;
+  Standard_Integer  aCount = aSelector.Select(aBVH1, aBVH2);
+
+  EXPECT_EQ(aCount, 2500); // 50 x 50 pairs
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_Tree_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_Tree_Test.cxx
new file mode 100644 (file)
index 0000000..68777b1
--- /dev/null
@@ -0,0 +1,399 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_BinaryTree.hxx>
+#include <BVH_Box.hxx>
+#include <Precision.hxx>
+
+TEST(BVH_TreeTest, DefaultConstructor)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  EXPECT_EQ(aTree.Length(), 0);
+  EXPECT_EQ(aTree.Depth(), 0);
+}
+
+TEST(BVH_TreeTest, AddLeafNode)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  BVH_Vec3d aMin(0.0, 0.0, 0.0);
+  BVH_Vec3d aMax(1.0, 1.0, 1.0);
+
+  int aNodeIndex = aTree.AddLeafNode(aMin, aMax, 0, 10);
+
+  EXPECT_EQ(aNodeIndex, 0);
+  EXPECT_EQ(aTree.Length(), 1);
+  EXPECT_TRUE(aTree.IsOuter(0));
+  EXPECT_EQ(aTree.BegPrimitive(0), 0);
+  EXPECT_EQ(aTree.EndPrimitive(0), 10);
+  EXPECT_EQ(aTree.NbPrimitives(0), 11);
+}
+
+TEST(BVH_TreeTest, AddInnerNode)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  // Add two leaf nodes first
+  BVH_Vec3d aMin1(0.0, 0.0, 0.0);
+  BVH_Vec3d aMax1(1.0, 1.0, 1.0);
+  int       aLeaf1 = aTree.AddLeafNode(aMin1, aMax1, 0, 5);
+
+  BVH_Vec3d aMin2(2.0, 0.0, 0.0);
+  BVH_Vec3d aMax2(3.0, 1.0, 1.0);
+  int       aLeaf2 = aTree.AddLeafNode(aMin2, aMax2, 6, 10);
+
+  // Add inner node
+  BVH_Vec3d aMinRoot(0.0, 0.0, 0.0);
+  BVH_Vec3d aMaxRoot(3.0, 1.0, 1.0);
+  int       aRoot = aTree.AddInnerNode(aMinRoot, aMaxRoot, aLeaf1, aLeaf2);
+
+  EXPECT_EQ(aTree.Length(), 3);
+  EXPECT_FALSE(aTree.IsOuter(aRoot));
+  EXPECT_EQ(aTree.template Child<0>(aRoot), aLeaf1);
+  EXPECT_EQ(aTree.template Child<1>(aRoot), aLeaf2);
+}
+
+TEST(BVH_TreeTest, MinMaxPoints)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  BVH_Vec3d aMin(1.0, 2.0, 3.0);
+  BVH_Vec3d aMax(4.0, 5.0, 6.0);
+
+  aTree.AddLeafNode(aMin, aMax, 0, 0);
+
+  EXPECT_NEAR(aTree.MinPoint(0).x(), 1.0, Precision::Confusion());
+  EXPECT_NEAR(aTree.MinPoint(0).y(), 2.0, Precision::Confusion());
+  EXPECT_NEAR(aTree.MinPoint(0).z(), 3.0, Precision::Confusion());
+  EXPECT_NEAR(aTree.MaxPoint(0).x(), 4.0, Precision::Confusion());
+  EXPECT_NEAR(aTree.MaxPoint(0).y(), 5.0, Precision::Confusion());
+  EXPECT_NEAR(aTree.MaxPoint(0).z(), 6.0, Precision::Confusion());
+}
+
+TEST(BVH_TreeTest, Clear)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 0, 0);
+  aTree.AddLeafNode(BVH_Vec3d(1.0, 0.0, 0.0), BVH_Vec3d(2.0, 1.0, 1.0), 1, 1);
+
+  EXPECT_EQ(aTree.Length(), 2);
+
+  aTree.Clear();
+
+  EXPECT_EQ(aTree.Length(), 0);
+  EXPECT_EQ(aTree.Depth(), 0);
+}
+
+TEST(BVH_TreeTest, Reserve)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  // Reserve should not throw
+  aTree.Reserve(100);
+
+  // Can still add nodes after reserve
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 0, 0);
+  EXPECT_EQ(aTree.Length(), 1);
+}
+
+TEST(BVH_TreeTest, SetOuterInner)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  // Add leaf and change to inner
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 0, 0);
+
+  EXPECT_TRUE(aTree.IsOuter(0));
+
+  aTree.SetInner(0);
+  EXPECT_FALSE(aTree.IsOuter(0));
+
+  aTree.SetOuter(0);
+  EXPECT_TRUE(aTree.IsOuter(0));
+}
+
+TEST(BVH_TreeTest, Level)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 0, 0);
+
+  // Default level is 0
+  EXPECT_EQ(aTree.Level(0), 0);
+
+  // Change level
+  aTree.Level(0) = 5;
+  EXPECT_EQ(aTree.Level(0), 5);
+}
+
+TEST(BVH_TreeTest, AddLeafNodeWithBox)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  BVH_Box<Standard_Real, 3> aBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+
+  int aNodeIndex = aTree.AddLeafNode(aBox, 0, 5);
+
+  EXPECT_EQ(aNodeIndex, 0);
+  EXPECT_TRUE(aTree.IsOuter(0));
+  EXPECT_EQ(aTree.BegPrimitive(0), 0);
+  EXPECT_EQ(aTree.EndPrimitive(0), 5);
+}
+
+TEST(BVH_TreeTest, AddInnerNodeWithBox)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  BVH_Box<Standard_Real, 3> aBox1(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aBox2(BVH_Vec3d(2.0, 0.0, 0.0), BVH_Vec3d(3.0, 1.0, 1.0));
+  BVH_Box<Standard_Real, 3> aRootBox(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(3.0, 1.0, 1.0));
+
+  int aLeaf1 = aTree.AddLeafNode(aBox1, 0, 5);
+  int aLeaf2 = aTree.AddLeafNode(aBox2, 6, 10);
+  int aRoot  = aTree.AddInnerNode(aRootBox, aLeaf1, aLeaf2);
+
+  EXPECT_FALSE(aTree.IsOuter(aRoot));
+  EXPECT_EQ(aTree.template Child<0>(aRoot), aLeaf1);
+  EXPECT_EQ(aTree.template Child<1>(aRoot), aLeaf2);
+}
+
+TEST(BVH_TreeTest, EstimateSAH)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  // Create a simple tree with root and two leaves
+  BVH_Vec3d aMin1(0.0, 0.0, 0.0);
+  BVH_Vec3d aMax1(1.0, 1.0, 1.0);
+  int       aLeaf1 = aTree.AddLeafNode(aMin1, aMax1, 0, 0);
+
+  BVH_Vec3d aMin2(2.0, 0.0, 0.0);
+  BVH_Vec3d aMax2(3.0, 1.0, 1.0);
+  int       aLeaf2 = aTree.AddLeafNode(aMin2, aMax2, 1, 1);
+
+  BVH_Vec3d aMinRoot(0.0, 0.0, 0.0);
+  BVH_Vec3d aMaxRoot(3.0, 1.0, 1.0);
+  aTree.AddInnerNode(aMinRoot, aMaxRoot, aLeaf1, aLeaf2);
+
+  Standard_Real aSAH = aTree.EstimateSAH();
+
+  // SAH should be positive
+  EXPECT_GT(aSAH, 0.0);
+}
+
+TEST(BVH_TreeTest, NbPrimitives)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 5, 15);
+
+  // NbPrimitives = EndPrimitive - BegPrimitive + 1 = 15 - 5 + 1 = 11
+  EXPECT_EQ(aTree.NbPrimitives(0), 11);
+}
+
+TEST(BVH_TreeTest, SinglePrimitive)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 7, 7);
+
+  EXPECT_EQ(aTree.NbPrimitives(0), 1);
+  EXPECT_EQ(aTree.BegPrimitive(0), 7);
+  EXPECT_EQ(aTree.EndPrimitive(0), 7);
+}
+
+TEST(BVH_TreeTest, Float3DTree)
+{
+  BVH_Tree<Standard_ShortReal, 3, BVH_BinaryTree> aTree;
+
+  BVH_Vec3f aMin(0.0f, 0.0f, 0.0f);
+  BVH_Vec3f aMax(1.0f, 1.0f, 1.0f);
+
+  aTree.AddLeafNode(aMin, aMax, 0, 5);
+
+  EXPECT_EQ(aTree.Length(), 1);
+  EXPECT_NEAR(aTree.MinPoint(0).x(), 0.0f, 1e-5f);
+  EXPECT_NEAR(aTree.MaxPoint(0).x(), 1.0f, 1e-5f);
+}
+
+TEST(BVH_TreeTest, Tree2D)
+{
+  BVH_Tree<Standard_Real, 2, BVH_BinaryTree> aTree;
+
+  BVH_Vec2d aMin(0.0, 0.0);
+  BVH_Vec2d aMax(1.0, 1.0);
+
+  aTree.AddLeafNode(aMin, aMax, 0, 5);
+
+  EXPECT_EQ(aTree.Length(), 1);
+  EXPECT_NEAR(aTree.MinPoint(0).x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aTree.MinPoint(0).y(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_TreeTest, Tree4D)
+{
+  BVH_Tree<Standard_Real, 4, BVH_BinaryTree> aTree;
+
+  BVH_Vec4d aMin(0.0, 0.0, 0.0, 0.0);
+  BVH_Vec4d aMax(1.0, 1.0, 1.0, 1.0);
+
+  aTree.AddLeafNode(aMin, aMax, 0, 5);
+
+  EXPECT_EQ(aTree.Length(), 1);
+  EXPECT_NEAR(aTree.MinPoint(0).x(), 0.0, Precision::Confusion());
+  EXPECT_NEAR(aTree.MinPoint(0).w(), 0.0, Precision::Confusion());
+}
+
+TEST(BVH_TreeTest, MultipleLeaves)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  for (int i = 0; i < 10; ++i)
+  {
+    BVH_Vec3d aMin(i * 1.0, 0.0, 0.0);
+    BVH_Vec3d aMax(i * 1.0 + 1.0, 1.0, 1.0);
+    aTree.AddLeafNode(aMin, aMax, i, i);
+  }
+
+  EXPECT_EQ(aTree.Length(), 10);
+
+  for (int i = 0; i < 10; ++i)
+  {
+    EXPECT_TRUE(aTree.IsOuter(i));
+    EXPECT_EQ(aTree.BegPrimitive(i), i);
+    EXPECT_EQ(aTree.EndPrimitive(i), i);
+  }
+}
+
+TEST(BVH_TreeTest, DeepTree)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  // Create leaves
+  BVH_Vec3d aMin1(0.0, 0.0, 0.0);
+  BVH_Vec3d aMax1(1.0, 1.0, 1.0);
+  int       aLeaf1 = aTree.AddLeafNode(aMin1, aMax1, 0, 0);
+
+  BVH_Vec3d aMin2(1.0, 0.0, 0.0);
+  BVH_Vec3d aMax2(2.0, 1.0, 1.0);
+  int       aLeaf2 = aTree.AddLeafNode(aMin2, aMax2, 1, 1);
+
+  BVH_Vec3d aMin3(2.0, 0.0, 0.0);
+  BVH_Vec3d aMax3(3.0, 1.0, 1.0);
+  int       aLeaf3 = aTree.AddLeafNode(aMin3, aMax3, 2, 2);
+
+  BVH_Vec3d aMin4(3.0, 0.0, 0.0);
+  BVH_Vec3d aMax4(4.0, 1.0, 1.0);
+  int       aLeaf4 = aTree.AddLeafNode(aMin4, aMax4, 3, 3);
+
+  // Create intermediate nodes
+  BVH_Vec3d aMinI1(0.0, 0.0, 0.0);
+  BVH_Vec3d aMaxI1(2.0, 1.0, 1.0);
+  int       aInner1 = aTree.AddInnerNode(aMinI1, aMaxI1, aLeaf1, aLeaf2);
+
+  BVH_Vec3d aMinI2(2.0, 0.0, 0.0);
+  BVH_Vec3d aMaxI2(4.0, 1.0, 1.0);
+  int       aInner2 = aTree.AddInnerNode(aMinI2, aMaxI2, aLeaf3, aLeaf4);
+
+  // Create root
+  BVH_Vec3d aMinRoot(0.0, 0.0, 0.0);
+  BVH_Vec3d aMaxRoot(4.0, 1.0, 1.0);
+  int       aRoot = aTree.AddInnerNode(aMinRoot, aMaxRoot, aInner1, aInner2);
+
+  EXPECT_EQ(aTree.Length(), 7);
+  EXPECT_FALSE(aTree.IsOuter(aRoot));
+  EXPECT_FALSE(aTree.IsOuter(aInner1));
+  EXPECT_FALSE(aTree.IsOuter(aInner2));
+  EXPECT_TRUE(aTree.IsOuter(aLeaf1));
+  EXPECT_TRUE(aTree.IsOuter(aLeaf4));
+}
+
+TEST(BVH_TreeTest, ModifyPrimitiveIndices)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 0, 5);
+
+  // Modify primitive indices
+  aTree.BegPrimitive(0) = 10;
+  aTree.EndPrimitive(0) = 20;
+
+  EXPECT_EQ(aTree.BegPrimitive(0), 10);
+  EXPECT_EQ(aTree.EndPrimitive(0), 20);
+  EXPECT_EQ(aTree.NbPrimitives(0), 11);
+}
+
+TEST(BVH_TreeTest, ModifyMinMaxPoints)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 0, 0);
+
+  // Modify min/max points
+  aTree.MinPoint(0) = BVH_Vec3d(-1.0, -1.0, -1.0);
+  aTree.MaxPoint(0) = BVH_Vec3d(2.0, 2.0, 2.0);
+
+  EXPECT_NEAR(aTree.MinPoint(0).x(), -1.0, Precision::Confusion());
+  EXPECT_NEAR(aTree.MaxPoint(0).x(), 2.0, Precision::Confusion());
+}
+
+TEST(BVH_TreeTest, ChangeChild)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  int aLeaf1 = aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 0, 0);
+  int aLeaf2 = aTree.AddLeafNode(BVH_Vec3d(1.0, 0.0, 0.0), BVH_Vec3d(2.0, 1.0, 1.0), 1, 1);
+  int aLeaf3 = aTree.AddLeafNode(BVH_Vec3d(2.0, 0.0, 0.0), BVH_Vec3d(3.0, 1.0, 1.0), 2, 2);
+  int aRoot =
+    aTree.AddInnerNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(2.0, 1.0, 1.0), aLeaf1, aLeaf2);
+
+  EXPECT_EQ(aTree.template Child<0>(aRoot), aLeaf1);
+  EXPECT_EQ(aTree.template Child<1>(aRoot), aLeaf2);
+
+  // Change children
+  aTree.template ChangeChild<0>(aRoot) = aLeaf3;
+
+  EXPECT_EQ(aTree.template Child<0>(aRoot), aLeaf3);
+  EXPECT_EQ(aTree.template Child<1>(aRoot), aLeaf2);
+}
+
+TEST(BVH_TreeTest, NodeInfoBuffer)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 5, 10);
+
+  // Access node info buffer
+  const BVH_Array4i& aBuffer = aTree.NodeInfoBuffer();
+  int                aSize   = BVH::Array<int, 4>::Size(aBuffer);
+  EXPECT_EQ(aSize, 1);
+}
+
+TEST(BVH_TreeTest, MinMaxPointBuffers)
+{
+  BVH_Tree<Standard_Real, 3, BVH_BinaryTree> aTree;
+
+  aTree.AddLeafNode(BVH_Vec3d(0.0, 0.0, 0.0), BVH_Vec3d(1.0, 1.0, 1.0), 0, 0);
+
+  // Access point buffers
+  const auto& aMinBuffer = aTree.MinPointBuffer();
+  const auto& aMaxBuffer = aTree.MaxPointBuffer();
+
+  int aMinSize = BVH::Array<Standard_Real, 3>::Size(aMinBuffer);
+  int aMaxSize = BVH::Array<Standard_Real, 3>::Size(aMaxBuffer);
+  EXPECT_EQ(aMinSize, 1);
+  EXPECT_EQ(aMaxSize, 1);
+}
diff --git a/src/FoundationClasses/TKMath/GTests/BVH_Triangulation_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_Triangulation_Test.cxx
new file mode 100644 (file)
index 0000000..4842442
--- /dev/null
@@ -0,0 +1,330 @@
+// Copyright (c) 2025 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.
+
+#include <gtest/gtest.h>
+
+#include <BVH_Triangulation.hxx>
+
+// =============================================================================
+// BVH_Triangulation Basic Tests
+// =============================================================================
+
+TEST(BVH_TriangulationTest, DefaultConstructor)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  EXPECT_EQ(aTriangulation.Size(), 0);
+  EXPECT_EQ((BVH::Array<Standard_Real, 3>::Size(aTriangulation.Vertices)), 0);
+  EXPECT_EQ((BVH::Array<Standard_Integer, 4>::Size(aTriangulation.Elements)), 0);
+}
+
+TEST(BVH_TriangulationTest, AddSingleTriangle)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  EXPECT_EQ(aTriangulation.Size(), 1);
+  EXPECT_EQ((BVH::Array<Standard_Real, 3>::Size(aTriangulation.Vertices)), 3);
+}
+
+TEST(BVH_TriangulationTest, AddMultipleTriangles)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  for (int i = 0; i < 5; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i);
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  EXPECT_EQ(aTriangulation.Size(), 5);
+  EXPECT_EQ((BVH::Array<Standard_Real, 3>::Size(aTriangulation.Vertices)), 15);
+}
+
+// =============================================================================
+// BVH_Triangulation Box Tests
+// =============================================================================
+
+TEST(BVH_TriangulationTest, BoxForSingleTriangle)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 2.0, 3.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(4.0, 1.0, 2.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(2.0, 3.0, 1.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  BVH_Box<Standard_Real, 3> aBox = aTriangulation.Box(0);
+
+  // Min point should be componentwise min of all vertices
+  EXPECT_NEAR(aBox.CornerMin().x(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMin().y(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMin().z(), 1.0, 1e-10);
+
+  // Max point should be componentwise max of all vertices
+  EXPECT_NEAR(aBox.CornerMax().x(), 4.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().y(), 3.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().z(), 3.0, 1e-10);
+}
+
+TEST(BVH_TriangulationTest, BoxForDegenerateTriangle)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Triangle with all vertices at same point (degenerate)
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 1.0, 1.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  BVH_Box<Standard_Real, 3> aBox = aTriangulation.Box(0);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMin().y(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMin().z(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().y(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().z(), 1.0, 1e-10);
+}
+
+TEST(BVH_TriangulationTest, BoxForFlatTriangle)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Triangle flat in XY plane (Z = 0)
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(2.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 3.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  BVH_Box<Standard_Real, 3> aBox = aTriangulation.Box(0);
+
+  EXPECT_NEAR(aBox.CornerMin().z(), 0.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().z(), 0.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().x(), 2.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().y(), 3.0, 1e-10);
+}
+
+// =============================================================================
+// BVH_Triangulation Center Tests
+// =============================================================================
+
+TEST(BVH_TriangulationTest, CenterComputation)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Triangle with vertices at (0,0,0), (3,0,0), (0,3,0)
+  // Centroid should be at (1,1,0)
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(3.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 3.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  EXPECT_NEAR(aTriangulation.Center(0, 0), 1.0, 1e-10); // X centroid
+  EXPECT_NEAR(aTriangulation.Center(0, 1), 1.0, 1e-10); // Y centroid
+  EXPECT_NEAR(aTriangulation.Center(0, 2), 0.0, 1e-10); // Z centroid
+}
+
+TEST(BVH_TriangulationTest, CenterAlongXAxis)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(2.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(3.0, 0.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  // Centroid X = (1 + 2 + 3) / 3 = 2.0
+  EXPECT_NEAR(aTriangulation.Center(0, 0), 2.0, 1e-10);
+}
+
+TEST(BVH_TriangulationTest, CenterAlongYAxis)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 2.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 4.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 6.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  // Centroid Y = (2 + 4 + 6) / 3 = 4.0
+  EXPECT_NEAR(aTriangulation.Center(0, 1), 4.0, 1e-10);
+}
+
+TEST(BVH_TriangulationTest, CenterAlongZAxis)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 1.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 4.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 7.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  // Centroid Z = (1 + 4 + 7) / 3 = 4.0
+  EXPECT_NEAR(aTriangulation.Center(0, 2), 4.0, 1e-10);
+}
+
+// =============================================================================
+// BVH_Triangulation Swap Tests
+// =============================================================================
+
+TEST(BVH_TriangulationTest, SwapTwoTriangles)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Triangle 0
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 1.0, 0.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  // Triangle 1
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(5.0, 5.0, 5.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(6.0, 5.0, 5.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(5.0, 6.0, 5.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(3, 4, 5, 0));
+
+  Standard_Real aCentroid0Before = aTriangulation.Center(0, 0);
+  Standard_Real aCentroid1Before = aTriangulation.Center(1, 0);
+
+  aTriangulation.Swap(0, 1);
+
+  // After swap, centroids should be swapped
+  EXPECT_NEAR(aTriangulation.Center(0, 0), aCentroid1Before, 1e-10);
+  EXPECT_NEAR(aTriangulation.Center(1, 0), aCentroid0Before, 1e-10);
+}
+
+TEST(BVH_TriangulationTest, SwapPreservesVertices)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Add vertices and triangles
+  for (int i = 0; i < 3; ++i)
+  {
+    Standard_Real x = static_cast<Standard_Real>(i) * 10.0;
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 1.0, 0.0, 0.0));
+    BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(x + 0.5, 1.0, 0.0));
+    BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements,
+                                            BVH_Vec4i(i * 3, i * 3 + 1, i * 3 + 2, 0));
+  }
+
+  Standard_Integer aVertexCount = BVH::Array<Standard_Real, 3>::Size(aTriangulation.Vertices);
+
+  aTriangulation.Swap(0, 2);
+
+  // Vertex count should not change
+  EXPECT_EQ((BVH::Array<Standard_Real, 3>::Size(aTriangulation.Vertices)), aVertexCount);
+}
+
+// =============================================================================
+// BVH_Triangulation 2D Tests
+// =============================================================================
+
+TEST(BVH_TriangulationTest, Triangulation2D)
+{
+  BVH_Triangulation<Standard_Real, 2> aTriangulation2D;
+
+  BVH::Array<Standard_Real, 2>::Append(aTriangulation2D.Vertices, BVH_Vec2d(0.0, 0.0));
+  BVH::Array<Standard_Real, 2>::Append(aTriangulation2D.Vertices, BVH_Vec2d(1.0, 0.0));
+  BVH::Array<Standard_Real, 2>::Append(aTriangulation2D.Vertices, BVH_Vec2d(0.5, 1.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation2D.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  EXPECT_EQ(aTriangulation2D.Size(), 1);
+
+  BVH_Box<Standard_Real, 2> aBox = aTriangulation2D.Box(0);
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMin().y(), 0.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().y(), 1.0, 1e-10);
+}
+
+// =============================================================================
+// BVH_Triangulation Shared Vertices Tests
+// =============================================================================
+
+TEST(BVH_TriangulationTest, SharedVertices)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  // Create quad with 4 vertices, 2 triangles sharing edge
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 0.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, 1.0, 0.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 1.0, 0.0));
+
+  // Triangle 0: (0, 1, 2)
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+  // Triangle 1: (0, 2, 3) - shares vertices 0 and 2 with triangle 0
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 2, 3, 0));
+
+  EXPECT_EQ(aTriangulation.Size(), 2);
+  EXPECT_EQ((BVH::Array<Standard_Real, 3>::Size(aTriangulation.Vertices)), 4);
+
+  // Both triangles should have valid boxes
+  BVH_Box<Standard_Real, 3> aBox0 = aTriangulation.Box(0);
+  BVH_Box<Standard_Real, 3> aBox1 = aTriangulation.Box(1);
+
+  EXPECT_FALSE(aBox0.IsOut(aBox1)); // Should overlap
+}
+
+// =============================================================================
+// BVH_Triangulation Float Precision Tests
+// =============================================================================
+
+TEST(BVH_TriangulationTest, FloatPrecision)
+{
+  BVH_Triangulation<Standard_ShortReal, 3> aTriangulation;
+
+  BVH::Array<Standard_ShortReal, 3>::Append(aTriangulation.Vertices, BVH_Vec3f(0.0f, 0.0f, 0.0f));
+  BVH::Array<Standard_ShortReal, 3>::Append(aTriangulation.Vertices, BVH_Vec3f(1.0f, 0.0f, 0.0f));
+  BVH::Array<Standard_ShortReal, 3>::Append(aTriangulation.Vertices, BVH_Vec3f(0.0f, 1.0f, 0.0f));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  EXPECT_EQ(aTriangulation.Size(), 1);
+
+  BVH_Box<Standard_ShortReal, 3> aBox = aTriangulation.Box(0);
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0f, 1e-6f);
+  EXPECT_NEAR(aBox.CornerMax().y(), 1.0f, 1e-6f);
+}
+
+// =============================================================================
+// BVH_Triangulation Negative Coordinates Tests
+// =============================================================================
+
+TEST(BVH_TriangulationTest, NegativeCoordinates)
+{
+  BVH_Triangulation<Standard_Real, 3> aTriangulation;
+
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(-1.0, -2.0, -3.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(1.0, -1.0, -2.0));
+  BVH::Array<Standard_Real, 3>::Append(aTriangulation.Vertices, BVH_Vec3d(0.0, 0.0, -1.0));
+  BVH::Array<Standard_Integer, 4>::Append(aTriangulation.Elements, BVH_Vec4i(0, 1, 2, 0));
+
+  BVH_Box<Standard_Real, 3> aBox = aTriangulation.Box(0);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), -1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMin().y(), -2.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMin().z(), -3.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().y(), 0.0, 1e-10);
+  EXPECT_NEAR(aBox.CornerMax().z(), -1.0, 1e-10);
+}
index 7f3afa417b0c7fb771dfc00a5d31d7a0172f5958..bfd4fd84f0baf655dd342233424dce811f42e834 100644 (file)
@@ -7,6 +7,19 @@ set(OCCT_TKMath_GTests_FILES
   Bnd_BoundSortBox_Test.cxx
   Bnd_Box_Test.cxx
   Bnd_OBB_Test.cxx
+  BVH_BinnedBuilder_Test.cxx
+  BVH_Box_Test.cxx
+  BVH_BuildQueue_Test.cxx
+  BVH_LinearBuilder_Test.cxx
+  BVH_QuickSorter_Test.cxx
+  BVH_RadixSorter_Test.cxx
+  BVH_Ray_Test.cxx
+  BVH_SpatialMedianBuilder_Test.cxx
+  BVH_SweepPlaneBuilder_Test.cxx
+  BVH_Tools_Test.cxx
+  BVH_Traverse_Test.cxx
+  BVH_Triangulation_Test.cxx
+  BVH_Tree_Test.cxx
   ElCLib_Test.cxx
   gp_Ax3_Test.cxx
   gp_Mat_Test.cxx
index 0f1baa404fa6d809d3d4b01f84ade027750750d3..4bb5ad9f4e8e45e1f4267c69bc311a7957359976 100644 (file)
@@ -946,8 +946,7 @@ Standard_Boolean Select3D_SensitivePrimitiveArray::overlapsElement(
   }
 
   const Standard_Integer  aPatchSize = myBvhIndices.PatchSize(theElemIdx);
-  Select3D_BndBox3d       aBox;
-  Standard_Boolean        aResult = Standard_False;
+  Standard_Boolean        aResult    = Standard_False;
   SelectBasics_PickResult aPickResult;
   switch (myPrimType)
   {