From 1663c65625565d11fc4608d2d9eab700d9d0b9a3 Mon Sep 17 00:00:00 2001 From: Pasukhin Dmitry Date: Wed, 26 Nov 2025 21:15:01 +0000 Subject: [PATCH] Foundation Classes - Refactor BVH_Box to use generic vector types and add transformation tests (#858) - Replaced `Graphic3d_Vec3d` and `Graphic3d_Vec4d` with generic `BVH_VecNt` and `BVH::VectorType::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 | 29 +++--- .../TKMath/GTests/BVH_Box_Test.cxx | 90 +++++++++++++++++++ 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/src/FoundationClasses/TKMath/BVH/BVH_Box.hxx b/src/FoundationClasses/TKMath/BVH/BVH_Box.hxx index d511df0f17..8f10f27e28 100644 --- a/src/FoundationClasses/TKMath/BVH/BVH_Box.hxx +++ b/src/FoundationClasses/TKMath/BVH/BVH_Box.hxx @@ -21,8 +21,6 @@ #include #include #include -#include -#include #include @@ -68,6 +66,8 @@ public: //! given transformation to this box. BVH_Box Transformed(const NCollection_Mat4& theTransform) const { + using BVH_VecNt = typename BVH_Box::BVH_VecNt; + const BVH_Box* aThis = static_cast*>(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::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 aResultBox(aNewMinPnt, aNewMaxPnt); - return aResultBox; + return BVH_Box(aNewMinPnt, aNewMaxPnt); } }; diff --git a/src/FoundationClasses/TKMath/GTests/BVH_Box_Test.cxx b/src/FoundationClasses/TKMath/GTests/BVH_Box_Test.cxx index c05d57eaf2..a564aba326 100644 --- a/src/FoundationClasses/TKMath/GTests/BVH_Box_Test.cxx +++ b/src/FoundationClasses/TKMath/GTests/BVH_Box_Test.cxx @@ -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 aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f)); + + NCollection_Mat4 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 aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f)); + + NCollection_Mat4 aTransform; + aTransform.InitIdentity(); + aTransform.SetColumn(3, NCollection_Vec3(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 aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f)); + + NCollection_Mat4 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 aBox(BVH_Vec3f(0.0f, 0.0f, 0.0f), BVH_Vec3f(1.0f, 1.0f, 1.0f)); + + NCollection_Mat4 aTransform; + aTransform.InitIdentity(); + aTransform.SetColumn(3, NCollection_Vec3(10.0f, 20.0f, 30.0f)); + + BVH_Box 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 aBox; // Invalid box + + NCollection_Mat4 aTransform; + aTransform.InitIdentity(); + aTransform.SetColumn(3, NCollection_Vec3(10.0f, 20.0f, 30.0f)); + + aBox.Transform(aTransform); + + // Should remain invalid + EXPECT_FALSE(aBox.IsValid()); +} -- 2.39.5