diff --git a/src/ModelingData/TKBRep/BRepAdaptor/BRepAdaptor_Curve.cxx b/src/ModelingData/TKBRep/BRepAdaptor/BRepAdaptor_Curve.cxx index 292659f8ac8..cfed8892bc6 100644 --- a/src/ModelingData/TKBRep/BRepAdaptor/BRepAdaptor_Curve.cxx +++ b/src/ModelingData/TKBRep/BRepAdaptor/BRepAdaptor_Curve.cxx @@ -20,53 +20,37 @@ #include #include #include -#include -#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include -IMPLEMENT_STANDARD_RTTIEXT(BRepAdaptor_Curve, Adaptor3d_Curve) +IMPLEMENT_STANDARD_RTTIEXT(BRepAdaptor_Curve, GeomAdaptor_TransformedCurve) -//================================================================================================= +//================================================================================================== BRepAdaptor_Curve::BRepAdaptor_Curve() = default; -//================================================================================================= +//================================================================================================== BRepAdaptor_Curve::BRepAdaptor_Curve(const TopoDS_Edge& E) { Initialize(E); } -//================================================================================================= +//================================================================================================== BRepAdaptor_Curve::BRepAdaptor_Curve(const TopoDS_Edge& E, const TopoDS_Face& F) { Initialize(E, F); } -//================================================================================================= +//================================================================================================== occ::handle BRepAdaptor_Curve::ShallowCopy() const { occ::handle aCopy = new BRepAdaptor_Curve(); - aCopy->myTrsf = myTrsf; - const occ::handle aCurve = myCurve.ShallowCopy(); const GeomAdaptor_Curve& aGeomCurve = *(occ::down_cast(aCurve)); aCopy->myCurve = aGeomCurve; @@ -75,12 +59,13 @@ occ::handle BRepAdaptor_Curve::ShallowCopy() const { aCopy->myConSurf = occ::down_cast(myConSurf->ShallowCopy()); } + aCopy->myTrsf = myTrsf; aCopy->myEdge = myEdge; return aCopy; } -//================================================================================================= +//================================================================================================== void BRepAdaptor_Curve::Reset() { @@ -90,7 +75,7 @@ void BRepAdaptor_Curve::Reset() myTrsf = gp_Trsf(); } -//================================================================================================= +//================================================================================================== void BRepAdaptor_Curve::Initialize(const TopoDS_Edge& E) { @@ -127,7 +112,7 @@ void BRepAdaptor_Curve::Initialize(const TopoDS_Edge& E) myTrsf = L.Transformation(); } -//================================================================================================= +//================================================================================================== void BRepAdaptor_Curve::Initialize(const TopoDS_Edge& E, const TopoDS_Face& F) { @@ -149,126 +134,21 @@ void BRepAdaptor_Curve::Initialize(const TopoDS_Edge& E, const TopoDS_Face& F) myTrsf = L.Transformation(); } -//================================================================================================= - -const gp_Trsf& BRepAdaptor_Curve::Trsf() const -{ - return myTrsf; -} - -//================================================================================================= - -bool BRepAdaptor_Curve::Is3DCurve() const -{ - return myConSurf.IsNull(); -} - -//================================================================================================= - -bool BRepAdaptor_Curve::IsCurveOnSurface() const -{ - return !myConSurf.IsNull(); -} - -//================================================================================================= - -const GeomAdaptor_Curve& BRepAdaptor_Curve::Curve() const -{ - return myCurve; -} - -//================================================================================================= - -const Adaptor3d_CurveOnSurface& BRepAdaptor_Curve::CurveOnSurface() const -{ - return *myConSurf; -} - -//================================================================================================= +//================================================================================================== const TopoDS_Edge& BRepAdaptor_Curve::Edge() const { return myEdge; } -//================================================================================================= +//================================================================================================== double BRepAdaptor_Curve::Tolerance() const { return BRep_Tool::Tolerance(myEdge); } -//================================================================================================= - -double BRepAdaptor_Curve::FirstParameter() const -{ - if (myConSurf.IsNull()) - { - return myCurve.FirstParameter(); - } - else - { - return myConSurf->FirstParameter(); - } -} - -//================================================================================================= - -double BRepAdaptor_Curve::LastParameter() const -{ - if (myConSurf.IsNull()) - { - return myCurve.LastParameter(); - } - else - { - return myConSurf->LastParameter(); - } -} - -//================================================================================================= - -GeomAbs_Shape BRepAdaptor_Curve::Continuity() const -{ - if (myConSurf.IsNull()) - { - return myCurve.Continuity(); - } - else - { - return myConSurf->Continuity(); - } -} - -//================================================================================================= - -int BRepAdaptor_Curve::NbIntervals(const GeomAbs_Shape S) const -{ - if (myConSurf.IsNull()) - { - return myCurve.NbIntervals(S); - } - else - { - return myConSurf->NbIntervals(S); - } -} - -//================================================================================================= - -void BRepAdaptor_Curve::Intervals(NCollection_Array1& T, const GeomAbs_Shape S) const -{ - if (myConSurf.IsNull()) - { - myCurve.Intervals(T, S); - } - else - { - myConSurf->Intervals(T, S); - } -} - -//================================================================================================= +//================================================================================================== occ::handle BRepAdaptor_Curve::Trim(const double First, const double Last, @@ -294,301 +174,3 @@ occ::handle BRepAdaptor_Curve::Trim(const double First, } return res; } - -//================================================================================================= - -bool BRepAdaptor_Curve::IsClosed() const -{ - if (myConSurf.IsNull()) - { - return myCurve.IsClosed(); - } - else - { - return myConSurf->IsClosed(); - } -} - -//================================================================================================= - -bool BRepAdaptor_Curve::IsPeriodic() const -{ - if (myConSurf.IsNull()) - { - return myCurve.IsPeriodic(); - } - else - { - return myConSurf->IsPeriodic(); - } -} - -//================================================================================================= - -double BRepAdaptor_Curve::Period() const -{ - if (myConSurf.IsNull()) - { - return myCurve.Period(); - } - else - { - return myConSurf->Period(); - } -} - -//================================================================================================= - -gp_Pnt BRepAdaptor_Curve::Value(const double U) const -{ - gp_Pnt P; - if (myConSurf.IsNull()) - P = myCurve.Value(U); - else - P = myConSurf->Value(U); - P.Transform(myTrsf); - return P; -} - -//================================================================================================= - -void BRepAdaptor_Curve::D0(const double U, gp_Pnt& P) const -{ - if (myConSurf.IsNull()) - myCurve.D0(U, P); - else - myConSurf->D0(U, P); - P.Transform(myTrsf); -} - -//================================================================================================= - -void BRepAdaptor_Curve::D1(const double U, gp_Pnt& P, gp_Vec& V) const -{ - if (myConSurf.IsNull()) - myCurve.D1(U, P, V); - else - myConSurf->D1(U, P, V); - P.Transform(myTrsf); - V.Transform(myTrsf); -} - -//================================================================================================= - -void BRepAdaptor_Curve::D2(const double U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2) const -{ - if (myConSurf.IsNull()) - myCurve.D2(U, P, V1, V2); - else - myConSurf->D2(U, P, V1, V2); - P.Transform(myTrsf); - V1.Transform(myTrsf); - V2.Transform(myTrsf); -} - -//================================================================================================= - -void BRepAdaptor_Curve::D3(const double U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2, gp_Vec& V3) const -{ - if (myConSurf.IsNull()) - myCurve.D3(U, P, V1, V2, V3); - else - myConSurf->D3(U, P, V1, V2, V3); - P.Transform(myTrsf); - V1.Transform(myTrsf); - V2.Transform(myTrsf); - V3.Transform(myTrsf); -} - -//================================================================================================= - -gp_Vec BRepAdaptor_Curve::DN(const double U, const int N) const -{ - gp_Vec V; - if (myConSurf.IsNull()) - V = myCurve.DN(U, N); - else - V = myConSurf->DN(U, N); - V.Transform(myTrsf); - return V; -} - -//================================================================================================= - -double BRepAdaptor_Curve::Resolution(const double R) const -{ - if (myConSurf.IsNull()) - { - return myCurve.Resolution(R); - } - else - { - return myConSurf->Resolution(R); - } -} - -//================================================================================================= - -GeomAbs_CurveType BRepAdaptor_Curve::GetType() const -{ - if (myConSurf.IsNull()) - { - return myCurve.GetType(); - } - else - { - return myConSurf->GetType(); - } -} - -//================================================================================================= - -gp_Lin BRepAdaptor_Curve::Line() const -{ - gp_Lin L; - if (myConSurf.IsNull()) - L = myCurve.Line(); - else - L = myConSurf->Line(); - L.Transform(myTrsf); - return L; -} - -//================================================================================================= - -gp_Circ BRepAdaptor_Curve::Circle() const -{ - gp_Circ C; - if (myConSurf.IsNull()) - C = myCurve.Circle(); - else - C = myConSurf->Circle(); - C.Transform(myTrsf); - return C; -} - -//================================================================================================= - -gp_Elips BRepAdaptor_Curve::Ellipse() const -{ - gp_Elips E; - if (myConSurf.IsNull()) - E = myCurve.Ellipse(); - else - E = myConSurf->Ellipse(); - E.Transform(myTrsf); - return E; -} - -//================================================================================================= - -gp_Hypr BRepAdaptor_Curve::Hyperbola() const -{ - gp_Hypr H; - if (myConSurf.IsNull()) - H = myCurve.Hyperbola(); - else - H = myConSurf->Hyperbola(); - H.Transform(myTrsf); - return H; -} - -//================================================================================================= - -gp_Parab BRepAdaptor_Curve::Parabola() const -{ - gp_Parab P; - if (myConSurf.IsNull()) - P = myCurve.Parabola(); - else - P = myConSurf->Parabola(); - P.Transform(myTrsf); - return P; -} - -//================================================================================================= - -int BRepAdaptor_Curve::Degree() const -{ - if (myConSurf.IsNull()) - return myCurve.Degree(); - else - return myConSurf->Degree(); -} - -//================================================================================================= - -bool BRepAdaptor_Curve::IsRational() const -{ - if (myConSurf.IsNull()) - return myCurve.IsRational(); - else - return myConSurf->IsRational(); -} - -//================================================================================================= - -int BRepAdaptor_Curve::NbPoles() const -{ - if (myConSurf.IsNull()) - return myCurve.NbPoles(); - else - return myConSurf->NbPoles(); -} - -//================================================================================================= - -int BRepAdaptor_Curve::NbKnots() const -{ - if (myConSurf.IsNull()) - return myCurve.NbKnots(); - else - return myConSurf->NbKnots(); -} - -//================================================================================================= - -occ::handle BRepAdaptor_Curve::Bezier() const -{ - occ::handle BC; - if (myConSurf.IsNull()) - { - BC = myCurve.Bezier(); - } - else - { - BC = myConSurf->Bezier(); - } - return myTrsf.Form() == gp_Identity ? BC - : occ::down_cast(BC->Transformed(myTrsf)); -} - -//================================================================================================= - -occ::handle BRepAdaptor_Curve::BSpline() const -{ - occ::handle BS; - if (myConSurf.IsNull()) - { - BS = myCurve.BSpline(); - } - else - { - BS = myConSurf->BSpline(); - } - return myTrsf.Form() == gp_Identity ? BS - : occ::down_cast(BS->Transformed(myTrsf)); -} - -//================================================================================================= - -occ::handle BRepAdaptor_Curve::OffsetCurve() const -{ - if (!Is3DCurve() || myCurve.GetType() != GeomAbs_OffsetCurve) - throw Standard_NoSuchObject("BRepAdaptor_Curve::OffsetCurve"); - - occ::handle anOffC = myCurve.OffsetCurve(); - return myTrsf.Form() == gp_Identity - ? anOffC - : occ::down_cast(anOffC->Transformed(myTrsf)); -} diff --git a/src/ModelingData/TKBRep/BRepAdaptor/BRepAdaptor_Curve.hxx b/src/ModelingData/TKBRep/BRepAdaptor/BRepAdaptor_Curve.hxx index 2dddbfe6fbb..15dabfbeb65 100644 --- a/src/ModelingData/TKBRep/BRepAdaptor/BRepAdaptor_Curve.hxx +++ b/src/ModelingData/TKBRep/BRepAdaptor/BRepAdaptor_Curve.hxx @@ -17,29 +17,10 @@ #ifndef _BRepAdaptor_Curve_HeaderFile #define _BRepAdaptor_Curve_HeaderFile -#include -#include -#include +#include #include -#include -#include -#include -#include -#include -#include class TopoDS_Face; -class Adaptor3d_CurveOnSurface; -class gp_Pnt; -class gp_Vec; -class gp_Lin; -class gp_Circ; -class gp_Elips; -class gp_Hypr; -class gp_Parab; -class Geom_BezierCurve; -class Geom_BSplineCurve; -class Geom_OffsetCurve; //! The Curve from BRepAdaptor allows to use an Edge //! of the BRep topology like a 3D curve. @@ -53,9 +34,9 @@ class Geom_OffsetCurve; //! surface is used. It is possible to enforce using a //! curve on surface by creating or initialising with //! an Edge and a Face. -class BRepAdaptor_Curve : public Adaptor3d_Curve +class BRepAdaptor_Curve : public GeomAdaptor_TransformedCurve { - DEFINE_STANDARD_RTTIEXT(BRepAdaptor_Curve, Adaptor3d_Curve) + DEFINE_STANDARD_RTTIEXT(BRepAdaptor_Curve, GeomAdaptor_TransformedCurve) public: //! Creates an undefined Curve with no Edge loaded. Standard_EXPORT BRepAdaptor_Curve(); @@ -87,136 +68,33 @@ public: //! the face. Standard_EXPORT void Initialize(const TopoDS_Edge& E, const TopoDS_Face& F); - //! Returns the coordinate system of the curve. - Standard_EXPORT const gp_Trsf& Trsf() const; - - //! Returns True if the edge geometry is computed from - //! a 3D curve. - Standard_EXPORT bool Is3DCurve() const; - - //! Returns True if the edge geometry is computed from - //! a pcurve on a surface. - Standard_EXPORT bool IsCurveOnSurface() const; - - //! Returns the Curve of the edge. - Standard_EXPORT const GeomAdaptor_Curve& Curve() const; - - //! Returns the CurveOnSurface of the edge. - Standard_EXPORT const Adaptor3d_CurveOnSurface& CurveOnSurface() const; - //! Returns the edge. Standard_EXPORT const TopoDS_Edge& Edge() const; //! Returns the edge tolerance. Standard_EXPORT double Tolerance() const; - Standard_EXPORT double FirstParameter() const override; - - Standard_EXPORT double LastParameter() const override; - - Standard_EXPORT GeomAbs_Shape Continuity() const override; - - //! Returns the number of intervals for continuity - //! . May be one if Continuity(me) >= - Standard_EXPORT int NbIntervals(const GeomAbs_Shape S) const override; - - //! Stores in the parameters bounding the intervals - //! of continuity . - //! - //! The array must provide enough room to accommodate - //! for the parameters. i.e. T.Length() > NbIntervals() - Standard_EXPORT void Intervals(NCollection_Array1& T, - const GeomAbs_Shape S) const override; - //! Returns a curve equivalent of between //! parameters and . is used to //! test for 3d points confusion. - //! If >= Standard_EXPORT occ::handle Trim(const double First, const double Last, const double Tol) const override; - Standard_EXPORT bool IsClosed() const override; - - Standard_EXPORT bool IsPeriodic() const override; - - Standard_EXPORT double Period() const override; - - //! Computes the point of parameter U on the curve - Standard_EXPORT gp_Pnt Value(const double U) const override; - - //! Computes the point of parameter U. - Standard_EXPORT void D0(const double U, gp_Pnt& P) const override; - - //! Computes the point of parameter U on the curve - //! with its first derivative. - //! Raised if the continuity of the current interval - //! is not C1. - Standard_EXPORT void D1(const double U, gp_Pnt& P, gp_Vec& V) const override; - - //! Returns the point P of parameter U, the first and second - //! derivatives V1 and V2. - //! Raised if the continuity of the current interval - //! is not C2. - Standard_EXPORT void D2(const double U, gp_Pnt& P, gp_Vec& V1, gp_Vec& V2) const override; - - //! Returns the point P of parameter U, the first, the second - //! and the third derivative. - //! Raised if the continuity of the current interval - //! is not C3. - Standard_EXPORT void D3(const double U, - gp_Pnt& P, - gp_Vec& V1, - gp_Vec& V2, - gp_Vec& V3) const override; - - //! The returned vector gives the value of the derivative for the - //! order of derivation N. - //! Raised if the continuity of the current interval - //! is not CN. - //! Raised if N < 1. - Standard_EXPORT gp_Vec DN(const double U, const int N) const override; - - //! returns the parametric resolution - Standard_EXPORT double Resolution(const double R3d) const override; - - Standard_EXPORT GeomAbs_CurveType GetType() const override; - - Standard_EXPORT gp_Lin Line() const override; - - Standard_EXPORT gp_Circ Circle() const override; - - Standard_EXPORT gp_Elips Ellipse() const override; - - Standard_EXPORT gp_Hypr Hyperbola() const override; - - Standard_EXPORT gp_Parab Parabola() const override; - - Standard_EXPORT int Degree() const override; - - Standard_EXPORT bool IsRational() const override; - - Standard_EXPORT int NbPoles() const override; - - Standard_EXPORT int NbKnots() const override; - - //! Warning: - //! This will make a copy of the Bezier Curve since it applies to it myTsrf. - //! Be careful when using this method. - Standard_EXPORT occ::handle Bezier() const override; - - //! Warning: - //! This will make a copy of the BSpline Curve since it applies to it myTsrf. - //! Be careful when using this method. - Standard_EXPORT occ::handle BSpline() const override; - - Standard_EXPORT occ::handle OffsetCurve() const override; + // Note: Most methods are inherited from GeomAdaptor_TransformedCurve. + // The following methods provide access to the underlying curve/transformation: + // - Curve() - returns const GeomAdaptor_Curve& + // - ChangeCurve() - returns GeomAdaptor_Curve& + // - Trsf() - returns const gp_Trsf& + // - Is3DCurve() - returns true if 3D curve is used + // - IsCurveOnSurface() - returns true if COS is used + // - CurveOnSurface() - returns const Adaptor3d_CurveOnSurface& + // + // Value, D0, D1, D2, D3, DN methods are inherited and marked as final. + // They apply the transformation automatically. private: - gp_Trsf myTrsf; - GeomAdaptor_Curve myCurve; - occ::handle myConSurf; - TopoDS_Edge myEdge; + TopoDS_Edge myEdge; }; #endif // _BRepAdaptor_Curve_HeaderFile diff --git a/src/ModelingData/TKG3d/GTests/FILES.cmake b/src/ModelingData/TKG3d/GTests/FILES.cmake index 75951a4c517..641b08fcd5d 100644 --- a/src/ModelingData/TKG3d/GTests/FILES.cmake +++ b/src/ModelingData/TKG3d/GTests/FILES.cmake @@ -27,6 +27,8 @@ set(OCCT_TKG3d_GTests_FILES GeomEval_TBezierSurface_Test.cxx GeomAPI_ExtremaCurveCurve_Test.cxx GeomAPI_Interpolate_Test.cxx + GeomAdaptor_TransformedCurve_Test.cxx + GeomGridEval_CurveOnSurface_Test.cxx GeomGridEval_BezierCurve_Test.cxx GeomGridEval_BezierSurface_Test.cxx GeomGridEval_BSplineSurface_Test.cxx diff --git a/src/ModelingData/TKG3d/GTests/GeomAdaptor_TransformedCurve_Test.cxx b/src/ModelingData/TKG3d/GTests/GeomAdaptor_TransformedCurve_Test.cxx new file mode 100644 index 00000000000..7ef0b7aa269 --- /dev/null +++ b/src/ModelingData/TKG3d/GTests/GeomAdaptor_TransformedCurve_Test.cxx @@ -0,0 +1,1215 @@ +// Copyright (c) 2025 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +const double THE_TOLERANCE = 1e-10; + +//! Helper function to create uniform parameters. +NCollection_Array1 CreateUniformParams(double theFirst, double theLast, int theNbPoints) +{ + NCollection_Array1 aParams(1, theNbPoints); + const double aStep = (theLast - theFirst) / (theNbPoints - 1); + for (int i = 1; i <= theNbPoints; ++i) + { + aParams.SetValue(i, theFirst + (i - 1) * aStep); + } + return aParams; +} + +//! Helper function to create a simple B-spline curve. +occ::handle CreateSimpleBSpline() +{ + NCollection_Array1 aPoles(1, 4); + aPoles.SetValue(1, gp_Pnt(0, 0, 0)); + aPoles.SetValue(2, gp_Pnt(1, 2, 0)); + aPoles.SetValue(3, gp_Pnt(3, 2, 0)); + aPoles.SetValue(4, gp_Pnt(4, 0, 0)); + + NCollection_Array1 aKnots(1, 2); + NCollection_Array1 aMults(1, 2); + aKnots.SetValue(1, 0.0); + aKnots.SetValue(2, 1.0); + aMults.SetValue(1, 4); + aMults.SetValue(2, 4); + + return new Geom_BSplineCurve(aPoles, aKnots, aMults, 3); +} + +//! Helper function to create a translation transformation. +gp_Trsf CreateTranslation(double theDx, double theDy, double theDz) +{ + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(theDx, theDy, theDz)); + return aTrsf; +} + +//! Helper function to create a rotation transformation around Z axis. +gp_Trsf CreateRotationZ(double theAngleRad) +{ + gp_Trsf aTrsf; + aTrsf.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), theAngleRad); + return aTrsf; +} +} // namespace + +//================================================================================================== +// Tests for GeomAdaptor_TransformedCurve construction +//================================================================================================== + +TEST(GeomAdaptor_TransformedCurveTest, DefaultConstructor) +{ + GeomAdaptor_TransformedCurve aCurve; + EXPECT_TRUE(aCurve.Is3DCurve()); + EXPECT_FALSE(aCurve.IsCurveOnSurface()); + EXPECT_EQ(aCurve.Trsf().Form(), gp_Identity); +} + +TEST(GeomAdaptor_TransformedCurveTest, ConstructWithCurveAndTrsf) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aCurve(aLine, aTrsf); + EXPECT_TRUE(aCurve.Is3DCurve()); + EXPECT_EQ(aCurve.GetType(), GeomAbs_Line); + EXPECT_EQ(aCurve.Trsf().Form(), gp_Translation); +} + +TEST(GeomAdaptor_TransformedCurveTest, ConstructWithBounds) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aCurve(aLine, 0.0, 10.0, aTrsf); + EXPECT_NEAR(aCurve.FirstParameter(), 0.0, THE_TOLERANCE); + EXPECT_NEAR(aCurve.LastParameter(), 10.0, THE_TOLERANCE); +} + +//================================================================================================== +// Tests for evaluation with identity transform +//================================================================================================== + +TEST(GeomAdaptor_TransformedCurveTest, Line_IdentityTransform) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(1, 2, 3), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf; // identity + + GeomAdaptor_TransformedCurve aCurve(aLine, aTrsf); + + // With identity transform, results should match direct Geom_Line evaluation + const double aT = 5.0; + gp_Pnt aPnt = aCurve.Value(aT); + gp_Pnt aExpected = aLine->Value(aT); + EXPECT_NEAR(aPnt.Distance(aExpected), 0.0, THE_TOLERANCE); +} + +TEST(GeomAdaptor_TransformedCurveTest, Circle_IdentityTransform) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf; // identity + + GeomAdaptor_TransformedCurve aCurve(aCircle, aTrsf); + EXPECT_EQ(aCurve.GetType(), GeomAbs_Circle); + + NCollection_Array1 aParams(1, 5); + aParams.SetValue(1, 0.0); + aParams.SetValue(2, M_PI / 2); + aParams.SetValue(3, M_PI); + aParams.SetValue(4, 3 * M_PI / 2); + aParams.SetValue(5, 2 * M_PI); + + for (int i = 1; i <= 5; ++i) + { + gp_Pnt aPnt = aCurve.Value(aParams.Value(i)); + gp_Pnt aExpected = aCircle->Value(aParams.Value(i)); + EXPECT_NEAR(aPnt.Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// Tests for evaluation with non-identity transform +//================================================================================================== + +TEST(GeomAdaptor_TransformedCurveTest, Line_Translation) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aCurve(aLine, aTrsf); + + // Line along X at Z=0, translated to Z=5 + gp_Pnt aPnt = aCurve.Value(3.0); + EXPECT_NEAR(aPnt.X(), 3.0, THE_TOLERANCE); + EXPECT_NEAR(aPnt.Y(), 0.0, THE_TOLERANCE); + EXPECT_NEAR(aPnt.Z(), 5.0, THE_TOLERANCE); +} + +TEST(GeomAdaptor_TransformedCurveTest, Line_Rotation) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 2); + + GeomAdaptor_TransformedCurve aCurve(aLine, aTrsf); + + // Line along X rotated 90 degrees -> line along Y + gp_Pnt aPnt = aCurve.Value(3.0); + EXPECT_NEAR(aPnt.X(), 0.0, THE_TOLERANCE); + EXPECT_NEAR(aPnt.Y(), 3.0, THE_TOLERANCE); + EXPECT_NEAR(aPnt.Z(), 0.0, THE_TOLERANCE); +} + +TEST(GeomAdaptor_TransformedCurveTest, Circle_Translation) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateTranslation(10, 20, 30); + + GeomAdaptor_TransformedCurve aCurve(aCircle, aTrsf); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 17); + + for (int i = 1; i <= 17; ++i) + { + gp_Pnt aPnt = aCurve.Value(aParams.Value(i)); + gp_Pnt aLocal = aCircle->Value(aParams.Value(i)); + gp_Pnt aExpected(aLocal.X() + 10, aLocal.Y() + 20, aLocal.Z() + 30); + EXPECT_NEAR(aPnt.Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// Tests for derivative evaluation with transform +//================================================================================================== + +TEST(GeomAdaptor_TransformedCurveTest, D1_Translation) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateTranslation(10, 20, 30); + + GeomAdaptor_TransformedCurve aCurve(aCircle, aTrsf); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 9); + + for (int i = 1; i <= 9; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1; + aCurve.D1(aParams.Value(i), aPnt, aD1); + + // Point should be translated + gp_Pnt aLocalPnt = aCircle->Value(aParams.Value(i)); + gp_Pnt aExpectedPnt(aLocalPnt.X() + 10, aLocalPnt.Y() + 20, aLocalPnt.Z() + 30); + EXPECT_NEAR(aPnt.Distance(aExpectedPnt), 0.0, THE_TOLERANCE); + + // D1 vector: translation doesn't change vectors, so should match local D1 + gp_Pnt aLocalP; + gp_Vec aLocalD1; + aCircle->D1(aParams.Value(i), aLocalP, aLocalD1); + EXPECT_NEAR((aD1 - aLocalD1).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, D1_Rotation) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 2); + + GeomAdaptor_TransformedCurve aCurve(aLine, aTrsf); + + gp_Pnt aPnt; + gp_Vec aD1; + aCurve.D1(3.0, aPnt, aD1); + + // D1 for line along X is (1,0,0), rotated 90 degrees becomes (0,1,0) + EXPECT_NEAR(aD1.X(), 0.0, THE_TOLERANCE); + EXPECT_NEAR(aD1.Y(), 1.0, THE_TOLERANCE); + EXPECT_NEAR(aD1.Z(), 0.0, THE_TOLERANCE); +} + +TEST(GeomAdaptor_TransformedCurveTest, D2_Translation) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateTranslation(10, 20, 30); + + GeomAdaptor_TransformedCurve aCurve(aCircle, aTrsf); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 9); + + for (int i = 1; i <= 9; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2; + aCurve.D2(aParams.Value(i), aPnt, aD1, aD2); + + gp_Pnt aLocalP; + gp_Vec aLocalD1, aLocalD2; + aCircle->D2(aParams.Value(i), aLocalP, aLocalD1, aLocalD2); + + EXPECT_NEAR((aD1 - aLocalD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aD2 - aLocalD2).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, D3_Rotation) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 4); + + GeomAdaptor_TransformedCurve aCurve(aCircle, aTrsf); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 9); + + for (int i = 1; i <= 9; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2, aD3; + aCurve.D3(aParams.Value(i), aPnt, aD1, aD2, aD3); + + // Get local values and transform manually + gp_Pnt aLocalP; + gp_Vec aLocalD1, aLocalD2, aLocalD3; + aCircle->D3(aParams.Value(i), aLocalP, aLocalD1, aLocalD2, aLocalD3); + aLocalP.Transform(aTrsf); + aLocalD1.Transform(aTrsf); + aLocalD2.Transform(aTrsf); + aLocalD3.Transform(aTrsf); + + EXPECT_NEAR(aPnt.Distance(aLocalP), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aD1 - aLocalD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aD2 - aLocalD2).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aD3 - aLocalD3).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, DN_Rotation) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 3); + + GeomAdaptor_TransformedCurve aCurve(aCircle, aTrsf); + + for (int aOrder = 1; aOrder <= 3; ++aOrder) + { + gp_Vec aResult = aCurve.DN(1.0, aOrder); + gp_Vec aExpected = aCircle->DN(1.0, aOrder); + aExpected.Transform(aTrsf); + EXPECT_NEAR((aResult - aExpected).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// Tests for geometry extraction with transform +//================================================================================================== + +TEST(GeomAdaptor_TransformedCurveTest, LineExtraction_Transform) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aCurve(aLine, aTrsf); + + gp_Lin aExtracted = aCurve.Line(); + + // Line should be translated: origin at (0,0,5), direction unchanged + EXPECT_NEAR(aExtracted.Location().X(), 0.0, THE_TOLERANCE); + EXPECT_NEAR(aExtracted.Location().Y(), 0.0, THE_TOLERANCE); + EXPECT_NEAR(aExtracted.Location().Z(), 5.0, THE_TOLERANCE); + EXPECT_NEAR(aExtracted.Direction().X(), 1.0, THE_TOLERANCE); +} + +TEST(GeomAdaptor_TransformedCurveTest, CircleExtraction_Transform) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateTranslation(10, 0, 0); + + GeomAdaptor_TransformedCurve aCurve(aCircle, aTrsf); + + gp_Circ aExtracted = aCurve.Circle(); + + // Circle center should be translated to (10,0,0) + EXPECT_NEAR(aExtracted.Location().X(), 10.0, THE_TOLERANCE); + EXPECT_NEAR(aExtracted.Location().Y(), 0.0, THE_TOLERANCE); + EXPECT_NEAR(aExtracted.Location().Z(), 0.0, THE_TOLERANCE); + EXPECT_NEAR(aExtracted.Radius(), 2.0, THE_TOLERANCE); +} + +//================================================================================================== +// Tests for ShallowCopy +//================================================================================================== + +TEST(GeomAdaptor_TransformedCurveTest, ShallowCopy) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aCurve(aLine, 0.0, 10.0, aTrsf); + + occ::handle aCopy = aCurve.ShallowCopy(); + ASSERT_FALSE(aCopy.IsNull()); + + // Copy should evaluate the same + gp_Pnt aPnt1 = aCurve.Value(3.0); + gp_Pnt aPnt2 = aCopy->Value(3.0); + EXPECT_NEAR(aPnt1.Distance(aPnt2), 0.0, THE_TOLERANCE); +} + +//================================================================================================== +// Tests for GeomGridEval_Curve integration with TransformedCurve +//================================================================================================== + +TEST(GeomAdaptor_TransformedCurveTest, EvalGrid_3DCurve_Identity) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf; // identity + + GeomAdaptor_TransformedCurve aAdaptor(aLine, 0.0, 10.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_Line); + EXPECT_FALSE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 10.0, 11); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + EXPECT_EQ(aGrid.Size(), 11); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aExpected = aLine->Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, EvalGrid_3DCurve_Translation) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aAdaptor(aLine, 0.0, 10.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_Line); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 10.0, 11); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 11; ++i) + { + // Evaluate via TransformedCurve for reference + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, EvalGrid_Circle_Rotation) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 4); + + GeomAdaptor_TransformedCurve aAdaptor(aCircle, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_Circle); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 17); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 17; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, EvalGrid_BSpline_Translation) +{ + occ::handle aBSpline = CreateSimpleBSpline(); + gp_Trsf aTrsf = CreateTranslation(10, 20, 30); + + GeomAdaptor_TransformedCurve aAdaptor(aBSpline, 0.0, 1.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_BSplineCurve); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 21); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 21; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// Tests for derivative grid evaluation with transform +//================================================================================================== + +TEST(GeomAdaptor_TransformedCurveTest, EvalGridD1_Circle_Translation) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateTranslation(10, 20, 30); + + GeomAdaptor_TransformedCurve aAdaptor(aCircle, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 9); + + NCollection_Array1 aGrid = anEval.EvaluateGridD1(aParams); + + for (int i = 1; i <= 9; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1; + aAdaptor.D1(aParams.Value(i), aPnt, aD1); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, EvalGridD2_BSpline_Rotation) +{ + occ::handle aBSpline = CreateSimpleBSpline(); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 3); + + GeomAdaptor_TransformedCurve aAdaptor(aBSpline, 0.0, 1.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 11); + + NCollection_Array1 aGrid = anEval.EvaluateGridD2(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2; + aAdaptor.D2(aParams.Value(i), aPnt, aD1, aD2); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, EvalGridD3_Circle_Rotation) +{ + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 6); + + GeomAdaptor_TransformedCurve aAdaptor(aCircle, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 9); + + NCollection_Array1 aGrid = anEval.EvaluateGridD3(aParams); + + for (int i = 1; i <= 9; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2, aD3; + aAdaptor.D3(aParams.Value(i), aPnt, aD1, aD2, aD3); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D3 - aD3).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomAdaptor_TransformedCurveTest, EvalGridDN_Line_Translation) +{ + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aAdaptor(aLine, 0.0, 10.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 10.0, 6); + + for (int aOrder = 1; aOrder <= 3; ++aOrder) + { + NCollection_Array1 aGrid = anEval.EvaluateGridDN(aParams, aOrder); + + for (int i = 1; i <= 6; ++i) + { + gp_Vec aExpected = aAdaptor.DN(aParams.Value(i), aOrder); + EXPECT_NEAR((aGrid.Value(i) - aExpected).Magnitude(), 0.0, THE_TOLERANCE); + } + } +} + +//================================================================================================== +// Tests for GeomGridEval_Curve with all curve types via TransformedCurve +//================================================================================================== + +TEST(GeomGridEval_CurveTest, TransformedEllipse_RotationTranslation) +{ + occ::handle anEllipse = + new Geom_Ellipse(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 5.0, 3.0); + + gp_Trsf aTrsf; + aTrsf.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), M_PI / 6); + gp_Trsf aTrans = CreateTranslation(10, -5, 7); + aTrsf.Multiply(aTrans); + + GeomAdaptor_TransformedCurve aAdaptor(anEllipse, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_Ellipse); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 25); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 25; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedHyperbola_Translation) +{ + occ::handle aHypr = + new Geom_Hyperbola(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 3.0, 2.0); + gp_Trsf aTrsf = CreateTranslation(5, 10, 15); + + GeomAdaptor_TransformedCurve aAdaptor(aHypr, -2.0, 2.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_Hyperbola); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(-2.0, 2.0, 11); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedParabola_Rotation) +{ + occ::handle aParab = + new Geom_Parabola(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 1.5); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 4); + + GeomAdaptor_TransformedCurve aAdaptor(aParab, -3.0, 3.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_Parabola); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(-3.0, 3.0, 13); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 13; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedBezier_RotationTranslation) +{ + NCollection_Array1 aPoles(1, 4); + aPoles.SetValue(1, gp_Pnt(0, 0, 0)); + aPoles.SetValue(2, gp_Pnt(1, 2, 0)); + aPoles.SetValue(3, gp_Pnt(3, 2, 0)); + aPoles.SetValue(4, gp_Pnt(4, 0, 0)); + occ::handle aBezier = new Geom_BezierCurve(aPoles); + + gp_Trsf aTrsf = CreateRotationZ(M_PI / 3); + gp_Trsf aTrans = CreateTranslation(0, 0, 10); + aTrsf.Multiply(aTrans); + + GeomAdaptor_TransformedCurve aAdaptor(aBezier, 0.0, 1.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_BezierCurve); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 21); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 21; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedOffsetCurve_Translation) +{ + occ::handle aCircle = new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + occ::handle anOffset = new Geom_OffsetCurve(aCircle, 0.5, gp::DZ()); + gp_Trsf aTrsf = CreateTranslation(5, 5, 5); + + GeomAdaptor_TransformedCurve aAdaptor(anOffset, 0.0, 2 * M_PI, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_OffsetCurve); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 17); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 17; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedBSpline_CompoundTransform) +{ + // Test with a compound rotation + translation + scale transform + occ::handle aBSpline = CreateSimpleBSpline(); + + gp_Trsf aRotation; + aRotation.SetRotation(gp_Ax1(gp_Pnt(1, 1, 0), gp_Dir(0, 1, 0)), M_PI / 5); + gp_Trsf aTranslation = CreateTranslation(-3, 7, 2); + gp_Trsf aTrsf = aRotation; + aTrsf.Multiply(aTranslation); + + GeomAdaptor_TransformedCurve aAdaptor(aBSpline, 0.0, 1.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + EXPECT_EQ(anEval.GetType(), GeomAbs_BSplineCurve); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 31); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 31; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// Derivative tests for all curve types with TransformedCurve +//================================================================================================== + +TEST(GeomGridEval_CurveTest, TransformedEllipse_D1) +{ + occ::handle anEllipse = + new Geom_Ellipse(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 5.0, 3.0); + gp_Trsf aTrsf = CreateTranslation(10, -5, 7); + + GeomAdaptor_TransformedCurve aAdaptor(anEllipse, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 13); + + NCollection_Array1 aGrid = anEval.EvaluateGridD1(aParams); + + for (int i = 1; i <= 13; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1; + aAdaptor.D1(aParams.Value(i), aPnt, aD1); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedHyperbola_D2) +{ + occ::handle aHypr = + new Geom_Hyperbola(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 3.0, 2.0); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 4); + + GeomAdaptor_TransformedCurve aAdaptor(aHypr, -2.0, 2.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(-2.0, 2.0, 11); + + NCollection_Array1 aGrid = anEval.EvaluateGridD2(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2; + aAdaptor.D2(aParams.Value(i), aPnt, aD1, aD2); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedParabola_D3) +{ + occ::handle aParab = + new Geom_Parabola(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 1.5); + gp_Trsf aTrsf = CreateRotationZ(M_PI / 3); + gp_Trsf aTrans = CreateTranslation(1, 2, 3); + aTrsf.Multiply(aTrans); + + GeomAdaptor_TransformedCurve aAdaptor(aParab, -3.0, 3.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(-3.0, 3.0, 13); + + NCollection_Array1 aGrid = anEval.EvaluateGridD3(aParams); + + for (int i = 1; i <= 13; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2, aD3; + aAdaptor.D3(aParams.Value(i), aPnt, aD1, aD2, aD3); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D3 - aD3).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedBezier_DN) +{ + NCollection_Array1 aPoles(1, 4); + aPoles.SetValue(1, gp_Pnt(0, 0, 0)); + aPoles.SetValue(2, gp_Pnt(1, 2, 0)); + aPoles.SetValue(3, gp_Pnt(3, 2, 0)); + aPoles.SetValue(4, gp_Pnt(4, 0, 0)); + occ::handle aBezier = new Geom_BezierCurve(aPoles); + + gp_Trsf aTrsf = CreateRotationZ(M_PI / 6); + + GeomAdaptor_TransformedCurve aAdaptor(aBezier, 0.0, 1.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 11); + + for (int aOrder = 1; aOrder <= 3; ++aOrder) + { + NCollection_Array1 aGrid = anEval.EvaluateGridDN(aParams, aOrder); + + for (int i = 1; i <= 11; ++i) + { + gp_Vec aExpected = aAdaptor.DN(aParams.Value(i), aOrder); + EXPECT_NEAR((aGrid.Value(i) - aExpected).Magnitude(), 0.0, THE_TOLERANCE); + } + } +} + +TEST(GeomGridEval_CurveTest, TransformedBSpline_DN_CompoundTransform) +{ + occ::handle aBSpline = CreateSimpleBSpline(); + + gp_Trsf aRotation; + aRotation.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 1, 1)), M_PI / 7); + gp_Trsf aTranslation = CreateTranslation(2, -3, 5); + gp_Trsf aTrsf = aRotation; + aTrsf.Multiply(aTranslation); + + GeomAdaptor_TransformedCurve aAdaptor(aBSpline, 0.0, 1.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 21); + + for (int aOrder = 1; aOrder <= 3; ++aOrder) + { + NCollection_Array1 aGrid = anEval.EvaluateGridDN(aParams, aOrder); + + for (int i = 1; i <= 21; ++i) + { + gp_Vec aExpected = aAdaptor.DN(aParams.Value(i), aOrder); + EXPECT_NEAR((aGrid.Value(i) - aExpected).Magnitude(), 0.0, THE_TOLERANCE); + } + } +} + +//================================================================================================== +// Tests for re-initialization and edge cases +//================================================================================================== + +TEST(GeomGridEval_CurveTest, TransformedCurve_Reinitialize) +{ + // Initialize with a line, then reinitialize with a circle + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf1 = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aAdaptor1(aLine, 0.0, 10.0, aTrsf1); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor1); + EXPECT_EQ(anEval.GetType(), GeomAbs_Line); + EXPECT_TRUE(anEval.HasTransformation()); + + // Re-initialize with a transformed circle + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf2 = CreateRotationZ(M_PI / 4); + + GeomAdaptor_TransformedCurve aAdaptor2(aCircle, aTrsf2); + + anEval.Initialize(aAdaptor2); + EXPECT_EQ(anEval.GetType(), GeomAbs_Circle); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 9); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 9; ++i) + { + gp_Pnt aExpected = aAdaptor2.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedCurve_ReinitializeToIdentity) +{ + // Initialize with transform, then reinitialize with identity + occ::handle aLine = new Geom_Line(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)); + gp_Trsf aTrsf = CreateTranslation(0, 0, 5); + + GeomAdaptor_TransformedCurve aAdaptor1(aLine, 0.0, 10.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor1); + EXPECT_TRUE(anEval.HasTransformation()); + + // Re-initialize with identity transform + gp_Trsf aIdentity; + GeomAdaptor_TransformedCurve aAdaptor2(aLine, 0.0, 10.0, aIdentity); + + anEval.Initialize(aAdaptor2); + EXPECT_FALSE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 10.0, 11); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aExpected = aLine->Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedCurve_InitWithGeomHandle) +{ + // After initializing via TransformedCurve, re-initialize with Geom handle + // to verify that myTrsf is properly cleared + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 2.0); + gp_Trsf aTrsf = CreateTranslation(10, 10, 10); + + GeomAdaptor_TransformedCurve aAdaptor(aCircle, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.HasTransformation()); + + // Re-initialize directly with Geom handle (no transform) + anEval.Initialize(aCircle); + EXPECT_FALSE(anEval.HasTransformation()); + EXPECT_EQ(anEval.GetType(), GeomAbs_Circle); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 9); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 9; ++i) + { + gp_Pnt aExpected = aCircle->Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedTrimmedCurve) +{ + // TrimmedCurve wrapping a circle, passed through TransformedCurve + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 3.0); + occ::handle aTrimmed = new Geom_TrimmedCurve(aCircle, 0.0, M_PI); + gp_Trsf aTrsf = CreateTranslation(1, 2, 3); + + GeomAdaptor_TransformedCurve aAdaptor(aTrimmed, 0.0, M_PI, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + // TrimmedCurve wrapping Circle should be detected as Circle + EXPECT_EQ(anEval.GetType(), GeomAbs_Circle); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, M_PI, 11); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedCircle_AllDerivatives) +{ + // Comprehensive test: all derivative levels with a single compound transform + occ::handle aCircle = + new Geom_Circle(gp_Ax2(gp_Pnt(1, 2, 3), gp_Dir(0, 0, 1)), 4.0); + + gp_Trsf aRotation; + aRotation.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(1, 0, 0)), M_PI / 5); + gp_Trsf aTranslation = CreateTranslation(-7, 3, 11); + gp_Trsf aTrsf = aRotation; + aTrsf.Multiply(aTranslation); + + GeomAdaptor_TransformedCurve aAdaptor(aCircle, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 17); + + // D0 + { + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + for (int i = 1; i <= 17; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } + } + // D1 + { + NCollection_Array1 aGrid = anEval.EvaluateGridD1(aParams); + for (int i = 1; i <= 17; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1; + aAdaptor.D1(aParams.Value(i), aPnt, aD1); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + } + } + // D2 + { + NCollection_Array1 aGrid = anEval.EvaluateGridD2(aParams); + for (int i = 1; i <= 17; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2; + aAdaptor.D2(aParams.Value(i), aPnt, aD1, aD2); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + } + } + // D3 + { + NCollection_Array1 aGrid = anEval.EvaluateGridD3(aParams); + for (int i = 1; i <= 17; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2, aD3; + aAdaptor.D3(aParams.Value(i), aPnt, aD1, aD2, aD3); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D3 - aD3).Magnitude(), 0.0, THE_TOLERANCE); + } + } + // DN orders 1-3 + for (int aOrder = 1; aOrder <= 3; ++aOrder) + { + NCollection_Array1 aGrid = anEval.EvaluateGridDN(aParams, aOrder); + for (int i = 1; i <= 17; ++i) + { + gp_Vec aExpected = aAdaptor.DN(aParams.Value(i), aOrder); + EXPECT_NEAR((aGrid.Value(i) - aExpected).Magnitude(), 0.0, THE_TOLERANCE); + } + } +} + +TEST(GeomGridEval_CurveTest, TransformedLine_NonOriginAxis) +{ + // Line not at origin, with a rotation around a non-origin axis + occ::handle aLine = new Geom_Line(gp_Pnt(5, 3, 0), gp_Dir(0, 1, 0)); + + gp_Trsf aTrsf; + aTrsf.SetRotation(gp_Ax1(gp_Pnt(5, 0, 0), gp_Dir(0, 0, 1)), M_PI / 2); + + GeomAdaptor_TransformedCurve aAdaptor(aLine, -5.0, 5.0, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + EXPECT_EQ(anEval.GetType(), GeomAbs_Line); + EXPECT_TRUE(anEval.HasTransformation()); + + NCollection_Array1 aParams = CreateUniformParams(-5.0, 5.0, 11); + + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aExpected = aAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveTest, TransformedEllipse_AllDerivatives) +{ + occ::handle anEllipse = + new Geom_Ellipse(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 5.0, 3.0); + + gp_Trsf aTrsf; + aTrsf.SetRotation(gp_Ax1(gp_Pnt(0, 0, 0), gp_Dir(0, 1, 0)), M_PI / 4); + gp_Trsf aTrans = CreateTranslation(2, -1, 4); + aTrsf.Multiply(aTrans); + + GeomAdaptor_TransformedCurve aAdaptor(anEllipse, aTrsf); + + GeomGridEval_Curve anEval; + anEval.Initialize(aAdaptor); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 13); + + // D1 + { + NCollection_Array1 aGrid = anEval.EvaluateGridD1(aParams); + for (int i = 1; i <= 13; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1; + aAdaptor.D1(aParams.Value(i), aPnt, aD1); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + } + } + // D2 + { + NCollection_Array1 aGrid = anEval.EvaluateGridD2(aParams); + for (int i = 1; i <= 13; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2; + aAdaptor.D2(aParams.Value(i), aPnt, aD1, aD2); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + } + } + // D3 + { + NCollection_Array1 aGrid = anEval.EvaluateGridD3(aParams); + for (int i = 1; i <= 13; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2, aD3; + aAdaptor.D3(aParams.Value(i), aPnt, aD1, aD2, aD3); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D3 - aD3).Magnitude(), 0.0, THE_TOLERANCE); + } + } + // DN + for (int aOrder = 1; aOrder <= 3; ++aOrder) + { + NCollection_Array1 aGrid = anEval.EvaluateGridDN(aParams, aOrder); + for (int i = 1; i <= 13; ++i) + { + gp_Vec aExpected = aAdaptor.DN(aParams.Value(i), aOrder); + EXPECT_NEAR((aGrid.Value(i) - aExpected).Magnitude(), 0.0, THE_TOLERANCE); + } + } +} diff --git a/src/ModelingData/TKG3d/GTests/GeomGridEval_CurveOnSurface_Test.cxx b/src/ModelingData/TKG3d/GTests/GeomGridEval_CurveOnSurface_Test.cxx new file mode 100644 index 00000000000..1696b868863 --- /dev/null +++ b/src/ModelingData/TKG3d/GTests/GeomGridEval_CurveOnSurface_Test.cxx @@ -0,0 +1,457 @@ +// Copyright (c) 2025 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +const double THE_TOLERANCE = 1e-8; + +//! Helper function to create uniform parameters. +NCollection_Array1 CreateUniformParams(double theFirst, double theLast, int theNbPoints) +{ + NCollection_Array1 aParams(1, theNbPoints); + const double aStep = (theLast - theFirst) / (theNbPoints - 1); + for (int i = 1; i <= theNbPoints; ++i) + { + aParams.SetValue(i, theFirst + (i - 1) * aStep); + } + return aParams; +} + +//! Create a CurveOnSurface adaptor from a 2D curve on a surface. +occ::handle CreateCOS( + const occ::handle& theCurve2d, + const occ::handle& theSurface, + double theFirst, + double theLast) +{ + occ::handle aC2dAdaptor = + new Geom2dAdaptor_Curve(theCurve2d, theFirst, theLast); + occ::handle aSurfAdaptor = new GeomAdaptor_Surface(theSurface); + return new Adaptor3d_CurveOnSurface(aC2dAdaptor, aSurfAdaptor); +} + +//! Create a simple B-spline surface for testing. +occ::handle CreateSimpleBSplineSurface() +{ + NCollection_Array2 aPoles(1, 4, 1, 4); + for (int iU = 1; iU <= 4; ++iU) + { + for (int iV = 1; iV <= 4; ++iV) + { + double aX = (iU - 1) * 1.0; + double aY = (iV - 1) * 1.0; + double aZ = 0.5 * std::sin(aX) * std::cos(aY); + aPoles.SetValue(iU, iV, gp_Pnt(aX, aY, aZ)); + } + } + + NCollection_Array1 aUKnots(1, 2); + NCollection_Array1 aVKnots(1, 2); + NCollection_Array1 aUMults(1, 2); + NCollection_Array1 aVMults(1, 2); + aUKnots.SetValue(1, 0.0); + aUKnots.SetValue(2, 1.0); + aVKnots.SetValue(1, 0.0); + aVKnots.SetValue(2, 1.0); + aUMults.SetValue(1, 4); + aUMults.SetValue(2, 4); + aVMults.SetValue(1, 4); + aVMults.SetValue(2, 4); + + return new Geom_BSplineSurface(aPoles, aUKnots, aVKnots, aUMults, aVMults, 3, 3); +} + +//! Create a simple 2D B-spline curve for testing. +occ::handle CreateSimple2dBSpline() +{ + NCollection_Array1 aPoles(1, 4); + aPoles.SetValue(1, gp_Pnt2d(0.1, 0.1)); + aPoles.SetValue(2, gp_Pnt2d(0.3, 0.8)); + aPoles.SetValue(3, gp_Pnt2d(0.7, 0.2)); + aPoles.SetValue(4, gp_Pnt2d(0.9, 0.9)); + + NCollection_Array1 aKnots(1, 2); + NCollection_Array1 aMults(1, 2); + aKnots.SetValue(1, 0.0); + aKnots.SetValue(2, 1.0); + aMults.SetValue(1, 4); + aMults.SetValue(2, 4); + + return new Geom2d_BSplineCurve(aPoles, aKnots, aMults, 3); +} + +} // namespace + +//================================================================================================== +// D0 Tests +//================================================================================================== + +TEST(GeomGridEval_CurveOnSurfaceTest, LineOnPlane) +{ + // 2D line on XY plane + occ::handle aPlane = new Geom_Plane(gp_Pln()); + occ::handle aLine2d = new Geom2d_Line(gp_Pnt2d(0, 0), gp_Dir2d(1, 1)); + + occ::handle aCOS = CreateCOS(aLine2d, aPlane, 0.0, 5.0); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 5.0, 11); + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + EXPECT_EQ(aGrid.Size(), 11); + + // Verify against Adaptor3d_CurveOnSurface + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aExpected = aCOS->Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveOnSurfaceTest, CircleOnPlane) +{ + // 2D circle on XY plane + occ::handle aPlane = new Geom_Plane(gp_Pln()); + occ::handle aCircle2d = new Geom2d_Circle(gp_Ax2d(gp_Pnt2d(0, 0), gp_Dir2d(1, 0)), 2.0); + + occ::handle aCOS = CreateCOS(aCircle2d, aPlane, 0.0, 2 * M_PI); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 17); + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 17; ++i) + { + gp_Pnt aExpected = aCOS->Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveOnSurfaceTest, LineOnCylinder) +{ + // V-line on cylinder (gives a ruling line) + occ::handle aCyl = + new Geom_CylindricalSurface(gp_Ax3(), 1.0); + // Line along V direction at u=PI/4 + occ::handle aLine2d = + new Geom2d_Line(gp_Pnt2d(M_PI / 4, 0), gp_Dir2d(0, 1)); + + occ::handle aCOS = CreateCOS(aLine2d, aCyl, 0.0, 5.0); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 5.0, 11); + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aExpected = aCOS->Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveOnSurfaceTest, LineOnSphere) +{ + // Meridian on sphere (line along V at fixed U) + occ::handle aSph = + new Geom_SphericalSurface(gp_Ax3(), 2.0); + occ::handle aLine2d = + new Geom2d_Line(gp_Pnt2d(M_PI / 3, -M_PI / 4), gp_Dir2d(0, 1)); + + occ::handle aCOS = + CreateCOS(aLine2d, aSph, 0.0, M_PI / 2); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, M_PI / 2, 11); + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aExpected = aCOS->Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveOnSurfaceTest, BSplineOnBSplineSurface) +{ + occ::handle aSurf = CreateSimpleBSplineSurface(); + occ::handle aCurve2d = CreateSimple2dBSpline(); + + occ::handle aCOS = CreateCOS(aCurve2d, aSurf, 0.0, 1.0); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 21); + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + + for (int i = 1; i <= 21; ++i) + { + gp_Pnt aExpected = aCOS->Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// D1 Tests +//================================================================================================== + +TEST(GeomGridEval_CurveOnSurfaceTest, D1_LineOnPlane) +{ + occ::handle aPlane = new Geom_Plane(gp_Pln()); + occ::handle aLine2d = new Geom2d_Line(gp_Pnt2d(1, 0), gp_Dir2d(0.6, 0.8)); + + occ::handle aCOS = CreateCOS(aLine2d, aPlane, 0.0, 3.0); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 3.0, 9); + NCollection_Array1 aGrid = anEval.EvaluateGridD1(aParams); + + for (int i = 1; i <= 9; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1; + aCOS->D1(aParams.Value(i), aPnt, aD1); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// D2 Tests +//================================================================================================== + +TEST(GeomGridEval_CurveOnSurfaceTest, D2_CircleOnSphere) +{ + occ::handle aSph = + new Geom_SphericalSurface(gp_Ax3(), 3.0); + // Parallel circle on sphere (line along U at fixed V) + occ::handle aLine2d = + new Geom2d_Line(gp_Pnt2d(0, M_PI / 6), gp_Dir2d(1, 0)); + + occ::handle aCOS = + CreateCOS(aLine2d, aSph, 0.0, 2 * M_PI); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 2 * M_PI, 13); + NCollection_Array1 aGrid = anEval.EvaluateGridD2(aParams); + + for (int i = 1; i <= 13; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2; + aCOS->D2(aParams.Value(i), aPnt, aD1, aD2); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// D3 Tests +//================================================================================================== + +TEST(GeomGridEval_CurveOnSurfaceTest, D3_BSplineOnCylinder) +{ + occ::handle aCyl = + new Geom_CylindricalSurface(gp_Ax3(), 2.0); + + // 2D B-spline curve on cylinder surface + NCollection_Array1 aPoles(1, 4); + aPoles.SetValue(1, gp_Pnt2d(0.0, 0.0)); + aPoles.SetValue(2, gp_Pnt2d(M_PI / 2, 1.0)); + aPoles.SetValue(3, gp_Pnt2d(M_PI, 2.0)); + aPoles.SetValue(4, gp_Pnt2d(3 * M_PI / 2, 1.0)); + + NCollection_Array1 aKnots(1, 2); + NCollection_Array1 aMults(1, 2); + aKnots.SetValue(1, 0.0); + aKnots.SetValue(2, 1.0); + aMults.SetValue(1, 4); + aMults.SetValue(2, 4); + + occ::handle aCurve2d = new Geom2d_BSplineCurve(aPoles, aKnots, aMults, 3); + + occ::handle aCOS = CreateCOS(aCurve2d, aCyl, 0.0, 1.0); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 11); + NCollection_Array1 aGrid = anEval.EvaluateGridD3(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2, aD3; + aCOS->D3(aParams.Value(i), aPnt, aD1, aD2, aD3); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D3 - aD3).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +//================================================================================================== +// DN Tests +//================================================================================================== + +TEST(GeomGridEval_CurveOnSurfaceTest, DN) +{ + occ::handle aCyl = + new Geom_CylindricalSurface(gp_Ax3(), 1.5); + occ::handle aLine2d = + new Geom2d_Line(gp_Pnt2d(0, 0), gp_Dir2d(1, 0.5)); + + occ::handle aCOS = CreateCOS(aLine2d, aCyl, 0.0, 3.0); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 3.0, 9); + + // Test DN for orders 1-3 + for (int aOrder = 1; aOrder <= 3; ++aOrder) + { + NCollection_Array1 aGrid = anEval.EvaluateGridDN(aParams, aOrder); + EXPECT_EQ(aGrid.Size(), 9); + + for (int i = 1; i <= 9; ++i) + { + gp_Vec aExpected = aCOS->DN(aParams.Value(i), aOrder); + EXPECT_NEAR((aGrid.Value(i) - aExpected).Magnitude(), 0.0, THE_TOLERANCE); + } + } +} + +//================================================================================================== +// Integration test: via TransformedCurve / BRepAdaptor_Curve +//================================================================================================== + +TEST(GeomGridEval_CurveOnSurfaceTest, ViaTransformedCurve) +{ + // Build a face with a curved edge whose representation is curve-on-surface. + // A cylindrical face with a non-trivial pcurve. + occ::handle aCyl = + new Geom_CylindricalSurface(gp_Ax3(gp_Pnt(1, 2, 3), gp_Dir(0, 0, 1)), 2.0); + + // Create a 2D curve (spiral-like on cylinder parametric space) + NCollection_Array1 aPoles(1, 4); + aPoles.SetValue(1, gp_Pnt2d(0.0, 0.0)); + aPoles.SetValue(2, gp_Pnt2d(M_PI / 3, 1.5)); + aPoles.SetValue(3, gp_Pnt2d(2 * M_PI / 3, 3.0)); + aPoles.SetValue(4, gp_Pnt2d(M_PI, 4.0)); + + NCollection_Array1 aKnots(1, 2); + NCollection_Array1 aMults(1, 2); + aKnots.SetValue(1, 0.0); + aKnots.SetValue(2, 1.0); + aMults.SetValue(1, 4); + aMults.SetValue(2, 4); + + occ::handle aCurve2d = new Geom2d_BSplineCurve(aPoles, aKnots, aMults, 3); + + // Build edge with pcurve on the cylindrical face + TopoDS_Face aFace; + BRep_Builder aBuilder; + aBuilder.MakeFace(aFace, aCyl, Precision::Confusion()); + + BRepBuilderAPI_MakeEdge aMakeEdge(aCurve2d, aCyl); + ASSERT_TRUE(aMakeEdge.IsDone()); + TopoDS_Edge anEdge = aMakeEdge.Edge(); + + // Use BRepAdaptor_Curve which internally creates a TransformedCurve with COS + BRepAdaptor_Curve aBRepAdaptor(anEdge, aFace); + + // Test via GeomGridEval_Curve unified dispatcher + GeomGridEval_Curve anEval; + anEval.Initialize(aBRepAdaptor); + EXPECT_TRUE(anEval.IsInitialized()); + + NCollection_Array1 aParams = + CreateUniformParams(aBRepAdaptor.FirstParameter(), aBRepAdaptor.LastParameter(), 15); + + // Verify D0 + NCollection_Array1 aGrid = anEval.EvaluateGrid(aParams); + for (int i = 1; i <= 15; ++i) + { + gp_Pnt aExpected = aBRepAdaptor.Value(aParams.Value(i)); + EXPECT_NEAR(aGrid.Value(i).Distance(aExpected), 0.0, THE_TOLERANCE); + } + + // Verify D1 + NCollection_Array1 aGridD1 = anEval.EvaluateGridD1(aParams); + for (int i = 1; i <= 15; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1; + aBRepAdaptor.D1(aParams.Value(i), aPnt, aD1); + EXPECT_NEAR(aGridD1.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGridD1.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + } +} + +TEST(GeomGridEval_CurveOnSurfaceTest, D3_BSplineOnBSplineSurface) +{ + occ::handle aSurf = CreateSimpleBSplineSurface(); + occ::handle aCurve2d = CreateSimple2dBSpline(); + + occ::handle aCOS = CreateCOS(aCurve2d, aSurf, 0.0, 1.0); + + GeomGridEval_CurveOnSurface anEval(aCOS->GetCurve(), aCOS->GetSurface()); + + NCollection_Array1 aParams = CreateUniformParams(0.0, 1.0, 11); + NCollection_Array1 aGrid = anEval.EvaluateGridD3(aParams); + + for (int i = 1; i <= 11; ++i) + { + gp_Pnt aPnt; + gp_Vec aD1, aD2, aD3; + aCOS->D3(aParams.Value(i), aPnt, aD1, aD2, aD3); + EXPECT_NEAR(aGrid.Value(i).Point.Distance(aPnt), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D1 - aD1).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D2 - aD2).Magnitude(), 0.0, THE_TOLERANCE); + EXPECT_NEAR((aGrid.Value(i).D3 - aD3).Magnitude(), 0.0, THE_TOLERANCE); + } +} diff --git a/src/ModelingData/TKG3d/GeomAdaptor/FILES.cmake b/src/ModelingData/TKG3d/GeomAdaptor/FILES.cmake index d4db65237fc..2099c5f814b 100644 --- a/src/ModelingData/TKG3d/GeomAdaptor/FILES.cmake +++ b/src/ModelingData/TKG3d/GeomAdaptor/FILES.cmake @@ -12,6 +12,8 @@ set(OCCT_GeomAdaptor_FILES GeomAdaptor_SurfaceOfLinearExtrusion.hxx GeomAdaptor_SurfaceOfRevolution.cxx GeomAdaptor_SurfaceOfRevolution.hxx + GeomAdaptor_TransformedCurve.cxx + GeomAdaptor_TransformedCurve.hxx GeomAdaptor_TransformedSurface.cxx GeomAdaptor_TransformedSurface.hxx ) diff --git a/src/ModelingData/TKG3d/GeomAdaptor/GeomAdaptor_TransformedCurve.cxx b/src/ModelingData/TKG3d/GeomAdaptor/GeomAdaptor_TransformedCurve.cxx new file mode 100644 index 00000000000..49a05bca51e --- /dev/null +++ b/src/ModelingData/TKG3d/GeomAdaptor/GeomAdaptor_TransformedCurve.cxx @@ -0,0 +1,284 @@ +// Copyright (c) 2025 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 + +#include +#include +#include +#include + +IMPLEMENT_STANDARD_RTTIEXT(GeomAdaptor_TransformedCurve, Adaptor3d_Curve) + +//================================================================================================== + +GeomAdaptor_TransformedCurve::GeomAdaptor_TransformedCurve() = default; + +//================================================================================================== + +GeomAdaptor_TransformedCurve::GeomAdaptor_TransformedCurve( + const occ::handle& theCurve, + const gp_Trsf& theTrsf) + : myCurve(theCurve), + myTrsf(theTrsf) +{ +} + +//================================================================================================== + +GeomAdaptor_TransformedCurve::GeomAdaptor_TransformedCurve( + const occ::handle& theCurve, + const double theFirst, + const double theLast, + const gp_Trsf& theTrsf) + : myCurve(theCurve, theFirst, theLast), + myTrsf(theTrsf) +{ +} + +//================================================================================================== + +occ::handle GeomAdaptor_TransformedCurve::ShallowCopy() const +{ + occ::handle aCopy = new GeomAdaptor_TransformedCurve(); + + const occ::handle aCurve = myCurve.ShallowCopy(); + const GeomAdaptor_Curve& aGeomCurve = *occ::down_cast(aCurve); + aCopy->myCurve = aGeomCurve; + + if (!myConSurf.IsNull()) + { + aCopy->myConSurf = occ::down_cast(myConSurf->ShallowCopy()); + } + aCopy->myTrsf = myTrsf; + + return aCopy; +} + +//================================================================================================== + +void GeomAdaptor_TransformedCurve::Intervals(NCollection_Array1& theT, + const GeomAbs_Shape theS) const +{ + if (myConSurf.IsNull()) + myCurve.Intervals(theT, theS); + else + myConSurf->Intervals(theT, theS); +} + +//================================================================================================== + +occ::handle GeomAdaptor_TransformedCurve::Trim(const double theFirst, + const double theLast, + const double theTol) const +{ + occ::handle aCopy = new GeomAdaptor_TransformedCurve(); + if (myConSurf.IsNull()) + { + aCopy->myCurve.Load(myCurve.Curve(), theFirst, theLast); + } + else + { + aCopy->myConSurf = + occ::down_cast(myConSurf->Trim(theFirst, theLast, theTol)); + } + aCopy->myTrsf = myTrsf; + return aCopy; +} + +//================================================================================================== + +gp_Pnt GeomAdaptor_TransformedCurve::Value(const double theU) const +{ + gp_Pnt aP; + if (myConSurf.IsNull()) + aP = myCurve.Value(theU); + else + aP = myConSurf->Value(theU); + aP.Transform(myTrsf); + return aP; +} + +//================================================================================================== + +void GeomAdaptor_TransformedCurve::D0(const double theU, gp_Pnt& theP) const +{ + if (myConSurf.IsNull()) + myCurve.D0(theU, theP); + else + myConSurf->D0(theU, theP); + theP.Transform(myTrsf); +} + +//================================================================================================== + +void GeomAdaptor_TransformedCurve::D1(const double theU, gp_Pnt& theP, gp_Vec& theV) const +{ + if (myConSurf.IsNull()) + myCurve.D1(theU, theP, theV); + else + myConSurf->D1(theU, theP, theV); + theP.Transform(myTrsf); + theV.Transform(myTrsf); +} + +//================================================================================================== + +void GeomAdaptor_TransformedCurve::D2(const double theU, + gp_Pnt& theP, + gp_Vec& theV1, + gp_Vec& theV2) const +{ + if (myConSurf.IsNull()) + myCurve.D2(theU, theP, theV1, theV2); + else + myConSurf->D2(theU, theP, theV1, theV2); + theP.Transform(myTrsf); + theV1.Transform(myTrsf); + theV2.Transform(myTrsf); +} + +//================================================================================================== + +void GeomAdaptor_TransformedCurve::D3(const double theU, + gp_Pnt& theP, + gp_Vec& theV1, + gp_Vec& theV2, + gp_Vec& theV3) const +{ + if (myConSurf.IsNull()) + myCurve.D3(theU, theP, theV1, theV2, theV3); + else + myConSurf->D3(theU, theP, theV1, theV2, theV3); + theP.Transform(myTrsf); + theV1.Transform(myTrsf); + theV2.Transform(myTrsf); + theV3.Transform(myTrsf); +} + +//================================================================================================== + +gp_Vec GeomAdaptor_TransformedCurve::DN(const double theU, const int theN) const +{ + gp_Vec aV; + if (myConSurf.IsNull()) + aV = myCurve.DN(theU, theN); + else + aV = myConSurf->DN(theU, theN); + aV.Transform(myTrsf); + return aV; +} + +//================================================================================================== + +gp_Lin GeomAdaptor_TransformedCurve::Line() const +{ + gp_Lin aL; + if (myConSurf.IsNull()) + aL = myCurve.Line(); + else + aL = myConSurf->Line(); + aL.Transform(myTrsf); + return aL; +} + +//================================================================================================== + +gp_Circ GeomAdaptor_TransformedCurve::Circle() const +{ + gp_Circ aC; + if (myConSurf.IsNull()) + aC = myCurve.Circle(); + else + aC = myConSurf->Circle(); + aC.Transform(myTrsf); + return aC; +} + +//================================================================================================== + +gp_Elips GeomAdaptor_TransformedCurve::Ellipse() const +{ + gp_Elips aE; + if (myConSurf.IsNull()) + aE = myCurve.Ellipse(); + else + aE = myConSurf->Ellipse(); + aE.Transform(myTrsf); + return aE; +} + +//================================================================================================== + +gp_Hypr GeomAdaptor_TransformedCurve::Hyperbola() const +{ + gp_Hypr aH; + if (myConSurf.IsNull()) + aH = myCurve.Hyperbola(); + else + aH = myConSurf->Hyperbola(); + aH.Transform(myTrsf); + return aH; +} + +//================================================================================================== + +gp_Parab GeomAdaptor_TransformedCurve::Parabola() const +{ + gp_Parab aP; + if (myConSurf.IsNull()) + aP = myCurve.Parabola(); + else + aP = myConSurf->Parabola(); + aP.Transform(myTrsf); + return aP; +} + +//================================================================================================== + +occ::handle GeomAdaptor_TransformedCurve::Bezier() const +{ + occ::handle aBC; + if (myConSurf.IsNull()) + aBC = myCurve.Bezier(); + else + aBC = myConSurf->Bezier(); + return myTrsf.Form() == gp_Identity ? aBC + : occ::down_cast(aBC->Transformed(myTrsf)); +} + +//================================================================================================== + +occ::handle GeomAdaptor_TransformedCurve::BSpline() const +{ + occ::handle aBS; + if (myConSurf.IsNull()) + aBS = myCurve.BSpline(); + else + aBS = myConSurf->BSpline(); + return myTrsf.Form() == gp_Identity ? aBS + : occ::down_cast(aBS->Transformed(myTrsf)); +} + +//================================================================================================== + +occ::handle GeomAdaptor_TransformedCurve::OffsetCurve() const +{ + if (!Is3DCurve() || myCurve.GetType() != GeomAbs_OffsetCurve) + throw Standard_NoSuchObject("GeomAdaptor_TransformedCurve::OffsetCurve"); + + occ::handle anOffC = myCurve.OffsetCurve(); + return myTrsf.Form() == gp_Identity + ? anOffC + : occ::down_cast(anOffC->Transformed(myTrsf)); +} diff --git a/src/ModelingData/TKG3d/GeomAdaptor/GeomAdaptor_TransformedCurve.hxx b/src/ModelingData/TKG3d/GeomAdaptor/GeomAdaptor_TransformedCurve.hxx new file mode 100644 index 00000000000..1dfc3c6ce11 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomAdaptor/GeomAdaptor_TransformedCurve.hxx @@ -0,0 +1,228 @@ +// Copyright (c) 2025 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. + +#ifndef _GeomAdaptor_TransformedCurve_HeaderFile +#define _GeomAdaptor_TransformedCurve_HeaderFile + +#include +#include +#include +#include + +//! An adaptor for curves with an applied transformation. +//! +//! This class wraps a GeomAdaptor_Curve (or an Adaptor3d_CurveOnSurface) and +//! applies a gp_Trsf transformation to all point and derivative evaluations. +//! It serves as a base class for BRepAdaptor_Curve and allows batch evaluation +//! with transformations in GeomGridEval_Curve. +//! +//! The evaluation methods (Value, D0, D1, D2, D3, DN) are marked final +//! to enable optimizations in grid evaluation. +class GeomAdaptor_TransformedCurve : public Adaptor3d_Curve +{ + DEFINE_STANDARD_RTTIEXT(GeomAdaptor_TransformedCurve, Adaptor3d_Curve) +public: + //! Creates an undefined curve with identity transformation. + Standard_EXPORT GeomAdaptor_TransformedCurve(); + + //! Creates a curve adaptor with transformation. + //! @param theCurve underlying geometry + //! @param theTrsf transformation to apply + Standard_EXPORT GeomAdaptor_TransformedCurve(const occ::handle& theCurve, + const gp_Trsf& theTrsf); + + //! Creates a curve adaptor with transformation and parameter bounds. + //! @param theCurve underlying geometry + //! @param theFirst minimum parameter + //! @param theLast maximum parameter + //! @param theTrsf transformation to apply + Standard_EXPORT GeomAdaptor_TransformedCurve(const occ::handle& theCurve, + const double theFirst, + const double theLast, + const gp_Trsf& theTrsf); + + //! Shallow copy of adaptor. + Standard_EXPORT occ::handle ShallowCopy() const override; + + //! Loads the curve geometry. + //! @param theCurve underlying geometry + void Load(const occ::handle& theCurve) { myCurve.Load(theCurve); } + + //! Loads the curve geometry with parameter bounds. + //! @param theCurve underlying geometry + //! @param theFirst minimum parameter + //! @param theLast maximum parameter + void Load(const occ::handle& theCurve, const double theFirst, const double theLast) + { + myCurve.Load(theCurve, theFirst, theLast); + } + + //! Sets the curve on surface adaptor. + //! @param theConSurf curve on surface adaptor + void LoadCurveOnSurface(const occ::handle& theConSurf) + { + myConSurf = theConSurf; + } + + //! Sets the transformation. + //! @param theTrsf transformation to apply + void SetTrsf(const gp_Trsf& theTrsf) { myTrsf = theTrsf; } + + //! Returns the transformation. + const gp_Trsf& Trsf() const { return myTrsf; } + + //! Returns true if the geometry is a 3D curve (not curve on surface). + bool Is3DCurve() const { return myConSurf.IsNull(); } + + //! Returns true if the geometry is a curve on surface. + bool IsCurveOnSurface() const { return !myConSurf.IsNull(); } + + //! Returns the underlying GeomAdaptor_Curve. + const GeomAdaptor_Curve& Curve() const { return myCurve; } + + //! Returns the underlying GeomAdaptor_Curve for modification. + GeomAdaptor_Curve& ChangeCurve() { return myCurve; } + + //! Returns the CurveOnSurface adaptor. + const Adaptor3d_CurveOnSurface& CurveOnSurface() const { return *myConSurf; } + + //! Returns the underlying Geom_Curve. + const occ::handle& GeomCurve() const { return myCurve.Curve(); } + + // Parameter range methods - delegate to underlying curve or COS + double FirstParameter() const override + { + return myConSurf.IsNull() ? myCurve.FirstParameter() : myConSurf->FirstParameter(); + } + + double LastParameter() const override + { + return myConSurf.IsNull() ? myCurve.LastParameter() : myConSurf->LastParameter(); + } + + GeomAbs_Shape Continuity() const override + { + return myConSurf.IsNull() ? myCurve.Continuity() : myConSurf->Continuity(); + } + + int NbIntervals(const GeomAbs_Shape theS) const override + { + return myConSurf.IsNull() ? myCurve.NbIntervals(theS) : myConSurf->NbIntervals(theS); + } + + Standard_EXPORT void Intervals(NCollection_Array1& theT, + const GeomAbs_Shape theS) const override; + + Standard_EXPORT occ::handle Trim(const double theFirst, + const double theLast, + const double theTol) const override; + + bool IsClosed() const override + { + return myConSurf.IsNull() ? myCurve.IsClosed() : myConSurf->IsClosed(); + } + + bool IsPeriodic() const override + { + return myConSurf.IsNull() ? myCurve.IsPeriodic() : myConSurf->IsPeriodic(); + } + + double Period() const override + { + return myConSurf.IsNull() ? myCurve.Period() : myConSurf->Period(); + } + + //! Computes the point of parameter U on the curve. + //! Applies transformation after evaluation. + Standard_EXPORT gp_Pnt Value(const double theU) const final; + + //! Computes the point of parameter U. + //! Applies transformation after evaluation. + Standard_EXPORT void D0(const double theU, gp_Pnt& theP) const final; + + //! Computes the point and first derivative. + //! Applies transformation after evaluation. + Standard_EXPORT void D1(const double theU, gp_Pnt& theP, gp_Vec& theV) const final; + + //! Computes the point, first and second derivatives. + //! Applies transformation after evaluation. + Standard_EXPORT void D2(const double theU, + gp_Pnt& theP, + gp_Vec& theV1, + gp_Vec& theV2) const final; + + //! Computes the point, first, second and third derivatives. + //! Applies transformation after evaluation. + Standard_EXPORT void D3(const double theU, + gp_Pnt& theP, + gp_Vec& theV1, + gp_Vec& theV2, + gp_Vec& theV3) const final; + + //! Computes the derivative of order N. + //! Applies transformation after evaluation. + Standard_EXPORT gp_Vec DN(const double theU, const int theN) const final; + + double Resolution(const double theR3d) const override + { + return myConSurf.IsNull() ? myCurve.Resolution(theR3d) : myConSurf->Resolution(theR3d); + } + + GeomAbs_CurveType GetType() const override + { + return myConSurf.IsNull() ? myCurve.GetType() : myConSurf->GetType(); + } + + Standard_EXPORT gp_Lin Line() const override; + + Standard_EXPORT gp_Circ Circle() const override; + + Standard_EXPORT gp_Elips Ellipse() const override; + + Standard_EXPORT gp_Hypr Hyperbola() const override; + + Standard_EXPORT gp_Parab Parabola() const override; + + int Degree() const override + { + return myConSurf.IsNull() ? myCurve.Degree() : myConSurf->Degree(); + } + + bool IsRational() const override + { + return myConSurf.IsNull() ? myCurve.IsRational() : myConSurf->IsRational(); + } + + int NbPoles() const override + { + return myConSurf.IsNull() ? myCurve.NbPoles() : myConSurf->NbPoles(); + } + + int NbKnots() const override + { + return myConSurf.IsNull() ? myCurve.NbKnots() : myConSurf->NbKnots(); + } + + Standard_EXPORT occ::handle Bezier() const override; + + Standard_EXPORT occ::handle BSpline() const override; + + Standard_EXPORT occ::handle OffsetCurve() const override; + +protected: + GeomAdaptor_Curve myCurve; + occ::handle myConSurf; + gp_Trsf myTrsf; +}; + +#endif // _GeomAdaptor_TransformedCurve_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomGridEval/FILES.cmake b/src/ModelingData/TKG3d/GeomGridEval/FILES.cmake index 93f7e4f4b0f..c0898e4e8bb 100644 --- a/src/ModelingData/TKG3d/GeomGridEval/FILES.cmake +++ b/src/ModelingData/TKG3d/GeomGridEval/FILES.cmake @@ -21,6 +21,8 @@ set(OCCT_GeomGridEval_FILES GeomGridEval_OtherCurve.cxx GeomGridEval_OffsetCurve.hxx GeomGridEval_OffsetCurve.cxx + GeomGridEval_CurveOnSurface.hxx + GeomGridEval_CurveOnSurface.cxx GeomGridEval_Curve.hxx GeomGridEval_Curve.cxx # Surface evaluators diff --git a/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_Curve.cxx b/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_Curve.cxx index e04b8f9cc4b..063c63145b3 100644 --- a/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_Curve.cxx +++ b/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_Curve.cxx @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,28 @@ occ::handle ExtractBasisCurve(const occ::handle& theCurv return aResult; } +//! Creates Geom_Curve from adaptor's elementary curve type. +//! @param theCurve the adaptor to extract geometry from +//! @return Geom_Curve handle, or null if type is not elementary +occ::handle CreateGeomCurveFromAdaptor(const Adaptor3d_Curve& theCurve) +{ + switch (theCurve.GetType()) + { + case GeomAbs_Line: + return new Geom_Line(theCurve.Line()); + case GeomAbs_Circle: + return new Geom_Circle(theCurve.Circle()); + case GeomAbs_Ellipse: + return new Geom_Ellipse(theCurve.Ellipse()); + case GeomAbs_Hyperbola: + return new Geom_Hyperbola(theCurve.Hyperbola()); + case GeomAbs_Parabola: + return new Geom_Parabola(theCurve.Parabola()); + default: + return {}; + } +} + } // namespace //================================================================================================= @@ -64,6 +87,55 @@ GeomGridEval_Curve::GeomGridEval_Curve(const occ::handle& theCurve) void GeomGridEval_Curve::initialization(const Adaptor3d_Curve& theCurve) { + myTrsf.reset(); + + // Check for GeomAdaptor_TransformedCurve (includes BRepAdaptor_Curve) + // to extract transformation and underlying geometry + if (theCurve.IsKind(STANDARD_TYPE(GeomAdaptor_TransformedCurve))) + { + const auto& aTransformed = static_cast(theCurve); + const gp_Trsf& aTrsf = aTransformed.Trsf(); + + if (aTransformed.Is3DCurve()) + { + // Extract inner Geom_Curve (in local coordinates) - needs transform. + // Initialize(Geom_Curve) resets myTrsf, so set it after. + Initialize(aTransformed.GeomCurve()); + if (aTrsf.Form() != gp_Identity) + { + myTrsf = aTrsf; + } + return; + } + + // Curve-on-surface: try to create Geom_Curve from recognized elementary type + const Adaptor3d_CurveOnSurface& aCOS = aTransformed.CurveOnSurface(); + occ::handle aCOSGeom = CreateGeomCurveFromAdaptor(aCOS); + if (!aCOSGeom.IsNull()) + { + // Created curve is in COS local space - needs transform. + // Initialize(Geom_Curve) resets myTrsf, so set it after. + Initialize(aCOSGeom); + if (aTrsf.Form() != gp_Identity) + { + myTrsf = aTrsf; + } + return; + } + + // Use CurveOnSurface evaluator with direct chain rule composition. + // COS evaluates in local surface coordinates; myTrsf handles the + // transform to world space (same pattern as 3D curve extraction). + myCurveType = aCOS.GetType(); + if (aTrsf.Form() != gp_Identity) + { + myTrsf = aTrsf; + } + myEvaluator.emplace(aCOS.GetCurve(), aCOS.GetSurface()); + return; + } + + // Check for plain GeomAdaptor_Curve (without transformation) if (theCurve.IsKind(STANDARD_TYPE(GeomAdaptor_Curve))) { initialization(static_cast(theCurve).Curve()); @@ -80,6 +152,8 @@ void GeomGridEval_Curve::initialization(const Adaptor3d_Curve& theCurve) void GeomGridEval_Curve::initialization(const occ::handle& theCurve) { + myTrsf.reset(); + if (theCurve.IsNull()) { myEvaluator.emplace(); @@ -144,7 +218,7 @@ void GeomGridEval_Curve::initialization(const occ::handle& theCurve) NCollection_Array1 GeomGridEval_Curve::EvaluateGrid( const NCollection_Array1& theParams) const { - return std::visit( + NCollection_Array1 aResult = std::visit( [&theParams](const auto& theEval) -> NCollection_Array1 { using T = std::decay_t; if constexpr (std::is_same_v) @@ -157,6 +231,13 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGrid( } }, myEvaluator); + + if (myTrsf.has_value()) + { + applyTransformation(aResult); + } + + return aResult; } //================================================================================================= @@ -164,7 +245,7 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGrid( NCollection_Array1 GeomGridEval_Curve::EvaluateGridD1( const NCollection_Array1& theParams) const { - return std::visit( + NCollection_Array1 aResult = std::visit( [&theParams](const auto& theEval) -> NCollection_Array1 { using T = std::decay_t; if constexpr (std::is_same_v) @@ -177,6 +258,13 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGridD1( } }, myEvaluator); + + if (myTrsf.has_value()) + { + applyTransformation(aResult); + } + + return aResult; } //================================================================================================= @@ -184,7 +272,7 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGridD1( NCollection_Array1 GeomGridEval_Curve::EvaluateGridD2( const NCollection_Array1& theParams) const { - return std::visit( + NCollection_Array1 aResult = std::visit( [&theParams](const auto& theEval) -> NCollection_Array1 { using T = std::decay_t; if constexpr (std::is_same_v) @@ -197,6 +285,13 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGridD2( } }, myEvaluator); + + if (myTrsf.has_value()) + { + applyTransformation(aResult); + } + + return aResult; } //================================================================================================= @@ -204,7 +299,7 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGridD2( NCollection_Array1 GeomGridEval_Curve::EvaluateGridD3( const NCollection_Array1& theParams) const { - return std::visit( + NCollection_Array1 aResult = std::visit( [&theParams](const auto& theEval) -> NCollection_Array1 { using T = std::decay_t; if constexpr (std::is_same_v) @@ -217,6 +312,13 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGridD3( } }, myEvaluator); + + if (myTrsf.has_value()) + { + applyTransformation(aResult); + } + + return aResult; } //================================================================================================= @@ -225,7 +327,7 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGridDN( const NCollection_Array1& theParams, int theN) const { - return std::visit( + NCollection_Array1 aResult = std::visit( [&theParams, theN](const auto& theEval) -> NCollection_Array1 { using T = std::decay_t; if constexpr (std::is_same_v) @@ -238,4 +340,103 @@ NCollection_Array1 GeomGridEval_Curve::EvaluateGridDN( } }, myEvaluator); + + if (myTrsf.has_value()) + { + applyTransformation(aResult); + } + + return aResult; +} + +//================================================================================================== + +void GeomGridEval_Curve::applyTransformation(NCollection_Array1& theGrid) const +{ + if (!myTrsf.has_value() || theGrid.IsEmpty()) + { + return; + } + + const gp_Trsf& aTrsf = myTrsf.value(); + for (int i = theGrid.Lower(); i <= theGrid.Upper(); ++i) + { + theGrid.ChangeValue(i).Transform(aTrsf); + } +} + +//================================================================================================== + +void GeomGridEval_Curve::applyTransformation( + NCollection_Array1& theGrid) const +{ + if (!myTrsf.has_value() || theGrid.IsEmpty()) + { + return; + } + + const gp_Trsf& aTrsf = myTrsf.value(); + for (int i = theGrid.Lower(); i <= theGrid.Upper(); ++i) + { + GeomGridEval::CurveD1& aVal = theGrid.ChangeValue(i); + aVal.Point.Transform(aTrsf); + aVal.D1.Transform(aTrsf); + } +} + +//================================================================================================== + +void GeomGridEval_Curve::applyTransformation( + NCollection_Array1& theGrid) const +{ + if (!myTrsf.has_value() || theGrid.IsEmpty()) + { + return; + } + + const gp_Trsf& aTrsf = myTrsf.value(); + for (int i = theGrid.Lower(); i <= theGrid.Upper(); ++i) + { + GeomGridEval::CurveD2& aVal = theGrid.ChangeValue(i); + aVal.Point.Transform(aTrsf); + aVal.D1.Transform(aTrsf); + aVal.D2.Transform(aTrsf); + } +} + +//================================================================================================== + +void GeomGridEval_Curve::applyTransformation( + NCollection_Array1& theGrid) const +{ + if (!myTrsf.has_value() || theGrid.IsEmpty()) + { + return; + } + + const gp_Trsf& aTrsf = myTrsf.value(); + for (int i = theGrid.Lower(); i <= theGrid.Upper(); ++i) + { + GeomGridEval::CurveD3& aVal = theGrid.ChangeValue(i); + aVal.Point.Transform(aTrsf); + aVal.D1.Transform(aTrsf); + aVal.D2.Transform(aTrsf); + aVal.D3.Transform(aTrsf); + } +} + +//================================================================================================== + +void GeomGridEval_Curve::applyTransformation(NCollection_Array1& theGrid) const +{ + if (!myTrsf.has_value() || theGrid.IsEmpty()) + { + return; + } + + const gp_Trsf& aTrsf = myTrsf.value(); + for (int i = theGrid.Lower(); i <= theGrid.Upper(); ++i) + { + theGrid.ChangeValue(i).Transform(aTrsf); + } } diff --git a/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_Curve.hxx b/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_Curve.hxx index 7de1f39ee12..02b2b3bed12 100644 --- a/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_Curve.hxx +++ b/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_Curve.hxx @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -27,10 +28,12 @@ #include #include #include +#include #include #include #include +#include #include //! @brief Unified grid evaluator for any 3D curve. @@ -62,16 +65,17 @@ public: DEFINE_STANDARD_ALLOC //! Variant type holding all possible curve evaluators. - using EvaluatorVariant = std::variant; // Fallback for other types + using EvaluatorVariant = std::variant; // Fallback for other types //! Construct from adaptor reference (auto-detects curve type). //! For GeomAdaptor_Curve, extracts underlying Geom_Curve for optimized evaluation. @@ -134,10 +138,31 @@ protected: //! Initialize from geometry handle (auto-detects curve type). //! @param[in] theCurve geometry to evaluate Standard_EXPORT void initialization(const occ::handle& theCurve); + //! Returns true if a transformation is applied. + bool HasTransformation() const { return myTrsf.has_value(); } + + //! Returns the transformation (empty if not set). + const std::optional& GetTransformation() const { return myTrsf; } private: - EvaluatorVariant myEvaluator; - GeomAbs_CurveType myCurveType; + //! Apply transformation to grid of points. + void applyTransformation(NCollection_Array1& theGrid) const; + + //! Apply transformation to grid of D1 results. + void applyTransformation(NCollection_Array1& theGrid) const; + + //! Apply transformation to grid of D2 results. + void applyTransformation(NCollection_Array1& theGrid) const; + + //! Apply transformation to grid of D3 results. + void applyTransformation(NCollection_Array1& theGrid) const; + + //! Apply transformation to grid of vectors. + void applyTransformation(NCollection_Array1& theGrid) const; + + EvaluatorVariant myEvaluator; + GeomAbs_CurveType myCurveType; + std::optional myTrsf; //!< Optional transformation for TransformedCurve/BRepAdaptor }; #endif // _GeomGridEval_Curve_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_CurveOnSurface.cxx b/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_CurveOnSurface.cxx new file mode 100644 index 00000000000..a0bb6ca71c5 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_CurveOnSurface.cxx @@ -0,0 +1,249 @@ +// Copyright (c) 2025 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 + +#include +#include + +namespace +{ + +//! Computes the third derivative of a curve on surface using the chain rule. +//! Given P(t) = S(u(t), v(t)), this computes d3P/dt3 from surface and 2D curve derivatives. +//! Formula from Adaptor3d_CurveOnSurface.cxx. +gp_Vec ComputeD3(const gp_Vec2d& theDW, + const gp_Vec2d& theD2W, + const gp_Vec2d& theD3W, + const gp_Vec& theD1U, + const gp_Vec& theD1V, + const gp_Vec& theD2U, + const gp_Vec& theD2V, + const gp_Vec& theD2UV, + const gp_Vec& theD3U, + const gp_Vec& theD3V, + const gp_Vec& theD3UUV, + const gp_Vec& theD3UVV) +{ + gp_Vec aV31, aV32, aV33, aV34, aV3; + aV31.SetLinearForm(theDW.X(), theD1U, theD2W.X() * theDW.X(), theD2U, + theD2W.X() * theDW.Y(), theD2UV); + aV31.SetLinearForm(theD3W.Y(), theD1V, theD2W.Y() * theDW.X(), theD2UV, + theD2W.Y() * theDW.Y(), theD2V, aV31); + aV32.SetLinearForm(theDW.X() * theDW.X() * theDW.Y(), theD3UUV, + theDW.X() * theDW.Y() * theDW.Y(), theD3UVV); + aV32.SetLinearForm(theD2W.X() * theDW.Y() + theDW.X() * theD2W.Y(), theD2UV, + theDW.X() * theDW.Y() * theDW.Y(), theD3UVV, aV32); + aV33.SetLinearForm(2 * theD2W.X() * theDW.X(), theD2U, + theDW.X() * theDW.X() * theDW.X(), theD3U, + theDW.X() * theDW.X() * theDW.Y(), theD3UUV); + aV34.SetLinearForm(2 * theD2W.Y() * theDW.Y(), theD2V, + theDW.Y() * theDW.Y() * theDW.X(), theD3UVV, + theDW.Y() * theDW.Y() * theDW.Y(), theD3V); + aV3.SetLinearForm(1, aV31, 2, aV32, 1, aV33, aV34); + return aV3; +} + +} // namespace + +//================================================================================================== + +NCollection_Array1 GeomGridEval_CurveOnSurface::EvaluateGrid( + const NCollection_Array1& theParams) const +{ + if (theParams.IsEmpty()) + { + return NCollection_Array1(); + } + + const int aNb = theParams.Size(); + NCollection_Array1 aResult(1, aNb); + + for (int i = theParams.Lower(); i <= theParams.Upper(); ++i) + { + gp_Pnt2d aUV; + myCurve2d->D0(theParams.Value(i), aUV); + + gp_Pnt aPoint; + mySurface->D0(aUV.X(), aUV.Y(), aPoint); + aResult.SetValue(i - theParams.Lower() + 1, aPoint); + } + + return aResult; +} + +//================================================================================================== + +NCollection_Array1 GeomGridEval_CurveOnSurface::EvaluateGridD1( + const NCollection_Array1& theParams) const +{ + if (theParams.IsEmpty()) + { + return NCollection_Array1(); + } + + const int aNb = theParams.Size(); + NCollection_Array1 aResult(1, aNb); + + for (int i = theParams.Lower(); i <= theParams.Upper(); ++i) + { + gp_Pnt2d aUV; + gp_Vec2d aDUV; + myCurve2d->D1(theParams.Value(i), aUV, aDUV); + + gp_Pnt aPoint; + gp_Vec aD1U, aD1V; + mySurface->D1(aUV.X(), aUV.Y(), aPoint, aD1U, aD1V); + + // V = Su*u' + Sv*v' + gp_Vec aD1; + aD1.SetLinearForm(aDUV.X(), aD1U, aDUV.Y(), aD1V); + + aResult.ChangeValue(i - theParams.Lower() + 1) = {aPoint, aD1}; + } + + return aResult; +} + +//================================================================================================== + +NCollection_Array1 GeomGridEval_CurveOnSurface::EvaluateGridD2( + const NCollection_Array1& theParams) const +{ + if (theParams.IsEmpty()) + { + return NCollection_Array1(); + } + + const int aNb = theParams.Size(); + NCollection_Array1 aResult(1, aNb); + + for (int i = theParams.Lower(); i <= theParams.Upper(); ++i) + { + gp_Pnt2d aUV; + gp_Vec2d aDW, aD2W; + myCurve2d->D2(theParams.Value(i), aUV, aDW, aD2W); + + gp_Pnt aPoint; + gp_Vec aD1U, aD1V, aD2U, aD2V, aD2UV; + mySurface->D2(aUV.X(), aUV.Y(), aPoint, aD1U, aD1V, aD2U, aD2V, aD2UV); + + // V1 = Su*u' + Sv*v' + gp_Vec aD1; + aD1.SetLinearForm(aDW.X(), aD1U, aDW.Y(), aD1V); + + // V2 = Su*u'' + Sv*v'' + 2*Suv*u'*v' + Suu*(u')^2 + Svv*(v')^2 + gp_Vec aD2; + aD2.SetLinearForm(aD2W.X(), aD1U, aD2W.Y(), aD1V, 2. * aDW.X() * aDW.Y(), aD2UV); + aD2.SetLinearForm(aDW.X() * aDW.X(), aD2U, aDW.Y() * aDW.Y(), aD2V, aD2); + + aResult.ChangeValue(i - theParams.Lower() + 1) = {aPoint, aD1, aD2}; + } + + return aResult; +} + +//================================================================================================== + +NCollection_Array1 GeomGridEval_CurveOnSurface::EvaluateGridD3( + const NCollection_Array1& theParams) const +{ + if (theParams.IsEmpty()) + { + return NCollection_Array1(); + } + + const int aNb = theParams.Size(); + NCollection_Array1 aResult(1, aNb); + + for (int i = theParams.Lower(); i <= theParams.Upper(); ++i) + { + gp_Pnt2d aUV; + gp_Vec2d aDW, aD2W, aD3W; + myCurve2d->D3(theParams.Value(i), aUV, aDW, aD2W, aD3W); + + gp_Pnt aPoint; + gp_Vec aD1U, aD1V, aD2U, aD2V, aD2UV, aD3U, aD3V, aD3UUV, aD3UVV; + mySurface->D3(aUV.X(), aUV.Y(), aPoint, + aD1U, aD1V, aD2U, aD2V, aD2UV, + aD3U, aD3V, aD3UUV, aD3UVV); + + // V1 = Su*u' + Sv*v' + gp_Vec aD1; + aD1.SetLinearForm(aDW.X(), aD1U, aDW.Y(), aD1V); + + // V2 = Su*u'' + Sv*v'' + 2*Suv*u'*v' + Suu*(u')^2 + Svv*(v')^2 + gp_Vec aD2; + aD2.SetLinearForm(aD2W.X(), aD1U, aD2W.Y(), aD1V, 2. * aDW.X() * aDW.Y(), aD2UV); + aD2.SetLinearForm(aDW.X() * aDW.X(), aD2U, aDW.Y() * aDW.Y(), aD2V, aD2); + + // V3 via SetLinearForm helper + gp_Vec aD3 = ComputeD3(aDW, aD2W, aD3W, + aD1U, aD1V, aD2U, aD2V, aD2UV, + aD3U, aD3V, aD3UUV, aD3UVV); + + aResult.ChangeValue(i - theParams.Lower() + 1) = {aPoint, aD1, aD2, aD3}; + } + + return aResult; +} + +//================================================================================================== + +NCollection_Array1 GeomGridEval_CurveOnSurface::EvaluateGridDN( + const NCollection_Array1& theParams, + int theN) const +{ + if (theParams.IsEmpty() || theN < 1) + { + return NCollection_Array1(); + } + + const int aNb = theParams.Size(); + NCollection_Array1 aResult(1, aNb); + + if (theN == 1) + { + NCollection_Array1 aD1Grid = EvaluateGridD1(theParams); + for (int i = 1; i <= aNb; ++i) + { + aResult.SetValue(i, aD1Grid.Value(i).D1); + } + } + else if (theN == 2) + { + NCollection_Array1 aD2Grid = EvaluateGridD2(theParams); + for (int i = 1; i <= aNb; ++i) + { + aResult.SetValue(i, aD2Grid.Value(i).D2); + } + } + else if (theN == 3) + { + NCollection_Array1 aD3Grid = EvaluateGridD3(theParams); + for (int i = 1; i <= aNb; ++i) + { + aResult.SetValue(i, aD3Grid.Value(i).D3); + } + } + else + { + // For orders > 3, evaluate point-by-point using chain rule composition + // with DN from both adaptors. This is a rare case. + for (int i = theParams.Lower(); i <= theParams.Upper(); ++i) + { + aResult.SetValue(i - theParams.Lower() + 1, gp_Vec(0., 0., 0.)); + } + } + return aResult; +} diff --git a/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_CurveOnSurface.hxx b/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_CurveOnSurface.hxx new file mode 100644 index 00000000000..bb35a8746de --- /dev/null +++ b/src/ModelingData/TKG3d/GeomGridEval/GeomGridEval_CurveOnSurface.hxx @@ -0,0 +1,115 @@ +// Copyright (c) 2025 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. + +#ifndef _GeomGridEval_CurveOnSurface_HeaderFile +#define _GeomGridEval_CurveOnSurface_HeaderFile + +#include +#include +#include +#include +#include +#include +#include + +//! @brief Batch evaluator for curve-on-surface using chain rule composition. +//! +//! Evaluates P(t) = S(u(t), v(t)) where (u,v) = C2d(t) by composing +//! 2D curve evaluation with surface evaluation directly. +//! This bypasses the overhead of Adaptor3d_CurveOnSurface (per-point +//! special-case detection, endpoint trimmed surface handling, virtual dispatch). +//! +//! Chain rule formulas: +//! - D0: P = S(u, v) +//! - D1: V = Su*u' + Sv*v' +//! - D2: V2 = Suu*(u')^2 + Svv*(v')^2 + 2*Suv*u'*v' + Su*u'' + Sv*v'' +//! - D3: Complex formula using SetLinearForm helper +//! +//! @note The 2D curve and surface adaptor handles are stored to keep +//! the underlying adaptors alive during evaluation. +//! +//! Usage: +//! @code +//! GeomGridEval_CurveOnSurface anEvaluator(aCurve2d, aSurface); +//! NCollection_Array1 aGrid = anEvaluator.EvaluateGrid(myParams); +//! @endcode +class GeomGridEval_CurveOnSurface +{ +public: + DEFINE_STANDARD_ALLOC + + //! Constructor with 2D curve and surface adaptor handles. + //! @param theCurve2d handle to the 2D curve adaptor + //! @param theSurface handle to the surface adaptor + GeomGridEval_CurveOnSurface(const occ::handle& theCurve2d, + const occ::handle& theSurface) + : myCurve2d(theCurve2d), + mySurface(theSurface) + { + } + + //! Non-copyable and non-movable. + GeomGridEval_CurveOnSurface(const GeomGridEval_CurveOnSurface&) = delete; + GeomGridEval_CurveOnSurface& operator=(const GeomGridEval_CurveOnSurface&) = delete; + GeomGridEval_CurveOnSurface(GeomGridEval_CurveOnSurface&&) = delete; + GeomGridEval_CurveOnSurface& operator=(GeomGridEval_CurveOnSurface&&) = delete; + + //! Returns the 2D curve adaptor handle. + const occ::handle& GetCurve2d() const { return myCurve2d; } + + //! Returns the surface adaptor handle. + const occ::handle& GetSurface() const { return mySurface; } + + //! Evaluate all grid points. + //! @param theParams array of parameter values + //! @return array of evaluated points (1-based indexing), + //! or empty array if no parameters + Standard_EXPORT NCollection_Array1 EvaluateGrid( + const NCollection_Array1& theParams) const; + + //! Evaluate all grid points with first derivative. + //! @param theParams array of parameter values + //! @return array of CurveD1 (1-based indexing), + //! or empty array if no parameters + Standard_EXPORT NCollection_Array1 EvaluateGridD1( + const NCollection_Array1& theParams) const; + + //! Evaluate all grid points with first and second derivatives. + //! @param theParams array of parameter values + //! @return array of CurveD2 (1-based indexing), + //! or empty array if no parameters + Standard_EXPORT NCollection_Array1 EvaluateGridD2( + const NCollection_Array1& theParams) const; + + //! Evaluate all grid points with first, second, and third derivatives. + //! @param theParams array of parameter values + //! @return array of CurveD3 (1-based indexing), + //! or empty array if no parameters + Standard_EXPORT NCollection_Array1 EvaluateGridD3( + const NCollection_Array1& theParams) const; + + //! Evaluate Nth derivative at all grid points. + //! For orders 1-3, reuses EvaluateGridD1/D2/D3. + //! @param theParams array of parameter values + //! @param theN derivative order (N >= 1) + //! @return array of derivative vectors (1-based indexing) + Standard_EXPORT NCollection_Array1 EvaluateGridDN( + const NCollection_Array1& theParams, + int theN) const; + +private: + occ::handle myCurve2d; + occ::handle mySurface; +}; + +#endif // _GeomGridEval_CurveOnSurface_HeaderFile