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_Hyperbola_HeaderFile
0015 #define _ExtremaPC_Hyperbola_HeaderFile
0016 
0017 #include <ElCLib.hxx>
0018 #include <ExtremaPC.hxx>
0019 #include <gp_Hypr.hxx>
0020 #include <gp_Pnt.hxx>
0021 #include <gp_Vec.hxx>
0022 #include <MathPoly_Quartic.hxx>
0023 #include <Precision.hxx>
0024 #include <Standard_DefineAlloc.hxx>
0025 
0026 #include <cmath>
0027 #include <optional>
0028 
0029 //! @brief Point-Hyperbola extrema computation.
0030 //!
0031 //! Computes the extrema between a 3D point and a hyperbola.
0032 //! Uses quartic polynomial solving via MathPoly::Quartic with substitution.
0033 //!
0034 //! The algorithm:
0035 //! 1. Projects point P onto the hyperbola plane -> Pp
0036 //! 2. For hyperbola C(u) = (R*cosh(u), r*sinh(u)) with major radius R and minor radius r,
0037 //!    substitutes v = e^u to convert the transcendental equation to a polynomial:
0038 //!    ((R^2 + r^2)/4) * v^4 - ((X*R + Y*r)/2) * v^3 + ((X*R - Y*r)/2) * v - ((R^2 + r^2)/4) = 0
0039 //! 3. Filters positive roots (v > 0) and converts back via u = ln(v)
0040 //!
0041 //! @note A hyperbola can have up to 4 extrema.
0042 //!
0043 //! The domain is fixed at construction time for optimal performance.
0044 //! For infinite hyperbola, construct without domain or with nullopt.
0045 class ExtremaPC_Hyperbola
0046 {
0047 public:
0048   DEFINE_STANDARD_ALLOC
0049 
0050   //! Constructor with hyperbola geometry (infinite).
0051   //! @param[in] theHyperbola the hyperbola to compute extrema for
0052   explicit ExtremaPC_Hyperbola(const gp_Hypr& theHyperbola)
0053       : myHyperbola(theHyperbola),
0054         myDomain(std::nullopt)
0055   {
0056     cacheGeometry();
0057   }
0058 
0059   //! Constructor with hyperbola geometry and parameter domain.
0060   //! @param[in] theHyperbola the hyperbola to compute extrema for
0061   //! @param[in] theDomain parameter domain (fixed for all queries)
0062   ExtremaPC_Hyperbola(const gp_Hypr& theHyperbola, const ExtremaPC::Domain1D& theDomain)
0063       : myHyperbola(theHyperbola),
0064         myDomain(theDomain.IsFinite() ? std::optional<ExtremaPC::Domain1D>(theDomain)
0065                                       : std::nullopt)
0066   {
0067     cacheGeometry();
0068   }
0069 
0070   //! Copy constructor is deleted.
0071   ExtremaPC_Hyperbola(const ExtremaPC_Hyperbola&) = delete;
0072 
0073   //! Copy assignment operator is deleted.
0074   ExtremaPC_Hyperbola& operator=(const ExtremaPC_Hyperbola&) = delete;
0075 
0076   //! Move constructor.
0077   ExtremaPC_Hyperbola(ExtremaPC_Hyperbola&&) = default;
0078 
0079   //! Move assignment operator.
0080   ExtremaPC_Hyperbola& operator=(ExtremaPC_Hyperbola&&) = default;
0081 
0082   //! Evaluates point on hyperbola at parameter using cached geometry.
0083   //! @param theU parameter
0084   //! @return point on hyperbola
0085   gp_Pnt Value(double theU) const
0086   {
0087     // Hyperbola: P(u) = Center + R*cosh(u)*XDir + r*sinh(u)*YDir
0088     const double aCosh  = std::cosh(theU);
0089     const double aSinh  = std::sinh(theU);
0090     const double aRCosh = myMajorR * aCosh;
0091     const double arSinh = myMinorR * aSinh;
0092     return gp_Pnt(myCenterX + aRCosh * myXDirX + arSinh * myYDirX,
0093                   myCenterY + aRCosh * myXDirY + arSinh * myYDirY,
0094                   myCenterZ + aRCosh * myXDirZ + arSinh * myYDirZ);
0095   }
0096 
0097   //! Returns true if domain is bounded.
0098   bool IsBounded() const { return myDomain.has_value(); }
0099 
0100   //! Returns the domain (only valid if IsBounded() is true).
0101   const ExtremaPC::Domain1D& Domain() const { return *myDomain; }
0102 
0103   //! Compute extrema between point P and the hyperbola.
0104   //! Uses domain specified at construction time.
0105   //! @param theP query point
0106   //! @param theTol tolerance for duplicate detection
0107   //! @param theMode search mode (MinMax, Min, or Max)
0108   //! @return const reference to result containing extrema
0109   [[nodiscard]] const ExtremaPC::Result& Perform(
0110     const gp_Pnt&         theP,
0111     double                theTol,
0112     ExtremaPC::SearchMode theMode = ExtremaPC::SearchMode::MinMax) const
0113   {
0114     performCore(theP, myDomain, theTol, theMode);
0115     return myResult;
0116   }
0117 
0118   //! Compute extrema between point P and the hyperbola arc including endpoints.
0119   //! Uses domain specified at construction time.
0120   //! @param theP query point
0121   //! @param theTol tolerance for duplicate detection
0122   //! @param theMode search mode (MinMax, Min, or Max)
0123   //! @return const reference to result containing interior + endpoint extrema
0124   [[nodiscard]] const ExtremaPC::Result& PerformWithEndpoints(
0125     const gp_Pnt&         theP,
0126     double                theTol,
0127     ExtremaPC::SearchMode theMode = ExtremaPC::SearchMode::MinMax) const
0128   {
0129     (void)Perform(theP, theTol, theMode);
0130 
0131     // Add endpoints if interior computation succeeded and domain is bounded
0132     if (myResult.Status == ExtremaPC::Status::OK && myDomain.has_value())
0133     {
0134       ExtremaPC::AddEndpointExtrema(myResult, theP, *myDomain, *this, theTol, theMode);
0135     }
0136 
0137     return myResult;
0138   }
0139 
0140   //! Returns the hyperbola geometry.
0141   const gp_Hypr& Hyperbola() const { return myHyperbola; }
0142 
0143 private:
0144   //! Cache geometry components for fast computation.
0145   void cacheGeometry()
0146   {
0147     const gp_Pnt& aCenter = myHyperbola.Location();
0148     myCenterX             = aCenter.X();
0149     myCenterY             = aCenter.Y();
0150     myCenterZ             = aCenter.Z();
0151 
0152     const gp_Dir aXDir = myHyperbola.XAxis().Direction();
0153     myXDirX            = aXDir.X();
0154     myXDirY            = aXDir.Y();
0155     myXDirZ            = aXDir.Z();
0156 
0157     const gp_Dir aYDir = myHyperbola.YAxis().Direction();
0158     myYDirX            = aYDir.X();
0159     myYDirY            = aYDir.Y();
0160     myYDirZ            = aYDir.Z();
0161 
0162     const gp_Dir& aAxis = myHyperbola.Axis().Direction();
0163     myAxisX             = aAxis.X();
0164     myAxisY             = aAxis.Y();
0165     myAxisZ             = aAxis.Z();
0166 
0167     myMajorR        = myHyperbola.MajorRadius();
0168     myMinorR        = myHyperbola.MinorRadius();
0169     myR2PlusR2Over4 = (myMajorR * myMajorR + myMinorR * myMinorR) / 4.0;
0170   }
0171 
0172   //! Solve the quartic equation and return polynomial result using cached geometry.
0173   MathPoly::PolyResult solveQuartic(const gp_Pnt& theP) const
0174   {
0175     // Vector from center to point
0176     const double aDx = theP.X() - myCenterX;
0177     const double aDy = theP.Y() - myCenterY;
0178     const double aDz = theP.Z() - myCenterZ;
0179 
0180     // Project point P onto the hyperbola plane
0181     // Height = (P - Center) . Axis
0182     const double aHeight = aDx * myAxisX + aDy * myAxisY + aDz * myAxisZ;
0183 
0184     // Projected point Pp = P - Height * Axis
0185     const double aPpX = theP.X() - aHeight * myAxisX;
0186     const double aPpY = theP.Y() - aHeight * myAxisY;
0187     const double aPpZ = theP.Z() - aHeight * myAxisZ;
0188 
0189     // Vector from center to projected point
0190     const double aOPpX = aPpX - myCenterX;
0191     const double aOPpY = aPpY - myCenterY;
0192     const double aOPpZ = aPpZ - myCenterZ;
0193 
0194     // Local coordinates in hyperbola frame
0195     const double aX = aOPpX * myXDirX + aOPpY * myXDirY + aOPpZ * myXDirZ;
0196     const double aY = aOPpX * myYDirX + aOPpY * myYDirY + aOPpZ * myYDirZ;
0197 
0198     // Solve quartic equation in v = e^u
0199     const double aC1 = myR2PlusR2Over4;
0200     const double aC2 = -(aX * myMajorR + aY * myMinorR) / 2.0;
0201     const double aC3 = 0.0;
0202     const double aC4 = (aX * myMajorR - aY * myMinorR) / 2.0;
0203     const double aC5 = -myR2PlusR2Over4;
0204 
0205     return MathPoly::Quartic(aC1, aC2, aC3, aC4, aC5);
0206   }
0207 
0208   //! Core algorithm - finds extrema with optional bounds checking.
0209   //! Stores results in myResult.
0210   //! @param theP query point
0211   //! @param theDomain optional parameter domain (nullopt for unbounded)
0212   //! @param theTol tolerance for duplicate detection
0213   //! @param theMode search mode
0214   void performCore(const gp_Pnt&                             theP,
0215                    const std::optional<ExtremaPC::Domain1D>& theDomain,
0216                    double                                    theTol,
0217                    ExtremaPC::SearchMode                     theMode) const
0218   {
0219     myResult.Clear();
0220 
0221     MathPoly::PolyResult aPolyRes = solveQuartic(theP);
0222 
0223     if (!aPolyRes.IsDone())
0224     {
0225       if (aPolyRes.Status == MathUtils::Status::InfiniteSolutions)
0226       {
0227         myResult.Status                 = ExtremaPC::Status::InfiniteSolutions;
0228         gp_Pnt aPtOnCurve               = Value(0.0);
0229         myResult.InfiniteSquareDistance = theP.SquareDistance(aPtOnCurve);
0230       }
0231       else
0232       {
0233         myResult.Status = ExtremaPC::Status::NumericalError;
0234       }
0235       return;
0236     }
0237 
0238     double aTol2 = theTol * theTol;
0239 
0240     // Process all positive roots (v > 0 required for u = ln(v))
0241     for (size_t i = 0; i < aPolyRes.NbRoots; ++i)
0242     {
0243       double aV = aPolyRes.Roots[i];
0244       if (aV <= 0.0)
0245         continue;
0246 
0247       double aU = std::log(aV);
0248 
0249       // Check bounds if domain is specified
0250       if (theDomain.has_value())
0251       {
0252         if (aU < theDomain->Min || aU > theDomain->Max)
0253           continue;
0254       }
0255 
0256       gp_Pnt aCurvePt = Value(aU);
0257 
0258       // Check for duplicates
0259       bool aDuplicate = false;
0260       for (int j = 0; j < myResult.Extrema.Length(); ++j)
0261       {
0262         if (aCurvePt.SquareDistance(myResult.Extrema.Value(j).Point) < aTol2)
0263         {
0264           aDuplicate = true;
0265           break;
0266         }
0267       }
0268       if (aDuplicate)
0269         continue;
0270 
0271       // Determine if minimum or maximum using neighbor comparison
0272       double aSqDist = theP.SquareDistance(aCurvePt);
0273       double aNeighborU;
0274       if (theDomain.has_value())
0275       {
0276         // For bounded case, choose neighbor direction based on position in domain
0277         aNeighborU = aU + (aU < (theDomain->Min + theDomain->Max) * 0.5 ? 1.0 : -1.0);
0278         aNeighborU = std::max(theDomain->Min, std::min(theDomain->Max, aNeighborU));
0279       }
0280       else
0281       {
0282         // For unbounded case, just use +1.0
0283         aNeighborU = aU + 1.0;
0284       }
0285       gp_Pnt aNeighborPt   = Value(aNeighborU);
0286       double aNeighborDist = theP.SquareDistance(aNeighborPt);
0287       bool   aIsMin        = aSqDist < aNeighborDist;
0288 
0289       // Filter by search mode
0290       if (theMode == ExtremaPC::SearchMode::Min && !aIsMin)
0291         continue;
0292       if (theMode == ExtremaPC::SearchMode::Max && aIsMin)
0293         continue;
0294 
0295       ExtremaPC::ExtremumResult anExt;
0296       anExt.Parameter      = aU;
0297       anExt.Point          = aCurvePt;
0298       anExt.SquareDistance = aSqDist;
0299       anExt.IsMinimum      = aIsMin;
0300 
0301       myResult.Extrema.Append(anExt);
0302     }
0303 
0304     myResult.Status = ExtremaPC::Status::OK;
0305   }
0306 
0307   gp_Hypr                            myHyperbola; //!< Hyperbola geometry
0308   std::optional<ExtremaPC::Domain1D> myDomain;    //!< Parameter domain (nullopt for infinite)
0309   mutable ExtremaPC::Result          myResult;    //!< Reusable result storage
0310 
0311   // Cached geometry components for fast computation
0312   double myCenterX, myCenterY, myCenterZ; //!< Center location
0313   double myXDirX, myXDirY, myXDirZ;       //!< X-axis direction
0314   double myYDirX, myYDirY, myYDirZ;       //!< Y-axis direction
0315   double myAxisX, myAxisY, myAxisZ;       //!< Main axis direction
0316   double myMajorR;                        //!< Major radius
0317   double myMinorR;                        //!< Minor radius
0318   double myR2PlusR2Over4;                 //!< Precomputed (R^2 + r^2)/4
0319 };
0320 
0321 #endif // _ExtremaPC_Hyperbola_HeaderFile