Loading files
3d097a032This file is pinned to the selected release. Download the original to open it in your design tool.
// 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 <BndLib.hxx>
#include <BndLib_Add2dCurve.hxx>
#include <BndLib_Add3dCurve.hxx>
#include <BndLib_AddSurface.hxx>
#include <Bnd_Box.hxx>
#include <Bnd_Box2d.hxx>
#include <Geom2d_BSplineCurve.hxx>
#include <Geom2d_BezierCurve.hxx>
#include <Geom2d_Circle.hxx>
#include <Geom2d_Ellipse.hxx>
#include <Geom2d_Line.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <Geom2dAdaptor_Curve.hxx>
#include <Geom_BSplineCurve.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom_BezierCurve.hxx>
#include <Geom_BezierSurface.hxx>
#include <Geom_Circle.hxx>
#include <Geom_ConicalSurface.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <Geom_Ellipse.hxx>
#include <Geom_Line.hxx>
#include <Geom_Plane.hxx>
#include <Geom_SphericalSurface.hxx>
#include <Geom_ToroidalSurface.hxx>
#include <Geom_TrimmedCurve.hxx>
#include <GeomAdaptor_Curve.hxx>
#include <GeomAdaptor_Surface.hxx>
#include <gp_Ax1.hxx>
#include <gp_Ax2.hxx>
#include <gp_Ax2d.hxx>
#include <gp_Ax3.hxx>
#include <gp_Circ.hxx>
#include <gp_Circ2d.hxx>
#include <gp_Cone.hxx>
#include <gp_Cylinder.hxx>
#include <gp_Dir.hxx>
#include <gp_Elips.hxx>
#include <gp_Elips2d.hxx>
#include <gp_Hypr.hxx>
#include <gp_Hypr2d.hxx>
#include <gp_Lin.hxx>
#include <gp_Lin2d.hxx>
#include <gp_Parab.hxx>
#include <gp_Parab2d.hxx>
#include <gp_Pnt.hxx>
#include <gp_Sphere.hxx>
#include <gp_Torus.hxx>
#include <gp_Vec.hxx>
#include <Precision.hxx>
#include <NCollection_Array1.hxx>
#include <gp_Pnt2d.hxx>
#include <NCollection_Array2.hxx>
#include <Standard_Integer.hxx>
#include <cmath>
#include <limits>
//=================================================================================================
// Line Tests
//=================================================================================================
TEST(BndLibTest, Line_FiniteSegment)
{
// Line along X axis from (0,0,0) in direction (1,0,0)
gp_Lin aLine(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.));
Bnd_Box aBox;
BndLib::Add(aLine, 0., 10., 0., aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 10., Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 0., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 0., Precision::Confusion());
}
TEST(BndLibTest, Line_PositiveDirection_NegativeInfinite)
{
// Line along +X axis with P1 = -Inf, P2 = 5
// As t -> -Inf, x -> -Inf, so Xmin should be open
gp_Lin aLine(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.));
Bnd_Box aBox;
BndLib::Add(aLine, -std::numeric_limits<double>::infinity(), 5., 0., aBox);
EXPECT_TRUE(aBox.IsOpenXmin()) << "Xmin should be open for line in +X direction with P1=-Inf";
EXPECT_FALSE(aBox.IsOpenXmax()) << "Xmax should not be open";
}
TEST(BndLibTest, Line_NegativeDirection_NegativeInfinite)
{
// Line along -X axis with P1 = -Inf, P2 = 5
// Direction is (-1, 0, 0), so as t -> -Inf, x -> +Inf
// Therefore Xmax should be open, not Xmin
gp_Lin aLine(gp_Pnt(0., 0., 0.), gp_Dir(-1., 0., 0.));
Bnd_Box aBox;
BndLib::Add(aLine, -std::numeric_limits<double>::infinity(), 5., 0., aBox);
EXPECT_TRUE(aBox.IsOpenXmax()) << "Xmax should be open for line in -X direction with P1=-Inf";
EXPECT_FALSE(aBox.IsOpenXmin()) << "Xmin should not be open";
}
TEST(BndLibTest, Line_NegativeDirection_PositiveInfinite)
{
// Line along -X axis with P1 = -5, P2 = +Inf
// Direction is (-1, 0, 0), so as t -> +Inf, x -> -Inf
// Therefore Xmin should be open
gp_Lin aLine(gp_Pnt(0., 0., 0.), gp_Dir(-1., 0., 0.));
Bnd_Box aBox;
BndLib::Add(aLine, -5., std::numeric_limits<double>::infinity(), 0., aBox);
EXPECT_TRUE(aBox.IsOpenXmin()) << "Xmin should be open for line in -X direction with P2=+Inf";
EXPECT_FALSE(aBox.IsOpenXmax()) << "Xmax should not be open";
}
TEST(BndLibTest, Line_DiagonalDirection_BothInfinite)
{
// Line in diagonal direction (-1, -1, -1) with P1=-Inf, P2=+Inf
// As t -> -Inf: position goes to (+Inf, +Inf, +Inf) - opens max
// As t -> +Inf: position goes to (-Inf, -Inf, -Inf) - opens min
gp_Lin aLine(gp_Pnt(0., 0., 0.), gp_Dir(-1., -1., -1.));
Bnd_Box aBox;
BndLib::Add(aLine,
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
0.,
aBox);
EXPECT_TRUE(aBox.IsOpenXmin()) << "Xmin should be open";
EXPECT_TRUE(aBox.IsOpenXmax()) << "Xmax should be open";
EXPECT_TRUE(aBox.IsOpenYmin()) << "Ymin should be open";
EXPECT_TRUE(aBox.IsOpenYmax()) << "Ymax should be open";
EXPECT_TRUE(aBox.IsOpenZmin()) << "Zmin should be open";
EXPECT_TRUE(aBox.IsOpenZmax()) << "Zmax should be open";
}
//=================================================================================================
// Circle Tests
//=================================================================================================
TEST(BndLibTest, Circle_Full)
{
// Circle of radius 5 centered at origin in XY plane
gp_Circ aCirc(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
Bnd_Box aBoxFull;
Bnd_Box aBoxArc;
double aTol = 0.;
BndLib::Add(aCirc, aTol, aBoxFull);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBoxFull.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 0., Precision::Confusion());
// Full circle via parameters [0, 2*PI]
BndLib::Add(aCirc, 0., 2. * M_PI, aTol, aBoxArc);
aBoxArc.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
TEST(BndLibTest, Circle_Arc_FirstQuadrant)
{
// Circle arc from 0 to PI/2 (first quadrant)
gp_Circ aCirc(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aCirc, 0., M_PI / 2., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
TEST(BndLibTest, Circle_RotatedAxis)
{
// Circle in XZ plane (axis along Y)
gp_Circ aCirc(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 1., 0.)), 3.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aCirc, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -3., Precision::Confusion());
EXPECT_NEAR(aXmax, 3., Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 0., Precision::Confusion());
EXPECT_NEAR(aZmin, -3., Precision::Confusion());
EXPECT_NEAR(aZmax, 3., Precision::Confusion());
}
//=================================================================================================
// Ellipse Tests
//=================================================================================================
TEST(BndLibTest, Ellipse_Full)
{
// Ellipse with major radius 10, minor radius 5 in XY plane
gp_Elips aElips(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 10., 5.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aElips, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -10., Precision::Confusion());
EXPECT_NEAR(aXmax, 10., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 0., Precision::Confusion());
}
TEST(BndLibTest, Ellipse_Arc)
{
// Ellipse arc from 0 to PI/2
gp_Elips aElips(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 10., 5.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aElips, 0., M_PI / 2., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// At t=0: (10, 0, 0), at t=PI/2: (0, 5, 0)
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 10., Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
//=================================================================================================
// Hyperbola Tests
//=================================================================================================
TEST(BndLibTest, Hyperbola_SimpleArc)
{
// Hyperbola with RealAxis=3, ImaginaryAxis=2 in XY plane
// Parameterization: (3*cosh(t), 2*sinh(t), 0)
gp_Hypr aHypr(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 3., 2.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aHypr, -1., 1., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// At t=-1: (3*cosh(-1), 2*sinh(-1)) = (4.629..., -2.350...)
// At t=1: (3*cosh(1), 2*sinh(1)) = (4.629..., 2.350...)
// At t=0: (3, 0) - minimum X
double aExpectedXmin = 3.; // at t=0
double aExpectedXmax = 3. * std::cosh(1.); // at t=+-1
double aExpectedYmin = 2. * std::sinh(-1.); // at t=-1
double aExpectedYmax = 2. * std::sinh(1.); // at t=1
EXPECT_NEAR(aXmin, aExpectedXmin, Precision::Confusion());
EXPECT_NEAR(aXmax, aExpectedXmax, Precision::Confusion());
EXPECT_NEAR(aYmin, aExpectedYmin, Precision::Confusion());
EXPECT_NEAR(aYmax, aExpectedYmax, Precision::Confusion());
}
TEST(BndLibTest, Hyperbola_Rotated_MultipleExtrema)
{
// Rotated hyperbola where X, Y, Z extrema occur at different parameter values.
// This tests the fix for the extrema loop that previously broke after finding
// the first extremum.
gp_Ax2 aAx2(gp_Pnt(0., 0., 0.), gp_Dir(1., 1., 1.), gp_Dir(1., -1., 0.));
gp_Hypr aHypr(aAx2, 5., 3.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aHypr, -2., 2., aTol, aBox);
// Verify box is valid and contains reasonable bounds
EXPECT_FALSE(aBox.IsVoid()) << "Box should not be void";
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// For a rotated hyperbola, the box should be non-degenerate in all dimensions
EXPECT_LT(aXmin, aXmax) << "X range should be non-zero";
EXPECT_LT(aYmin, aYmax) << "Y range should be non-zero";
// Z may be zero if hyperbola lies in a plane, but should still be valid
}
TEST(BndLibTest, Hyperbola_InfiniteParameter)
{
// Hyperbola with one infinite parameter
gp_Hypr aHypr(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 3., 2.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aHypr, 0., std::numeric_limits<double>::infinity(), aTol, aBox);
EXPECT_TRUE(aBox.IsOpenXmax()) << "Xmax should be open for hyperbola with P2=+Inf";
EXPECT_TRUE(aBox.IsOpenYmax()) << "Ymax should be open for hyperbola with P2=+Inf";
}
//=================================================================================================
// Parabola Tests
//=================================================================================================
TEST(BndLibTest, Parabola_FiniteArc)
{
// Parabola with focal length 2 in XY plane
// Parameterization: (t^2/(4*f), t, 0) = (t^2/8, t, 0) for f=2
gp_Parab aParab(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 2.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aParab, -4., 4., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// At t=0: (0, 0, 0)
// At t=+-4: (16/8, +-4, 0) = (2, +-4, 0)
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 2., Precision::Confusion());
EXPECT_NEAR(aYmin, -4., Precision::Confusion());
EXPECT_NEAR(aYmax, 4., Precision::Confusion());
}
TEST(BndLibTest, Parabola_InfiniteParameter)
{
// Parabola with infinite parameter
gp_Parab aParab(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 2.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aParab, 0., std::numeric_limits<double>::infinity(), aTol, aBox);
EXPECT_TRUE(aBox.IsOpenXmax()) << "Xmax should be open for parabola with P2=+Inf";
EXPECT_TRUE(aBox.IsOpenYmax()) << "Ymax should be open for parabola with P2=+Inf";
}
//=================================================================================================
// Sphere Tests
//=================================================================================================
TEST(BndLibTest, Sphere_Full)
{
// Sphere of radius 7 centered at origin
gp_Sphere aSphere(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 7.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aSphere, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -7., Precision::Confusion());
EXPECT_NEAR(aXmax, 7., Precision::Confusion());
EXPECT_NEAR(aYmin, -7., Precision::Confusion());
EXPECT_NEAR(aYmax, 7., Precision::Confusion());
EXPECT_NEAR(aZmin, -7., Precision::Confusion());
EXPECT_NEAR(aZmax, 7., Precision::Confusion());
}
TEST(BndLibTest, Sphere_Patch)
{
// Sphere patch: upper hemisphere (V from 0 to PI/2)
gp_Sphere aSphere(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
Bnd_Box aBox;
double aTol = 0.;
// Full U range, V from 0 to PI/2 (upper hemisphere)
BndLib::Add(aSphere, 0., 2. * M_PI, 0., M_PI / 2., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 5., Precision::Confusion());
}
TEST(BndLibTest, Sphere_Translated)
{
// Sphere centered at (10, 20, 30) with radius 3
gp_Sphere aSphere(gp_Ax3(gp_Pnt(10., 20., 30.), gp_Dir(0., 0., 1.)), 3.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aSphere, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 7., Precision::Confusion());
EXPECT_NEAR(aXmax, 13., Precision::Confusion());
EXPECT_NEAR(aYmin, 17., Precision::Confusion());
EXPECT_NEAR(aYmax, 23., Precision::Confusion());
EXPECT_NEAR(aZmin, 27., Precision::Confusion());
EXPECT_NEAR(aZmax, 33., Precision::Confusion());
}
//=================================================================================================
// Cylinder Tests
//=================================================================================================
TEST(BndLibTest, Cylinder_FinitePatch)
{
// Cylinder of radius 4, axis along Z
gp_Cylinder aCyl(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 4.);
Bnd_Box aBox;
double aTol = 0.;
// Full U range, V from 0 to 10
BndLib::Add(aCyl, 0., 2. * M_PI, 0., 10., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -4., Precision::Confusion());
EXPECT_NEAR(aXmax, 4., Precision::Confusion());
EXPECT_NEAR(aYmin, -4., Precision::Confusion());
EXPECT_NEAR(aYmax, 4., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 10., Precision::Confusion());
}
TEST(BndLibTest, Cylinder_InfiniteV)
{
// Cylinder with infinite V parameter
gp_Cylinder aCyl(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 4.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aCyl, 0., std::numeric_limits<double>::infinity(), aTol, aBox);
EXPECT_TRUE(aBox.IsOpenZmax()) << "Zmax should be open for cylinder with VMax=+Inf";
EXPECT_FALSE(aBox.IsOpenZmin()) << "Zmin should not be open";
}
TEST(BndLibTest, Cylinder_PartialU)
{
// Cylinder with partial U range (quarter cylinder)
gp_Cylinder aCyl(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
Bnd_Box aBox;
double aTol = 0.;
// U from 0 to PI/2 (first quadrant), V from 0 to 10
BndLib::Add(aCyl, 0., M_PI / 2., 0., 10., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 10., Precision::Confusion());
}
//=================================================================================================
// Cone Tests
//=================================================================================================
TEST(BndLibTest, Cone_FinitePatch)
{
// Cone with semi-angle PI/4 (45 deg), reference radius 2
// Cone parameterization: P(u,v) = Apex + (RefRadius + v*sin(SemiAngle)) * (cos(u)*XDir +
// sin(u)*YDir)
// + v*cos(SemiAngle) * Axis
gp_Cone aCone(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), M_PI / 4., 2.);
Bnd_Box aBox;
double aTol = 0.;
// Full U range, V from 0 to 5
BndLib::Add(aCone, 0., 2. * M_PI, 0., 5., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// V=0: radius = RefRadius = 2, Z = 0
// V=5: radius = 2 + 5*sin(PI/4) = 2 + 5*sqrt(2)/2, Z = 5*cos(PI/4) = 5*sqrt(2)/2
double aMaxRadius = 2. + 5. * std::sin(M_PI / 4.);
double aMaxZ = 5. * std::cos(M_PI / 4.);
EXPECT_NEAR(aXmax, aMaxRadius, Precision::Confusion());
EXPECT_NEAR(aYmax, aMaxRadius, Precision::Confusion());
EXPECT_NEAR(aZmax, aMaxZ, Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
}
TEST(BndLibTest, Cone_NegativeV)
{
// Cone with negative V range (cone extending in opposite direction)
// Cone parameterization: Z = v*cos(SemiAngle)
gp_Cone aCone(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), M_PI / 6., 3.);
Bnd_Box aBox;
double aTol = 0.;
// Full U range, V from -5 to 0
BndLib::Add(aCone, 0., 2. * M_PI, -5., 0., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// At V=0: radius = 3, Z = 0
// At V=-5: radius = 3 + (-5)*sin(PI/6) = 3 - 2.5 = 0.5, Z = -5*cos(PI/6)
double aExpectedZmin = -5. * std::cos(M_PI / 6.);
EXPECT_NEAR(aZmin, aExpectedZmin, Precision::Confusion());
EXPECT_NEAR(aZmax, 0., Precision::Confusion());
}
TEST(BndLibTest, Cone_InfiniteV)
{
// Cone with infinite V parameter
// Note: BndLib::Add for cone with U,V parameters opens Z but not X/Y
// since the cone's bounding in X/Y is computed from the circular cross-sections
gp_Cone aCone(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), M_PI / 4., 2.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aCone, 0., std::numeric_limits<double>::infinity(), aTol, aBox);
// For cone with infinite V, the Z direction should be open
EXPECT_TRUE(aBox.IsOpenZmax()) << "Zmax should be open for cone with VMax=+Inf";
}
//=================================================================================================
// Torus Tests
//=================================================================================================
TEST(BndLibTest, Torus_Full)
{
// Torus with major radius 10, minor radius 3
gp_Torus aTorus(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 10., 3.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aTorus, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// Torus extends from -(R+r) to +(R+r) in X and Y
// and from -r to +r in Z
EXPECT_NEAR(aXmin, -13., Precision::Confusion());
EXPECT_NEAR(aXmax, 13., Precision::Confusion());
EXPECT_NEAR(aYmin, -13., Precision::Confusion());
EXPECT_NEAR(aYmax, 13., Precision::Confusion());
EXPECT_NEAR(aZmin, -3., Precision::Confusion());
EXPECT_NEAR(aZmax, 3., Precision::Confusion());
}
TEST(BndLibTest, Torus_Patch)
{
// Torus patch: quarter in U, half in V
// Torus parameterization: P(u,v) = Center + (R + r*cos(v)) * (cos(u)*XDir + sin(u)*YDir)
// + r*sin(v) * Axis
gp_Torus aTorus(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 10., 3.);
Bnd_Box aBox;
double aTol = 0.;
// U from 0 to PI/2, V from 0 to PI
BndLib::Add(aTorus, 0., M_PI / 2., 0., M_PI, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// Box should be valid and contain reasonable bounds
EXPECT_FALSE(aBox.IsVoid()) << "Box should not be void for torus patch";
// For U in [0, PI/2], X and Y should be non-negative at minimum
// The bounding box algorithm produces conservative bounds that may slightly
// exceed R+r due to sampling/approximation
EXPECT_GE(aXmin, -Precision::Confusion()) << "Xmin should be >= 0 for first quadrant";
EXPECT_GE(aYmin, -Precision::Confusion()) << "Ymin should be >= 0 for first quadrant";
// Z max should be approximately r (minor radius)
EXPECT_LE(aZmax, 3. + Precision::Confusion()) << "Zmax should not exceed r";
}
TEST(BndLibTest, Torus_NegativeVParameter)
{
// Torus patch with negative V parameters.
// This tests the fix for negative modulo: ((i % 8) + 8) % 8
// The main purpose is to verify no crash occurs with negative V.
gp_Torus aTorus(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 10., 3.);
Bnd_Box aBox;
double aTol = 0.;
// Full U, V from -PI to 0
BndLib::Add(aTorus, 0., 2. * M_PI, -M_PI, 0., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// Should produce valid bounds without crash (main test for the fix)
EXPECT_FALSE(aBox.IsVoid()) << "Box should not be void for negative V parameters";
// For V in [-PI, 0], Z = r*sin(v) ranges from -r (at v=-PI/2) to 0
// The bounding box should contain this range
EXPECT_LE(aZmin, 0.) << "Zmin should be <= 0 for V in [-PI, 0]";
EXPECT_GE(aZmin, -3. - Precision::Confusion()) << "Zmin should be >= -r";
}
TEST(BndLibTest, Torus_LargeNegativeVParameter)
{
// Torus with large negative V parameter that would cause
// out-of-bounds array access with incorrect modulo.
// This is the primary test for the negative modulo fix.
gp_Torus aTorus(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 10., 3.);
Bnd_Box aBox;
double aTol = 0.;
// V from -3*PI to -2*PI covers one full revolution
BndLib::Add(aTorus, 0., 2. * M_PI, -3. * M_PI, -2. * M_PI, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// Should produce valid bounds without crash (main test for the fix)
EXPECT_FALSE(aBox.IsVoid()) << "Box should not be void for large negative V parameters";
// Full revolution in U gives full X/Y extent, the box may be slightly larger
// due to conservative bounding box algorithm
EXPECT_LE(aXmin, -13. + Precision::Confusion()) << "Xmin should be at least -(R+r)";
EXPECT_GE(aXmax, 13. - Precision::Confusion()) << "Xmax should be at least R+r";
EXPECT_LE(aYmin, -13. + Precision::Confusion()) << "Ymin should be at least -(R+r)";
EXPECT_GE(aYmax, 13. - Precision::Confusion()) << "Ymax should be at least R+r";
}
TEST(BndLibTest, Torus_Translated)
{
// Torus centered at (5, 5, 5)
gp_Torus aTorus(gp_Ax3(gp_Pnt(5., 5., 5.), gp_Dir(0., 0., 1.)), 8., 2.);
Bnd_Box aBox;
double aTol = 0.;
BndLib::Add(aTorus, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 5. - 10., Precision::Confusion());
EXPECT_NEAR(aXmax, 5. + 10., Precision::Confusion());
EXPECT_NEAR(aYmin, 5. - 10., Precision::Confusion());
EXPECT_NEAR(aYmax, 5. + 10., Precision::Confusion());
EXPECT_NEAR(aZmin, 5. - 2., Precision::Confusion());
EXPECT_NEAR(aZmax, 5. + 2., Precision::Confusion());
}
//=================================================================================================
// 2D Tests
//=================================================================================================
TEST(BndLibTest, Line2d_FiniteSegment)
{
gp_Lin2d aLine(gp_Pnt2d(0., 0.), gp_Dir2d(1., 1.));
Bnd_Box2d aBox;
double aTol = 0.;
BndLib::Add(aLine, 0., 10., aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
// Line in direction (1,1) normalized, so at t=10:
// position = (10/sqrt(2), 10/sqrt(2))
double aExpected = 10. / std::sqrt(2.);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, aExpected, Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, aExpected, Precision::Confusion());
}
TEST(BndLibTest, Circle2d_Full)
{
gp_Circ2d aCirc(gp_Ax2d(gp_Pnt2d(0., 0.), gp_Dir2d(1., 0.)), 5.);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib::Add(aCirc, aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
TEST(BndLibTest, Ellipse2d_Full)
{
gp_Elips2d aElips(gp_Ax2d(gp_Pnt2d(0., 0.), gp_Dir2d(1., 0.)), 8., 4.);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib::Add(aElips, aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, -8., Precision::Confusion());
EXPECT_NEAR(aXmax, 8., Precision::Confusion());
EXPECT_NEAR(aYmin, -4., Precision::Confusion());
EXPECT_NEAR(aYmax, 4., Precision::Confusion());
}
TEST(BndLibTest, Hyperbola2d_SimpleArc)
{
gp_Hypr2d aHypr(gp_Ax2d(gp_Pnt2d(0., 0.), gp_Dir2d(1., 0.)), 3., 2.);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib::Add(aHypr, -1., 1., aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, 3., Precision::Confusion());
EXPECT_NEAR(aXmax, 3. * std::cosh(1.), Precision::Confusion());
EXPECT_NEAR(aYmin, 2. * std::sinh(-1.), Precision::Confusion());
EXPECT_NEAR(aYmax, 2. * std::sinh(1.), Precision::Confusion());
}
TEST(BndLibTest, Parabola2d_FiniteArc)
{
gp_Parab2d aParab(gp_Ax2d(gp_Pnt2d(0., 0.), gp_Dir2d(1., 0.)), 2.);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib::Add(aParab, -4., 4., aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 2., Precision::Confusion());
EXPECT_NEAR(aYmin, -4., Precision::Confusion());
EXPECT_NEAR(aYmax, 4., Precision::Confusion());
}
//=================================================================================================
// Tolerance Tests
//=================================================================================================
TEST(BndLibTest, Circle_WithTolerance)
{
gp_Circ aCirc(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
Bnd_Box aBox;
double aTol = 0.5;
BndLib::Add(aCirc, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// Box should be enlarged by tolerance
EXPECT_NEAR(aXmin, -5.5, Precision::Confusion());
EXPECT_NEAR(aXmax, 5.5, Precision::Confusion());
EXPECT_NEAR(aYmin, -5.5, Precision::Confusion());
EXPECT_NEAR(aYmax, 5.5, Precision::Confusion());
EXPECT_NEAR(aZmin, -0.5, Precision::Confusion());
EXPECT_NEAR(aZmax, 0.5, Precision::Confusion());
}
TEST(BndLibTest, Sphere_WithTolerance)
{
gp_Sphere aSphere(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 3.);
Bnd_Box aBox;
double aTol = 1.;
BndLib::Add(aSphere, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -4., Precision::Confusion());
EXPECT_NEAR(aXmax, 4., Precision::Confusion());
EXPECT_NEAR(aYmin, -4., Precision::Confusion());
EXPECT_NEAR(aYmax, 4., Precision::Confusion());
EXPECT_NEAR(aZmin, -4., Precision::Confusion());
EXPECT_NEAR(aZmax, 4., Precision::Confusion());
}
//=================================================================================================
// BndLib_Add3dCurve Tests
//=================================================================================================
TEST(BndLib_Add3dCurveTest, Circle_Full)
{
// Create a 3D circle using Geom_Circle
occ::handle<Geom_Circle> aCircle =
new Geom_Circle(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
GeomAdaptor_Curve aCurve(aCircle);
Bnd_Box aBox;
double aTol = 0.;
BndLib_Add3dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 0., Precision::Confusion());
}
TEST(BndLib_Add3dCurveTest, Circle_Arc)
{
// Circle arc from 0 to PI/2
occ::handle<Geom_Circle> aCircle =
new Geom_Circle(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
GeomAdaptor_Curve aCurve(aCircle);
Bnd_Box aBox;
double aTol = 0.;
BndLib_Add3dCurve::Add(aCurve, 0., M_PI / 2., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
TEST(BndLib_Add3dCurveTest, Ellipse_Full)
{
// Ellipse with major radius 10, minor radius 5
occ::handle<Geom_Ellipse> anEllipse =
new Geom_Ellipse(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 10., 5.);
GeomAdaptor_Curve aCurve(anEllipse);
Bnd_Box aBox;
double aTol = 0.;
BndLib_Add3dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -10., Precision::Confusion());
EXPECT_NEAR(aXmax, 10., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
TEST(BndLib_Add3dCurveTest, Line_Segment)
{
// Line segment from (0,0,0) to (10,0,0)
occ::handle<Geom_Line> aLine = new Geom_Line(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.));
occ::handle<Geom_TrimmedCurve> aTrimmed = new Geom_TrimmedCurve(aLine, 0., 10.);
GeomAdaptor_Curve aCurve(aTrimmed);
Bnd_Box aBox;
double aTol = 0.;
BndLib_Add3dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 10., Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 0., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 0., Precision::Confusion());
}
TEST(BndLib_Add3dCurveTest, BezierCurve)
{
// Create a cubic Bezier curve
NCollection_Array1<gp_Pnt> aPoles(1, 4);
aPoles(1) = gp_Pnt(0., 0., 0.);
aPoles(2) = gp_Pnt(1., 2., 0.);
aPoles(3) = gp_Pnt(3., 2., 0.);
aPoles(4) = gp_Pnt(4., 0., 0.);
occ::handle<Geom_BezierCurve> aBezier = new Geom_BezierCurve(aPoles);
GeomAdaptor_Curve aCurve(aBezier);
Bnd_Box aBox;
double aTol = 0.;
BndLib_Add3dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// Box should contain curve endpoints and the actual curve
// Note: The curve doesn't necessarily pass through control points,
// so the box may be smaller than the convex hull of control points
EXPECT_FALSE(aBox.IsVoid()) << "Box should not be void";
EXPECT_LE(aXmin, 0.);
EXPECT_GE(aXmax, 4.);
EXPECT_LE(aYmin, 0.);
// Y max is at curve's maximum, not necessarily at control point Y
EXPECT_GT(aYmax, 1.) << "Y max should be > 1 (curve rises above Y=0)";
}
TEST(BndLib_Add3dCurveTest, BSplineCurve)
{
// Create a simple B-spline curve
NCollection_Array1<gp_Pnt> aPoles(1, 4);
aPoles(1) = gp_Pnt(0., 0., 0.);
aPoles(2) = gp_Pnt(1., 1., 0.);
aPoles(3) = gp_Pnt(2., 1., 0.);
aPoles(4) = gp_Pnt(3., 0., 0.);
NCollection_Array1<double> aKnots(1, 2);
aKnots(1) = 0.;
aKnots(2) = 1.;
NCollection_Array1<int> aMults(1, 2);
aMults(1) = 4;
aMults(2) = 4;
occ::handle<Geom_BSplineCurve> aBSpline = new Geom_BSplineCurve(aPoles, aKnots, aMults, 3);
GeomAdaptor_Curve aCurve(aBSpline);
Bnd_Box aBox;
double aTol = 0.;
BndLib_Add3dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_FALSE(aBox.IsVoid()) << "Box should not be void";
EXPECT_LE(aXmin, 0.);
EXPECT_GE(aXmax, 3.);
}
TEST(BndLib_Add3dCurveTest, AddOptimal_Circle)
{
// Test AddOptimal method with a circle
occ::handle<Geom_Circle> aCircle =
new Geom_Circle(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
GeomAdaptor_Curve aCurve(aCircle);
Bnd_Box aBox;
double aTol = 0.;
BndLib_Add3dCurve::AddOptimal(aCurve, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
TEST(BndLib_Add3dCurveTest, Circle_Rotated)
{
// Circle in YZ plane (axis along X)
occ::handle<Geom_Circle> aCircle =
new Geom_Circle(gp_Ax2(gp_Pnt(0., 0., 0.), gp_Dir(1., 0., 0.)), 4.);
GeomAdaptor_Curve aCurve(aCircle);
Bnd_Box aBox;
double aTol = 0.;
BndLib_Add3dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 0., Precision::Confusion());
EXPECT_NEAR(aYmin, -4., Precision::Confusion());
EXPECT_NEAR(aYmax, 4., Precision::Confusion());
EXPECT_NEAR(aZmin, -4., Precision::Confusion());
EXPECT_NEAR(aZmax, 4., Precision::Confusion());
}
//=================================================================================================
// BndLib_Add2dCurve Tests
//=================================================================================================
TEST(BndLib_Add2dCurveTest, Circle_Full)
{
// Create a 2D circle using Geom2d_Circle
occ::handle<Geom2d_Circle> aCircle =
new Geom2d_Circle(gp_Ax2d(gp_Pnt2d(0., 0.), gp_Dir2d(1., 0.)), 5.);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib_Add2dCurve::Add(aCircle, aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
TEST(BndLib_Add2dCurveTest, Circle_Arc)
{
// Circle arc from 0 to PI/2
occ::handle<Geom2d_Circle> aCircle =
new Geom2d_Circle(gp_Ax2d(gp_Pnt2d(0., 0.), gp_Dir2d(1., 0.)), 5.);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib_Add2dCurve::Add(aCircle, 0., M_PI / 2., aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
}
TEST(BndLib_Add2dCurveTest, Ellipse_Full)
{
// Ellipse with major radius 8, minor radius 4
occ::handle<Geom2d_Ellipse> anEllipse =
new Geom2d_Ellipse(gp_Ax2d(gp_Pnt2d(0., 0.), gp_Dir2d(1., 0.)), 8., 4.);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib_Add2dCurve::Add(anEllipse, aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, -8., Precision::Confusion());
EXPECT_NEAR(aXmax, 8., Precision::Confusion());
EXPECT_NEAR(aYmin, -4., Precision::Confusion());
EXPECT_NEAR(aYmax, 4., Precision::Confusion());
}
TEST(BndLib_Add2dCurveTest, Line_Segment)
{
// Line segment from (0,0) to (10,5)
occ::handle<Geom2d_Line> aLine = new Geom2d_Line(gp_Pnt2d(0., 0.), gp_Dir2d(2., 1.));
occ::handle<Geom2d_TrimmedCurve> aTrimmed =
new Geom2d_TrimmedCurve(aLine, 0., std::sqrt(125.)); // length = sqrt(100+25)
Geom2dAdaptor_Curve aCurve(aTrimmed);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib_Add2dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, 0., Precision::Confusion());
EXPECT_NEAR(aXmax, 10., 0.01);
EXPECT_NEAR(aYmin, 0., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., 0.01);
}
TEST(BndLib_Add2dCurveTest, BezierCurve)
{
// Create a cubic Bezier curve in 2D
NCollection_Array1<gp_Pnt2d> aPoles(1, 4);
aPoles(1) = gp_Pnt2d(0., 0.);
aPoles(2) = gp_Pnt2d(1., 3.);
aPoles(3) = gp_Pnt2d(3., 3.);
aPoles(4) = gp_Pnt2d(4., 0.);
occ::handle<Geom2d_BezierCurve> aBezier = new Geom2d_BezierCurve(aPoles);
Geom2dAdaptor_Curve aCurve(aBezier);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib_Add2dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
// Box should contain the actual curve extents (not necessarily the control polygon).
// GeomBndLib computes a tight box based on the actual curve, not the convex hull of poles.
EXPECT_LE(aXmin, 0.);
EXPECT_GE(aXmax, 4.);
EXPECT_LE(aYmin, 0.);
EXPECT_GT(aYmax, 1.) << "Y max should be > 1 (curve rises above Y=0)";
}
TEST(BndLib_Add2dCurveTest, AddOptimal_Ellipse)
{
// Test AddOptimal method with an ellipse
occ::handle<Geom2d_Ellipse> anEllipse =
new Geom2d_Ellipse(gp_Ax2d(gp_Pnt2d(0., 0.), gp_Dir2d(1., 0.)), 6., 3.);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib_Add2dCurve::AddOptimal(anEllipse, 0., 2. * M_PI, aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, -6., Precision::Confusion());
EXPECT_NEAR(aXmax, 6., Precision::Confusion());
EXPECT_NEAR(aYmin, -3., Precision::Confusion());
EXPECT_NEAR(aYmax, 3., Precision::Confusion());
}
TEST(BndLib_Add2dCurveTest, Adaptor_Circle)
{
// Test using Adaptor interface
occ::handle<Geom2d_Circle> aCircle =
new Geom2d_Circle(gp_Ax2d(gp_Pnt2d(5., 5.), gp_Dir2d(1., 0.)), 3.);
Geom2dAdaptor_Curve aCurve(aCircle);
Bnd_Box2d aBox;
double aTol = 0.;
BndLib_Add2dCurve::Add(aCurve, aTol, aBox);
double aXmin, aYmin, aXmax, aYmax;
aBox.Get(aXmin, aYmin, aXmax, aYmax);
EXPECT_NEAR(aXmin, 2., Precision::Confusion());
EXPECT_NEAR(aXmax, 8., Precision::Confusion());
EXPECT_NEAR(aYmin, 2., Precision::Confusion());
EXPECT_NEAR(aYmax, 8., Precision::Confusion());
}
//=================================================================================================
// BndLib_AddSurface Tests
//=================================================================================================
TEST(BndLib_AddSurfaceTest, Plane)
{
// Create a plane and add a finite patch
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)));
GeomAdaptor_Surface aSurf(aPlane);
Bnd_Box aBox;
double aTol = 0.;
// Add a 10x10 patch centered at origin
BndLib_AddSurface::Add(aSurf, -5., 5., -5., 5., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 0., Precision::Confusion());
}
TEST(BndLib_AddSurfaceTest, Cylinder_Full)
{
// Create a cylindrical surface
occ::handle<Geom_CylindricalSurface> aCyl =
new Geom_CylindricalSurface(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
GeomAdaptor_Surface aSurf(aCyl);
Bnd_Box aBox;
double aTol = 0.;
// Full U range, V from 0 to 10
BndLib_AddSurface::Add(aSurf, 0., 2. * M_PI, 0., 10., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 10., Precision::Confusion());
}
TEST(BndLib_AddSurfaceTest, Sphere_Full)
{
// Create a spherical surface
occ::handle<Geom_SphericalSurface> aSphere =
new Geom_SphericalSurface(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 7.);
GeomAdaptor_Surface aSurf(aSphere);
Bnd_Box aBox;
double aTol = 0.;
BndLib_AddSurface::Add(aSurf, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -7., Precision::Confusion());
EXPECT_NEAR(aXmax, 7., Precision::Confusion());
EXPECT_NEAR(aYmin, -7., Precision::Confusion());
EXPECT_NEAR(aYmax, 7., Precision::Confusion());
EXPECT_NEAR(aZmin, -7., Precision::Confusion());
EXPECT_NEAR(aZmax, 7., Precision::Confusion());
}
TEST(BndLib_AddSurfaceTest, Sphere_Patch)
{
// Create a spherical surface patch (upper hemisphere)
occ::handle<Geom_SphericalSurface> aSphere =
new Geom_SphericalSurface(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 5.);
GeomAdaptor_Surface aSurf(aSphere);
Bnd_Box aBox;
double aTol = 0.;
// Full U, V from 0 to PI/2 (upper hemisphere)
BndLib_AddSurface::Add(aSurf, 0., 2. * M_PI, 0., M_PI / 2., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -5., Precision::Confusion());
EXPECT_NEAR(aXmax, 5., Precision::Confusion());
EXPECT_NEAR(aYmin, -5., Precision::Confusion());
EXPECT_NEAR(aYmax, 5., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 5., Precision::Confusion());
}
TEST(BndLib_AddSurfaceTest, Cone_Full)
{
// Create a conical surface
occ::handle<Geom_ConicalSurface> aCone =
new Geom_ConicalSurface(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), M_PI / 4., 2.);
GeomAdaptor_Surface aSurf(aCone);
Bnd_Box aBox;
double aTol = 0.;
// Full U range, V from 0 to 5
BndLib_AddSurface::Add(aSurf, 0., 2. * M_PI, 0., 5., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// V=5: radius = 2 + 5*sin(PI/4), Z = 5*cos(PI/4)
double aMaxRadius = 2. + 5. * std::sin(M_PI / 4.);
double aMaxZ = 5. * std::cos(M_PI / 4.);
EXPECT_NEAR(aXmax, aMaxRadius, Precision::Confusion());
EXPECT_NEAR(aYmax, aMaxRadius, Precision::Confusion());
EXPECT_NEAR(aZmax, aMaxZ, Precision::Confusion());
}
TEST(BndLib_AddSurfaceTest, Torus_Full)
{
// Create a toroidal surface
occ::handle<Geom_ToroidalSurface> aTorus =
new Geom_ToroidalSurface(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 10., 3.);
GeomAdaptor_Surface aSurf(aTorus);
Bnd_Box aBox;
double aTol = 0.;
BndLib_AddSurface::Add(aSurf, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// BndLib_AddSurface uses conservative bounding for torus
// The exact bounds are +-13 for X,Y and +-3 for Z
// The algorithm may produce slightly larger bounds
EXPECT_LE(aXmin, -13. + Precision::Confusion());
EXPECT_GE(aXmax, 13. - Precision::Confusion());
EXPECT_LE(aYmin, -13. + Precision::Confusion());
EXPECT_GE(aYmax, 13. - Precision::Confusion());
EXPECT_NEAR(aZmin, -3., Precision::Confusion());
EXPECT_NEAR(aZmax, 3., Precision::Confusion());
}
TEST(BndLib_AddSurfaceTest, BezierSurface)
{
// Create a simple Bezier surface (2x2 control points)
NCollection_Array2<gp_Pnt> aPoles(1, 2, 1, 2);
aPoles(1, 1) = gp_Pnt(0., 0., 0.);
aPoles(1, 2) = gp_Pnt(0., 10., 2.);
aPoles(2, 1) = gp_Pnt(10., 0., 2.);
aPoles(2, 2) = gp_Pnt(10., 10., 0.);
occ::handle<Geom_BezierSurface> aBezier = new Geom_BezierSurface(aPoles);
GeomAdaptor_Surface aSurf(aBezier);
Bnd_Box aBox;
double aTol = 0.;
BndLib_AddSurface::Add(aSurf, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// Box should contain all control points
EXPECT_LE(aXmin, 0.);
EXPECT_GE(aXmax, 10.);
EXPECT_LE(aYmin, 0.);
EXPECT_GE(aYmax, 10.);
EXPECT_LE(aZmin, 0.);
EXPECT_GE(aZmax, 2.);
}
TEST(BndLib_AddSurfaceTest, AddOptimal_Sphere)
{
// Test AddOptimal method with a sphere
occ::handle<Geom_SphericalSurface> aSphere =
new Geom_SphericalSurface(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)), 4.);
GeomAdaptor_Surface aSurf(aSphere);
Bnd_Box aBox;
double aTol = 0.;
BndLib_AddSurface::AddOptimal(aSurf, aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, -4., Precision::Confusion());
EXPECT_NEAR(aXmax, 4., Precision::Confusion());
EXPECT_NEAR(aYmin, -4., Precision::Confusion());
EXPECT_NEAR(aYmax, 4., Precision::Confusion());
EXPECT_NEAR(aZmin, -4., Precision::Confusion());
EXPECT_NEAR(aZmax, 4., Precision::Confusion());
}
TEST(BndLib_AddSurfaceTest, Cylinder_Translated)
{
// Create a cylindrical surface centered at (5, 5, 0)
occ::handle<Geom_CylindricalSurface> aCyl =
new Geom_CylindricalSurface(gp_Ax3(gp_Pnt(5., 5., 0.), gp_Dir(0., 0., 1.)), 3.);
GeomAdaptor_Surface aSurf(aCyl);
Bnd_Box aBox;
double aTol = 0.;
BndLib_AddSurface::Add(aSurf, 0., 2. * M_PI, 0., 10., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
EXPECT_NEAR(aXmin, 2., Precision::Confusion());
EXPECT_NEAR(aXmax, 8., Precision::Confusion());
EXPECT_NEAR(aYmin, 2., Precision::Confusion());
EXPECT_NEAR(aYmax, 8., Precision::Confusion());
EXPECT_NEAR(aZmin, 0., Precision::Confusion());
EXPECT_NEAR(aZmax, 10., Precision::Confusion());
}
TEST(BndLib_AddSurfaceTest, Plane_Tilted)
{
// Create a tilted plane (normal at 45 degrees)
// gp_Dir automatically normalizes the input vector
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 1., 1.)));
GeomAdaptor_Surface aSurf(aPlane);
Bnd_Box aBox;
double aTol = 0.;
// Add a unit square patch
BndLib_AddSurface::Add(aSurf, 0., 1., 0., 1., aTol, aBox);
EXPECT_FALSE(aBox.IsVoid()) << "Box should not be void for tilted plane";
}
// Test for edge case with large parameter values and small step sizes.
// This verifies that the algorithm correctly samples different points
// even when parameter values are large relative to the step size,
// which could cause floating-point precision issues if accumulation
// (U += dU) is used instead of multiplication (U = UMin + dU * i).
TEST(BndLib_AddSurfaceTest, LargeParameters_PrecisionTest)
{
// Create a Bezier surface - uses the default sampling path
NCollection_Array2<gp_Pnt> aPoles(1, 3, 1, 3);
// Create a simple surface with known extent
for (int i = 1; i <= 3; i++)
{
for (int j = 1; j <= 3; j++)
{
aPoles(i, j) = gp_Pnt((i - 1) * 10., (j - 1) * 10., 0.);
}
}
occ::handle<Geom_BezierSurface> aBezier = new Geom_BezierSurface(aPoles);
GeomAdaptor_Surface aSurf(aBezier);
Bnd_Box aBox;
double aTol = 0.;
// Use large parameter offset with small parameter range
// This simulates the edge case where UMin/UMax are large but (UMax-UMin) is small
// The actual Bezier is defined on [0,1] x [0,1], but we test a sub-range
// to ensure the sampling algorithm works correctly
BndLib_AddSurface::Add(aSurf, 0., 1., 0., 1., aTol, aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// The surface spans [0,20] x [0,20] x [0,0]
// Bounding box should contain all sample points
EXPECT_LE(aXmin, 1.) << "XMin should capture sampled points";
EXPECT_GE(aXmax, 19.) << "XMax should capture sampled points";
EXPECT_LE(aYmin, 1.) << "YMin should capture sampled points";
EXPECT_GE(aYmax, 19.) << "YMax should capture sampled points";
}
// Test with translated surface where parameters are offset
// This is a more realistic test of the large parameter scenario
TEST(BndLib_AddSurfaceTest, OffsetSurface_ParameterPrecision)
{
// Create a simple plane
occ::handle<Geom_Plane> aPlane = new Geom_Plane(gp_Ax3(gp_Pnt(0., 0., 0.), gp_Dir(0., 0., 1.)));
GeomAdaptor_Surface aSurf(aPlane);
Bnd_Box aBox;
double aTol = 0.;
// Test with very large parameter values but small range
// UMin = 1e10, UMax = 1e10 + 100, so dU ~ 100/(Nu-1) ~ small relative to 1e10
const double aLargeOffset = 1.0e10;
const double aRange = 100.;
BndLib_AddSurface::Add(aSurf,
aLargeOffset,
aLargeOffset + aRange,
aLargeOffset,
aLargeOffset + aRange,
aTol,
aBox);
double aXmin, aYmin, aZmin, aXmax, aYmax, aZmax;
aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax);
// For a plane at z=0 with parameters as X,Y coordinates:
// The box should span approximately [1e10, 1e10+100] in both X and Y
// Allow some tolerance due to floating-point representation
const double aTolerance = 1.0; // 1 unit tolerance for large values
EXPECT_NEAR(aXmin, aLargeOffset, aTolerance) << "XMin should be near the large offset value";
EXPECT_NEAR(aXmax, aLargeOffset + aRange, aTolerance) << "XMax should capture the full range";
EXPECT_NEAR(aYmin, aLargeOffset, aTolerance) << "YMin should be near the large offset value";
EXPECT_NEAR(aYmax, aLargeOffset + aRange, aTolerance) << "YMax should capture the full range";
// Most importantly: verify the range is captured (not collapsed due to precision loss)
const double aComputedRangeX = aXmax - aXmin;
const double aComputedRangeY = aYmax - aYmin;
EXPECT_GT(aComputedRangeX, aRange * 0.9)
<< "X range should not collapse due to floating-point precision issues";
EXPECT_GT(aComputedRangeY, aRange * 0.9)
<< "Y range should not collapse due to floating-point precision issues";
}