mirror of
https://github.com/Open-Cascade-SAS/OCCT.git
synced 2026-09-06 04:37:55 +08:00
Modeling - Optimize exact face classification fallback (#1400)
- Add a thread-safe lazy grid to CSLib_Class2d for constant-time classification away from polygon boundaries while preserving the exact tolerance path for boundary cells. - Normalize coordinates and explicit tolerances robustly, handle invalid and extreme input values, and preserve cache state correctly across copy and move operations. - Cache face wires, edge occurrences, pcurves, parameter ranges, and optional 2D bounding boxes in BRepClass_FaceExplorer to avoid repeated topology and geometry traversal during exact classification. - Introduce explicit cached-geometry and bounding-box states in BRepClass_Edge, invalidate derived data on topology changes, and isolate bounding-box construction failures per edge. - Reuse a lazily constructed exact face explorer in BRepTopAdaptor_FClass2d, cache periodic surface properties, and serialize access to mutable exact-classification traversal state. - Replace node-based classifier and point sequences with contiguous linear vectors, remove intermediate polygon copies, and make tighter wire discretization transactional. - Consolidate Perform() and TestOnRestriction() through a shared classification path while preserving their boundary and periodic recadrement semantics.
This commit is contained in:
@@ -16,26 +16,91 @@
|
||||
|
||||
#include <CSLib_Class2d.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
#include <NCollection_LinearVector.hxx>
|
||||
#include <Precision.hxx>
|
||||
#include <Standard_OutOfMemory.hxx>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr double THE_MIN_NORMALIZATION_RANGE = 1.0e-10;
|
||||
|
||||
//! Returns true when direct arithmetic cannot reach OCCT's geometric infinity range.
|
||||
inline bool isSafeForDirectArithmetic(const double theValue)
|
||||
{
|
||||
return !std::isnan(theValue) && !Precision::IsInfinite(theValue);
|
||||
}
|
||||
|
||||
//! Returns true for representable values, including OCCT's finite infinity sentinels.
|
||||
inline bool isRepresentableValue(const double theValue)
|
||||
{
|
||||
constexpr double THE_MAX_VALUE = std::numeric_limits<double>::max();
|
||||
return theValue >= -THE_MAX_VALUE && theValue <= THE_MAX_VALUE;
|
||||
}
|
||||
|
||||
//! Transforms a coordinate from original space to normalized [0,1] space.
|
||||
//! @param[in] theU Original coordinate value
|
||||
//! @param[in] theUMin Minimum bound of original range
|
||||
//! @param[in] theURange Range of original domain (theUMax - theUMin)
|
||||
//! @param[in] theUMax Maximum bound of original range
|
||||
//! @return Normalized coordinate in [0,1], or original value if range is too small
|
||||
inline double transformToNormalized(const double theU, const double theUMin, const double theURange)
|
||||
inline double transformToNormalized(const double theU, const double theUMin, const double theUMax)
|
||||
{
|
||||
constexpr double THE_MIN_RANGE = 1e-10;
|
||||
if (theURange > THE_MIN_RANGE)
|
||||
const double aRange = theUMax - theUMin;
|
||||
if (aRange > THE_MIN_NORMALIZATION_RANGE)
|
||||
{
|
||||
return (theU - theUMin) / theURange;
|
||||
const double aDifference = theU - theUMin;
|
||||
if (isSafeForDirectArithmetic(aDifference) && isSafeForDirectArithmetic(aRange))
|
||||
{
|
||||
return aDifference / aRange;
|
||||
}
|
||||
const double aScale = std::max(std::abs(theUMin), std::abs(theUMax));
|
||||
const double aScaledMin = theUMin / aScale;
|
||||
return (theU / aScale - aScaledMin) / (theUMax / aScale - aScaledMin);
|
||||
}
|
||||
return theU;
|
||||
}
|
||||
|
||||
//! Converts an external tolerance to a finite, non-negative value.
|
||||
inline double sanitizeTolerance(const double theTolerance)
|
||||
{
|
||||
if (std::isnan(theTolerance) || theTolerance <= 0.0)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
return isRepresentableValue(theTolerance) ? theTolerance : std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
//! Normalizes a distance without overflowing when the finite bounds span more
|
||||
//! than the representable double range.
|
||||
inline double normalizeTolerance(const double theTolerance,
|
||||
const double theMin,
|
||||
const double theMax)
|
||||
{
|
||||
const double aTolerance = sanitizeTolerance(theTolerance);
|
||||
const double aRange = theMax - theMin;
|
||||
if (!(aRange > THE_MIN_NORMALIZATION_RANGE))
|
||||
{
|
||||
return aTolerance;
|
||||
}
|
||||
if (isSafeForDirectArithmetic(aRange))
|
||||
{
|
||||
return sanitizeTolerance(aTolerance / aRange);
|
||||
}
|
||||
|
||||
const double aScale = std::max(std::abs(theMin), std::abs(theMax));
|
||||
const double aScaledRange = theMax / aScale - theMin / aScale;
|
||||
return sanitizeTolerance((aTolerance / aScale) / aScaledRange);
|
||||
}
|
||||
|
||||
//! Grid cache for O(1) classification of points far from polygon edges.
|
||||
//! Construction is delayed until repeated queries amortize its cost.
|
||||
static constexpr int THE_GRID_SIZE = 32;
|
||||
static constexpr int THE_GRID_MIN_POINTS = 24;
|
||||
static constexpr size_t THE_GRID_BUILD_QUERY_COUNT = 64;
|
||||
} // namespace
|
||||
|
||||
//=================================================================================================
|
||||
@@ -55,46 +120,36 @@ void CSLib_Class2d::init(const TCol_Containers2d& thePnts2d,
|
||||
myVMax = theVMax;
|
||||
|
||||
// Validate input parameters.
|
||||
if (theUMax <= theUMin || theVMax <= theVMin || thePnts2d.Length() < 3)
|
||||
if (!isRepresentableValue(theUMin) || !isRepresentableValue(theVMin)
|
||||
|| !isRepresentableValue(theUMax) || !isRepresentableValue(theVMax) || theUMax <= theUMin
|
||||
|| theVMax <= theVMin || thePnts2d.Length() < 3)
|
||||
{
|
||||
myPointsCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
myPointsCount = thePnts2d.Length();
|
||||
myTolU = theTolU;
|
||||
myTolV = theTolV;
|
||||
myPointsCount = thePnts2d.Length();
|
||||
myOriginalTolU = sanitizeTolerance(theTolU);
|
||||
myOriginalTolV = sanitizeTolerance(theTolV);
|
||||
myTolU = normalizeTolerance(theTolU, theUMin, theUMax);
|
||||
myTolV = normalizeTolerance(theTolV, theVMin, theVMax);
|
||||
|
||||
// Allocate arrays with one extra element for closing the polygon.
|
||||
myPnts2dX.Resize(0, myPointsCount, false);
|
||||
myPnts2dY.Resize(0, myPointsCount, false);
|
||||
|
||||
const double aDu = theUMax - theUMin;
|
||||
const double aDv = theVMax - theVMin;
|
||||
|
||||
// Transform points to normalized coordinates.
|
||||
const int aLower = thePnts2d.Lower();
|
||||
for (int i = 0; i < myPointsCount; ++i)
|
||||
{
|
||||
const gp_Pnt2d& aP2D = thePnts2d(i + aLower);
|
||||
myPnts2dX.ChangeValue(i) = transformToNormalized(aP2D.X(), theUMin, aDu);
|
||||
myPnts2dY.ChangeValue(i) = transformToNormalized(aP2D.Y(), theVMin, aDv);
|
||||
myPnts2dX.ChangeValue(i) = transformToNormalized(aP2D.X(), theUMin, theUMax);
|
||||
myPnts2dY.ChangeValue(i) = transformToNormalized(aP2D.Y(), theVMin, theVMax);
|
||||
}
|
||||
|
||||
// Close the polygon by copying first point to last position.
|
||||
myPnts2dX.ChangeLast() = myPnts2dX.First();
|
||||
myPnts2dY.ChangeLast() = myPnts2dY.First();
|
||||
|
||||
// Normalize tolerances.
|
||||
constexpr double THE_MIN_RANGE = 1e-10;
|
||||
if (aDu > THE_MIN_RANGE)
|
||||
{
|
||||
myTolU /= aDu;
|
||||
}
|
||||
if (aDv > THE_MIN_RANGE)
|
||||
{
|
||||
myTolV /= aDv;
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
@@ -138,6 +193,285 @@ CSLib_Class2d::CSLib_Class2d(const NCollection_DynamicArray<gp_Pnt2d>& thePnts2d
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
CSLib_Class2d::CSLib_Class2d(const CSLib_Class2d& theOther)
|
||||
: myPnts2dX(theOther.myPnts2dX),
|
||||
myPnts2dY(theOther.myPnts2dY),
|
||||
myTolU(theOther.myTolU),
|
||||
myTolV(theOther.myTolV),
|
||||
myOriginalTolU(theOther.myOriginalTolU),
|
||||
myOriginalTolV(theOther.myOriginalTolV),
|
||||
myPointsCount(theOther.myPointsCount),
|
||||
myUMin(theOther.myUMin),
|
||||
myVMin(theOther.myVMin),
|
||||
myUMax(theOther.myUMax),
|
||||
myVMax(theOther.myVMax),
|
||||
myQueryCount(theOther.myGridState.load(std::memory_order_acquire) == GridState::Building
|
||||
? 0
|
||||
: theOther.myQueryCount.load(std::memory_order_relaxed))
|
||||
{
|
||||
const GridState aState = theOther.myGridState.load(std::memory_order_acquire);
|
||||
if (aState == GridState::Ready)
|
||||
{
|
||||
myGrid = theOther.myGrid;
|
||||
myGridState.store(GridState::Ready, std::memory_order_relaxed);
|
||||
}
|
||||
else if (aState == GridState::Disabled)
|
||||
{
|
||||
myGridState.store(GridState::Disabled, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
CSLib_Class2d& CSLib_Class2d::operator=(const CSLib_Class2d& theOther)
|
||||
{
|
||||
if (this == &theOther)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
CSLib_Class2d aCopy(theOther);
|
||||
*this = std::move(aCopy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
CSLib_Class2d::CSLib_Class2d(CSLib_Class2d&& theOther) noexcept
|
||||
: myPnts2dX(std::move(theOther.myPnts2dX)),
|
||||
myPnts2dY(std::move(theOther.myPnts2dY)),
|
||||
myTolU(theOther.myTolU),
|
||||
myTolV(theOther.myTolV),
|
||||
myOriginalTolU(theOther.myOriginalTolU),
|
||||
myOriginalTolV(theOther.myOriginalTolV),
|
||||
myPointsCount(theOther.myPointsCount),
|
||||
myUMin(theOther.myUMin),
|
||||
myVMin(theOther.myVMin),
|
||||
myUMax(theOther.myUMax),
|
||||
myVMax(theOther.myVMax),
|
||||
myQueryCount(theOther.myGridState.load(std::memory_order_acquire) == GridState::Building
|
||||
? 0
|
||||
: theOther.myQueryCount.load(std::memory_order_relaxed))
|
||||
{
|
||||
const GridState aState = theOther.myGridState.load(std::memory_order_acquire);
|
||||
if (aState == GridState::Ready)
|
||||
{
|
||||
myGrid = std::move(theOther.myGrid);
|
||||
myGridState.store(GridState::Ready, std::memory_order_relaxed);
|
||||
}
|
||||
else if (aState == GridState::Disabled)
|
||||
{
|
||||
myGridState.store(GridState::Disabled, std::memory_order_relaxed);
|
||||
}
|
||||
theOther.myPointsCount = 0;
|
||||
theOther.myGridState.store(GridState::Disabled, std::memory_order_release);
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
CSLib_Class2d& CSLib_Class2d::operator=(CSLib_Class2d&& theOther) noexcept
|
||||
{
|
||||
if (this == &theOther)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
const GridState aState = theOther.myGridState.load(std::memory_order_acquire);
|
||||
myPnts2dX = std::move(theOther.myPnts2dX);
|
||||
myPnts2dY = std::move(theOther.myPnts2dY);
|
||||
myTolU = theOther.myTolU;
|
||||
myTolV = theOther.myTolV;
|
||||
myOriginalTolU = theOther.myOriginalTolU;
|
||||
myOriginalTolV = theOther.myOriginalTolV;
|
||||
myPointsCount = theOther.myPointsCount;
|
||||
myUMin = theOther.myUMin;
|
||||
myVMin = theOther.myVMin;
|
||||
myUMax = theOther.myUMax;
|
||||
myVMax = theOther.myVMax;
|
||||
myQueryCount.store(
|
||||
aState == GridState::Building ? 0 : theOther.myQueryCount.load(std::memory_order_relaxed),
|
||||
std::memory_order_relaxed);
|
||||
myGrid = NCollection_Array1<GridCell>();
|
||||
if (aState == GridState::Ready)
|
||||
{
|
||||
myGrid = std::move(theOther.myGrid);
|
||||
myGridState.store(GridState::Ready, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
myGridState.store(aState == GridState::Disabled ? GridState::Disabled : GridState::NotBuilt,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
theOther.myPointsCount = 0;
|
||||
theOther.myGridState.store(GridState::Disabled, std::memory_order_release);
|
||||
return *this;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void CSLib_Class2d::buildGridCache() const
|
||||
{
|
||||
if (myPointsCount < THE_GRID_MIN_POINTS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GridState anExpectedState = GridState::NotBuilt;
|
||||
if (!myGridState.compare_exchange_strong(anExpectedState,
|
||||
GridState::Building,
|
||||
std::memory_order_acq_rel,
|
||||
std::memory_order_acquire))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const int aTotalCells = THE_GRID_SIZE * THE_GRID_SIZE;
|
||||
myGrid.Resize(0, aTotalCells - 1, false);
|
||||
myGrid.Init(GridCell_Unvisited);
|
||||
|
||||
const double aCellSize = 1.0 / THE_GRID_SIZE;
|
||||
const double anEps = 8.0 * std::numeric_limits<double>::epsilon();
|
||||
const double* pX = &myPnts2dX.First();
|
||||
const double* pY = &myPnts2dY.First();
|
||||
NCollection_LinearVector<int> aCellQueue(static_cast<size_t>(aTotalCells));
|
||||
|
||||
// Non-finite polygon data and domain-sized tolerances are valid for the
|
||||
// exact path but cannot produce a useful, safely indexed cache.
|
||||
if (myTolU >= 1.0 || myTolV >= 1.0)
|
||||
{
|
||||
myGrid = NCollection_Array1<GridCell>();
|
||||
myGridState.store(GridState::Disabled, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
for (int aPointIdx = 0; aPointIdx < myPointsCount; ++aPointIdx)
|
||||
{
|
||||
if (!isSafeForDirectArithmetic(pX[aPointIdx]) || !isSafeForDirectArithmetic(pY[aPointIdx]))
|
||||
{
|
||||
myGrid = NCollection_Array1<GridCell>();
|
||||
myGridState.store(GridState::Disabled, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Rasterize tolerance-expanded edge boxes into the fixed grid. This is
|
||||
// equivalent to testing every cell box against every edge box, but avoids
|
||||
// an O(grid cells * polygon edges) scan.
|
||||
for (int anEdgeIdx = 0; anEdgeIdx < myPointsCount; ++anEdgeIdx)
|
||||
{
|
||||
const double aMinX = std::min(pX[anEdgeIdx], pX[anEdgeIdx + 1]) - myTolU - anEps;
|
||||
const double aMaxX = std::max(pX[anEdgeIdx], pX[anEdgeIdx + 1]) + myTolU + anEps;
|
||||
const double aMinY = std::min(pY[anEdgeIdx], pY[anEdgeIdx + 1]) - myTolV - anEps;
|
||||
const double aMaxY = std::max(pY[anEdgeIdx], pY[anEdgeIdx + 1]) + myTolV + anEps;
|
||||
if (aMaxX < 0.0 || aMinX > 1.0 || aMaxY < 0.0 || aMinY > 1.0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const double aClippedMinX = std::clamp(aMinX, 0.0, 1.0);
|
||||
const double aClippedMaxX = std::clamp(aMaxX, 0.0, 1.0);
|
||||
const double aClippedMinY = std::clamp(aMinY, 0.0, 1.0);
|
||||
const double aClippedMaxY = std::clamp(aMaxY, 0.0, 1.0);
|
||||
const int aMinCellX =
|
||||
std::max(static_cast<int>(std::ceil(aClippedMinX * THE_GRID_SIZE)) - 1, 0);
|
||||
const int aMaxCellX =
|
||||
std::min(static_cast<int>(std::floor(aClippedMaxX * THE_GRID_SIZE)), THE_GRID_SIZE - 1);
|
||||
const int aMinCellY =
|
||||
std::max(static_cast<int>(std::ceil(aClippedMinY * THE_GRID_SIZE)) - 1, 0);
|
||||
const int aMaxCellY =
|
||||
std::min(static_cast<int>(std::floor(aClippedMaxY * THE_GRID_SIZE)), THE_GRID_SIZE - 1);
|
||||
|
||||
for (int aCellY = aMinCellY; aCellY <= aMaxCellY; ++aCellY)
|
||||
{
|
||||
for (int aCellX = aMinCellX; aCellX <= aMaxCellX; ++aCellX)
|
||||
{
|
||||
myGrid.SetValue(aCellY * THE_GRID_SIZE + aCellX, GridCell_Boundary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A polygon cannot change classification inside a connected set of cells
|
||||
// that contains no boundary. Classify one center per component and flood
|
||||
// the result through the remaining cells.
|
||||
int aUsableCellCount = 0;
|
||||
for (int aCellIndex = 0; aCellIndex < aTotalCells; ++aCellIndex)
|
||||
{
|
||||
if (myGrid.Value(aCellIndex) != GridCell_Unvisited)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const int aSeedX = aCellIndex % THE_GRID_SIZE;
|
||||
const int aSeedY = aCellIndex / THE_GRID_SIZE;
|
||||
const double aSeedCenterX = (static_cast<double>(aSeedX) + 0.5) * aCellSize;
|
||||
const double aSeedCenterY = (static_cast<double>(aSeedY) + 0.5) * aCellSize;
|
||||
const GridCell aComponentValue =
|
||||
internalSiDans(aSeedCenterX, aSeedCenterY) ? GridCell_Inside : GridCell_Outside;
|
||||
|
||||
aCellQueue.Clear();
|
||||
aCellQueue.Append(aCellIndex);
|
||||
myGrid.SetValue(aCellIndex, aComponentValue);
|
||||
for (size_t aQueueIndex = 0; aQueueIndex < aCellQueue.Size(); ++aQueueIndex)
|
||||
{
|
||||
const int aCurrentCell = aCellQueue[aQueueIndex];
|
||||
++aUsableCellCount;
|
||||
|
||||
const int aCurrentX = aCurrentCell % THE_GRID_SIZE;
|
||||
const int aCurrentY = aCurrentCell / THE_GRID_SIZE;
|
||||
const int aNeighborCells[4] = {aCurrentX > 0 ? aCurrentCell - 1 : -1,
|
||||
aCurrentX + 1 < THE_GRID_SIZE ? aCurrentCell + 1 : -1,
|
||||
aCurrentY > 0 ? aCurrentCell - THE_GRID_SIZE : -1,
|
||||
aCurrentY + 1 < THE_GRID_SIZE ? aCurrentCell + THE_GRID_SIZE
|
||||
: -1};
|
||||
for (size_t aNeighborIndex = 0; aNeighborIndex < 4; ++aNeighborIndex)
|
||||
{
|
||||
const int aNeighborCell = aNeighborCells[aNeighborIndex];
|
||||
if (aNeighborCell < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (myGrid.Value(aNeighborCell) == GridCell_Unvisited)
|
||||
{
|
||||
myGrid.SetValue(aNeighborCell, aComponentValue);
|
||||
aCellQueue.Append(aNeighborCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Even a partial cache is useful after the sustained-use threshold: a
|
||||
// classified cell avoids an O(edges) scan, while boundary cells remain exact.
|
||||
if (aUsableCellCount == 0)
|
||||
{
|
||||
myGrid = NCollection_Array1<GridCell>();
|
||||
myGridState.store(GridState::Disabled, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
myGridState.store(GridState::Ready, std::memory_order_release);
|
||||
}
|
||||
catch (const Standard_OutOfMemory&)
|
||||
{
|
||||
myGrid = NCollection_Array1<GridCell>();
|
||||
myQueryCount.store(0, std::memory_order_relaxed);
|
||||
myGridState.store(GridState::NotBuilt, std::memory_order_release);
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
myGrid = NCollection_Array1<GridCell>();
|
||||
myQueryCount.store(0, std::memory_order_relaxed);
|
||||
myGridState.store(GridState::NotBuilt, std::memory_order_release);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
myGrid = NCollection_Array1<GridCell>();
|
||||
myQueryCount.store(0, std::memory_order_relaxed);
|
||||
myGridState.store(GridState::NotBuilt, std::memory_order_release);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
CSLib_Class2d::Result CSLib_Class2d::SiDans(const gp_Pnt2d& thePoint) const
|
||||
{
|
||||
if (myPointsCount == 0)
|
||||
@@ -149,8 +483,8 @@ CSLib_Class2d::Result CSLib_Class2d::SiDans(const gp_Pnt2d& thePoint) const
|
||||
double aY = thePoint.Y();
|
||||
|
||||
// Compute tolerance in original coordinate space.
|
||||
const double aTolU = myTolU * (myUMax - myUMin);
|
||||
const double aTolV = myTolV * (myVMax - myVMin);
|
||||
const double aTolU = myOriginalTolU;
|
||||
const double aTolV = myOriginalTolV;
|
||||
|
||||
// Quick rejection test for points clearly outside the bounding box.
|
||||
if (aX < (myUMin - aTolU) || aX > (myUMax + aTolU) || aY < (myVMin - aTolV)
|
||||
@@ -160,11 +494,44 @@ CSLib_Class2d::Result CSLib_Class2d::SiDans(const gp_Pnt2d& thePoint) const
|
||||
}
|
||||
|
||||
// Transform to normalized coordinates.
|
||||
aX = transformToNormalized(aX, myUMin, myUMax - myUMin);
|
||||
aY = transformToNormalized(aY, myVMin, myVMax - myVMin);
|
||||
aX = transformToNormalized(aX, myUMin, myUMax);
|
||||
aY = transformToNormalized(aY, myVMin, myVMax);
|
||||
|
||||
// Build the acceleration grid only for sustained workloads. Short-lived
|
||||
// classifiers and small polygons remain on the cheaper exact scan.
|
||||
GridState aGridState = myGridState.load(std::memory_order_acquire);
|
||||
if (aGridState == GridState::NotBuilt && myPointsCount >= THE_GRID_MIN_POINTS)
|
||||
{
|
||||
const size_t aQueryCount = myQueryCount.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
if (aQueryCount >= THE_GRID_BUILD_QUERY_COUNT)
|
||||
{
|
||||
buildGridCache();
|
||||
aGridState = myGridState.load(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
|
||||
// Fast-path: conservative grid lookup (O(1) away from polygon edges).
|
||||
if (aGridState == GridState::Ready && isSafeForDirectArithmetic(aX)
|
||||
&& isSafeForDirectArithmetic(aY) && aX >= 0.0 && aX <= 1.0 && aY >= 0.0 && aY <= 1.0)
|
||||
{
|
||||
int aIX = static_cast<int>(aX * THE_GRID_SIZE);
|
||||
int aIY = static_cast<int>(aY * THE_GRID_SIZE);
|
||||
aIX = std::clamp(aIX, 0, THE_GRID_SIZE - 1);
|
||||
aIY = std::clamp(aIY, 0, THE_GRID_SIZE - 1);
|
||||
const GridCell aCell = myGrid.Value(aIY * THE_GRID_SIZE + aIX);
|
||||
if (aCell == GridCell_Inside)
|
||||
{
|
||||
return Result_Inside;
|
||||
}
|
||||
if (aCell == GridCell_Outside)
|
||||
{
|
||||
return Result_Outside;
|
||||
}
|
||||
// Boundary cells fall through to exact classification.
|
||||
}
|
||||
|
||||
// Perform classification with ON detection.
|
||||
const Result aResult = internalSiDansOuOn(aX, aY);
|
||||
const Result aResult = internalSiDansOuOn(aX, aY, myTolU, myTolV);
|
||||
if (aResult == Result_Uncertain)
|
||||
{
|
||||
return Result_Uncertain; // ON boundary
|
||||
@@ -196,31 +563,34 @@ CSLib_Class2d::Result CSLib_Class2d::SiDans_OnMode(const gp_Pnt2d& thePoint,
|
||||
return Result_Uncertain;
|
||||
}
|
||||
|
||||
double aX = thePoint.X();
|
||||
double aY = thePoint.Y();
|
||||
double aX = thePoint.X();
|
||||
double aY = thePoint.Y();
|
||||
const double aTolerance = sanitizeTolerance(theTol);
|
||||
const double aTolU = normalizeTolerance(aTolerance, myUMin, myUMax);
|
||||
const double aTolV = normalizeTolerance(aTolerance, myVMin, myVMax);
|
||||
|
||||
// Quick rejection test.
|
||||
if (aX < (myUMin - theTol) || aX > (myUMax + theTol) || aY < (myVMin - theTol)
|
||||
|| aY > (myVMax + theTol))
|
||||
if (aX < (myUMin - aTolerance) || aX > (myUMax + aTolerance) || aY < (myVMin - aTolerance)
|
||||
|| aY > (myVMax + aTolerance))
|
||||
{
|
||||
return Result_Outside;
|
||||
}
|
||||
|
||||
// Transform to normalized coordinates.
|
||||
aX = transformToNormalized(aX, myUMin, myUMax - myUMin);
|
||||
aY = transformToNormalized(aY, myVMin, myVMax - myVMin);
|
||||
aX = transformToNormalized(aX, myUMin, myUMax);
|
||||
aY = transformToNormalized(aY, myVMin, myVMax);
|
||||
|
||||
// Perform classification with ON detection.
|
||||
const Result aResult = internalSiDansOuOn(aX, aY);
|
||||
const Result aResult = internalSiDansOuOn(aX, aY, aTolU, aTolV);
|
||||
|
||||
// Check corner points with tolerance.
|
||||
if (theTol > 0.0)
|
||||
if (aTolU > 0.0 || aTolV > 0.0)
|
||||
{
|
||||
const bool isInside = (aResult == Result_Inside);
|
||||
if (isInside != internalSiDans(aX - theTol, aY - theTol)
|
||||
|| isInside != internalSiDans(aX + theTol, aY - theTol)
|
||||
|| isInside != internalSiDans(aX - theTol, aY + theTol)
|
||||
|| isInside != internalSiDans(aX + theTol, aY + theTol))
|
||||
if (isInside != internalSiDans(aX - aTolU, aY - aTolV)
|
||||
|| isInside != internalSiDans(aX + aTolU, aY - aTolV)
|
||||
|| isInside != internalSiDans(aX - aTolU, aY + aTolV)
|
||||
|| isInside != internalSiDans(aX + aTolU, aY + aTolV))
|
||||
{
|
||||
return Result_Uncertain;
|
||||
}
|
||||
@@ -234,16 +604,20 @@ CSLib_Class2d::Result CSLib_Class2d::SiDans_OnMode(const gp_Pnt2d& thePoint,
|
||||
bool CSLib_Class2d::internalSiDans(const double thePx, const double thePy) const
|
||||
{
|
||||
// Ray-casting algorithm: count edge crossings with a horizontal ray from (Px, Py) to +infinity.
|
||||
// Use raw pointers for cache-friendly sequential access and auto-vectorization.
|
||||
const double* pX = &myPnts2dX.First();
|
||||
const double* pY = &myPnts2dY.First();
|
||||
|
||||
int aNbCrossings = 0;
|
||||
|
||||
double aPrevDx = myPnts2dX.Value(0) - thePx;
|
||||
double aPrevDy = myPnts2dY.Value(0) - thePy;
|
||||
double aPrevDx = pX[0] - thePx;
|
||||
double aPrevDy = pY[0] - thePy;
|
||||
bool aPrevYIsNegative = (aPrevDy < 0.0);
|
||||
|
||||
for (int aNextIdx = 1; aNextIdx <= myPointsCount; ++aNextIdx)
|
||||
{
|
||||
const double aCurrDx = myPnts2dX.Value(aNextIdx) - thePx;
|
||||
const double aCurrDy = myPnts2dY.Value(aNextIdx) - thePy;
|
||||
const double aCurrDx = pX[aNextIdx] - thePx;
|
||||
const double aCurrDy = pY[aNextIdx] - thePy;
|
||||
const bool aCurrYIsNegative = (aCurrDy < 0.0);
|
||||
|
||||
// Check for edge crossing when Y changes sign.
|
||||
@@ -277,23 +651,29 @@ bool CSLib_Class2d::internalSiDans(const double thePx, const double thePy) const
|
||||
//=================================================================================================
|
||||
|
||||
CSLib_Class2d::Result CSLib_Class2d::internalSiDansOuOn(const double thePx,
|
||||
const double thePy) const
|
||||
const double thePy,
|
||||
const double theTolU,
|
||||
const double theTolV) const
|
||||
{
|
||||
// Ray-casting algorithm with ON detection.
|
||||
// Use raw pointers for cache-friendly sequential access and auto-vectorization.
|
||||
const double* pX = &myPnts2dX.First();
|
||||
const double* pY = &myPnts2dY.First();
|
||||
|
||||
int aNbCrossings = 0;
|
||||
|
||||
double aPrevDx = myPnts2dX.Value(0) - thePx;
|
||||
double aPrevDy = myPnts2dY.Value(0) - thePy;
|
||||
double aPrevDx = pX[0] - thePx;
|
||||
double aPrevDy = pY[0] - thePy;
|
||||
bool aPrevYIsNegative = (aPrevDy < 0.0);
|
||||
|
||||
for (int aNextIdx = 1; aNextIdx <= myPointsCount; ++aNextIdx)
|
||||
{
|
||||
const int aPrevIdx = aNextIdx - 1;
|
||||
const double aCurrDx = myPnts2dX.Value(aNextIdx) - thePx;
|
||||
const double aCurrDy = myPnts2dY.Value(aNextIdx) - thePy;
|
||||
const double aCurrDx = pX[aNextIdx] - thePx;
|
||||
const double aCurrDy = pY[aNextIdx] - thePy;
|
||||
|
||||
// Check if point is very close to current vertex.
|
||||
if (aCurrDx < myTolU && aCurrDx > -myTolU && aCurrDy < myTolV && aCurrDy > -myTolV)
|
||||
if (aCurrDx < theTolU && aCurrDx > -theTolU && aCurrDy < theTolV && aCurrDy > -theTolV)
|
||||
{
|
||||
return Result_Uncertain; // ON boundary (at vertex)
|
||||
}
|
||||
@@ -301,15 +681,12 @@ CSLib_Class2d::Result CSLib_Class2d::internalSiDansOuOn(const double thePx,
|
||||
// Check if point is ON the edge by computing Y at the test point's X.
|
||||
// Skip interpolation for nearly vertical edges to avoid division instability.
|
||||
// For vertical edges, the ON detection is handled by the tolerance check above.
|
||||
const double aEdgeDx = myPnts2dX.Value(aNextIdx) - myPnts2dX.Value(aPrevIdx);
|
||||
if ((myPnts2dX.Value(aPrevIdx) - thePx) * aCurrDx < 0.0
|
||||
&& std::abs(aEdgeDx) > Precision::PConfusion())
|
||||
const double aEdgeDx = pX[aNextIdx] - pX[aPrevIdx];
|
||||
if ((pX[aPrevIdx] - thePx) * aCurrDx < 0.0 && std::abs(aEdgeDx) > Precision::PConfusion())
|
||||
{
|
||||
const double aInterpY =
|
||||
myPnts2dY.Value(aNextIdx)
|
||||
- (myPnts2dY.Value(aNextIdx) - myPnts2dY.Value(aPrevIdx)) / aEdgeDx * aCurrDx;
|
||||
const double aDeltaY = aInterpY - thePy;
|
||||
if (aDeltaY >= -myTolV && aDeltaY <= myTolV)
|
||||
const double aInterpY = pY[aNextIdx] - (pY[aNextIdx] - pY[aPrevIdx]) / aEdgeDx * aCurrDx;
|
||||
const double aDeltaY = aInterpY - thePy;
|
||||
if (aDeltaY >= -theTolV && aDeltaY <= theTolV)
|
||||
{
|
||||
return Result_Uncertain; // ON boundary (on edge)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include <NCollection_Sequence.hxx>
|
||||
#include <NCollection_DynamicArray.hxx>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
class gp_Pnt2d;
|
||||
|
||||
//! Low-level algorithm for 2D point-in-polygon classification.
|
||||
@@ -110,37 +112,17 @@ public:
|
||||
double theUMax,
|
||||
double theVMax);
|
||||
|
||||
//! Deep-copy constructor. The immutable polygon and a completed grid cache are copied.
|
||||
Standard_EXPORT CSLib_Class2d(const CSLib_Class2d& theOther);
|
||||
|
||||
//! Deep-copy assignment. A grid under construction is intentionally not copied.
|
||||
Standard_EXPORT CSLib_Class2d& operator=(const CSLib_Class2d& theOther);
|
||||
|
||||
//! Move constructor.
|
||||
CSLib_Class2d(CSLib_Class2d&& theOther) noexcept
|
||||
: myPnts2dX(std::move(theOther.myPnts2dX)),
|
||||
myPnts2dY(std::move(theOther.myPnts2dY)),
|
||||
myTolU(theOther.myTolU),
|
||||
myTolV(theOther.myTolV),
|
||||
myPointsCount(theOther.myPointsCount),
|
||||
myUMin(theOther.myUMin),
|
||||
myVMin(theOther.myVMin),
|
||||
myUMax(theOther.myUMax),
|
||||
myVMax(theOther.myVMax)
|
||||
{
|
||||
}
|
||||
Standard_EXPORT CSLib_Class2d(CSLib_Class2d&& theOther) noexcept;
|
||||
|
||||
//! Move assignment operator.
|
||||
CSLib_Class2d& operator=(CSLib_Class2d&& theOther) noexcept
|
||||
{
|
||||
if (this != &theOther)
|
||||
{
|
||||
myPnts2dX = std::move(theOther.myPnts2dX);
|
||||
myPnts2dY = std::move(theOther.myPnts2dY);
|
||||
myTolU = theOther.myTolU;
|
||||
myTolV = theOther.myTolV;
|
||||
myPointsCount = theOther.myPointsCount;
|
||||
myUMin = theOther.myUMin;
|
||||
myVMin = theOther.myVMin;
|
||||
myUMax = theOther.myUMax;
|
||||
myVMax = theOther.myVMax;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
Standard_EXPORT CSLib_Class2d& operator=(CSLib_Class2d&& theOther) noexcept;
|
||||
|
||||
//! Classifies a point relative to the polygon.
|
||||
//!
|
||||
@@ -173,10 +155,12 @@ private:
|
||||
//!
|
||||
//! Same as internalSiDans() but also detects if the point lies on the boundary.
|
||||
//!
|
||||
//! @param[in] theX X coordinate in normalized space
|
||||
//! @param[in] theY Y coordinate in normalized space
|
||||
//! @param[in] theX X coordinate in normalized space
|
||||
//! @param[in] theY Y coordinate in normalized space
|
||||
//! @param[in] theTolU U tolerance in normalized space
|
||||
//! @param[in] theTolV V tolerance in normalized space
|
||||
//! @return Classification result
|
||||
Result internalSiDansOuOn(double theX, double theY) const;
|
||||
Result internalSiDansOuOn(double theX, double theY, double theTolU, double theTolV) const;
|
||||
|
||||
//! Initializes the classifier with polygon data.
|
||||
//! @tparam TCol_Containers2d Container type (Array1 or Sequence)
|
||||
@@ -189,22 +173,44 @@ private:
|
||||
double theUMax,
|
||||
double theVMax);
|
||||
|
||||
//! Copy constructor is deleted.
|
||||
CSLib_Class2d(const CSLib_Class2d&) = delete;
|
||||
|
||||
//! Copy assignment operator is deleted.
|
||||
CSLib_Class2d& operator=(const CSLib_Class2d&) = delete;
|
||||
//! Builds the grid cache for fast point classification on first sustained use.
|
||||
//! Cells whose box overlaps a tolerance-expanded polygon-edge box remain on
|
||||
//! the exact path; only provably boundary-free cells are classified/cached.
|
||||
void buildGridCache() const;
|
||||
|
||||
private:
|
||||
NCollection_Array1<double> myPnts2dX; //!< X coordinates (normalized)
|
||||
NCollection_Array1<double> myPnts2dY; //!< Y coordinates (normalized)
|
||||
double myTolU = 0.0; //!< Tolerance in U direction (normalized)
|
||||
double myTolV = 0.0; //!< Tolerance in V direction (normalized)
|
||||
int myPointsCount = 0; //!< Number of polygon vertices
|
||||
double myUMin = 0.0; //!< Original minimum U bound
|
||||
double myVMin = 0.0; //!< Original minimum V bound
|
||||
double myUMax = 0.0; //!< Original maximum U bound
|
||||
double myVMax = 0.0; //!< Original maximum V bound
|
||||
//! Grid cell classification for the fast-path cache.
|
||||
enum GridCell : signed char
|
||||
{
|
||||
GridCell_Outside = -1, //!< Cell is fully outside the polygon
|
||||
GridCell_Boundary = 0, //!< Cell straddles a polygon edge and needs an exact test
|
||||
GridCell_Inside = 1, //!< Cell is fully inside the polygon
|
||||
GridCell_Unvisited = 2 //!< Boundary-free cell not yet assigned by flood fill
|
||||
};
|
||||
|
||||
//! Lifecycle of the optional immutable grid cache.
|
||||
enum class GridState : unsigned char
|
||||
{
|
||||
NotBuilt, //!< No cache, including after a transient allocation failure
|
||||
Building, //!< One thread owns construction; other threads use the exact path
|
||||
Ready, //!< Grid is complete and immutable
|
||||
Disabled //!< Polygon or tolerance intrinsically cannot use a grid
|
||||
};
|
||||
|
||||
NCollection_Array1<double> myPnts2dX; //!< X coordinates (normalized)
|
||||
NCollection_Array1<double> myPnts2dY; //!< Y coordinates (normalized)
|
||||
double myTolU = 0.0; //!< U tolerance (normalized)
|
||||
double myTolV = 0.0; //!< V tolerance (normalized)
|
||||
double myOriginalTolU = 0.0; //!< U tolerance in input coordinates
|
||||
double myOriginalTolV = 0.0; //!< V tolerance in input coordinates
|
||||
int myPointsCount = 0; //!< Number of polygon vertices
|
||||
double myUMin = 0.0; //!< Original minimum U bound
|
||||
double myVMin = 0.0; //!< Original minimum V bound
|
||||
double myUMax = 0.0; //!< Original maximum U bound
|
||||
double myVMax = 0.0; //!< Original maximum V bound
|
||||
mutable NCollection_Array1<GridCell> myGrid; //!< Immutable when Ready
|
||||
mutable std::atomic<GridState> myGridState{GridState::NotBuilt};
|
||||
mutable std::atomic<size_t> myQueryCount{0};
|
||||
};
|
||||
|
||||
#endif // _CSLib_Class2d_HeaderFile
|
||||
|
||||
@@ -28,7 +28,11 @@
|
||||
#include <NCollection_Array2.hxx>
|
||||
#include <NCollection_Sequence.hxx>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <thread>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -254,6 +258,28 @@ TEST_F(CSLibClass2dTest, SiDans_PointOnBoundary)
|
||||
EXPECT_EQ(aClassifier.SiDans(aPointOnEdge), 0);
|
||||
}
|
||||
|
||||
TEST_F(CSLibClass2dTest, DeepCopyPreservesClassification)
|
||||
{
|
||||
NCollection_Array1<gp_Pnt2d> aPnts(1, 4);
|
||||
aPnts(1) = gp_Pnt2d(0.0, 0.0);
|
||||
aPnts(2) = gp_Pnt2d(1.0, 0.0);
|
||||
aPnts(3) = gp_Pnt2d(1.0, 1.0);
|
||||
aPnts(4) = gp_Pnt2d(0.0, 1.0);
|
||||
|
||||
CSLib_Class2d aSource(aPnts, 0.01, 0.01, 0.0, 0.0, 1.0, 1.0);
|
||||
const CSLib_Class2d aCopy(aSource);
|
||||
CSLib_Class2d anAssigned;
|
||||
anAssigned = aSource;
|
||||
|
||||
const gp_Pnt2d aSamples[] = {gp_Pnt2d(0.5, 0.5), gp_Pnt2d(2.0, 2.0), gp_Pnt2d(0.5, 0.0)};
|
||||
for (const gp_Pnt2d& aPoint : aSamples)
|
||||
{
|
||||
const CSLib_Class2d::Result aState = aSource.SiDans(aPoint);
|
||||
EXPECT_EQ(aCopy.SiDans(aPoint), aState);
|
||||
EXPECT_EQ(anAssigned.SiDans(aPoint), aState);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CSLibClass2dTest, SiDans_TriangularPolygon)
|
||||
{
|
||||
NCollection_Array1<gp_Pnt2d> aPnts(1, 3);
|
||||
@@ -303,6 +329,266 @@ TEST_F(CSLibClass2dTest, InternalSiDans_NormalizedCoordinates)
|
||||
EXPECT_EQ(aClassifier.SiDans(gp_Pnt2d(15.0, 15.0)), CSLib_Class2d::Result_Outside);
|
||||
}
|
||||
|
||||
TEST_F(CSLibClass2dTest, LazyGridRemainsExactAwayFromBoundary)
|
||||
{
|
||||
constexpr int THE_POINT_COUNT = 64;
|
||||
NCollection_Array1<gp_Pnt2d> aPnts(1, THE_POINT_COUNT);
|
||||
for (int anIdx = 0; anIdx < THE_POINT_COUNT; ++anIdx)
|
||||
{
|
||||
const double anAngle =
|
||||
2.0 * M_PI * static_cast<double>(anIdx) / static_cast<double>(THE_POINT_COUNT);
|
||||
aPnts(anIdx + 1) = gp_Pnt2d(0.5 + 0.4 * std::cos(anAngle), 0.5 + 0.4 * std::sin(anAngle));
|
||||
}
|
||||
|
||||
CSLib_Class2d aClassifier(aPnts, 1.0e-8, 1.0e-8, 0.0, 0.0, 1.0, 1.0);
|
||||
|
||||
// Cross the lazy-build threshold with exact interior queries.
|
||||
for (int aQueryIdx = 0; aQueryIdx < 64; ++aQueryIdx)
|
||||
{
|
||||
EXPECT_EQ(aClassifier.SiDans(gp_Pnt2d(0.5, 0.5)), CSLib_Class2d::Result_Inside);
|
||||
}
|
||||
|
||||
// Exercise cached cells, excluding an annulus around the polygon boundary.
|
||||
for (int aY = 0; aY < 16; ++aY)
|
||||
{
|
||||
for (int anX = 0; anX < 16; ++anX)
|
||||
{
|
||||
const gp_Pnt2d aPoint((static_cast<double>(anX) + 0.5) / 16.0,
|
||||
(static_cast<double>(aY) + 0.5) / 16.0);
|
||||
const double aDx = aPoint.X() - 0.5;
|
||||
const double aDy = aPoint.Y() - 0.5;
|
||||
const double aRadius = std::sqrt(aDx * aDx + aDy * aDy);
|
||||
if (aRadius < 0.35)
|
||||
{
|
||||
EXPECT_EQ(aClassifier.SiDans(aPoint), CSLib_Class2d::Result_Inside);
|
||||
}
|
||||
else if (aRadius > 0.45)
|
||||
{
|
||||
EXPECT_EQ(aClassifier.SiDans(aPoint), CSLib_Class2d::Result_Outside);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Boundary candidates must stay on the exact/tolerance path after caching.
|
||||
EXPECT_EQ(aClassifier.SiDans(aPnts(1)), CSLib_Class2d::Result_Uncertain);
|
||||
|
||||
const CSLib_Class2d aCopy(aClassifier);
|
||||
CSLib_Class2d anAssigned;
|
||||
anAssigned = aClassifier;
|
||||
EXPECT_EQ(aCopy.SiDans(gp_Pnt2d(0.5, 0.5)), CSLib_Class2d::Result_Inside);
|
||||
EXPECT_EQ(anAssigned.SiDans(gp_Pnt2d(0.99, 0.99)), CSLib_Class2d::Result_Outside);
|
||||
EXPECT_EQ(aCopy.SiDans(aPnts(1)), CSLib_Class2d::Result_Uncertain);
|
||||
}
|
||||
|
||||
TEST_F(CSLibClass2dTest, LazyGridMatchesExactPathForConcaveDiagonalPolygon)
|
||||
{
|
||||
constexpr int THE_POINT_COUNT = 32;
|
||||
NCollection_Array1<gp_Pnt2d> aPnts(1, THE_POINT_COUNT);
|
||||
for (int anIdx = 0; anIdx < THE_POINT_COUNT; ++anIdx)
|
||||
{
|
||||
const double anAngle =
|
||||
2.0 * M_PI * static_cast<double>(anIdx) / static_cast<double>(THE_POINT_COUNT);
|
||||
const double aRadius = (anIdx % 4 == 1) ? 0.22 : ((anIdx % 2 == 0) ? 0.46 : 0.34);
|
||||
aPnts(anIdx + 1) =
|
||||
gp_Pnt2d(0.5 + aRadius * std::cos(anAngle), 0.5 + aRadius * std::sin(anAngle));
|
||||
}
|
||||
|
||||
CSLib_Class2d aCached(aPnts, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0);
|
||||
CSLib_Class2d anExact(aPnts, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0);
|
||||
for (size_t aQueryIdx = 0; aQueryIdx < 64; ++aQueryIdx)
|
||||
{
|
||||
ASSERT_EQ(aCached.SiDans(gp_Pnt2d(0.5, 0.5)), CSLib_Class2d::Result_Inside);
|
||||
}
|
||||
|
||||
for (int aY = 0; aY < 47; ++aY)
|
||||
{
|
||||
for (int anX = 0; anX < 47; ++anX)
|
||||
{
|
||||
const gp_Pnt2d aPoint((static_cast<double>(anX) + 0.37) / 47.0,
|
||||
(static_cast<double>(aY) + 0.61) / 47.0);
|
||||
EXPECT_EQ(aCached.SiDans(aPoint), anExact.SiDans_OnMode(aPoint, 0.0))
|
||||
<< "sample (" << anX << ", " << aY << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CSLibClass2dTest, LazyGridConcurrentBuildOverlapIsThreadSafe)
|
||||
{
|
||||
// The large polygon keeps the designated build query active while synchronized
|
||||
// readers exercise the exact fallback. Run this test under TSAN for race detection.
|
||||
constexpr int THE_POINT_COUNT = 32768;
|
||||
NCollection_Array1<gp_Pnt2d> aPnts(1, THE_POINT_COUNT);
|
||||
for (int anIdx = 0; anIdx < THE_POINT_COUNT; ++anIdx)
|
||||
{
|
||||
const double anAngle =
|
||||
2.0 * M_PI * static_cast<double>(anIdx) / static_cast<double>(THE_POINT_COUNT);
|
||||
aPnts(anIdx + 1) = gp_Pnt2d(0.5 + 0.4 * std::cos(anAngle), 0.5 + 0.4 * std::sin(anAngle));
|
||||
}
|
||||
|
||||
CSLib_Class2d aClassifier(aPnts, 1.0e-8, 1.0e-8, 0.0, 0.0, 1.0, 1.0);
|
||||
for (size_t aQueryIdx = 0; aQueryIdx < 63; ++aQueryIdx)
|
||||
{
|
||||
ASSERT_EQ(aClassifier.SiDans(gp_Pnt2d(0.5, 0.5)), CSLib_Class2d::Result_Inside);
|
||||
}
|
||||
|
||||
std::atomic<bool> hasFailure{false};
|
||||
std::atomic<bool> canStart{false};
|
||||
std::atomic<bool> hasBuildQueryStarted{false};
|
||||
std::atomic<bool> hasBuildQueryFinished{false};
|
||||
std::atomic<size_t> anOverlapQueryCount{0};
|
||||
std::atomic<size_t> aReadyCount{0};
|
||||
std::thread aBuildThread([&]() {
|
||||
while (!canStart.load(std::memory_order_acquire))
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
hasBuildQueryStarted.store(true, std::memory_order_release);
|
||||
if (aClassifier.SiDans(gp_Pnt2d(0.5, 0.5)) != CSLib_Class2d::Result_Inside)
|
||||
{
|
||||
hasFailure.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
hasBuildQueryFinished.store(true, std::memory_order_release);
|
||||
});
|
||||
|
||||
std::array<std::thread, 7> aReaderThreads;
|
||||
for (size_t aThreadIdx = 0; aThreadIdx < aReaderThreads.size(); ++aThreadIdx)
|
||||
{
|
||||
aReaderThreads[aThreadIdx] = std::thread([&]() {
|
||||
aReadyCount.fetch_add(1, std::memory_order_release);
|
||||
while (!hasBuildQueryStarted.load(std::memory_order_acquire))
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
for (size_t aQueryIdx = 0; aQueryIdx < 8; ++aQueryIdx)
|
||||
{
|
||||
if (!hasBuildQueryFinished.load(std::memory_order_acquire))
|
||||
{
|
||||
anOverlapQueryCount.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
const bool isInside = (aQueryIdx & 1u) == 0u;
|
||||
const gp_Pnt2d aPoint = isInside ? gp_Pnt2d(0.5, 0.5) : gp_Pnt2d(0.99, 0.99);
|
||||
const CSLib_Class2d::Result anExpected =
|
||||
isInside ? CSLib_Class2d::Result_Inside : CSLib_Class2d::Result_Outside;
|
||||
if (aClassifier.SiDans(aPoint) != anExpected)
|
||||
{
|
||||
hasFailure.store(true, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
while (aReadyCount.load(std::memory_order_acquire) != aReaderThreads.size())
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
canStart.store(true, std::memory_order_release);
|
||||
while (!hasBuildQueryStarted.load(std::memory_order_acquire))
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
if (!hasBuildQueryFinished.load(std::memory_order_acquire))
|
||||
{
|
||||
anOverlapQueryCount.fetch_add(1, std::memory_order_relaxed);
|
||||
if (aClassifier.SiDans(gp_Pnt2d(0.99, 0.99)) != CSLib_Class2d::Result_Outside)
|
||||
{
|
||||
hasFailure.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
aBuildThread.join();
|
||||
for (std::thread& aThread : aReaderThreads)
|
||||
{
|
||||
aThread.join();
|
||||
}
|
||||
EXPECT_FALSE(hasFailure.load(std::memory_order_relaxed));
|
||||
EXPECT_GT(anOverlapQueryCount.load(std::memory_order_relaxed), 0u);
|
||||
}
|
||||
|
||||
TEST_F(CSLibClass2dTest, SiDansHandlesNegativeNonFiniteAndExtremeInputs)
|
||||
{
|
||||
constexpr int THE_POINT_COUNT = 32;
|
||||
constexpr int THE_POINTS_PER_SIDE = THE_POINT_COUNT / 4;
|
||||
NCollection_Array1<gp_Pnt2d> aPnts(1, THE_POINT_COUNT);
|
||||
for (int anIdx = 0; anIdx < THE_POINTS_PER_SIDE; ++anIdx)
|
||||
{
|
||||
const double aParameter = static_cast<double>(anIdx) / THE_POINTS_PER_SIDE;
|
||||
aPnts(1 + anIdx) = gp_Pnt2d(aParameter, 0.0);
|
||||
aPnts(1 + THE_POINTS_PER_SIDE + anIdx) = gp_Pnt2d(1.0, aParameter);
|
||||
aPnts(1 + 2 * THE_POINTS_PER_SIDE + anIdx) = gp_Pnt2d(1.0 - aParameter, 1.0);
|
||||
aPnts(1 + 3 * THE_POINTS_PER_SIDE + anIdx) = gp_Pnt2d(0.0, 1.0 - aParameter);
|
||||
}
|
||||
|
||||
CSLib_Class2d
|
||||
aNegative(aPnts, -1.0, -std::numeric_limits<double>::infinity(), 0.0, 0.0, 1.0, 1.0);
|
||||
EXPECT_EQ(aNegative.SiDans(gp_Pnt2d(0.5, 0.5)), CSLib_Class2d::Result_Inside);
|
||||
EXPECT_EQ(aNegative.SiDans_OnMode(gp_Pnt2d(1.5, 0.5), -1.0), CSLib_Class2d::Result_Outside);
|
||||
|
||||
CSLib_Class2d anExtremeTolerance(aPnts,
|
||||
std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::max(),
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0);
|
||||
for (size_t aQueryIdx = 0; aQueryIdx < 80; ++aQueryIdx)
|
||||
{
|
||||
EXPECT_EQ(anExtremeTolerance.SiDans(gp_Pnt2d(0.5, 0.5)), CSLib_Class2d::Result_Uncertain);
|
||||
}
|
||||
|
||||
const double aLimit = Precision::Infinite();
|
||||
NCollection_Array1<gp_Pnt2d> anExtremePnts(1, 4);
|
||||
anExtremePnts(1) = gp_Pnt2d(-0.5 * aLimit, -0.5 * aLimit);
|
||||
anExtremePnts(2) = gp_Pnt2d(0.5 * aLimit, -0.5 * aLimit);
|
||||
anExtremePnts(3) = gp_Pnt2d(0.5 * aLimit, 0.5 * aLimit);
|
||||
anExtremePnts(4) = gp_Pnt2d(-0.5 * aLimit, 0.5 * aLimit);
|
||||
CSLib_Class2d anExtremeCoordinates(anExtremePnts, 0.0, 0.0, -aLimit, -aLimit, aLimit, aLimit);
|
||||
EXPECT_EQ(anExtremeCoordinates.SiDans(gp_Pnt2d(0.0, 0.0)), CSLib_Class2d::Result_Inside);
|
||||
EXPECT_EQ(anExtremeCoordinates.SiDans(gp_Pnt2d(0.75 * aLimit, 0.0)),
|
||||
CSLib_Class2d::Result_Outside);
|
||||
|
||||
const double aLargeTolerance = 0.1 * aLimit;
|
||||
CSLib_Class2d aTolerantExtreme(anExtremePnts,
|
||||
aLargeTolerance,
|
||||
aLargeTolerance,
|
||||
-aLimit,
|
||||
-aLimit,
|
||||
aLimit,
|
||||
aLimit);
|
||||
EXPECT_EQ(aTolerantExtreme.SiDans(gp_Pnt2d(0.55 * aLimit, 0.0)), CSLib_Class2d::Result_Uncertain);
|
||||
EXPECT_EQ(aTolerantExtreme.SiDans(gp_Pnt2d(0.65 * aLimit, 0.0)), CSLib_Class2d::Result_Outside);
|
||||
EXPECT_EQ(anExtremeCoordinates.SiDans_OnMode(gp_Pnt2d(0.0, 0.55 * aLimit), aLargeTolerance),
|
||||
CSLib_Class2d::Result_Uncertain);
|
||||
}
|
||||
|
||||
TEST_F(CSLibClass2dTest, LazyGridPreservesToleranceAcrossCellBoundary)
|
||||
{
|
||||
constexpr int THE_POINTS_PER_SIDE = 8;
|
||||
constexpr int THE_POINT_COUNT = 4 * THE_POINTS_PER_SIDE;
|
||||
constexpr double THE_MIN = 0.25;
|
||||
constexpr double THE_MAX = 0.75;
|
||||
constexpr double THE_TOLERANCE = 1.0e-4;
|
||||
NCollection_Array1<gp_Pnt2d> aPnts(1, THE_POINT_COUNT);
|
||||
for (int anIdx = 0; anIdx < THE_POINTS_PER_SIDE; ++anIdx)
|
||||
{
|
||||
const double aParameter = static_cast<double>(anIdx) / static_cast<double>(THE_POINTS_PER_SIDE);
|
||||
aPnts(1 + anIdx) = gp_Pnt2d(THE_MIN + (THE_MAX - THE_MIN) * aParameter, THE_MIN);
|
||||
aPnts(1 + THE_POINTS_PER_SIDE + anIdx) =
|
||||
gp_Pnt2d(THE_MAX, THE_MIN + (THE_MAX - THE_MIN) * aParameter);
|
||||
aPnts(1 + 2 * THE_POINTS_PER_SIDE + anIdx) =
|
||||
gp_Pnt2d(THE_MAX - (THE_MAX - THE_MIN) * aParameter, THE_MAX);
|
||||
aPnts(1 + 3 * THE_POINTS_PER_SIDE + anIdx) =
|
||||
gp_Pnt2d(THE_MIN, THE_MAX - (THE_MAX - THE_MIN) * aParameter);
|
||||
}
|
||||
|
||||
CSLib_Class2d aClassifier(aPnts, THE_TOLERANCE, THE_TOLERANCE, 0.0, 0.0, 1.0, 1.0);
|
||||
for (size_t aQueryIdx = 0; aQueryIdx < 64; ++aQueryIdx)
|
||||
{
|
||||
ASSERT_EQ(aClassifier.SiDans(gp_Pnt2d(0.5, 0.5)), CSLib_Class2d::Result_Inside);
|
||||
}
|
||||
|
||||
EXPECT_EQ(aClassifier.SiDans(gp_Pnt2d(THE_MIN - 0.5 * THE_TOLERANCE, 0.5)),
|
||||
CSLib_Class2d::Result_Uncertain);
|
||||
}
|
||||
|
||||
// Test SiDans_OnMode
|
||||
TEST_F(CSLibClass2dTest, SiDans_OnMode_PointInside)
|
||||
{
|
||||
@@ -318,6 +604,25 @@ TEST_F(CSLibClass2dTest, SiDans_OnMode_PointInside)
|
||||
EXPECT_EQ(aClassifier.SiDans_OnMode(aPointInside, 0.01), 1);
|
||||
}
|
||||
|
||||
TEST_F(CSLibClass2dTest, SiDans_OnMode_NormalizesAndReplacesToleranceOnAnisotropicDomain)
|
||||
{
|
||||
NCollection_Array1<gp_Pnt2d> aPnts(1, 4);
|
||||
aPnts(1) = gp_Pnt2d(20.0, -1.0);
|
||||
aPnts(2) = gp_Pnt2d(80.0, -1.0);
|
||||
aPnts(3) = gp_Pnt2d(80.0, 1.0);
|
||||
aPnts(4) = gp_Pnt2d(20.0, 1.0);
|
||||
|
||||
// Constructor tolerances are deliberately larger than the explicit one.
|
||||
CSLib_Class2d aClassifier(aPnts, 10.0, 1.0, 0.0, -2.0, 100.0, 2.0);
|
||||
EXPECT_EQ(aClassifier.SiDans_OnMode(gp_Pnt2d(50.0, 1.5), 0.01), CSLib_Class2d::Result_Outside);
|
||||
|
||||
// The same original-space tolerance has different normalized U/V values.
|
||||
EXPECT_EQ(aClassifier.SiDans_OnMode(gp_Pnt2d(19.75, 0.0), 0.5), CSLib_Class2d::Result_Uncertain);
|
||||
EXPECT_EQ(aClassifier.SiDans_OnMode(gp_Pnt2d(19.0, 0.0), 0.5), CSLib_Class2d::Result_Outside);
|
||||
EXPECT_EQ(aClassifier.SiDans_OnMode(gp_Pnt2d(50.0, 1.25), 0.5), CSLib_Class2d::Result_Uncertain);
|
||||
EXPECT_EQ(aClassifier.SiDans_OnMode(gp_Pnt2d(50.0, 1.75), 0.5), CSLib_Class2d::Result_Outside);
|
||||
}
|
||||
|
||||
// Test with degenerate polygon (less than 3 points effective)
|
||||
TEST_F(CSLibClass2dTest, DegeneratePolygon_InvalidBounds)
|
||||
{
|
||||
@@ -401,10 +706,12 @@ TEST_F(CSLibNormalPolyDefTest, Value_AtSingularPoints)
|
||||
// the tolerance check RealSmall().
|
||||
// Test that the function doesn't crash at these points.
|
||||
EXPECT_TRUE(aPoly.Value(0.0, aValue));
|
||||
EXPECT_TRUE(std::isfinite(aValue));
|
||||
EXPECT_FALSE(std::isnan(aValue));
|
||||
EXPECT_FALSE(Precision::IsInfinite(aValue));
|
||||
|
||||
EXPECT_TRUE(aPoly.Value(M_PI / 2.0, aValue));
|
||||
EXPECT_TRUE(std::isfinite(aValue));
|
||||
EXPECT_FALSE(std::isnan(aValue));
|
||||
EXPECT_FALSE(Precision::IsInfinite(aValue));
|
||||
}
|
||||
|
||||
// Test Derivative function
|
||||
@@ -421,7 +728,8 @@ TEST_F(CSLibNormalPolyDefTest, Derivative_AtRegularPoint)
|
||||
double aDeriv;
|
||||
EXPECT_TRUE(aPoly.Derivative(M_PI / 4.0, aDeriv));
|
||||
// Derivative should be computed without crash
|
||||
EXPECT_TRUE(std::isfinite(aDeriv));
|
||||
EXPECT_FALSE(std::isnan(aDeriv));
|
||||
EXPECT_FALSE(Precision::IsInfinite(aDeriv));
|
||||
}
|
||||
|
||||
TEST_F(CSLibNormalPolyDefTest, Derivative_AtSingularPoint)
|
||||
|
||||
@@ -1657,7 +1657,7 @@ static bool AreFacesCoincideInArea(const TopoDS_Shape& theBase
|
||||
double tol2d = Precision::PConfusion();
|
||||
BRepClass_Intersector anInter;
|
||||
BRepClass_Edge aBCE;
|
||||
aBCE.Face() = aBaseFace;
|
||||
aBCE.SetFace(aBaseFace);
|
||||
double maxDist = std::max(BRep_Tool::Tolerance(aBaseFace), BRep_Tool::Tolerance(aFace));
|
||||
|
||||
bool isError = false;
|
||||
@@ -1687,7 +1687,7 @@ static bool AreFacesCoincideInArea(const TopoDS_Shape& theBase
|
||||
}
|
||||
BB.UpdateEdge(aE, PC, aBaseFace, tolE);
|
||||
}
|
||||
aBCE.Edge() = aE;
|
||||
aBCE.SetEdge(aE);
|
||||
anInter.Perform(aLin, pLinMin, tol2d, aBCE);
|
||||
if (anInter.IsDone())
|
||||
{
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ TopOpeBRepBuild_WireEdgeClassifier::TopOpeBRepBuild_WireEdgeClassifier(
|
||||
const TopOpeBRepBuild_BlockBuilder& BB)
|
||||
: TopOpeBRepBuild_CompositeClassifier(BB)
|
||||
{
|
||||
myBCEdge.Face() = TopoDS::Face(F);
|
||||
myBCEdge.SetFace(TopoDS::Face(F));
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
@@ -509,7 +509,7 @@ bool TopOpeBRepBuild_WireEdgeClassifier::CompareElement(const TopoDS_Shape& EE)
|
||||
myFirstCompare = false;
|
||||
}
|
||||
|
||||
myBCEdge.Edge() = E;
|
||||
myBCEdge.SetEdge(E);
|
||||
TopAbs_Orientation Eori = E.Orientation();
|
||||
myFPC.Compare(myBCEdge, Eori);
|
||||
#ifdef OCCT_DEBUG
|
||||
|
||||
@@ -15,49 +15,33 @@
|
||||
// commercial license or contractual agreement.
|
||||
|
||||
#include <BRepClass_Edge.hxx>
|
||||
#include <NCollection_IndexedDataMap.hxx>
|
||||
#include <Precision.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Vertex.hxx>
|
||||
#include <TopExp.hxx>
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
BRepClass_Edge::BRepClass_Edge()
|
||||
: myMaxTolerance(Precision::Infinite()),
|
||||
: myFirstParameter(0.0),
|
||||
myLastParameter(0.0),
|
||||
myMaxTolerance(Precision::Infinite()),
|
||||
myBoundingBoxState(BndBoxState::NotBuilt),
|
||||
myUseBndBox(false)
|
||||
{
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void BRepClass_Edge::SetNextEdge(
|
||||
const NCollection_IndexedDataMap<TopoDS_Shape,
|
||||
NCollection_List<TopoDS_Shape>,
|
||||
TopTools_ShapeMapHasher>& theMapVE)
|
||||
void BRepClass_Edge::SetEdge(const TopoDS_Edge& theEdge)
|
||||
{
|
||||
if (theMapVE.IsEmpty() || myEdge.IsNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
TopoDS_Vertex aVF, aVL;
|
||||
TopExp::Vertices(myEdge, aVF, aVL, true);
|
||||
invalidateDerivedData();
|
||||
myEdge = theEdge;
|
||||
}
|
||||
|
||||
if (aVL.IsNull() || aVL.IsSame(aVF))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const NCollection_List<TopoDS_Shape>* aListE = theMapVE.Seek(aVL);
|
||||
if (aListE->Extent() == 2)
|
||||
{
|
||||
for (NCollection_List<TopoDS_Shape>::Iterator anIt(*aListE); anIt.More(); anIt.Next())
|
||||
{
|
||||
if ((!anIt.Value().IsNull()) && (!anIt.Value().IsSame(myEdge)))
|
||||
{
|
||||
myNextEdge = TopoDS::Edge(anIt.Value());
|
||||
}
|
||||
}
|
||||
}
|
||||
//=================================================================================================
|
||||
|
||||
void BRepClass_Edge::SetFace(const TopoDS_Face& theFace)
|
||||
{
|
||||
invalidateDerivedData();
|
||||
myFace = theFace;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
@@ -65,7 +49,56 @@ void BRepClass_Edge::SetNextEdge(
|
||||
BRepClass_Edge::BRepClass_Edge(const TopoDS_Edge& E, const TopoDS_Face& F)
|
||||
: myEdge(E),
|
||||
myFace(F),
|
||||
myFirstParameter(0.0),
|
||||
myLastParameter(0.0),
|
||||
myMaxTolerance(Precision::Infinite()),
|
||||
myBoundingBoxState(BndBoxState::NotBuilt),
|
||||
myUseBndBox(false)
|
||||
{
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void BRepClass_Edge::SetGeometry(const occ::handle<Geom2d_Curve>& theCurve,
|
||||
const double theFirst,
|
||||
const double theLast)
|
||||
{
|
||||
myCurve = theCurve;
|
||||
myFirstParameter = theFirst;
|
||||
myLastParameter = theLast;
|
||||
myBoundingBox.SetVoid();
|
||||
myBoundingBoxState = BndBoxState::NotBuilt;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void BRepClass_Edge::SetBoundingBox(const Bnd_Box2d& theBox)
|
||||
{
|
||||
if (theBox.IsVoid())
|
||||
{
|
||||
SetBoundingBoxUnavailable();
|
||||
return;
|
||||
}
|
||||
myBoundingBox = theBox;
|
||||
myBoundingBoxState = BndBoxState::Ready;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void BRepClass_Edge::SetBoundingBoxUnavailable()
|
||||
{
|
||||
myBoundingBox.SetVoid();
|
||||
myBoundingBoxState = BndBoxState::Unavailable;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void BRepClass_Edge::invalidateDerivedData()
|
||||
{
|
||||
myNextEdge.Nullify();
|
||||
myCurve.Nullify();
|
||||
myBoundingBox.SetVoid();
|
||||
myFirstParameter = 0.0;
|
||||
myLastParameter = 0.0;
|
||||
myBoundingBoxState = BndBoxState::NotBuilt;
|
||||
}
|
||||
|
||||
@@ -17,17 +17,14 @@
|
||||
#ifndef _BRepClass_Edge_HeaderFile
|
||||
#define _BRepClass_Edge_HeaderFile
|
||||
|
||||
#include <Standard.hxx>
|
||||
#include <Bnd_Box2d.hxx>
|
||||
#include <Standard_DefineAlloc.hxx>
|
||||
#include <Standard_Handle.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <NCollection_List.hxx>
|
||||
#include <TopTools_ShapeMapHasher.hxx>
|
||||
#include <NCollection_IndexedDataMap.hxx>
|
||||
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
|
||||
class Geom2d_Curve;
|
||||
|
||||
//! This class is used to send the description of an
|
||||
//! Edge to the classifier. It contains an Edge and a
|
||||
//! Face. So the PCurve of the Edge can be found.
|
||||
@@ -36,28 +33,38 @@ class BRepClass_Edge
|
||||
public:
|
||||
DEFINE_STANDARD_ALLOC
|
||||
|
||||
//! State of the cached pcurve bounding box.
|
||||
enum class BndBoxState
|
||||
{
|
||||
NotBuilt,
|
||||
Ready,
|
||||
Unavailable
|
||||
};
|
||||
|
||||
Standard_EXPORT BRepClass_Edge();
|
||||
|
||||
Standard_EXPORT BRepClass_Edge(const TopoDS_Edge& E, const TopoDS_Face& F);
|
||||
|
||||
//! Returns the current Edge
|
||||
TopoDS_Edge& Edge() { return myEdge; }
|
||||
|
||||
//! Returns the current edge.
|
||||
const TopoDS_Edge& Edge() const { return myEdge; }
|
||||
|
||||
//! Returns the Face for the current Edge
|
||||
TopoDS_Face& Face() { return myFace; }
|
||||
|
||||
//! Returns the face for the current edge.
|
||||
const TopoDS_Face& Face() const { return myFace; }
|
||||
|
||||
//! Sets the current edge and invalidates topology-derived data.
|
||||
//! @param[in] theEdge new edge
|
||||
Standard_EXPORT void SetEdge(const TopoDS_Edge& theEdge);
|
||||
|
||||
//! Sets the face and invalidates topology-derived data.
|
||||
//! @param[in] theFace new face
|
||||
Standard_EXPORT void SetFace(const TopoDS_Face& theFace);
|
||||
|
||||
//! Returns the next Edge
|
||||
const TopoDS_Edge& NextEdge() const { return myNextEdge; }
|
||||
|
||||
//! Finds and sets the next Edge for the current
|
||||
Standard_EXPORT void SetNextEdge(
|
||||
const NCollection_IndexedDataMap<TopoDS_Shape,
|
||||
NCollection_List<TopoDS_Shape>,
|
||||
TopTools_ShapeMapHasher>& theMapVE);
|
||||
//! Sets the next edge at the last vertex of the current edge.
|
||||
//! @param[in] theEdge next edge
|
||||
void SetNextEdge(const TopoDS_Edge& theEdge) { myNextEdge = theEdge; }
|
||||
|
||||
//! Returns the maximum tolerance
|
||||
double MaxTolerance() const { return myMaxTolerance; }
|
||||
@@ -74,12 +81,50 @@ public:
|
||||
//! using boxes or not
|
||||
void SetUseBndBox(const bool theValue) { myUseBndBox = theValue; }
|
||||
|
||||
//! Sets cached 2D geometry and invalidates its bounding box.
|
||||
//! @param[in] theCurve pcurve on the associated face
|
||||
//! @param[in] theFirst first pcurve parameter
|
||||
//! @param[in] theLast last pcurve parameter
|
||||
Standard_EXPORT void SetGeometry(const occ::handle<Geom2d_Curve>& theCurve,
|
||||
double theFirst,
|
||||
double theLast);
|
||||
|
||||
//! Sets a successfully computed pcurve bounding box. A void box marks the box unavailable.
|
||||
//! @param[in] theBox pcurve bounding box
|
||||
Standard_EXPORT void SetBoundingBox(const Bnd_Box2d& theBox);
|
||||
|
||||
//! Marks the pcurve bounding box as unavailable after a failed build.
|
||||
Standard_EXPORT void SetBoundingBoxUnavailable();
|
||||
|
||||
//! Returns cached pcurve, or null when it is unavailable.
|
||||
const occ::handle<Geom2d_Curve>& Curve() const { return myCurve; }
|
||||
|
||||
//! Returns cached first pcurve parameter.
|
||||
double FirstParameter() const { return myFirstParameter; }
|
||||
|
||||
//! Returns cached last pcurve parameter.
|
||||
double LastParameter() const { return myLastParameter; }
|
||||
|
||||
//! Returns cached pcurve bounding box.
|
||||
const Bnd_Box2d& BoundingBox() const { return myBoundingBox; }
|
||||
|
||||
//! Returns the state of the cached pcurve bounding box.
|
||||
BndBoxState BoundingBoxState() const { return myBoundingBoxState; }
|
||||
|
||||
private:
|
||||
TopoDS_Edge myEdge;
|
||||
TopoDS_Face myFace;
|
||||
TopoDS_Edge myNextEdge;
|
||||
double myMaxTolerance;
|
||||
bool myUseBndBox;
|
||||
void invalidateDerivedData();
|
||||
|
||||
private:
|
||||
TopoDS_Edge myEdge;
|
||||
TopoDS_Face myFace;
|
||||
TopoDS_Edge myNextEdge;
|
||||
occ::handle<Geom2d_Curve> myCurve;
|
||||
Bnd_Box2d myBoundingBox;
|
||||
double myFirstParameter;
|
||||
double myLastParameter;
|
||||
double myMaxTolerance;
|
||||
BndBoxState myBoundingBoxState;
|
||||
bool myUseBndBox;
|
||||
};
|
||||
|
||||
#endif // _BRepClass_Edge_HeaderFile
|
||||
|
||||
@@ -18,24 +18,83 @@
|
||||
// Total rewriting of the method Segment; add the method OtherSegment.
|
||||
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <Bnd_Box2d.hxx>
|
||||
#include <BndLib_Add2dCurve.hxx>
|
||||
#include <BRepClass_Edge.hxx>
|
||||
#include <BRepClass_FaceExplorer.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
#include <Geom2d_Curve.hxx>
|
||||
#include <Geom2dAdaptor_Curve.hxx>
|
||||
#include <NCollection_DataMap.hxx>
|
||||
#include <Precision.hxx>
|
||||
#include <Standard_ErrorHandler.hxx>
|
||||
#include <Standard_Failure.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopExp.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <TopTools_ShapeMapHasher.hxx>
|
||||
#include <Geom2dAPI_ProjectPointOnCurve.hxx>
|
||||
|
||||
static const double Probing_Start = 0.123;
|
||||
static const double Probing_End = 0.7;
|
||||
static const double Probing_Step = 0.2111;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr size_t THE_MIN_EDGES_FOR_BOUNDING_BOX = 10;
|
||||
|
||||
void cacheGeometry(BRepClass_Edge& theEdge)
|
||||
{
|
||||
const BRepClass_Edge& anEdge = theEdge;
|
||||
double aFirst = 0.0;
|
||||
double aLast = 0.0;
|
||||
const occ::handle<Geom2d_Curve>& aCurve =
|
||||
BRep_Tool::CurveOnSurface(anEdge.Edge(), anEdge.Face(), aFirst, aLast);
|
||||
if (aCurve.IsNull())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
theEdge.SetGeometry(aCurve, aFirst, aLast);
|
||||
}
|
||||
|
||||
struct VertexEdges
|
||||
{
|
||||
TopoDS_Edge First;
|
||||
TopoDS_Edge Second;
|
||||
uint32_t Count = 0;
|
||||
|
||||
void Add(const TopoDS_Edge& theEdge)
|
||||
{
|
||||
if (Count == 0)
|
||||
{
|
||||
First = theEdge;
|
||||
}
|
||||
else if (Count == 1)
|
||||
{
|
||||
Second = theEdge;
|
||||
}
|
||||
++Count;
|
||||
}
|
||||
};
|
||||
|
||||
struct EdgeOccurrences
|
||||
{
|
||||
uint32_t First = 0;
|
||||
uint32_t Last = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
BRepClass_FaceExplorer::BRepClass_FaceExplorer(const TopoDS_Face& F)
|
||||
: myFace(F),
|
||||
myCurEdgeInd(1),
|
||||
myCurrentWire(0),
|
||||
myCurrentEdge(0),
|
||||
myCurrentEdgeEnd(0),
|
||||
myCurEdgeInd(0),
|
||||
myCurEdgePar(Probing_Start),
|
||||
myMaxTolerance(0.1),
|
||||
myUseBndBox(false),
|
||||
@@ -46,6 +105,113 @@ BRepClass_FaceExplorer::BRepClass_FaceExplorer(const TopoDS_Face& F)
|
||||
|
||||
{
|
||||
myFace.Orientation(TopAbs_FORWARD);
|
||||
|
||||
constexpr uint32_t THE_NO_EDGE = UINT32_MAX;
|
||||
NCollection_DataMap<TopoDS_Shape, EdgeOccurrences, TopTools_ShapeMapHasher> anOccurrences;
|
||||
NCollection_LinearVector<uint32_t> aNextOccurrence;
|
||||
|
||||
for (TopExp_Explorer aWireExp(myFace, TopAbs_WIRE); aWireExp.More(); aWireExp.Next())
|
||||
{
|
||||
WireData aWire;
|
||||
aWire.FirstEdge = static_cast<uint32_t>(myEdges.Size());
|
||||
|
||||
NCollection_DataMap<TopoDS_Shape, VertexEdges, TopTools_ShapeMapHasher> aVertexEdges;
|
||||
for (TopExp_Explorer anEdgeExp(aWireExp.Current(), TopAbs_EDGE); anEdgeExp.More();
|
||||
anEdgeExp.Next())
|
||||
{
|
||||
const TopoDS_Edge& aTopoEdge = TopoDS::Edge(anEdgeExp.Current());
|
||||
BRepClass_Edge anEdgeData(aTopoEdge, myFace);
|
||||
cacheGeometry(anEdgeData);
|
||||
myEdges.Append(std::move(anEdgeData));
|
||||
const uint32_t anEdgeIndex = static_cast<uint32_t>(myEdges.Size() - 1);
|
||||
|
||||
aNextOccurrence.Append(THE_NO_EDGE);
|
||||
EdgeOccurrences* anOccurrence = anOccurrences.ChangeSeek(aTopoEdge);
|
||||
if (anOccurrence == nullptr)
|
||||
{
|
||||
anOccurrences.Bind(aTopoEdge, EdgeOccurrences{anEdgeIndex, anEdgeIndex});
|
||||
}
|
||||
else
|
||||
{
|
||||
aNextOccurrence[anOccurrence->Last] = anEdgeIndex;
|
||||
anOccurrence->Last = anEdgeIndex;
|
||||
}
|
||||
|
||||
for (TopExp_Explorer aVertexExp(aTopoEdge, TopAbs_VERTEX); aVertexExp.More();
|
||||
aVertexExp.Next())
|
||||
{
|
||||
const TopoDS_Shape& aVertex = aVertexExp.Current();
|
||||
VertexEdges* anEdges = aVertexEdges.ChangeSeek(aVertex);
|
||||
if (anEdges == nullptr)
|
||||
{
|
||||
VertexEdges aNewEdges;
|
||||
aNewEdges.Add(aTopoEdge);
|
||||
aVertexEdges.Bind(aVertex, std::move(aNewEdges));
|
||||
}
|
||||
else
|
||||
{
|
||||
anEdges->Add(aTopoEdge);
|
||||
}
|
||||
}
|
||||
++aWire.NbEdges;
|
||||
}
|
||||
|
||||
for (uint32_t anEdgeIndex = aWire.FirstEdge; anEdgeIndex < aWire.FirstEdge + aWire.NbEdges;
|
||||
++anEdgeIndex)
|
||||
{
|
||||
const BRepClass_Edge& anEdgeData = myEdges[anEdgeIndex];
|
||||
TopoDS_Vertex aFirstVertex;
|
||||
TopoDS_Vertex aLastVertex;
|
||||
TopExp::Vertices(anEdgeData.Edge(), aFirstVertex, aLastVertex, true);
|
||||
if (aLastVertex.IsNull() || aLastVertex.IsSame(aFirstVertex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const VertexEdges* anEdges = aVertexEdges.Seek(aLastVertex);
|
||||
if (anEdges == nullptr || anEdges->Count != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const TopoDS_Edge& aNextEdge =
|
||||
anEdges->First.IsSame(anEdgeData.Edge()) ? anEdges->Second : anEdges->First;
|
||||
if (!aNextEdge.IsNull() && !aNextEdge.IsSame(anEdgeData.Edge()))
|
||||
{
|
||||
myEdges[anEdgeIndex].SetNextEdge(aNextEdge);
|
||||
}
|
||||
}
|
||||
myWires.Append(aWire);
|
||||
}
|
||||
|
||||
for (TopExp_Explorer anEdgeExp(myFace, TopAbs_EDGE); anEdgeExp.More(); anEdgeExp.Next())
|
||||
{
|
||||
const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExp.Current());
|
||||
uint32_t anEdgeIndex = 0;
|
||||
bool isFound = false;
|
||||
const EdgeOccurrences* anOccurrence = anOccurrences.Seek(anEdge);
|
||||
if (anOccurrence != nullptr)
|
||||
{
|
||||
for (uint32_t aCandidateIndex = anOccurrence->First; aCandidateIndex != THE_NO_EDGE;
|
||||
aCandidateIndex = aNextOccurrence[aCandidateIndex])
|
||||
{
|
||||
const BRepClass_Edge& aCandidate = myEdges[aCandidateIndex];
|
||||
if (aCandidate.Edge().IsEqual(anEdge))
|
||||
{
|
||||
anEdgeIndex = aCandidateIndex;
|
||||
isFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isFound)
|
||||
{
|
||||
BRepClass_Edge anEdgeData(anEdge, myFace);
|
||||
cacheGeometry(anEdgeData);
|
||||
myEdges.Append(std::move(anEdgeData));
|
||||
anEdgeIndex = static_cast<uint32_t>(myEdges.Size() - 1);
|
||||
}
|
||||
myProbeEdges.Append(anEdgeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
@@ -110,7 +276,7 @@ bool BRepClass_FaceExplorer::Reject(const gp_Pnt2d&) const
|
||||
|
||||
bool BRepClass_FaceExplorer::Segment(const gp_Pnt2d& P, gp_Lin2d& L, double& Par)
|
||||
{
|
||||
myCurEdgeInd = 1;
|
||||
myCurEdgeInd = 0;
|
||||
myCurEdgePar = Probing_Start;
|
||||
|
||||
return OtherSegment(P, L, Par);
|
||||
@@ -120,30 +286,19 @@ bool BRepClass_FaceExplorer::Segment(const gp_Pnt2d& P, gp_Lin2d& L, double& Par
|
||||
|
||||
bool BRepClass_FaceExplorer::OtherSegment(const gp_Pnt2d& P, gp_Lin2d& L, double& Par)
|
||||
{
|
||||
TopExp_Explorer anExpF(myFace, TopAbs_EDGE);
|
||||
int i;
|
||||
double aFPar;
|
||||
double aLPar;
|
||||
occ::handle<Geom2d_Curve> aC2d;
|
||||
constexpr double aTolParConf2 = Precision::PConfusion() * Precision::PConfusion();
|
||||
gp_Pnt2d aPOnC;
|
||||
double aParamIn;
|
||||
|
||||
for (i = 1; anExpF.More(); anExpF.Next(), i++)
|
||||
constexpr double aTolParConf2 = Precision::PConfusion() * Precision::PConfusion();
|
||||
gp_Pnt2d aPOnC;
|
||||
while (myCurEdgeInd < myProbeEdges.Size())
|
||||
{
|
||||
if (i != myCurEdgeInd)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const TopoDS_Shape& aLocalShape = anExpF.Current();
|
||||
const TopAbs_Orientation anOrientation = aLocalShape.Orientation();
|
||||
const BRepClass_Edge& anEdgeData = myEdges[myProbeEdges[myCurEdgeInd]];
|
||||
const TopoDS_Edge& anEdge = anEdgeData.Edge();
|
||||
const TopAbs_Orientation anOrientation = anEdge.Orientation();
|
||||
|
||||
if (anOrientation == TopAbs_FORWARD || anOrientation == TopAbs_REVERSED)
|
||||
{
|
||||
const TopoDS_Edge& anEdge = TopoDS::Edge(aLocalShape);
|
||||
|
||||
aC2d = BRep_Tool::CurveOnSurface(anEdge, myFace, aFPar, aLPar);
|
||||
const occ::handle<Geom2d_Curve>& aC2d = anEdgeData.Curve();
|
||||
double aFPar = anEdgeData.FirstParameter();
|
||||
double aLPar = anEdgeData.LastParameter();
|
||||
|
||||
if (!aC2d.IsNull())
|
||||
{
|
||||
@@ -167,7 +322,7 @@ bool BRepClass_FaceExplorer::OtherSegment(const gp_Pnt2d& P, gp_Lin2d& L, double
|
||||
|
||||
for (; myCurEdgePar < Probing_End; myCurEdgePar += Probing_Step)
|
||||
{
|
||||
aParamIn = myCurEdgePar * aFPar + (1. - myCurEdgePar) * aLPar;
|
||||
const double aParamIn = myCurEdgePar * aFPar + (1. - myCurEdgePar) * aLPar;
|
||||
|
||||
gp_Vec2d aTanVec;
|
||||
aC2d->D1(aParamIn, aPOnC, aTanVec);
|
||||
@@ -254,7 +409,7 @@ bool BRepClass_FaceExplorer::OtherSegment(const gp_Pnt2d& P, gp_Lin2d& L, double
|
||||
myCurEdgePar += Probing_Step;
|
||||
if (myCurEdgePar >= Probing_End)
|
||||
{
|
||||
myCurEdgeInd++;
|
||||
++myCurEdgeInd;
|
||||
myCurEdgePar = Probing_Start;
|
||||
}
|
||||
|
||||
@@ -268,7 +423,7 @@ bool BRepClass_FaceExplorer::OtherSegment(const gp_Pnt2d& P, gp_Lin2d& L, double
|
||||
} // if (anOrientation == TopAbs_FORWARD ...
|
||||
|
||||
// This curve is not valid for line construction. Go to another edge.
|
||||
myCurEdgeInd++;
|
||||
++myCurEdgeInd;
|
||||
myCurEdgePar = Probing_Start;
|
||||
}
|
||||
|
||||
@@ -283,13 +438,15 @@ bool BRepClass_FaceExplorer::OtherSegment(const gp_Pnt2d& P, gp_Lin2d& L, double
|
||||
|
||||
void BRepClass_FaceExplorer::InitWires()
|
||||
{
|
||||
myWExplorer.Init(myFace, TopAbs_WIRE);
|
||||
myCurrentWire = 0;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
bool BRepClass_FaceExplorer::RejectWire(const gp_Lin2d&, const double) const
|
||||
bool BRepClass_FaceExplorer::RejectWire(const gp_Lin2d& theLine, const double theParameter) const
|
||||
{
|
||||
(void)theLine;
|
||||
(void)theParameter;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -297,26 +454,99 @@ bool BRepClass_FaceExplorer::RejectWire(const gp_Lin2d&, const double) const
|
||||
|
||||
void BRepClass_FaceExplorer::InitEdges()
|
||||
{
|
||||
myEExplorer.Init(myWExplorer.Current(), TopAbs_EDGE);
|
||||
myMapVE.Clear();
|
||||
TopExp::MapShapesAndAncestors(myWExplorer.Current(), TopAbs_VERTEX, TopAbs_EDGE, myMapVE);
|
||||
const WireData& aWire = myWires[myCurrentWire];
|
||||
myCurrentEdge = aWire.FirstEdge;
|
||||
myCurrentEdgeEnd = aWire.FirstEdge + aWire.NbEdges;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
bool BRepClass_FaceExplorer::RejectEdge(const gp_Lin2d&, const double) const
|
||||
bool BRepClass_FaceExplorer::RejectEdge(const gp_Lin2d& theLine, const double theParameter) const
|
||||
{
|
||||
(void)theLine;
|
||||
(void)theParameter;
|
||||
return false;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void BRepClass_FaceExplorer::SetUseBndBox(const bool theValue)
|
||||
{
|
||||
if (!theValue || myUseBndBox)
|
||||
{
|
||||
myUseBndBox = theValue;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const WireData& aWire : myWires)
|
||||
{
|
||||
for (uint32_t anEdgeIndex = aWire.FirstEdge; anEdgeIndex < aWire.FirstEdge + aWire.NbEdges;
|
||||
++anEdgeIndex)
|
||||
{
|
||||
BRepClass_Edge& anEdge = myEdges[anEdgeIndex];
|
||||
if (anEdge.BoundingBoxState() != BRepClass_Edge::BndBoxState::NotBuilt)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (anEdge.Curve().IsNull())
|
||||
{
|
||||
anEdge.SetBoundingBoxUnavailable();
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
OCC_CATCH_SIGNALS
|
||||
Bnd_Box2d aBox;
|
||||
BndLib_Add2dCurve::Add(anEdge.Curve(),
|
||||
anEdge.FirstParameter(),
|
||||
anEdge.LastParameter(),
|
||||
0.0,
|
||||
aBox);
|
||||
anEdge.SetBoundingBox(aBox);
|
||||
}
|
||||
catch (const Standard_Failure&)
|
||||
{
|
||||
anEdge.SetBoundingBoxUnavailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
myUseBndBox = true;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
bool BRepClass_FaceExplorer::ShouldUseBndBox() const
|
||||
{
|
||||
if (myEdges.Size() <= THE_MIN_EDGES_FOR_BOUNDING_BOX)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t aNbSplineEdges = 0;
|
||||
for (const BRepClass_Edge& anEdge : myEdges)
|
||||
{
|
||||
if (anEdge.Curve().IsNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const GeomAbs_CurveType aType = Geom2dAdaptor_Curve(anEdge.Curve()).GetType();
|
||||
if (aType == GeomAbs_BSplineCurve || aType == GeomAbs_BezierCurve
|
||||
|| aType == GeomAbs_OffsetCurve)
|
||||
{
|
||||
++aNbSplineEdges;
|
||||
}
|
||||
}
|
||||
return aNbSplineEdges * 2 >= myEdges.Size();
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void BRepClass_FaceExplorer::CurrentEdge(BRepClass_Edge& E, TopAbs_Orientation& Or) const
|
||||
{
|
||||
E.Edge() = TopoDS::Edge(myEExplorer.Current());
|
||||
E.Face() = myFace;
|
||||
Or = E.Edge().Orientation();
|
||||
E.SetNextEdge(myMapVE);
|
||||
E = myEdges[myCurrentEdge];
|
||||
const BRepClass_Edge& anEdge = E;
|
||||
Or = anEdge.Edge().Orientation();
|
||||
E.SetMaxTolerance(myMaxTolerance);
|
||||
E.SetUseBndBox(myUseBndBox);
|
||||
}
|
||||
|
||||
@@ -17,23 +17,21 @@
|
||||
#ifndef _BRepClass_FaceExplorer_HeaderFile
|
||||
#define _BRepClass_FaceExplorer_HeaderFile
|
||||
|
||||
#include <Standard.hxx>
|
||||
#include <BRepClass_Edge.hxx>
|
||||
#include <NCollection_LinearVector.hxx>
|
||||
#include <Standard_DefineAlloc.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <NCollection_List.hxx>
|
||||
#include <TopTools_ShapeMapHasher.hxx>
|
||||
#include <NCollection_IndexedDataMap.hxx>
|
||||
|
||||
#include <TopAbs_Orientation.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <Standard_Integer.hxx>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class gp_Pnt2d;
|
||||
class gp_Lin2d;
|
||||
class BRepClass_Edge;
|
||||
|
||||
//! Provide an exploration of a BRep Face for the
|
||||
//! classification. Return UV edges.
|
||||
//! The explored topology and pcurves form a snapshot. Reconstruct the explorer after modifying the
|
||||
//! underlying face or its edge representations.
|
||||
class BRepClass_FaceExplorer
|
||||
{
|
||||
public:
|
||||
@@ -65,10 +63,10 @@ public:
|
||||
Standard_EXPORT void InitWires();
|
||||
|
||||
//! Returns True if there is a current wire.
|
||||
bool MoreWires() const { return myWExplorer.More(); }
|
||||
bool MoreWires() const { return myCurrentWire < myWires.Size(); }
|
||||
|
||||
//! Sets the explorer to the next wire.
|
||||
void NextWire() { myWExplorer.Next(); }
|
||||
void NextWire() { ++myCurrentWire; }
|
||||
|
||||
//! Returns True if the wire bounding volume does not
|
||||
//! intersect the segment.
|
||||
@@ -79,10 +77,10 @@ public:
|
||||
Standard_EXPORT void InitEdges();
|
||||
|
||||
//! Returns True if there is a current edge.
|
||||
bool MoreEdges() const { return myEExplorer.More(); }
|
||||
bool MoreEdges() const { return myCurrentEdge < myCurrentEdgeEnd; }
|
||||
|
||||
//! Sets the explorer to the next edge.
|
||||
void NextEdge() { myEExplorer.Next(); }
|
||||
void NextEdge() { ++myCurrentEdge; }
|
||||
|
||||
//! Returns True if the edge bounding volume does not
|
||||
//! intersect the segment.
|
||||
@@ -104,22 +102,33 @@ public:
|
||||
|
||||
//! Sets the status of whether we are
|
||||
//! using boxes or not
|
||||
void SetUseBndBox(const bool theValue) { myUseBndBox = theValue; }
|
||||
Standard_EXPORT void SetUseBndBox(const bool theValue);
|
||||
|
||||
//! Returns true when cached boxes are expected to benefit this face.
|
||||
Standard_EXPORT bool ShouldUseBndBox() const;
|
||||
|
||||
protected:
|
||||
//! Computes UV bounds of a face
|
||||
Standard_EXPORT void ComputeFaceBounds();
|
||||
|
||||
private:
|
||||
TopoDS_Face myFace;
|
||||
TopExp_Explorer myWExplorer;
|
||||
TopExp_Explorer myEExplorer;
|
||||
int myCurEdgeInd;
|
||||
double myCurEdgePar;
|
||||
double myMaxTolerance;
|
||||
bool myUseBndBox;
|
||||
NCollection_IndexedDataMap<TopoDS_Shape, NCollection_List<TopoDS_Shape>, TopTools_ShapeMapHasher>
|
||||
myMapVE;
|
||||
struct WireData
|
||||
{
|
||||
uint32_t FirstEdge = 0;
|
||||
uint32_t NbEdges = 0;
|
||||
};
|
||||
|
||||
TopoDS_Face myFace;
|
||||
NCollection_LinearVector<WireData> myWires;
|
||||
NCollection_LinearVector<BRepClass_Edge> myEdges;
|
||||
NCollection_LinearVector<uint32_t> myProbeEdges;
|
||||
size_t myCurrentWire;
|
||||
size_t myCurrentEdge;
|
||||
size_t myCurrentEdgeEnd;
|
||||
size_t myCurEdgeInd;
|
||||
double myCurEdgePar;
|
||||
double myMaxTolerance;
|
||||
bool myUseBndBox;
|
||||
|
||||
double myUMin;
|
||||
double myUMax;
|
||||
|
||||
@@ -338,7 +338,16 @@ void BRepClass_Intersector::Perform(const gp_Lin2d& L,
|
||||
const TopoDS_Face& F = E.Face();
|
||||
|
||||
//
|
||||
aC2D = BRep_Tool::CurveOnSurface(EE, F, deb, fin);
|
||||
aC2D = E.Curve();
|
||||
if (!aC2D.IsNull())
|
||||
{
|
||||
deb = E.FirstParameter();
|
||||
fin = E.LastParameter();
|
||||
}
|
||||
else
|
||||
{
|
||||
aC2D = BRep_Tool::CurveOnSurface(EE, F, deb, fin);
|
||||
}
|
||||
if (aC2D.IsNull())
|
||||
{
|
||||
done = false; // !IsDone()
|
||||
@@ -347,12 +356,23 @@ void BRepClass_Intersector::Perform(const gp_Lin2d& L,
|
||||
//
|
||||
Bnd_Box2d aBond;
|
||||
gp_Pnt2d aPntF;
|
||||
bool anUseBndBox = E.UseBndBox();
|
||||
if (anUseBndBox)
|
||||
bool anUseBndBox = false;
|
||||
if (E.UseBndBox() && E.BoundingBoxState() != BRepClass_Edge::BndBoxState::Unavailable)
|
||||
{
|
||||
BndLib_Add2dCurve::Add(aC2D, deb, fin, 0., aBond);
|
||||
aBond.SetGap(aTolZ);
|
||||
aPntF = L.Location();
|
||||
if (E.BoundingBoxState() == BRepClass_Edge::BndBoxState::Ready)
|
||||
{
|
||||
aBond = E.BoundingBox();
|
||||
}
|
||||
else
|
||||
{
|
||||
BndLib_Add2dCurve::Add(aC2D, deb, fin, 0.0, aBond);
|
||||
}
|
||||
anUseBndBox = !aBond.IsVoid();
|
||||
if (anUseBndBox)
|
||||
{
|
||||
aBond.SetGap(aTolZ);
|
||||
aPntF = L.Location();
|
||||
}
|
||||
}
|
||||
//
|
||||
Geom2dAdaptor_Curve C(aC2D, deb, fin);
|
||||
@@ -448,9 +468,14 @@ void BRepClass_Intersector::LocalGeometry(const BRepClass_Edge& E,
|
||||
gp_Dir2d& Norm,
|
||||
double& C) const
|
||||
{
|
||||
double fpar, lpar;
|
||||
occ::handle<Geom2d_Curve> aPCurve = BRep_Tool::CurveOnSurface(E.Edge(), E.Face(), fpar, lpar);
|
||||
GeomLProp_CLProps2d Prop(aPCurve, U, 2, Precision::PConfusion());
|
||||
double fpar = E.FirstParameter();
|
||||
double lpar = E.LastParameter();
|
||||
occ::handle<Geom2d_Curve> aPCurve = E.Curve();
|
||||
if (aPCurve.IsNull())
|
||||
{
|
||||
aPCurve = BRep_Tool::CurveOnSurface(E.Edge(), E.Face(), fpar, lpar);
|
||||
}
|
||||
GeomLProp_CLProps2d Prop(aPCurve, U, 2, Precision::PConfusion());
|
||||
|
||||
C = 0.;
|
||||
if (Prop.IsTangentDefined())
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <BRepAdaptor_Curve2d.hxx>
|
||||
#include <BRepAdaptor_Surface.hxx>
|
||||
#include <BRepClass_FaceClassifier.hxx>
|
||||
#include <BRepClass_FaceExplorer.hxx>
|
||||
#include <BRepTools_WireExplorer.hxx>
|
||||
#include <BRepTopAdaptor_FClass2d.hxx>
|
||||
#include <CSLib_Class2d.hxx>
|
||||
@@ -31,8 +32,7 @@
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
#include <Precision.hxx>
|
||||
#include <NCollection_Array1.hxx>
|
||||
#include <NCollection_Sequence.hxx>
|
||||
#include <NCollection_LinearVector.hxx>
|
||||
#include <TopAbs_Orientation.hxx>
|
||||
#include <TopExp.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
@@ -40,12 +40,22 @@
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
//! Returns true for representable values, including OCCT's finite infinity sentinels.
|
||||
inline bool isRepresentableValue(const double theValue)
|
||||
{
|
||||
constexpr double THE_MAX_VALUE = std::numeric_limits<double>::max();
|
||||
return theValue >= -THE_MAX_VALUE && theValue <= THE_MAX_VALUE;
|
||||
}
|
||||
|
||||
// Increments @p theValue by @p theIncrement towards @p theDirection, ensuring that the result is
|
||||
// different from @p theValue. For large values of theValue with small theIncrement the result of
|
||||
// theValue + theIncrement can be equal to theValue due to the limited resolution of double
|
||||
@@ -83,11 +93,59 @@ bool isDegenerated(const BRepAdaptor_Curve& theCurve,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
struct PolygonMetrics
|
||||
{
|
||||
double Area = 0.0;
|
||||
double Perimeter = 0.0;
|
||||
};
|
||||
|
||||
PolygonMetrics polygonMetrics(const NCollection_LinearVector<gp_Pnt2d>& thePoints)
|
||||
{
|
||||
PolygonMetrics aMetrics;
|
||||
if (thePoints.Size() < 2)
|
||||
{
|
||||
return aMetrics;
|
||||
}
|
||||
|
||||
const size_t aLastUnique = thePoints.Size() - 2;
|
||||
size_t aPrevious = aLastUnique;
|
||||
for (size_t aCurrent = 0; aCurrent <= aLastUnique; ++aCurrent)
|
||||
{
|
||||
const gp_Pnt2d& aCurrentPoint = thePoints[aCurrent];
|
||||
const gp_Pnt2d& aPreviousPoint = thePoints[aPrevious];
|
||||
aMetrics.Area +=
|
||||
(aCurrentPoint.X() - aPreviousPoint.X()) * (aCurrentPoint.Y() + aPreviousPoint.Y()) * 0.5;
|
||||
aMetrics.Perimeter += (aCurrentPoint.XY() - aPreviousPoint.XY()).Modulus();
|
||||
aPrevious = aCurrent;
|
||||
}
|
||||
return aMetrics;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
bool expectedThickness(const PolygonMetrics& theMetrics, double& theThickness)
|
||||
{
|
||||
if (!isRepresentableValue(theMetrics.Area) || !isRepresentableValue(theMetrics.Perimeter)
|
||||
|| theMetrics.Perimeter <= 0.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
theThickness = std::max(2.0 * (std::abs(theMetrics.Area) / theMetrics.Perimeter), 1.e-7);
|
||||
return isRepresentableValue(theThickness);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const double TolUV)
|
||||
: Toluv(TolUV),
|
||||
Face(aFace),
|
||||
myIsUPeriodic(false),
|
||||
myIsVPeriodic(false),
|
||||
myUPeriod(0.0),
|
||||
myVPeriod(0.0),
|
||||
U1(0.0),
|
||||
V1(0.0),
|
||||
U2(0.0),
|
||||
@@ -96,8 +154,11 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
//-- dead end on surfaces defined on more than one period
|
||||
|
||||
Face.Orientation(TopAbs_FORWARD);
|
||||
occ::handle<BRepAdaptor_Surface> surf = new BRepAdaptor_Surface();
|
||||
surf->Initialize(aFace, false);
|
||||
BRepAdaptor_Surface aSurface(aFace, false);
|
||||
myIsUPeriodic = aSurface.IsUPeriodic();
|
||||
myIsVPeriodic = aSurface.IsVPeriodic();
|
||||
myUPeriod = myIsUPeriodic ? aSurface.UPeriod() : 0.0;
|
||||
myVPeriod = myIsVPeriodic ? aSurface.VPeriod() : 0.0;
|
||||
|
||||
TopoDS_Edge edge;
|
||||
TopAbs_Orientation Or;
|
||||
@@ -112,13 +173,13 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
for (TopExp_Explorer aFaceExplorer(Face, TopAbs_WIRE); (aFaceExplorer.More() && !anIsBadWire);
|
||||
aFaceExplorer.Next())
|
||||
{
|
||||
int nbpnts = 0;
|
||||
NCollection_Sequence<gp_Pnt2d> SeqPnt2d;
|
||||
int firstpoint = 1;
|
||||
double FlecheU = 0.0;
|
||||
double FlecheV = 0.0;
|
||||
bool WireIsNotEmpty = false;
|
||||
int NbEdges = 0;
|
||||
int nbpnts = 0;
|
||||
NCollection_LinearVector<gp_Pnt2d> SeqPnt2d;
|
||||
int firstpoint = 1;
|
||||
double FlecheU = 0.0;
|
||||
double FlecheV = 0.0;
|
||||
bool WireIsNotEmpty = false;
|
||||
int NbEdges = 0;
|
||||
|
||||
TopExp_Explorer Explorer;
|
||||
for (Explorer.Init(aFaceExplorer.Current(), TopAbs_EDGE); Explorer.More(); Explorer.Next())
|
||||
@@ -261,14 +322,14 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
//",nbpnts,u,FlecheU,FlecheV,ii,Avant);
|
||||
// if(ii>(Avant+4))
|
||||
// Modified by Sergey KHROMOV - Fri Apr 19 09:46:12 2002 Begin
|
||||
if (ii > (Avant + 4) && SeqPnt2d(ii - 2).SquareDistance(SeqPnt2d(ii)))
|
||||
if (ii > (Avant + 4) && SeqPnt2d[ii - 3].SquareDistance(SeqPnt2d[ii - 1]))
|
||||
// Modified by Sergey KHROMOV - Fri Apr 19 09:46:13 2002 End
|
||||
{
|
||||
gp_Lin2d Lin(SeqPnt2d(ii - 2), gp_Dir2d(gp_Vec2d(SeqPnt2d(ii - 2), SeqPnt2d(ii))));
|
||||
double ul = ElCLib::Parameter(Lin, SeqPnt2d(ii - 1));
|
||||
gp_Lin2d Lin(SeqPnt2d[ii - 3], gp_Dir2d(gp_Vec2d(SeqPnt2d[ii - 3], SeqPnt2d[ii - 1])));
|
||||
double ul = ElCLib::Parameter(Lin, SeqPnt2d[ii - 2]);
|
||||
gp_Pnt2d Pp = ElCLib::Value(ul, Lin);
|
||||
double dU = std::abs(Pp.X() - SeqPnt2d(ii - 1).X());
|
||||
double dV = std::abs(Pp.Y() - SeqPnt2d(ii - 1).Y());
|
||||
double dU = std::abs(Pp.X() - SeqPnt2d[ii - 2].X());
|
||||
double dV = std::abs(Pp.Y() - SeqPnt2d[ii - 2].Y());
|
||||
//-- printf(" (du=%7.5g dv=%7.5g)",dU,dV);
|
||||
if (dU > FlecheU)
|
||||
{
|
||||
@@ -290,19 +351,15 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
|
||||
if (NbEdges)
|
||||
{ //-- on compte ++ with a normal explorer and with the Wire Explorer
|
||||
NCollection_Array1<gp_Pnt2d> PClass(1, 2);
|
||||
//// modified by jgv, 28.04.2009 ////
|
||||
PClass.Init(gp_Pnt2d(0., 0.));
|
||||
/////////////////////////////////////
|
||||
TabClass.Append(CSLib_Class2d(PClass, FlecheU, FlecheV, Umin, Vmin, Umax, Vmax));
|
||||
NCollection_LinearVector<gp_Pnt2d> aPoints(2, gp_Pnt2d(0.0, 0.0));
|
||||
TabClass.Append(CSLib_Class2d(aPoints.ToArray1(), FlecheU, FlecheV, Umin, Vmin, Umax, Vmax));
|
||||
anIsBadWire = true;
|
||||
TabOrien.Append(-1);
|
||||
TabOrien.Append(WireRole::Invalid);
|
||||
}
|
||||
else if (WireIsNotEmpty)
|
||||
{
|
||||
// double anglep=0,anglem=0;
|
||||
NCollection_Array1<gp_Pnt2d> PClass(1, nbpnts);
|
||||
double square = 0.0;
|
||||
double aSignedArea = 0.0;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
//-- ** The mode of calculation was somewhat changed
|
||||
@@ -314,45 +371,30 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
|
||||
if (nbpnts > 3)
|
||||
{
|
||||
// int im2=nbpnts-2;
|
||||
int im1 = nbpnts - 1;
|
||||
int im0 = 1;
|
||||
// PClass(im2)=SeqPnt2d.Value(im2);
|
||||
PClass(im1) = SeqPnt2d.Value(im1);
|
||||
PClass(nbpnts) = SeqPnt2d.Value(nbpnts);
|
||||
|
||||
double aPer = 0.;
|
||||
// for(int ii=1; ii<nbpnts; ii++,im0++,im1++,im2++)
|
||||
for (int ii = 1; ii < nbpnts; ii++, im0++, im1++)
|
||||
PolygonMetrics aMetrics = polygonMetrics(SeqPnt2d);
|
||||
aSignedArea = aMetrics.Area;
|
||||
double anExpThick = 0.0;
|
||||
if (!expectedThickness(aMetrics, anExpThick))
|
||||
{
|
||||
// if(im2>=nbpnts) im2=1;
|
||||
if (im1 >= nbpnts)
|
||||
{
|
||||
im1 = 1;
|
||||
}
|
||||
PClass(ii) = SeqPnt2d.Value(ii);
|
||||
// gp_Vec2d A(PClass(im2),PClass(im1));
|
||||
// gp_Vec2d B(PClass(im1),PClass(im0));
|
||||
// double N = A.Magnitude() * B.Magnitude();
|
||||
|
||||
square += (PClass(im0).X() - PClass(im1).X()) * (PClass(im0).Y() + PClass(im1).Y()) * .5;
|
||||
aPer += (PClass(im0).XY() - PClass(im1).XY()).Modulus();
|
||||
|
||||
// if(N>1e-16){ double a=A.Angle(B); angle+=a; }
|
||||
anIsBadWire = true;
|
||||
TabOrien.Append(WireRole::Invalid);
|
||||
NCollection_LinearVector<gp_Pnt2d> aFallbackPoints(2, gp_Pnt2d(0.0, 0.0));
|
||||
TabClass.Append(
|
||||
CSLib_Class2d(aFallbackPoints.ToArray1(), FlecheU, FlecheV, Umin, Vmin, Umax, Vmax));
|
||||
continue;
|
||||
}
|
||||
|
||||
double anExpThick = std::max(2. * std::abs(square) / aPer, 1e-7);
|
||||
double aDefl = std::max(FlecheU, FlecheV);
|
||||
double aDiscrDefl = std::min(aDefl * 0.1, anExpThick * 10.);
|
||||
while (aDefl > anExpThick && aDiscrDefl > 1e-7)
|
||||
{
|
||||
// Deflection of the polygon is too much for this ratio of area and perimeter,
|
||||
// and this might lead to self-intersections.
|
||||
// Discretize the wire more tightly to eliminate the error.
|
||||
firstpoint = 1;
|
||||
SeqPnt2d.Clear();
|
||||
FlecheU = 0.0;
|
||||
FlecheV = 0.0;
|
||||
// Build tighter samples separately so a failed edge leaves the coarse polygon intact.
|
||||
NCollection_LinearVector<gp_Pnt2d> aRefinedPoints;
|
||||
int aRefinedFirstPoint = 1;
|
||||
double aRefinedFlecheU = 0.0;
|
||||
double aRefinedFlecheV = 0.0;
|
||||
bool isRefinementDone = true;
|
||||
for (WireExplorer.Init(TopoDS::Wire(aFaceExplorer.Current()), Face); WireExplorer.More();
|
||||
WireExplorer.Next())
|
||||
{
|
||||
@@ -370,6 +412,7 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
GCPnts_QuasiUniformDeflection aDiscr(C, aDiscrDefl);
|
||||
if (!aDiscr.IsDone())
|
||||
{
|
||||
isRefinementDone = false;
|
||||
break;
|
||||
}
|
||||
int nbp = aDiscr.NbPoints();
|
||||
@@ -380,69 +423,64 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
i = nbp;
|
||||
iEnd = 0;
|
||||
}
|
||||
if (firstpoint == 2)
|
||||
if (aRefinedFirstPoint == 2)
|
||||
{
|
||||
i += iStep;
|
||||
}
|
||||
for (; i != iEnd; i += iStep)
|
||||
{
|
||||
gp_Pnt2d aP2d = C.Value(aDiscr.Parameter(i));
|
||||
SeqPnt2d.Append(aP2d);
|
||||
aRefinedPoints.Append(C.Value(aDiscr.Parameter(i)));
|
||||
}
|
||||
if (nbp > 2)
|
||||
{
|
||||
int ii = SeqPnt2d.Length();
|
||||
gp_Lin2d Lin(SeqPnt2d(ii - 2), gp_Dir2d(gp_Vec2d(SeqPnt2d(ii - 2), SeqPnt2d(ii))));
|
||||
double ul = ElCLib::Parameter(Lin, SeqPnt2d(ii - 1));
|
||||
gp_Pnt2d Pp = ElCLib::Value(ul, Lin);
|
||||
double dU = std::abs(Pp.X() - SeqPnt2d(ii - 1).X());
|
||||
double dV = std::abs(Pp.Y() - SeqPnt2d(ii - 1).Y());
|
||||
if (dU > FlecheU)
|
||||
const size_t ii = aRefinedPoints.Size();
|
||||
gp_Lin2d Lin(aRefinedPoints[ii - 3],
|
||||
gp_Dir2d(gp_Vec2d(aRefinedPoints[ii - 3], aRefinedPoints[ii - 1])));
|
||||
double ul = ElCLib::Parameter(Lin, aRefinedPoints[ii - 2]);
|
||||
gp_Pnt2d Pp = ElCLib::Value(ul, Lin);
|
||||
double dU = std::abs(Pp.X() - aRefinedPoints[ii - 2].X());
|
||||
double dV = std::abs(Pp.Y() - aRefinedPoints[ii - 2].Y());
|
||||
if (dU > aRefinedFlecheU)
|
||||
{
|
||||
FlecheU = dU;
|
||||
aRefinedFlecheU = dU;
|
||||
}
|
||||
if (dV > FlecheV)
|
||||
if (dV > aRefinedFlecheV)
|
||||
{
|
||||
FlecheV = dV;
|
||||
aRefinedFlecheV = dV;
|
||||
}
|
||||
}
|
||||
firstpoint = 2;
|
||||
aRefinedFirstPoint = 2;
|
||||
}
|
||||
}
|
||||
nbpnts = SeqPnt2d.Length();
|
||||
PClass.Resize(1, nbpnts, false);
|
||||
im1 = nbpnts - 1;
|
||||
im0 = 1;
|
||||
PClass(im1) = SeqPnt2d.Value(im1);
|
||||
PClass(nbpnts) = SeqPnt2d.Value(nbpnts);
|
||||
square = 0.;
|
||||
aPer = 0.;
|
||||
for (int ii = 1; ii < nbpnts; ii++, im0++, im1++)
|
||||
{
|
||||
if (im1 >= nbpnts)
|
||||
{
|
||||
im1 = 1;
|
||||
}
|
||||
PClass(ii) = SeqPnt2d.Value(ii);
|
||||
square +=
|
||||
(PClass(im0).X() - PClass(im1).X()) * (PClass(im0).Y() + PClass(im1).Y()) * .5;
|
||||
aPer += (PClass(im0).XY() - PClass(im1).XY()).Modulus();
|
||||
}
|
||||
|
||||
anExpThick = std::max(2. * std::abs(square) / aPer, 1e-7);
|
||||
aDefl = std::max(FlecheU, FlecheV);
|
||||
aDiscrDefl = std::min(aDiscrDefl * 0.1, anExpThick * 10.);
|
||||
const PolygonMetrics aRefinedMetrics = polygonMetrics(aRefinedPoints);
|
||||
double aRefinedThickness = 0.0;
|
||||
if (!isRefinementDone || aRefinedPoints.Size() <= 3
|
||||
|| !expectedThickness(aRefinedMetrics, aRefinedThickness))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
SeqPnt2d = std::move(aRefinedPoints);
|
||||
nbpnts = static_cast<int>(SeqPnt2d.Size());
|
||||
FlecheU = aRefinedFlecheU;
|
||||
FlecheV = aRefinedFlecheV;
|
||||
aMetrics = aRefinedMetrics;
|
||||
aSignedArea = aMetrics.Area;
|
||||
anExpThick = aRefinedThickness;
|
||||
aDefl = std::max(FlecheU, FlecheV);
|
||||
aDiscrDefl = std::min(aDiscrDefl * 0.1, anExpThick * 10.);
|
||||
}
|
||||
|
||||
//-- FlecheU*=10.0;
|
||||
//-- FlecheV*=10.0;
|
||||
if (aNbE == 1 && FlecheU < eps && FlecheV < eps && std::abs(square) < eps)
|
||||
if (aNbE == 1 && FlecheU < eps && FlecheV < eps && std::abs(aSignedArea) < eps)
|
||||
{
|
||||
TabOrien.Append(1);
|
||||
TabOrien.Append(WireRole::Outer);
|
||||
}
|
||||
else
|
||||
{
|
||||
TabOrien.Append(((square < 0.0) ? 1 : 0));
|
||||
TabOrien.Append(aSignedArea < 0.0 ? WireRole::Outer : WireRole::Inner);
|
||||
}
|
||||
|
||||
if (FlecheU < Toluv)
|
||||
@@ -453,33 +491,32 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
{
|
||||
FlecheV = Toluv;
|
||||
}
|
||||
TabClass.Append(CSLib_Class2d(PClass, FlecheU, FlecheV, Umin, Vmin, Umax, Vmax));
|
||||
TabClass.Append(
|
||||
CSLib_Class2d(SeqPnt2d.ToArray1(), FlecheU, FlecheV, Umin, Vmin, Umax, Vmax));
|
||||
} // if(nbpoints>3
|
||||
else
|
||||
{
|
||||
anIsBadWire = true;
|
||||
TabOrien.Append(-1);
|
||||
NCollection_Array1<gp_Pnt2d> xPClass(1, 2);
|
||||
xPClass(1) = SeqPnt2d(1);
|
||||
xPClass(2) = SeqPnt2d(2);
|
||||
TabClass.Append(CSLib_Class2d(xPClass, FlecheU, FlecheV, Umin, Vmin, Umax, Vmax));
|
||||
TabOrien.Append(WireRole::Invalid);
|
||||
TabClass.Append(
|
||||
CSLib_Class2d(SeqPnt2d.ToArray1(), FlecheU, FlecheV, Umin, Vmin, Umax, Vmax));
|
||||
}
|
||||
} // else if(WareIsNotEmpty
|
||||
} // for(FaceExplorer
|
||||
|
||||
int nbtabclass = TabClass.Length();
|
||||
const size_t nbtabclass = TabClass.Size();
|
||||
|
||||
if (nbtabclass > 0)
|
||||
{
|
||||
//-- If an error was detected on a wire: set all TabOrien to -1
|
||||
if (anIsBadWire)
|
||||
{
|
||||
TabOrien(1) = -1;
|
||||
TabOrien[0] = WireRole::Invalid;
|
||||
}
|
||||
|
||||
if (surf->GetType() == GeomAbs_Cone || surf->GetType() == GeomAbs_Cylinder
|
||||
|| surf->GetType() == GeomAbs_Torus || surf->GetType() == GeomAbs_Sphere
|
||||
|| surf->GetType() == GeomAbs_SurfaceOfRevolution)
|
||||
if (aSurface.GetType() == GeomAbs_Cone || aSurface.GetType() == GeomAbs_Cylinder
|
||||
|| aSurface.GetType() == GeomAbs_Torus || aSurface.GetType() == GeomAbs_Sphere
|
||||
|| aSurface.GetType() == GeomAbs_SurfaceOfRevolution)
|
||||
|
||||
{
|
||||
double uuu = M_PI + M_PI - (Umax - Umin);
|
||||
@@ -495,7 +532,7 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
U1 = U2 = 0.0;
|
||||
}
|
||||
|
||||
if (surf->GetType() == GeomAbs_Torus)
|
||||
if (aSurface.GetType() == GeomAbs_Torus)
|
||||
{
|
||||
double uuu = M_PI + M_PI - (Vmax - Vmin);
|
||||
if (uuu < 0)
|
||||
@@ -512,6 +549,15 @@ BRepTopAdaptor_FClass2d::BRepTopAdaptor_FClass2d(const TopoDS_Face& aFace, const
|
||||
}
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
BRepTopAdaptor_FClass2d::~BRepTopAdaptor_FClass2d()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
TopAbs_State BRepTopAdaptor_FClass2d::PerformInfinitePoint() const
|
||||
{
|
||||
if (Umax == -RealLast() || Vmax == -RealLast() || Umin == RealLast() || Vmin == RealLast())
|
||||
@@ -522,338 +568,196 @@ TopAbs_State BRepTopAdaptor_FClass2d::PerformInfinitePoint() const
|
||||
return (Perform(P, false));
|
||||
}
|
||||
|
||||
TopAbs_State BRepTopAdaptor_FClass2d::Perform(const gp_Pnt2d& _Puv,
|
||||
const bool RecadreOnPeriodic) const
|
||||
{
|
||||
int dedans;
|
||||
int nbtabclass = TabClass.Length();
|
||||
//=================================================================================================
|
||||
|
||||
if (nbtabclass == 0)
|
||||
TopAbs_State BRepTopAdaptor_FClass2d::exactState(const gp_Pnt2d& thePoint,
|
||||
const double theTolerance) const
|
||||
{
|
||||
std::lock_guard<std::mutex> aLock(myExactMutex);
|
||||
if (!myExactExplorer)
|
||||
{
|
||||
return (TopAbs_IN);
|
||||
myExactExplorer = std::make_unique<BRepClass_FaceExplorer>(Face);
|
||||
myExactExplorer->SetUseBndBox(myExactExplorer->ShouldUseBndBox());
|
||||
}
|
||||
BRepClass_FaceClassifier aClassifier(*myExactExplorer, thePoint, theTolerance);
|
||||
return aClassifier.State();
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
TopAbs_State BRepTopAdaptor_FClass2d::classify(const gp_Pnt2d& thePoint,
|
||||
const double theTolerance,
|
||||
const bool theRecadreOnPeriodic,
|
||||
const ClassificationMode theMode) const
|
||||
{
|
||||
const size_t aClassifierCount = TabClass.Size();
|
||||
if (aClassifierCount == 0)
|
||||
{
|
||||
return TopAbs_IN;
|
||||
}
|
||||
|
||||
//-- U1 is the First Param and U2 in this case is U1+Period
|
||||
double u = _Puv.X();
|
||||
double v = _Puv.Y();
|
||||
double uu = u, vv = v;
|
||||
double aU = thePoint.X();
|
||||
double aV = thePoint.Y();
|
||||
double aReframedU = aU;
|
||||
double aReframedV = aV;
|
||||
bool isUReframed = false;
|
||||
bool isVReframed = false;
|
||||
|
||||
occ::handle<BRepAdaptor_Surface> surf = new BRepAdaptor_Surface();
|
||||
surf->Initialize(Face, false);
|
||||
const bool IsUPer = surf->IsUPeriodic();
|
||||
const bool IsVPer = surf->IsVPeriodic();
|
||||
const double uperiod = IsUPer ? surf->UPeriod() : 0.0;
|
||||
const double vperiod = IsVPer ? surf->VPeriod() : 0.0;
|
||||
TopAbs_State aStatus = TopAbs_UNKNOWN;
|
||||
bool urecadre = false, vrecadre = false;
|
||||
|
||||
if (RecadreOnPeriodic)
|
||||
if (theRecadreOnPeriodic)
|
||||
{
|
||||
if (IsUPer)
|
||||
if (myIsUPeriodic)
|
||||
{
|
||||
if (uu < Umin)
|
||||
if (aReframedU < Umin)
|
||||
{
|
||||
while (uu < Umin)
|
||||
while (aReframedU < Umin)
|
||||
{
|
||||
uu += uperiod;
|
||||
aReframedU += myUPeriod;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (uu >= Umin)
|
||||
while (aReframedU >= Umin)
|
||||
{
|
||||
uu -= uperiod;
|
||||
aReframedU -= myUPeriod;
|
||||
}
|
||||
uu += uperiod;
|
||||
aReframedU += myUPeriod;
|
||||
}
|
||||
}
|
||||
if (IsVPer)
|
||||
if (myIsVPeriodic)
|
||||
{
|
||||
if (vv < Vmin)
|
||||
if (aReframedV < Vmin)
|
||||
{
|
||||
while (vv < Vmin)
|
||||
while (aReframedV < Vmin)
|
||||
{
|
||||
vv += vperiod;
|
||||
aReframedV += myVPeriod;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (vv >= Vmin)
|
||||
while (aReframedV >= Vmin)
|
||||
{
|
||||
vv -= vperiod;
|
||||
aReframedV -= myVPeriod;
|
||||
}
|
||||
vv += vperiod;
|
||||
aReframedV += myVPeriod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TopAbs_State aState = TopAbs_UNKNOWN;
|
||||
for (;;)
|
||||
{
|
||||
dedans = 1;
|
||||
gp_Pnt2d Puv(u, v);
|
||||
const gp_Pnt2d aPoint(aU, aV);
|
||||
|
||||
if (TabOrien(1) != -1)
|
||||
if (TabOrien[0] != WireRole::Invalid)
|
||||
{
|
||||
for (int n = 1; n <= nbtabclass; n++)
|
||||
CSLib_Class2d::Result aPolygonResult = CSLib_Class2d::Result_Inside;
|
||||
for (size_t aWireIndex = 0; aWireIndex < aClassifierCount; ++aWireIndex)
|
||||
{
|
||||
int cur = TabClass(n).SiDans(Puv);
|
||||
if (cur == 1)
|
||||
const int aWireResult = theMode == ClassificationMode::Perform
|
||||
? TabClass[aWireIndex].SiDans(aPoint)
|
||||
: TabClass[aWireIndex].SiDans_OnMode(aPoint, theTolerance);
|
||||
if (aWireResult == CSLib_Class2d::Result_Inside)
|
||||
{
|
||||
if (TabOrien(n) == 0)
|
||||
if (TabOrien[aWireIndex] == WireRole::Inner)
|
||||
{
|
||||
dedans = -1;
|
||||
aPolygonResult = CSLib_Class2d::Result_Outside;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (cur == -1)
|
||||
else if (aWireResult == CSLib_Class2d::Result_Outside)
|
||||
{
|
||||
if (TabOrien(n) == 1)
|
||||
if (TabOrien[aWireIndex] == WireRole::Outer)
|
||||
{
|
||||
dedans = -1;
|
||||
aPolygonResult = CSLib_Class2d::Result_Outside;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dedans = 0;
|
||||
aPolygonResult = CSLib_Class2d::Result_Uncertain;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dedans == 0)
|
||||
|
||||
if (aPolygonResult == CSLib_Class2d::Result_Uncertain)
|
||||
{
|
||||
BRepClass_FaceClassifier aClassifier;
|
||||
double m_Toluv = (Toluv > 4.0) ? 4.0 : Toluv;
|
||||
// aClassifier.Perform(Face,Puv,Toluv);
|
||||
aClassifier.Perform(Face, Puv, m_Toluv);
|
||||
aStatus = aClassifier.State();
|
||||
aState = theMode == ClassificationMode::Perform ? exactState(aPoint, std::min(Toluv, 4.0))
|
||||
: TopAbs_ON;
|
||||
}
|
||||
if (dedans == 1)
|
||||
else
|
||||
{
|
||||
aStatus = TopAbs_IN;
|
||||
}
|
||||
if (dedans == -1)
|
||||
{
|
||||
aStatus = TopAbs_OUT;
|
||||
aState = aPolygonResult == CSLib_Class2d::Result_Inside ? TopAbs_IN : TopAbs_OUT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ //-- TabOrien(1)=-1 False Wire
|
||||
BRepClass_FaceClassifier aClassifier;
|
||||
aClassifier.Perform(Face, Puv, Toluv);
|
||||
aStatus = aClassifier.State();
|
||||
{
|
||||
// A malformed wire cannot be classified reliably by its polygon.
|
||||
aState = exactState(aPoint, theMode == ClassificationMode::Perform ? Toluv : theTolerance);
|
||||
}
|
||||
|
||||
if (!RecadreOnPeriodic || (!IsUPer && !IsVPer))
|
||||
if (!theRecadreOnPeriodic || (!myIsUPeriodic && !myIsVPeriodic))
|
||||
{
|
||||
return aStatus;
|
||||
return aState;
|
||||
}
|
||||
if (aStatus == TopAbs_IN || aStatus == TopAbs_ON)
|
||||
if (aState == TopAbs_IN || aState == TopAbs_ON)
|
||||
{
|
||||
return aStatus;
|
||||
return aState;
|
||||
}
|
||||
|
||||
if (!urecadre)
|
||||
if (!isUReframed)
|
||||
{
|
||||
u = uu;
|
||||
urecadre = true;
|
||||
aU = aReframedU;
|
||||
isUReframed = true;
|
||||
}
|
||||
else if (IsUPer)
|
||||
else if (myIsUPeriodic)
|
||||
{
|
||||
u += uperiod;
|
||||
aU += myUPeriod;
|
||||
}
|
||||
if (u > Umax || !IsUPer)
|
||||
if (aU > Umax || !myIsUPeriodic)
|
||||
{
|
||||
if (!vrecadre)
|
||||
if (!isVReframed)
|
||||
{
|
||||
v = vv;
|
||||
vrecadre = true;
|
||||
aV = aReframedV;
|
||||
isVReframed = true;
|
||||
}
|
||||
else if (IsVPer)
|
||||
else if (myIsVPeriodic)
|
||||
{
|
||||
v += vperiod;
|
||||
aV += myVPeriod;
|
||||
}
|
||||
|
||||
u = uu;
|
||||
aU = aReframedU;
|
||||
|
||||
if (v > Vmax || !IsVPer)
|
||||
if (aV > Vmax || !myIsVPeriodic)
|
||||
{
|
||||
return aStatus;
|
||||
return aState;
|
||||
}
|
||||
}
|
||||
} // for (;;)
|
||||
}
|
||||
}
|
||||
|
||||
TopAbs_State BRepTopAdaptor_FClass2d::TestOnRestriction(const gp_Pnt2d& _Puv,
|
||||
const double Tol,
|
||||
const bool RecadreOnPeriodic) const
|
||||
//=================================================================================================
|
||||
|
||||
TopAbs_State BRepTopAdaptor_FClass2d::Perform(const gp_Pnt2d& thePoint,
|
||||
const bool theRecadreOnPeriodic) const
|
||||
{
|
||||
int dedans;
|
||||
int nbtabclass = TabClass.Length();
|
||||
|
||||
if (nbtabclass == 0)
|
||||
{
|
||||
return (TopAbs_IN);
|
||||
}
|
||||
|
||||
//-- U1 is the First Param and U2 in this case is U1+Period
|
||||
double u = _Puv.X();
|
||||
double v = _Puv.Y();
|
||||
double uu = u, vv = v;
|
||||
|
||||
occ::handle<BRepAdaptor_Surface> surf = new BRepAdaptor_Surface();
|
||||
surf->Initialize(Face, false);
|
||||
const bool IsUPer = surf->IsUPeriodic();
|
||||
const bool IsVPer = surf->IsVPeriodic();
|
||||
const double uperiod = IsUPer ? surf->UPeriod() : 0.0;
|
||||
const double vperiod = IsVPer ? surf->VPeriod() : 0.0;
|
||||
TopAbs_State aStatus = TopAbs_UNKNOWN;
|
||||
bool urecadre = false, vrecadre = false;
|
||||
|
||||
if (RecadreOnPeriodic)
|
||||
{
|
||||
if (IsUPer)
|
||||
{
|
||||
if (uu < Umin)
|
||||
{
|
||||
while (uu < Umin)
|
||||
{
|
||||
uu += uperiod;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (uu >= Umin)
|
||||
{
|
||||
uu -= uperiod;
|
||||
}
|
||||
uu += uperiod;
|
||||
}
|
||||
}
|
||||
if (IsVPer)
|
||||
{
|
||||
if (vv < Vmin)
|
||||
{
|
||||
while (vv < Vmin)
|
||||
{
|
||||
vv += vperiod;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (vv >= Vmin)
|
||||
{
|
||||
vv -= vperiod;
|
||||
}
|
||||
vv += vperiod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
dedans = 1;
|
||||
gp_Pnt2d Puv(u, v);
|
||||
|
||||
if (TabOrien(1) != -1)
|
||||
{
|
||||
for (int n = 1; n <= nbtabclass; n++)
|
||||
{
|
||||
int cur = TabClass(n).SiDans_OnMode(Puv, Tol);
|
||||
if (cur == 1)
|
||||
{
|
||||
if (TabOrien(n) == 0)
|
||||
{
|
||||
dedans = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (cur == -1)
|
||||
{
|
||||
if (TabOrien(n) == 1)
|
||||
{
|
||||
dedans = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dedans = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dedans == 0)
|
||||
{
|
||||
aStatus = TopAbs_ON;
|
||||
}
|
||||
if (dedans == 1)
|
||||
{
|
||||
aStatus = TopAbs_IN;
|
||||
}
|
||||
if (dedans == -1)
|
||||
{
|
||||
aStatus = TopAbs_OUT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ //-- TabOrien(1)=-1 False Wire
|
||||
BRepClass_FaceClassifier aClassifier;
|
||||
aClassifier.Perform(Face, Puv, Tol);
|
||||
aStatus = aClassifier.State();
|
||||
}
|
||||
|
||||
if (!RecadreOnPeriodic || (!IsUPer && !IsVPer))
|
||||
{
|
||||
return aStatus;
|
||||
}
|
||||
if (aStatus == TopAbs_IN || aStatus == TopAbs_ON)
|
||||
{
|
||||
return aStatus;
|
||||
}
|
||||
|
||||
if (!urecadre)
|
||||
{
|
||||
u = uu;
|
||||
urecadre = true;
|
||||
}
|
||||
else if (IsUPer)
|
||||
{
|
||||
u += uperiod;
|
||||
}
|
||||
if (u > Umax || !IsUPer)
|
||||
{
|
||||
if (!vrecadre)
|
||||
{
|
||||
v = vv;
|
||||
vrecadre = true;
|
||||
}
|
||||
else if (IsVPer)
|
||||
{
|
||||
v += vperiod;
|
||||
}
|
||||
|
||||
u = uu;
|
||||
|
||||
if (v > Vmax || !IsVPer)
|
||||
{
|
||||
return aStatus;
|
||||
}
|
||||
}
|
||||
} // for (;;)
|
||||
return classify(thePoint, Toluv, theRecadreOnPeriodic, ClassificationMode::Perform);
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
TopAbs_State BRepTopAdaptor_FClass2d::TestOnRestriction(const gp_Pnt2d& thePoint,
|
||||
const double theTolerance,
|
||||
const bool theRecadreOnPeriodic) const
|
||||
{
|
||||
return classify(thePoint, theTolerance, theRecadreOnPeriodic, ClassificationMode::OnRestriction);
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
|
||||
void BRepTopAdaptor_FClass2d::Destroy()
|
||||
{
|
||||
TabClass.Clear();
|
||||
}
|
||||
|
||||
#include <Standard_ConstructionError.hxx>
|
||||
|
||||
// const BRepTopAdaptor_FClass2d & BRepTopAdaptor_FClass2d::Copy(const BRepTopAdaptor_FClass2d&
|
||||
// Other) const {
|
||||
const BRepTopAdaptor_FClass2d& BRepTopAdaptor_FClass2d::Copy(const BRepTopAdaptor_FClass2d&) const
|
||||
{
|
||||
#ifdef OCCT_DEBUG
|
||||
std::cerr << "Copy not allowed in BRepTopAdaptor_FClass2d" << std::endl;
|
||||
#endif
|
||||
throw Standard_ConstructionError();
|
||||
TabClass.Clear(true);
|
||||
TabOrien.Clear(true);
|
||||
myExactExplorer.reset();
|
||||
}
|
||||
|
||||
@@ -21,11 +21,16 @@
|
||||
#include <Standard_DefineAlloc.hxx>
|
||||
|
||||
#include <CSLib_Class2d.hxx>
|
||||
#include <NCollection_Sequence.hxx>
|
||||
#include <NCollection_LinearVector.hxx>
|
||||
#include <Standard_Integer.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopAbs_State.hxx>
|
||||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
|
||||
class BRepClass_FaceExplorer;
|
||||
class gp_Pnt2d;
|
||||
|
||||
class BRepTopAdaptor_FClass2d
|
||||
@@ -35,6 +40,11 @@ public:
|
||||
|
||||
Standard_EXPORT BRepTopAdaptor_FClass2d(const TopoDS_Face& F, const double Tol);
|
||||
|
||||
BRepTopAdaptor_FClass2d(const BRepTopAdaptor_FClass2d&) = delete;
|
||||
BRepTopAdaptor_FClass2d(BRepTopAdaptor_FClass2d&&) = delete;
|
||||
BRepTopAdaptor_FClass2d& operator=(const BRepTopAdaptor_FClass2d&) = delete;
|
||||
BRepTopAdaptor_FClass2d& operator=(BRepTopAdaptor_FClass2d&&) = delete;
|
||||
|
||||
Standard_EXPORT TopAbs_State PerformInfinitePoint() const;
|
||||
|
||||
Standard_EXPORT TopAbs_State Perform(const gp_Pnt2d& Puv,
|
||||
@@ -42,14 +52,7 @@ public:
|
||||
|
||||
Standard_EXPORT void Destroy();
|
||||
|
||||
~BRepTopAdaptor_FClass2d() { Destroy(); }
|
||||
|
||||
Standard_EXPORT const BRepTopAdaptor_FClass2d& Copy(const BRepTopAdaptor_FClass2d& Other) const;
|
||||
|
||||
const BRepTopAdaptor_FClass2d& operator=(const BRepTopAdaptor_FClass2d& Other) const
|
||||
{
|
||||
return Copy(Other);
|
||||
}
|
||||
Standard_EXPORT ~BRepTopAdaptor_FClass2d();
|
||||
|
||||
//! Test a point with +- an offset (Tol) and returns
|
||||
//! On if some points are OUT an some are IN
|
||||
@@ -59,18 +62,45 @@ public:
|
||||
const bool RecadreOnPeriodic = true) const;
|
||||
|
||||
private:
|
||||
NCollection_Sequence<CSLib_Class2d> TabClass;
|
||||
NCollection_Sequence<int> TabOrien;
|
||||
double Toluv;
|
||||
TopoDS_Face Face;
|
||||
double U1;
|
||||
double V1;
|
||||
double U2;
|
||||
double V2;
|
||||
double Umin;
|
||||
double Umax;
|
||||
double Vmin;
|
||||
double Vmax;
|
||||
enum class ClassificationMode : uint8_t
|
||||
{
|
||||
Perform,
|
||||
OnRestriction
|
||||
};
|
||||
|
||||
enum class WireRole : int8_t
|
||||
{
|
||||
Invalid = -1,
|
||||
Inner = 0,
|
||||
Outer = 1
|
||||
};
|
||||
|
||||
TopAbs_State exactState(const gp_Pnt2d& thePoint, const double theTolerance) const;
|
||||
|
||||
TopAbs_State classify(const gp_Pnt2d& thePoint,
|
||||
const double theTolerance,
|
||||
const bool theRecadreOnPeriodic,
|
||||
const ClassificationMode theMode) const;
|
||||
|
||||
private:
|
||||
NCollection_LinearVector<CSLib_Class2d> TabClass;
|
||||
NCollection_LinearVector<WireRole> TabOrien;
|
||||
double Toluv;
|
||||
TopoDS_Face Face;
|
||||
mutable std::unique_ptr<BRepClass_FaceExplorer> myExactExplorer;
|
||||
mutable std::mutex myExactMutex;
|
||||
bool myIsUPeriodic;
|
||||
bool myIsVPeriodic;
|
||||
double myUPeriod;
|
||||
double myVPeriod;
|
||||
double U1;
|
||||
double V1;
|
||||
double U2;
|
||||
double V2;
|
||||
double Umin;
|
||||
double Umax;
|
||||
double Vmin;
|
||||
double Vmax;
|
||||
};
|
||||
|
||||
#endif // _BRepTopAdaptor_FClass2d_HeaderFile
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
// Copyright (c) 2026 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 <BRep_Tool.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepClass_Edge.hxx>
|
||||
#include <BRepClass_FClassifier.hxx>
|
||||
#include <BRepClass_FaceExplorer.hxx>
|
||||
#include <BRepClass_Intersector.hxx>
|
||||
#include <BRepPrimAPI_MakeSphere.hxx>
|
||||
#include <Geom2d_Curve.hxx>
|
||||
#include <Geom_Circle.hxx>
|
||||
#include <Geom_CylindricalSurface.hxx>
|
||||
#include <NCollection_IndexedDataMap.hxx>
|
||||
#include <NCollection_List.hxx>
|
||||
#include <NCollection_Sequence.hxx>
|
||||
#include <Precision.hxx>
|
||||
#include <TopExp.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <TopTools_ShapeMapHasher.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <gp.hxx>
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Dir2d.hxx>
|
||||
#include <gp_Lin2d.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
#include <gp_Vec2d.hxx>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
TopoDS_Wire makeRectangle(const double theXMin,
|
||||
const double theYMin,
|
||||
const double theXMax,
|
||||
const double theYMax)
|
||||
{
|
||||
const gp_Pnt aPoints[] = {gp_Pnt(theXMin, theYMin, 0.0),
|
||||
gp_Pnt(theXMax, theYMin, 0.0),
|
||||
gp_Pnt(theXMax, theYMax, 0.0),
|
||||
gp_Pnt(theXMin, theYMax, 0.0)};
|
||||
BRepBuilderAPI_MakeWire aWireBuilder;
|
||||
for (int anIndex = 0; anIndex < 4; ++anIndex)
|
||||
{
|
||||
BRepBuilderAPI_MakeEdge anEdgeBuilder(aPoints[anIndex], aPoints[(anIndex + 1) % 4]);
|
||||
if (!anEdgeBuilder.IsDone())
|
||||
{
|
||||
ADD_FAILURE() << "Edge creation failed";
|
||||
return TopoDS_Wire();
|
||||
}
|
||||
aWireBuilder.Add(anEdgeBuilder.Edge());
|
||||
}
|
||||
if (!aWireBuilder.IsDone())
|
||||
{
|
||||
ADD_FAILURE() << "Wire creation failed";
|
||||
return TopoDS_Wire();
|
||||
}
|
||||
return aWireBuilder.Wire();
|
||||
}
|
||||
|
||||
TopAbs_State classify(const TopoDS_Face& theFace, const gp_Pnt2d& thePoint, const bool theUseBndBox)
|
||||
{
|
||||
BRepClass_FaceExplorer anExplorer(theFace);
|
||||
anExplorer.SetUseBndBox(theUseBndBox);
|
||||
BRepClass_FClassifier aClassifier(anExplorer, thePoint, Precision::Confusion());
|
||||
return aClassifier.State();
|
||||
}
|
||||
|
||||
TopoDS_Edge referenceNextEdge(const TopoDS_Edge& theEdge, const TopoDS_Wire& theWire)
|
||||
{
|
||||
TopoDS_Vertex aFirstVertex;
|
||||
TopoDS_Vertex aLastVertex;
|
||||
TopExp::Vertices(theEdge, aFirstVertex, aLastVertex, true);
|
||||
if (aLastVertex.IsNull() || aLastVertex.IsSame(aFirstVertex))
|
||||
{
|
||||
return TopoDS_Edge();
|
||||
}
|
||||
|
||||
NCollection_IndexedDataMap<TopoDS_Shape, NCollection_List<TopoDS_Shape>, TopTools_ShapeMapHasher>
|
||||
aVertexEdges;
|
||||
TopExp::MapShapesAndAncestors(theWire, TopAbs_VERTEX, TopAbs_EDGE, aVertexEdges);
|
||||
const NCollection_List<TopoDS_Shape>* anEdges = aVertexEdges.Seek(aLastVertex);
|
||||
if (anEdges == nullptr || anEdges->Extent() != 2)
|
||||
{
|
||||
return TopoDS_Edge();
|
||||
}
|
||||
|
||||
TopoDS_Edge aNextEdge;
|
||||
for (NCollection_List<TopoDS_Shape>::Iterator anIt(*anEdges); anIt.More(); anIt.Next())
|
||||
{
|
||||
if (!anIt.Value().IsNull() && !anIt.Value().IsSame(theEdge))
|
||||
{
|
||||
aNextEdge = TopoDS::Edge(anIt.Value());
|
||||
}
|
||||
}
|
||||
return aNextEdge;
|
||||
}
|
||||
|
||||
void expectNextEdgesMatchReference(const TopoDS_Face& theFace)
|
||||
{
|
||||
BRepClass_FaceExplorer anExplorer(theFace);
|
||||
TopExp_Explorer aWireExp(theFace, TopAbs_WIRE);
|
||||
for (anExplorer.InitWires(); anExplorer.MoreWires(); anExplorer.NextWire(), aWireExp.Next())
|
||||
{
|
||||
ASSERT_TRUE(aWireExp.More());
|
||||
const TopoDS_Wire& aWire = TopoDS::Wire(aWireExp.Current());
|
||||
TopExp_Explorer anEdgeExp(aWire, TopAbs_EDGE);
|
||||
for (anExplorer.InitEdges(); anExplorer.MoreEdges(); anExplorer.NextEdge(), anEdgeExp.Next())
|
||||
{
|
||||
ASSERT_TRUE(anEdgeExp.More());
|
||||
BRepClass_Edge anEdgeData;
|
||||
TopAbs_Orientation anOrientation = TopAbs_EXTERNAL;
|
||||
anExplorer.CurrentEdge(anEdgeData, anOrientation);
|
||||
const TopoDS_Edge& anExpectedEdge = TopoDS::Edge(anEdgeExp.Current());
|
||||
ASSERT_TRUE(anEdgeData.Edge().IsEqual(anExpectedEdge));
|
||||
|
||||
const TopoDS_Edge aExpectedNext = referenceNextEdge(anExpectedEdge, aWire);
|
||||
if (aExpectedNext.IsNull())
|
||||
{
|
||||
EXPECT_TRUE(anEdgeData.NextEdge().IsNull());
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_TRUE(anEdgeData.NextEdge().IsEqual(aExpectedNext));
|
||||
}
|
||||
}
|
||||
EXPECT_FALSE(anEdgeExp.More());
|
||||
}
|
||||
EXPECT_FALSE(aWireExp.More());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(BRepClassFaceExplorerTest, MultiWireHole_BoundingBoxesPreserveClassification)
|
||||
{
|
||||
const TopoDS_Wire anInnerWire = makeRectangle(3.0, 3.0, 7.0, 7.0);
|
||||
ASSERT_FALSE(anInnerWire.IsNull());
|
||||
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(gp_Pln(gp::XOY()), 0.0, 10.0, 0.0, 10.0);
|
||||
ASSERT_TRUE(aFaceBuilder.IsDone());
|
||||
aFaceBuilder.Add(TopoDS::Wire(anInnerWire.Reversed()));
|
||||
ASSERT_TRUE(aFaceBuilder.IsDone());
|
||||
const TopoDS_Face aFace = aFaceBuilder.Face();
|
||||
|
||||
const gp_Pnt2d aPoints[] = {gp_Pnt2d(1.0, 1.0), gp_Pnt2d(5.0, 5.0), gp_Pnt2d(12.0, 5.0)};
|
||||
const TopAbs_State anExpected[] = {TopAbs_IN, TopAbs_OUT, TopAbs_OUT};
|
||||
for (int anIndex = 0; anIndex < 3; ++anIndex)
|
||||
{
|
||||
EXPECT_EQ(classify(aFace, aPoints[anIndex], false), anExpected[anIndex]);
|
||||
EXPECT_EQ(classify(aFace, aPoints[anIndex], true), anExpected[anIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BRepClassFaceExplorerTest, CylindricalSeam_PreservesWireOccurrenceOrder)
|
||||
{
|
||||
occ::handle<Geom_CylindricalSurface> aSurface =
|
||||
new Geom_CylindricalSurface(gp_Ax3(gp::Origin(), gp::DZ()), 2.0);
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(aSurface, 0.0, 2.0 * M_PI, 0.0, 5.0, Precision::Confusion());
|
||||
ASSERT_TRUE(aFaceBuilder.IsDone());
|
||||
const TopoDS_Face aFace = aFaceBuilder.Face();
|
||||
|
||||
NCollection_Sequence<TopoDS_Edge> anExpectedEdges;
|
||||
for (TopExp_Explorer aWireExp(aFace, TopAbs_WIRE); aWireExp.More(); aWireExp.Next())
|
||||
{
|
||||
for (TopExp_Explorer anEdgeExp(aWireExp.Current(), TopAbs_EDGE); anEdgeExp.More();
|
||||
anEdgeExp.Next())
|
||||
{
|
||||
anExpectedEdges.Append(TopoDS::Edge(anEdgeExp.Current()));
|
||||
}
|
||||
}
|
||||
|
||||
bool hasSeamOccurrences = false;
|
||||
for (size_t anIndex = 1; anIndex <= anExpectedEdges.Size(); ++anIndex)
|
||||
{
|
||||
for (size_t anOther = anIndex + 1; anOther <= anExpectedEdges.Size(); ++anOther)
|
||||
{
|
||||
if (anExpectedEdges.Value(anIndex).IsSame(anExpectedEdges.Value(anOther)))
|
||||
{
|
||||
hasSeamOccurrences = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(hasSeamOccurrences);
|
||||
|
||||
BRepClass_FaceExplorer anExplorer(aFace);
|
||||
size_t anExpectedIndex = 1;
|
||||
for (anExplorer.InitWires(); anExplorer.MoreWires(); anExplorer.NextWire())
|
||||
{
|
||||
for (anExplorer.InitEdges(); anExplorer.MoreEdges(); anExplorer.NextEdge())
|
||||
{
|
||||
ASSERT_LE(anExpectedIndex, anExpectedEdges.Size());
|
||||
BRepClass_Edge anEdgeData;
|
||||
TopAbs_Orientation anOrientation = TopAbs_EXTERNAL;
|
||||
anExplorer.CurrentEdge(anEdgeData, anOrientation);
|
||||
const BRepClass_Edge& aConstEdgeData = anEdgeData;
|
||||
EXPECT_TRUE(aConstEdgeData.Edge().IsEqual(anExpectedEdges.Value(anExpectedIndex)));
|
||||
EXPECT_EQ(anOrientation, anExpectedEdges.Value(anExpectedIndex).Orientation());
|
||||
++anExpectedIndex;
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(anExpectedIndex, anExpectedEdges.Size() + 1);
|
||||
expectNextEdgesMatchReference(aFace);
|
||||
EXPECT_EQ(classify(aFace, gp_Pnt2d(M_PI, 2.5), false), TopAbs_IN);
|
||||
EXPECT_EQ(classify(aFace, gp_Pnt2d(M_PI, 2.5), true), TopAbs_IN);
|
||||
EXPECT_EQ(classify(aFace, gp_Pnt2d(M_PI, 6.0), true), TopAbs_OUT);
|
||||
}
|
||||
|
||||
TEST(BRepClassFaceExplorerTest, ReversedWire_NextEdgesMatchReference)
|
||||
{
|
||||
const TopoDS_Wire aWire = makeRectangle(0.0, 0.0, 2.0, 2.0);
|
||||
ASSERT_FALSE(aWire.IsNull());
|
||||
const TopoDS_Wire aReversedWire = TopoDS::Wire(aWire.Reversed());
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(aReversedWire, true);
|
||||
ASSERT_TRUE(aFaceBuilder.IsDone());
|
||||
|
||||
expectNextEdgesMatchReference(aFaceBuilder.Face());
|
||||
}
|
||||
|
||||
TEST(BRepClassFaceExplorerTest, SingleClosedEdge_HasNoNextEdge)
|
||||
{
|
||||
occ::handle<Geom_Circle> aCircle = new Geom_Circle(gp_Ax2(gp::Origin(), gp::DZ()), 2.0);
|
||||
BRepBuilderAPI_MakeEdge anEdgeBuilder(aCircle);
|
||||
ASSERT_TRUE(anEdgeBuilder.IsDone());
|
||||
BRepBuilderAPI_MakeWire aWireBuilder(anEdgeBuilder.Edge());
|
||||
ASSERT_TRUE(aWireBuilder.IsDone());
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(aWireBuilder.Wire(), true);
|
||||
ASSERT_TRUE(aFaceBuilder.IsDone());
|
||||
|
||||
BRepClass_FaceExplorer anExplorer(aFaceBuilder.Face());
|
||||
anExplorer.InitWires();
|
||||
ASSERT_TRUE(anExplorer.MoreWires());
|
||||
anExplorer.InitEdges();
|
||||
ASSERT_TRUE(anExplorer.MoreEdges());
|
||||
BRepClass_Edge anEdgeData;
|
||||
TopAbs_Orientation anOrientation = TopAbs_EXTERNAL;
|
||||
anExplorer.CurrentEdge(anEdgeData, anOrientation);
|
||||
EXPECT_TRUE(anEdgeData.NextEdge().IsNull());
|
||||
anExplorer.NextEdge();
|
||||
EXPECT_FALSE(anExplorer.MoreEdges());
|
||||
expectNextEdgesMatchReference(aFaceBuilder.Face());
|
||||
}
|
||||
|
||||
TEST(BRepClassFaceExplorerTest, DegenerateEdges_HaveNoNextEdge)
|
||||
{
|
||||
BRepPrimAPI_MakeSphere aSphereBuilder(2.0);
|
||||
aSphereBuilder.Build();
|
||||
ASSERT_TRUE(aSphereBuilder.IsDone());
|
||||
const TopoDS_Shape aSphere = aSphereBuilder.Shape();
|
||||
ASSERT_FALSE(aSphere.IsNull());
|
||||
|
||||
bool hasDegenerateEdge = false;
|
||||
for (TopExp_Explorer aFaceExp(aSphere, TopAbs_FACE); aFaceExp.More(); aFaceExp.Next())
|
||||
{
|
||||
const TopoDS_Face& aFace = TopoDS::Face(aFaceExp.Current());
|
||||
BRepClass_FaceExplorer anExplorer(aFace);
|
||||
for (anExplorer.InitWires(); anExplorer.MoreWires(); anExplorer.NextWire())
|
||||
{
|
||||
for (anExplorer.InitEdges(); anExplorer.MoreEdges(); anExplorer.NextEdge())
|
||||
{
|
||||
BRepClass_Edge anEdgeData;
|
||||
TopAbs_Orientation anOrientation = TopAbs_EXTERNAL;
|
||||
anExplorer.CurrentEdge(anEdgeData, anOrientation);
|
||||
if (BRep_Tool::Degenerated(anEdgeData.Edge()))
|
||||
{
|
||||
hasDegenerateEdge = true;
|
||||
EXPECT_TRUE(anEdgeData.NextEdge().IsNull());
|
||||
}
|
||||
}
|
||||
}
|
||||
expectNextEdgesMatchReference(aFace);
|
||||
}
|
||||
EXPECT_TRUE(hasDegenerateEdge);
|
||||
}
|
||||
|
||||
TEST(BRepClassEdgeTest, SetTopology_InvalidatesDerivedData)
|
||||
{
|
||||
const TopoDS_Wire aWire = makeRectangle(0.0, 0.0, 2.0, 2.0);
|
||||
ASSERT_FALSE(aWire.IsNull());
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(aWire, true);
|
||||
ASSERT_TRUE(aFaceBuilder.IsDone());
|
||||
const TopoDS_Face aFace = aFaceBuilder.Face();
|
||||
|
||||
TopExp_Explorer anEdgeExp(aFace, TopAbs_EDGE);
|
||||
ASSERT_TRUE(anEdgeExp.More());
|
||||
const TopoDS_Edge anEdge = TopoDS::Edge(anEdgeExp.Current());
|
||||
anEdgeExp.Next();
|
||||
ASSERT_TRUE(anEdgeExp.More());
|
||||
const TopoDS_Edge aNextEdge = TopoDS::Edge(anEdgeExp.Current());
|
||||
|
||||
double aFirst = 0.0;
|
||||
double aLast = 0.0;
|
||||
const occ::handle<Geom2d_Curve>& aCurve = BRep_Tool::CurveOnSurface(anEdge, aFace, aFirst, aLast);
|
||||
ASSERT_FALSE(aCurve.IsNull());
|
||||
|
||||
BRepClass_Edge anEdgeData(anEdge, aFace);
|
||||
anEdgeData.SetGeometry(aCurve, aFirst, aLast);
|
||||
Bnd_Box2d aBox;
|
||||
aBox.Update(0.0, 0.0, 2.0, 2.0);
|
||||
anEdgeData.SetBoundingBox(aBox);
|
||||
anEdgeData.SetNextEdge(aNextEdge);
|
||||
|
||||
anEdgeData.SetEdge(TopoDS::Edge(anEdge.Reversed()));
|
||||
EXPECT_TRUE(anEdgeData.Curve().IsNull());
|
||||
EXPECT_TRUE(anEdgeData.NextEdge().IsNull());
|
||||
EXPECT_TRUE(anEdgeData.BoundingBox().IsVoid());
|
||||
EXPECT_EQ(anEdgeData.BoundingBoxState(), BRepClass_Edge::BndBoxState::NotBuilt);
|
||||
EXPECT_EQ(anEdgeData.FirstParameter(), 0.0);
|
||||
EXPECT_EQ(anEdgeData.LastParameter(), 0.0);
|
||||
|
||||
anEdgeData.SetGeometry(aCurve, aFirst, aLast);
|
||||
anEdgeData.SetBoundingBox(aBox);
|
||||
anEdgeData.SetFace(TopoDS::Face(aFace.Reversed()));
|
||||
EXPECT_TRUE(anEdgeData.Curve().IsNull());
|
||||
EXPECT_TRUE(anEdgeData.BoundingBox().IsVoid());
|
||||
EXPECT_EQ(anEdgeData.BoundingBoxState(), BRepClass_Edge::BndBoxState::NotBuilt);
|
||||
}
|
||||
|
||||
TEST(BRepClassEdgeTest, VoidBoundingBox_BecomesUnavailable)
|
||||
{
|
||||
BRepClass_Edge anEdgeData;
|
||||
anEdgeData.SetBoundingBox(Bnd_Box2d());
|
||||
EXPECT_TRUE(anEdgeData.BoundingBox().IsVoid());
|
||||
EXPECT_EQ(anEdgeData.BoundingBoxState(), BRepClass_Edge::BndBoxState::Unavailable);
|
||||
}
|
||||
|
||||
TEST(BRepClassIntersectorTest, NotBuiltAndUnavailableBoundingBoxes_IntersectWithoutCacheWrites)
|
||||
{
|
||||
const TopoDS_Wire aWire = makeRectangle(0.0, 0.0, 2.0, 2.0);
|
||||
ASSERT_FALSE(aWire.IsNull());
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(aWire, true);
|
||||
ASSERT_TRUE(aFaceBuilder.IsDone());
|
||||
const TopoDS_Face aFace = aFaceBuilder.Face();
|
||||
|
||||
TopExp_Explorer anEdgeExp(aFace, TopAbs_EDGE);
|
||||
ASSERT_TRUE(anEdgeExp.More());
|
||||
const TopoDS_Edge anEdge = TopoDS::Edge(anEdgeExp.Current());
|
||||
double aFirst = 0.0;
|
||||
double aLast = 0.0;
|
||||
const occ::handle<Geom2d_Curve>& aCurve = BRep_Tool::CurveOnSurface(anEdge, aFace, aFirst, aLast);
|
||||
ASSERT_FALSE(aCurve.IsNull());
|
||||
|
||||
BRepClass_Edge anEdgeData(anEdge, aFace);
|
||||
anEdgeData.SetGeometry(aCurve, aFirst, aLast);
|
||||
anEdgeData.SetUseBndBox(true);
|
||||
|
||||
gp_Pnt2d aPoint;
|
||||
gp_Vec2d aTangent;
|
||||
aCurve->D1(0.5 * (aFirst + aLast), aPoint, aTangent);
|
||||
ASSERT_GT(aTangent.SquareMagnitude(), Precision::SquarePConfusion());
|
||||
const gp_Dir2d aNormal(-aTangent.Y(), aTangent.X());
|
||||
|
||||
BRepClass_Intersector aNotBuiltIntersector;
|
||||
aNotBuiltIntersector.Perform(gp_Lin2d(aPoint, aNormal),
|
||||
RealLast(),
|
||||
Precision::Confusion(),
|
||||
anEdgeData);
|
||||
EXPECT_TRUE(aNotBuiltIntersector.IsDone());
|
||||
EXPECT_EQ(anEdgeData.BoundingBoxState(), BRepClass_Edge::BndBoxState::NotBuilt);
|
||||
|
||||
anEdgeData.SetBoundingBoxUnavailable();
|
||||
BRepClass_Intersector anUnavailableIntersector;
|
||||
anUnavailableIntersector.Perform(gp_Lin2d(aPoint, aNormal),
|
||||
RealLast(),
|
||||
Precision::Confusion(),
|
||||
anEdgeData);
|
||||
EXPECT_TRUE(anUnavailableIntersector.IsDone());
|
||||
EXPECT_EQ(anEdgeData.BoundingBoxState(), BRepClass_Edge::BndBoxState::Unavailable);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2026 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 <BRep_Builder.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepClass_FaceClassifier.hxx>
|
||||
#include <BRepTopAdaptor_FClass2d.hxx>
|
||||
#include <CSLib_Class2d.hxx>
|
||||
#include <Geom_CylindricalSurface.hxx>
|
||||
#include <NCollection_Array1.hxx>
|
||||
#include <NCollection_LinearVector.hxx>
|
||||
#include <Precision.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <gp.hxx>
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
TopoDS_Wire makeRectangle(const double theXMin,
|
||||
const double theYMin,
|
||||
const double theXMax,
|
||||
const double theYMax)
|
||||
{
|
||||
const gp_Pnt aPoints[] = {gp_Pnt(theXMin, theYMin, 0.0),
|
||||
gp_Pnt(theXMax, theYMin, 0.0),
|
||||
gp_Pnt(theXMax, theYMax, 0.0),
|
||||
gp_Pnt(theXMin, theYMax, 0.0)};
|
||||
BRepBuilderAPI_MakeWire aWireBuilder;
|
||||
for (int anIndex = 0; anIndex < 4; ++anIndex)
|
||||
{
|
||||
BRepBuilderAPI_MakeEdge anEdgeBuilder(aPoints[anIndex], aPoints[(anIndex + 1) % 4]);
|
||||
if (!anEdgeBuilder.IsDone())
|
||||
{
|
||||
return TopoDS_Wire();
|
||||
}
|
||||
aWireBuilder.Add(anEdgeBuilder.Edge());
|
||||
}
|
||||
return aWireBuilder.IsDone() ? aWireBuilder.Wire() : TopoDS_Wire();
|
||||
}
|
||||
|
||||
TopoDS_Face makeFaceWithHole()
|
||||
{
|
||||
const TopoDS_Wire anInnerWire = makeRectangle(3.0, 2.0, 9.0, 6.0);
|
||||
if (anInnerWire.IsNull())
|
||||
{
|
||||
return TopoDS_Face();
|
||||
}
|
||||
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(gp_Pln(gp::XOY()), 0.0, 12.0, 0.0, 8.0);
|
||||
if (!aFaceBuilder.IsDone())
|
||||
{
|
||||
return TopoDS_Face();
|
||||
}
|
||||
aFaceBuilder.Add(TopoDS::Wire(anInnerWire.Reversed()));
|
||||
return aFaceBuilder.IsDone() ? aFaceBuilder.Face() : TopoDS_Face();
|
||||
}
|
||||
|
||||
TopoDS_Face makeFaceWithDisconnectedWire()
|
||||
{
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(gp_Pln(gp::XOY()), 0.0, 10.0, 0.0, 10.0);
|
||||
if (!aFaceBuilder.IsDone())
|
||||
{
|
||||
return TopoDS_Face();
|
||||
}
|
||||
|
||||
TopoDS_Face aFace = aFaceBuilder.Face();
|
||||
TopoDS_Edge aFirstEdge;
|
||||
TopoDS_Edge aThirdEdge;
|
||||
int anEdgeIndex = 0;
|
||||
for (TopExp_Explorer anExplorer(aFace, TopAbs_EDGE); anExplorer.More(); anExplorer.Next())
|
||||
{
|
||||
if (anEdgeIndex == 0)
|
||||
{
|
||||
aFirstEdge = TopoDS::Edge(anExplorer.Current());
|
||||
}
|
||||
else if (anEdgeIndex == 2)
|
||||
{
|
||||
aThirdEdge = TopoDS::Edge(anExplorer.Current());
|
||||
}
|
||||
++anEdgeIndex;
|
||||
}
|
||||
if (aFirstEdge.IsNull() || aThirdEdge.IsNull())
|
||||
{
|
||||
return TopoDS_Face();
|
||||
}
|
||||
|
||||
BRep_Builder aBuilder;
|
||||
TopoDS_Wire aDisconnectedWire;
|
||||
aBuilder.MakeWire(aDisconnectedWire);
|
||||
aBuilder.Add(aDisconnectedWire, aFirstEdge);
|
||||
aBuilder.Add(aDisconnectedWire, aThirdEdge);
|
||||
aBuilder.Add(aFace, aDisconnectedWire);
|
||||
return aFace;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(BRepTopAdaptorFClass2dTest, LinearVectorView_ZeroBasedOrderIsClassifiedCorrectly)
|
||||
{
|
||||
NCollection_LinearVector<gp_Pnt2d> aPoints;
|
||||
aPoints.Append(gp_Pnt2d(0.0, 0.0));
|
||||
aPoints.Append(gp_Pnt2d(7.0, 0.0));
|
||||
aPoints.Append(gp_Pnt2d(7.0, 3.0));
|
||||
aPoints.Append(gp_Pnt2d(0.0, 3.0));
|
||||
aPoints.Append(gp_Pnt2d(0.0, 0.0));
|
||||
|
||||
NCollection_Array1<gp_Pnt2d> aView = aPoints.ToArray1();
|
||||
ASSERT_EQ(aView.Lower(), 0);
|
||||
ASSERT_EQ(aView.Upper(), 4);
|
||||
EXPECT_TRUE(aView(0).IsEqual(aPoints[0], Precision::PConfusion()));
|
||||
EXPECT_TRUE(aView(3).IsEqual(aPoints[3], Precision::PConfusion()));
|
||||
|
||||
CSLib_Class2d aClassifier(aPoints.ToArray1(), 1.e-7, 1.e-7, 0.0, 0.0, 7.0, 3.0);
|
||||
EXPECT_EQ(aClassifier.SiDans(gp_Pnt2d(2.0, 1.0)), CSLib_Class2d::Result_Inside);
|
||||
EXPECT_EQ(aClassifier.SiDans(gp_Pnt2d(8.0, 1.0)), CSLib_Class2d::Result_Outside);
|
||||
}
|
||||
|
||||
TEST(BRepTopAdaptorFClass2dTest, PlanarHole_PerformAndRestrictionPreserveWireRoles)
|
||||
{
|
||||
const TopoDS_Face aFace = makeFaceWithHole();
|
||||
ASSERT_FALSE(aFace.IsNull());
|
||||
BRepTopAdaptor_FClass2d aClassifier(aFace, Precision::PConfusion());
|
||||
|
||||
const gp_Pnt2d aMaterialPoint(1.0, 1.0);
|
||||
const gp_Pnt2d aHolePoint(5.0, 4.0);
|
||||
const gp_Pnt2d anOutsidePoint(14.0, 4.0);
|
||||
EXPECT_EQ(aClassifier.Perform(aMaterialPoint), TopAbs_IN);
|
||||
EXPECT_EQ(aClassifier.Perform(aHolePoint), TopAbs_OUT);
|
||||
EXPECT_EQ(aClassifier.Perform(anOutsidePoint), TopAbs_OUT);
|
||||
EXPECT_EQ(aClassifier.TestOnRestriction(aMaterialPoint, 1.e-7), TopAbs_IN);
|
||||
EXPECT_EQ(aClassifier.TestOnRestriction(aHolePoint, 1.e-7), TopAbs_OUT);
|
||||
EXPECT_EQ(aClassifier.TestOnRestriction(anOutsidePoint, 1.e-7), TopAbs_OUT);
|
||||
}
|
||||
|
||||
TEST(BRepTopAdaptorFClass2dTest, Boundary_PerformUsesRepeatableExactFallback)
|
||||
{
|
||||
const TopoDS_Face aFace = makeFaceWithHole();
|
||||
ASSERT_FALSE(aFace.IsNull());
|
||||
BRepTopAdaptor_FClass2d aClassifier(aFace, Precision::PConfusion());
|
||||
const gp_Pnt2d aBoundaryPoint(0.0, 3.0);
|
||||
|
||||
BRepClass_FaceClassifier anExact(aFace, aBoundaryPoint, Precision::PConfusion());
|
||||
ASSERT_EQ(anExact.State(), TopAbs_ON);
|
||||
for (int anIteration = 0; anIteration < 20; ++anIteration)
|
||||
{
|
||||
EXPECT_EQ(aClassifier.Perform(aBoundaryPoint), anExact.State());
|
||||
EXPECT_EQ(aClassifier.TestOnRestriction(aBoundaryPoint, 1.e-6), TopAbs_ON);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BRepTopAdaptorFClass2dTest, PeriodicCylinder_RecadresEquivalentParameters)
|
||||
{
|
||||
occ::handle<Geom_CylindricalSurface> aSurface =
|
||||
new Geom_CylindricalSurface(gp_Ax3(gp::Origin(), gp::DZ()), 2.0);
|
||||
ASSERT_FALSE(aSurface.IsNull());
|
||||
BRepBuilderAPI_MakeFace aFaceBuilder(aSurface, 0.0, 2.0 * M_PI, 0.0, 5.0, Precision::Confusion());
|
||||
ASSERT_TRUE(aFaceBuilder.IsDone());
|
||||
BRepTopAdaptor_FClass2d aClassifier(aFaceBuilder.Face(), Precision::PConfusion());
|
||||
|
||||
const gp_Pnt2d aCanonicalPoint(0.75, 2.0);
|
||||
const gp_Pnt2d aShiftedPoint(aCanonicalPoint.X() + 4.0 * M_PI, aCanonicalPoint.Y());
|
||||
EXPECT_EQ(aClassifier.Perform(aCanonicalPoint), TopAbs_IN);
|
||||
EXPECT_EQ(aClassifier.Perform(aShiftedPoint), aClassifier.Perform(aCanonicalPoint));
|
||||
EXPECT_EQ(aClassifier.TestOnRestriction(aShiftedPoint, 1.e-7),
|
||||
aClassifier.TestOnRestriction(aCanonicalPoint, 1.e-7));
|
||||
}
|
||||
|
||||
TEST(BRepTopAdaptorFClass2dTest, DisconnectedWire_FallsBackToStableExactClassification)
|
||||
{
|
||||
const TopoDS_Face aFace = makeFaceWithDisconnectedWire();
|
||||
ASSERT_FALSE(aFace.IsNull());
|
||||
const gp_Pnt2d aPoint(5.0, 5.0);
|
||||
BRepClass_FaceClassifier anExact(aFace, aPoint, Precision::PConfusion());
|
||||
BRepTopAdaptor_FClass2d aClassifier(aFace, Precision::PConfusion());
|
||||
|
||||
const TopAbs_State anExpected = anExact.State();
|
||||
for (int anIteration = 0; anIteration < 10; ++anIteration)
|
||||
{
|
||||
EXPECT_EQ(aClassifier.Perform(aPoint, false), anExpected);
|
||||
EXPECT_EQ(aClassifier.TestOnRestriction(aPoint, Precision::PConfusion(), false), anExpected);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,10 @@ set(OCCT_TKTopAlgo_GTests_FILES
|
||||
BRepBuilderAPI_Transform_Test.cxx
|
||||
BRepCheck_Face_Test.cxx
|
||||
BRepClass3d_SolidClassifier_Test.cxx
|
||||
BRepClass_FaceExplorer_Test.cxx
|
||||
BRepExtrema_DistShapeShape_Test.cxx
|
||||
BRepGProp_Test.cxx
|
||||
BRepLib_MakeWire_Test.cxx
|
||||
BRepOffsetAPI_ThruSections_Test.cxx
|
||||
BRepTopAdaptor_FClass2d_Test.cxx
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user