]> OCCT Git - occt.git/commitdiff
Foundation Classes - Refactor BVH_Box to use generic vector types and add transformat...
authorPasukhin Dmitry <dpasukhi@opencascade.com>
Wed, 26 Nov 2025 21:15:01 +0000 (21:15 +0000)
committerGitHub <noreply@github.com>
Wed, 26 Nov 2025 21:15:01 +0000 (21:15 +0000)
- Replaced `Graphic3d_Vec3d` and `Graphic3d_Vec4d` with generic `BVH_VecNt` and `BVH::VectorType<T, 4>::Type`
- Changed `Standard_Integer` and `Standard_Real` to template type `T` and `int` for generic implementation
- Added comprehensive test coverage for float-precision transformations

src/FoundationClasses/TKMath/BVH/BVH_Box.hxx
src/FoundationClasses/TKMath/GTests/BVH_Box_Test.cxx

index d511df0f179ad46418b41fa3dc8f1136f5c4d10a..8f10f27e28f89ac9a718441e397d45b2f29103cd 100644 (file)
@@ -21,8 +21,6 @@
 #include <Standard_Macro.hxx>
 #include <Standard_Dump.hxx>
 #include <Standard_ShortReal.hxx>
-#include <Graphic3d_Vec4.hxx>
-#include <Graphic3d_Vec3.hxx>
 
 #include <limits>
 
@@ -68,6 +66,8 @@ public:
   //! given transformation to this box.
   BVH_Box<T, 3> Transformed(const NCollection_Mat4<T>& theTransform) const
   {
+    using BVH_VecNt = typename BVH_Box<T, 3>::BVH_VecNt;
+
     const BVH_Box<T, 3>* aThis = static_cast<const BVH_Box<T, 3>*>(this);
     if (theTransform.IsIdentity())
     {
@@ -80,15 +80,13 @@ public:
     }
 
     // Untransformed AABB min and max points
-    Graphic3d_Vec3d anOldMinPnt = aThis->CornerMin();
-    Graphic3d_Vec3d anOldMaxPnt = aThis->CornerMax();
+    const BVH_VecNt& anOldMinPnt = aThis->CornerMin();
+    const BVH_VecNt& anOldMaxPnt = aThis->CornerMax();
 
     // Define an empty AABB located in the transformation translation point
-    Graphic3d_Vec4d aTranslation = theTransform.GetColumn(3);
-    Graphic3d_Vec3d aNewMinPnt =
-      Graphic3d_Vec3d(aTranslation.x(), aTranslation.y(), aTranslation.z());
-    Graphic3d_Vec3d aNewMaxPnt =
-      Graphic3d_Vec3d(aTranslation.x(), aTranslation.y(), aTranslation.z());
+    const typename BVH::VectorType<T, 4>::Type aTranslation = theTransform.GetColumn(3);
+    BVH_VecNt aNewMinPnt = BVH_VecNt(aTranslation.x(), aTranslation.y(), aTranslation.z());
+    BVH_VecNt aNewMaxPnt = BVH_VecNt(aTranslation.x(), aTranslation.y(), aTranslation.z());
 
     // This implements James Arvo's algorithm for transforming an axis-aligned bounding box (AABB)
     // under an affine transformation. For each row of the transformation matrix, we compute
@@ -96,21 +94,20 @@ public:
     // minimum and maximum values to form the new bounding box. This ensures that the transformed
     // box tightly encloses the original box after transformation, accounting for rotation and
     // scaling.
-    for (Standard_Integer aCol = 0; aCol < 3; ++aCol)
+    for (int aCol = 0; aCol < 3; ++aCol)
     {
-      for (Standard_Integer aRow = 0; aRow < 3; ++aRow)
+      for (int aRow = 0; aRow < 3; ++aRow)
       {
-        Standard_Real aMatValue = theTransform.GetValue(aRow, aCol);
-        Standard_Real anOffset1 = aMatValue * anOldMinPnt.GetData()[aCol];
-        Standard_Real anOffset2 = aMatValue * anOldMaxPnt.GetData()[aCol];
+        const T aMatValue = theTransform.GetValue(aRow, aCol);
+        const T anOffset1 = aMatValue * anOldMinPnt.GetData()[aCol];
+        const T anOffset2 = aMatValue * anOldMaxPnt.GetData()[aCol];
 
         aNewMinPnt.ChangeData()[aRow] += (std::min)(anOffset1, anOffset2);
         aNewMaxPnt.ChangeData()[aRow] += (std::max)(anOffset1, anOffset2);
       }
     }
 
-    BVH_Box<T, 3> aResultBox(aNewMinPnt, aNewMaxPnt);
-    return aResultBox;
+    return BVH_Box<T, 3>(aNewMinPnt, aNewMaxPnt);
   }
 };
 
index c05d57eaf2ed3a519cc4a34756ce28318771601b..a564aba3267397e920f1f7652eaa0fb71f204e18 100644 (file)
@@ -1043,3 +1043,93 @@ TEST(BVH_BoxTest, Constexpr_DefaultInvalid)
 
   static_assert(!aBox.IsValid(), "Default constexpr box should be invalid");
 }
+
+// =======================================================================================
+// Tests for Transform/Transformed with float precision
+// =======================================================================================
+
+TEST(BVH_BoxTest, Transform_Float_Identity)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+
+  NCollection_Mat4<Standard_ShortReal> aIdentity;
+  aIdentity.InitIdentity();
+
+  aBox.Transform(aIdentity);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0f, 1e-5f);
+}
+
+TEST(BVH_BoxTest, Transform_Float_Translation)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+
+  NCollection_Mat4<Standard_ShortReal> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetColumn(3, NCollection_Vec3<Standard_ShortReal>(5.0f, 10.0f, 15.0f));
+
+  aBox.Transform(aTransform);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), 5.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMin().y(), 10.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMin().z(), 15.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().x(), 6.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().y(), 11.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().z(), 16.0f, 1e-5f);
+}
+
+TEST(BVH_BoxTest, Transform_Float_Scale)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+
+  NCollection_Mat4<Standard_ShortReal> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetValue(0, 0, 2.0f); // Scale X by 2
+  aTransform.SetValue(1, 1, 3.0f); // Scale Y by 3
+  aTransform.SetValue(2, 2, 4.0f); // Scale Z by 4
+
+  aBox.Transform(aTransform);
+
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().x(), 2.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().y(), 3.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().z(), 4.0f, 1e-5f);
+}
+
+TEST(BVH_BoxTest, Transformed_Float_Translation)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f));
+
+  NCollection_Mat4<Standard_ShortReal> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetColumn(3, NCollection_Vec3<Standard_ShortReal>(10.0f, 20.0f, 30.0f));
+
+  BVH_Box<Standard_ShortReal, 3> aTransformed = aBox.Transformed(aTransform);
+
+  // Original should be unchanged
+  EXPECT_NEAR(aBox.CornerMin().x(), 0.0f, 1e-5f);
+  EXPECT_NEAR(aBox.CornerMax().x(), 1.0f, 1e-5f);
+
+  // Transformed should be translated
+  EXPECT_NEAR(aTransformed.CornerMin().x(), 10.0f, 1e-5f);
+  EXPECT_NEAR(aTransformed.CornerMin().y(), 20.0f, 1e-5f);
+  EXPECT_NEAR(aTransformed.CornerMin().z(), 30.0f, 1e-5f);
+  EXPECT_NEAR(aTransformed.CornerMax().x(), 11.0f, 1e-5f);
+  EXPECT_NEAR(aTransformed.CornerMax().y(), 21.0f, 1e-5f);
+  EXPECT_NEAR(aTransformed.CornerMax().z(), 31.0f, 1e-5f);
+}
+
+TEST(BVH_BoxTest, Transform_Float_InvalidBox)
+{
+  BVH_Box<Standard_ShortReal, 3> aBox; // Invalid box
+
+  NCollection_Mat4<Standard_ShortReal> aTransform;
+  aTransform.InitIdentity();
+  aTransform.SetColumn(3, NCollection_Vec3<Standard_ShortReal>(10.0f, 20.0f, 30.0f));
+
+  aBox.Transform(aTransform);
+
+  // Should remain invalid
+  EXPECT_FALSE(aBox.IsValid());
+}