Back to home page

EIC code displayed by LXR

 
 

    


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

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