Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-28 09:19:57

0001 // Copyright (c) 2025 OPEN CASCADE SAS
0002 //
0003 // This file is part of Open CASCADE Technology software library.
0004 //
0005 // This library is free software; you can redistribute it and/or modify it under
0006 // the terms of the GNU Lesser General Public License version 2.1 as published
0007 // by the Free Software Foundation, with special exception defined in the file
0008 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
0009 // distribution for complete text of the license and disclaimer of any warranty.
0010 //
0011 // Alternatively, this file may be used under the terms of Open CASCADE
0012 // commercial license or contractual agreement.
0013 
0014 #ifndef _ExtremaPC_HeaderFile
0015 #define _ExtremaPC_HeaderFile
0016 
0017 #include <gp_Pnt.hxx>
0018 #include <MathUtils_Domain.hxx>
0019 #include <NCollection_DynamicArray.hxx>
0020 #include <Precision.hxx>
0021 
0022 #include <limits>
0023 #include <optional>
0024 
0025 //! @file ExtremaPC.hxx
0026 //! @brief Common types and utilities for Point-Curve extrema computation.
0027 //!
0028 //! The ExtremaPC package provides modern C++ implementation of point-curve
0029 //! extrema computation using std::variant for curve type dispatch and
0030 //! BVH-based hierarchical algorithms for numerical curves.
0031 
0032 namespace ExtremaPC
0033 {
0034 
0035 //==================================================================================================
0036 //! @name Precision Constants
0037 //! Centralized tolerance and precision values used throughout the ExtremaPC package.
0038 //! These replace magic numbers to improve code clarity and maintainability.
0039 //==================================================================================================
0040 
0041 //! Default tolerance for root finding and distance comparison.
0042 //! Used when no explicit tolerance is provided.
0043 constexpr double THE_DEFAULT_TOLERANCE = Precision::Confusion();
0044 
0045 //! Tolerance for parameter domain comparison (cache validation).
0046 //! Uses PConfusion which is appropriate for parametric space.
0047 constexpr double THE_PARAM_TOLERANCE = Precision::PConfusion();
0048 
0049 //! Ratio of parameter range for neighbor point sampling.
0050 //! Used to evaluate if an endpoint is a local extremum.
0051 constexpr double THE_NEIGHBOR_STEP_RATIO = 0.01;
0052 
0053 //! Ratio of parameter range for search interval expansion.
0054 //! Used in grid-based methods to expand candidate intervals.
0055 constexpr double THE_INTERVAL_EXPAND_RATIO = 0.05;
0056 
0057 //! Ratio of parameter range for refinement step.
0058 //! Used in iterative refinement algorithms.
0059 constexpr double THE_REFINEMENT_STEP_RATIO = 0.001;
0060 
0061 //! Multiplier for X (parameter) tolerance in Newton refinement.
0062 constexpr double THE_NEWTON_XTOL_FACTOR = 0.01;
0063 
0064 //! Multiplier for F (function) tolerance in Newton refinement.
0065 constexpr double THE_NEWTON_FTOL_FACTOR = 0.001;
0066 
0067 //! Default search radius for hint-based search (fraction of parameter range).
0068 constexpr double THE_HINT_SEARCH_RADIUS = 0.1;
0069 
0070 //! Factor for narrowing parameter range during refinement iterations.
0071 constexpr double THE_RANGE_NARROWING_FACTOR = 0.25;
0072 
0073 //! Threshold for skipping max candidates that are clearly worse.
0074 //! If estimated distance < best * threshold, skip candidate.
0075 constexpr double THE_MAX_SKIP_THRESHOLD = 0.9;
0076 
0077 //! Multiplier for near-zero F detection during grid scan.
0078 //! F values smaller than tolerance * this factor are considered near-zero.
0079 constexpr double THE_NEAR_ZERO_F_FACTOR = 10.0;
0080 
0081 //! Multiplier for fallback F tolerance when Newton fails.
0082 //! Used to accept grid points as approximate solutions.
0083 constexpr double THE_FALLBACK_F_FACTOR = 100.0;
0084 
0085 //! Maximum number of Newton iterations for refinement.
0086 constexpr int THE_MAX_NEWTON_ITERATIONS = 20;
0087 
0088 //! Number of samples for iterative grid refinement fallback.
0089 constexpr int THE_REFINEMENT_NB_SAMPLES = 20;
0090 
0091 //! Number of refinement passes when Newton fails.
0092 constexpr int THE_REFINEMENT_NB_PASSES = 3;
0093 
0094 //! Minimum number of samples for Bezier curves.
0095 constexpr int THE_BEZIER_MIN_SAMPLES = 24;
0096 
0097 //! Multiplier for degree to compute Bezier samples: samples = max(min, multiplier * (degree + 1)).
0098 constexpr int THE_BEZIER_DEGREE_MULTIPLIER = 3;
0099 
0100 //! Default number of samples for general (other) curves.
0101 constexpr int THE_OTHER_CURVE_NB_SAMPLES = 64;
0102 
0103 //! Fallback number of samples for BSpline curves when curve is null.
0104 constexpr int THE_BSPLINE_FALLBACK_SAMPLES = 32;
0105 
0106 //! Multiplier for BSpline curve samples per knot span: samples = multiplier * (degree + 1).
0107 //! For a degree 3 curve: 2*4 = 8 samples per span.
0108 //! This is higher than the surface counterpart because curves are 1D and require
0109 //! finer sampling to detect extrema reliably. Surfaces use degree+2 per direction,
0110 //! resulting in (degree+2)^2 samples per cell, which provides adequate coverage.
0111 constexpr int THE_BSPLINE_SPAN_MULTIPLIER = 2;
0112 
0113 //! 1D parameter domain for curves (alias for MathUtils::Domain1D).
0114 using Domain1D = MathUtils::Domain1D;
0115 
0116 //! Status of extrema computation.
0117 enum class Status
0118 {
0119   OK,                //!< Computation succeeded, finite number of extrema found
0120   NotDone,           //!< Computation not performed
0121   InfiniteSolutions, //!< Infinite solutions exist (e.g., point at circle center)
0122   NoSolution,        //!< No extrema found in the given parameter range
0123   NumericalError     //!< Numerical issues during computation
0124 };
0125 
0126 //! Search mode for extrema computation.
0127 //! Controls which extrema to find, enabling performance optimizations.
0128 enum class SearchMode
0129 {
0130   MinMax, //!< Find all extrema (both minima and maxima) - default
0131   Min,    //!< Find only minimum distance (enables early termination in BVH)
0132   Max     //!< Find only maximum distance
0133 };
0134 
0135 //! Result of a single extremum computation.
0136 struct ExtremumResult
0137 {
0138   double Parameter = 0.0;       //!< Parameter value on curve
0139   gp_Pnt Point;                 //!< Point on curve at parameter
0140   double SquareDistance = 0.0;  //!< Square of the distance from query point to curve point
0141   bool   IsMinimum      = true; //!< True if this is a local minimum, false if maximum
0142 };
0143 
0144 //! Result of extrema computation containing all found extrema.
0145 //! Non-copyable to enforce use of const reference from Perform().
0146 struct Result
0147 {
0148   ExtremaPC::Status Status = ExtremaPC::Status::NotDone; //!< Computation status
0149   NCollection_DynamicArray<ExtremumResult> Extrema{8};   //!< Collection of found extrema
0150 
0151   //! For infinite solutions, stores the constant squared distance.
0152   //! Only meaningful when Status == Status::InfiniteSolutions.
0153   double InfiniteSquareDistance = 0.0;
0154 
0155   //! Default constructor.
0156   Result() = default;
0157 
0158   //! Copy constructor is deleted.
0159   Result(const Result&) = delete;
0160 
0161   //! Copy assignment is deleted.
0162   Result& operator=(const Result&) = delete;
0163 
0164   //! Move constructor.
0165   Result(Result&&) = default;
0166 
0167   //! Move assignment.
0168   Result& operator=(Result&&) = default;
0169 
0170   //! Returns true if computation succeeded with finite number of extrema.
0171   bool IsDone() const { return Status == Status::OK; }
0172 
0173   //! Returns true if there are infinite solutions.
0174   bool IsInfinite() const { return Status == Status::InfiniteSolutions; }
0175 
0176   //! Returns number of extrema found (0 if infinite or failed).
0177   int NbExt() const { return Extrema.Length(); }
0178 
0179   //! Access extremum by 0-based index.
0180   const ExtremumResult& operator[](int theIndex) const { return Extrema.Value(theIndex); }
0181 
0182   //! Returns the squared distance of the closest extremum.
0183   //! Returns infinity if no extrema found.
0184   double MinSquareDistance() const
0185   {
0186     if (Extrema.IsEmpty())
0187     {
0188       return std::numeric_limits<double>::infinity();
0189     }
0190     double aMinSqDist = Extrema.Value(0).SquareDistance;
0191     for (int i = 1; i < Extrema.Length(); ++i)
0192     {
0193       if (Extrema.Value(i).SquareDistance < aMinSqDist)
0194       {
0195         aMinSqDist = Extrema.Value(i).SquareDistance;
0196       }
0197     }
0198     return aMinSqDist;
0199   }
0200 
0201   //! Returns the index of the closest extremum (0-based).
0202   //! Returns -1 if no extrema found.
0203   int MinIndex() const
0204   {
0205     if (Extrema.IsEmpty())
0206     {
0207       return -1;
0208     }
0209     int    aMinIdx    = 0;
0210     double aMinSqDist = Extrema.Value(0).SquareDistance;
0211     for (int i = 1; i < Extrema.Length(); ++i)
0212     {
0213       if (Extrema.Value(i).SquareDistance < aMinSqDist)
0214       {
0215         aMinSqDist = Extrema.Value(i).SquareDistance;
0216         aMinIdx    = i;
0217       }
0218     }
0219     return aMinIdx;
0220   }
0221 
0222   //! Returns the squared distance of the farthest extremum.
0223   //! Returns 0 if no extrema found.
0224   double MaxSquareDistance() const
0225   {
0226     if (Extrema.IsEmpty())
0227     {
0228       return 0.0;
0229     }
0230     double aMaxSqDist = Extrema.Value(0).SquareDistance;
0231     for (int i = 1; i < Extrema.Length(); ++i)
0232     {
0233       if (Extrema.Value(i).SquareDistance > aMaxSqDist)
0234       {
0235         aMaxSqDist = Extrema.Value(i).SquareDistance;
0236       }
0237     }
0238     return aMaxSqDist;
0239   }
0240 
0241   //! Returns the index of the farthest extremum (0-based).
0242   //! Returns -1 if no extrema found.
0243   int MaxIndex() const
0244   {
0245     if (Extrema.IsEmpty())
0246     {
0247       return -1;
0248     }
0249     int    aMaxIdx    = 0;
0250     double aMaxSqDist = Extrema.Value(0).SquareDistance;
0251     for (int i = 1; i < Extrema.Length(); ++i)
0252     {
0253       if (Extrema.Value(i).SquareDistance > aMaxSqDist)
0254       {
0255         aMaxSqDist = Extrema.Value(i).SquareDistance;
0256         aMaxIdx    = i;
0257       }
0258     }
0259     return aMaxIdx;
0260   }
0261 
0262   //! Clear the result for reuse.
0263   //! Preserves allocated memory in Extrema vector.
0264   void Clear()
0265   {
0266     Status = Status::NotDone;
0267     Extrema.Clear();
0268     InfiniteSquareDistance = 0.0;
0269   }
0270 };
0271 
0272 //! Configuration for extrema computation.
0273 struct Config
0274 {
0275   double                  Tolerance = THE_DEFAULT_TOLERANCE; //!< Tolerance for root finding
0276   std::optional<Domain1D> Domain;         //!< Parameter domain (nullopt = use natural/unbounded)
0277   int                     NbSamples = 32; //!< Number of samples for numerical methods
0278   SearchMode              Mode      = SearchMode::MinMax; //!< Search mode (MinMax, Min, or Max)
0279   bool                    IncludeEndpoints = true; //!< Include endpoints as potential extrema
0280 };
0281 
0282 //! @brief Adds endpoint extrema to result for bounded curves.
0283 //!
0284 //! This function adds the curve endpoints as extrema when:
0285 //! - The parameter bounds are finite
0286 //! - The endpoint is a true local extremum (checked via neighbor distance)
0287 //! - The endpoint doesn't duplicate an existing extremum
0288 //!
0289 //! An endpoint is considered a local minimum if the distance to a neighboring
0290 //! point on the curve is greater than the distance to the endpoint.
0291 //! An endpoint is considered a local maximum if the distance to a neighboring
0292 //! point is less than the distance to the endpoint.
0293 //!
0294 //! @tparam CurveEvaluator Type with Value(double) method returning gp_Pnt
0295 //! @param theResult result to add endpoints to
0296 //! @param theP query point
0297 //! @param theDomain parameter domain
0298 //! @param theEval curve evaluator
0299 //! @param theTol tolerance for duplicate detection
0300 //! @param theMode search mode
0301 template <typename CurveEvaluator>
0302 inline void AddEndpointExtrema(Result&               theResult,
0303                                const gp_Pnt&         theP,
0304                                const Domain1D&       theDomain,
0305                                const CurveEvaluator& theEval,
0306                                double                theTol,
0307                                SearchMode            theMode)
0308 {
0309   // Check for infinite bounds
0310   if (!theDomain.IsFinite())
0311   {
0312     return;
0313   }
0314 
0315   const double theUMin = theDomain.Min;
0316   const double theUMax = theDomain.Max;
0317 
0318   // Helper to check if parameter or point already exists in result
0319   auto isDuplicate = [&](double theU, const gp_Pnt& thePt) -> bool {
0320     for (int i = 0; i < theResult.Extrema.Length(); ++i)
0321     {
0322       // Check parameter proximity
0323       if (std::abs(theResult.Extrema.Value(i).Parameter - theU) < theTol)
0324       {
0325         return true;
0326       }
0327       // Check point proximity (handles periodic curves where different params map to same point)
0328       double aSqDist = theResult.Extrema.Value(i).Point.SquareDistance(thePt);
0329       if (aSqDist < theTol * theTol)
0330       {
0331         return true;
0332       }
0333     }
0334     return false;
0335   };
0336 
0337   // Evaluate endpoints
0338   gp_Pnt aPtMin = theEval.Value(theUMin);
0339   gp_Pnt aPtMax = theEval.Value(theUMax);
0340 
0341   double aSqDistMin = theP.SquareDistance(aPtMin);
0342   double aSqDistMax = theP.SquareDistance(aPtMax);
0343 
0344   // Sample step for neighbor point evaluation
0345   double aStep = (theUMax - theUMin) * THE_NEIGHBOR_STEP_RATIO;
0346   if (aStep < theTol)
0347   {
0348     aStep = theTol;
0349   }
0350 
0351   // Check if UMin is a local extremum by comparing to neighbor
0352   gp_Pnt aNeighborMin     = theEval.Value(theUMin + aStep);
0353   double aNeighborDistMin = theP.SquareDistance(aNeighborMin);
0354   bool   aIsMinAtUMin     = (aSqDistMin <= aNeighborDistMin);
0355   bool   aIsMaxAtUMin     = (aSqDistMin >= aNeighborDistMin);
0356 
0357   // Check if UMax is a local extremum by comparing to neighbor
0358   gp_Pnt aNeighborMax     = theEval.Value(theUMax - aStep);
0359   double aNeighborDistMax = theP.SquareDistance(aNeighborMax);
0360   bool   aIsMinAtUMax     = (aSqDistMax <= aNeighborDistMax);
0361   bool   aIsMaxAtUMax     = (aSqDistMax >= aNeighborDistMax);
0362 
0363   // Also check if endpoints themselves are duplicates (for periodic curves)
0364   bool aEndpointsAreSame = aPtMin.SquareDistance(aPtMax) < theTol * theTol;
0365 
0366   // For periodic/closed curves, there are no true endpoints to consider
0367   if (aEndpointsAreSame)
0368   {
0369     return;
0370   }
0371 
0372   // Add endpoints only if they are true local extrema matching the search mode
0373   if (theMode == SearchMode::Min || theMode == SearchMode::MinMax)
0374   {
0375     // Add UMin if it's a local minimum
0376     if (aIsMinAtUMin && !isDuplicate(theUMin, aPtMin))
0377     {
0378       ExtremumResult anExt;
0379       anExt.Parameter      = theUMin;
0380       anExt.Point          = aPtMin;
0381       anExt.SquareDistance = aSqDistMin;
0382       anExt.IsMinimum      = true;
0383       theResult.Extrema.Append(anExt);
0384     }
0385     // Add UMax if it's a local minimum (and not same point as UMin)
0386     if (aIsMinAtUMax && !aEndpointsAreSame && !isDuplicate(theUMax, aPtMax))
0387     {
0388       ExtremumResult anExt;
0389       anExt.Parameter      = theUMax;
0390       anExt.Point          = aPtMax;
0391       anExt.SquareDistance = aSqDistMax;
0392       anExt.IsMinimum      = true;
0393       theResult.Extrema.Append(anExt);
0394     }
0395   }
0396 
0397   if (theMode == SearchMode::Max || theMode == SearchMode::MinMax)
0398   {
0399     // Add UMin if it's a local maximum
0400     if (aIsMaxAtUMin && !aIsMinAtUMin && !isDuplicate(theUMin, aPtMin))
0401     {
0402       ExtremumResult anExt;
0403       anExt.Parameter      = theUMin;
0404       anExt.Point          = aPtMin;
0405       anExt.SquareDistance = aSqDistMin;
0406       anExt.IsMinimum      = false;
0407       theResult.Extrema.Append(anExt);
0408     }
0409     // Add UMax if it's a local maximum (and not same point as UMin)
0410     if (aIsMaxAtUMax && !aIsMinAtUMax && !aEndpointsAreSame && !isDuplicate(theUMax, aPtMax))
0411     {
0412       ExtremumResult anExt;
0413       anExt.Parameter      = theUMax;
0414       anExt.Point          = aPtMax;
0415       anExt.SquareDistance = aSqDistMax;
0416       anExt.IsMinimum      = false;
0417       theResult.Extrema.Append(anExt);
0418     }
0419   }
0420 }
0421 
0422 } // namespace ExtremaPC
0423 
0424 #endif // _ExtremaPC_HeaderFile