From 1bb67d3844af0f972667f584d1d3112cfa2b83f0 Mon Sep 17 00:00:00 2001 From: emv Date: Thu, 18 Apr 2019 11:17:18 +0300 Subject: [PATCH] 0030595: Oriented Bounding Box seems not optimal for some shapes Add possibility of construction of the Optimal Oriented Bounding Box from set of points (the case of shape with triangulation). The interface of the BRepBndLib::AddOBB method is not changed, but the option now controls also the construction of the OBB from Set of points. The slightly modified DiTo algorithm will be used, checking all possible axes created by the extreme points. The performance of the construction of the Optimal OBB is lower but the quality is usually much higher (can't be worse by definition). Test cases for the issue. --- .../images/modeling_data_obb_125K.png | Bin 0 -> 18607 bytes .../images/modeling_data_opt_obb_125K.png | Bin 0 -> 17980 bytes .../images/modeling_data_pca_obb_125K.png | Bin 0 -> 14940 bytes .../modeling_data/modeling_data.md | 21 +- src/BRepBndLib/BRepBndLib.hxx | 5 +- src/BRepBndLib/BRepBndLib_1.cxx | 5 +- src/BRepTest/BRepTest_BasicCommands.cxx | 4 +- src/Bnd/Bnd_OBB.cxx | 555 ++++++++++++++---- src/Bnd/Bnd_OBB.hxx | 9 +- tests/bugs/modalg_7/bug30595_1 | 41 ++ tests/bugs/modalg_7/bug30595_2 | 18 + tests/bugs/modalg_7/bug30595_3 | 50 ++ 12 files changed, 579 insertions(+), 129 deletions(-) create mode 100644 dox/user_guides/modeling_data/images/modeling_data_obb_125K.png create mode 100644 dox/user_guides/modeling_data/images/modeling_data_opt_obb_125K.png create mode 100644 dox/user_guides/modeling_data/images/modeling_data_pca_obb_125K.png create mode 100644 tests/bugs/modalg_7/bug30595_1 create mode 100644 tests/bugs/modalg_7/bug30595_2 create mode 100644 tests/bugs/modalg_7/bug30595_3 diff --git a/dox/user_guides/modeling_data/images/modeling_data_obb_125K.png b/dox/user_guides/modeling_data/images/modeling_data_obb_125K.png new file mode 100644 index 0000000000000000000000000000000000000000..439458abfb21ddaa8c2d218e88cba3e2a59ff53c GIT binary patch literal 18607 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

LhCgF^Ee!sC2RndxoY5ga}xLZN}|LG2}etqQCL% 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 M+y08*d+N{s1q(s@TL1t6 literal 0 HcmV?d00001 diff --git a/dox/user_guides/modeling_data/images/modeling_data_pca_obb_125K.png b/dox/user_guides/modeling_data/images/modeling_data_pca_obb_125K.png new file mode 100644 index 0000000000000000000000000000000000000000..70f8d184e04738ed54134a9be7c94333ffe399b3 GIT binary patch literal 14940 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 E2TL`c5&!@I literal 0 HcmV?d00001 diff --git a/dox/user_guides/modeling_data/modeling_data.md b/dox/user_guides/modeling_data/modeling_data.md index 5c31123d48..912e0a1dbe 100644 --- a/dox/user_guides/modeling_data/modeling_data.md +++ b/dox/user_guides/modeling_data/modeling_data.md @@ -1341,7 +1341,26 @@ Further, let us consider the triangle \f$ T_{0}\left \langle p_{0}, p_{1}, p_{2} 10. Compute the center of OBB and its half dimensions.
11. Create OBB using the center, axes and half dimensions.
-This algorithm is implemented in the *Bnd_OBB::ReBuild(...)* method. +@subsubsection occt_modat_6_1_1_opt Creation of Optimal OBB from set of points + +For creation of the optimal OBB from set of points the same algorithm as described above is used but with some simplifications in logic and increased computation time. +For the optimal OBB it is necessary to check all possible axes which can be created by the extremal points. And since the extremal points are only valid for the initial axes it is necessary to project the whole set of points on each axis. +This approach usually provides much tighter OBB but the performance is lower. The complexity of the algorithm is still linear and with use of BVH for the set of points it is O(N + C*log(N)). + +Here is the example of optimal and not optimal OBB for the model using the set of 125K nodes: + + + + + + +
@figure{/user_guides/modeling_data/images/modeling_data_obb_125K.png,"Not optimal OBB by DiTo-14",160}@figure{/user_guides/modeling_data/images/modeling_data_opt_obb_125K.png,"Optimal OBB by DiTo-14",160}@figure{/user_guides/modeling_data/images/modeling_data_pca_obb_125K.png,"Not optimal OBB by PCA",160}
+ +Computation of the not optimal OBB in this case took 0.007 sec, optimal - 0.1 sec, which is about 14 times slower. Such performance is comparable to creation of the OBB for this shape by PCA approach (see below) which takes about 0.17 sec. + +The computation of optimal OBB is controlled by the same *theIsOptimal* flag in the BRepBndLib::AddOBB method as for PCA algorithm. + +These algorithms are implemented in the *Bnd_OBB::ReBuild(...)* method. @subsubsection occt_modat_6_1_2 Creation of OBB based on Axes of inertia diff --git a/src/BRepBndLib/BRepBndLib.hxx b/src/BRepBndLib/BRepBndLib.hxx index 6f343874ab..c643cca2fd 100644 --- a/src/BRepBndLib/BRepBndLib.hxx +++ b/src/BRepBndLib/BRepBndLib.hxx @@ -93,9 +93,8 @@ public: //! be ignored at all. //! If theIsShapeToleranceUsed == TRUE then resulting box will be //! extended on the tolerance of the shape. - //! theIsOptimal flag defines the algorithm for construction of initial - //! Bnd_Box for the second method (if theIsOptimal == TRUE then - //! this box will be created by AddOptimal(...) method). + //! theIsOptimal flag defines whether to look for the more tight + //! OBB for the cost of performance or not. Standard_EXPORT static void AddOBB(const TopoDS_Shape& theS, Bnd_OBB& theOBB, diff --git a/src/BRepBndLib/BRepBndLib_1.cxx b/src/BRepBndLib/BRepBndLib_1.cxx index c70c2df236..1dbf95479c 100644 --- a/src/BRepBndLib/BRepBndLib_1.cxx +++ b/src/BRepBndLib/BRepBndLib_1.cxx @@ -293,6 +293,7 @@ static Standard_Integer IsWCS(const gp_Dir& theDir) //======================================================================= static Standard_Boolean CheckPoints(const TopoDS_Shape& theS, const Standard_Boolean theIsTriangulationUsed, + const Standard_Boolean theIsOptimal, const Standard_Boolean theIsShapeToleranceUsed, Bnd_OBB& theOBB) { @@ -329,7 +330,7 @@ static Standard_Boolean CheckPoints(const TopoDS_Shape& theS, } #endif - theOBB.ReBuild(anArrPnts, aPtrArrTol); + theOBB.ReBuild(anArrPnts, aPtrArrTol, theIsOptimal); return (!theOBB.IsVoid()); } @@ -498,7 +499,7 @@ void BRepBndLib::AddOBB(const TopoDS_Shape& theS, const Standard_Boolean theIsOptimal, const Standard_Boolean theIsShapeToleranceUsed) { - if(CheckPoints(theS, theIsTriangulationUsed, theIsShapeToleranceUsed, theOBB)) + if (CheckPoints(theS, theIsTriangulationUsed, theIsOptimal, theIsShapeToleranceUsed, theOBB)) return; ComputePCA(theS, theOBB, theIsTriangulationUsed, theIsOptimal, theIsShapeToleranceUsed); diff --git a/src/BRepTest/BRepTest_BasicCommands.cxx b/src/BRepTest/BRepTest_BasicCommands.cxx index 7e7b36acff..c128542ef2 100644 --- a/src/BRepTest/BRepTest_BasicCommands.cxx +++ b/src/BRepTest/BRepTest_BasicCommands.cxx @@ -1486,7 +1486,9 @@ void BRepTest::BasicCommands(Draw_Interpretor& theCommands) "\n\t\t: -noTriangulation Force use of exact geometry for calculation" "\n\t\t: even if triangulation is present." "\n\t\t: -optimal Force calculation of optimal (more tight) AABB." - "\n\t\t: In case of OBB, applies to initial AABB used in OBB calculation." + "\n\t\t: In case of OBB:" + "\n\t\t: - for PCA approach applies to initial AABB used in OBB calculation" + "\n\t\t: - for DiTo approach modifies the DiTo algorithm to check more axes." "\n\t\t: -extToler Include tolerance of the shape in the resulting box." "\n\t\t:" "\n\t\t: Output options:" diff --git a/src/Bnd/Bnd_OBB.cxx b/src/Bnd/Bnd_OBB.cxx index e9b7f36ea8..0120cfe1de 100644 --- a/src/Bnd/Bnd_OBB.cxx +++ b/src/Bnd/Bnd_OBB.cxx @@ -14,11 +14,177 @@ #include -#include +#include +#include + +#include +#include + +#include + #include #include #include +//! Auxiliary class to select from the points stored in +//! BVH tree the two points giving the extreme projection +//! parameters on the axis +class OBB_ExtremePointsSelector : + public BVH_Traverse , Bnd_Range> +{ +public: + + //! Constructor + OBB_ExtremePointsSelector() : + BVH_Traverse , Bnd_Range>(), + myPrmMin (RealLast()), + myPrmMax (RealFirst()) + {} + +public: //! @name Set axis for projection + + //! Sets the axis + void SetAxis (const gp_XYZ& theAxis) { myAxis = theAxis; } + +public: //! @name Clears the points from previous runs + + //! Clear + void Clear() + { + myPrmMin = RealLast(); + myPrmMax = RealFirst(); + } + +public: //! @name Getting the results + + //! Returns the minimal projection parameter + Standard_Real MinPrm() const { return myPrmMin; } + + //! Returns the maximal projection parameter + Standard_Real MaxPrm() const { return myPrmMax; } + + //! Returns the minimal projection point + const gp_XYZ& MinPnt() const { return myPntMin; } + + //! Returns the maximal projection point + const gp_XYZ& MaxPnt() const { return myPntMax; } + +public: //! @name Definition of rejection/acceptance rules + + //! Defines the rules for node rejection + virtual Standard_Boolean RejectNode (const BVH_Vec3d& theCMin, + const BVH_Vec3d& theCMax, + Bnd_Range& theMetric) const Standard_OVERRIDE + { + if (myPrmMin > myPrmMax) + // No parameters computed yet + return Standard_False; + + Standard_Real aPrmMin = myPrmMin, aPrmMax = myPrmMax; + Standard_Boolean isToReject = Standard_True; + + // Check if the current node is between already found parameters + for (Standard_Integer i = 0; i < 2; ++i) + { + Standard_Real x = !i ? theCMin.x() : theCMax.x(); + for (Standard_Integer j = 0; j < 2; ++j) + { + Standard_Real y = !j ? theCMin.y() : theCMax.y(); + for (Standard_Integer k = 0; k < 2; ++k) + { + Standard_Real z = !k ? theCMin.z() : theCMax.z(); + + Standard_Real aPrm = myAxis.Dot (gp_XYZ (x, y, z)); + if (aPrm < aPrmMin) + { + aPrmMin = aPrm; + isToReject = Standard_False; + } + else if (aPrm > aPrmMax) + { + aPrmMax = aPrm; + isToReject = Standard_False; + } + } + } + } + + theMetric = Bnd_Range (aPrmMin, aPrmMax); + + return isToReject; + } + + //! Rules for node rejection by the metric + virtual Standard_Boolean RejectMetric (const Bnd_Range& theMetric) const Standard_OVERRIDE + { + if (myPrmMin > myPrmMax) + // no parameters computed + return Standard_False; + + Standard_Real aMin, aMax; + if (!theMetric.GetBounds (aMin, aMax)) + // void metric + return Standard_False; + + // Check if the box of the branch is inside of the already computed parameters + return aMin > myPrmMin && aMax < myPrmMax; + } + + //! Defines the rules for leaf acceptance + virtual Standard_Boolean Accept (const Standard_Integer theIndex, + const Bnd_Range&) Standard_OVERRIDE + { + const gp_XYZ& theLeaf = myBVHSet->Element (theIndex); + Standard_Real aPrm = myAxis.Dot (theLeaf); + if (aPrm < myPrmMin) + { + myPrmMin = aPrm; + myPntMin = theLeaf; + } + if (aPrm > myPrmMax) + { + myPrmMax = aPrm; + myPntMax = theLeaf; + } + return Standard_True; + } + +public: //! @name Choosing the best branch + + //! Returns true if the metric of the left branch is better than the metric of the right + virtual Standard_Boolean IsMetricBetter (const Bnd_Range& theLeft, + const Bnd_Range& theRight) const Standard_OVERRIDE + { + if (myPrmMin > myPrmMax) + // no parameters computed + return Standard_True; + + Standard_Real aMin[2], aMax[2]; + if (!theLeft.GetBounds (aMin[0], aMax[0]) || + !theRight.GetBounds (aMin[1], aMax[1])) + // void metrics + return Standard_True; + + // Choose branch with larger extension over computed parameters + Standard_Real anExt[2] = {0.0, 0.0}; + for (int i = 0; i < 2; ++i) + { + if (aMin[i] < myPrmMin) anExt[i] += myPrmMin - aMin[i]; + if (aMax[i] > myPrmMax) anExt[i] += aMax[i] - myPrmMax; + } + return anExt[0] > anExt[1]; + } + +protected: //! @name Fields + + gp_XYZ myAxis; //!< Axis to project the points to + Standard_Real myPrmMin; //!< Minimal projection parameter + Standard_Real myPrmMax; //!< Maximal projection parameter + gp_XYZ myPntMin; //!< Minimal projection point + gp_XYZ myPntMax; //!< Maximal projection point +}; + +//! Tool for OBB construction class OBBTool { public: @@ -31,7 +197,8 @@ public: //! must be available during all time of OBB creation //! (i.e. while the object of OBBTool exists). OBBTool(const TColgp_Array1OfPnt& theL, - const TColStd_Array1OfReal *theLT = 0); + const TColStd_Array1OfReal *theLT = 0, + Standard_Boolean theIsOptimal = Standard_False); //! DiTO algorithm for OBB construction //! (http://www.idt.mdh.se/~tla/publ/FastOBBs.pdf) @@ -41,6 +208,10 @@ public: void BuildBox(Bnd_OBB& theBox); protected: + + // Computes the extreme points on the set of Initial axes + void ComputeExtremePoints (); + //! Works with the triangle set by the points in myTriIdx. //! If theIsBuiltTrg == TRUE, new set of triangles will be //! recomputed. @@ -71,6 +242,106 @@ protected: OBBTool& operator=(const OBBTool&); private: + //! Params structure stores the two values meaning + //! min and max parameters on the axis + struct Params + { + Params() : + _ParamMin(RealLast()), _ParamMax(RealFirst()) + {} + + Params(Standard_Real theMin, Standard_Real theMax) + : _ParamMin(theMin), _ParamMax(theMax) + {} + + Standard_Real _ParamMin; + Standard_Real _ParamMax; + }; + + //! Computes the Minimal and maximal parameters on the vector + //! connecting the points myLExtremalPoints[theId1] and myLExtremalPoints[theId2] + void ComputeParams (const Standard_Integer theId1, + const Standard_Integer theId2, + Standard_Real &theMin, + Standard_Real &theMax) + { + theMin = myParams[theId1][theId2]._ParamMin; + theMax = myParams[theId1][theId2]._ParamMax; + + if (theMin > theMax) + { + FindMinMax ((myLExtremalPoints[theId1] - myLExtremalPoints[theId2]).Normalized(), theMin, theMax); + myParams[theId1][theId2]._ParamMin = myParams[theId2][theId1]._ParamMin = theMin; + myParams[theId1][theId2]._ParamMax = myParams[theId2][theId1]._ParamMax = theMax; + } + } + + //! Looks for the min-max parameters on the axis. + //! For optimal case projects all the points on the axis, + //! for not optimal - only the set of extreme points. + void FindMinMax (const gp_XYZ& theAxis, + Standard_Real &theMin, + Standard_Real &theMax) + { + theMin = RealLast(), theMax = RealFirst(); + + if (myOptimal) + Project (theAxis, theMin, theMax); + else + { + for (Standard_Integer i = 0; i < myNbExtremalPoints; ++i) + { + Standard_Real aPrm = theAxis.Dot (myLExtremalPoints[i]); + if (aPrm < theMin) theMin = aPrm; + if (aPrm > theMax) theMax = aPrm; + } + } + } + + //! Projects the set of points on the axis + void Project (const gp_XYZ& theAxis, + Standard_Real& theMin, Standard_Real& theMax, + gp_XYZ* thePntMin = 0, gp_XYZ* thePntMax = 0) + { + theMin = RealLast(), theMax = RealFirst(); + + if (myOptimal) + { + // Project BVH + OBB_ExtremePointsSelector anExtremePointsSelector; + anExtremePointsSelector.SetBVHSet (myPointBoxSet.get()); + anExtremePointsSelector.SetAxis (theAxis); + anExtremePointsSelector.Select(); + theMin = anExtremePointsSelector.MinPrm(); + theMax = anExtremePointsSelector.MaxPrm(); + if (thePntMin) *thePntMin = anExtremePointsSelector.MinPnt(); + if (thePntMax) *thePntMax = anExtremePointsSelector.MaxPnt(); + } + else + { + // Project all points + for (Standard_Integer iP = myPntsList.Lower(); iP <= myPntsList.Upper(); ++iP) + { + const gp_XYZ& aPoint = myPntsList(iP).XYZ(); + const Standard_Real aPrm = theAxis.Dot (aPoint); + if (aPrm < theMin) + { + theMin = aPrm; + if (thePntMin) + *thePntMin = aPoint; + } + if (aPrm > theMax) + { + theMax = aPrm; + if (thePntMax) + *thePntMax = aPoint; + } + } + } + } + +private: + //! Number of the initial axes. static const Standard_Integer myNbInitAxes = 7; @@ -96,6 +367,16 @@ private: //! The surface area of the OBB Standard_Real myQualityCriterion; + + //! Defines if the OBB should be computed more tight. + //! Takes more time, but the volume is less. + Standard_Boolean myOptimal; + + //! Point box set organized with BVH + opencascade::handle> myPointBoxSet; + + //! Stored min/max parameters for the axes between extremal points + Params myParams[myNbExtremalPoints][myNbExtremalPoints]; }; //======================================================================= @@ -110,7 +391,7 @@ static inline void SetMinMax(Standard_Real* const thePrmArr, { thePrmArr[0] = theNewParam; } - else if(theNewParam > thePrmArr[1]) + if(theNewParam > thePrmArr[1]) { thePrmArr[1] = theNewParam; } @@ -122,76 +403,102 @@ static inline void SetMinMax(Standard_Real* const thePrmArr, //======================================================================= OBBTool:: OBBTool(const TColgp_Array1OfPnt& theL, - const TColStd_Array1OfReal *theLT) :myPntsList(theL), - myListOfTolers(theLT), - myQualityCriterion(RealLast()) + const TColStd_Array1OfReal *theLT, + const Standard_Boolean theIsOptimal) : myPntsList(theL), + myListOfTolers(theLT), + myQualityCriterion(RealLast()), + myOptimal (theIsOptimal) { + if (myOptimal) + { + // Use linear builder for BVH construction with 30 elements in the leaf + opencascade::handle > aLBuilder = + new BVH_LinearBuilder (30); + myPointBoxSet = new BVH_BoxSet (aLBuilder); + myPointBoxSet->SetSize(myPntsList.Length()); + + // Add the points into Set + for (Standard_Integer iP = 0; iP < theL.Length(); ++iP) + { + const gp_Pnt& aP = theL (iP); + Standard_Real aTol = theLT ? theLT->Value(iP) : Precision::Confusion(); + BVH_Box aBox (BVH_Vec3d (aP.X() - aTol, aP.Y() - aTol, aP.Z() - aTol), + BVH_Vec3d (aP.X() + aTol, aP.Y() + aTol, aP.Z() + aTol)); + myPointBoxSet->Add (aP.XYZ(), aBox); + } + + myPointBoxSet->Build(); + } + + ComputeExtremePoints(); +} + +//======================================================================= +// Function : ComputeExtremePoints +// purpose : +//======================================================================= +void OBBTool::ComputeExtremePoints() +{ + // Six initial axes show great quality on the Optimal OBB, plus + // the performance is better (due to the less number of operations). + // But they show worse quality for the not optimal approach. + //const Standard_Real a = (sqrt(5) - 1) / 2.; + //const gp_XYZ anInitialAxes6[myNbInitAxes] = { gp_XYZ (0, 1, a), + // gp_XYZ (0, 1, -a), + // gp_XYZ (1, a, 0), + // gp_XYZ (1, -a, 0), + // gp_XYZ (a, 0, 1), + // gp_XYZ (a, 0, -1) }; const Standard_Real aSqrt3 = Sqrt(3); - // Origin of all initial axis is (0,0,0). - // All axes must be normalized. - const gp_XYZ anInitialAxesArray[myNbInitAxes] = {gp_XYZ(1.0, 0.0, 0.0), - gp_XYZ(0.0, 1.0, 0.0), - gp_XYZ(0.0, 0.0, 1.0), - gp_XYZ(1.0, 1.0, 1.0) / aSqrt3, - gp_XYZ(1.0, 1.0, -1.0) / aSqrt3, - gp_XYZ(1.0, -1.0, 1.0) / aSqrt3, - gp_XYZ(1.0, -1.0, -1.0) / aSqrt3}; + const gp_XYZ anInitialAxes7[myNbInitAxes] = { gp_XYZ (1.0, 0.0, 0.0), + gp_XYZ (0.0, 1.0, 0.0), + gp_XYZ (0.0, 0.0, 1.0), + gp_XYZ (1.0, 1.0, 1.0) / aSqrt3, + gp_XYZ (1.0, 1.0, -1.0) / aSqrt3, + gp_XYZ (1.0, -1.0, 1.0) / aSqrt3, + gp_XYZ (1.0, -1.0, -1.0) / aSqrt3 }; - // Minimal and maximal point on every axis - const Standard_Integer aNbPoints = 2 * myNbInitAxes; + // Set of initial axes + const gp_XYZ *anInitialAxesArray = anInitialAxes7; - for(Standard_Integer i = 0; i < 5; i++) - { - myTriIdx[i] = INT_MAX; - } - // Min and Max parameter - Standard_Real aParams[aNbPoints]; - for(Standard_Integer i = 0; i < aNbPoints; i += 2) - { - aParams[i] = RealLast(); - aParams[i + 1] = RealFirst(); - } - + Standard_Real aParams[myNbExtremalPoints]; // Look for the extremal points (myLExtremalPoints) - for(Standard_Integer i = myPntsList.Lower() ; i <= myPntsList.Upper(); i++) + for (Standard_Integer anAxeInd = 0, aPrmInd = -1; anAxeInd < myNbInitAxes; ++anAxeInd) { - const gp_XYZ &aCurrPoint = myPntsList(i).XYZ(); - for(Standard_Integer anAxeInd = 0, aPrmInd = 0; anAxeInd < myNbInitAxes; anAxeInd++, aPrmInd++) - { - const Standard_Real aParam = aCurrPoint.Dot(anInitialAxesArray[anAxeInd]); - if(aParam < aParams[aPrmInd]) - { - myLExtremalPoints[aPrmInd] = aCurrPoint; - aParams[aPrmInd] = aParam; - } - aPrmInd++; - - if(aParam > aParams[aPrmInd]) - { - myLExtremalPoints[aPrmInd] = aCurrPoint; - aParams[aPrmInd] = aParam; - } - } + Standard_Integer aMinInd = ++aPrmInd, aMaxInd = ++aPrmInd; + aParams[aMinInd] = RealLast(); + aParams[aMaxInd] = -RealLast(); + Project (anInitialAxesArray[anAxeInd], + aParams[aMinInd], aParams[aMaxInd], + &myLExtremalPoints[aMinInd], &myLExtremalPoints[aMaxInd]); } - // Compute myTriIdx[0] and myTriIdx[1]. - - Standard_Real aMaxSqDist = -1.0; - for(Standard_Integer aPrmInd = 0; aPrmInd < aNbPoints; aPrmInd += 2) + // For not optimal box it is necessary to compute the max axis + // created by the maximally distant extreme points + if (!myOptimal) { - const gp_Pnt &aP1 = myLExtremalPoints[aPrmInd], - &aP2 = myLExtremalPoints[aPrmInd + 1]; - const Standard_Real aSqDist = aP1.SquareDistance(aP2); - if(aSqDist > aMaxSqDist) - { - aMaxSqDist = aSqDist; - myTriIdx[0] = aPrmInd; - myTriIdx[1] = aPrmInd + 1; - } - } + for(Standard_Integer i = 0; i < 5; i++) + myTriIdx[i] = INT_MAX; - FillToTriangle3(); + // Compute myTriIdx[0] and myTriIdx[1]. + Standard_Real aMaxSqDist = -1.0; + for (Standard_Integer aPrmInd = 0; aPrmInd < myNbExtremalPoints; aPrmInd += 2) + { + const gp_Pnt &aP1 = myLExtremalPoints[aPrmInd], + &aP2 = myLExtremalPoints[aPrmInd + 1]; + const Standard_Real aSqDist = aP1.SquareDistance(aP2); + if (aSqDist > aMaxSqDist) + { + aMaxSqDist = aSqDist; + myTriIdx[0] = aPrmInd; + myTriIdx[1] = aPrmInd + 1; + } + } + + // Compute the maximal axis orthogonal to the found one + FillToTriangle3(); + } } //======================================================================= @@ -233,6 +540,7 @@ void OBBTool::FillToTriangle5(const gp_XYZ& theNormal, const gp_XYZ& theBarryCenter) { Standard_Real aParams[2] = {0.0, 0.0}; + Standard_Integer id3 = -1, id4 = -1; for(Standard_Integer aPtIdx = 0; aPtIdx < myNbExtremalPoints; aPtIdx++) { @@ -242,28 +550,24 @@ void OBBTool::FillToTriangle5(const gp_XYZ& theNormal, const gp_XYZ &aCurrPoint = myLExtremalPoints[aPtIdx]; const Standard_Real aParam = theNormal.Dot(aCurrPoint - theBarryCenter); - if(aParam < aParams[0]) + if (aParam < aParams[0]) { - myTriIdx[3] = aPtIdx; + id3 = aPtIdx; aParams[0] = aParam; } - else if(aParam > aParams[1]) + else if (aParam > aParams[1]) { - myTriIdx[4] = aPtIdx; + id4 = aPtIdx; aParams[1] = aParam; } } // The points must be in the different sides of the triangle plane. - if(aParams[0] > -Precision::Confusion()) - { - myTriIdx[3] = INT_MAX; - } + if (id3 >= 0 && aParams[0] < -Precision::Confusion()) + myTriIdx[3] = id3; - if(aParams[1] < Precision::Confusion()) - { - myTriIdx[4] = INT_MAX; - } + if (id4 >= 0 && aParams[1] > Precision::Confusion()) + myTriIdx[4] = id4; } //======================================================================= @@ -279,71 +583,60 @@ void OBBTool::ProcessTriangle(const Standard_Integer theIdx1, { const Standard_Integer aNbAxes = 3; - //Some vertex of the triangle - const gp_XYZ aP0 = myLExtremalPoints[theIdx1]; - // All axes must be normalized in order to provide correct area computation // (see ComputeQuality(...) method). - gp_XYZ aYAxis[aNbAxes] = {(myLExtremalPoints[theIdx2] - myLExtremalPoints[theIdx1]), - (myLExtremalPoints[theIdx3] - myLExtremalPoints[theIdx2]), - (myLExtremalPoints[theIdx1] - myLExtremalPoints[theIdx3])}; + int ID1[3] = { theIdx2, theIdx3, theIdx1 }, + ID2[3] = { theIdx1, theIdx2, theIdx3 }; + gp_XYZ aYAxis[aNbAxes] = {(myLExtremalPoints[ID1[0]] - myLExtremalPoints[ID2[0]]), + (myLExtremalPoints[ID1[1]] - myLExtremalPoints[ID2[1]]), + (myLExtremalPoints[ID1[2]] - myLExtremalPoints[ID2[2]])}; // Normal to the triangle plane gp_XYZ aZAxis = aYAxis[0].Crossed(aYAxis[1]); Standard_Real aSqMod = aZAxis.SquareModulus(); - if(aSqMod < Precision::SquareConfusion()) + if (aSqMod < Precision::SquareConfusion()) return; aZAxis /= Sqrt(aSqMod); gp_XYZ aXAxis[aNbAxes]; - for(Standard_Integer i = 0; i < aNbAxes; i++) - { + for (Standard_Integer i = 0; i < aNbAxes; i++) aXAxis[i] = aYAxis[i].Crossed(aZAxis).Normalized(); - aYAxis[i].Normalize(); - } - if(theIsBuiltTrg) - FillToTriangle5(aZAxis, aP0); + if (theIsBuiltTrg) + FillToTriangle5 (aZAxis, myLExtremalPoints[theIdx1]); // Min and Max parameter const Standard_Integer aNbPoints = 2 * aNbAxes; - + + // Compute Min/Max params for ZAxis + Standard_Real aParams[aNbPoints]; + FindMinMax (aZAxis, aParams[4], aParams[5]); // Compute params on ZAxis once + Standard_Integer aMinIdx = -1; for(Standard_Integer anAxeInd = 0; anAxeInd < aNbAxes; anAxeInd++) { - const gp_XYZ &aAX = aXAxis[anAxeInd], - &aAY = aYAxis[anAxeInd]; - - Standard_Real aParams[aNbPoints] = {0.0, 0.0, 0.0, - 0.0, 0.0, 0.0}; - - for(Standard_Integer aPtIdx = 0; aPtIdx < myNbExtremalPoints; aPtIdx++) - { - if(aPtIdx == theIdx1) - continue; - - const gp_XYZ aCurrPoint = myLExtremalPoints[aPtIdx] - aP0; - SetMinMax(&aParams[0], aAX.Dot(aCurrPoint)); - SetMinMax(&aParams[2], aAY.Dot(aCurrPoint)); - SetMinMax(&aParams[4], aZAxis.Dot(aCurrPoint)); - } + const gp_XYZ &aAX = aXAxis[anAxeInd]; + // Compute params on XAxis + FindMinMax (aAX, aParams[0], aParams[1]); + // Compute params on YAxis checking for stored values + ComputeParams (ID1[anAxeInd], ID2[anAxeInd], aParams[2], aParams[3]); const Standard_Real anArea = ComputeQuality(aParams); - if(anArea < myQualityCriterion) + if (anArea < myQualityCriterion) { myQualityCriterion = anArea; aMinIdx = anAxeInd; } } - if(aMinIdx < 0) + if (aMinIdx < 0) return; myAxes[0] = aXAxis[aMinIdx]; - myAxes[1] = aYAxis[aMinIdx]; + myAxes[1] = aYAxis[aMinIdx].Normalized(); myAxes[2] = aZAxis; } //======================================================================= @@ -352,20 +645,41 @@ void OBBTool::ProcessTriangle(const Standard_Integer theIdx1, //======================================================================= void OBBTool::ProcessDiTetrahedron() { - ProcessTriangle(myTriIdx[0], myTriIdx[1], myTriIdx[2], Standard_True); - - if(myTriIdx[3] <= myNbExtremalPoints) + // To compute the optimal OBB it is necessary to check all possible + // axes created by the extremal points. It is also necessary to project + // all the points on the axis, as for each different axis there will be + // different extremal points. + if (myOptimal) { - ProcessTriangle(myTriIdx[0], myTriIdx[1], myTriIdx[3], Standard_False); - ProcessTriangle(myTriIdx[1], myTriIdx[2], myTriIdx[3], Standard_False); - ProcessTriangle(myTriIdx[0], myTriIdx[2], myTriIdx[3], Standard_False); + for (Standard_Integer i = 0; i < myNbExtremalPoints - 2; i++) + { + for (Standard_Integer j = i + 1; j < myNbExtremalPoints - 1; j++) + { + for (Standard_Integer k = j + 1; k < myNbExtremalPoints; k++) + { + ProcessTriangle (i, j, k, Standard_False); + } + } + } } - - if(myTriIdx[4] <= myNbExtremalPoints) + else { - ProcessTriangle(myTriIdx[0], myTriIdx[1], myTriIdx[4], Standard_False); - ProcessTriangle(myTriIdx[1], myTriIdx[2], myTriIdx[4], Standard_False); - ProcessTriangle(myTriIdx[0], myTriIdx[2], myTriIdx[4], Standard_False); + // Use the standard DiTo approach + ProcessTriangle(myTriIdx[0], myTriIdx[1], myTriIdx[2], Standard_True); + + if (myTriIdx[3] <= myNbExtremalPoints) + { + ProcessTriangle(myTriIdx[0], myTriIdx[1], myTriIdx[3], Standard_False); + ProcessTriangle(myTriIdx[1], myTriIdx[2], myTriIdx[3], Standard_False); + ProcessTriangle(myTriIdx[0], myTriIdx[2], myTriIdx[3], Standard_False); + } + + if (myTriIdx[4] <= myNbExtremalPoints) + { + ProcessTriangle(myTriIdx[0], myTriIdx[1], myTriIdx[4], Standard_False); + ProcessTriangle(myTriIdx[1], myTriIdx[2], myTriIdx[4], Standard_False); + ProcessTriangle(myTriIdx[0], myTriIdx[2], myTriIdx[4], Standard_False); + } } } @@ -452,7 +766,8 @@ void OBBTool::BuildBox(Bnd_OBB& theBox) // purpose : http://www.idt.mdh.se/~tla/publ/ // ======================================================================= void Bnd_OBB::ReBuild(const TColgp_Array1OfPnt& theListOfPoints, - const TColStd_Array1OfReal *theListOfTolerances) + const TColStd_Array1OfReal *theListOfTolerances, + const Standard_Boolean theIsOptimal) { switch(theListOfPoints.Length()) { @@ -504,7 +819,7 @@ void Bnd_OBB::ReBuild(const TColgp_Array1OfPnt& theListOfPoints, break; } - OBBTool aTool(theListOfPoints, theListOfTolerances); + OBBTool aTool(theListOfPoints, theListOfTolerances, theIsOptimal); aTool.ProcessDiTetrahedron(); aTool.BuildBox(*this); } diff --git a/src/Bnd/Bnd_OBB.hxx b/src/Bnd/Bnd_OBB.hxx index e561d2b299..ffed615cca 100644 --- a/src/Bnd/Bnd_OBB.hxx +++ b/src/Bnd/Bnd_OBB.hxx @@ -95,12 +95,17 @@ public: myCenter.SetCoord(0.5*(aX2 + aX1), 0.5*(aY2 + aY1), 0.5*(aZ2 + aZ1)); } - //! Created new OBB covering every point in theListOfPoints. + //! Creates new OBB covering every point in theListOfPoints. //! Tolerance of every such point is set by *theListOfTolerances array. //! If this array is not void (not null-pointer) then the resulted Bnd_OBB //! will be enlarged using tolerances of points lying on the box surface. + //! flag defines the mode in which the OBB will be built. + //! Constructing Optimal box takes more time, but the resulting box is usually + //! more tight. In case of construction of Optimal OBB more possible + //! axes are checked. Standard_EXPORT void ReBuild(const TColgp_Array1OfPnt& theListOfPoints, - const TColStd_Array1OfReal *theListOfTolerances = 0); + const TColStd_Array1OfReal *theListOfTolerances = 0, + const Standard_Boolean theIsOptimal = Standard_False); //! Sets the center of OBB void SetCenter(const gp_Pnt& theCenter) diff --git a/tests/bugs/modalg_7/bug30595_1 b/tests/bugs/modalg_7/bug30595_1 new file mode 100644 index 0000000000..61d7803c9e --- /dev/null +++ b/tests/bugs/modalg_7/bug30595_1 @@ -0,0 +1,41 @@ +puts "===============================================================" +puts "0030595: Oriented Bounding Box seems not optimal for some shapes" +puts "===============================================================" +puts "" + +# average volumes of OBBs on different sets +set OBB_Vol_Exp 615000 +# set relative error to 1% +set eps 0.01 + +restore [locate_data_file bug30595_UC4_P_2K.brep] s1 +# build OBB +dchrono s1_time start +bounding s1 -obb -shape bs1 -optimal +dchrono s1_time stop counter s1_OBB +# check volume +checkprops bs1 -v $OBB_Vol_Exp -deps $eps + +restore [locate_data_file bug30595_UC4_P_13K.brep] s2 +# build OBB +dchrono s2_time start +bounding s2 -obb -shape bs2 -optimal +dchrono s2_time stop counter s2_OBB +# check volume +checkprops bs2 -v $OBB_Vol_Exp -deps $eps + +restore [locate_data_file bug30595_UC4_P_125K.brep] s3 +# build OBB +dchrono s3_time start +bounding s3 -obb -shape bs3 -optimal +dchrono s3_time stop counter s3_OBB +# check volume +checkprops bs3 -v $OBB_Vol_Exp -deps $eps + +smallview +X+Z +donly s3 bs3; fit +checkview -screenshot -2d -path ${imagedir}/${test_image}_xz.png + +smallview +X+Y +donly s3 bs3; fit +checkview -screenshot -2d -path ${imagedir}/${test_image}_xy.png diff --git a/tests/bugs/modalg_7/bug30595_2 b/tests/bugs/modalg_7/bug30595_2 new file mode 100644 index 0000000000..a3ae8a39c1 --- /dev/null +++ b/tests/bugs/modalg_7/bug30595_2 @@ -0,0 +1,18 @@ +puts "===============================================================" +puts "0030595: Oriented Bounding Box seems not optimal for some shapes" +puts "===============================================================" +puts "" + +pload XSDRAW + +stepread [locate_data_file bug30595_UC1.stp] s * +incmesh s_1 0.1 +dchrono stime start +bounding s_1 -obb -shape bs -optimal +dchrono stime stop counter s_OBB + +checkprops bs -v 1.32656e+07 -deps 1.e-5 + +smallview +donly s_1 bs; fit +checkview -screenshot -2d -path ${imagedir}/${test_image}.png diff --git a/tests/bugs/modalg_7/bug30595_3 b/tests/bugs/modalg_7/bug30595_3 new file mode 100644 index 0000000000..44095d1698 --- /dev/null +++ b/tests/bugs/modalg_7/bug30595_3 @@ -0,0 +1,50 @@ +puts "===============================================================" +puts "0030595: Oriented Bounding Box seems not optimal for some shapes" +puts "===============================================================" +puts "" + +# test is the copy of the test case bug29311_2 +# but computing the optimal OBB comparing to tight AABB +# with 1.e-6% precision + +set NbIters 101 +set step [expr 360.0/($NbIters-1) ] + +restore [locate_data_file bug29237_no_overlap.rhs.brep] a + +# Create AABB for a and put it into "r1" variable +# Draw[]> bounding a -shape r1 +# The volume of one AABB is +# Draw[]> vprops r1 1.0e-12 -full +# 32736000.184203226 +set Vexp 32736000.184203226 + +set VMax 0 +set MaxIteration 0 + +for {set i 1} { $i <= $NbIters} { incr i } { + bounding a -obb -shape rr$i -optimal + + regexp {Mass +: +([-0-9.+eE]+)} [vprops rr$i 1.0e-12 -full] full Vreal + + if { $Vreal > $VMax } { + set VMax $Vreal + set MaxIteration $i + copy a amax + } + + if { $i != $NbIters } { trotate a 283 162 317 2 7 9 $step } +} + +set aDeltaMax [ expr 100.0*abs($VMax/$Vexp - 1.0) ] + +puts "Delta of computation not greater than $aDeltaMax %. Maximal delta is achieved in $MaxIteration iteration. See \"amax\" shape." + +if { $aDeltaMax > 1.e-6 } { + puts "Error: The obtained OBB(s) is not precise." +} + +smallview +donly amax rr${MaxIteration} +fit +checkview -screenshot -2d -path ${imagedir}/${test_image}.png