From: Pasukhin Dmitry Date: Sat, 22 Nov 2025 11:16:25 +0000 (+0000) Subject: Modeling Data - Add GeomHash and Geom2dHash packages (#845) X-Git-Url: http://git.dev.opencascade.org/gitweb/?a=commitdiff_plain;h=052c37669bee14b7f4da33d27ccb9242860ea79d;p=occt.git Modeling Data - Add GeomHash and Geom2dHash packages (#845) - Implementation of hashers for analytic curves (Line, Circle, Ellipse, Hyperbola, Parabola) and freeform curves (Bezier, BSpline, Trimmed, Offset) in both 2D and 3D - Implementation of hashers for surfaces including elementary surfaces (Plane, Cylinder, Cone, Sphere, Torus) and derived surfaces (Revolution, LinearExtrusion, RectangularTrimmed, Offset) - Comprehensive test coverage for all hasher implementations --- diff --git a/src/ModelingData/TKG2d/GTests/FILES.cmake b/src/ModelingData/TKG2d/GTests/FILES.cmake index db61773428..42478599b5 100644 --- a/src/ModelingData/TKG2d/GTests/FILES.cmake +++ b/src/ModelingData/TKG2d/GTests/FILES.cmake @@ -8,4 +8,5 @@ set(OCCT_TKG2d_GTests_FILES Geom2dAPI_InterCurveCurve_Test.cxx Geom2dGcc_Circ2d2TanOn_Test.cxx Geom2dGcc_Circ2d2TanRad_Test.cxx + Geom2dHash_CurveHasher_Test.cxx ) diff --git a/src/ModelingData/TKG2d/GTests/Geom2dHash_CurveHasher_Test.cxx b/src/ModelingData/TKG2d/GTests/Geom2dHash_CurveHasher_Test.cxx new file mode 100644 index 0000000000..b858b17a19 --- /dev/null +++ b/src/ModelingData/TKG2d/GTests/Geom2dHash_CurveHasher_Test.cxx @@ -0,0 +1,654 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Geom2dHash_CurveHasherTest : public ::testing::Test +{ +protected: + Geom2dHash_CurveHasher myHasher; +}; + +// ============================================================================ +// Line Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Line_CopiedLines_SameHash) +{ + gp_Pnt2d aLoc(1.0, 2.0); + gp_Dir2d aDir(1.0, 0.0); + Handle(Geom2d_Line) aLine1 = new Geom2d_Line(aLoc, aDir); + Handle(Geom2d_Line) aLine2 = Handle(Geom2d_Line)::DownCast(aLine1->Copy()); + + EXPECT_EQ(myHasher(aLine1), myHasher(aLine2)); + EXPECT_TRUE(myHasher(aLine1, aLine2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Line_DifferentLines_DifferentHash) +{ + Handle(Geom2d_Line) aLine1 = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_Line) aLine2 = new Geom2d_Line(gp_Pnt2d(1.0, 0.0), gp_Dir2d(1.0, 0.0)); + + EXPECT_NE(myHasher(aLine1), myHasher(aLine2)); + EXPECT_FALSE(myHasher(aLine1, aLine2)); +} + +// ============================================================================ +// Circle Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Circle_CopiedCircles_SameHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Circle) aCircle1 = new Geom2d_Circle(anAxis, 5.0); + Handle(Geom2d_Circle) aCircle2 = Handle(Geom2d_Circle)::DownCast(aCircle1->Copy()); + + EXPECT_EQ(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_TRUE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Circle_DifferentRadius_DifferentHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Circle) aCircle1 = new Geom2d_Circle(anAxis, 5.0); + Handle(Geom2d_Circle) aCircle2 = new Geom2d_Circle(anAxis, 10.0); + + EXPECT_NE(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_FALSE(myHasher(aCircle1, aCircle2)); +} + +// ============================================================================ +// Ellipse Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Ellipse_CopiedEllipses_SameHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Ellipse) anEllipse1 = new Geom2d_Ellipse(anAxis, 10.0, 5.0); + Handle(Geom2d_Ellipse) anEllipse2 = Handle(Geom2d_Ellipse)::DownCast(anEllipse1->Copy()); + + EXPECT_EQ(myHasher(anEllipse1), myHasher(anEllipse2)); + EXPECT_TRUE(myHasher(anEllipse1, anEllipse2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Ellipse_DifferentRadii_DifferentHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Ellipse) anEllipse1 = new Geom2d_Ellipse(anAxis, 10.0, 5.0); + Handle(Geom2d_Ellipse) anEllipse2 = new Geom2d_Ellipse(anAxis, 10.0, 7.0); + + EXPECT_NE(myHasher(anEllipse1), myHasher(anEllipse2)); + EXPECT_FALSE(myHasher(anEllipse1, anEllipse2)); +} + +// ============================================================================ +// Hyperbola Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Hyperbola_CopiedHyperbolas_SameHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Hyperbola) aHyp1 = new Geom2d_Hyperbola(anAxis, 5.0, 3.0); + Handle(Geom2d_Hyperbola) aHyp2 = Handle(Geom2d_Hyperbola)::DownCast(aHyp1->Copy()); + + EXPECT_EQ(myHasher(aHyp1), myHasher(aHyp2)); + EXPECT_TRUE(myHasher(aHyp1, aHyp2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Hyperbola_DifferentRadii_DifferentHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Hyperbola) aHyp1 = new Geom2d_Hyperbola(anAxis, 5.0, 3.0); + Handle(Geom2d_Hyperbola) aHyp2 = new Geom2d_Hyperbola(anAxis, 5.0, 4.0); + + EXPECT_NE(myHasher(aHyp1), myHasher(aHyp2)); + EXPECT_FALSE(myHasher(aHyp1, aHyp2)); +} + +// ============================================================================ +// Parabola Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Parabola_CopiedParabolas_SameHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Parabola) aPar1 = new Geom2d_Parabola(anAxis, 2.0); + Handle(Geom2d_Parabola) aPar2 = Handle(Geom2d_Parabola)::DownCast(aPar1->Copy()); + + EXPECT_EQ(myHasher(aPar1), myHasher(aPar2)); + EXPECT_TRUE(myHasher(aPar1, aPar2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Parabola_DifferentFocal_DifferentHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Parabola) aPar1 = new Geom2d_Parabola(anAxis, 2.0); + Handle(Geom2d_Parabola) aPar2 = new Geom2d_Parabola(anAxis, 3.0); + + EXPECT_NE(myHasher(aPar1), myHasher(aPar2)); + EXPECT_FALSE(myHasher(aPar1, aPar2)); +} + +// ============================================================================ +// BezierCurve Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, BezierCurve_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt2d aPoles(1, 4); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 2.0); + aPoles(3) = gp_Pnt2d(3.0, 2.0); + aPoles(4) = gp_Pnt2d(4.0, 0.0); + + Handle(Geom2d_BezierCurve) aCurve1 = new Geom2d_BezierCurve(aPoles); + Handle(Geom2d_BezierCurve) aCurve2 = Handle(Geom2d_BezierCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, BezierCurve_DifferentPoles_DifferentComparison) +{ + TColgp_Array1OfPnt2d aPoles1(1, 3); + aPoles1(1) = gp_Pnt2d(0.0, 0.0); + aPoles1(2) = gp_Pnt2d(1.0, 2.0); + aPoles1(3) = gp_Pnt2d(2.0, 0.0); + + TColgp_Array1OfPnt2d aPoles2(1, 3); + aPoles2(1) = gp_Pnt2d(0.0, 0.0); + aPoles2(2) = gp_Pnt2d(1.0, 3.0); // Different + aPoles2(3) = gp_Pnt2d(2.0, 0.0); + + Handle(Geom2d_BezierCurve) aCurve1 = new Geom2d_BezierCurve(aPoles1); + Handle(Geom2d_BezierCurve) aCurve2 = new Geom2d_BezierCurve(aPoles2); + + // Hash may be same (metadata only), but comparison should differ + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// BSplineCurve Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, BSplineCurve_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt2d aPoles(1, 4); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 1.0); + aPoles(3) = gp_Pnt2d(2.0, 1.0); + aPoles(4) = gp_Pnt2d(3.0, 0.0); + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 4; + aMults(2) = 4; + + Handle(Geom2d_BSplineCurve) aCurve1 = new Geom2d_BSplineCurve(aPoles, aKnots, aMults, 3); + Handle(Geom2d_BSplineCurve) aCurve2 = Handle(Geom2d_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// TrimmedCurve Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, TrimmedCurve_CopiedCurves_SameHash) +{ + Handle(Geom2d_Line) aLine = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_TrimmedCurve) aTrimmed1 = new Geom2d_TrimmedCurve(aLine, 0.0, 10.0); + Handle(Geom2d_TrimmedCurve) aTrimmed2 = Handle(Geom2d_TrimmedCurve)::DownCast(aTrimmed1->Copy()); + + EXPECT_EQ(myHasher(aTrimmed1), myHasher(aTrimmed2)); + EXPECT_TRUE(myHasher(aTrimmed1, aTrimmed2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, TrimmedCurve_DifferentBounds_DifferentHash) +{ + Handle(Geom2d_Line) aLine = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_TrimmedCurve) aTrimmed1 = new Geom2d_TrimmedCurve(aLine, 0.0, 10.0); + Handle(Geom2d_TrimmedCurve) aTrimmed2 = new Geom2d_TrimmedCurve(aLine, 0.0, 20.0); + + EXPECT_NE(myHasher(aTrimmed1), myHasher(aTrimmed2)); + EXPECT_FALSE(myHasher(aTrimmed1, aTrimmed2)); +} + +// ============================================================================ +// OffsetCurve Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, OffsetCurve_CopiedCurves_SameHash) +{ + Handle(Geom2d_Line) aLine = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_OffsetCurve) anOffset1 = new Geom2d_OffsetCurve(aLine, 5.0); + Handle(Geom2d_OffsetCurve) anOffset2 = Handle(Geom2d_OffsetCurve)::DownCast(anOffset1->Copy()); + + EXPECT_EQ(myHasher(anOffset1), myHasher(anOffset2)); + EXPECT_TRUE(myHasher(anOffset1, anOffset2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, OffsetCurve_DifferentOffset_DifferentHash) +{ + Handle(Geom2d_Line) aLine = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_OffsetCurve) anOffset1 = new Geom2d_OffsetCurve(aLine, 5.0); + Handle(Geom2d_OffsetCurve) anOffset2 = new Geom2d_OffsetCurve(aLine, 10.0); + + EXPECT_NE(myHasher(anOffset1), myHasher(anOffset2)); + EXPECT_FALSE(myHasher(anOffset1, anOffset2)); +} + +// ============================================================================ +// Cross-type Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, DifferentTypes_DifferentComparison) +{ + Handle(Geom2d_Line) aLine = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Circle) aCircle = new Geom2d_Circle(anAxis, 5.0); + + EXPECT_FALSE(myHasher(aLine, aCircle)); +} + +TEST_F(Geom2dHash_CurveHasherTest, NullCurves_HandledCorrectly) +{ + Handle(Geom2d_Curve) aNullCurve; + Handle(Geom2d_Line) aLine = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + + EXPECT_EQ(myHasher(aNullCurve), 0u); + EXPECT_TRUE(myHasher(aNullCurve, aNullCurve)); + EXPECT_FALSE(myHasher(aLine, aNullCurve)); +} + +TEST_F(Geom2dHash_CurveHasherTest, SameObject_Equal) +{ + Handle(Geom2d_Line) aLine = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + EXPECT_TRUE(myHasher(aLine, aLine)); +} + +// ============================================================================ +// Weighted BSpline Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, BSplineCurve_Weighted_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt2d aPoles(1, 4); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 1.0); + aPoles(3) = gp_Pnt2d(2.0, 1.0); + aPoles(4) = gp_Pnt2d(3.0, 0.0); + + TColStd_Array1OfReal aWeights(1, 4); + aWeights(1) = 1.0; + aWeights(2) = 2.0; + aWeights(3) = 2.0; + aWeights(4) = 1.0; + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 4; + aMults(2) = 4; + + Handle(Geom2d_BSplineCurve) aCurve1 = + new Geom2d_BSplineCurve(aPoles, aWeights, aKnots, aMults, 3); + Handle(Geom2d_BSplineCurve) aCurve2 = Handle(Geom2d_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, BSplineCurve_DifferentWeights_DifferentComparison) +{ + TColgp_Array1OfPnt2d aPoles(1, 4); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 1.0); + aPoles(3) = gp_Pnt2d(2.0, 1.0); + aPoles(4) = gp_Pnt2d(3.0, 0.0); + + TColStd_Array1OfReal aWeights1(1, 4); + aWeights1(1) = 1.0; + aWeights1(2) = 2.0; + aWeights1(3) = 2.0; + aWeights1(4) = 1.0; + + TColStd_Array1OfReal aWeights2(1, 4); + aWeights2(1) = 1.0; + aWeights2(2) = 3.0; // Different + aWeights2(3) = 2.0; + aWeights2(4) = 1.0; + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 4; + aMults(2) = 4; + + Handle(Geom2d_BSplineCurve) aCurve1 = + new Geom2d_BSplineCurve(aPoles, aWeights1, aKnots, aMults, 3); + Handle(Geom2d_BSplineCurve) aCurve2 = + new Geom2d_BSplineCurve(aPoles, aWeights2, aKnots, aMults, 3); + + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Weighted Bezier Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, BezierCurve_Weighted_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt2d aPoles(1, 3); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 2.0); + aPoles(3) = gp_Pnt2d(2.0, 0.0); + + TColStd_Array1OfReal aWeights(1, 3); + aWeights(1) = 1.0; + aWeights(2) = 2.0; + aWeights(3) = 1.0; + + Handle(Geom2d_BezierCurve) aCurve1 = new Geom2d_BezierCurve(aPoles, aWeights); + Handle(Geom2d_BezierCurve) aCurve2 = Handle(Geom2d_BezierCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Axis Orientation Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Circle_DifferentAxisOrientation_DifferentHash) +{ + gp_Ax22d anAxis1(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + gp_Ax22d anAxis2(gp_Pnt2d(0.0, 0.0), gp_Dir2d(0.0, 1.0), gp_Dir2d(-1.0, 0.0)); + Handle(Geom2d_Circle) aCircle1 = new Geom2d_Circle(anAxis1, 5.0); + Handle(Geom2d_Circle) aCircle2 = new Geom2d_Circle(anAxis2, 5.0); + + EXPECT_NE(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_FALSE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Line_DifferentDirection_DifferentHash) +{ + Handle(Geom2d_Line) aLine1 = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_Line) aLine2 = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(0.0, 1.0)); + + EXPECT_NE(myHasher(aLine1), myHasher(aLine2)); + EXPECT_FALSE(myHasher(aLine1, aLine2)); +} + +// ============================================================================ +// BSpline with Different Knots Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, BSplineCurve_DifferentKnots_DifferentComparison) +{ + TColgp_Array1OfPnt2d aPoles(1, 4); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 1.0); + aPoles(3) = gp_Pnt2d(2.0, 1.0); + aPoles(4) = gp_Pnt2d(3.0, 0.0); + + TColStd_Array1OfReal aKnots1(1, 2); + aKnots1(1) = 0.0; + aKnots1(2) = 1.0; + + TColStd_Array1OfReal aKnots2(1, 2); + aKnots2(1) = 0.0; + aKnots2(2) = 2.0; // Different + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 4; + aMults(2) = 4; + + Handle(Geom2d_BSplineCurve) aCurve1 = new Geom2d_BSplineCurve(aPoles, aKnots1, aMults, 3); + Handle(Geom2d_BSplineCurve) aCurve2 = new Geom2d_BSplineCurve(aPoles, aKnots2, aMults, 3); + + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Periodic BSpline Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, BSplineCurve_QuadraticBezierEquivalent_CopiedCurves_SameHash) +{ + // Simple quadratic B-spline (Bezier-like, single span) + TColgp_Array1OfPnt2d aPoles(1, 3); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 2.0); + aPoles(3) = gp_Pnt2d(2.0, 0.0); + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 3; + aMults(2) = 3; + + Handle(Geom2d_BSplineCurve) aCurve1 = new Geom2d_BSplineCurve(aPoles, aKnots, aMults, 2); + Handle(Geom2d_BSplineCurve) aCurve2 = Handle(Geom2d_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Reversed Curve Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Line_Reversed_DifferentComparison) +{ + Handle(Geom2d_Line) aLine1 = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_Line) aLine2 = Handle(Geom2d_Line)::DownCast(aLine1->Reversed()); + + EXPECT_FALSE(myHasher(aLine1, aLine2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, BezierCurve_Reversed_DifferentComparison) +{ + TColgp_Array1OfPnt2d aPoles(1, 3); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 2.0); + aPoles(3) = gp_Pnt2d(2.0, 0.0); + + Handle(Geom2d_BezierCurve) aCurve1 = new Geom2d_BezierCurve(aPoles); + Handle(Geom2d_BezierCurve) aCurve2 = Handle(Geom2d_BezierCurve)::DownCast(aCurve1->Reversed()); + + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Transformed Curve Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Circle_Translated_DifferentHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Circle) aCircle1 = new Geom2d_Circle(anAxis, 5.0); + Handle(Geom2d_Circle) aCircle2 = Handle(Geom2d_Circle)::DownCast(aCircle1->Copy()); + aCircle2->Translate(gp_Vec2d(1.0, 0.0)); + + EXPECT_NE(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_FALSE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Circle_Scaled_DifferentHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Circle) aCircle1 = new Geom2d_Circle(anAxis, 5.0); + Handle(Geom2d_Circle) aCircle2 = Handle(Geom2d_Circle)::DownCast(aCircle1->Copy()); + aCircle2->Scale(gp_Pnt2d(0.0, 0.0), 2.0); + + EXPECT_NE(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_FALSE(myHasher(aCircle1, aCircle2)); +} + +// ============================================================================ +// Higher Degree BSpline Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, BSplineCurve_HigherDegree_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt2d aPoles(1, 6); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 1.0); + aPoles(3) = gp_Pnt2d(2.0, 0.5); + aPoles(4) = gp_Pnt2d(3.0, 1.5); + aPoles(5) = gp_Pnt2d(4.0, 0.0); + aPoles(6) = gp_Pnt2d(5.0, 1.0); + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 6; + aMults(2) = 6; + + Handle(Geom2d_BSplineCurve) aCurve1 = new Geom2d_BSplineCurve(aPoles, aKnots, aMults, 5); + Handle(Geom2d_BSplineCurve) aCurve2 = Handle(Geom2d_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Multiple Knot Spans Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, BSplineCurve_LinearMultipleSpans_CopiedCurves_SameHash) +{ + // Linear B-spline with multiple knots (piecewise linear) + TColgp_Array1OfPnt2d aPoles(1, 4); + aPoles(1) = gp_Pnt2d(0.0, 0.0); + aPoles(2) = gp_Pnt2d(1.0, 1.0); + aPoles(3) = gp_Pnt2d(2.0, 0.0); + aPoles(4) = gp_Pnt2d(3.0, 1.0); + + TColStd_Array1OfReal aKnots(1, 4); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + aKnots(3) = 2.0; + aKnots(4) = 3.0; + + TColStd_Array1OfInteger aMults(1, 4); + aMults(1) = 2; + aMults(2) = 1; + aMults(3) = 1; + aMults(4) = 2; + + Handle(Geom2d_BSplineCurve) aCurve1 = new Geom2d_BSplineCurve(aPoles, aKnots, aMults, 1); + Handle(Geom2d_BSplineCurve) aCurve2 = Handle(Geom2d_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Cross-type Conic Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Ellipse_vs_Hyperbola_DifferentComparison) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Ellipse) anEllipse = new Geom2d_Ellipse(anAxis, 5.0, 3.0); + Handle(Geom2d_Hyperbola) aHyperbola = new Geom2d_Hyperbola(anAxis, 5.0, 3.0); + + EXPECT_FALSE(myHasher(anEllipse, aHyperbola)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Circle_vs_Ellipse_DifferentComparison) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Circle) aCircle = new Geom2d_Circle(anAxis, 5.0); + Handle(Geom2d_Ellipse) anEllipse = new Geom2d_Ellipse(anAxis, 5.0, 5.0); + + EXPECT_FALSE(myHasher(aCircle, anEllipse)); +} + +// ============================================================================ +// Trimmed vs Base Curve Tests +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, TrimmedCurve_vs_BaseCurve_DifferentComparison) +{ + Handle(Geom2d_Line) aLine = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_TrimmedCurve) aTrimmed = new Geom2d_TrimmedCurve(aLine, 0.0, 10.0); + + EXPECT_FALSE(myHasher(aLine, aTrimmed)); +} + +// ============================================================================ +// Edge Cases - Degenerate/Special Values +// ============================================================================ + +TEST_F(Geom2dHash_CurveHasherTest, Circle_VerySmallRadius_CopiedCircles_SameHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Circle) aCircle1 = new Geom2d_Circle(anAxis, 1e-10); + Handle(Geom2d_Circle) aCircle2 = Handle(Geom2d_Circle)::DownCast(aCircle1->Copy()); + + EXPECT_EQ(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_TRUE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Circle_VeryLargeRadius_CopiedCircles_SameHash) +{ + gp_Ax22d anAxis(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0), gp_Dir2d(0.0, 1.0)); + Handle(Geom2d_Circle) aCircle1 = new Geom2d_Circle(anAxis, 1e10); + Handle(Geom2d_Circle) aCircle2 = Handle(Geom2d_Circle)::DownCast(aCircle1->Copy()); + + EXPECT_EQ(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_TRUE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(Geom2dHash_CurveHasherTest, Line_AtOrigin_vs_FarFromOrigin_DifferentHash) +{ + Handle(Geom2d_Line) aLine1 = new Geom2d_Line(gp_Pnt2d(0.0, 0.0), gp_Dir2d(1.0, 0.0)); + Handle(Geom2d_Line) aLine2 = new Geom2d_Line(gp_Pnt2d(1e10, 1e10), gp_Dir2d(1.0, 0.0)); + + EXPECT_NE(myHasher(aLine1), myHasher(aLine2)); + EXPECT_FALSE(myHasher(aLine1, aLine2)); +} diff --git a/src/ModelingData/TKG2d/Geom2dHash/FILES.cmake b/src/ModelingData/TKG2d/Geom2dHash/FILES.cmake new file mode 100644 index 0000000000..93deddcaea --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/FILES.cmake @@ -0,0 +1,22 @@ +# Source files for Geom2dHash package +set(OCCT_Geom2dHash_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}") + +set(OCCT_Geom2dHash_FILES + # Foundational Hashers + Geom2dHash_PointHasher.pxx + Geom2dHash_DirectionHasher.pxx + Geom2dHash_AxisPlacement.pxx + + # Curve Hashers + Geom2dHash_LineHasher.pxx + Geom2dHash_CircleHasher.pxx + Geom2dHash_EllipseHasher.pxx + Geom2dHash_HyperbolaHasher.pxx + Geom2dHash_ParabolaHasher.pxx + Geom2dHash_BezierCurveHasher.pxx + Geom2dHash_BSplineCurveHasher.pxx + Geom2dHash_TrimmedCurveHasher.pxx + Geom2dHash_OffsetCurveHasher.pxx + Geom2dHash_CurveHasher.hxx + Geom2dHash_CurveHasher.cxx +) diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_AxisPlacement.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_AxisPlacement.pxx new file mode 100644 index 0000000000..d5592e3557 --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_AxisPlacement.pxx @@ -0,0 +1,51 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_AxisPlacement_HeaderFile +#define _Geom2dHash_AxisPlacement_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for gp_Ax22d (2D coordinate system). +//! Used for geometry deduplication. +struct Geom2dHash_AxisPlacement +{ + // Hashes a 2D axis placement by its location, X direction, and Y direction. + std::size_t operator()(const gp_Ax22d& theAxisPlacement) const noexcept + { + const Geom2dHash_PointHasher aPointHasher; + const Geom2dHash_DirectionHasher aDirHasher; + + const std::size_t aHashes[3] = {aPointHasher(theAxisPlacement.Location()), + aDirHasher(theAxisPlacement.XDirection()), + aDirHasher(theAxisPlacement.YDirection())}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two 2D axis placements. + bool operator()(const gp_Ax22d& theAxisPlacement1, + const gp_Ax22d& theAxisPlacement2) const noexcept + { + const Geom2dHash_PointHasher aPointHasher; + const Geom2dHash_DirectionHasher aDirHasher; + + return aPointHasher(theAxisPlacement1.Location(), theAxisPlacement2.Location()) + && aDirHasher(theAxisPlacement1.XDirection(), theAxisPlacement2.XDirection()) + && aDirHasher(theAxisPlacement1.YDirection(), theAxisPlacement2.YDirection()); + } +}; + +#endif // _Geom2dHash_AxisPlacement_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_BSplineCurveHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_BSplineCurveHasher.pxx new file mode 100644 index 0000000000..9dea2eba96 --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_BSplineCurveHasher.pxx @@ -0,0 +1,98 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_BSplineCurveHasher_HeaderFile +#define _Geom2dHash_BSplineCurveHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_BSplineCurve (2D B-spline curve). +//! Used for geometry deduplication. +//! Hashes only metadata (degree, pole count, knot count, rationality) for efficiency. +struct Geom2dHash_BSplineCurveHasher +{ + // Hashes the B-spline curve metadata only. + std::size_t operator()(const Handle(Geom2d_BSplineCurve)& theCurve) const noexcept + { + const std::size_t aHashes[4] = {opencascade::hash(theCurve->Degree()), + opencascade::hash(theCurve->NbPoles()), + opencascade::hash(theCurve->NbKnots()), + opencascade::hash(static_cast(theCurve->IsRational()))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two B-spline curves by full geometric data. + bool operator()(const Handle(Geom2d_BSplineCurve)& theCurve1, + const Handle(Geom2d_BSplineCurve)& theCurve2) const noexcept + { + constexpr double aTolerance = 1e-12; + + // Compare degrees + if (theCurve1->Degree() != theCurve2->Degree()) + { + return false; + } + + // Compare knot counts + if (theCurve1->NbKnots() != theCurve2->NbKnots()) + { + return false; + } + + // Compare knots and multiplicities + for (int i = 1; i <= theCurve1->NbKnots(); ++i) + { + if (std::abs(theCurve1->Knot(i) - theCurve2->Knot(i)) > aTolerance + || theCurve1->Multiplicity(i) != theCurve2->Multiplicity(i)) + { + return false; + } + } + + // Compare rationality + if (theCurve1->IsRational() != theCurve2->IsRational()) + { + return false; + } + + const Geom2dHash_PointHasher aPointHasher; + + // Compare poles + for (int i = 1; i <= theCurve1->NbPoles(); ++i) + { + if (!aPointHasher(theCurve1->Pole(i), theCurve2->Pole(i))) + { + return false; + } + } + + // Compare weights if rational + if (theCurve1->IsRational()) + { + for (int i = 1; i <= theCurve1->NbPoles(); ++i) + { + if (std::abs(theCurve1->Weight(i) - theCurve2->Weight(i)) > aTolerance) + { + return false; + } + } + } + + return true; + } +}; + +#endif // _Geom2dHash_BSplineCurveHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_BezierCurveHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_BezierCurveHasher.pxx new file mode 100644 index 0000000000..3e46201e9f --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_BezierCurveHasher.pxx @@ -0,0 +1,81 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_BezierCurveHasher_HeaderFile +#define _Geom2dHash_BezierCurveHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_BezierCurve (2D Bezier curve). +//! Used for geometry deduplication. +//! Hashes only metadata (degree, pole count, rationality) for efficiency. +struct Geom2dHash_BezierCurveHasher +{ + // Hashes the Bezier curve metadata only. + std::size_t operator()(const Handle(Geom2d_BezierCurve)& theCurve) const noexcept + { + const std::size_t aHashes[3] = {opencascade::hash(theCurve->Degree()), + opencascade::hash(theCurve->NbPoles()), + opencascade::hash(static_cast(theCurve->IsRational()))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two Bezier curves by full geometric data. + bool operator()(const Handle(Geom2d_BezierCurve)& theCurve1, + const Handle(Geom2d_BezierCurve)& theCurve2) const noexcept + { + constexpr double aTolerance = 1e-12; + + // Compare degrees + if (theCurve1->Degree() != theCurve2->Degree()) + { + return false; + } + + // Compare rationality + if (theCurve1->IsRational() != theCurve2->IsRational()) + { + return false; + } + + const Geom2dHash_PointHasher aPointHasher; + + // Compare poles + for (int i = 1; i <= theCurve1->NbPoles(); ++i) + { + if (!aPointHasher(theCurve1->Pole(i), theCurve2->Pole(i))) + { + return false; + } + } + + // Compare weights if rational + if (theCurve1->IsRational()) + { + for (int i = 1; i <= theCurve1->NbPoles(); ++i) + { + if (std::abs(theCurve1->Weight(i) - theCurve2->Weight(i)) > aTolerance) + { + return false; + } + } + } + + return true; + } +}; + +#endif // _Geom2dHash_BezierCurveHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CircleHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CircleHasher.pxx new file mode 100644 index 0000000000..c8f866530e --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CircleHasher.pxx @@ -0,0 +1,51 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_CircleHasher_HeaderFile +#define _Geom2dHash_CircleHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_Circle (2D circle). +//! Used for geometry deduplication. +struct Geom2dHash_CircleHasher +{ + // Hashes the circle by its position and radius. + std::size_t operator()(const Handle(Geom2d_Circle)& theCircle) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const Geom2dHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[2] = { + anAxisHasher(theCircle->Position()), + opencascade::hash(static_cast(std::round(theCircle->Radius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two circles by their positions and radii. + bool operator()(const Handle(Geom2d_Circle)& theCircle1, + const Handle(Geom2d_Circle)& theCircle2) const noexcept + { + constexpr double aTolerance = 1e-12; + const Geom2dHash_AxisPlacement anAxisHasher; + + return anAxisHasher(theCircle1->Position(), theCircle2->Position()) + && std::abs(theCircle1->Radius() - theCircle2->Radius()) <= aTolerance; + } +}; + +#endif // _Geom2dHash_CircleHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CurveHasher.cxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CurveHasher.cxx new file mode 100644 index 0000000000..8b29a7642b --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CurveHasher.cxx @@ -0,0 +1,152 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//================================================================================================= + +std::size_t Geom2dHash_CurveHasher::operator()(const Handle(Geom2d_Curve)& theCurve) const noexcept +{ + if (theCurve.IsNull()) + { + return 0; + } + + // Dispatch based on actual curve type + if (Handle(Geom2d_Line) aLine = Handle(Geom2d_Line)::DownCast(theCurve)) + { + return Geom2dHash_LineHasher{}(aLine); + } + if (Handle(Geom2d_Circle) aCircle = Handle(Geom2d_Circle)::DownCast(theCurve)) + { + return Geom2dHash_CircleHasher{}(aCircle); + } + if (Handle(Geom2d_Ellipse) anEllipse = Handle(Geom2d_Ellipse)::DownCast(theCurve)) + { + return Geom2dHash_EllipseHasher{}(anEllipse); + } + if (Handle(Geom2d_Hyperbola) aHyperbola = Handle(Geom2d_Hyperbola)::DownCast(theCurve)) + { + return Geom2dHash_HyperbolaHasher{}(aHyperbola); + } + if (Handle(Geom2d_Parabola) aParabola = Handle(Geom2d_Parabola)::DownCast(theCurve)) + { + return Geom2dHash_ParabolaHasher{}(aParabola); + } + if (Handle(Geom2d_BezierCurve) aBezier = Handle(Geom2d_BezierCurve)::DownCast(theCurve)) + { + return Geom2dHash_BezierCurveHasher{}(aBezier); + } + if (Handle(Geom2d_BSplineCurve) aBSpline = Handle(Geom2d_BSplineCurve)::DownCast(theCurve)) + { + return Geom2dHash_BSplineCurveHasher{}(aBSpline); + } + if (Handle(Geom2d_TrimmedCurve) aTrimmed = Handle(Geom2d_TrimmedCurve)::DownCast(theCurve)) + { + return Geom2dHash_TrimmedCurveHasher{}(aTrimmed); + } + if (Handle(Geom2d_OffsetCurve) anOffset = Handle(Geom2d_OffsetCurve)::DownCast(theCurve)) + { + return Geom2dHash_OffsetCurveHasher{}(anOffset); + } + + // Unknown curve type - hash the type name + return std::hash{}(theCurve->DynamicType()->Name()); +} + +//================================================================================================= + +bool Geom2dHash_CurveHasher::operator()(const Handle(Geom2d_Curve)& theCurve1, + const Handle(Geom2d_Curve)& theCurve2) const noexcept +{ + if (theCurve1.IsNull() || theCurve2.IsNull()) + { + return theCurve1.IsNull() && theCurve2.IsNull(); + } + + if (theCurve1 == theCurve2) + { + return true; + } + + // Must be same type + if (theCurve1->DynamicType() != theCurve2->DynamicType()) + { + return false; + } + + // Dispatch based on actual curve type + if (Handle(Geom2d_Line) aLine1 = Handle(Geom2d_Line)::DownCast(theCurve1)) + { + return Geom2dHash_LineHasher{}(aLine1, Handle(Geom2d_Line)::DownCast(theCurve2)); + } + if (Handle(Geom2d_Circle) aCircle1 = Handle(Geom2d_Circle)::DownCast(theCurve1)) + { + return Geom2dHash_CircleHasher{}(aCircle1, Handle(Geom2d_Circle)::DownCast(theCurve2)); + } + if (Handle(Geom2d_Ellipse) anEllipse1 = Handle(Geom2d_Ellipse)::DownCast(theCurve1)) + { + return Geom2dHash_EllipseHasher{}(anEllipse1, Handle(Geom2d_Ellipse)::DownCast(theCurve2)); + } + if (Handle(Geom2d_Hyperbola) aHyp1 = Handle(Geom2d_Hyperbola)::DownCast(theCurve1)) + { + return Geom2dHash_HyperbolaHasher{}(aHyp1, Handle(Geom2d_Hyperbola)::DownCast(theCurve2)); + } + if (Handle(Geom2d_Parabola) aPar1 = Handle(Geom2d_Parabola)::DownCast(theCurve1)) + { + return Geom2dHash_ParabolaHasher{}(aPar1, Handle(Geom2d_Parabola)::DownCast(theCurve2)); + } + if (Handle(Geom2d_BezierCurve) aBez1 = Handle(Geom2d_BezierCurve)::DownCast(theCurve1)) + { + return Geom2dHash_BezierCurveHasher{}(aBez1, Handle(Geom2d_BezierCurve)::DownCast(theCurve2)); + } + if (Handle(Geom2d_BSplineCurve) aBSpl1 = Handle(Geom2d_BSplineCurve)::DownCast(theCurve1)) + { + return Geom2dHash_BSplineCurveHasher{}(aBSpl1, + Handle(Geom2d_BSplineCurve)::DownCast(theCurve2)); + } + if (Handle(Geom2d_TrimmedCurve) aTrim1 = Handle(Geom2d_TrimmedCurve)::DownCast(theCurve1)) + { + return Geom2dHash_TrimmedCurveHasher{}(aTrim1, + Handle(Geom2d_TrimmedCurve)::DownCast(theCurve2)); + } + if (Handle(Geom2d_OffsetCurve) aOff1 = Handle(Geom2d_OffsetCurve)::DownCast(theCurve1)) + { + return Geom2dHash_OffsetCurveHasher{}(aOff1, Handle(Geom2d_OffsetCurve)::DownCast(theCurve2)); + } + + // Unknown curve type - compare by pointer + return theCurve1.get() == theCurve2.get(); +} diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CurveHasher.hxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CurveHasher.hxx new file mode 100644 index 0000000000..fe35e77146 --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_CurveHasher.hxx @@ -0,0 +1,34 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_CurveHasher_HeaderFile +#define _Geom2dHash_CurveHasher_HeaderFile + +#include +#include + +class Geom2d_Curve; + +//! Polymorphic hasher for Geom2d_Curve using RTTI dispatch. +//! Used for geometry deduplication. +struct Geom2dHash_CurveHasher +{ + // Hashes any Geom2d_Curve by dispatching to the appropriate specific hasher. + Standard_EXPORT std::size_t operator()(const Handle(Geom2d_Curve)& theCurve) const noexcept; + + // Compares two curves using polymorphic dispatch. + Standard_EXPORT bool operator()(const Handle(Geom2d_Curve)& theCurve1, + const Handle(Geom2d_Curve)& theCurve2) const noexcept; +}; + +#endif // _Geom2dHash_CurveHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_DirectionHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_DirectionHasher.pxx new file mode 100644 index 0000000000..86e73a7700 --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_DirectionHasher.pxx @@ -0,0 +1,47 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_DirectionHasher_HeaderFile +#define _Geom2dHash_DirectionHasher_HeaderFile + +#include +#include +#include + +//! OCCT-style hasher for gp_Dir2d (2D directions). +//! Used for geometry deduplication. +struct Geom2dHash_DirectionHasher +{ + // Hashes the 2D direction by its XY components. + std::size_t operator()(const gp_Dir2d& theDirection) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + // Round each component to tolerance precision before hashing + const std::size_t aHashes[2] = { + opencascade::hash(static_cast(std::round(theDirection.X() * aFactor))), + opencascade::hash(static_cast(std::round(theDirection.Y() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two 2D directions with fixed tolerance. + bool operator()(const gp_Dir2d& theDirection1, const gp_Dir2d& theDirection2) const noexcept + { + constexpr double aTolerance = 1e-12; + return std::abs(theDirection1.X() - theDirection2.X()) <= aTolerance + && std::abs(theDirection1.Y() - theDirection2.Y()) <= aTolerance; + } +}; + +#endif // _Geom2dHash_DirectionHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_EllipseHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_EllipseHasher.pxx new file mode 100644 index 0000000000..398bb5bc8b --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_EllipseHasher.pxx @@ -0,0 +1,53 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_EllipseHasher_HeaderFile +#define _Geom2dHash_EllipseHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_Ellipse (2D ellipse). +//! Used for geometry deduplication. +struct Geom2dHash_EllipseHasher +{ + // Hashes the ellipse by its position, major radius, and minor radius. + std::size_t operator()(const Handle(Geom2d_Ellipse)& theEllipse) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const Geom2dHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[3] = { + anAxisHasher(theEllipse->Position()), + opencascade::hash(static_cast(std::round(theEllipse->MajorRadius() * aFactor))), + opencascade::hash(static_cast(std::round(theEllipse->MinorRadius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two ellipses by their positions and radii. + bool operator()(const Handle(Geom2d_Ellipse)& theEllipse1, + const Handle(Geom2d_Ellipse)& theEllipse2) const noexcept + { + constexpr double aTolerance = 1e-12; + const Geom2dHash_AxisPlacement anAxisHasher; + + return anAxisHasher(theEllipse1->Position(), theEllipse2->Position()) + && std::abs(theEllipse1->MajorRadius() - theEllipse2->MajorRadius()) <= aTolerance + && std::abs(theEllipse1->MinorRadius() - theEllipse2->MinorRadius()) <= aTolerance; + } +}; + +#endif // _Geom2dHash_EllipseHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_HyperbolaHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_HyperbolaHasher.pxx new file mode 100644 index 0000000000..c8de9ec49e --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_HyperbolaHasher.pxx @@ -0,0 +1,53 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_HyperbolaHasher_HeaderFile +#define _Geom2dHash_HyperbolaHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_Hyperbola (2D hyperbola). +//! Used for geometry deduplication. +struct Geom2dHash_HyperbolaHasher +{ + // Hashes the hyperbola by its position, major radius, and minor radius. + std::size_t operator()(const Handle(Geom2d_Hyperbola)& theHyperbola) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const Geom2dHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[3] = { + anAxisHasher(theHyperbola->Position()), + opencascade::hash(static_cast(std::round(theHyperbola->MajorRadius() * aFactor))), + opencascade::hash(static_cast(std::round(theHyperbola->MinorRadius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two hyperbolas by their positions and radii. + bool operator()(const Handle(Geom2d_Hyperbola)& theHyperbola1, + const Handle(Geom2d_Hyperbola)& theHyperbola2) const noexcept + { + constexpr double aTolerance = 1e-12; + const Geom2dHash_AxisPlacement anAxisHasher; + + return anAxisHasher(theHyperbola1->Position(), theHyperbola2->Position()) + && std::abs(theHyperbola1->MajorRadius() - theHyperbola2->MajorRadius()) <= aTolerance + && std::abs(theHyperbola1->MinorRadius() - theHyperbola2->MinorRadius()) <= aTolerance; + } +}; + +#endif // _Geom2dHash_HyperbolaHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_LineHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_LineHasher.pxx new file mode 100644 index 0000000000..873a52190a --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_LineHasher.pxx @@ -0,0 +1,49 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_LineHasher_HeaderFile +#define _Geom2dHash_LineHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_Line (2D line). +//! Used for geometry deduplication. +struct Geom2dHash_LineHasher +{ + // Hashes the line by its location and direction. + std::size_t operator()(const Handle(Geom2d_Line)& theLine) const noexcept + { + const Geom2dHash_PointHasher aPointHasher; + const Geom2dHash_DirectionHasher aDirHasher; + + const std::size_t aHashes[2] = {aPointHasher(theLine->Position().Location()), + aDirHasher(theLine->Position().Direction())}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two lines by their positions. + bool operator()(const Handle(Geom2d_Line)& theLine1, + const Handle(Geom2d_Line)& theLine2) const noexcept + { + const Geom2dHash_PointHasher aPointHasher; + const Geom2dHash_DirectionHasher aDirHasher; + + return aPointHasher(theLine1->Position().Location(), theLine2->Position().Location()) + && aDirHasher(theLine1->Position().Direction(), theLine2->Position().Direction()); + } +}; + +#endif // _Geom2dHash_LineHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_OffsetCurveHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_OffsetCurveHasher.pxx new file mode 100644 index 0000000000..e10a5aca0b --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_OffsetCurveHasher.pxx @@ -0,0 +1,52 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_OffsetCurveHasher_HeaderFile +#define _Geom2dHash_OffsetCurveHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_OffsetCurve (2D offset curve). +//! Used for geometry deduplication. +struct Geom2dHash_OffsetCurveHasher +{ + // Hashes the offset curve by its offset distance and basis curve. + std::size_t operator()(const Handle(Geom2d_OffsetCurve)& theCurve) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const Geom2dHash_CurveHasher aCurveHasher; + const std::size_t aHashes[2] = { + aCurveHasher(theCurve->BasisCurve()), + opencascade::hash(static_cast(std::round(theCurve->Offset() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two offset curves. + bool operator()(const Handle(Geom2d_OffsetCurve)& theCurve1, + const Handle(Geom2d_OffsetCurve)& theCurve2) const noexcept + { + constexpr double aTolerance = 1e-12; + + const Geom2dHash_CurveHasher aCurveHasher; + + return aCurveHasher(theCurve1->BasisCurve(), theCurve2->BasisCurve()) + && std::abs(theCurve1->Offset() - theCurve2->Offset()) <= aTolerance; + } +}; + +#endif // _Geom2dHash_OffsetCurveHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_ParabolaHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_ParabolaHasher.pxx new file mode 100644 index 0000000000..3fea42ce84 --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_ParabolaHasher.pxx @@ -0,0 +1,51 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_ParabolaHasher_HeaderFile +#define _Geom2dHash_ParabolaHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_Parabola (2D parabola). +//! Used for geometry deduplication. +struct Geom2dHash_ParabolaHasher +{ + // Hashes the parabola by its position and focal length. + std::size_t operator()(const Handle(Geom2d_Parabola)& theParabola) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const Geom2dHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[2] = { + anAxisHasher(theParabola->Position()), + opencascade::hash(static_cast(std::round(theParabola->Focal() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two parabolas by their positions and focal lengths. + bool operator()(const Handle(Geom2d_Parabola)& theParabola1, + const Handle(Geom2d_Parabola)& theParabola2) const noexcept + { + constexpr double aTolerance = 1e-12; + const Geom2dHash_AxisPlacement anAxisHasher; + + return anAxisHasher(theParabola1->Position(), theParabola2->Position()) + && std::abs(theParabola1->Focal() - theParabola2->Focal()) <= aTolerance; + } +}; + +#endif // _Geom2dHash_ParabolaHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_PointHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_PointHasher.pxx new file mode 100644 index 0000000000..12b31c1e56 --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_PointHasher.pxx @@ -0,0 +1,47 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_PointHasher_HeaderFile +#define _Geom2dHash_PointHasher_HeaderFile + +#include +#include +#include + +//! OCCT-style hasher for gp_Pnt2d (2D points). +//! Used for geometry deduplication. +struct Geom2dHash_PointHasher +{ + // Hashes the 2D point by its XY coordinates. + std::size_t operator()(const gp_Pnt2d& thePoint) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + // Round each coordinate to tolerance precision before hashing + const std::size_t aHashes[2] = { + opencascade::hash(static_cast(std::round(thePoint.X() * aFactor))), + opencascade::hash(static_cast(std::round(thePoint.Y() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two 2D points with fixed tolerance. + bool operator()(const gp_Pnt2d& thePoint1, const gp_Pnt2d& thePoint2) const noexcept + { + constexpr double aTolerance = 1e-12; + return std::abs(thePoint1.X() - thePoint2.X()) <= aTolerance + && std::abs(thePoint1.Y() - thePoint2.Y()) <= aTolerance; + } +}; + +#endif // _Geom2dHash_PointHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_TrimmedCurveHasher.pxx b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_TrimmedCurveHasher.pxx new file mode 100644 index 0000000000..3da1da43c0 --- /dev/null +++ b/src/ModelingData/TKG2d/Geom2dHash/Geom2dHash_TrimmedCurveHasher.pxx @@ -0,0 +1,54 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Geom2dHash_TrimmedCurveHasher_HeaderFile +#define _Geom2dHash_TrimmedCurveHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom2d_TrimmedCurve (2D trimmed curve). +//! Used for geometry deduplication. +struct Geom2dHash_TrimmedCurveHasher +{ + // Hashes the trimmed curve by its parameters and basis curve. + std::size_t operator()(const Handle(Geom2d_TrimmedCurve)& theCurve) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const Geom2dHash_CurveHasher aCurveHasher; + const std::size_t aHashes[3] = { + aCurveHasher(theCurve->BasisCurve()), + opencascade::hash(static_cast(std::round(theCurve->FirstParameter() * aFactor))), + opencascade::hash(static_cast(std::round(theCurve->LastParameter() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two trimmed curves. + bool operator()(const Handle(Geom2d_TrimmedCurve)& theCurve1, + const Handle(Geom2d_TrimmedCurve)& theCurve2) const noexcept + { + constexpr double aTolerance = 1e-12; + + const Geom2dHash_CurveHasher aCurveHasher; + + return aCurveHasher(theCurve1->BasisCurve(), theCurve2->BasisCurve()) + && std::abs(theCurve1->FirstParameter() - theCurve2->FirstParameter()) <= aTolerance + && std::abs(theCurve1->LastParameter() - theCurve2->LastParameter()) <= aTolerance; + } +}; + +#endif // _Geom2dHash_TrimmedCurveHasher_HeaderFile diff --git a/src/ModelingData/TKG2d/PACKAGES.cmake b/src/ModelingData/TKG2d/PACKAGES.cmake index 3b08d25058..53148bd634 100644 --- a/src/ModelingData/TKG2d/PACKAGES.cmake +++ b/src/ModelingData/TKG2d/PACKAGES.cmake @@ -7,4 +7,5 @@ set(OCCT_TKG2d_LIST_OF_PACKAGES Geom2dLProp Geom2dAdaptor Geom2dEvaluator + Geom2dHash ) diff --git a/src/ModelingData/TKG3d/GTests/FILES.cmake b/src/ModelingData/TKG3d/GTests/FILES.cmake index 679da3f866..3e7d05db7c 100644 --- a/src/ModelingData/TKG3d/GTests/FILES.cmake +++ b/src/ModelingData/TKG3d/GTests/FILES.cmake @@ -10,4 +10,6 @@ set(OCCT_TKG3d_GTests_FILES Geom_OffsetSurface_Test.cxx GeomAPI_ExtremaCurveCurve_Test.cxx GeomAPI_Interpolate_Test.cxx + GeomHash_CurveHasher_Test.cxx + GeomHash_SurfaceHasher_Test.cxx ) diff --git a/src/ModelingData/TKG3d/GTests/GeomHash_CurveHasher_Test.cxx b/src/ModelingData/TKG3d/GTests/GeomHash_CurveHasher_Test.cxx new file mode 100644 index 0000000000..6a8054769c --- /dev/null +++ b/src/ModelingData/TKG3d/GTests/GeomHash_CurveHasher_Test.cxx @@ -0,0 +1,690 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class GeomHash_CurveHasherTest : public ::testing::Test +{ +protected: + GeomHash_CurveHasher myHasher; +}; + +// ============================================================================ +// Line Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Line_CopiedLines_SameHash) +{ + gp_Pnt aLoc(1.0, 2.0, 3.0); + gp_Dir aDir(1.0, 0.0, 0.0); + Handle(Geom_Line) aLine1 = new Geom_Line(aLoc, aDir); + Handle(Geom_Line) aLine2 = Handle(Geom_Line)::DownCast(aLine1->Copy()); + + EXPECT_EQ(myHasher(aLine1), myHasher(aLine2)); + EXPECT_TRUE(myHasher(aLine1, aLine2)); +} + +TEST_F(GeomHash_CurveHasherTest, Line_DifferentLines_DifferentHash) +{ + Handle(Geom_Line) aLine1 = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + Handle(Geom_Line) aLine2 = new Geom_Line(gp_Pnt(1.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + + EXPECT_NE(myHasher(aLine1), myHasher(aLine2)); + EXPECT_FALSE(myHasher(aLine1, aLine2)); +} + +// ============================================================================ +// Circle Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Circle_CopiedCircles_SameHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Circle) aCircle1 = new Geom_Circle(anAxis, 5.0); + Handle(Geom_Circle) aCircle2 = Handle(Geom_Circle)::DownCast(aCircle1->Copy()); + + EXPECT_EQ(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_TRUE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(GeomHash_CurveHasherTest, Circle_DifferentRadius_DifferentHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Circle) aCircle1 = new Geom_Circle(anAxis, 5.0); + Handle(Geom_Circle) aCircle2 = new Geom_Circle(anAxis, 10.0); + + EXPECT_NE(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_FALSE(myHasher(aCircle1, aCircle2)); +} + +// ============================================================================ +// Ellipse Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Ellipse_CopiedEllipses_SameHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Ellipse) anEllipse1 = new Geom_Ellipse(anAxis, 10.0, 5.0); + Handle(Geom_Ellipse) anEllipse2 = Handle(Geom_Ellipse)::DownCast(anEllipse1->Copy()); + + EXPECT_EQ(myHasher(anEllipse1), myHasher(anEllipse2)); + EXPECT_TRUE(myHasher(anEllipse1, anEllipse2)); +} + +TEST_F(GeomHash_CurveHasherTest, Ellipse_DifferentRadii_DifferentHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Ellipse) anEllipse1 = new Geom_Ellipse(anAxis, 10.0, 5.0); + Handle(Geom_Ellipse) anEllipse2 = new Geom_Ellipse(anAxis, 10.0, 7.0); + + EXPECT_NE(myHasher(anEllipse1), myHasher(anEllipse2)); + EXPECT_FALSE(myHasher(anEllipse1, anEllipse2)); +} + +// ============================================================================ +// Hyperbola Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Hyperbola_CopiedHyperbolas_SameHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Hyperbola) aHyp1 = new Geom_Hyperbola(anAxis, 5.0, 3.0); + Handle(Geom_Hyperbola) aHyp2 = Handle(Geom_Hyperbola)::DownCast(aHyp1->Copy()); + + EXPECT_EQ(myHasher(aHyp1), myHasher(aHyp2)); + EXPECT_TRUE(myHasher(aHyp1, aHyp2)); +} + +TEST_F(GeomHash_CurveHasherTest, Hyperbola_DifferentRadii_DifferentHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Hyperbola) aHyp1 = new Geom_Hyperbola(anAxis, 5.0, 3.0); + Handle(Geom_Hyperbola) aHyp2 = new Geom_Hyperbola(anAxis, 5.0, 4.0); + + EXPECT_NE(myHasher(aHyp1), myHasher(aHyp2)); + EXPECT_FALSE(myHasher(aHyp1, aHyp2)); +} + +// ============================================================================ +// Parabola Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Parabola_CopiedParabolas_SameHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Parabola) aPar1 = new Geom_Parabola(anAxis, 2.0); + Handle(Geom_Parabola) aPar2 = Handle(Geom_Parabola)::DownCast(aPar1->Copy()); + + EXPECT_EQ(myHasher(aPar1), myHasher(aPar2)); + EXPECT_TRUE(myHasher(aPar1, aPar2)); +} + +TEST_F(GeomHash_CurveHasherTest, Parabola_DifferentFocal_DifferentHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Parabola) aPar1 = new Geom_Parabola(anAxis, 2.0); + Handle(Geom_Parabola) aPar2 = new Geom_Parabola(anAxis, 3.0); + + EXPECT_NE(myHasher(aPar1), myHasher(aPar2)); + EXPECT_FALSE(myHasher(aPar1, aPar2)); +} + +// ============================================================================ +// BezierCurve Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, BezierCurve_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt aPoles(1, 4); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 2.0, 0.0); + aPoles(3) = gp_Pnt(3.0, 2.0, 0.0); + aPoles(4) = gp_Pnt(4.0, 0.0, 0.0); + + Handle(Geom_BezierCurve) aCurve1 = new Geom_BezierCurve(aPoles); + Handle(Geom_BezierCurve) aCurve2 = Handle(Geom_BezierCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +TEST_F(GeomHash_CurveHasherTest, BezierCurve_DifferentPoles_DifferentComparison) +{ + TColgp_Array1OfPnt aPoles1(1, 3); + aPoles1(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles1(2) = gp_Pnt(1.0, 2.0, 0.0); + aPoles1(3) = gp_Pnt(2.0, 0.0, 0.0); + + TColgp_Array1OfPnt aPoles2(1, 3); + aPoles2(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles2(2) = gp_Pnt(1.0, 3.0, 0.0); // Different + aPoles2(3) = gp_Pnt(2.0, 0.0, 0.0); + + Handle(Geom_BezierCurve) aCurve1 = new Geom_BezierCurve(aPoles1); + Handle(Geom_BezierCurve) aCurve2 = new Geom_BezierCurve(aPoles2); + + // Hash may be same (metadata only), but comparison should differ + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// BSplineCurve Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, BSplineCurve_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt aPoles(1, 4); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 1.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 1.0, 0.0); + aPoles(4) = gp_Pnt(3.0, 0.0, 0.0); + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 4; + aMults(2) = 4; + + Handle(Geom_BSplineCurve) aCurve1 = new Geom_BSplineCurve(aPoles, aKnots, aMults, 3); + Handle(Geom_BSplineCurve) aCurve2 = Handle(Geom_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// TrimmedCurve Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, TrimmedCurve_CopiedCurves_SameHash) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + Handle(Geom_TrimmedCurve) aTrimmed1 = new Geom_TrimmedCurve(aLine, 0.0, 10.0); + Handle(Geom_TrimmedCurve) aTrimmed2 = Handle(Geom_TrimmedCurve)::DownCast(aTrimmed1->Copy()); + + EXPECT_EQ(myHasher(aTrimmed1), myHasher(aTrimmed2)); + EXPECT_TRUE(myHasher(aTrimmed1, aTrimmed2)); +} + +TEST_F(GeomHash_CurveHasherTest, TrimmedCurve_DifferentBounds_DifferentHash) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + Handle(Geom_TrimmedCurve) aTrimmed1 = new Geom_TrimmedCurve(aLine, 0.0, 10.0); + Handle(Geom_TrimmedCurve) aTrimmed2 = new Geom_TrimmedCurve(aLine, 0.0, 20.0); + + EXPECT_NE(myHasher(aTrimmed1), myHasher(aTrimmed2)); + EXPECT_FALSE(myHasher(aTrimmed1, aTrimmed2)); +} + +// ============================================================================ +// OffsetCurve Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, OffsetCurve_CopiedCurves_SameHash) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + gp_Dir aRefDir(0.0, 0.0, 1.0); + Handle(Geom_OffsetCurve) anOffset1 = new Geom_OffsetCurve(aLine, 5.0, aRefDir); + Handle(Geom_OffsetCurve) anOffset2 = Handle(Geom_OffsetCurve)::DownCast(anOffset1->Copy()); + + EXPECT_EQ(myHasher(anOffset1), myHasher(anOffset2)); + EXPECT_TRUE(myHasher(anOffset1, anOffset2)); +} + +TEST_F(GeomHash_CurveHasherTest, OffsetCurve_DifferentOffset_DifferentHash) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + gp_Dir aRefDir(0.0, 0.0, 1.0); + Handle(Geom_OffsetCurve) anOffset1 = new Geom_OffsetCurve(aLine, 5.0, aRefDir); + Handle(Geom_OffsetCurve) anOffset2 = new Geom_OffsetCurve(aLine, 10.0, aRefDir); + + EXPECT_NE(myHasher(anOffset1), myHasher(anOffset2)); + EXPECT_FALSE(myHasher(anOffset1, anOffset2)); +} + +// ============================================================================ +// Cross-type Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, DifferentTypes_DifferentComparison) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Circle) aCircle = new Geom_Circle(anAxis, 5.0); + + EXPECT_FALSE(myHasher(aLine, aCircle)); +} + +TEST_F(GeomHash_CurveHasherTest, NullCurves_HandledCorrectly) +{ + Handle(Geom_Curve) aNullCurve; + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + + EXPECT_EQ(myHasher(aNullCurve), 0u); + EXPECT_TRUE(myHasher(aNullCurve, aNullCurve)); + EXPECT_FALSE(myHasher(aLine, aNullCurve)); +} + +TEST_F(GeomHash_CurveHasherTest, SameObject_Equal) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + EXPECT_TRUE(myHasher(aLine, aLine)); +} + +// ============================================================================ +// Weighted BSpline Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, BSplineCurve_Weighted_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt aPoles(1, 4); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 1.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 1.0, 0.0); + aPoles(4) = gp_Pnt(3.0, 0.0, 0.0); + + TColStd_Array1OfReal aWeights(1, 4); + aWeights(1) = 1.0; + aWeights(2) = 2.0; + aWeights(3) = 2.0; + aWeights(4) = 1.0; + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 4; + aMults(2) = 4; + + Handle(Geom_BSplineCurve) aCurve1 = new Geom_BSplineCurve(aPoles, aWeights, aKnots, aMults, 3); + Handle(Geom_BSplineCurve) aCurve2 = Handle(Geom_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +TEST_F(GeomHash_CurveHasherTest, BSplineCurve_DifferentWeights_DifferentComparison) +{ + TColgp_Array1OfPnt aPoles(1, 4); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 1.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 1.0, 0.0); + aPoles(4) = gp_Pnt(3.0, 0.0, 0.0); + + TColStd_Array1OfReal aWeights1(1, 4); + aWeights1(1) = 1.0; + aWeights1(2) = 2.0; + aWeights1(3) = 2.0; + aWeights1(4) = 1.0; + + TColStd_Array1OfReal aWeights2(1, 4); + aWeights2(1) = 1.0; + aWeights2(2) = 3.0; // Different + aWeights2(3) = 2.0; + aWeights2(4) = 1.0; + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 4; + aMults(2) = 4; + + Handle(Geom_BSplineCurve) aCurve1 = new Geom_BSplineCurve(aPoles, aWeights1, aKnots, aMults, 3); + Handle(Geom_BSplineCurve) aCurve2 = new Geom_BSplineCurve(aPoles, aWeights2, aKnots, aMults, 3); + + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Weighted Bezier Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, BezierCurve_Weighted_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt aPoles(1, 3); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 2.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 0.0, 0.0); + + TColStd_Array1OfReal aWeights(1, 3); + aWeights(1) = 1.0; + aWeights(2) = 2.0; + aWeights(3) = 1.0; + + Handle(Geom_BezierCurve) aCurve1 = new Geom_BezierCurve(aPoles, aWeights); + Handle(Geom_BezierCurve) aCurve2 = Handle(Geom_BezierCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Axis Orientation Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Circle_DifferentAxisOrientation_DifferentHash) +{ + gp_Ax2 anAxis1(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + gp_Ax2 anAxis2(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 1.0, 0.0)); + Handle(Geom_Circle) aCircle1 = new Geom_Circle(anAxis1, 5.0); + Handle(Geom_Circle) aCircle2 = new Geom_Circle(anAxis2, 5.0); + + EXPECT_NE(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_FALSE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(GeomHash_CurveHasherTest, Line_DifferentDirection_DifferentHash) +{ + Handle(Geom_Line) aLine1 = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + Handle(Geom_Line) aLine2 = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 1.0, 0.0)); + + EXPECT_NE(myHasher(aLine1), myHasher(aLine2)); + EXPECT_FALSE(myHasher(aLine1, aLine2)); +} + +// ============================================================================ +// BSpline with Different Knots Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, BSplineCurve_DifferentKnots_DifferentComparison) +{ + TColgp_Array1OfPnt aPoles(1, 4); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 1.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 1.0, 0.0); + aPoles(4) = gp_Pnt(3.0, 0.0, 0.0); + + TColStd_Array1OfReal aKnots1(1, 2); + aKnots1(1) = 0.0; + aKnots1(2) = 1.0; + + TColStd_Array1OfReal aKnots2(1, 2); + aKnots2(1) = 0.0; + aKnots2(2) = 2.0; // Different + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 4; + aMults(2) = 4; + + Handle(Geom_BSplineCurve) aCurve1 = new Geom_BSplineCurve(aPoles, aKnots1, aMults, 3); + Handle(Geom_BSplineCurve) aCurve2 = new Geom_BSplineCurve(aPoles, aKnots2, aMults, 3); + + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Periodic BSpline Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, BSplineCurve_QuadraticBezierEquivalent_CopiedCurves_SameHash) +{ + // Simple quadratic B-spline (Bezier-like, single span) + TColgp_Array1OfPnt aPoles(1, 3); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 2.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 0.0, 0.0); + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 3; + aMults(2) = 3; + + Handle(Geom_BSplineCurve) aCurve1 = new Geom_BSplineCurve(aPoles, aKnots, aMults, 2); + Handle(Geom_BSplineCurve) aCurve2 = Handle(Geom_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Reversed Curve Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Line_Reversed_DifferentComparison) +{ + Handle(Geom_Line) aLine1 = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + Handle(Geom_Line) aLine2 = Handle(Geom_Line)::DownCast(aLine1->Reversed()); + + // Reversed line has opposite direction + EXPECT_FALSE(myHasher(aLine1, aLine2)); +} + +TEST_F(GeomHash_CurveHasherTest, BezierCurve_Reversed_DifferentComparison) +{ + TColgp_Array1OfPnt aPoles(1, 3); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 2.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 0.0, 0.0); + + Handle(Geom_BezierCurve) aCurve1 = new Geom_BezierCurve(aPoles); + Handle(Geom_BezierCurve) aCurve2 = Handle(Geom_BezierCurve)::DownCast(aCurve1->Reversed()); + + // Reversed curve has poles in different order + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Transformed Curve Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Circle_Translated_DifferentHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Circle) aCircle1 = new Geom_Circle(anAxis, 5.0); + Handle(Geom_Circle) aCircle2 = Handle(Geom_Circle)::DownCast(aCircle1->Copy()); + aCircle2->Translate(gp_Vec(1.0, 0.0, 0.0)); + + EXPECT_NE(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_FALSE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(GeomHash_CurveHasherTest, Circle_Scaled_DifferentHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Circle) aCircle1 = new Geom_Circle(anAxis, 5.0); + Handle(Geom_Circle) aCircle2 = Handle(Geom_Circle)::DownCast(aCircle1->Copy()); + aCircle2->Scale(gp_Pnt(0.0, 0.0, 0.0), 2.0); + + EXPECT_NE(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_FALSE(myHasher(aCircle1, aCircle2)); +} + +// ============================================================================ +// Higher Degree BSpline Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, BSplineCurve_HigherDegree_CopiedCurves_SameHash) +{ + TColgp_Array1OfPnt aPoles(1, 6); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 1.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 0.5, 0.0); + aPoles(4) = gp_Pnt(3.0, 1.5, 0.0); + aPoles(5) = gp_Pnt(4.0, 0.0, 0.0); + aPoles(6) = gp_Pnt(5.0, 1.0, 0.0); + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults(1, 2); + aMults(1) = 6; + aMults(2) = 6; + + // Degree 5 curve + Handle(Geom_BSplineCurve) aCurve1 = new Geom_BSplineCurve(aPoles, aKnots, aMults, 5); + Handle(Geom_BSplineCurve) aCurve2 = Handle(Geom_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +TEST_F(GeomHash_CurveHasherTest, BSplineCurve_DifferentDegree_DifferentComparison) +{ + TColgp_Array1OfPnt aPoles(1, 4); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 1.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 1.0, 0.0); + aPoles(4) = gp_Pnt(3.0, 0.0, 0.0); + + TColStd_Array1OfReal aKnots(1, 2); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + + TColStd_Array1OfInteger aMults1(1, 2); + aMults1(1) = 4; + aMults1(2) = 4; + + TColStd_Array1OfInteger aMults2(1, 2); + aMults2(1) = 3; + aMults2(2) = 3; + + // Different degrees: 3 vs 2 + Handle(Geom_BSplineCurve) aCurve1 = new Geom_BSplineCurve(aPoles, aKnots, aMults1, 3); + + TColgp_Array1OfPnt aPoles2(1, 3); + aPoles2(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles2(2) = gp_Pnt(1.5, 1.0, 0.0); + aPoles2(3) = gp_Pnt(3.0, 0.0, 0.0); + + Handle(Geom_BSplineCurve) aCurve2 = new Geom_BSplineCurve(aPoles2, aKnots, aMults2, 2); + + EXPECT_FALSE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Multiple Knot Spans Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, BSplineCurve_LinearMultipleSpans_CopiedCurves_SameHash) +{ + // Linear B-spline with multiple knots (piecewise linear) + TColgp_Array1OfPnt aPoles(1, 4); + aPoles(1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(2) = gp_Pnt(1.0, 1.0, 0.0); + aPoles(3) = gp_Pnt(2.0, 0.0, 0.0); + aPoles(4) = gp_Pnt(3.0, 1.0, 0.0); + + TColStd_Array1OfReal aKnots(1, 4); + aKnots(1) = 0.0; + aKnots(2) = 1.0; + aKnots(3) = 2.0; + aKnots(4) = 3.0; + + TColStd_Array1OfInteger aMults(1, 4); + aMults(1) = 2; + aMults(2) = 1; + aMults(3) = 1; + aMults(4) = 2; + + Handle(Geom_BSplineCurve) aCurve1 = new Geom_BSplineCurve(aPoles, aKnots, aMults, 1); + Handle(Geom_BSplineCurve) aCurve2 = Handle(Geom_BSplineCurve)::DownCast(aCurve1->Copy()); + + EXPECT_EQ(myHasher(aCurve1), myHasher(aCurve2)); + EXPECT_TRUE(myHasher(aCurve1, aCurve2)); +} + +// ============================================================================ +// Cross-type Conic Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Ellipse_vs_Hyperbola_DifferentComparison) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Ellipse) anEllipse = new Geom_Ellipse(anAxis, 5.0, 3.0); + Handle(Geom_Hyperbola) aHyperbola = new Geom_Hyperbola(anAxis, 5.0, 3.0); + + EXPECT_FALSE(myHasher(anEllipse, aHyperbola)); +} + +TEST_F(GeomHash_CurveHasherTest, Circle_vs_Ellipse_DifferentComparison) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Circle) aCircle = new Geom_Circle(anAxis, 5.0); + Handle(Geom_Ellipse) anEllipse = new Geom_Ellipse(anAxis, 5.0, 5.0); + + EXPECT_FALSE(myHasher(aCircle, anEllipse)); +} + +// ============================================================================ +// Trimmed vs Base Curve Tests +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, TrimmedCurve_vs_BaseCurve_DifferentComparison) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + Handle(Geom_TrimmedCurve) aTrimmed = new Geom_TrimmedCurve(aLine, 0.0, 10.0); + + // Trimmed curve is a different type from base curve + EXPECT_FALSE(myHasher(aLine, aTrimmed)); +} + +// ============================================================================ +// Edge Cases - Degenerate/Special Values +// ============================================================================ + +TEST_F(GeomHash_CurveHasherTest, Circle_VerySmallRadius_CopiedCircles_SameHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Circle) aCircle1 = new Geom_Circle(anAxis, 1e-10); + Handle(Geom_Circle) aCircle2 = Handle(Geom_Circle)::DownCast(aCircle1->Copy()); + + EXPECT_EQ(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_TRUE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(GeomHash_CurveHasherTest, Circle_VeryLargeRadius_CopiedCircles_SameHash) +{ + gp_Ax2 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Circle) aCircle1 = new Geom_Circle(anAxis, 1e10); + Handle(Geom_Circle) aCircle2 = Handle(Geom_Circle)::DownCast(aCircle1->Copy()); + + EXPECT_EQ(myHasher(aCircle1), myHasher(aCircle2)); + EXPECT_TRUE(myHasher(aCircle1, aCircle2)); +} + +TEST_F(GeomHash_CurveHasherTest, Line_AtOrigin_vs_FarFromOrigin_DifferentHash) +{ + Handle(Geom_Line) aLine1 = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + Handle(Geom_Line) aLine2 = new Geom_Line(gp_Pnt(1e10, 1e10, 1e10), gp_Dir(1.0, 0.0, 0.0)); + + EXPECT_NE(myHasher(aLine1), myHasher(aLine2)); + EXPECT_FALSE(myHasher(aLine1, aLine2)); +} diff --git a/src/ModelingData/TKG3d/GTests/GeomHash_SurfaceHasher_Test.cxx b/src/ModelingData/TKG3d/GTests/GeomHash_SurfaceHasher_Test.cxx new file mode 100644 index 0000000000..3f7570760f --- /dev/null +++ b/src/ModelingData/TKG3d/GTests/GeomHash_SurfaceHasher_Test.cxx @@ -0,0 +1,749 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class GeomHash_SurfaceHasherTest : public ::testing::Test +{ +protected: + GeomHash_SurfaceHasher myHasher; +}; + +// ============================================================================ +// Plane Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, Plane_CopiedPlanes_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane1 = new Geom_Plane(anAxis); + Handle(Geom_Plane) aPlane2 = Handle(Geom_Plane)::DownCast(aPlane1->Copy()); + + EXPECT_EQ(myHasher(aPlane1), myHasher(aPlane2)); + EXPECT_TRUE(myHasher(aPlane1, aPlane2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, Plane_DifferentPlanes_DifferentHash) +{ + Handle(Geom_Plane) aPlane1 = new Geom_Plane(gp_Ax3(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0))); + Handle(Geom_Plane) aPlane2 = new Geom_Plane(gp_Ax3(gp_Pnt(0.0, 0.0, 1.0), gp_Dir(0.0, 0.0, 1.0))); + + EXPECT_NE(myHasher(aPlane1), myHasher(aPlane2)); + EXPECT_FALSE(myHasher(aPlane1, aPlane2)); +} + +// ============================================================================ +// Cylindrical Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, CylindricalSurface_CopiedSurfaces_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_CylindricalSurface) aCyl1 = new Geom_CylindricalSurface(anAxis, 5.0); + Handle(Geom_CylindricalSurface) aCyl2 = Handle(Geom_CylindricalSurface)::DownCast(aCyl1->Copy()); + + EXPECT_EQ(myHasher(aCyl1), myHasher(aCyl2)); + EXPECT_TRUE(myHasher(aCyl1, aCyl2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, CylindricalSurface_DifferentRadius_DifferentHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_CylindricalSurface) aCyl1 = new Geom_CylindricalSurface(anAxis, 5.0); + Handle(Geom_CylindricalSurface) aCyl2 = new Geom_CylindricalSurface(anAxis, 10.0); + + EXPECT_NE(myHasher(aCyl1), myHasher(aCyl2)); + EXPECT_FALSE(myHasher(aCyl1, aCyl2)); +} + +// ============================================================================ +// Conical Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, ConicalSurface_CopiedSurfaces_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_ConicalSurface) aCone1 = new Geom_ConicalSurface(anAxis, M_PI / 6.0, 5.0); + Handle(Geom_ConicalSurface) aCone2 = Handle(Geom_ConicalSurface)::DownCast(aCone1->Copy()); + + EXPECT_EQ(myHasher(aCone1), myHasher(aCone2)); + EXPECT_TRUE(myHasher(aCone1, aCone2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, ConicalSurface_DifferentAngle_DifferentHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_ConicalSurface) aCone1 = new Geom_ConicalSurface(anAxis, M_PI / 6.0, 5.0); + Handle(Geom_ConicalSurface) aCone2 = new Geom_ConicalSurface(anAxis, M_PI / 4.0, 5.0); + + EXPECT_NE(myHasher(aCone1), myHasher(aCone2)); + EXPECT_FALSE(myHasher(aCone1, aCone2)); +} + +// ============================================================================ +// Spherical Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, SphericalSurface_CopiedSurfaces_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SphericalSurface) aSphere1 = new Geom_SphericalSurface(anAxis, 5.0); + Handle(Geom_SphericalSurface) aSphere2 = + Handle(Geom_SphericalSurface)::DownCast(aSphere1->Copy()); + + EXPECT_EQ(myHasher(aSphere1), myHasher(aSphere2)); + EXPECT_TRUE(myHasher(aSphere1, aSphere2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, SphericalSurface_DifferentRadius_DifferentHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SphericalSurface) aSphere1 = new Geom_SphericalSurface(anAxis, 5.0); + Handle(Geom_SphericalSurface) aSphere2 = new Geom_SphericalSurface(anAxis, 10.0); + + EXPECT_NE(myHasher(aSphere1), myHasher(aSphere2)); + EXPECT_FALSE(myHasher(aSphere1, aSphere2)); +} + +// ============================================================================ +// Toroidal Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, ToroidalSurface_CopiedSurfaces_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_ToroidalSurface) aTorus1 = new Geom_ToroidalSurface(anAxis, 10.0, 3.0); + Handle(Geom_ToroidalSurface) aTorus2 = Handle(Geom_ToroidalSurface)::DownCast(aTorus1->Copy()); + + EXPECT_EQ(myHasher(aTorus1), myHasher(aTorus2)); + EXPECT_TRUE(myHasher(aTorus1, aTorus2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, ToroidalSurface_DifferentRadii_DifferentHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_ToroidalSurface) aTorus1 = new Geom_ToroidalSurface(anAxis, 10.0, 3.0); + Handle(Geom_ToroidalSurface) aTorus2 = new Geom_ToroidalSurface(anAxis, 10.0, 5.0); + + EXPECT_NE(myHasher(aTorus1), myHasher(aTorus2)); + EXPECT_FALSE(myHasher(aTorus1, aTorus2)); +} + +// ============================================================================ +// BezierSurface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, BezierSurface_CopiedSurfaces_SameHash) +{ + TColgp_Array2OfPnt aPoles(1, 2, 1, 2); + aPoles(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + Handle(Geom_BezierSurface) aSurf1 = new Geom_BezierSurface(aPoles); + Handle(Geom_BezierSurface) aSurf2 = Handle(Geom_BezierSurface)::DownCast(aSurf1->Copy()); + + EXPECT_EQ(myHasher(aSurf1), myHasher(aSurf2)); + EXPECT_TRUE(myHasher(aSurf1, aSurf2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, BezierSurface_DifferentPoles_DifferentComparison) +{ + TColgp_Array2OfPnt aPoles1(1, 2, 1, 2); + aPoles1(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles1(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles1(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles1(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + TColgp_Array2OfPnt aPoles2(1, 2, 1, 2); + aPoles2(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles2(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles2(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles2(2, 2) = gp_Pnt(1.0, 1.0, 2.0); // Different + + Handle(Geom_BezierSurface) aSurf1 = new Geom_BezierSurface(aPoles1); + Handle(Geom_BezierSurface) aSurf2 = new Geom_BezierSurface(aPoles2); + + // Hash may be same (metadata only), but comparison should differ + EXPECT_FALSE(myHasher(aSurf1, aSurf2)); +} + +// ============================================================================ +// BSplineSurface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, BSplineSurface_CopiedSurfaces_SameHash) +{ + TColgp_Array2OfPnt aPoles(1, 2, 1, 2); + aPoles(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + TColStd_Array1OfReal aUKnots(1, 2), aVKnots(1, 2); + aUKnots(1) = 0.0; + aUKnots(2) = 1.0; + aVKnots(1) = 0.0; + aVKnots(2) = 1.0; + + TColStd_Array1OfInteger aUMults(1, 2), aVMults(1, 2); + aUMults(1) = 2; + aUMults(2) = 2; + aVMults(1) = 2; + aVMults(2) = 2; + + Handle(Geom_BSplineSurface) aSurf1 = + new Geom_BSplineSurface(aPoles, aUKnots, aVKnots, aUMults, aVMults, 1, 1); + Handle(Geom_BSplineSurface) aSurf2 = Handle(Geom_BSplineSurface)::DownCast(aSurf1->Copy()); + + EXPECT_EQ(myHasher(aSurf1), myHasher(aSurf2)); + EXPECT_TRUE(myHasher(aSurf1, aSurf2)); +} + +// ============================================================================ +// SurfaceOfRevolution Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, SurfaceOfRevolution_CopiedSurfaces_SameHash) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(1.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + gp_Ax1 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SurfaceOfRevolution) aRev1 = new Geom_SurfaceOfRevolution(aLine, anAxis); + Handle(Geom_SurfaceOfRevolution) aRev2 = + Handle(Geom_SurfaceOfRevolution)::DownCast(aRev1->Copy()); + + EXPECT_EQ(myHasher(aRev1), myHasher(aRev2)); + EXPECT_TRUE(myHasher(aRev1, aRev2)); +} + +// ============================================================================ +// SurfaceOfLinearExtrusion Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, SurfaceOfLinearExtrusion_CopiedSurfaces_SameHash) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + gp_Dir aDir(0.0, 0.0, 1.0); + Handle(Geom_SurfaceOfLinearExtrusion) anExt1 = new Geom_SurfaceOfLinearExtrusion(aLine, aDir); + Handle(Geom_SurfaceOfLinearExtrusion) anExt2 = + Handle(Geom_SurfaceOfLinearExtrusion)::DownCast(anExt1->Copy()); + + EXPECT_EQ(myHasher(anExt1), myHasher(anExt2)); + EXPECT_TRUE(myHasher(anExt1, anExt2)); +} + +// ============================================================================ +// RectangularTrimmedSurface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, RectangularTrimmedSurface_CopiedSurfaces_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane = new Geom_Plane(anAxis); + Handle(Geom_RectangularTrimmedSurface) aTrimmed1 = + new Geom_RectangularTrimmedSurface(aPlane, 0.0, 10.0, 0.0, 10.0); + Handle(Geom_RectangularTrimmedSurface) aTrimmed2 = + Handle(Geom_RectangularTrimmedSurface)::DownCast(aTrimmed1->Copy()); + + EXPECT_EQ(myHasher(aTrimmed1), myHasher(aTrimmed2)); + EXPECT_TRUE(myHasher(aTrimmed1, aTrimmed2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, RectangularTrimmedSurface_DifferentBounds_DifferentHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane = new Geom_Plane(anAxis); + Handle(Geom_RectangularTrimmedSurface) aTrimmed1 = + new Geom_RectangularTrimmedSurface(aPlane, 0.0, 10.0, 0.0, 10.0); + Handle(Geom_RectangularTrimmedSurface) aTrimmed2 = + new Geom_RectangularTrimmedSurface(aPlane, 0.0, 20.0, 0.0, 10.0); + + EXPECT_NE(myHasher(aTrimmed1), myHasher(aTrimmed2)); + EXPECT_FALSE(myHasher(aTrimmed1, aTrimmed2)); +} + +// ============================================================================ +// OffsetSurface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, OffsetSurface_CopiedSurfaces_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane = new Geom_Plane(anAxis); + Handle(Geom_OffsetSurface) anOffset1 = new Geom_OffsetSurface(aPlane, 5.0); + Handle(Geom_OffsetSurface) anOffset2 = Handle(Geom_OffsetSurface)::DownCast(anOffset1->Copy()); + + EXPECT_EQ(myHasher(anOffset1), myHasher(anOffset2)); + EXPECT_TRUE(myHasher(anOffset1, anOffset2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, OffsetSurface_DifferentOffset_DifferentHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane = new Geom_Plane(anAxis); + Handle(Geom_OffsetSurface) anOffset1 = new Geom_OffsetSurface(aPlane, 5.0); + Handle(Geom_OffsetSurface) anOffset2 = new Geom_OffsetSurface(aPlane, 10.0); + + EXPECT_NE(myHasher(anOffset1), myHasher(anOffset2)); + EXPECT_FALSE(myHasher(anOffset1, anOffset2)); +} + +// ============================================================================ +// Cross-type Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, DifferentTypes_DifferentComparison) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane = new Geom_Plane(anAxis); + Handle(Geom_SphericalSurface) aSphere = new Geom_SphericalSurface(anAxis, 5.0); + + EXPECT_FALSE(myHasher(aPlane, aSphere)); +} + +TEST_F(GeomHash_SurfaceHasherTest, NullSurfaces_HandledCorrectly) +{ + Handle(Geom_Surface) aNullSurface; + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane = new Geom_Plane(anAxis); + + EXPECT_EQ(myHasher(aNullSurface), 0u); + EXPECT_TRUE(myHasher(aNullSurface, aNullSurface)); + EXPECT_FALSE(myHasher(aPlane, aNullSurface)); +} + +TEST_F(GeomHash_SurfaceHasherTest, SameObject_Equal) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane = new Geom_Plane(anAxis); + EXPECT_TRUE(myHasher(aPlane, aPlane)); +} + +// ============================================================================ +// Weighted BSpline Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, BSplineSurface_Weighted_CopiedSurfaces_SameHash) +{ + TColgp_Array2OfPnt aPoles(1, 2, 1, 2); + aPoles(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + TColStd_Array2OfReal aWeights(1, 2, 1, 2); + aWeights(1, 1) = 1.0; + aWeights(1, 2) = 1.0; + aWeights(2, 1) = 1.0; + aWeights(2, 2) = 2.0; + + TColStd_Array1OfReal aUKnots(1, 2), aVKnots(1, 2); + aUKnots(1) = 0.0; + aUKnots(2) = 1.0; + aVKnots(1) = 0.0; + aVKnots(2) = 1.0; + + TColStd_Array1OfInteger aUMults(1, 2), aVMults(1, 2); + aUMults(1) = 2; + aUMults(2) = 2; + aVMults(1) = 2; + aVMults(2) = 2; + + Handle(Geom_BSplineSurface) aSurf1 = + new Geom_BSplineSurface(aPoles, aWeights, aUKnots, aVKnots, aUMults, aVMults, 1, 1); + Handle(Geom_BSplineSurface) aSurf2 = Handle(Geom_BSplineSurface)::DownCast(aSurf1->Copy()); + + EXPECT_EQ(myHasher(aSurf1), myHasher(aSurf2)); + EXPECT_TRUE(myHasher(aSurf1, aSurf2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, BSplineSurface_DifferentWeights_DifferentComparison) +{ + TColgp_Array2OfPnt aPoles(1, 2, 1, 2); + aPoles(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + TColStd_Array2OfReal aWeights1(1, 2, 1, 2); + aWeights1(1, 1) = 1.0; + aWeights1(1, 2) = 1.0; + aWeights1(2, 1) = 1.0; + aWeights1(2, 2) = 2.0; + + TColStd_Array2OfReal aWeights2(1, 2, 1, 2); + aWeights2(1, 1) = 1.0; + aWeights2(1, 2) = 1.0; + aWeights2(2, 1) = 1.0; + aWeights2(2, 2) = 3.0; // Different + + TColStd_Array1OfReal aUKnots(1, 2), aVKnots(1, 2); + aUKnots(1) = 0.0; + aUKnots(2) = 1.0; + aVKnots(1) = 0.0; + aVKnots(2) = 1.0; + + TColStd_Array1OfInteger aUMults(1, 2), aVMults(1, 2); + aUMults(1) = 2; + aUMults(2) = 2; + aVMults(1) = 2; + aVMults(2) = 2; + + Handle(Geom_BSplineSurface) aSurf1 = + new Geom_BSplineSurface(aPoles, aWeights1, aUKnots, aVKnots, aUMults, aVMults, 1, 1); + Handle(Geom_BSplineSurface) aSurf2 = + new Geom_BSplineSurface(aPoles, aWeights2, aUKnots, aVKnots, aUMults, aVMults, 1, 1); + + EXPECT_FALSE(myHasher(aSurf1, aSurf2)); +} + +// ============================================================================ +// Weighted Bezier Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, BezierSurface_Weighted_CopiedSurfaces_SameHash) +{ + TColgp_Array2OfPnt aPoles(1, 2, 1, 2); + aPoles(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + TColStd_Array2OfReal aWeights(1, 2, 1, 2); + aWeights(1, 1) = 1.0; + aWeights(1, 2) = 1.0; + aWeights(2, 1) = 1.0; + aWeights(2, 2) = 2.0; + + Handle(Geom_BezierSurface) aSurf1 = new Geom_BezierSurface(aPoles, aWeights); + Handle(Geom_BezierSurface) aSurf2 = Handle(Geom_BezierSurface)::DownCast(aSurf1->Copy()); + + EXPECT_EQ(myHasher(aSurf1), myHasher(aSurf2)); + EXPECT_TRUE(myHasher(aSurf1, aSurf2)); +} + +// ============================================================================ +// Axis Orientation Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, Plane_DifferentNormal_DifferentHash) +{ + gp_Ax3 anAxis1(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + gp_Ax3 anAxis2(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 1.0, 0.0)); + Handle(Geom_Plane) aPlane1 = new Geom_Plane(anAxis1); + Handle(Geom_Plane) aPlane2 = new Geom_Plane(anAxis2); + + EXPECT_NE(myHasher(aPlane1), myHasher(aPlane2)); + EXPECT_FALSE(myHasher(aPlane1, aPlane2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, CylindricalSurface_DifferentAxis_DifferentHash) +{ + gp_Ax3 anAxis1(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + gp_Ax3 anAxis2(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + Handle(Geom_CylindricalSurface) aCyl1 = new Geom_CylindricalSurface(anAxis1, 5.0); + Handle(Geom_CylindricalSurface) aCyl2 = new Geom_CylindricalSurface(anAxis2, 5.0); + + EXPECT_NE(myHasher(aCyl1), myHasher(aCyl2)); + EXPECT_FALSE(myHasher(aCyl1, aCyl2)); +} + +// ============================================================================ +// Revolution with Different Basis Curve Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, SurfaceOfRevolution_DifferentBasisCurve_DifferentComparison) +{ + Handle(Geom_Line) aLine1 = new Geom_Line(gp_Pnt(1.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Line) aLine2 = new Geom_Line(gp_Pnt(2.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + gp_Ax1 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SurfaceOfRevolution) aRev1 = new Geom_SurfaceOfRevolution(aLine1, anAxis); + Handle(Geom_SurfaceOfRevolution) aRev2 = new Geom_SurfaceOfRevolution(aLine2, anAxis); + + EXPECT_FALSE(myHasher(aRev1, aRev2)); +} + +// ============================================================================ +// Extrusion with Different Direction Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, SurfaceOfLinearExtrusion_DifferentDirection_DifferentHash) +{ + Handle(Geom_Line) aLine = new Geom_Line(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(1.0, 0.0, 0.0)); + gp_Dir aDir1(0.0, 0.0, 1.0); + gp_Dir aDir2(0.0, 1.0, 0.0); + Handle(Geom_SurfaceOfLinearExtrusion) anExt1 = new Geom_SurfaceOfLinearExtrusion(aLine, aDir1); + Handle(Geom_SurfaceOfLinearExtrusion) anExt2 = new Geom_SurfaceOfLinearExtrusion(aLine, aDir2); + + EXPECT_NE(myHasher(anExt1), myHasher(anExt2)); + EXPECT_FALSE(myHasher(anExt1, anExt2)); +} + +// ============================================================================ +// BSpline Surface with Different Knots Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, BSplineSurface_DifferentKnots_DifferentComparison) +{ + TColgp_Array2OfPnt aPoles(1, 2, 1, 2); + aPoles(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + TColStd_Array1OfReal aUKnots1(1, 2), aUKnots2(1, 2), aVKnots(1, 2); + aUKnots1(1) = 0.0; + aUKnots1(2) = 1.0; + aUKnots2(1) = 0.0; + aUKnots2(2) = 2.0; // Different + aVKnots(1) = 0.0; + aVKnots(2) = 1.0; + + TColStd_Array1OfInteger aUMults(1, 2), aVMults(1, 2); + aUMults(1) = 2; + aUMults(2) = 2; + aVMults(1) = 2; + aVMults(2) = 2; + + Handle(Geom_BSplineSurface) aSurf1 = + new Geom_BSplineSurface(aPoles, aUKnots1, aVKnots, aUMults, aVMults, 1, 1); + Handle(Geom_BSplineSurface) aSurf2 = + new Geom_BSplineSurface(aPoles, aUKnots2, aVKnots, aUMults, aVMults, 1, 1); + + EXPECT_FALSE(myHasher(aSurf1, aSurf2)); +} + +// ============================================================================ +// Transformed Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, Plane_Translated_DifferentHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane1 = new Geom_Plane(anAxis); + Handle(Geom_Plane) aPlane2 = Handle(Geom_Plane)::DownCast(aPlane1->Copy()); + aPlane2->Translate(gp_Vec(0.0, 0.0, 1.0)); + + EXPECT_NE(myHasher(aPlane1), myHasher(aPlane2)); + EXPECT_FALSE(myHasher(aPlane1, aPlane2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, Sphere_Scaled_DifferentHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SphericalSurface) aSphere1 = new Geom_SphericalSurface(anAxis, 5.0); + Handle(Geom_SphericalSurface) aSphere2 = + Handle(Geom_SphericalSurface)::DownCast(aSphere1->Copy()); + aSphere2->Scale(gp_Pnt(0.0, 0.0, 0.0), 2.0); + + EXPECT_NE(myHasher(aSphere1), myHasher(aSphere2)); + EXPECT_FALSE(myHasher(aSphere1, aSphere2)); +} + +// ============================================================================ +// UReversed/VReversed Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, BezierSurface_UReversed_DifferentComparison) +{ + TColgp_Array2OfPnt aPoles(1, 2, 1, 2); + aPoles(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + Handle(Geom_BezierSurface) aSurf1 = new Geom_BezierSurface(aPoles); + Handle(Geom_BezierSurface) aSurf2 = Handle(Geom_BezierSurface)::DownCast(aSurf1->UReversed()); + + EXPECT_FALSE(myHasher(aSurf1, aSurf2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, BezierSurface_VReversed_DifferentComparison) +{ + TColgp_Array2OfPnt aPoles(1, 2, 1, 2); + aPoles(1, 1) = gp_Pnt(0.0, 0.0, 0.0); + aPoles(1, 2) = gp_Pnt(1.0, 0.0, 0.0); + aPoles(2, 1) = gp_Pnt(0.0, 1.0, 0.0); + aPoles(2, 2) = gp_Pnt(1.0, 1.0, 1.0); + + Handle(Geom_BezierSurface) aSurf1 = new Geom_BezierSurface(aPoles); + Handle(Geom_BezierSurface) aSurf2 = Handle(Geom_BezierSurface)::DownCast(aSurf1->VReversed()); + + EXPECT_FALSE(myHasher(aSurf1, aSurf2)); +} + +// ============================================================================ +// Higher Degree BSpline Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, BSplineSurface_HigherDegree_CopiedSurfaces_SameHash) +{ + TColgp_Array2OfPnt aPoles(1, 4, 1, 4); + for (Standard_Integer i = 1; i <= 4; ++i) + { + for (Standard_Integer j = 1; j <= 4; ++j) + { + aPoles(i, j) = gp_Pnt((i - 1) * 1.0, (j - 1) * 1.0, (i + j) * 0.1); + } + } + + TColStd_Array1OfReal aUKnots(1, 2), aVKnots(1, 2); + aUKnots(1) = 0.0; + aUKnots(2) = 1.0; + aVKnots(1) = 0.0; + aVKnots(2) = 1.0; + + TColStd_Array1OfInteger aUMults(1, 2), aVMults(1, 2); + aUMults(1) = 4; + aUMults(2) = 4; + aVMults(1) = 4; + aVMults(2) = 4; + + Handle(Geom_BSplineSurface) aSurf1 = + new Geom_BSplineSurface(aPoles, aUKnots, aVKnots, aUMults, aVMults, 3, 3); + Handle(Geom_BSplineSurface) aSurf2 = Handle(Geom_BSplineSurface)::DownCast(aSurf1->Copy()); + + EXPECT_EQ(myHasher(aSurf1), myHasher(aSurf2)); + EXPECT_TRUE(myHasher(aSurf1, aSurf2)); +} + +// ============================================================================ +// Cross-type Elementary Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, Cylinder_vs_Cone_DifferentComparison) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_CylindricalSurface) aCylinder = new Geom_CylindricalSurface(anAxis, 5.0); + Handle(Geom_ConicalSurface) aCone = new Geom_ConicalSurface(anAxis, M_PI / 6.0, 5.0); + + EXPECT_FALSE(myHasher(aCylinder, aCone)); +} + +TEST_F(GeomHash_SurfaceHasherTest, Sphere_vs_Torus_DifferentComparison) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SphericalSurface) aSphere = new Geom_SphericalSurface(anAxis, 5.0); + Handle(Geom_ToroidalSurface) aTorus = new Geom_ToroidalSurface(anAxis, 5.0, 1.0); + + EXPECT_FALSE(myHasher(aSphere, aTorus)); +} + +// ============================================================================ +// Trimmed vs Base Surface Tests +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, RectangularTrimmedSurface_vs_BaseSurface_DifferentComparison) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane = new Geom_Plane(anAxis); + Handle(Geom_RectangularTrimmedSurface) aTrimmed = + new Geom_RectangularTrimmedSurface(aPlane, 0.0, 10.0, 0.0, 10.0); + + EXPECT_FALSE(myHasher(aPlane, aTrimmed)); +} + +// ============================================================================ +// Edge Cases - Degenerate/Special Values +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, Sphere_VerySmallRadius_CopiedSpheres_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SphericalSurface) aSphere1 = new Geom_SphericalSurface(anAxis, 1e-10); + Handle(Geom_SphericalSurface) aSphere2 = + Handle(Geom_SphericalSurface)::DownCast(aSphere1->Copy()); + + EXPECT_EQ(myHasher(aSphere1), myHasher(aSphere2)); + EXPECT_TRUE(myHasher(aSphere1, aSphere2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, Sphere_VeryLargeRadius_CopiedSpheres_SameHash) +{ + gp_Ax3 anAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SphericalSurface) aSphere1 = new Geom_SphericalSurface(anAxis, 1e10); + Handle(Geom_SphericalSurface) aSphere2 = + Handle(Geom_SphericalSurface)::DownCast(aSphere1->Copy()); + + EXPECT_EQ(myHasher(aSphere1), myHasher(aSphere2)); + EXPECT_TRUE(myHasher(aSphere1, aSphere2)); +} + +TEST_F(GeomHash_SurfaceHasherTest, Plane_AtOrigin_vs_FarFromOrigin_DifferentHash) +{ + gp_Ax3 anAxis1(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + gp_Ax3 anAxis2(gp_Pnt(1e10, 1e10, 1e10), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane1 = new Geom_Plane(anAxis1); + Handle(Geom_Plane) aPlane2 = new Geom_Plane(anAxis2); + + EXPECT_NE(myHasher(aPlane1), myHasher(aPlane2)); + EXPECT_FALSE(myHasher(aPlane1, aPlane2)); +} + +// ============================================================================ +// Revolution with Circle Basis (forms Torus-like) +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, SurfaceOfRevolution_CircleBasis_CopiedSurfaces_SameHash) +{ + gp_Ax2 aCircleAxis(gp_Pnt(5.0, 0.0, 0.0), gp_Dir(0.0, 1.0, 0.0)); + Handle(Geom_Circle) aCircle = new Geom_Circle(aCircleAxis, 1.0); + gp_Ax1 aRevAxis(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_SurfaceOfRevolution) aRev1 = new Geom_SurfaceOfRevolution(aCircle, aRevAxis); + Handle(Geom_SurfaceOfRevolution) aRev2 = + Handle(Geom_SurfaceOfRevolution)::DownCast(aRev1->Copy()); + + EXPECT_EQ(myHasher(aRev1), myHasher(aRev2)); + EXPECT_TRUE(myHasher(aRev1, aRev2)); +} + +// ============================================================================ +// Offset Surface with Different Base +// ============================================================================ + +TEST_F(GeomHash_SurfaceHasherTest, OffsetSurface_DifferentBaseSurface_DifferentComparison) +{ + gp_Ax3 anAxis1(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + gp_Ax3 anAxis2(gp_Pnt(1.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)); + Handle(Geom_Plane) aPlane1 = new Geom_Plane(anAxis1); + Handle(Geom_Plane) aPlane2 = new Geom_Plane(anAxis2); + Handle(Geom_OffsetSurface) anOffset1 = new Geom_OffsetSurface(aPlane1, 5.0); + Handle(Geom_OffsetSurface) anOffset2 = new Geom_OffsetSurface(aPlane2, 5.0); + + EXPECT_FALSE(myHasher(anOffset1, anOffset2)); +} diff --git a/src/ModelingData/TKG3d/GeomHash/FILES.cmake b/src/ModelingData/TKG3d/GeomHash/FILES.cmake new file mode 100644 index 0000000000..65744901b4 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/FILES.cmake @@ -0,0 +1,38 @@ +# Source files for GeomHash package +set(OCCT_GeomHash_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}") + +set(OCCT_GeomHash_FILES + # Foundational Hashers + GeomHash_PointHasher.pxx + GeomHash_DirectionHasher.pxx + GeomHash_VectorHasher.pxx + GeomHash_AxisPlacement.pxx + + # Surface Hashers + GeomHash_PlaneHasher.pxx + GeomHash_CylindricalSurfaceHasher.pxx + GeomHash_ConicalSurfaceHasher.pxx + GeomHash_SphericalSurfaceHasher.pxx + GeomHash_ToroidalSurfaceHasher.pxx + GeomHash_SurfaceOfRevolutionHasher.pxx + GeomHash_SurfaceOfLinearExtrusionHasher.pxx + GeomHash_BezierSurfaceHasher.pxx + GeomHash_BSplineSurfaceHasher.pxx + GeomHash_RectangularTrimmedSurfaceHasher.pxx + GeomHash_OffsetSurfaceHasher.pxx + GeomHash_SurfaceHasher.hxx + GeomHash_SurfaceHasher.cxx + + # Curve Hashers + GeomHash_LineHasher.pxx + GeomHash_CircleHasher.pxx + GeomHash_EllipseHasher.pxx + GeomHash_HyperbolaHasher.pxx + GeomHash_ParabolaHasher.pxx + GeomHash_BezierCurveHasher.pxx + GeomHash_BSplineCurveHasher.pxx + GeomHash_TrimmedCurveHasher.pxx + GeomHash_OffsetCurveHasher.pxx + GeomHash_CurveHasher.hxx + GeomHash_CurveHasher.cxx +) diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_AxisPlacement.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_AxisPlacement.pxx new file mode 100644 index 0000000000..c7174b0b1b --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_AxisPlacement.pxx @@ -0,0 +1,52 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_AxisPlacement_HeaderFile +#define _GeomHash_AxisPlacement_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for gp_Ax2 (axis placement). +//! Used for geometry deduplication. +//! Compositional hasher using PointHasher and DirectionHasher. +struct GeomHash_AxisPlacement +{ + // Hashes the axis placement by location, axis direction, and X direction. + std::size_t operator()(const gp_Ax2& theAxisPlacement) const noexcept + { + const GeomHash_PointHasher aPointHasher; + const GeomHash_DirectionHasher aDirectionHasher; + + const std::size_t aHashes[3] = {aPointHasher(theAxisPlacement.Location()), + aDirectionHasher(theAxisPlacement.Direction()), + aDirectionHasher(theAxisPlacement.XDirection())}; + + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two axis placements. + bool operator()(const gp_Ax2& theAxisPlacement1, const gp_Ax2& theAxisPlacement2) const noexcept + { + const GeomHash_PointHasher aPointHasher; + const GeomHash_DirectionHasher aDirectionHasher; + + return aPointHasher(theAxisPlacement1.Location(), theAxisPlacement2.Location()) + && aDirectionHasher(theAxisPlacement1.Direction(), theAxisPlacement2.Direction()) + && aDirectionHasher(theAxisPlacement1.XDirection(), theAxisPlacement2.XDirection()); + } +}; + +#endif // _GeomHash_AxisPlacement_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_BSplineCurveHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_BSplineCurveHasher.pxx new file mode 100644 index 0000000000..01f6948fc2 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_BSplineCurveHasher.pxx @@ -0,0 +1,98 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_BSplineCurveHasher_HeaderFile +#define _GeomHash_BSplineCurveHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_BSplineCurve (3D B-spline curve). +//! Used for geometry deduplication. +//! Hashes only metadata (degree, pole count, knot count, rationality) for efficiency. +struct GeomHash_BSplineCurveHasher +{ + // Hashes the B-spline curve metadata only. + std::size_t operator()(const Handle(Geom_BSplineCurve)& theCurve) const noexcept + { + const std::size_t aHashes[4] = {opencascade::hash(theCurve->Degree()), + opencascade::hash(theCurve->NbPoles()), + opencascade::hash(theCurve->NbKnots()), + opencascade::hash(static_cast(theCurve->IsRational()))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two B-spline curves by full geometric data. + bool operator()(const Handle(Geom_BSplineCurve)& theCurve1, + const Handle(Geom_BSplineCurve)& theCurve2) const noexcept + { + constexpr double aTolerance = 1e-12; + + // Compare degrees + if (theCurve1->Degree() != theCurve2->Degree()) + { + return false; + } + + // Compare knot counts + if (theCurve1->NbKnots() != theCurve2->NbKnots()) + { + return false; + } + + // Compare knots and multiplicities + for (int i = 1; i <= theCurve1->NbKnots(); ++i) + { + if (std::abs(theCurve1->Knot(i) - theCurve2->Knot(i)) > aTolerance + || theCurve1->Multiplicity(i) != theCurve2->Multiplicity(i)) + { + return false; + } + } + + // Compare rationality + if (theCurve1->IsRational() != theCurve2->IsRational()) + { + return false; + } + + const GeomHash_PointHasher aPointHasher; + + // Compare poles + for (int i = 1; i <= theCurve1->NbPoles(); ++i) + { + if (!aPointHasher(theCurve1->Pole(i), theCurve2->Pole(i))) + { + return false; + } + } + + // Compare weights if rational + if (theCurve1->IsRational()) + { + for (int i = 1; i <= theCurve1->NbPoles(); ++i) + { + if (std::abs(theCurve1->Weight(i) - theCurve2->Weight(i)) > aTolerance) + { + return false; + } + } + } + + return true; + } +}; + +#endif // _GeomHash_BSplineCurveHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_BSplineSurfaceHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_BSplineSurfaceHasher.pxx new file mode 100644 index 0000000000..8b8d94b54e --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_BSplineSurfaceHasher.pxx @@ -0,0 +1,122 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_BSplineSurfaceHasher_HeaderFile +#define _GeomHash_BSplineSurfaceHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_BSplineSurface. +//! Used for geometry deduplication. +//! Hashes only metadata (degrees, pole counts, knot counts, rationality) for efficiency. +struct GeomHash_BSplineSurfaceHasher +{ + // Hashes the B-spline surface metadata only. + std::size_t operator()(const Handle(Geom_BSplineSurface)& theSurface) const noexcept + { + const std::size_t aHashes[7] = { + opencascade::hash(theSurface->UDegree()), + opencascade::hash(theSurface->VDegree()), + opencascade::hash(theSurface->NbUPoles()), + opencascade::hash(theSurface->NbVPoles()), + opencascade::hash(theSurface->NbUKnots()), + opencascade::hash(theSurface->NbVKnots()), + opencascade::hash(static_cast(theSurface->IsURational()) + | (static_cast(theSurface->IsVRational()) << 1))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two B-spline surfaces by full geometric data. + bool operator()(const Handle(Geom_BSplineSurface)& theSurface1, + const Handle(Geom_BSplineSurface)& theSurface2) const noexcept + { + constexpr double aTolerance = 1e-12; + + // Compare degrees + if (theSurface1->UDegree() != theSurface2->UDegree() + || theSurface1->VDegree() != theSurface2->VDegree()) + { + return false; + } + + // Compare knot counts + if (theSurface1->NbUKnots() != theSurface2->NbUKnots() + || theSurface1->NbVKnots() != theSurface2->NbVKnots()) + { + return false; + } + + // Compare U knots and multiplicities + for (int i = 1; i <= theSurface1->NbUKnots(); ++i) + { + if (std::abs(theSurface1->UKnot(i) - theSurface2->UKnot(i)) > aTolerance + || theSurface1->UMultiplicity(i) != theSurface2->UMultiplicity(i)) + { + return false; + } + } + + // Compare V knots and multiplicities + for (int i = 1; i <= theSurface1->NbVKnots(); ++i) + { + if (std::abs(theSurface1->VKnot(i) - theSurface2->VKnot(i)) > aTolerance + || theSurface1->VMultiplicity(i) != theSurface2->VMultiplicity(i)) + { + return false; + } + } + + // Compare rationality + if (theSurface1->IsURational() != theSurface2->IsURational() + || theSurface1->IsVRational() != theSurface2->IsVRational()) + { + return false; + } + + const GeomHash_PointHasher aPointHasher; + + // Compare poles + for (int i = 1; i <= theSurface1->NbUPoles(); ++i) + { + for (int j = 1; j <= theSurface1->NbVPoles(); ++j) + { + if (!aPointHasher(theSurface1->Pole(i, j), theSurface2->Pole(i, j))) + { + return false; + } + } + } + + // Compare weights if rational + if (theSurface1->IsURational() || theSurface1->IsVRational()) + { + for (int i = 1; i <= theSurface1->NbUPoles(); ++i) + { + for (int j = 1; j <= theSurface1->NbVPoles(); ++j) + { + if (std::abs(theSurface1->Weight(i, j) - theSurface2->Weight(i, j)) > aTolerance) + { + return false; + } + } + } + } + + return true; + } +}; + +#endif // _GeomHash_BSplineSurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_BezierCurveHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_BezierCurveHasher.pxx new file mode 100644 index 0000000000..d1f87f47b7 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_BezierCurveHasher.pxx @@ -0,0 +1,81 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_BezierCurveHasher_HeaderFile +#define _GeomHash_BezierCurveHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_BezierCurve (3D Bezier curve). +//! Used for geometry deduplication. +//! Hashes only metadata (degree, pole count, rationality) for efficiency. +struct GeomHash_BezierCurveHasher +{ + // Hashes the Bezier curve metadata only. + std::size_t operator()(const Handle(Geom_BezierCurve)& theCurve) const noexcept + { + const std::size_t aHashes[3] = {opencascade::hash(theCurve->Degree()), + opencascade::hash(theCurve->NbPoles()), + opencascade::hash(static_cast(theCurve->IsRational()))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two Bezier curves by full geometric data. + bool operator()(const Handle(Geom_BezierCurve)& theCurve1, + const Handle(Geom_BezierCurve)& theCurve2) const noexcept + { + constexpr double aTolerance = 1e-12; + + // Compare degrees + if (theCurve1->Degree() != theCurve2->Degree()) + { + return false; + } + + // Compare rationality + if (theCurve1->IsRational() != theCurve2->IsRational()) + { + return false; + } + + const GeomHash_PointHasher aPointHasher; + + // Compare poles + for (int i = 1; i <= theCurve1->NbPoles(); ++i) + { + if (!aPointHasher(theCurve1->Pole(i), theCurve2->Pole(i))) + { + return false; + } + } + + // Compare weights if rational + if (theCurve1->IsRational()) + { + for (int i = 1; i <= theCurve1->NbPoles(); ++i) + { + if (std::abs(theCurve1->Weight(i) - theCurve2->Weight(i)) > aTolerance) + { + return false; + } + } + } + + return true; + } +}; + +#endif // _GeomHash_BezierCurveHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_BezierSurfaceHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_BezierSurfaceHasher.pxx new file mode 100644 index 0000000000..52cf68e5a3 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_BezierSurfaceHasher.pxx @@ -0,0 +1,93 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_BezierSurfaceHasher_HeaderFile +#define _GeomHash_BezierSurfaceHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_BezierSurface. +//! Used for geometry deduplication. +//! Hashes only metadata (degrees, pole counts, rationality) for efficiency. +struct GeomHash_BezierSurfaceHasher +{ + // Hashes the Bezier surface metadata only. + std::size_t operator()(const Handle(Geom_BezierSurface)& theSurface) const noexcept + { + const std::size_t aHashes[5] = { + opencascade::hash(theSurface->UDegree()), + opencascade::hash(theSurface->VDegree()), + opencascade::hash(theSurface->NbUPoles()), + opencascade::hash(theSurface->NbVPoles()), + opencascade::hash(static_cast(theSurface->IsURational()) + | (static_cast(theSurface->IsVRational()) << 1))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two Bezier surfaces by full geometric data. + bool operator()(const Handle(Geom_BezierSurface)& theSurface1, + const Handle(Geom_BezierSurface)& theSurface2) const noexcept + { + constexpr double aTolerance = 1e-12; + + // Compare degrees + if (theSurface1->UDegree() != theSurface2->UDegree() + || theSurface1->VDegree() != theSurface2->VDegree()) + { + return false; + } + + // Compare rationality + if (theSurface1->IsURational() != theSurface2->IsURational() + || theSurface1->IsVRational() != theSurface2->IsVRational()) + { + return false; + } + + const GeomHash_PointHasher aPointHasher; + + // Compare poles + for (int i = 1; i <= theSurface1->NbUPoles(); ++i) + { + for (int j = 1; j <= theSurface1->NbVPoles(); ++j) + { + if (!aPointHasher(theSurface1->Pole(i, j), theSurface2->Pole(i, j))) + { + return false; + } + } + } + + // Compare weights if rational + if (theSurface1->IsURational() || theSurface1->IsVRational()) + { + for (int i = 1; i <= theSurface1->NbUPoles(); ++i) + { + for (int j = 1; j <= theSurface1->NbVPoles(); ++j) + { + if (std::abs(theSurface1->Weight(i, j) - theSurface2->Weight(i, j)) > aTolerance) + { + return false; + } + } + } + } + + return true; + } +}; + +#endif // _GeomHash_BezierSurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_CircleHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_CircleHasher.pxx new file mode 100644 index 0000000000..59f488e953 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_CircleHasher.pxx @@ -0,0 +1,51 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_CircleHasher_HeaderFile +#define _GeomHash_CircleHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_Circle (3D circle). +//! Used for geometry deduplication. +struct GeomHash_CircleHasher +{ + // Hashes the circle by its position and radius. + std::size_t operator()(const Handle(Geom_Circle)& theCircle) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[2] = { + anAxisHasher(theCircle->Position()), + opencascade::hash(static_cast(std::round(theCircle->Radius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two circles by their positions and radii. + bool operator()(const Handle(Geom_Circle)& theCircle1, + const Handle(Geom_Circle)& theCircle2) const noexcept + { + constexpr double aTolerance = 1e-12; + const GeomHash_AxisPlacement anAxisHasher; + + return anAxisHasher(theCircle1->Position(), theCircle2->Position()) + && std::abs(theCircle1->Radius() - theCircle2->Radius()) <= aTolerance; + } +}; + +#endif // _GeomHash_CircleHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_ConicalSurfaceHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_ConicalSurfaceHasher.pxx new file mode 100644 index 0000000000..ceefad366e --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_ConicalSurfaceHasher.pxx @@ -0,0 +1,52 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_ConicalSurfaceHasher_HeaderFile +#define _GeomHash_ConicalSurfaceHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_ConicalSurface. +//! Used for geometry deduplication. +struct GeomHash_ConicalSurfaceHasher +{ + // Hashes the cone by its position, apex radius, and semi-angle. + std::size_t operator()(const Handle(Geom_ConicalSurface)& theCone) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[3] = { + anAxisHasher(theCone->Position().Ax2()), + opencascade::hash(static_cast(std::round(theCone->RefRadius() * aFactor))), + opencascade::hash(static_cast(std::round(theCone->SemiAngle() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two cones by their positions, radii, and semi-angles. + bool operator()(const Handle(Geom_ConicalSurface)& theCone1, + const Handle(Geom_ConicalSurface)& theCone2) const noexcept + { + constexpr double aTolerance = 1e-12; + const GeomHash_AxisPlacement anAxisHasher; + return anAxisHasher(theCone1->Position().Ax2(), theCone2->Position().Ax2()) + && std::abs(theCone1->RefRadius() - theCone2->RefRadius()) <= aTolerance + && std::abs(theCone1->SemiAngle() - theCone2->SemiAngle()) <= aTolerance; + } +}; + +#endif // _GeomHash_ConicalSurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_CurveHasher.cxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_CurveHasher.cxx new file mode 100644 index 0000000000..f742e6cb42 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_CurveHasher.cxx @@ -0,0 +1,150 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//================================================================================================= + +std::size_t GeomHash_CurveHasher::operator()(const Handle(Geom_Curve)& theCurve) const noexcept +{ + if (theCurve.IsNull()) + { + return 0; + } + + // Dispatch based on actual curve type + if (Handle(Geom_Line) aLine = Handle(Geom_Line)::DownCast(theCurve)) + { + return GeomHash_LineHasher{}(aLine); + } + if (Handle(Geom_Circle) aCircle = Handle(Geom_Circle)::DownCast(theCurve)) + { + return GeomHash_CircleHasher{}(aCircle); + } + if (Handle(Geom_Ellipse) anEllipse = Handle(Geom_Ellipse)::DownCast(theCurve)) + { + return GeomHash_EllipseHasher{}(anEllipse); + } + if (Handle(Geom_Hyperbola) aHyperbola = Handle(Geom_Hyperbola)::DownCast(theCurve)) + { + return GeomHash_HyperbolaHasher{}(aHyperbola); + } + if (Handle(Geom_Parabola) aParabola = Handle(Geom_Parabola)::DownCast(theCurve)) + { + return GeomHash_ParabolaHasher{}(aParabola); + } + if (Handle(Geom_BezierCurve) aBezier = Handle(Geom_BezierCurve)::DownCast(theCurve)) + { + return GeomHash_BezierCurveHasher{}(aBezier); + } + if (Handle(Geom_BSplineCurve) aBSpline = Handle(Geom_BSplineCurve)::DownCast(theCurve)) + { + return GeomHash_BSplineCurveHasher{}(aBSpline); + } + if (Handle(Geom_TrimmedCurve) aTrimmed = Handle(Geom_TrimmedCurve)::DownCast(theCurve)) + { + return GeomHash_TrimmedCurveHasher{}(aTrimmed); + } + if (Handle(Geom_OffsetCurve) anOffset = Handle(Geom_OffsetCurve)::DownCast(theCurve)) + { + return GeomHash_OffsetCurveHasher{}(anOffset); + } + + // Unknown curve type - hash the type name + return std::hash{}(theCurve->DynamicType()->Name()); +} + +//================================================================================================= + +bool GeomHash_CurveHasher::operator()(const Handle(Geom_Curve)& theCurve1, + const Handle(Geom_Curve)& theCurve2) const noexcept +{ + if (theCurve1.IsNull() || theCurve2.IsNull()) + { + return theCurve1.IsNull() && theCurve2.IsNull(); + } + + if (theCurve1 == theCurve2) + { + return true; + } + + // Must be same type + if (theCurve1->DynamicType() != theCurve2->DynamicType()) + { + return false; + } + + // Dispatch based on actual curve type + if (Handle(Geom_Line) aLine1 = Handle(Geom_Line)::DownCast(theCurve1)) + { + return GeomHash_LineHasher{}(aLine1, Handle(Geom_Line)::DownCast(theCurve2)); + } + if (Handle(Geom_Circle) aCircle1 = Handle(Geom_Circle)::DownCast(theCurve1)) + { + return GeomHash_CircleHasher{}(aCircle1, Handle(Geom_Circle)::DownCast(theCurve2)); + } + if (Handle(Geom_Ellipse) anEllipse1 = Handle(Geom_Ellipse)::DownCast(theCurve1)) + { + return GeomHash_EllipseHasher{}(anEllipse1, Handle(Geom_Ellipse)::DownCast(theCurve2)); + } + if (Handle(Geom_Hyperbola) aHyp1 = Handle(Geom_Hyperbola)::DownCast(theCurve1)) + { + return GeomHash_HyperbolaHasher{}(aHyp1, Handle(Geom_Hyperbola)::DownCast(theCurve2)); + } + if (Handle(Geom_Parabola) aPar1 = Handle(Geom_Parabola)::DownCast(theCurve1)) + { + return GeomHash_ParabolaHasher{}(aPar1, Handle(Geom_Parabola)::DownCast(theCurve2)); + } + if (Handle(Geom_BezierCurve) aBez1 = Handle(Geom_BezierCurve)::DownCast(theCurve1)) + { + return GeomHash_BezierCurveHasher{}(aBez1, Handle(Geom_BezierCurve)::DownCast(theCurve2)); + } + if (Handle(Geom_BSplineCurve) aBSpl1 = Handle(Geom_BSplineCurve)::DownCast(theCurve1)) + { + return GeomHash_BSplineCurveHasher{}(aBSpl1, Handle(Geom_BSplineCurve)::DownCast(theCurve2)); + } + if (Handle(Geom_TrimmedCurve) aTrim1 = Handle(Geom_TrimmedCurve)::DownCast(theCurve1)) + { + return GeomHash_TrimmedCurveHasher{}(aTrim1, Handle(Geom_TrimmedCurve)::DownCast(theCurve2)); + } + if (Handle(Geom_OffsetCurve) aOff1 = Handle(Geom_OffsetCurve)::DownCast(theCurve1)) + { + return GeomHash_OffsetCurveHasher{}(aOff1, Handle(Geom_OffsetCurve)::DownCast(theCurve2)); + } + + // Unknown curve type - compare by pointer + return theCurve1.get() == theCurve2.get(); +} diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_CurveHasher.hxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_CurveHasher.hxx new file mode 100644 index 0000000000..9036541351 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_CurveHasher.hxx @@ -0,0 +1,34 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_CurveHasher_HeaderFile +#define _GeomHash_CurveHasher_HeaderFile + +#include +#include + +class Geom_Curve; + +//! Polymorphic hasher for Geom_Curve using RTTI dispatch. +//! Used for geometry deduplication. +struct GeomHash_CurveHasher +{ + // Hashes any Geom_Curve by dispatching to the appropriate specific hasher. + Standard_EXPORT std::size_t operator()(const Handle(Geom_Curve)& theCurve) const noexcept; + + // Compares two curves using polymorphic dispatch. + Standard_EXPORT bool operator()(const Handle(Geom_Curve)& theCurve1, + const Handle(Geom_Curve)& theCurve2) const noexcept; +}; + +#endif // _GeomHash_CurveHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_CylindricalSurfaceHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_CylindricalSurfaceHasher.pxx new file mode 100644 index 0000000000..5367df7b5b --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_CylindricalSurfaceHasher.pxx @@ -0,0 +1,50 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_CylindricalSurfaceHasher_HeaderFile +#define _GeomHash_CylindricalSurfaceHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_CylindricalSurface. +//! Used for geometry deduplication. +struct GeomHash_CylindricalSurfaceHasher +{ + // Hashes the cylinder by its position and radius. + std::size_t operator()(const Handle(Geom_CylindricalSurface)& theCylinder) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[2] = { + anAxisHasher(theCylinder->Position().Ax2()), + opencascade::hash(static_cast(std::round(theCylinder->Radius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two cylinders by their positions and radii. + bool operator()(const Handle(Geom_CylindricalSurface)& theCylinder1, + const Handle(Geom_CylindricalSurface)& theCylinder2) const noexcept + { + constexpr double aTolerance = 1e-12; + const GeomHash_AxisPlacement anAxisHasher; + return anAxisHasher(theCylinder1->Position().Ax2(), theCylinder2->Position().Ax2()) + && std::abs(theCylinder1->Radius() - theCylinder2->Radius()) <= aTolerance; + } +}; + +#endif // _GeomHash_CylindricalSurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_DirectionHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_DirectionHasher.pxx new file mode 100644 index 0000000000..561b0e639b --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_DirectionHasher.pxx @@ -0,0 +1,49 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_DirectionHasher_HeaderFile +#define _GeomHash_DirectionHasher_HeaderFile + +#include +#include +#include + +//! OCCT-style hasher for gp_Dir (3D directions). +//! Used for geometry deduplication. +struct GeomHash_DirectionHasher +{ + // Hashes the 3D direction by its XYZ components. + std::size_t operator()(const gp_Dir& theDirection) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + // Round each component to tolerance precision before hashing + const std::size_t aHashes[3] = { + opencascade::hash(static_cast(std::round(theDirection.X() * aFactor))), + opencascade::hash(static_cast(std::round(theDirection.Y() * aFactor))), + opencascade::hash(static_cast(std::round(theDirection.Z() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two 3D directions with fixed tolerance. + bool operator()(const gp_Dir& theDirection1, const gp_Dir& theDirection2) const noexcept + { + constexpr double aTolerance = 1e-12; + return std::abs(theDirection1.X() - theDirection2.X()) <= aTolerance + && std::abs(theDirection1.Y() - theDirection2.Y()) <= aTolerance + && std::abs(theDirection1.Z() - theDirection2.Z()) <= aTolerance; + } +}; + +#endif // _GeomHash_DirectionHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_EllipseHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_EllipseHasher.pxx new file mode 100644 index 0000000000..65c67dae22 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_EllipseHasher.pxx @@ -0,0 +1,53 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_EllipseHasher_HeaderFile +#define _GeomHash_EllipseHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_Ellipse (3D ellipse). +//! Used for geometry deduplication. +struct GeomHash_EllipseHasher +{ + // Hashes the ellipse by its position, major radius, and minor radius. + std::size_t operator()(const Handle(Geom_Ellipse)& theEllipse) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[3] = { + anAxisHasher(theEllipse->Position()), + opencascade::hash(static_cast(std::round(theEllipse->MajorRadius() * aFactor))), + opencascade::hash(static_cast(std::round(theEllipse->MinorRadius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two ellipses by their positions and radii. + bool operator()(const Handle(Geom_Ellipse)& theEllipse1, + const Handle(Geom_Ellipse)& theEllipse2) const noexcept + { + constexpr double aTolerance = 1e-12; + const GeomHash_AxisPlacement anAxisHasher; + + return anAxisHasher(theEllipse1->Position(), theEllipse2->Position()) + && std::abs(theEllipse1->MajorRadius() - theEllipse2->MajorRadius()) <= aTolerance + && std::abs(theEllipse1->MinorRadius() - theEllipse2->MinorRadius()) <= aTolerance; + } +}; + +#endif // _GeomHash_EllipseHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_HyperbolaHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_HyperbolaHasher.pxx new file mode 100644 index 0000000000..68800d84e4 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_HyperbolaHasher.pxx @@ -0,0 +1,53 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_HyperbolaHasher_HeaderFile +#define _GeomHash_HyperbolaHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_Hyperbola (3D hyperbola). +//! Used for geometry deduplication. +struct GeomHash_HyperbolaHasher +{ + // Hashes the hyperbola by its position, major radius, and minor radius. + std::size_t operator()(const Handle(Geom_Hyperbola)& theHyperbola) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[3] = { + anAxisHasher(theHyperbola->Position()), + opencascade::hash(static_cast(std::round(theHyperbola->MajorRadius() * aFactor))), + opencascade::hash(static_cast(std::round(theHyperbola->MinorRadius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two hyperbolas by their positions and radii. + bool operator()(const Handle(Geom_Hyperbola)& theHyperbola1, + const Handle(Geom_Hyperbola)& theHyperbola2) const noexcept + { + constexpr double aTolerance = 1e-12; + const GeomHash_AxisPlacement anAxisHasher; + + return anAxisHasher(theHyperbola1->Position(), theHyperbola2->Position()) + && std::abs(theHyperbola1->MajorRadius() - theHyperbola2->MajorRadius()) <= aTolerance + && std::abs(theHyperbola1->MinorRadius() - theHyperbola2->MinorRadius()) <= aTolerance; + } +}; + +#endif // _GeomHash_HyperbolaHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_LineHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_LineHasher.pxx new file mode 100644 index 0000000000..a6ff8875ce --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_LineHasher.pxx @@ -0,0 +1,49 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_LineHasher_HeaderFile +#define _GeomHash_LineHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_Line (3D line). +//! Used for geometry deduplication. +struct GeomHash_LineHasher +{ + // Hashes the line by its location and direction. + std::size_t operator()(const Handle(Geom_Line)& theLine) const noexcept + { + const GeomHash_PointHasher aPointHasher; + const GeomHash_DirectionHasher aDirHasher; + + const std::size_t aHashes[2] = {aPointHasher(theLine->Position().Location()), + aDirHasher(theLine->Position().Direction())}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two lines by their positions. + bool operator()(const Handle(Geom_Line)& theLine1, + const Handle(Geom_Line)& theLine2) const noexcept + { + const GeomHash_PointHasher aPointHasher; + const GeomHash_DirectionHasher aDirHasher; + + return aPointHasher(theLine1->Position().Location(), theLine2->Position().Location()) + && aDirHasher(theLine1->Position().Direction(), theLine2->Position().Direction()); + } +}; + +#endif // _GeomHash_LineHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_OffsetCurveHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_OffsetCurveHasher.pxx new file mode 100644 index 0000000000..5c16d15aa8 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_OffsetCurveHasher.pxx @@ -0,0 +1,57 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_OffsetCurveHasher_HeaderFile +#define _GeomHash_OffsetCurveHasher_HeaderFile + +#include +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_OffsetCurve (3D offset curve). +//! Used for geometry deduplication. +struct GeomHash_OffsetCurveHasher +{ + // Hashes the offset curve by its offset distance, direction, and basis curve. + std::size_t operator()(const Handle(Geom_OffsetCurve)& theCurve) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_DirectionHasher aDirHasher; + const GeomHash_CurveHasher aCurveHasher; + const std::size_t aHashes[3] = { + aCurveHasher(theCurve->BasisCurve()), + opencascade::hash(static_cast(std::round(theCurve->Offset() * aFactor))), + aDirHasher(theCurve->Direction())}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two offset curves. + bool operator()(const Handle(Geom_OffsetCurve)& theCurve1, + const Handle(Geom_OffsetCurve)& theCurve2) const noexcept + { + constexpr double aTolerance = 1e-12; + + const GeomHash_DirectionHasher aDirHasher; + const GeomHash_CurveHasher aCurveHasher; + + return aCurveHasher(theCurve1->BasisCurve(), theCurve2->BasisCurve()) + && std::abs(theCurve1->Offset() - theCurve2->Offset()) <= aTolerance + && aDirHasher(theCurve1->Direction(), theCurve2->Direction()); + } +}; + +#endif // _GeomHash_OffsetCurveHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_OffsetSurfaceHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_OffsetSurfaceHasher.pxx new file mode 100644 index 0000000000..a6b95a8fdf --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_OffsetSurfaceHasher.pxx @@ -0,0 +1,52 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_OffsetSurfaceHasher_HeaderFile +#define _GeomHash_OffsetSurfaceHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_OffsetSurface. +//! Used for geometry deduplication. +struct GeomHash_OffsetSurfaceHasher +{ + // Hashes the offset surface by its offset distance and basis surface. + std::size_t operator()(const Handle(Geom_OffsetSurface)& theSurface) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_SurfaceHasher aSurfaceHasher; + const std::size_t aHashes[2] = { + aSurfaceHasher(theSurface->BasisSurface()), + opencascade::hash(static_cast(std::round(theSurface->Offset() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two offset surfaces by their offset distances and basis surfaces. + bool operator()(const Handle(Geom_OffsetSurface)& theSurface1, + const Handle(Geom_OffsetSurface)& theSurface2) const noexcept + { + constexpr double aTolerance = 1e-12; + + const GeomHash_SurfaceHasher aSurfaceHasher; + + return aSurfaceHasher(theSurface1->BasisSurface(), theSurface2->BasisSurface()) + && std::abs(theSurface1->Offset() - theSurface2->Offset()) <= aTolerance; + } +}; + +#endif // _GeomHash_OffsetSurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_ParabolaHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_ParabolaHasher.pxx new file mode 100644 index 0000000000..073cf87155 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_ParabolaHasher.pxx @@ -0,0 +1,51 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_ParabolaHasher_HeaderFile +#define _GeomHash_ParabolaHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_Parabola (3D parabola). +//! Used for geometry deduplication. +struct GeomHash_ParabolaHasher +{ + // Hashes the parabola by its position and focal length. + std::size_t operator()(const Handle(Geom_Parabola)& theParabola) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[2] = { + anAxisHasher(theParabola->Position()), + opencascade::hash(static_cast(std::round(theParabola->Focal() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two parabolas by their positions and focal lengths. + bool operator()(const Handle(Geom_Parabola)& theParabola1, + const Handle(Geom_Parabola)& theParabola2) const noexcept + { + constexpr double aTolerance = 1e-12; + const GeomHash_AxisPlacement anAxisHasher; + + return anAxisHasher(theParabola1->Position(), theParabola2->Position()) + && std::abs(theParabola1->Focal() - theParabola2->Focal()) <= aTolerance; + } +}; + +#endif // _GeomHash_ParabolaHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_PlaneHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_PlaneHasher.pxx new file mode 100644 index 0000000000..6aeeb567a8 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_PlaneHasher.pxx @@ -0,0 +1,41 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_PlaneHasher_HeaderFile +#define _GeomHash_PlaneHasher_HeaderFile + +#include +#include +#include + +//! OCCT-style hasher for Geom_Plane surfaces. +//! Used for geometry deduplication. +struct GeomHash_PlaneHasher +{ + // Hashes the plane by its position (location, normal, reference direction). + std::size_t operator()(const Handle(Geom_Plane)& thePlane) const noexcept + { + const GeomHash_AxisPlacement anAxisHasher; + return anAxisHasher(thePlane->Position().Ax2()); + } + + // Compares two planes by their positions. + bool operator()(const Handle(Geom_Plane)& thePlane1, + const Handle(Geom_Plane)& thePlane2) const noexcept + { + const GeomHash_AxisPlacement anAxisHasher; + return anAxisHasher(thePlane1->Position().Ax2(), thePlane2->Position().Ax2()); + } +}; + +#endif // _GeomHash_PlaneHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_PointHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_PointHasher.pxx new file mode 100644 index 0000000000..d7d9cf087e --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_PointHasher.pxx @@ -0,0 +1,49 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_PointHasher_HeaderFile +#define _GeomHash_PointHasher_HeaderFile + +#include +#include +#include + +//! OCCT-style hasher for gp_Pnt (3D points). +//! Used for geometry deduplication. +struct GeomHash_PointHasher +{ + // Hashes the 3D point by its XYZ coordinates. + std::size_t operator()(const gp_Pnt& thePoint) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + // Round each coordinate to tolerance precision before hashing + const std::size_t aHashes[3] = { + opencascade::hash(static_cast(std::round(thePoint.X() * aFactor))), + opencascade::hash(static_cast(std::round(thePoint.Y() * aFactor))), + opencascade::hash(static_cast(std::round(thePoint.Z() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two 3D points with fixed tolerance. + bool operator()(const gp_Pnt& thePoint1, const gp_Pnt& thePoint2) const noexcept + { + constexpr double aTolerance = 1e-12; + return std::abs(thePoint1.X() - thePoint2.X()) <= aTolerance + && std::abs(thePoint1.Y() - thePoint2.Y()) <= aTolerance + && std::abs(thePoint1.Z() - thePoint2.Z()) <= aTolerance; + } +}; + +#endif // _GeomHash_PointHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_RectangularTrimmedSurfaceHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_RectangularTrimmedSurfaceHasher.pxx new file mode 100644 index 0000000000..774e0cc53f --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_RectangularTrimmedSurfaceHasher.pxx @@ -0,0 +1,71 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_RectangularTrimmedSurfaceHasher_HeaderFile +#define _GeomHash_RectangularTrimmedSurfaceHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_RectangularTrimmedSurface. +//! Used for geometry deduplication. +struct GeomHash_RectangularTrimmedSurfaceHasher +{ + // Hashes the trimmed surface by its trim bounds and basis surface. + std::size_t operator()(const Handle(Geom_RectangularTrimmedSurface)& theSurface) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_SurfaceHasher aSurfaceHasher; + + double aU1, aU2, aV1, aV2; + theSurface->Bounds(aU1, aU2, aV1, aV2); + + const std::size_t aHashes[5] = { + aSurfaceHasher(theSurface->BasisSurface()), + opencascade::hash(static_cast(std::round(aU1 * aFactor))), + opencascade::hash(static_cast(std::round(aU2 * aFactor))), + opencascade::hash(static_cast(std::round(aV1 * aFactor))), + opencascade::hash(static_cast(std::round(aV2 * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two trimmed surfaces by their trim bounds and basis surfaces. + bool operator()(const Handle(Geom_RectangularTrimmedSurface)& theSurface1, + const Handle(Geom_RectangularTrimmedSurface)& theSurface2) const noexcept + { + constexpr double aTolerance = 1e-12; + + const GeomHash_SurfaceHasher aSurfaceHasher; + + // Compare basis surfaces + if (!aSurfaceHasher(theSurface1->BasisSurface(), theSurface2->BasisSurface())) + { + return false; + } + + // Compare trim bounds + double aU1_1, aU2_1, aV1_1, aV2_1; + double aU1_2, aU2_2, aV1_2, aV2_2; + theSurface1->Bounds(aU1_1, aU2_1, aV1_1, aV2_1); + theSurface2->Bounds(aU1_2, aU2_2, aV1_2, aV2_2); + + return std::abs(aU1_1 - aU1_2) <= aTolerance && std::abs(aU2_1 - aU2_2) <= aTolerance + && std::abs(aV1_1 - aV1_2) <= aTolerance && std::abs(aV2_1 - aV2_2) <= aTolerance; + } +}; + +#endif // _GeomHash_RectangularTrimmedSurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_SphericalSurfaceHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_SphericalSurfaceHasher.pxx new file mode 100644 index 0000000000..06a71c1983 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_SphericalSurfaceHasher.pxx @@ -0,0 +1,50 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_SphericalSurfaceHasher_HeaderFile +#define _GeomHash_SphericalSurfaceHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_SphericalSurface. +//! Used for geometry deduplication. +struct GeomHash_SphericalSurfaceHasher +{ + // Hashes the sphere by its position and radius. + std::size_t operator()(const Handle(Geom_SphericalSurface)& theSphere) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[2] = { + anAxisHasher(theSphere->Position().Ax2()), + opencascade::hash(static_cast(std::round(theSphere->Radius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two spheres by their positions and radii. + bool operator()(const Handle(Geom_SphericalSurface)& theSphere1, + const Handle(Geom_SphericalSurface)& theSphere2) const noexcept + { + constexpr double aTolerance = 1e-12; + const GeomHash_AxisPlacement anAxisHasher; + return anAxisHasher(theSphere1->Position().Ax2(), theSphere2->Position().Ax2()) + && std::abs(theSphere1->Radius() - theSphere2->Radius()) <= aTolerance; + } +}; + +#endif // _GeomHash_SphericalSurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceHasher.cxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceHasher.cxx new file mode 100644 index 0000000000..01fa9dbb83 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceHasher.cxx @@ -0,0 +1,191 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//================================================================================================= + +std::size_t GeomHash_SurfaceHasher::operator()( + const Handle(Geom_Surface)& theSurface) const noexcept +{ + if (theSurface.IsNull()) + { + return 0; + } + + // Dispatch based on actual surface type + if (Handle(Geom_Plane) aPlane = Handle(Geom_Plane)::DownCast(theSurface)) + { + return GeomHash_PlaneHasher{}(aPlane); + } + if (Handle(Geom_CylindricalSurface) aCylinder = + Handle(Geom_CylindricalSurface)::DownCast(theSurface)) + { + return GeomHash_CylindricalSurfaceHasher{}(aCylinder); + } + if (Handle(Geom_ConicalSurface) aCone = Handle(Geom_ConicalSurface)::DownCast(theSurface)) + { + return GeomHash_ConicalSurfaceHasher{}(aCone); + } + if (Handle(Geom_SphericalSurface) aSphere = Handle(Geom_SphericalSurface)::DownCast(theSurface)) + { + return GeomHash_SphericalSurfaceHasher{}(aSphere); + } + if (Handle(Geom_ToroidalSurface) aTorus = Handle(Geom_ToroidalSurface)::DownCast(theSurface)) + { + return GeomHash_ToroidalSurfaceHasher{}(aTorus); + } + if (Handle(Geom_SurfaceOfRevolution) aRevol = + Handle(Geom_SurfaceOfRevolution)::DownCast(theSurface)) + { + return GeomHash_SurfaceOfRevolutionHasher{}(aRevol); + } + if (Handle(Geom_SurfaceOfLinearExtrusion) aExtr = + Handle(Geom_SurfaceOfLinearExtrusion)::DownCast(theSurface)) + { + return GeomHash_SurfaceOfLinearExtrusionHasher{}(aExtr); + } + if (Handle(Geom_BezierSurface) aBezier = Handle(Geom_BezierSurface)::DownCast(theSurface)) + { + return GeomHash_BezierSurfaceHasher{}(aBezier); + } + if (Handle(Geom_BSplineSurface) aBSpline = Handle(Geom_BSplineSurface)::DownCast(theSurface)) + { + return GeomHash_BSplineSurfaceHasher{}(aBSpline); + } + if (Handle(Geom_RectangularTrimmedSurface) aTrimmed = + Handle(Geom_RectangularTrimmedSurface)::DownCast(theSurface)) + { + return GeomHash_RectangularTrimmedSurfaceHasher{}(aTrimmed); + } + if (Handle(Geom_OffsetSurface) aOffset = Handle(Geom_OffsetSurface)::DownCast(theSurface)) + { + return GeomHash_OffsetSurfaceHasher{}(aOffset); + } + + // Unknown surface type - hash the type name + return std::hash{}(theSurface->DynamicType()->Name()); +} + +//================================================================================================= + +bool GeomHash_SurfaceHasher::operator()(const Handle(Geom_Surface)& theSurface1, + const Handle(Geom_Surface)& theSurface2) const noexcept +{ + if (theSurface1.IsNull() || theSurface2.IsNull()) + { + return theSurface1.IsNull() && theSurface2.IsNull(); + } + + if (theSurface1 == theSurface2) + { + return true; + } + + // Must be same type + if (theSurface1->DynamicType() != theSurface2->DynamicType()) + { + return false; + } + + // Dispatch based on actual surface type + if (Handle(Geom_Plane) aPlane1 = Handle(Geom_Plane)::DownCast(theSurface1)) + { + return GeomHash_PlaneHasher{}(aPlane1, Handle(Geom_Plane)::DownCast(theSurface2)); + } + if (Handle(Geom_CylindricalSurface) aCyl1 = + Handle(Geom_CylindricalSurface)::DownCast(theSurface1)) + { + return GeomHash_CylindricalSurfaceHasher{}( + aCyl1, + Handle(Geom_CylindricalSurface)::DownCast(theSurface2)); + } + if (Handle(Geom_ConicalSurface) aCone1 = Handle(Geom_ConicalSurface)::DownCast(theSurface1)) + { + return GeomHash_ConicalSurfaceHasher{}(aCone1, + Handle(Geom_ConicalSurface)::DownCast(theSurface2)); + } + if (Handle(Geom_SphericalSurface) aSph1 = Handle(Geom_SphericalSurface)::DownCast(theSurface1)) + { + return GeomHash_SphericalSurfaceHasher{}(aSph1, + Handle(Geom_SphericalSurface)::DownCast(theSurface2)); + } + if (Handle(Geom_ToroidalSurface) aTor1 = Handle(Geom_ToroidalSurface)::DownCast(theSurface1)) + { + return GeomHash_ToroidalSurfaceHasher{}(aTor1, + Handle(Geom_ToroidalSurface)::DownCast(theSurface2)); + } + if (Handle(Geom_SurfaceOfRevolution) aRev1 = + Handle(Geom_SurfaceOfRevolution)::DownCast(theSurface1)) + { + return GeomHash_SurfaceOfRevolutionHasher{}( + aRev1, + Handle(Geom_SurfaceOfRevolution)::DownCast(theSurface2)); + } + if (Handle(Geom_SurfaceOfLinearExtrusion) aExt1 = + Handle(Geom_SurfaceOfLinearExtrusion)::DownCast(theSurface1)) + { + return GeomHash_SurfaceOfLinearExtrusionHasher{}( + aExt1, + Handle(Geom_SurfaceOfLinearExtrusion)::DownCast(theSurface2)); + } + if (Handle(Geom_BezierSurface) aBez1 = Handle(Geom_BezierSurface)::DownCast(theSurface1)) + { + return GeomHash_BezierSurfaceHasher{}(aBez1, Handle(Geom_BezierSurface)::DownCast(theSurface2)); + } + if (Handle(Geom_BSplineSurface) aBSpl1 = Handle(Geom_BSplineSurface)::DownCast(theSurface1)) + { + return GeomHash_BSplineSurfaceHasher{}(aBSpl1, + Handle(Geom_BSplineSurface)::DownCast(theSurface2)); + } + if (Handle(Geom_RectangularTrimmedSurface) aTrim1 = + Handle(Geom_RectangularTrimmedSurface)::DownCast(theSurface1)) + { + return GeomHash_RectangularTrimmedSurfaceHasher{}( + aTrim1, + Handle(Geom_RectangularTrimmedSurface)::DownCast(theSurface2)); + } + if (Handle(Geom_OffsetSurface) aOff1 = Handle(Geom_OffsetSurface)::DownCast(theSurface1)) + { + return GeomHash_OffsetSurfaceHasher{}(aOff1, Handle(Geom_OffsetSurface)::DownCast(theSurface2)); + } + + // Unknown surface type - compare by pointer + return theSurface1.get() == theSurface2.get(); +} diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceHasher.hxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceHasher.hxx new file mode 100644 index 0000000000..f4cd74ba4e --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceHasher.hxx @@ -0,0 +1,34 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_SurfaceHasher_HeaderFile +#define _GeomHash_SurfaceHasher_HeaderFile + +#include +#include + +class Geom_Surface; + +//! Polymorphic hasher for Geom_Surface using RTTI dispatch. +//! Used for geometry deduplication. +struct GeomHash_SurfaceHasher +{ + // Hashes any Geom_Surface by dispatching to the appropriate specific hasher. + Standard_EXPORT std::size_t operator()(const Handle(Geom_Surface)& theSurface) const noexcept; + + // Compares two surfaces using polymorphic dispatch. + Standard_EXPORT bool operator()(const Handle(Geom_Surface)& theSurface1, + const Handle(Geom_Surface)& theSurface2) const noexcept; +}; + +#endif // _GeomHash_SurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceOfLinearExtrusionHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceOfLinearExtrusionHasher.pxx new file mode 100644 index 0000000000..b1d2fbae4f --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceOfLinearExtrusionHasher.pxx @@ -0,0 +1,48 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_SurfaceOfLinearExtrusionHasher_HeaderFile +#define _GeomHash_SurfaceOfLinearExtrusionHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_SurfaceOfLinearExtrusion. +//! Used for geometry deduplication. +struct GeomHash_SurfaceOfLinearExtrusionHasher +{ + // Hashes the extrusion surface by its direction and basis curve. + std::size_t operator()(const Handle(Geom_SurfaceOfLinearExtrusion)& theSurface) const noexcept + { + const GeomHash_DirectionHasher aDirHasher; + const GeomHash_CurveHasher aCurveHasher; + const std::size_t aHashes[2] = {aCurveHasher(theSurface->BasisCurve()), + aDirHasher(theSurface->Direction())}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two extrusion surfaces. + bool operator()(const Handle(Geom_SurfaceOfLinearExtrusion)& theSurface1, + const Handle(Geom_SurfaceOfLinearExtrusion)& theSurface2) const noexcept + { + const GeomHash_DirectionHasher aDirHasher; + const GeomHash_CurveHasher aCurveHasher; + + return aCurveHasher(theSurface1->BasisCurve(), theSurface2->BasisCurve()) + && aDirHasher(theSurface1->Direction(), theSurface2->Direction()); + } +}; + +#endif // _GeomHash_SurfaceOfLinearExtrusionHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceOfRevolutionHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceOfRevolutionHasher.pxx new file mode 100644 index 0000000000..ca3a8991bb --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_SurfaceOfRevolutionHasher.pxx @@ -0,0 +1,58 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_SurfaceOfRevolutionHasher_HeaderFile +#define _GeomHash_SurfaceOfRevolutionHasher_HeaderFile + +#include +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_SurfaceOfRevolution. +//! Used for geometry deduplication. +struct GeomHash_SurfaceOfRevolutionHasher +{ + // Hashes the revolution surface by its axis and basis curve. + std::size_t operator()(const Handle(Geom_SurfaceOfRevolution)& theSurface) const noexcept + { + const GeomHash_PointHasher aPointHasher; + const GeomHash_DirectionHasher aDirHasher; + const GeomHash_CurveHasher aCurveHasher; + + const gp_Ax1& anAxis = theSurface->Axis(); + const std::size_t aHashes[3] = {aCurveHasher(theSurface->BasisCurve()), + aPointHasher(anAxis.Location()), + aDirHasher(anAxis.Direction())}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two revolution surfaces. + bool operator()(const Handle(Geom_SurfaceOfRevolution)& theSurface1, + const Handle(Geom_SurfaceOfRevolution)& theSurface2) const noexcept + { + const GeomHash_PointHasher aPointHasher; + const GeomHash_DirectionHasher aDirHasher; + const GeomHash_CurveHasher aCurveHasher; + + const gp_Ax1& anAxis1 = theSurface1->Axis(); + const gp_Ax1& anAxis2 = theSurface2->Axis(); + + return aCurveHasher(theSurface1->BasisCurve(), theSurface2->BasisCurve()) + && aPointHasher(anAxis1.Location(), anAxis2.Location()) + && aDirHasher(anAxis1.Direction(), anAxis2.Direction()); + } +}; + +#endif // _GeomHash_SurfaceOfRevolutionHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_ToroidalSurfaceHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_ToroidalSurfaceHasher.pxx new file mode 100644 index 0000000000..ec6558ed7a --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_ToroidalSurfaceHasher.pxx @@ -0,0 +1,52 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_ToroidalSurfaceHasher_HeaderFile +#define _GeomHash_ToroidalSurfaceHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_ToroidalSurface. +//! Used for geometry deduplication. +struct GeomHash_ToroidalSurfaceHasher +{ + // Hashes the torus by its position, major radius, and minor radius. + std::size_t operator()(const Handle(Geom_ToroidalSurface)& theTorus) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_AxisPlacement anAxisHasher; + const std::size_t aHashes[3] = { + anAxisHasher(theTorus->Position().Ax2()), + opencascade::hash(static_cast(std::round(theTorus->MajorRadius() * aFactor))), + opencascade::hash(static_cast(std::round(theTorus->MinorRadius() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two tori by their positions, major radii, and minor radii. + bool operator()(const Handle(Geom_ToroidalSurface)& theTorus1, + const Handle(Geom_ToroidalSurface)& theTorus2) const noexcept + { + constexpr double aTolerance = 1e-12; + const GeomHash_AxisPlacement anAxisHasher; + return anAxisHasher(theTorus1->Position().Ax2(), theTorus2->Position().Ax2()) + && std::abs(theTorus1->MajorRadius() - theTorus2->MajorRadius()) <= aTolerance + && std::abs(theTorus1->MinorRadius() - theTorus2->MinorRadius()) <= aTolerance; + } +}; + +#endif // _GeomHash_ToroidalSurfaceHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_TrimmedCurveHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_TrimmedCurveHasher.pxx new file mode 100644 index 0000000000..f6e17cd71c --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_TrimmedCurveHasher.pxx @@ -0,0 +1,54 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_TrimmedCurveHasher_HeaderFile +#define _GeomHash_TrimmedCurveHasher_HeaderFile + +#include +#include +#include +#include + +//! OCCT-style hasher for Geom_TrimmedCurve (3D trimmed curve). +//! Used for geometry deduplication. +struct GeomHash_TrimmedCurveHasher +{ + // Hashes the trimmed curve by its parameters and basis curve. + std::size_t operator()(const Handle(Geom_TrimmedCurve)& theCurve) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + const GeomHash_CurveHasher aCurveHasher; + const std::size_t aHashes[3] = { + aCurveHasher(theCurve->BasisCurve()), + opencascade::hash(static_cast(std::round(theCurve->FirstParameter() * aFactor))), + opencascade::hash(static_cast(std::round(theCurve->LastParameter() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two trimmed curves. + bool operator()(const Handle(Geom_TrimmedCurve)& theCurve1, + const Handle(Geom_TrimmedCurve)& theCurve2) const noexcept + { + constexpr double aTolerance = 1e-12; + + const GeomHash_CurveHasher aCurveHasher; + + return aCurveHasher(theCurve1->BasisCurve(), theCurve2->BasisCurve()) + && std::abs(theCurve1->FirstParameter() - theCurve2->FirstParameter()) <= aTolerance + && std::abs(theCurve1->LastParameter() - theCurve2->LastParameter()) <= aTolerance; + } +}; + +#endif // _GeomHash_TrimmedCurveHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/GeomHash/GeomHash_VectorHasher.pxx b/src/ModelingData/TKG3d/GeomHash/GeomHash_VectorHasher.pxx new file mode 100644 index 0000000000..b81f0eb0d2 --- /dev/null +++ b/src/ModelingData/TKG3d/GeomHash/GeomHash_VectorHasher.pxx @@ -0,0 +1,49 @@ +// Copyright (c) 2025 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _GeomHash_VectorHasher_HeaderFile +#define _GeomHash_VectorHasher_HeaderFile + +#include +#include +#include + +//! OCCT-style hasher for gp_Vec (3D vectors). +//! Used for geometry deduplication. +struct GeomHash_VectorHasher +{ + // Hashes the 3D vector by its XYZ components. + std::size_t operator()(const gp_Vec& theVector) const noexcept + { + constexpr double aTolerance = 1e-12; + constexpr double aFactor = 1.0 / aTolerance; + + // Round each component to tolerance precision before hashing + const std::size_t aHashes[3] = { + opencascade::hash(static_cast(std::round(theVector.X() * aFactor))), + opencascade::hash(static_cast(std::round(theVector.Y() * aFactor))), + opencascade::hash(static_cast(std::round(theVector.Z() * aFactor)))}; + return opencascade::hashBytes(aHashes, sizeof(aHashes)); + } + + // Compares two 3D vectors with fixed tolerance. + bool operator()(const gp_Vec& theVector1, const gp_Vec& theVector2) const noexcept + { + constexpr double aTolerance = 1e-12; + return std::abs(theVector1.X() - theVector2.X()) <= aTolerance + && std::abs(theVector1.Y() - theVector2.Y()) <= aTolerance + && std::abs(theVector1.Z() - theVector2.Z()) <= aTolerance; + } +}; + +#endif // _GeomHash_VectorHasher_HeaderFile diff --git a/src/ModelingData/TKG3d/PACKAGES.cmake b/src/ModelingData/TKG3d/PACKAGES.cmake index f7bad084e3..f3e1b72791 100644 --- a/src/ModelingData/TKG3d/PACKAGES.cmake +++ b/src/ModelingData/TKG3d/PACKAGES.cmake @@ -10,4 +10,5 @@ set(OCCT_TKG3d_LIST_OF_PACKAGES TopAbs GeomEvaluator GProp + GeomHash )