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<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
This commit is contained in:
Pasukhin Dmitry
2025-11-26 21:15:01 +00:00
committed by GitHub
parent 652b07f92c
commit 1663c65625
2 changed files with 103 additions and 16 deletions

View 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);
}
};

View 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());
}