From b91b6d48b4a9bbf93b6f2dcc0dcb7162fce5b4de Mon Sep 17 00:00:00 2001 From: Pasukhin Dmitry Date: Fri, 24 Apr 2026 16:15:40 +0100 Subject: [PATCH] Modeling - Optimize interference detection in polyhedra (#924) - Introduced `IntPatch_PolyhedronBVH` to wrap polyhedra as BVH primitive sets - Implemented `IntPatch_BVHTraversal` for efficient dual-tree traversal to find candidate triangle pairs - Refactored `IntPatch_InterferencePolyhedron::Interference()` to use BVH-based detection --- .../TKGeomAlgo/GTests/FILES.cmake | 1 + .../GTests/IntPatch_PolyhedronBVH_Test.cxx | 239 ++++++++++++++++++ .../TKGeomAlgo/IntPatch/FILES.cmake | 4 + .../IntPatch/IntPatch_BVHTraversal.cxx | 116 +++++++++ .../IntPatch/IntPatch_BVHTraversal.hxx | 95 +++++++ .../IntPatch_InterferencePolyhedron.cxx | 94 +------ .../IntPatch/IntPatch_PolyhedronBVH.cxx | 189 ++++++++++++++ .../IntPatch/IntPatch_PolyhedronBVH.hxx | 85 +++++++ 8 files changed, 743 insertions(+), 80 deletions(-) create mode 100644 src/ModelingAlgorithms/TKGeomAlgo/GTests/IntPatch_PolyhedronBVH_Test.cxx create mode 100644 src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_BVHTraversal.cxx create mode 100644 src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_BVHTraversal.hxx create mode 100644 src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_PolyhedronBVH.cxx create mode 100644 src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_PolyhedronBVH.hxx diff --git a/src/ModelingAlgorithms/TKGeomAlgo/GTests/FILES.cmake b/src/ModelingAlgorithms/TKGeomAlgo/GTests/FILES.cmake index a350ba9c7c..53c4ca9f90 100644 --- a/src/ModelingAlgorithms/TKGeomAlgo/GTests/FILES.cmake +++ b/src/ModelingAlgorithms/TKGeomAlgo/GTests/FILES.cmake @@ -20,6 +20,7 @@ set(OCCT_TKGeomAlgo_GTests_FILES IntCurveSurface_ThePolygonOfHInter_Test.cxx Intf_Tool_Test.cxx IntPatch_Polyhedron_Test.cxx + IntPatch_PolyhedronBVH_Test.cxx IntPolyh_Intersection_Test.cxx IntPolyh_Point_Test.cxx IntSurf_LineOn2S_Test.cxx diff --git a/src/ModelingAlgorithms/TKGeomAlgo/GTests/IntPatch_PolyhedronBVH_Test.cxx b/src/ModelingAlgorithms/TKGeomAlgo/GTests/IntPatch_PolyhedronBVH_Test.cxx new file mode 100644 index 0000000000..80b254ad30 --- /dev/null +++ b/src/ModelingAlgorithms/TKGeomAlgo/GTests/IntPatch_PolyhedronBVH_Test.cxx @@ -0,0 +1,239 @@ +// Copyright (c) 2024 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Test fixture for IntPatch_PolyhedronBVH tests +class IntPatch_PolyhedronBVHTest : public testing::Test +{ +protected: + void SetUp() override + { + // Create a sphere surface + const gp_Sphere aSphere(gp_Ax3(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 1.0); + const occ::handle aSphereSurf = new Geom_SphericalSurface(aSphere); + mySphereAdaptor = new GeomAdaptor_Surface(aSphereSurf); + + // Create a cylinder surface + const gp_Cylinder aCyl(gp_Ax3(gp_Pnt(0.5, 0, 0), gp_Dir(0, 0, 1)), 0.8); + const occ::handle aCylSurf = new Geom_CylindricalSurface(aCyl); + myCylAdaptor = new GeomAdaptor_Surface(aCylSurf, 0, 2 * M_PI, -1, 1); + } + + occ::handle mySphereAdaptor; + occ::handle myCylAdaptor; +}; + +// Test that PolyhedronBVH can be constructed and has correct size +TEST_F(IntPatch_PolyhedronBVHTest, Construction) +{ + constexpr int aNbU = 10; + constexpr int aNbV = 10; + + const IntPatch_Polyhedron aPoly(mySphereAdaptor, aNbU, aNbV); + const IntPatch_PolyhedronBVH aBVH(aPoly); + + EXPECT_TRUE(aBVH.IsInitialized()); + EXPECT_GT(aBVH.Size(), 0); + EXPECT_LE(aBVH.Size(), IntPatch_PolyhedronTool::NbTriangles(aPoly)); +} + +// Test that Box() returns valid bounding boxes +TEST_F(IntPatch_PolyhedronBVHTest, Box) +{ + constexpr int aNbU = 5; + constexpr int aNbV = 5; + + const IntPatch_Polyhedron aPoly(mySphereAdaptor, aNbU, aNbV); + const IntPatch_PolyhedronBVH aBVH(aPoly); + + // Check that all boxes are valid + for (int i = 0; i < aBVH.Size(); ++i) + { + const BVH_Box aBox = aBVH.Box(i); + EXPECT_TRUE(aBox.IsValid()) << "Box " << i << " is not valid"; + } +} + +// Test that Center() returns valid centroid coordinates +TEST_F(IntPatch_PolyhedronBVHTest, Center) +{ + constexpr int aNbU = 5; + const int aNbV = 5; + + const IntPatch_Polyhedron aPoly(mySphereAdaptor, aNbU, aNbV); + const IntPatch_PolyhedronBVH aBVH(aPoly); + + // Check that centroids are within the overall bounding box + const Bnd_Box& aBounding = IntPatch_PolyhedronTool::Bounding(aPoly); + double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax; + aBounding.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax); + + for (int i = 0; i < aBVH.Size(); ++i) + { + const double aCx = aBVH.Center(i, 0); + const double aCy = aBVH.Center(i, 1); + const double aCz = aBVH.Center(i, 2); + + EXPECT_GE(aCx, aXmin - 1e-10) << "Center X of triangle " << i << " is below min"; + EXPECT_LE(aCx, aXmax + 1e-10) << "Center X of triangle " << i << " is above max"; + EXPECT_GE(aCy, aYmin - 1e-10) << "Center Y of triangle " << i << " is below min"; + EXPECT_LE(aCy, aYmax + 1e-10) << "Center Y of triangle " << i << " is above max"; + EXPECT_GE(aCz, aZmin - 1e-10) << "Center Z of triangle " << i << " is below min"; + EXPECT_LE(aCz, aZmax + 1e-10) << "Center Z of triangle " << i << " is above max"; + } +} + +// Test that OriginalIndex() returns valid 1-based indices +TEST_F(IntPatch_PolyhedronBVHTest, OriginalIndex) +{ + constexpr int aNbU = 5; + constexpr int aNbV = 5; + + const IntPatch_Polyhedron aPoly(mySphereAdaptor, aNbU, aNbV); + IntPatch_PolyhedronBVH aBVH(aPoly); + + const int aNbTri = aBVH.Size(); + const int aNbPolyTri = IntPatch_PolyhedronTool::NbTriangles(aPoly); + + // Before BVH build, indices should be sequential + for (int i = 0; i < aNbTri; ++i) + { + const int anOrigIdx = aBVH.OriginalIndex(i); + EXPECT_GE(anOrigIdx, 1) << "Original index should be >= 1"; + EXPECT_LE(anOrigIdx, aNbPolyTri) << "Original index should be <= NbTriangles"; + } + + // Force BVH build + aBVH.BVH(); + + // After BVH build, each original index should appear exactly once + std::vector aUsed(aNbPolyTri + 1, false); + for (int i = 0; i < aNbTri; ++i) + { + const int anOrigIdx = aBVH.OriginalIndex(i); + EXPECT_FALSE(aUsed[anOrigIdx]) << "Original index " << anOrigIdx << " used more than once"; + aUsed[anOrigIdx] = true; + } +} + +// Test BVH traversal finds overlapping triangles +TEST_F(IntPatch_PolyhedronBVHTest, Traversal) +{ + constexpr int aNbU = 10; + constexpr int aNbV = 10; + + const IntPatch_Polyhedron aPoly1(mySphereAdaptor, aNbU, aNbV); + const IntPatch_Polyhedron aPoly2(myCylAdaptor, aNbU, aNbV); + + IntPatch_PolyhedronBVH aSet1(aPoly1); + IntPatch_PolyhedronBVH aSet2(aPoly2); + + IntPatch_BVHTraversal aTraversal; + const int aNbPairs = aTraversal.Perform(aSet1, aSet2, false); + + // The sphere and cylinder should have some overlapping triangles + EXPECT_GT(aNbPairs, 0) << "Expected some overlapping triangle pairs"; + EXPECT_EQ(aNbPairs, static_cast(aTraversal.Pairs().Size())); + + // Verify all pairs have valid indices + for (const auto& aPair : aTraversal.Pairs()) + { + EXPECT_GE(aPair.First, 1); + EXPECT_LE(aPair.First, IntPatch_PolyhedronTool::NbTriangles(aPoly1)); + EXPECT_GE(aPair.Second, 1); + EXPECT_LE(aPair.Second, IntPatch_PolyhedronTool::NbTriangles(aPoly2)); + } +} + +// Test self-interference mode +TEST_F(IntPatch_PolyhedronBVHTest, SelfInterference) +{ + constexpr int aNbU = 5; + constexpr int aNbV = 5; + + const IntPatch_Polyhedron aPoly(mySphereAdaptor, aNbU, aNbV); + IntPatch_PolyhedronBVH aSet(aPoly); + + IntPatch_BVHTraversal aTraversal; + aTraversal.Perform(aSet, aSet, true); // self-interference mode + + // In self-interference mode, First < Second for all pairs + for (const auto& aPair : aTraversal.Pairs()) + { + EXPECT_LT(aPair.First, aPair.Second) << "Self-interference should have First < Second"; + } +} + +// Test that IntPatch_InterferencePolyhedron works with BVH +TEST_F(IntPatch_PolyhedronBVHTest, InterferencePolyhedron) +{ + constexpr int aNbU = 10; + constexpr int aNbV = 10; + + const IntPatch_Polyhedron aPoly1(mySphereAdaptor, aNbU, aNbV); + const IntPatch_Polyhedron aPoly2(myCylAdaptor, aNbU, aNbV); + + // Create interference and check it completes without error + const IntPatch_InterferencePolyhedron anInterf(aPoly1, aPoly2); + + // The result should have some section points or lines + // (exact number depends on geometry, just verify it runs) + const int aNbPoints = anInterf.NbSectionPoints(); + const int aNbLines = anInterf.NbSectionLines(); + const int aNbZones = anInterf.NbTangentZones(); + + // At least some intersection should be found + EXPECT_TRUE(aNbPoints > 0 || aNbLines > 0 || aNbZones > 0) + << "Expected some intersection results"; +} + +// Test with non-overlapping surfaces +TEST_F(IntPatch_PolyhedronBVHTest, NoOverlap) +{ + // Create a plane far from the sphere + const gp_Pln aPlane(gp_Pnt(10, 10, 10), gp_Dir(1, 0, 0)); + const occ::handle aPlaneSurf = new Geom_Plane(aPlane); + const occ::handle aPlaneAdaptor = + new GeomAdaptor_Surface(aPlaneSurf, -1, 1, -1, 1); + + const int aNbU = 5; + const int aNbV = 5; + + const IntPatch_Polyhedron aPoly1(mySphereAdaptor, aNbU, aNbV); + const IntPatch_Polyhedron aPoly2(aPlaneAdaptor, aNbU, aNbV); + + IntPatch_PolyhedronBVH aSet1(aPoly1); + IntPatch_PolyhedronBVH aSet2(aPoly2); + + IntPatch_BVHTraversal aTraversal; + const int aNbPairs = aTraversal.Perform(aSet1, aSet2, false); + + // Far-away surfaces should have no overlapping boxes + EXPECT_EQ(aNbPairs, 0) << "Expected no overlapping triangle pairs for distant surfaces"; +} diff --git a/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/FILES.cmake b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/FILES.cmake index e14f33a24e..bceb2d0e05 100644 --- a/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/FILES.cmake +++ b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/FILES.cmake @@ -10,6 +10,8 @@ set(OCCT_IntPatch_FILES IntPatch_ArcFunction.cxx IntPatch_ArcFunction.hxx IntPatch_ArcFunction.lxx + IntPatch_BVHTraversal.cxx + IntPatch_BVHTraversal.hxx IntPatch_CSFunction.cxx IntPatch_CSFunction.hxx IntPatch_CurvIntSurf.hxx @@ -54,6 +56,8 @@ set(OCCT_IntPatch_FILES IntPatch_Polygo.lxx IntPatch_Polyhedron.cxx IntPatch_Polyhedron.hxx + IntPatch_PolyhedronBVH.cxx + IntPatch_PolyhedronBVH.hxx IntPatch_PolyhedronTool.hxx IntPatch_PolyhedronTool.lxx IntPatch_PrmPrmIntersection.cxx diff --git a/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_BVHTraversal.cxx b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_BVHTraversal.cxx new file mode 100644 index 0000000000..116141a1fd --- /dev/null +++ b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_BVHTraversal.cxx @@ -0,0 +1,116 @@ +// Copyright (c) 2024 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 + +#include + +//================================================================================================== + +IntPatch_BVHTraversal::IntPatch_BVHTraversal() + : BVH_PairTraverse(), + mySet1(nullptr), + mySet2(nullptr), + mySelfInterference(false) +{ +} + +//================================================================================================== + +IntPatch_BVHTraversal::~IntPatch_BVHTraversal() {} + +//================================================================================================== + +int IntPatch_BVHTraversal::Perform(IntPatch_PolyhedronBVH& theSet1, + IntPatch_PolyhedronBVH& theSet2, + bool theSelfInterference) +{ + myPairs.Clear(); + mySet1 = &theSet1; + mySet2 = &theSet2; + mySelfInterference = theSelfInterference; + + if (!theSet1.IsInitialized() || !theSet2.IsInitialized()) + { + return 0; + } + + if (theSet1.Size() == 0 || theSet2.Size() == 0) + { + return 0; + } + + // Get BVH trees (builds them if necessary) + const opencascade::handle>& aBVH1 = theSet1.BVH(); + const opencascade::handle>& aBVH2 = theSet2.BVH(); + + if (aBVH1.IsNull() || aBVH2.IsNull()) + { + return 0; + } + + // Perform dual-tree traversal + return Select(aBVH1, aBVH2); +} + +//================================================================================================== + +bool IntPatch_BVHTraversal::RejectNode(const BVH_Vec3d& theCMin1, + const BVH_Vec3d& theCMax1, + const BVH_Vec3d& theCMin2, + const BVH_Vec3d& theCMax2, + double& /*theMetric*/) const +{ + // AABB overlap test: reject if boxes don't overlap + // Two boxes overlap if and only if they overlap on all three axes + if (theCMin1.x() > theCMax2.x() || theCMax1.x() < theCMin2.x()) + { + return true; // No overlap on X axis + } + if (theCMin1.y() > theCMax2.y() || theCMax1.y() < theCMin2.y()) + { + return true; // No overlap on Y axis + } + if (theCMin1.z() > theCMax2.z() || theCMax1.z() < theCMin2.z()) + { + return true; // No overlap on Z axis + } + + return false; // Boxes overlap +} + +//================================================================================================== + +bool IntPatch_BVHTraversal::Accept(const int theIndex1, const int theIndex2) +{ + if (mySet1 == nullptr || mySet2 == nullptr) + { + return false; + } + + // Get original 1-based triangle indices + const int anOrigIdx1 = mySet1->OriginalIndex(theIndex1); + const int anOrigIdx2 = mySet2->OriginalIndex(theIndex2); + + // In self-interference mode, skip pairs where first index >= second index + // to avoid testing the same pair twice (and to avoid self-intersection) + if (mySelfInterference && anOrigIdx1 >= anOrigIdx2) + { + return false; + } + + // Store the pair + myPairs.Append(TrianglePair(anOrigIdx1, anOrigIdx2)); + + return true; +} diff --git a/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_BVHTraversal.hxx b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_BVHTraversal.hxx new file mode 100644 index 0000000000..de29d0624d --- /dev/null +++ b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_BVHTraversal.hxx @@ -0,0 +1,95 @@ +// Copyright (c) 2024 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef IntPatch_BVHTraversal_HeaderFile +#define IntPatch_BVHTraversal_HeaderFile + +#include +#include + +class IntPatch_PolyhedronBVH; + +//! Performs BVH tree traversal of two polyhedra to find candidate triangle pairs +//! for intersection testing. This class implements the BVH_PairTraverse interface +//! to efficiently find potentially intersecting triangles using bounding box tests. +//! +//! The traversal collects pairs of original (1-based) triangle indices that have +//! overlapping bounding boxes, which should then be tested for actual geometric +//! intersection using IntPatch_InterferencePolyhedron::Intersect(). +class IntPatch_BVHTraversal : public BVH_PairTraverse +{ +public: + //! Pair of triangle indices (both 1-based, original indices in polyhedra). + struct TrianglePair + { + int First; //!< Triangle index in first polyhedron (1-based) + int Second; //!< Triangle index in second polyhedron (1-based) + + TrianglePair(int theFirst = 0, int theSecond = 0) + : First(theFirst), + Second(theSecond) + { + } + }; + +public: + //! Creates an empty traversal object. + Standard_EXPORT IntPatch_BVHTraversal(); + + //! Destructor. + Standard_EXPORT virtual ~IntPatch_BVHTraversal(); + + //! Performs BVH traversal and collects candidate triangle pairs. + //! @param[in] theSet1 BVH set for the first polyhedron + //! @param[in] theSet2 BVH set for the second polyhedron + //! @param[in] theSelfInterference if true, skip pairs where first index >= second index + //! (used for self-intersection where we don't want to test same pair twice) + //! @return number of collected pairs + Standard_EXPORT int Perform(IntPatch_PolyhedronBVH& theSet1, + IntPatch_PolyhedronBVH& theSet2, + bool theSelfInterference = false); + + //! Returns the collected triangle pairs. + const NCollection_Vector& Pairs() const { return myPairs; } + + //! Clears the collected pairs. + void Clear() { myPairs.Clear(); } + +public: //! @name BVH_PairTraverse interface implementation + //! Rejects pair of nodes if their bounding boxes don't overlap. + //! @param[in] theCMin1 minimum corner of the first node's bounding box + //! @param[in] theCMax1 maximum corner of the first node's bounding box + //! @param[in] theCMin2 minimum corner of the second node's bounding box + //! @param[in] theCMax2 maximum corner of the second node's bounding box + //! @param[out] theMetric unused metric parameter + //! @return true if the pair should be rejected (no overlap), false otherwise + Standard_EXPORT virtual bool RejectNode(const BVH_Vec3d& theCMin1, + const BVH_Vec3d& theCMax1, + const BVH_Vec3d& theCMin2, + const BVH_Vec3d& theCMax2, + double& theMetric) const override; + + //! Accepts a pair of leaf elements and stores their original indices. + //! @param[in] theIndex1 0-based index in the first BVH set + //! @param[in] theIndex2 0-based index in the second BVH set + //! @return true (always accepts the pair) + Standard_EXPORT virtual bool Accept(const int theIndex1, const int theIndex2) override; + +private: + IntPatch_PolyhedronBVH* mySet1; //!< First BVH set + IntPatch_PolyhedronBVH* mySet2; //!< Second BVH set + bool mySelfInterference; //!< Self-interference mode flag + NCollection_Vector myPairs; //!< Collected triangle pairs +}; + +#endif // IntPatch_BVHTraversal_HeaderFile diff --git a/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_InterferencePolyhedron.cxx b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_InterferencePolyhedron.cxx index 535a7cdc19..0b62e923da 100644 --- a/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_InterferencePolyhedron.cxx +++ b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_InterferencePolyhedron.cxx @@ -14,7 +14,6 @@ // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. -#include #include #include #include @@ -22,8 +21,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -117,89 +118,22 @@ void IntPatch_InterferencePolyhedron::Perform(const IntPatch_Polyhedron& Objet) void IntPatch_InterferencePolyhedron::Interference(const IntPatch_Polyhedron&) {} -void IntPatch_InterferencePolyhedron::Interference(const IntPatch_Polyhedron& FirstPol, - const IntPatch_Polyhedron& SeconPol) +void IntPatch_InterferencePolyhedron::Interference(const IntPatch_Polyhedron& theFirstPol, + const IntPatch_Polyhedron& theSecondPol) { - bool gridOnFirst = true; - int NbTrianglesFirstPol = IntPatch_PolyhedronTool::NbTriangles(FirstPol); - int NbTrianglesSecondPol = IntPatch_PolyhedronTool::NbTriangles(SeconPol); - int iFirst, iSecon; + // Build BVH sets for both polyhedra + IntPatch_PolyhedronBVH aSet1(theFirstPol); + IntPatch_PolyhedronBVH aSet2(theSecondPol); - //------------------------------------------------------------------------------------------ - //-- the same number of triangles it is necessary to test better on - //-- the size of boxes. - //-- - //-- the second is chosen if nbTri1 > 2*nbTri2 or if VolBoit1 > 2*VolBoit2 - //-- - //--if (!SelfIntf && NbTrianglesFirstPol>NbTrianglesSecondPol) - //-- gridOnFirst=false; + // Find candidate triangle pairs via BVH traversal + IntPatch_BVHTraversal aTraversal; + aTraversal.Perform(aSet1, aSet2, SelfIntf); - if (!SelfIntf) + // Process each candidate pair + const NCollection_Vector& aPairs = aTraversal.Pairs(); + for (const auto& aPair : aPairs) { - if (NbTrianglesFirstPol > NbTrianglesSecondPol + NbTrianglesSecondPol) - gridOnFirst = false; - - double vol1, vol2, Xmin, Ymin, Zmin, Xmax, Ymax, Zmax; - IntPatch_PolyhedronTool::Bounding(FirstPol).Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax); - vol1 = (Xmax - Xmin) * (Ymax - Ymin) * (Zmax - Zmin); - - IntPatch_PolyhedronTool::Bounding(SeconPol).Get(Xmin, Ymin, Zmin, Xmax, Ymax, Zmax); - vol2 = (Xmax - Xmin) * (Ymax - Ymin) * (Zmax - Zmin); - - if (vol1 > 8.0 * vol2) - gridOnFirst = false; - } - - if (gridOnFirst) - { - Bnd_BoundSortBox TheGridFirst; - TheGridFirst.Initialize(IntPatch_PolyhedronTool::Bounding(FirstPol), - IntPatch_PolyhedronTool::ComponentsBounding(FirstPol)); - - for (iSecon = 1; iSecon <= NbTrianglesSecondPol; iSecon++) - { - - NCollection_List::Iterator iLoI( - TheGridFirst.Compare(IntPatch_PolyhedronTool::ComponentsBounding(SeconPol)->Value(iSecon))); - while (iLoI.More()) - { - iFirst = iLoI.Value(); - if (SelfIntf) - { - if (iFirst < iSecon) - Intersect(iFirst, FirstPol, iSecon, SeconPol); - } - else - Intersect(iFirst, FirstPol, iSecon, SeconPol); - iLoI.Next(); - } - } - } - - else - { - Bnd_BoundSortBox TheGridSecond; - TheGridSecond.Initialize(IntPatch_PolyhedronTool::Bounding(SeconPol), - IntPatch_PolyhedronTool::ComponentsBounding(SeconPol)); - - for (iFirst = 1; iFirst <= NbTrianglesFirstPol; iFirst++) - { - NCollection_List::Iterator iLoI(TheGridSecond.Compare( - IntPatch_PolyhedronTool::ComponentsBounding(FirstPol)->Value(iFirst))); - - while (iLoI.More()) - { - iSecon = iLoI.Value(); - if (SelfIntf) - { - if (iFirst < iSecon) - Intersect(iFirst, FirstPol, iSecon, SeconPol); - } - else - Intersect(iFirst, FirstPol, iSecon, SeconPol); - iLoI.Next(); - } - } + Intersect(aPair.First, theFirstPol, aPair.Second, theSecondPol); } } diff --git a/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_PolyhedronBVH.cxx b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_PolyhedronBVH.cxx new file mode 100644 index 0000000000..d00b6f7d98 --- /dev/null +++ b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_PolyhedronBVH.cxx @@ -0,0 +1,189 @@ +// Copyright (c) 2024 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 + +#include +#include +#include +#include + +#include + +//================================================================================================== + +IntPatch_PolyhedronBVH::IntPatch_PolyhedronBVH() + : BVH_PrimitiveSet( + new BVH_LinearBuilder(BVH_Constants_LeafNodeSizeDefault, + BVH_Constants_MaxTreeDepth)), + myPoly(nullptr) +{ +} + +//================================================================================================== + +IntPatch_PolyhedronBVH::IntPatch_PolyhedronBVH(const IntPatch_Polyhedron& thePoly) + : BVH_PrimitiveSet( + new BVH_LinearBuilder(BVH_Constants_LeafNodeSizeDefault, + BVH_Constants_MaxTreeDepth)), + myPoly(nullptr) +{ + Init(thePoly); +} + +//================================================================================================== + +IntPatch_PolyhedronBVH::~IntPatch_PolyhedronBVH() +{ + // +} + +//================================================================================================== + +void IntPatch_PolyhedronBVH::Init(const IntPatch_Polyhedron& thePoly) +{ + myPoly = &thePoly; + + const int aNbTriangles = IntPatch_PolyhedronTool::NbTriangles(thePoly); + + // Filter out degenerate triangles (those with void component bounding boxes) + // by only including triangles with valid boxes in the index mapping. + // This matches the behavior of the former Bnd_BoundSortBox-based approach, + // which never matched void boxes. + const occ::handle>& aCompBnd = + IntPatch_PolyhedronTool::ComponentsBounding(thePoly); + + myIndexMap.Clear(); + for (int anIdx = 1; anIdx <= aNbTriangles; ++anIdx) + { + if (!aCompBnd->Value(anIdx).IsVoid()) + { + myIndexMap.Append(anIdx); // Store 1-based original index + } + } + + // Mark as dirty to trigger BVH rebuild + MarkDirty(); +} + +//================================================================================================== + +void IntPatch_PolyhedronBVH::Clear() +{ + myPoly = nullptr; + myIndexMap.Clear(); + MarkDirty(); +} + +//================================================================================================== + +int IntPatch_PolyhedronBVH::Size() const +{ + if (myPoly == nullptr) + { + return 0; + } + return static_cast(myIndexMap.Size()); +} + +//================================================================================================== + +BVH_Box IntPatch_PolyhedronBVH::Box(const int theIndex) const +{ + BVH_Box aBox; + + if (myPoly == nullptr || theIndex < 0 || theIndex >= Size()) + { + return aBox; + } + + // Get original 1-based triangle index + const int anOrigIdx = myIndexMap.Value(theIndex); + + // Use pre-computed component bounding boxes from the polyhedron. + // These boxes include deflection enlargement, matching the behavior + // of the former Bnd_BoundSortBox-based approach. Degenerate triangles + // (with void boxes) are already excluded during Init(). + const occ::handle>& aCompBnd = + IntPatch_PolyhedronTool::ComponentsBounding(*myPoly); + const Bnd_Box& aBndBox = aCompBnd->Value(anOrigIdx); + + double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax; + aBndBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax); + + aBox.Add(BVH_Vec3d(aXmin, aYmin, aZmin)); + aBox.Add(BVH_Vec3d(aXmax, aYmax, aZmax)); + + return aBox; +} + +//================================================================================================== + +double IntPatch_PolyhedronBVH::Center(const int theIndex, const int theAxis) const +{ + if (myPoly == nullptr || theIndex < 0 || theIndex >= Size()) + { + return 0.0; + } + + // Get original 1-based triangle index + const int anOrigIdx = myIndexMap.Value(theIndex); + + // Get triangle vertex indices + int aP1, aP2, aP3; + IntPatch_PolyhedronTool::Triangle(*myPoly, anOrigIdx, aP1, aP2, aP3); + + // Get vertex coordinates + const gp_Pnt& aPnt1 = IntPatch_PolyhedronTool::Point(*myPoly, aP1); + const gp_Pnt& aPnt2 = IntPatch_PolyhedronTool::Point(*myPoly, aP2); + const gp_Pnt& aPnt3 = IntPatch_PolyhedronTool::Point(*myPoly, aP3); + + // Compute centroid coordinate along specified axis + switch (theAxis) + { + case 0: + return (aPnt1.X() + aPnt2.X() + aPnt3.X()) / 3.0; + case 1: + return (aPnt1.Y() + aPnt2.Y() + aPnt3.Y()) / 3.0; + case 2: + return (aPnt1.Z() + aPnt2.Z() + aPnt3.Z()) / 3.0; + default: + return 0.0; + } +} + +//================================================================================================== + +void IntPatch_PolyhedronBVH::Swap(const int theIndex1, const int theIndex2) +{ + if (theIndex1 == theIndex2) + { + return; + } + + // Swap indices in the mapping + const int aTmp = myIndexMap.Value(theIndex1); + myIndexMap.SetValue(theIndex1, myIndexMap.Value(theIndex2)); + myIndexMap.SetValue(theIndex2, aTmp); +} + +//================================================================================================== + +int IntPatch_PolyhedronBVH::OriginalIndex(const int theIndex) const +{ + if (theIndex < 0 || theIndex >= Size()) + { + return 0; + } + return myIndexMap.Value(theIndex); +} diff --git a/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_PolyhedronBVH.hxx b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_PolyhedronBVH.hxx new file mode 100644 index 0000000000..db25c59a28 --- /dev/null +++ b/src/ModelingAlgorithms/TKGeomAlgo/IntPatch/IntPatch_PolyhedronBVH.hxx @@ -0,0 +1,85 @@ +// Copyright (c) 2024 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef IntPatch_PolyhedronBVH_HeaderFile +#define IntPatch_PolyhedronBVH_HeaderFile + +#include +#include +#include + +class IntPatch_Polyhedron; + +//! Wraps IntPatch_Polyhedron as a BVH_PrimitiveSet for efficient spatial queries. +//! This class provides a BVH (Bounding Volume Hierarchy) representation of a polyhedron's +//! triangles, enabling O(log n) spatial queries instead of linear search. +//! +//! The class stores a reference to the polyhedron (no data copy) and maintains +//! an index mapping to track triangle reordering during BVH construction. +class IntPatch_PolyhedronBVH : public BVH_PrimitiveSet +{ +public: + //! Creates an empty BVH set. + Standard_EXPORT IntPatch_PolyhedronBVH(); + + //! Creates BVH set from the given polyhedron. + //! @param[in] thePoly the polyhedron to wrap (must remain valid during BVH lifetime) + Standard_EXPORT IntPatch_PolyhedronBVH(const IntPatch_Polyhedron& thePoly); + + //! Destructor. + Standard_EXPORT virtual ~IntPatch_PolyhedronBVH(); + + //! Initializes BVH set from the given polyhedron. + //! @param[in] thePoly the polyhedron to wrap (must remain valid during BVH lifetime) + Standard_EXPORT void Init(const IntPatch_Polyhedron& thePoly); + + //! Clears the BVH set. + Standard_EXPORT void Clear(); + +public: //! @name BVH_Set interface implementation + // Make inherited Box() method visible + using BVH_PrimitiveSet::Box; + + //! Returns the total number of triangles. + Standard_EXPORT virtual int Size() const override; + + //! Returns AABB of the triangle with the given index. + //! @param[in] theIndex 0-based triangle index (after BVH reordering) + Standard_EXPORT virtual BVH_Box Box(const int theIndex) const override; + + //! Returns centroid coordinate of the triangle along the given axis. + //! @param[in] theIndex 0-based triangle index (after BVH reordering) + //! @param[in] theAxis axis index (0=X, 1=Y, 2=Z) + Standard_EXPORT virtual double Center(const int theIndex, const int theAxis) const override; + + //! Swaps two triangles in the set (used during BVH construction). + //! @param[in] theIndex1 first triangle index + //! @param[in] theIndex2 second triangle index + Standard_EXPORT virtual void Swap(const int theIndex1, const int theIndex2) override; + +public: //! @name Additional methods + //! Returns the original (1-based) triangle index in the polyhedron + //! for the given 0-based index after BVH reordering. + //! @param[in] theIndex 0-based triangle index (after BVH reordering) + //! @return 1-based original triangle index in the polyhedron + Standard_EXPORT int OriginalIndex(const int theIndex) const; + + //! Returns true if the BVH set is initialized. + bool IsInitialized() const { return myPoly != nullptr; } + +private: + const IntPatch_Polyhedron* myPoly; //!< Reference to the wrapped polyhedron + NCollection_Vector myIndexMap; //!< Maps current indices to original 1-based indices +}; + +#endif // IntPatch_PolyhedronBVH_HeaderFile