Shape Healing - Optimize PCurve projection (#890)

- Implemented various test cases including projections of lines, circles, and B-splines on different surface types (planes, cylinders, spheres, and toroids).
- Refactored ShapeConstruct_ProjectCurveOnSurface to improve handling of periodic surfaces and edge cases.
- Updated header files to reflect new type aliases and improved structure for better readability and maintainability.
- Added a new function `extractBSplineCurve` to streamline the extraction of B-spline curves from both trimmed and untrimmed curves.
- Refactored `isBSplineCurveInvalid` to utilize the new extraction function, improving clarity and reducing code duplication.
- Updated `generateCurvePoints` to leverage the new extraction method for better handling of B-spline curves.
- Replaced std::vector with NCollection_Vector for better memory management in isBSplineCurveInvalid.
- Enhanced rebuildBSpline function to improve knot adjustment logic while preserving curve geometry.
- Introduced a new utility class, SurfaceProjectorWithCache, to enhance the projection of points onto B-spline surfaces by caching pole positions and their UV parameters.
This commit is contained in:
Pasukhin Dmitry
2025-12-08 09:08:13 +00:00
committed by GitHub
parent 9ba63b850e
commit 3b8185bafb
5 changed files with 2843 additions and 1620 deletions

View File

@@ -3,4 +3,5 @@ set(OCCT_TKShHealing_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}")
set(OCCT_TKShHealing_GTests_FILES
ShapeAnalysis_CanonicalRecognition_Test.cxx
ShapeConstruct_ProjectCurveOnSurface_Test.cxx
)

View File

@@ -0,0 +1,994 @@
// 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 <gtest/gtest.h>
#include <ShapeConstruct_ProjectCurveOnSurface.hxx>
#include <ShapeAnalysis_Surface.hxx>
#include <GC_MakeSegment.hxx>
#include <Geom2d_Curve.hxx>
#include <Geom_BSplineCurve.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom_Circle.hxx>
#include <Geom_ConicalSurface.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <Geom_Ellipse.hxx>
#include <Geom_Plane.hxx>
#include <Geom_SphericalSurface.hxx>
#include <Geom_ToroidalSurface.hxx>
#include <Geom_TrimmedCurve.hxx>
#include <GeomAPI_PointsToBSpline.hxx>
#include <gp_Ax2.hxx>
#include <gp_Circ.hxx>
#include <gp_Dir.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <Precision.hxx>
#include <ShapeExtend.hxx>
#include <TColgp_Array1OfPnt.hxx>
#include <TColgp_Array2OfPnt.hxx>
#include <TColStd_Array1OfInteger.hxx>
#include <TColStd_Array1OfReal.hxx>
//==================================================================================================
// Test fixture for ShapeConstruct_ProjectCurveOnSurface
//==================================================================================================
class ShapeConstruct_ProjectCurveOnSurfaceTest : public ::testing::Test
{
protected:
void SetUp() override
{
myProjector = new ShapeConstruct_ProjectCurveOnSurface();
myTolerance = Precision::Confusion();
}
//! Helper to create a simple B-spline curve lying on Z=0 plane
Handle(Geom_BSplineCurve) createPlanarBSpline()
{
TColgp_Array1OfPnt aPoles(1, 5);
aPoles(1) = gp_Pnt(0, 0, 0);
aPoles(2) = gp_Pnt(2, 3, 0);
aPoles(3) = gp_Pnt(5, 4, 0);
aPoles(4) = gp_Pnt(8, 2, 0);
aPoles(5) = gp_Pnt(10, 0, 0);
GeomAPI_PointsToBSpline aBuilder(aPoles);
return aBuilder.Curve();
}
//! Helper to create a helical B-spline on a cylinder
Handle(Geom_BSplineCurve) createHelicalBSpline(const double theRadius, const int theNbPoints)
{
TColgp_Array1OfPnt aPoles(1, theNbPoints);
for (int i = 1; i <= theNbPoints; ++i)
{
const double anAngle = (i - 1) * M_PI / 4.0;
const double aZ = (i - 1) * 1.0;
aPoles(i) = gp_Pnt(theRadius * cos(anAngle), theRadius * sin(anAngle), aZ);
}
GeomAPI_PointsToBSpline aBuilder(aPoles);
return aBuilder.Curve();
}
//! Verifies that pcurve correctly maps to 3D points on surface
void verifyProjection(const Handle(Geom_Curve)& theCurve,
const Handle(Geom2d_Curve)& thePCurve,
const Handle(ShapeAnalysis_Surface)& theSurface,
const double theTolerance)
{
const double aFirst = theCurve->FirstParameter();
const double aLast = theCurve->LastParameter();
for (double t = 0.0; t <= 1.0; t += 0.25)
{
const double aParam = aFirst + t * (aLast - aFirst);
gp_Pnt a3DPnt;
theCurve->D0(aParam, a3DPnt);
const gp_Pnt2d a2DPnt = thePCurve->Value(aParam);
gp_Pnt aSurfPnt = theSurface->Value(a2DPnt);
EXPECT_NEAR(a3DPnt.Distance(aSurfPnt), 0.0, theTolerance) << "Point mismatch at t=" << t;
}
}
Handle(ShapeConstruct_ProjectCurveOnSurface) myProjector;
double myTolerance;
};
//==================================================================================================
// Basic projection tests - lines and circles on analytical surfaces
//==================================================================================================
// Test: Project diagonal line on horizontal plane, verify 2D coordinates match X,Y of 3D points
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, LineOnPlane_A1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(Geom_TrimmedCurve) aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
Handle(ShapeAnalysis_Surface) aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
Handle(Geom2d_Curve) aPCurve;
const double aFirst = aLine->FirstParameter();
const double aLast = aLine->LastParameter();
const Standard_Boolean aResult = myProjector->Perform(aLine, aFirst, aLast, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// Verify start point maps to (0,0) and end point maps to (10,10)
const gp_Pnt2d aStart = aPCurve->Value(aFirst);
const gp_Pnt2d aEnd = aPCurve->Value(aLast);
EXPECT_NEAR(aStart.X(), 0.0, 0.01);
EXPECT_NEAR(aStart.Y(), 0.0, 0.01);
EXPECT_NEAR(aEnd.X(), 10.0, 0.01);
EXPECT_NEAR(aEnd.Y(), 10.0, 0.01);
}
// Test: Project circle lying on cylinder surface, V-coordinate should be constant
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, CircleOnCylinder_A2)
{
const double aRadius = 5.0;
Handle(Geom_CylindricalSurface) aCylinder =
new Geom_CylindricalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
// Circle at height Z=3 on the cylinder
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, 3), gp_Dir(0, 0, 1)), aRadius);
Handle(Geom_Circle) aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aCylinder), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// On cylinder parametrization, V = Z, so circle at Z=3 should have V=3
const gp_Pnt2d aMid = aPCurve->Value(M_PI);
EXPECT_NEAR(aMid.Y(), 3.0, 0.01) << "V coordinate should be 3";
}
// Test: Project vertical line on cylinder (isoparametric case), U should be constant
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, IsoparametricLineOnCylinder_A3)
{
const double aRadius = 5.0;
Handle(Geom_CylindricalSurface) aCylinder =
new Geom_CylindricalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
// Line along cylinder axis at X=radius, Y=0 (U=0 on cylinder)
Handle(Geom_TrimmedCurve) aLine =
GC_MakeSegment(gp_Pnt(aRadius, 0, 0), gp_Pnt(aRadius, 0, 10)).Value();
Handle(ShapeAnalysis_Surface) aSAS = new ShapeAnalysis_Surface(aCylinder);
myProjector->Init(aSAS, myTolerance);
Handle(Geom2d_Curve) aPCurve;
const double aFirst = aLine->FirstParameter();
const double aLast = aLine->LastParameter();
const Standard_Boolean aResult = myProjector->Perform(aLine, aFirst, aLast, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// U should be constant (isoparametric curve)
const gp_Pnt2d aStart = aPCurve->Value(aFirst);
const gp_Pnt2d aEnd = aPCurve->Value(aLast);
EXPECT_NEAR(aStart.X(), aEnd.X(), 0.01) << "U should be constant for isoparametric curve";
}
//==================================================================================================
// B-Spline curve projection tests
//==================================================================================================
// Test: Project planar B-spline on plane, endpoints should match original curve
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, BSplineOnPlane_B1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(Geom_BSplineCurve) aBSpline = createPlanarBSpline();
ASSERT_FALSE(aBSpline.IsNull());
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aBSpline, aBSpline->FirstParameter(), aBSpline->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// Verify endpoints
const gp_Pnt2d aStart = aPCurve->Value(aBSpline->FirstParameter());
const gp_Pnt2d aEnd = aPCurve->Value(aBSpline->LastParameter());
EXPECT_NEAR(aStart.X(), 0.0, 0.1);
EXPECT_NEAR(aStart.Y(), 0.0, 0.1);
EXPECT_NEAR(aEnd.X(), 10.0, 0.1);
EXPECT_NEAR(aEnd.Y(), 0.0, 0.1);
}
// Test: Project helical B-spline on cylinder, verify all sample points lie on surface
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, HelicalBSplineOnCylinder_B2)
{
const double aRadius = 5.0;
Handle(Geom_CylindricalSurface) aCylinder =
new Geom_CylindricalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
Handle(Geom_BSplineCurve) aBSpline = createHelicalBSpline(aRadius, 9);
ASSERT_FALSE(aBSpline.IsNull());
Handle(ShapeAnalysis_Surface) aSAS = new ShapeAnalysis_Surface(aCylinder);
myProjector->Init(aSAS, myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aBSpline, aBSpline->FirstParameter(), aBSpline->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
verifyProjection(aBSpline, aPCurve, aSAS, 0.1);
}
// Test: Project high-degree B-spline with many control points
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, HighDegreeBSpline_B3)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Create B-spline with 50 control points (sinusoidal shape)
const int aNbPoles = 50;
TColgp_Array1OfPnt aPoles(1, aNbPoles);
for (int i = 1; i <= aNbPoles; ++i)
{
const double aX = (i - 1) * 0.5;
const double aY = 5.0 * sin(aX * M_PI / 10.0);
aPoles(i) = gp_Pnt(aX, aY, 0);
}
GeomAPI_PointsToBSpline aBuilder(aPoles, 3, 8);
Handle(Geom_BSplineCurve) aBSpline = aBuilder.Curve();
ASSERT_FALSE(aBSpline.IsNull());
Handle(ShapeAnalysis_Surface) aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aBSpline, aBSpline->FirstParameter(), aBSpline->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
verifyProjection(aBSpline, aPCurve, aSAS, 0.5);
}
//==================================================================================================
// Periodic surface tests - sphere, torus
//==================================================================================================
// Test: Project equator circle on sphere
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, EquatorOnSphere_C1)
{
const double aRadius = 10.0;
Handle(Geom_SphericalSurface) aSphere =
new Geom_SphericalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
Handle(Geom_Circle) aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aSphere), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
}
// Test: Project latitude circle on sphere at 45 degrees, V should be constant
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, LatitudeOnSphere_C2)
{
const double aRadius = 10.0;
Handle(Geom_SphericalSurface) aSphere =
new Geom_SphericalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
// Latitude at 45 degrees
const double aLatitude = M_PI / 4.0;
const double aZ = aRadius * sin(aLatitude);
const double aCircRadius = aRadius * cos(aLatitude);
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, aZ), gp_Dir(0, 0, 1)), aCircRadius);
Handle(Geom_Circle) aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aSphere), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// V should be constant for latitude circle
const gp_Pnt2d aStart = aPCurve->Value(0.0);
const gp_Pnt2d aMid = aPCurve->Value(M_PI);
EXPECT_NEAR(aStart.Y(), aMid.Y(), 0.01) << "V should be constant for latitude circle";
}
// Test: Project on toroidal surface (both U and V periodic)
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, CircleOnTorus_C3)
{
const double aMajorRadius = 10.0;
const double aMinorRadius = 2.0;
Handle(Geom_ToroidalSurface) aTorus =
new Geom_ToroidalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aMajorRadius, aMinorRadius);
// Circle in the minor cross-section plane at U=0
gp_Circ aCirc(gp_Ax2(gp_Pnt(aMajorRadius + aMinorRadius, 0, 0), gp_Dir(0, 1, 0)), aMinorRadius);
Handle(Geom_Circle) aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aTorus), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
}
//==================================================================================================
// Conical surface tests
//==================================================================================================
// Test: Project circle on cone at specific height
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, CircleOnCone_D1)
{
const double aRadius = 5.0;
const double aSemiAngle = M_PI / 6.0; // 30 degrees
Handle(Geom_ConicalSurface) aCone =
new Geom_ConicalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aSemiAngle, aRadius);
// Circle at height Z=5, radius increases with height
const double aZ = 5.0;
const double aRadiusAtZ = aRadius + aZ * tan(aSemiAngle);
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, aZ), gp_Dir(0, 0, 1)), aRadiusAtZ);
Handle(Geom_Circle) aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aCone), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
}
//==================================================================================================
// Trimmed curve tests
//==================================================================================================
// Test: Project 90-degree arc on plane
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, ArcOnPlane_E1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 5.0);
Handle(Geom_Circle) aFullCircle = new Geom_Circle(aCirc);
Handle(Geom_TrimmedCurve) aArc = new Geom_TrimmedCurve(aFullCircle, 0.0, M_PI / 2.0);
Handle(ShapeAnalysis_Surface) aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
Handle(Geom2d_Curve) aPCurve;
const double aFirst = aArc->FirstParameter();
const double aLast = aArc->LastParameter();
const Standard_Boolean aResult = myProjector->Perform(aArc, aFirst, aLast, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// Arc from (5,0) to (0,5)
const gp_Pnt2d aStart = aPCurve->Value(aFirst);
const gp_Pnt2d aEnd = aPCurve->Value(aLast);
EXPECT_NEAR(aStart.X(), 5.0, 0.01);
EXPECT_NEAR(aStart.Y(), 0.0, 0.01);
EXPECT_NEAR(aEnd.X(), 0.0, 0.01);
EXPECT_NEAR(aEnd.Y(), 5.0, 0.01);
}
// Test: Project doubly-trimmed curve (tests recursive unwrapping of trimmed curves)
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, DoublyTrimmedCurve_E2)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(Geom_BSplineCurve) aBSpline = createPlanarBSpline();
ASSERT_FALSE(aBSpline.IsNull());
// First trim: take first half
const double aFirst1 = aBSpline->FirstParameter();
const double aLast1 = aBSpline->LastParameter();
const double aMid = (aFirst1 + aLast1) / 2.0;
Handle(Geom_TrimmedCurve) aTrim1 = new Geom_TrimmedCurve(aBSpline, aFirst1, aMid);
// Second trim (nested): take first quarter of the first trim
const double aFirst2 = aTrim1->FirstParameter();
const double aLast2 = aTrim1->LastParameter();
const double aQuart = aFirst2 + (aLast2 - aFirst2) / 4.0;
Handle(Geom_TrimmedCurve) aTrim2 = new Geom_TrimmedCurve(aTrim1, aFirst2, aQuart);
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aTrim2, aTrim2->FirstParameter(), aTrim2->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Projection of doubly-trimmed curve should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
}
//==================================================================================================
// Ellipse projection tests
//==================================================================================================
// Test: Project ellipse on plane, verify quadrant points
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, EllipseOnPlane_F1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Ellipse with major radius 10, minor radius 5
gp_Elips anElips(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 10.0, 5.0);
Handle(Geom_Ellipse) anEllipse = new Geom_Ellipse(anElips);
Handle(ShapeAnalysis_Surface) aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(anEllipse, 0.0, 2.0 * M_PI, aPCurve);
ASSERT_TRUE(aResult) << "Projection should succeed";
ASSERT_FALSE(aPCurve.IsNull()) << "PCurve should not be null";
// At parameter 0, ellipse is at (10, 0)
gp_Pnt2d aP0 = aPCurve->Value(0.0);
EXPECT_NEAR(aP0.X(), 10.0, 0.01);
EXPECT_NEAR(aP0.Y(), 0.0, 0.01);
// At parameter PI/2, ellipse is at (0, 5)
gp_Pnt2d aP1 = aPCurve->Value(M_PI / 2.0);
EXPECT_NEAR(aP1.X(), 0.0, 0.01);
EXPECT_NEAR(aP1.Y(), 5.0, 0.01);
}
//==================================================================================================
// API method tests - Init, SetSurface, SetPrecision
//==================================================================================================
// Test: Init with Geom_Surface directly (not wrapped in ShapeAnalysis_Surface)
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, InitWithGeomSurface_G1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(Geom_TrimmedCurve) aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
myProjector->Init(aPlane, Precision::Confusion());
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Init with Geom_Surface should work";
ASSERT_FALSE(aPCurve.IsNull());
}
// Test: SetSurface + SetPrecision separately
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, SetSurfaceAndPrecision_G2)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(Geom_TrimmedCurve) aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
myProjector->SetSurface(aPlane);
myProjector->SetPrecision(Precision::Confusion());
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "SetSurface + SetPrecision should work";
ASSERT_FALSE(aPCurve.IsNull());
}
// Test: AdjustOverDegenMode accessor returns modifiable reference
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, AdjustOverDegenModeAccessor_G3)
{
// Default value should be 1
EXPECT_EQ(myProjector->AdjustOverDegenMode(), 1);
// Modify through reference
myProjector->AdjustOverDegenMode() = 0;
EXPECT_EQ(myProjector->AdjustOverDegenMode(), 0);
// Restore
myProjector->AdjustOverDegenMode() = 1;
EXPECT_EQ(myProjector->AdjustOverDegenMode(), 1);
}
// Test: Status method returns meaningful status after projection
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, StatusMethod_G4)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(Geom_TrimmedCurve) aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
Handle(Geom2d_Curve) aPCurve;
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
// Status should indicate OK or DONE
EXPECT_TRUE(myProjector->Status(ShapeExtend_OK) || myProjector->Status(ShapeExtend_DONE))
<< "Status should indicate OK or DONE";
}
//==================================================================================================
// Tolerance and precision tests
//==================================================================================================
// Test: Projection with very tight tolerance on exact curve
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, TightTolerance_H1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(Geom_TrimmedCurve) aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), 1e-15);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Tight tolerance should work for exact curve";
ASSERT_FALSE(aPCurve.IsNull());
}
// Test: Projection with different endpoint tolerances
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, DifferentEndpointTolerances_H2)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Curve slightly off plane at end
TColgp_Array1OfPnt aPoles(1, 3);
aPoles(1) = gp_Pnt(0, 0, 0);
aPoles(2) = gp_Pnt(5, 5, 0.05);
aPoles(3) = gp_Pnt(10, 10, 0.1);
GeomAPI_PointsToBSpline aBuilder(aPoles, 2, 2);
Handle(Geom_BSplineCurve) aBSpline = aBuilder.Curve();
ASSERT_FALSE(aBSpline.IsNull());
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const double aTolFirst = 0.01;
const double aTolLast = 0.2;
const Standard_Boolean aResult = myProjector->Perform(aBSpline,
aBSpline->FirstParameter(),
aBSpline->LastParameter(),
aPCurve,
aTolFirst,
aTolLast);
ASSERT_TRUE(aResult) << "Projection with different endpoint tolerances should succeed";
ASSERT_FALSE(aPCurve.IsNull());
}
//==================================================================================================
// Partial curve projection tests
//==================================================================================================
// Test: Project only first quarter of full circle
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, PartialCircle_I1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), 5.0);
Handle(Geom_Circle) aCircle = new Geom_Circle(aCirc);
Handle(ShapeAnalysis_Surface) aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
// Project only 0 to PI/2 (first quadrant)
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aCircle, 0.0, M_PI / 2.0, aPCurve);
ASSERT_TRUE(aResult) << "Partial projection should succeed";
ASSERT_FALSE(aPCurve.IsNull());
// Verify endpoints: (5,0) to (0,5)
gp_Pnt2d aStart = aPCurve->Value(0.0);
gp_Pnt2d aEnd = aPCurve->Value(M_PI / 2.0);
EXPECT_NEAR(aStart.X(), 5.0, 0.01);
EXPECT_NEAR(aStart.Y(), 0.0, 0.01);
EXPECT_NEAR(aEnd.X(), 0.0, 0.01);
EXPECT_NEAR(aEnd.Y(), 5.0, 0.01);
}
// Test: Project middle portion of B-spline
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, MiddlePortionBSpline_I2)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(Geom_BSplineCurve) aBSpline = createPlanarBSpline();
ASSERT_FALSE(aBSpline.IsNull());
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
const double aFirst = aBSpline->FirstParameter();
const double aLast = aBSpline->LastParameter();
const double aRange = aLast - aFirst;
// Project middle 50%
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aBSpline, aFirst + 0.25 * aRange, aFirst + 0.75 * aRange, aPCurve);
ASSERT_TRUE(aResult) << "Middle portion projection should succeed";
ASSERT_FALSE(aPCurve.IsNull());
}
//==================================================================================================
// Multiple projection and reinitialization tests
//==================================================================================================
// Test: Multiple projections on same surface reuse setup correctly
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, MultipleProjections_J1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
Handle(ShapeAnalysis_Surface) aSAS = new ShapeAnalysis_Surface(aPlane);
myProjector->Init(aSAS, myTolerance);
// Project 5 different curves
for (int i = 0; i < 5; ++i)
{
const double aOffset = i * 10.0;
Handle(Geom_TrimmedCurve) aLine =
GC_MakeSegment(gp_Pnt(aOffset, 0, 0), gp_Pnt(aOffset + 5, 5, 0)).Value();
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
EXPECT_TRUE(aResult) << "Projection " << i << " should succeed";
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve " << i << " should not be null";
}
}
// Test: Surface reinitialization between projections
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, SurfaceReinitialization_J2)
{
// First surface
Handle(Geom_Plane) aPlane1 = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
myProjector->Init(new ShapeAnalysis_Surface(aPlane1), myTolerance);
Handle(Geom_TrimmedCurve) aLine1 = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(10, 10, 0)).Value();
Handle(Geom2d_Curve) aPCurve1;
myProjector->Perform(aLine1, aLine1->FirstParameter(), aLine1->LastParameter(), aPCurve1);
ASSERT_FALSE(aPCurve1.IsNull());
// Second surface (different plane)
Handle(Geom_Plane) aPlane2 = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 5), gp_Dir(0, 0, 1)));
myProjector->Init(new ShapeAnalysis_Surface(aPlane2), myTolerance);
Handle(Geom_TrimmedCurve) aLine2 = GC_MakeSegment(gp_Pnt(0, 0, 5), gp_Pnt(10, 10, 5)).Value();
Handle(Geom2d_Curve) aPCurve2;
myProjector->Perform(aLine2, aLine2->FirstParameter(), aLine2->LastParameter(), aPCurve2);
ASSERT_FALSE(aPCurve2.IsNull());
}
//==================================================================================================
// Near-singularity tests (poles, degenerate regions)
//==================================================================================================
// Test: Project small circle near north pole of sphere
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, NearPoleOnSphere_K1)
{
const double aRadius = 10.0;
Handle(Geom_SphericalSurface) aSphere =
new Geom_SphericalSurface(gp_Ax2(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)), aRadius);
// Small circle very close to north pole (latitude = 89.4 degrees)
const double aLatitude = M_PI / 2.0 - 0.01;
const double aZ = aRadius * sin(aLatitude);
const double aCircRadius = aRadius * cos(aLatitude);
gp_Circ aCirc(gp_Ax2(gp_Pnt(0, 0, aZ), gp_Dir(0, 0, 1)), aCircRadius);
Handle(Geom_Circle) aCircle = new Geom_Circle(aCirc);
myProjector->Init(new ShapeAnalysis_Surface(aSphere), myTolerance);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aCircle, 0.0, 2.0 * M_PI, aPCurve);
// Should succeed even near pole
ASSERT_TRUE(aResult) << "Projection near pole should succeed";
ASSERT_FALSE(aPCurve.IsNull());
}
//==================================================================================================
// Edge case tests
//==================================================================================================
// Test: Very short curve projection should not crash
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, VeryShortCurve_L1)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Very short line segment (1e-6 length)
Handle(Geom_TrimmedCurve) aLine = GC_MakeSegment(gp_Pnt(0, 0, 0), gp_Pnt(1e-6, 0, 0)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), 1e-4);
Handle(Geom2d_Curve) aPCurve;
// Result may vary but should not crash
(void)myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
SUCCEED() << "Short curve projection completed without crash";
}
// Test: Curve far from surface still projects
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, CurveFarFromSurface_L2)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Line at Z=100, far from plane at Z=0
Handle(Geom_TrimmedCurve) aLine = GC_MakeSegment(gp_Pnt(0, 0, 100), gp_Pnt(10, 10, 100)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), myTolerance);
Handle(Geom2d_Curve) aPCurve;
(void)myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
// Projection should still produce result (it projects onto the surface)
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve should be created even for distant curve";
}
// Test: Large tolerance projection
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, LargeTolerance_L3)
{
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pln(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)));
// Line slightly off plane
Handle(Geom_TrimmedCurve) aLine = GC_MakeSegment(gp_Pnt(0, 0, 0.1), gp_Pnt(10, 10, 0.1)).Value();
myProjector->Init(new ShapeAnalysis_Surface(aPlane), 1.0);
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult =
myProjector->Perform(aLine, aLine->FirstParameter(), aLine->LastParameter(), aPCurve);
ASSERT_TRUE(aResult) << "Large tolerance projection should succeed";
ASSERT_FALSE(aPCurve.IsNull());
}
//==================================================================================================
// Regression tests from bug reports
//==================================================================================================
// Test: Bug 27569 - B-spline with many knots on B-spline surface
// This test verifies that normal B-spline curves with many knots
// (but uniform spacing) project correctly without triggering false
// positive in knot spacing detection
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, Bug27569_ManyKnotsBSpline_M1)
{
// Create a B-spline curve with many knots by manually constructing it
// with degree 3 and 15 knots (requiring 15 + 3 + 1 - 2*(3+1) = 11 internal knots)
// For degree 3 with 15 knots (all simple): nbPoles = nbKnots + degree - 1 = 15 + 3 - 1 = 17
const int aDegree = 3;
const int aNbKnots = 15;
const int aNbPoles = aNbKnots + aDegree - 1; // = 17
TColgp_Array1OfPnt aPoles(1, aNbPoles);
for (int i = 1; i <= aNbPoles; ++i)
{
const double t = (i - 1.0) / (aNbPoles - 1.0);
const double aAngle = t * 2.0 * M_PI;
const double aRadius = 50.0 + 20.0 * cos(3.0 * aAngle);
aPoles(i) = gp_Pnt(aRadius * cos(aAngle), -30.0 * t, aRadius * sin(aAngle));
}
// Uniform knot vector from 0 to 1
TColStd_Array1OfReal aKnots(1, aNbKnots);
TColStd_Array1OfInteger aMults(1, aNbKnots);
for (int i = 1; i <= aNbKnots; ++i)
{
aKnots(i) = (i - 1.0) / (aNbKnots - 1.0);
aMults(i) = 1;
}
// Clamp end knots
aMults(1) = aDegree + 1;
aMults(aNbKnots) = aDegree + 1;
Handle(Geom_BSplineCurve) aBSplineCurve = new Geom_BSplineCurve(aPoles, aKnots, aMults, aDegree);
ASSERT_FALSE(aBSplineCurve.IsNull()) << "B-Spline curve should be created";
EXPECT_GE(aBSplineCurve->NbKnots(), 10) << "Curve should have at least 10 knots";
// Create a simple plane-like surface containing the curve
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pnt(0, -15, 0), gp_Dir(0, 1, 0));
// Test projection onto plane
myProjector->Init(aPlane, Precision::Confusion());
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aBSplineCurve,
aBSplineCurve->FirstParameter(),
aBSplineCurve->LastParameter(),
aPCurve);
EXPECT_TRUE(aResult) << "Projection should succeed for B-spline with many knots";
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve should be created";
}
// Test: Bug 27569 - Actual bug reproduction with high-degree B-spline curve on B-spline surface
// This test uses a B-spline curve of degree 7 with high multiplicities and a section of
// repeated poles (degenerate section), which is the actual problematic geometry from bug27569.
// The curve is projected onto a B-spline surface.
TEST_F(ShapeConstruct_ProjectCurveOnSurfaceTest, Bug27569_HighMultiplicityBSpline_M2)
{
// Construct a degree 7 B-spline curve with structure similar to bug27569:
// - 7 unique knots with high multiplicities: 15, 14, 14, 14, 14, 14, 15
// - Total knot count (with mults) = 15 + 14*5 + 15 = 100
// - Number of poles = 100 - 7 - 1 = 92 (approximately, depends on exact formula)
// For B-spline: sum of multiplicities = nbPoles + degree + 1
// => nbPoles = sum(mults) - degree - 1 = (15 + 14*5 + 15) - 7 - 1 = 100 - 8 = 92... but file has
// 85 Actually for non-periodic: sum(mults) = nbPoles + degree + 1
// => 15 + 14 + 14 + 14 + 14 + 14 + 15 = 100 = 85 + 7 + 1 = 93... doesn't match
// Looking at file: 85 poles, degree 7, knots with mults summing to 100
// Actually file shows: "14 85 7" meaning 14 spans(?), 85 poles, degree 7
// Let's construct a simpler problematic case:
// Degree 7 B-spline with a degenerate section (repeated identical poles)
const int aDegree = 7;
const int aNbKnots = 7;
// High multiplicities similar to bug27569 (adjusted for valid B-spline)
TColStd_Array1OfInteger aMults(1, aNbKnots);
aMults(1) = 8; // degree + 1 for clamped start
aMults(2) = 7;
aMults(3) = 7;
aMults(4) = 7;
aMults(5) = 7;
aMults(6) = 7;
aMults(7) = 8; // degree + 1 for clamped end
// Sum = 8 + 7*5 + 8 = 51
// nbPoles = sum(mults) - degree - 1 = 51 - 7 - 1 = 43
const int aNbPoles = 43;
// Uniform knot vector
TColStd_Array1OfReal aKnots(1, aNbKnots);
for (int i = 1; i <= aNbKnots; ++i)
{
aKnots(i) = (i - 1); // Knots: 0, 1, 2, 3, 4, 5, 6
}
// Create poles with a degenerate section (repeated identical poles in the middle)
TColgp_Array1OfPnt aPoles(1, aNbPoles);
// First section: smooth curve from start
for (int i = 1; i <= 15; ++i)
{
const double t = (i - 1.0) / 14.0;
const double aX = 144.0 - t * 74.0; // Decreasing from 144 to 70
const double aY = -t * 0.0002;
const double aZ = 8.3 + t * 51.7; // Increasing from 8.3 to 60
aPoles(i) = gp_Pnt(aX, aY, aZ);
}
// Middle section: degenerate (repeated identical poles) - like in bug27569
for (int i = 16; i <= 28; ++i)
{
aPoles(i) = gp_Pnt(70.0, 0.0, 60.0); // All identical
}
// Last section: smooth curve to end
for (int i = 29; i <= aNbPoles; ++i)
{
const double t = (i - 29.0) / (aNbPoles - 29.0);
const double aX = 70.0 - t * 80.0; // Decreasing from 70 to -10
const double aY = -t * 32.0;
const double aZ = 60.0 - t * 60.0; // Decreasing from 60 to 0
aPoles(i) = gp_Pnt(aX, aY, aZ);
}
Handle(Geom_BSplineCurve) aBSplineCurve;
try
{
aBSplineCurve = new Geom_BSplineCurve(aPoles, aKnots, aMults, aDegree);
}
catch (Standard_Failure const&)
{
// If construction fails, the test documents the expected behavior
GTEST_SKIP() << "Could not construct B-spline with high multiplicities";
}
ASSERT_FALSE(aBSplineCurve.IsNull()) << "B-Spline curve should be created";
// Create B-spline surface (simplified version of bug27569 surface)
// 8x2 poles B-spline surface
const int aNbUPoles = 8;
const int aNbVPoles = 2;
TColgp_Array2OfPnt aSurfPoles(1, aNbUPoles, 1, aNbVPoles);
// V=0 row
aSurfPoles(1, 1) = gp_Pnt(70, 0, 60);
aSurfPoles(2, 1) = gp_Pnt(85.01, 0, 60);
aSurfPoles(3, 1) = gp_Pnt(98.87, 0, 55.07);
aSurfPoles(4, 1) = gp_Pnt(111.6, 0, 46.57);
aSurfPoles(5, 1) = gp_Pnt(123.15, 0, 35.68);
aSurfPoles(6, 1) = gp_Pnt(133.46, 0, 23.56);
aSurfPoles(7, 1) = gp_Pnt(142.46, 0, 11.32);
aSurfPoles(8, 1) = gp_Pnt(150, 0, 0);
// V=1 row (same with Y offset)
aSurfPoles(1, 2) = gp_Pnt(54.99, 0, 60);
aSurfPoles(2, 2) = gp_Pnt(41.13, 0, 55.07);
aSurfPoles(3, 2) = gp_Pnt(28.4, 0, 46.57);
aSurfPoles(4, 2) = gp_Pnt(16.85, 0, 35.68);
aSurfPoles(5, 2) = gp_Pnt(6.54, 0, 23.56);
aSurfPoles(6, 2) = gp_Pnt(-2.46, 0, 11.32);
aSurfPoles(7, 2) = gp_Pnt(-10, 0, 0);
aSurfPoles(8, 2) = gp_Pnt(-10, -35.91, 0);
TColStd_Array1OfReal aUKnots(1, 2);
TColStd_Array1OfReal aVKnots(1, 2);
TColStd_Array1OfInteger aUMults(1, 2);
TColStd_Array1OfInteger aVMults(1, 2);
aUKnots(1) = 0;
aUKnots(2) = 1;
aVKnots(1) = 0;
aVKnots(2) = 1;
aUMults(1) = 8;
aUMults(2) = 8;
aVMults(1) = 2;
aVMults(2) = 2;
Handle(Geom_BSplineSurface) aBSplineSurface;
try
{
aBSplineSurface = new Geom_BSplineSurface(aSurfPoles, aUKnots, aVKnots, aUMults, aVMults, 7, 1);
}
catch (Standard_Failure const&)
{
// Fall back to a simple plane if surface construction fails
Handle(Geom_Plane) aPlane = new Geom_Plane(gp_Pnt(70, 0, 30), gp_Dir(0, 1, 0));
myProjector->Init(aPlane, Precision::Confusion());
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aBSplineCurve,
aBSplineCurve->FirstParameter(),
aBSplineCurve->LastParameter(),
aPCurve);
// This test documents expected behavior - projection should succeed
EXPECT_TRUE(aResult) << "Projection should succeed";
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve should be created";
return;
}
myProjector->Init(aBSplineSurface, Precision::Confusion());
Handle(Geom2d_Curve) aPCurve;
const Standard_Boolean aResult = myProjector->Perform(aBSplineCurve,
aBSplineCurve->FirstParameter(),
aBSplineCurve->LastParameter(),
aPCurve);
// This is the key assertion - the projection SHOULD succeed but currently may fail
// for curves with degenerate sections (repeated poles)
EXPECT_TRUE(aResult) << "Projection should succeed for B-spline with degenerate section";
EXPECT_FALSE(aPCurve.IsNull()) << "PCurve should be created";
}

View File

@@ -20,24 +20,21 @@
#include <Standard.hxx>
#include <Standard_Type.hxx>
#include <Standard_Integer.hxx>
#include <gp_Pnt.hxx>
#include <gp_Pnt2d.hxx>
#include <Standard_Transient.hxx>
#include <NCollection_Array1.hxx>
#include <Precision.hxx>
#include <ShapeExtend_Status.hxx>
#include <GeomAbs_Shape.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColgp_HArray1OfPnt2d.hxx>
#include <TColgp_HArray1OfPnt.hxx>
#include <TColgp_SequenceOfPnt.hxx>
#include <TColgp_SequenceOfPnt2d.hxx>
#include <TColStd_SequenceOfReal.hxx>
class ShapeAnalysis_Surface;
class Geom_Surface;
class Geom_Curve;
class Geom2d_Curve;
#include <Standard_Transient.hxx>
// resolve name collisions with X11 headers
#include <utility>
class Geom2d_Curve;
class Geom_Curve;
class Geom_Surface;
class ShapeAnalysis_Surface;
// Resolve name collisions with X11 headers
#ifdef Status
#undef Status
#endif
@@ -57,6 +54,14 @@ DEFINE_STANDARD_HANDLE(ShapeConstruct_ProjectCurveOnSurface, Standard_Transient)
//! the surface) are recognized with the given precision.
class ShapeConstruct_ProjectCurveOnSurface : public Standard_Transient
{
public:
// Type aliases for internal containers (1-based indexing for geometry arrays)
using ArrayOfPnt = NCollection_Array1<gp_Pnt>;
using ArrayOfPnt2d = NCollection_Array1<gp_Pnt2d>;
using ArrayOfReal = NCollection_Array1<Standard_Real>;
// Cache uses 0-based indexing (simple 2-element array)
using CachePoint = std::pair<gp_Pnt, gp_Pnt2d>;
using CacheArray = NCollection_Array1<CachePoint>;
public:
//! Empty constructor.
@@ -64,168 +69,235 @@ public:
//! Initializes the object with all necessary parameters,
//! i.e. surface and precision
Standard_EXPORT virtual void Init(const Handle(Geom_Surface)& surf, const Standard_Real preci);
//! @param[in] theSurf the surface to project on
//! @param[in] thePreci the precision for projection
Standard_EXPORT virtual void Init(const Handle(Geom_Surface)& theSurf,
const Standard_Real thePreci);
//! Initializes the object with all necessary parameters,
//! i.e. surface and precision
Standard_EXPORT virtual void Init(const Handle(ShapeAnalysis_Surface)& surf,
const Standard_Real preci);
//! @param[in] theSurf the surface to project on (ShapeAnalysis_Surface)
//! @param[in] thePreci the precision for projection
Standard_EXPORT virtual void Init(const Handle(ShapeAnalysis_Surface)& theSurf,
const Standard_Real thePreci);
//! Loads a surface (in the form of Geom_Surface) to project on
Standard_EXPORT void SetSurface(const Handle(Geom_Surface)& surf);
//! @param[in] theSurf the surface to project on
Standard_EXPORT void SetSurface(const Handle(Geom_Surface)& theSurf);
//! Loads a surface (in the form of ShapeAnalysis_Surface) to project on
Standard_EXPORT void SetSurface(const Handle(ShapeAnalysis_Surface)& surf);
//! @param[in] theSurf the surface to project on
Standard_EXPORT void SetSurface(const Handle(ShapeAnalysis_Surface)& theSurf);
//! Sets value for current precision
Standard_EXPORT void SetPrecision(const Standard_Real preci);
//! Returns (modifiable) the build-curve-3d mode, by default False
//! If True, if the projected curve has been recomputed by
//! interpolation, the 3d curve is also rebuild by interpolation
Standard_EXPORT Standard_Boolean& BuildCurveMode();
//! @param[in] thePreci the precision value
Standard_EXPORT void SetPrecision(const Standard_Real thePreci);
//! Returns (modifiable) the flag specifying to which side of
//! parametrical space adjust part of pcurve which lies on seam.
//! This is required in very rare case when 3d curve which is
//! to be projected goes partly along the seam on the closed
//! surface with singularity (e.g. sphere), goes through the
//! degenerated point and paerly lies on internal area of surface.
//! degenerated point and partly lies on internal area of surface.
//!
//! If this flag is True, the seam part of such curve will be
//! adjusted to the left side of parametric space (on sphere U=0),
//! else to the right side (on sphere U=2*PI)
//! Default value is True
//! @return modifiable reference to the adjustment flag
Standard_EXPORT Standard_Integer& AdjustOverDegenMode();
//! Returns the status of last Perform
//! @param[in] theStatus the status to query
//! @return true if the specified status is set
Standard_EXPORT Standard_Boolean Status(const ShapeExtend_Status theStatus) const;
//! Computes the projection of 3d curve onto a surface using the
//! specialized algorithm. Returns False if projector fails,
//! otherwise, if pcurve computed successfully, returns True.
//! The output curve 2D is guaranteed to be same-parameter
//! with input curve 3D on the interval [First, Last]. If the output curve
//! with input curve 3D on the interval [theFirst, theLast]. If the output curve
//! lies on a direct line the infinite line is returned, in the case
//! same-parameter condition is satisfied.
//! TolFirst and TolLast are the tolerances at the ends of input curve 3D.
Standard_EXPORT virtual Standard_Boolean Perform(Handle(Geom_Curve)& c3d,
const Standard_Real First,
const Standard_Real Last,
Handle(Geom2d_Curve)& c2d,
const Standard_Real TolFirst = -1,
const Standard_Real TolLast = -1);
//! @param[in] theC3D the 3D curve to project
//! @param[in] theFirst the first parameter of the curve
//! @param[in] theLast the last parameter of the curve
//! @param[out] theC2D the resulting 2D curve
//! @param[in] theTolFirst the tolerance at the first point (default: Precision::Confusion())
//! @param[in] theTolLast the tolerance at the last point (default: Precision::Confusion())
//! @return true if projection succeeded
Standard_EXPORT virtual Standard_Boolean Perform(
const Handle(Geom_Curve)& theC3D,
const Standard_Real theFirst,
const Standard_Real theLast,
Handle(Geom2d_Curve)& theC2D,
const Standard_Real theTolFirst = Precision::Confusion(),
const Standard_Real theTolLast = Precision::Confusion());
DEFINE_STANDARD_RTTIEXT(ShapeConstruct_ProjectCurveOnSurface, Standard_Transient)
protected:
//! Try to approximate 3D curve by Geom2d_Line
//! or Geom2d_BSplineCurve with degree 1 with specified tolerance.
//! @param[in] thePoints points obtained from 3d curve
//! @param[in] theParams parameters corresponding points on 3d curve
//! @param[out] thePoints2d 2d points lies on line in parametric space
//! @param[in] theTol tolerance used for compare initial points 3d and
//! 3d points obtained from line lying in parametric space of surface
//! @param[out] theIsRecompute flag indicating if recomputation is needed
//! @param[out] theIsFromCache flag indicating if result is from cache
//! @return the resulting 2D curve or null if line fitting failed
Standard_EXPORT Handle(Geom2d_Curve) getLine(const ArrayOfPnt& thePoints,
const ArrayOfReal& theParams,
ArrayOfPnt2d& thePoints2d,
const Standard_Real theTol,
Standard_Boolean& theIsRecompute,
Standard_Boolean& theIsFromCache) const;
//! Computes the projection of 3d curve onto a surface using the
//! standard algorithm from ProjLib. Returns False if standard
//! projector fails or raises an exception or cuts the curve by
//! parametrical bounds of the surface. Else, if pcurve computed
//! successfully, returns True.
//! The continuity, maxdeg and nbinterval are parameters of call
//! to Approx_CurveOnSurface. If nbinterval is equal to -1
//! (default), this value is computed depending on source 3d curve
//! and surface.
Standard_EXPORT Standard_Boolean PerformByProjLib(Handle(Geom_Curve)& c3d,
const Standard_Real First,
const Standard_Real Last,
Handle(Geom2d_Curve)& c2d,
const GeomAbs_Shape continuity = GeomAbs_C1,
const Standard_Integer maxdeg = 12,
const Standard_Integer nbinterval = -1);
//! @param[in] theC3D the 3D curve to project
//! @param[in] theFirst the first parameter of the curve
//! @param[in] theLast the last parameter of the curve
//! @param[out] theC2D the resulting 2D curve
//! @return true if projection succeeded
Standard_EXPORT Standard_Boolean PerformByProjLib(const Handle(Geom_Curve)& theC3D,
const Standard_Real theFirst,
const Standard_Real theLast,
Handle(Geom2d_Curve)& theC2D);
DEFINE_STANDARD_RTTIEXT(ShapeConstruct_ProjectCurveOnSurface, Standard_Transient)
//! Performs analytical projection for special cases (plane surfaces)
//! @param[in] theC3D the 3D curve to project
//! @return the resulting 2D curve or null if analytical projection not applicable
Standard_EXPORT Handle(Geom2d_Curve) projectAnalytic(const Handle(Geom_Curve)& theC3D) const;
protected:
//! Try to approximate 3D curve by Geom2d_Line
//! or Geom2d_BsplineCurve with degree 1 with specified tolerance.
//! points - points obtained from 3d curve.
//! params - parameters corresponding points on 3d curves
//! points2d - 2d points lies on line in parametric space
//! theTol - tolerance used for compare initial points 3d and
//! 3d points obtained from line lying in parameric space of surface
Standard_EXPORT Handle(Geom2d_Curve) getLine(const TColgp_SequenceOfPnt& points,
const TColStd_SequenceOfReal& params,
TColgp_SequenceOfPnt2d& points2d,
const Standard_Real theTol,
Standard_Boolean& IsRecompute,
Standard_Boolean& isFromCashe) const;
//! Main approximation routine for pcurve computation
//! @param[in] theNbPnt number of points
//! @param[in] theC3D the 3D curve
//! @param[in] theTolFirst tolerance at first point
//! @param[in] theTolLast tolerance at last point
//! @param[in,out] thePoints array of 3D points
//! @param[in,out] theParams array of parameters
//! @param[out] thePoints2d array of 2D points
//! @param[out] theC2D resulting 2D curve
//! @return true if approximation succeeded
Standard_EXPORT Standard_Boolean approxPCurve(const Standard_Integer theNbPnt,
const Handle(Geom_Curve)& theC3D,
const Standard_Real theTolFirst,
const Standard_Real theTolLast,
ArrayOfPnt& thePoints,
ArrayOfReal& theParams,
ArrayOfPnt2d& thePoints2d,
Handle(Geom2d_Curve)& theC2D);
Handle(ShapeAnalysis_Surface) mySurf;
Standard_Real myPreci;
Standard_Boolean myBuild;
Standard_Integer myStatus;
Standard_Integer myAdjustOverDegen;
Standard_Integer myNbCashe;
gp_Pnt myCashe3d[2];
gp_Pnt2d myCashe2d[2];
//! Corrects extremity point near singularity
//! @param[in] theC3D the 3D curve
//! @param[in] theParams array of parameters
//! @param[in,out] thePoints2d array of 2D points
//! @param[in] theIsFirstPoint true if correcting first point, false for last
//! @param[in] thePointOnIsoLine point on isoline
//! @param[in] theIsUIso true if U-isoline, false if V-isoline
Standard_EXPORT void correctExtremity(const Handle(Geom_Curve)& theC3D,
const ArrayOfReal& theParams,
ArrayOfPnt2d& thePoints2d,
const Standard_Boolean theIsFirstPoint,
const gp_Pnt2d& thePointOnIsoLine,
const Standard_Boolean theIsUIso);
//! Inserts additional point or adjusts coordinate to handle period jumps
//! @param[in,out] theToAdjust flag indicating if adjustment should be done
//! @param[in] theIndCoord coordinate index (1=U, 2=V)
//! @param[in] thePeriod the period value
//! @param[in] theTolOnPeriod tolerance on period
//! @param[in,out] theCurCoord current coordinate value
//! @param[in] thePrevCoord previous coordinate value
//! @param[in] theC3D the 3D curve
//! @param[in,out] theIndex current point index
//! @param[in,out] thePoints array of 3D points
//! @param[in,out] theParams array of parameters
//! @param[in,out] thePoints2d array of 2D points
Standard_EXPORT void insertAdditionalPointOrAdjust(Standard_Boolean& theToAdjust,
const Standard_Integer theIndCoord,
const Standard_Real thePeriod,
const Standard_Real theTolOnPeriod,
Standard_Real& theCurCoord,
const Standard_Real thePrevCoord,
const Handle(Geom_Curve)& theC3D,
Standard_Integer& theIndex,
ArrayOfPnt& thePoints,
ArrayOfReal& theParams,
ArrayOfPnt2d& thePoints2d);
//! Interpolates 2D curve from points
//! @param[in] theNbPnt number of points
//! @param[in] thePoints2d array of 2D points
//! @param[in] theParams array of parameters
//! @return the interpolated 2D curve or null on failure
Standard_EXPORT Handle(Geom2d_Curve) interpolatePCurve(const Standard_Integer theNbPnt,
const ArrayOfPnt2d& thePoints2d,
const ArrayOfReal& theParams) const;
//! Approximates 2D curve from points
//! @param[in] thePoints2d array of 2D points
//! @param[in] theParams array of parameters
//! @return the approximated 2D curve or null on failure
Standard_EXPORT Handle(Geom2d_Curve) approximatePCurve(const ArrayOfPnt2d& thePoints2d,
const ArrayOfReal& theParams) const;
//! Checks and removes coincident 3D points
//! @param[in,out] thePoints array of 3D points
//! @param[in,out] theParams array of parameters
//! @param[in,out] thePreci precision (may be adjusted)
Standard_EXPORT void checkPoints(ArrayOfPnt& thePoints,
ArrayOfReal& theParams,
Standard_Real& thePreci) const;
//! Checks and removes coincident 2D points
//! @param[in,out] thePoints2d array of 2D points
//! @param[in,out] theParams array of parameters
//! @param[in,out] thePreci precision (may be adjusted)
Standard_EXPORT void checkPoints2d(ArrayOfPnt2d& thePoints2d,
ArrayOfReal& theParams,
Standard_Real& thePreci) const;
//! Detects if curve is isoparametric (U=const or V=const)
//! @param[in] theNbPnt number of points
//! @param[in] thePoints array of 3D points
//! @param[in] theParams array of parameters
//! @param[out] theIsTypeU true if U-iso, false if V-iso
//! @param[out] theP1OnIso true if first point is on iso
//! @param[out] theValueP1 2D value of first point on iso
//! @param[out] theP2OnIso true if last point is on iso
//! @param[out] theValueP2 2D value of last point on iso
//! @param[out] theIsoPar2d3d true if parametrization matches
//! @param[out] theCIso the isoline curve
//! @param[out] theT1 first iso parameter
//! @param[out] theT2 last iso parameter
//! @param[out] theParamsOut output parameters on isoline
//! @return true if curve is isoparametric
Standard_EXPORT Standard_Boolean isAnIsoparametric(const Standard_Integer theNbPnt,
const ArrayOfPnt& thePoints,
const ArrayOfReal& theParams,
Standard_Boolean& theIsTypeU,
Standard_Boolean& theP1OnIso,
gp_Pnt2d& theValueP1,
Standard_Boolean& theP2OnIso,
gp_Pnt2d& theValueP2,
Standard_Boolean& theIsoPar2d3d,
Handle(Geom_Curve)& theCIso,
Standard_Real& theT1,
Standard_Real& theT2,
ArrayOfReal& theParamsOut) const;
private:
Standard_EXPORT Handle(Geom2d_Curve) ProjectAnalytic(const Handle(Geom_Curve)& c3d) const;
Standard_EXPORT Standard_Boolean ApproxPCurve(const Standard_Integer nbrPnt,
const Handle(Geom_Curve)& c3d,
const Standard_Real TolFirst,
const Standard_Real TolLast,
TColgp_SequenceOfPnt& points,
TColStd_SequenceOfReal& params,
TColgp_SequenceOfPnt2d& points2d,
Handle(Geom2d_Curve)& c2d);
Standard_EXPORT void CorrectExtremity(const Handle(Geom_Curve)& theC3d,
const TColStd_SequenceOfReal& theParams,
TColgp_SequenceOfPnt2d& thePnt2d,
const Standard_Boolean theIsFirstPoint,
const gp_Pnt2d& thePointOnIsoLine,
const Standard_Boolean theIsUiso);
Standard_EXPORT void InsertAdditionalPointOrAdjust(Standard_Boolean& ToAdjust,
const Standard_Integer theIndCoord,
const Standard_Real Period,
const Standard_Real TolOnPeriod,
Standard_Real& CurCoord,
const Standard_Real prevCoord,
const Handle(Geom_Curve)& c3d,
Standard_Integer& theIndex,
TColgp_SequenceOfPnt& points,
TColStd_SequenceOfReal& params,
TColgp_SequenceOfPnt2d& pnt2d);
Standard_EXPORT Handle(Geom2d_Curve) InterpolatePCurve(const Standard_Integer nbrPnt,
Handle(TColgp_HArray1OfPnt2d)& points2d,
Handle(TColStd_HArray1OfReal)& params,
const Handle(Geom_Curve)& orig) const;
Standard_EXPORT Handle(Geom2d_Curve) ApproximatePCurve(const Standard_Integer nbrPnt,
Handle(TColgp_HArray1OfPnt2d)& points2d,
Handle(TColStd_HArray1OfReal)& params,
const Handle(Geom_Curve)& orig) const;
Standard_EXPORT Handle(Geom_Curve) InterpolateCurve3d(const Standard_Integer nbrPnt,
Handle(TColgp_HArray1OfPnt)& points,
Handle(TColStd_HArray1OfReal)& params,
const Handle(Geom_Curve)& orig) const;
Standard_EXPORT void CheckPoints(Handle(TColgp_HArray1OfPnt)& points,
Handle(TColStd_HArray1OfReal)& params,
Standard_Real& preci) const;
Standard_EXPORT void CheckPoints2d(Handle(TColgp_HArray1OfPnt2d)& points,
Handle(TColStd_HArray1OfReal)& params,
Standard_Real& preci) const;
Standard_EXPORT Standard_Boolean IsAnIsoparametric(const Standard_Integer nbrPnt,
const TColgp_SequenceOfPnt& points,
const TColStd_SequenceOfReal& params,
Standard_Boolean& isoTypeU,
Standard_Boolean& p1OnIso,
gp_Pnt2d& valueP1,
Standard_Boolean& p2OnIso,
gp_Pnt2d& valueP2,
Standard_Boolean& isoPar2d3d,
Handle(Geom_Curve)& cIso,
Standard_Real& t1,
Standard_Real& t2,
TColStd_Array1OfReal& pout) const;
Handle(ShapeAnalysis_Surface) mySurf; //!< Surface to project on
Standard_Real myPreci; //!< Current precision
Standard_Integer myStatus; //!< Operation status
Standard_Integer myAdjustOverDegen; //!< Seam adjustment flag
CacheArray myCache; //!< Cached 3D/2D point pairs for projection optimization
};
#endif // _ShapeConstruct_ProjectCurveOnSurface_HeaderFile