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_Circle_HeaderFile
0015 #define _ExtremaPC_Circle_HeaderFile
0016 
0017 #include <ElCLib.hxx>
0018 #include <ExtremaPC.hxx>
0019 #include <gp_Circ.hxx>
0020 #include <gp_Pnt.hxx>
0021 #include <gp_Vec.hxx>
0022 #include <Precision.hxx>
0023 #include <Standard_DefineAlloc.hxx>
0024 
0025 #include <cmath>
0026 #include <optional>
0027 
0028 //! @brief Point-Circle extrema computation.
0029 //!
0030 //! Computes the extrema between a 3D point and a circle.
0031 //! Uses analytical solution via angle computation in the circle plane.
0032 //!
0033 //! For a circle with center O and radius R, the algorithm:
0034 //! 1. Projects point P onto the circle plane -> Pp
0035 //! 2. Computes angle from OPp to find closest/farthest points
0036 //!
0037 //! The domain is fixed at construction time for optimal performance.
0038 //! For full circle, construct without domain or with nullopt.
0039 //!
0040 //! @note Degenerate case: When P projects to the circle center,
0041 //!       all points on the circle are equidistant (infinite solutions).
0042 //!       Returns Status::InfiniteSolutions with InfiniteSquareDistance = R^2 + h^2
0043 //!       where h is the height above the circle plane.
0044 //!
0045 //! @note A circle always has exactly 2 extrema: one minimum (closest)
0046 //!       and one maximum (farthest), at opposite points on the circle.
0047 class ExtremaPC_Circle
0048 {
0049 public:
0050   DEFINE_STANDARD_ALLOC
0051 
0052   //! Constructor with circle geometry (full circle).
0053   //! @param[in] theCircle the circle to compute extrema for
0054   explicit ExtremaPC_Circle(const gp_Circ& theCircle)
0055       : myCircle(theCircle),
0056         myDomain(std::nullopt)
0057   {
0058   }
0059 
0060   //! Constructor with circle geometry and parameter domain.
0061   //! @param[in] theCircle the circle to compute extrema for
0062   //! @param[in] theDomain parameter domain in radians (fixed for all queries)
0063   ExtremaPC_Circle(const gp_Circ& theCircle, const ExtremaPC::Domain1D& theDomain)
0064       : myCircle(theCircle),
0065         myDomain(theDomain.IsFullPeriod(2.0 * M_PI) ? std::nullopt
0066                                                     : std::optional<ExtremaPC::Domain1D>(theDomain))
0067   {
0068   }
0069 
0070   //! Copy constructor is deleted.
0071   ExtremaPC_Circle(const ExtremaPC_Circle&) = delete;
0072 
0073   //! Copy assignment operator is deleted.
0074   ExtremaPC_Circle& operator=(const ExtremaPC_Circle&) = delete;
0075 
0076   //! Move constructor.
0077   ExtremaPC_Circle(ExtremaPC_Circle&&) = default;
0078 
0079   //! Move assignment operator.
0080   ExtremaPC_Circle& operator=(ExtremaPC_Circle&&) = default;
0081 
0082   //! Evaluates point on circle at parameter.
0083   //! @param theU parameter (radians)
0084   //! @return point on circle
0085   gp_Pnt Value(double theU) const { return ElCLib::Value(theU, myCircle); }
0086 
0087   //! Returns true if domain is bounded (partial arc).
0088   bool IsBounded() const { return myDomain.has_value(); }
0089 
0090   //! Returns the domain (only valid if IsBounded() is true).
0091   const ExtremaPC::Domain1D& Domain() const { return *myDomain; }
0092 
0093   //! Compute extrema between point P and the circle.
0094   //! Uses domain specified at construction time.
0095   //! @param theP query point
0096   //! @param theTol tolerance for degenerate case detection
0097   //! @param theMode search mode (MinMax, Min, or Max)
0098   //! @return const reference to result containing extrema or InfiniteSolutions status
0099   [[nodiscard]] const ExtremaPC::Result& Perform(
0100     const gp_Pnt&         theP,
0101     double                theTol,
0102     ExtremaPC::SearchMode theMode = ExtremaPC::SearchMode::MinMax) const
0103   {
0104     myResult.Clear();
0105 
0106     // Step 1: Project point P onto the circle plane
0107     const gp_Pnt& aCenter = myCircle.Location();
0108     const gp_Dir& aAxis   = myCircle.Axis().Direction();
0109     gp_Vec        aToP(aCenter, theP);
0110     double        aHeight = aToP.Dot(gp_Vec(aAxis));
0111     gp_Vec        aTrsl   = gp_Vec(aAxis) * (-aHeight);
0112     gp_Pnt        aPp     = theP.Translated(aTrsl);
0113 
0114     // Step 2: Check for degenerate case - point projects to center
0115     gp_Vec aOPp(aCenter, aPp);
0116     double aOPpMag = aOPp.Magnitude();
0117 
0118     if (aOPpMag < theTol)
0119     {
0120       // Point is on the circle axis - all points on circle are equidistant
0121       myResult.Status                 = ExtremaPC::Status::InfiniteSolutions;
0122       double aRadius                  = myCircle.Radius();
0123       myResult.InfiniteSquareDistance = aRadius * aRadius + aHeight * aHeight;
0124       return myResult;
0125     }
0126 
0127     // Step 3: Compute the angle of the closest point
0128     // Us1 corresponds to minimum distance (closest point)
0129     double aUs1 = myCircle.XAxis().Direction().AngleWithRef(aOPp, aAxis);
0130 
0131     // Handle angle boundaries
0132     constexpr double aAngTol = Precision::Angular();
0133     if (aUs1 + M_PI < aAngTol)
0134     {
0135       aUs1 = -M_PI;
0136     }
0137     else if (aUs1 - M_PI > -aAngTol)
0138     {
0139       aUs1 = M_PI;
0140     }
0141 
0142     // Us2 = Us1 + PI corresponds to maximum distance (farthest point)
0143     double aUs2 = aUs1 + M_PI;
0144 
0145     // Step 4: For bounded case, adjust for periodicity
0146     double aTolU = Precision::Angular();
0147     if (myDomain.has_value())
0148     {
0149       const double theUMin = myDomain->Min;
0150 
0151       double aRadius = myCircle.Radius();
0152       if (aRadius > gp::Resolution())
0153       {
0154         aTolU = theTol / aRadius;
0155       }
0156 
0157       // Adjust angles to be within [theUMin, theUMin + 2*PI]
0158       double aUinf = theUMin;
0159       ElCLib::AdjustPeriodic(theUMin, theUMin + 2.0 * M_PI, aTolU, aUinf, aUs1);
0160       ElCLib::AdjustPeriodic(theUMin, theUMin + 2.0 * M_PI, aTolU, aUinf, aUs2);
0161 
0162       // Handle boundary tolerance
0163       if (std::abs(aUs1 - 2.0 * M_PI - theUMin) < aTolU)
0164       {
0165         aUs1 = theUMin;
0166       }
0167       if (std::abs(aUs2 - 2.0 * M_PI - theUMin) < aTolU)
0168       {
0169         aUs2 = theUMin;
0170       }
0171     }
0172 
0173     // Step 5: Add extrema (with bounds check if domain specified)
0174     // Skip based on search mode: i=0 is minimum, i=1 is maximum
0175     int aStart = (theMode == ExtremaPC::SearchMode::Max) ? 1 : 0;
0176     int aEnd   = (theMode == ExtremaPC::SearchMode::Min) ? 1 : 2;
0177 
0178     double aSolutions[2] = {aUs1, aUs2};
0179 
0180     for (int i = aStart; i < aEnd; ++i)
0181     {
0182       double aU = aSolutions[i];
0183 
0184       // Check bounds only if domain is specified
0185       if (myDomain.has_value())
0186       {
0187         if (aU < myDomain->Min - aTolU || aU > myDomain->Max + aTolU)
0188           continue;
0189       }
0190 
0191       gp_Pnt aCurvePt = ElCLib::Value(aU, myCircle);
0192 
0193       ExtremaPC::ExtremumResult anExt;
0194       anExt.Parameter      = aU;
0195       anExt.Point          = aCurvePt;
0196       anExt.SquareDistance = theP.SquareDistance(aCurvePt);
0197       anExt.IsMinimum      = (i == 0); // First solution is minimum, second is maximum
0198 
0199       myResult.Extrema.Append(anExt);
0200     }
0201 
0202     myResult.Status = ExtremaPC::Status::OK;
0203     return myResult;
0204   }
0205 
0206   //! Compute extrema between point P and the circle arc including endpoints.
0207   //! Uses domain specified at construction time.
0208   //! @param theP query point
0209   //! @param theTol tolerance for degenerate case detection
0210   //! @param theMode search mode (MinMax, Min, or Max)
0211   //! @return const reference to result containing interior + endpoint extrema or InfiniteSolutions
0212   //! status
0213   [[nodiscard]] const ExtremaPC::Result& PerformWithEndpoints(
0214     const gp_Pnt&         theP,
0215     double                theTol,
0216     ExtremaPC::SearchMode theMode = ExtremaPC::SearchMode::MinMax) const
0217   {
0218     (void)Perform(theP, theTol, theMode);
0219 
0220     // Add endpoints if interior computation succeeded and domain is bounded
0221     if (myResult.Status == ExtremaPC::Status::OK && myDomain.has_value())
0222     {
0223       ExtremaPC::AddEndpointExtrema(myResult, theP, *myDomain, *this, theTol, theMode);
0224     }
0225 
0226     return myResult;
0227   }
0228 
0229   //! Returns the circle geometry.
0230   const gp_Circ& Circle() const { return myCircle; }
0231 
0232 private:
0233   gp_Circ                            myCircle; //!< Circle geometry
0234   std::optional<ExtremaPC::Domain1D> myDomain; //!< Parameter domain (nullopt for full circle)
0235   mutable ExtremaPC::Result          myResult; //!< Reusable result storage
0236 };
0237 
0238 #endif // _ExtremaPC_Circle_HeaderFile