From 985c3992f86c2cbcf4eaed3a888d3f8dc7eae9a2 Mon Sep 17 00:00:00 2001 From: dpasukhi Date: Tue, 2 Dec 2025 21:36:52 +0000 Subject: [PATCH] Revert "Foundation Classes - TopLoc package update (#849)" This reverts commit a6e68c7e70a175605fa0779add068143c7bc8ed2. --- .../TKMath/GTests/FILES.cmake | 1 - .../TKMath/GTests/TopLoc_Location_Test.cxx | 722 ------------------ .../TKMath/TopLoc/FILES.cmake | 1 + .../TKMath/TopLoc/TopLoc_Datum3D.hxx | 6 +- .../TKMath/TopLoc/TopLoc_ItemLocation.cxx | 2 +- .../TKMath/TopLoc/TopLoc_Location.cxx | 84 +- .../TKMath/TopLoc/TopLoc_Location.hxx | 29 +- .../TKMath/TopLoc/TopLoc_Location.lxx | 25 +- .../TopLoc/TopLoc_SListNodeOfItemLocation.hxx | 21 +- .../TopLoc/TopLoc_SListNodeOfItemLocation.lxx | 33 + .../TopLoc/TopLoc_SListOfItemLocation.cxx | 17 +- .../TopLoc/TopLoc_SListOfItemLocation.hxx | 16 +- .../TKernel/GTests/FILES.cmake | 1 + .../TKernel/GTests/TopLoc_Location_Test.cxx | 91 +++ 14 files changed, 212 insertions(+), 837 deletions(-) delete mode 100644 src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx create mode 100644 src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx create mode 100644 src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx diff --git a/src/FoundationClasses/TKMath/GTests/FILES.cmake b/src/FoundationClasses/TKMath/GTests/FILES.cmake index bfd4fd84f0..1df64acae5 100644 --- a/src/FoundationClasses/TKMath/GTests/FILES.cmake +++ b/src/FoundationClasses/TKMath/GTests/FILES.cmake @@ -58,5 +58,4 @@ set(OCCT_TKMath_GTests_FILES PLib_Test.cxx PLib_JacobiPolynomial_Test.cxx PLib_HermitJacobi_Test.cxx - TopLoc_Location_Test.cxx ) diff --git a/src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx b/src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx deleted file mode 100644 index 8cf4fa9f79..0000000000 --- a/src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx +++ /dev/null @@ -1,722 +0,0 @@ -// 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 - -namespace -{ -//! Functor for testing concurrent access to TopLoc_Location::Transformation() -struct TopLocTransformFunctor -{ - TopLocTransformFunctor(const std::vector& theShapeVec) - : myShapeVec(&theShapeVec), - myIsRaceDetected(0) - { - } - - void operator()(size_t i) const - { - if (!myIsRaceDetected) - { - const TopoDS_Vertex& aVertex = TopoDS::Vertex(myShapeVec->at(i)); - gp_Pnt aPoint = BRep_Tool::Pnt(aVertex); - if (aPoint.X() != static_cast(i)) - { - ++myIsRaceDetected; - } - } - } - - const std::vector* myShapeVec; - mutable std::atomic myIsRaceDetected; -}; -} // namespace - -TEST(TopLoc_Location_Test, OCC25545_ConcurrentTransformationAccess) -{ - // Bug OCC25545: TopLoc_Location::Transformation() provokes data races - // This test verifies that concurrent access to TopLoc_Location::Transformation() - // does not cause data races or incorrect geometry results - - // Place vertices in a vector, giving the i-th vertex the - // transformation that translates it on the vector (i,0,0) from the origin - Standard_Integer n = 1000; - std::vector aShapeVec(n); - std::vector aLocVec(n); - TopoDS_Shape aShape = BRepBuilderAPI_MakeVertex(gp::Origin()); - aShapeVec[0] = aShape; - - for (Standard_Integer i = 1; i < n; ++i) - { - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1, 0, 0)); - aLocVec[i] = aLocVec[i - 1] * aTrsf; - aShapeVec[i] = aShape.Moved(aLocVec[i]); - } - - // Evaluator function will access vertices geometry concurrently - TopLocTransformFunctor aFunc(aShapeVec); - - // Process concurrently - OSD_Parallel::For(0, n, aFunc); - - // Verify no data race was detected - EXPECT_EQ(aFunc.myIsRaceDetected, 0) - << "Data race detected in concurrent TopLoc_Location::Transformation() access"; -} - -//================================================================================================= -// Tests for Phase 1 & 2 Optimizations -//================================================================================================= - -TEST(TopLoc_Location_Test, DefaultConstructor_CreatesIdentity) -{ - // Test that default constructor creates identity location - TopLoc_Location aLoc; - - EXPECT_TRUE(aLoc.IsIdentity()) << "Default constructed location should be identity"; - - const gp_Trsf& aTrsf = aLoc.Transformation(); - EXPECT_EQ(aTrsf.Form(), gp_Identity) << "Default location should return identity transformation"; -} - -TEST(TopLoc_Location_Test, IdentityTransformation_ReturnsSameInstance) -{ - // Test that identity locations return the same transformation instance - TopLoc_Location aLoc1; - TopLoc_Location aLoc2; - - const gp_Trsf& aTrsf1 = aLoc1.Transformation(); - const gp_Trsf& aTrsf2 = aLoc2.Transformation(); - - // Both should return reference to the same identity transformation - EXPECT_EQ(&aTrsf1, &aTrsf2) << "Identity locations should share the same transformation instance"; -} - -TEST(TopLoc_Location_Test, Squared_EqualsMultipliedBySelf) -{ - // Test that Squared() equals Multiplied(self) - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); - TopLoc_Location aLoc(aTrsf); - - TopLoc_Location aSquared = aLoc.Squared(); - TopLoc_Location aMultiplied = aLoc.Multiplied(aLoc); - - EXPECT_EQ(aSquared, aMultiplied) << "Squared() should equal Multiplied(self)"; - - // Verify the transformation is actually squared - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aTransformed = anOrigin.Transformed(aSquared.Transformation()); - gp_Pnt anExpected(2.0, 4.0, 6.0); // Double translation - - EXPECT_NEAR(aTransformed.X(), anExpected.X(), 1e-10); - EXPECT_NEAR(aTransformed.Y(), anExpected.Y(), 1e-10); - EXPECT_NEAR(aTransformed.Z(), anExpected.Z(), 1e-10); -} - -TEST(TopLoc_Location_Test, Powered2_UsesSquaredFastPath) -{ - // Test that Powered(2) equals Squared() (verifies fast path) - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(5.0, -3.0, 1.5)); - TopLoc_Location aLoc(aTrsf); - - TopLoc_Location aPowered = aLoc.Powered(2); - TopLoc_Location aSquared = aLoc.Squared(); - - EXPECT_EQ(aPowered, aSquared) << "Powered(2) should equal Squared()"; -} - -TEST(TopLoc_Location_Test, Powered_VariousPowers) -{ - // Test Powered() with various power values - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc(aTrsf); - - // Power 0 should return identity - TopLoc_Location aPow0 = aLoc.Powered(0); - EXPECT_TRUE(aPow0.IsIdentity()) << "Powered(0) should return identity"; - - // Power 1 should return self - TopLoc_Location aPow1 = aLoc.Powered(1); - EXPECT_EQ(aPow1, aLoc) << "Powered(1) should return self"; - - // Power 2 should be double translation - TopLoc_Location aPow2 = aLoc.Powered(2); - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aResult = anOrigin.Transformed(aPow2.Transformation()); - EXPECT_NEAR(aResult.X(), 2.0, 1e-10) << "Powered(2) should double the translation"; - - // Power 3 - TopLoc_Location aPow3 = aLoc.Powered(3); - aResult = anOrigin.Transformed(aPow3.Transformation()); - EXPECT_NEAR(aResult.X(), 3.0, 1e-10) << "Powered(3) should triple the translation"; - - // Negative power (inverse) - TopLoc_Location aPowNeg1 = aLoc.Powered(-1); - TopLoc_Location anInverted = aLoc.Inverted(); - EXPECT_EQ(aPowNeg1, anInverted) << "Powered(-1) should equal Inverted()"; -} - -TEST(TopLoc_Location_Test, SharesNode_DetectsSharedStructure) -{ - // Test that SharesNode correctly detects shared list nodes - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); - TopLoc_Location aLoc1(aTrsf); - - // Copy constructor should share the same nodes - TopLoc_Location aLoc2 = aLoc1; - - EXPECT_EQ(aLoc1, aLoc2) << "Copied location should equal original"; - - // Assignment should also share nodes - TopLoc_Location aLoc3; - aLoc3 = aLoc1; - - EXPECT_EQ(aLoc3, aLoc1) << "Assigned location should equal original"; -} - -TEST(TopLoc_Location_Test, Equality_DifferentTransformations) -{ - // Test that different transformations are not equal - gp_Trsf aTrsf1; - aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc1(aTrsf1); - - gp_Trsf aTrsf2; - aTrsf2.SetTranslation(gp_Vec(0.0, 1.0, 0.0)); - TopLoc_Location aLoc2(aTrsf2); - - EXPECT_NE(aLoc1, aLoc2) << "Different transformations should not be equal"; - EXPECT_TRUE(aLoc1.IsDifferent(aLoc2)) - << "IsDifferent should return true for different transformations"; -} - -TEST(TopLoc_Location_Test, Multiplication_Composition) -{ - // Test location multiplication (composition) - gp_Trsf aTrsf1; - aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc1(aTrsf1); - - gp_Trsf aTrsf2; - aTrsf2.SetTranslation(gp_Vec(0.0, 2.0, 0.0)); - TopLoc_Location aLoc2(aTrsf2); - - TopLoc_Location aComposed = aLoc1 * aLoc2; - - // Apply composed transformation - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aResult = anOrigin.Transformed(aComposed.Transformation()); - - EXPECT_NEAR(aResult.X(), 1.0, 1e-10); - EXPECT_NEAR(aResult.Y(), 2.0, 1e-10); - EXPECT_NEAR(aResult.Z(), 0.0, 1e-10); -} - -TEST(TopLoc_Location_Test, Inverted_ProducesInverse) -{ - // Test that Location * Inverted() = Identity - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(5.0, -3.0, 2.0)); - TopLoc_Location aLoc(aTrsf); - - TopLoc_Location anInv = aLoc.Inverted(); - TopLoc_Location anIdentity = aLoc * anInv; - - EXPECT_TRUE(anIdentity.IsIdentity()) << "Location * Inverted() should produce identity"; - - // Also test in reverse order - TopLoc_Location anIdentity2 = anInv * aLoc; - EXPECT_TRUE(anIdentity2.IsIdentity()) << "Inverted() * Location should also produce identity"; -} - -TEST(TopLoc_Location_Test, Divided_EqualsMultipliedByInverse) -{ - // Test that Divided equals Multiplied by inverse - gp_Trsf aTrsf1; - aTrsf1.SetTranslation(gp_Vec(3.0, 4.0, 5.0)); - TopLoc_Location aLoc1(aTrsf1); - - gp_Trsf aTrsf2; - aTrsf2.SetTranslation(gp_Vec(1.0, 1.0, 1.0)); - TopLoc_Location aLoc2(aTrsf2); - - TopLoc_Location aDivided = aLoc1 / aLoc2; - TopLoc_Location aMultiplied = aLoc1 * aLoc2.Inverted(); - - EXPECT_EQ(aDivided, aMultiplied) << "Divided should equal Multiplied by Inverted"; -} - -TEST(TopLoc_Location_Test, Predivided_EqualsInverseMultiplied) -{ - // Test Predivided = Other.Inverted() * this - gp_Trsf aTrsf1; - aTrsf1.SetTranslation(gp_Vec(2.0, 3.0, 4.0)); - TopLoc_Location aLoc1(aTrsf1); - - gp_Trsf aTrsf2; - aTrsf2.SetTranslation(gp_Vec(1.0, 1.0, 1.0)); - TopLoc_Location aLoc2(aTrsf2); - - TopLoc_Location aPredivided = aLoc1.Predivided(aLoc2); - TopLoc_Location anExpected = aLoc2.Inverted() * aLoc1; - - EXPECT_EQ(aPredivided, anExpected) << "Predivided should equal Other.Inverted() * this"; -} - -TEST(TopLoc_Location_Test, HashCode_ConsistentForEqualLocations) -{ - // Test that locations sharing the same structure have the same hash code - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); - TopLoc_Location aLoc1(aTrsf); - - // Copy constructor shares the same underlying structure - TopLoc_Location aLoc2 = aLoc1; - - EXPECT_EQ(aLoc1.HashCode(), aLoc2.HashCode()) - << "Copied locations (sharing structure) should have the same hash code"; - - // Identity locations should have the same hash code - TopLoc_Location anId1; - TopLoc_Location anId2; - EXPECT_EQ(anId1.HashCode(), anId2.HashCode()) - << "Identity locations should have the same hash code"; - EXPECT_EQ(anId1.HashCode(), static_cast(0)) << "Identity location hash should be 0"; -} - -TEST(TopLoc_Location_Test, HashCode_DifferentForDifferentLocations) -{ - // Test that different locations (likely) have different hash codes - gp_Trsf aTrsf1; - aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc1(aTrsf1); - - gp_Trsf aTrsf2; - aTrsf2.SetTranslation(gp_Vec(0.0, 1.0, 0.0)); - TopLoc_Location aLoc2(aTrsf2); - - // Hash codes should (very likely) be different - // Note: Hash collision is theoretically possible but highly unlikely - EXPECT_NE(aLoc1.HashCode(), aLoc2.HashCode()) - << "Different locations should (likely) have different hash codes"; -} - -TEST(TopLoc_Location_Test, ConstCorrectness_TransformationAccess) -{ - // Test const correctness of Transformation() access - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); - const TopLoc_Location aLoc(aTrsf); - - // This should compile - Transformation() is const - const gp_Trsf& aTrsfRef = aLoc.Transformation(); - - EXPECT_EQ(aTrsfRef.Form(), gp_Translation); - - // Test identity case - const TopLoc_Location anIdLoc; - const gp_Trsf& anIdTrsf = anIdLoc.Transformation(); - - EXPECT_EQ(anIdTrsf.Form(), gp_Identity); -} - -TEST(TopLoc_Location_Test, Rotation_Composition) -{ - // Test rotation transformations - gp_Trsf aRot; - gp_Ax1 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)); - aRot.SetRotation(anAxis, M_PI / 2.0); // 90 degrees around Z - - TopLoc_Location aLoc(aRot); - TopLoc_Location aSquared = aLoc.Squared(); - - // Point at (1,0,0) rotated 90 degrees twice should be at (-1,0,0) - gp_Pnt aPnt(1, 0, 0); - gp_Pnt aResult = aPnt.Transformed(aSquared.Transformation()); - - EXPECT_NEAR(aResult.X(), -1.0, 1e-10); - EXPECT_NEAR(aResult.Y(), 0.0, 1e-10); - EXPECT_NEAR(aResult.Z(), 0.0, 1e-10); -} - -TEST(TopLoc_Location_Test, Clear_ResetsToIdentity) -{ - // Test that Clear() resets location to identity - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); - TopLoc_Location aLoc(aTrsf); - - EXPECT_FALSE(aLoc.IsIdentity()) << "Location should not be identity before Clear()"; - - aLoc.Clear(); - - EXPECT_TRUE(aLoc.IsIdentity()) << "Location should be identity after Clear()"; -} - -TEST(TopLoc_Location_Test, Identity_Method) -{ - // Test Identity() method - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 2.0, 3.0)); - TopLoc_Location aLoc(aTrsf); - - EXPECT_FALSE(aLoc.IsIdentity()); - - aLoc.Identity(); - - EXPECT_TRUE(aLoc.IsIdentity()) << "Identity() should reset location to identity"; -} - -//================================================================================================= -// Additional Edge Case Tests -//================================================================================================= - -TEST(TopLoc_Location_Test, ChainedMultiplication_MultipleLocations) -{ - // Test multiple location multiplications - gp_Trsf aTrsf1, aTrsf2, aTrsf3; - aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - aTrsf2.SetTranslation(gp_Vec(0.0, 1.0, 0.0)); - aTrsf3.SetTranslation(gp_Vec(0.0, 0.0, 1.0)); - - TopLoc_Location aLoc1(aTrsf1); - TopLoc_Location aLoc2(aTrsf2); - TopLoc_Location aLoc3(aTrsf3); - - // Chain multiplication - TopLoc_Location aResult = aLoc1 * aLoc2 * aLoc3; - - // Apply to point - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aTransformed = anOrigin.Transformed(aResult.Transformation()); - - EXPECT_NEAR(aTransformed.X(), 1.0, 1e-10); - EXPECT_NEAR(aTransformed.Y(), 1.0, 1e-10); - EXPECT_NEAR(aTransformed.Z(), 1.0, 1e-10); -} - -TEST(TopLoc_Location_Test, IdentityMultiplication_NeutralElement) -{ - // Test that identity is neutral element for multiplication - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(5.0, 3.0, 1.0)); - TopLoc_Location aLoc(aTrsf); - TopLoc_Location anIdentity; - - // Identity * Location = Location - TopLoc_Location aResult1 = anIdentity * aLoc; - EXPECT_EQ(aResult1, aLoc) << "Identity * Location should equal Location"; - - // Location * Identity = Location - TopLoc_Location aResult2 = aLoc * anIdentity; - EXPECT_EQ(aResult2, aLoc) << "Location * Identity should equal Location"; -} - -TEST(TopLoc_Location_Test, CopySemantics) -{ - // Test copy construction and assignment - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(2.0, 3.0, 4.0)); - TopLoc_Location anOriginal(aTrsf); - - // Copy construction - TopLoc_Location aCopied(anOriginal); - EXPECT_EQ(aCopied, anOriginal); - EXPECT_EQ(aCopied.HashCode(), anOriginal.HashCode()); - - // Copy assignment - TopLoc_Location anAssigned; - anAssigned = anOriginal; - EXPECT_EQ(anAssigned, anOriginal); - - // All three should be equal (share structure) - EXPECT_EQ(aCopied, anAssigned); -} - -TEST(TopLoc_Location_Test, ScaleTransformation_Composition) -{ - // Test scale transformations - gp_Trsf aScale; - aScale.SetScale(gp_Pnt(0, 0, 0), 2.0); - - TopLoc_Location aLoc(aScale); - TopLoc_Location aSquared = aLoc.Squared(); - - // Point scaled by 2, then by 2 again = scaled by 4 - gp_Pnt aPnt(1, 1, 1); - gp_Pnt aResult = aPnt.Transformed(aSquared.Transformation()); - - EXPECT_NEAR(aResult.X(), 4.0, 1e-10); - EXPECT_NEAR(aResult.Y(), 4.0, 1e-10); - EXPECT_NEAR(aResult.Z(), 4.0, 1e-10); -} - -TEST(TopLoc_Location_Test, ComplexTransformation_TranslationAndRotation) -{ - // Test combination of translation and rotation - gp_Trsf aTrans; - aTrans.SetTranslation(gp_Vec(10.0, 0.0, 0.0)); - - gp_Trsf aRot; - gp_Ax1 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)); - aRot.SetRotation(anAxis, M_PI); // 180 degrees - - TopLoc_Location aLocTrans(aTrans); - TopLoc_Location aLocRot(aRot); - - // Translate then rotate - TopLoc_Location aComposed = aLocRot * aLocTrans; - - // Point at origin, translate to (10,0,0), then rotate 180 degrees -> (-10,0,0) - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aResult = anOrigin.Transformed(aComposed.Transformation()); - - EXPECT_NEAR(aResult.X(), -10.0, 1e-9); - EXPECT_NEAR(aResult.Y(), 0.0, 1e-9); - EXPECT_NEAR(aResult.Z(), 0.0, 1e-9); -} - -TEST(TopLoc_Location_Test, PoweredWithLargePower) -{ - // Test Powered() with larger power value - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc(aTrsf); - - // Power of 10 - TopLoc_Location aPow10 = aLoc.Powered(10); - - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aResult = anOrigin.Transformed(aPow10.Transformation()); - - EXPECT_NEAR(aResult.X(), 10.0, 1e-9) << "Powered(10) should translate 10 units"; -} - -TEST(TopLoc_Location_Test, SelfMultiplication_MultipleIterations) -{ - // Test repeated self-multiplication - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc(aTrsf); - - TopLoc_Location aResult = aLoc; - for (int i = 0; i < 5; ++i) - { - aResult = aResult * aLoc; - } - - // Should be 6 times the original (1 + 5 multiplications) - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aTransformed = anOrigin.Transformed(aResult.Transformation()); - - EXPECT_NEAR(aTransformed.X(), 6.0, 1e-10); -} - -TEST(TopLoc_Location_Test, AssociativityOfMultiplication) -{ - // Test that (A * B) * C = A * (B * C) - gp_Trsf aTrsf1, aTrsf2, aTrsf3; - aTrsf1.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - aTrsf2.SetTranslation(gp_Vec(0.0, 2.0, 0.0)); - aTrsf3.SetTranslation(gp_Vec(0.0, 0.0, 3.0)); - - TopLoc_Location aLocA(aTrsf1); - TopLoc_Location aLocB(aTrsf2); - TopLoc_Location aLocC(aTrsf3); - - TopLoc_Location aLeft = (aLocA * aLocB) * aLocC; - TopLoc_Location aRight = aLocA * (aLocB * aLocC); - - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aResultLeft = anOrigin.Transformed(aLeft.Transformation()); - gp_Pnt aResultRight = anOrigin.Transformed(aRight.Transformation()); - - EXPECT_NEAR(aResultLeft.X(), aResultRight.X(), 1e-10); - EXPECT_NEAR(aResultLeft.Y(), aResultRight.Y(), 1e-10); - EXPECT_NEAR(aResultLeft.Z(), aResultRight.Z(), 1e-10); -} - -TEST(TopLoc_Location_Test, InversionIdempotence) -{ - // Test that (L.Inverted()).Inverted() = L - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(3.0, 4.0, 5.0)); - TopLoc_Location aLoc(aTrsf); - - TopLoc_Location aDoubleInverted = aLoc.Inverted().Inverted(); - - gp_Pnt aPnt(1, 1, 1); - gp_Pnt aResult1 = aPnt.Transformed(aLoc.Transformation()); - gp_Pnt aResult2 = aPnt.Transformed(aDoubleInverted.Transformation()); - - EXPECT_NEAR(aResult1.X(), aResult2.X(), 1e-10); - EXPECT_NEAR(aResult1.Y(), aResult2.Y(), 1e-10); - EXPECT_NEAR(aResult1.Z(), aResult2.Z(), 1e-10); -} - -//================================================================================================= -// Tests for Phase 3: Binary Exponentiation Optimization -//================================================================================================= - -TEST(TopLoc_Location_Test, BinaryExponentiation_PowerOf2) -{ - // Test binary exponentiation for powers of 2 (4, 8, 16) - // These should be most efficient: L^8 requires only 3 squarings instead of 7 multiplications - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc(aTrsf); - - // Test power of 4 (2^2) - TopLoc_Location aPow4 = aLoc.Powered(4); - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aResult = anOrigin.Transformed(aPow4.Transformation()); - EXPECT_NEAR(aResult.X(), 4.0, 1e-9) << "Powered(4) should translate 4 units"; - - // Test power of 8 (2^3) - TopLoc_Location aPow8 = aLoc.Powered(8); - aResult = anOrigin.Transformed(aPow8.Transformation()); - EXPECT_NEAR(aResult.X(), 8.0, 1e-9) << "Powered(8) should translate 8 units"; - - // Test power of 16 (2^4) - TopLoc_Location aPow16 = aLoc.Powered(16); - aResult = anOrigin.Transformed(aPow16.Transformation()); - EXPECT_NEAR(aResult.X(), 16.0, 1e-9) << "Powered(16) should translate 16 units"; -} - -TEST(TopLoc_Location_Test, BinaryExponentiation_OddPowers) -{ - // Test binary exponentiation for odd powers (5, 7, 9) - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc(aTrsf); - - gp_Pnt anOrigin(0, 0, 0); - - // Test power of 5 - TopLoc_Location aPow5 = aLoc.Powered(5); - gp_Pnt aResult = anOrigin.Transformed(aPow5.Transformation()); - EXPECT_NEAR(aResult.X(), 5.0, 1e-9) << "Powered(5) should translate 5 units"; - - // Test power of 7 - TopLoc_Location aPow7 = aLoc.Powered(7); - aResult = anOrigin.Transformed(aPow7.Transformation()); - EXPECT_NEAR(aResult.X(), 7.0, 1e-9) << "Powered(7) should translate 7 units"; - - // Test power of 9 - TopLoc_Location aPow9 = aLoc.Powered(9); - aResult = anOrigin.Transformed(aPow9.Transformation()); - EXPECT_NEAR(aResult.X(), 9.0, 1e-9) << "Powered(9) should translate 9 units"; -} - -TEST(TopLoc_Location_Test, BinaryExponentiation_VeryLargePower) -{ - // Test with very large power to verify efficiency improvement - // L^64 with binary exponentiation: only 6 squarings - // L^64 with naive: 63 multiplications - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(0.5, 0.0, 0.0)); - TopLoc_Location aLoc(aTrsf); - - TopLoc_Location aPow64 = aLoc.Powered(64); - - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aResult = anOrigin.Transformed(aPow64.Transformation()); - - EXPECT_NEAR(aResult.X(), 32.0, 1e-8) << "Powered(64) should translate 32 units (64 * 0.5)"; -} - -TEST(TopLoc_Location_Test, BinaryExponentiation_NegativePowers) -{ - // Test binary exponentiation for negative powers - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(1.0, 0.0, 0.0)); - TopLoc_Location aLoc(aTrsf); - - // Test power of -4 (should be inverse of power 4) - TopLoc_Location aPowNeg4 = aLoc.Powered(-4); - TopLoc_Location aPow4 = aLoc.Powered(4); - - TopLoc_Location aIdentity = aPowNeg4 * aPow4; - EXPECT_TRUE(aIdentity.IsIdentity()) << "L^(-4) * L^4 should produce identity"; - - // Verify the transformation - gp_Pnt anOrigin(0, 0, 0); - gp_Pnt aResult = anOrigin.Transformed(aPowNeg4.Transformation()); - EXPECT_NEAR(aResult.X(), -4.0, 1e-9) << "Powered(-4) should translate -4 units"; -} - -TEST(TopLoc_Location_Test, BinaryExponentiation_RotationPowers) -{ - // Test binary exponentiation with rotation transformations - // 45 degrees rotation, power of 8 should equal 360 degrees (identity) - gp_Trsf aRot; - gp_Ax1 anAxis(gp_Pnt(0, 0, 0), gp_Dir(0, 0, 1)); - aRot.SetRotation(anAxis, M_PI / 4.0); // 45 degrees - - TopLoc_Location aLoc(aRot); - TopLoc_Location aPow8 = aLoc.Powered(8); // 8 * 45 degrees = 360 degrees - - // Should be close to identity (full rotation) - gp_Pnt aPnt(1, 0, 0); - gp_Pnt aResult = aPnt.Transformed(aPow8.Transformation()); - - EXPECT_NEAR(aResult.X(), 1.0, 1e-8); - EXPECT_NEAR(aResult.Y(), 0.0, 1e-8); - EXPECT_NEAR(aResult.Z(), 0.0, 1e-8); -} - -TEST(TopLoc_Location_Test, BinaryExponentiation_MixedEvenOdd) -{ - // Test a mix of even and odd powers to verify the algorithm handles both cases - gp_Trsf aTrsf; - aTrsf.SetTranslation(gp_Vec(2.0, 0.0, 0.0)); - TopLoc_Location aLoc(aTrsf); - - gp_Pnt anOrigin(0, 0, 0); - - // Test 6 (even) - TopLoc_Location aPow6 = aLoc.Powered(6); - gp_Pnt aResult = anOrigin.Transformed(aPow6.Transformation()); - EXPECT_NEAR(aResult.X(), 12.0, 1e-9); - - // Test 11 (odd) - TopLoc_Location aPow11 = aLoc.Powered(11); - aResult = anOrigin.Transformed(aPow11.Transformation()); - EXPECT_NEAR(aResult.X(), 22.0, 1e-9); - - // Test 15 (odd) - TopLoc_Location aPow15 = aLoc.Powered(15); - aResult = anOrigin.Transformed(aPow15.Transformation()); - EXPECT_NEAR(aResult.X(), 30.0, 1e-9); -} diff --git a/src/FoundationClasses/TKMath/TopLoc/FILES.cmake b/src/FoundationClasses/TKMath/TopLoc/FILES.cmake index e70862c03b..44593cb6e0 100644 --- a/src/FoundationClasses/TKMath/TopLoc/FILES.cmake +++ b/src/FoundationClasses/TKMath/TopLoc/FILES.cmake @@ -14,6 +14,7 @@ set(OCCT_TopLoc_FILES TopLoc_MapOfLocation.hxx TopLoc_SListNodeOfItemLocation.cxx TopLoc_SListNodeOfItemLocation.hxx + TopLoc_SListNodeOfItemLocation.lxx TopLoc_SListOfItemLocation.cxx TopLoc_SListOfItemLocation.hxx ) diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx index 79279209e0..f8c8abdd31 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx @@ -44,13 +44,13 @@ public: Standard_EXPORT TopLoc_Datum3D(const gp_Trsf& T); //! Returns a gp_Trsf which, when applied to this datum, produces the default datum. - const gp_Trsf& Transformation() const noexcept { return myTrsf; } + const gp_Trsf& Transformation() const { return myTrsf; } //! Returns a gp_Trsf which, when applied to this datum, produces the default datum. - const gp_Trsf& Trsf() const noexcept { return myTrsf; } + const gp_Trsf& Trsf() const { return myTrsf; } //! Return transformation form. - gp_TrsfForm Form() const noexcept { return myTrsf.Form(); } + gp_TrsfForm Form() const { return myTrsf.Form(); } //! Dumps the content of me into the stream Standard_EXPORT void DumpJson(Standard_OStream& theOStream, Standard_Integer theDepth = -1) const; diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx index bf374e4059..9cbd5791fe 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx @@ -24,7 +24,7 @@ TopLoc_ItemLocation::TopLoc_ItemLocation(const Handle(TopLoc_Datum3D)& D, const Standard_Integer P) : myDatum(D), myPower(P), - myTrsf(P == 1 ? D->Transformation() : D->Transformation().Powered(P)) + myTrsf(D->Transformation().Powered(P)) { } diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx index 0ada67b56f..f78330be47 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx @@ -20,11 +20,14 @@ #include #include #include -#include #include //================================================================================================= +TopLoc_Location::TopLoc_Location() {} + +//================================================================================================= + TopLoc_Location::TopLoc_Location(const Handle(TopLoc_Datum3D)& D) { myItems.Construct(TopLoc_ItemLocation(D, 1)); @@ -105,6 +108,26 @@ TopLoc_Location TopLoc_Location::Multiplied(const TopLoc_Location& Other) const return result; } +//======================================================================= +// function : Divided +// purpose : operator / this*Other.Inverted() +//======================================================================= + +TopLoc_Location TopLoc_Location::Divided(const TopLoc_Location& Other) const +{ + return Multiplied(Other.Inverted()); +} + +//======================================================================= +// function : Predivided +// purpose : return Other.Inverted() * this +//======================================================================= + +TopLoc_Location TopLoc_Location::Predivided(const TopLoc_Location& Other) const +{ + return Other.Inverted().Multiplied(*this); +} + //================================================================================================= TopLoc_Location TopLoc_Location::Powered(const Standard_Integer pwr) const @@ -115,8 +138,6 @@ TopLoc_Location TopLoc_Location::Powered(const Standard_Integer pwr) const return *this; if (pwr == 0) return TopLoc_Location(); - if (pwr == 2) - return Squared(); // Fast path for most common case // optimisation when just one element if (myItems.Tail().IsEmpty()) @@ -126,62 +147,41 @@ TopLoc_Location TopLoc_Location::Powered(const Standard_Integer pwr) const return result; } - // Binary exponentiation for efficient computation: O(log n) instead of O(n) if (pwr > 0) - { - if (pwr % 2 == 0) - { - // Even power: L^n = (L^(n/2))^2 - return Powered(pwr / 2).Squared(); - } - else - { - // Odd power: L^n = L * L^(n-1) - return Multiplied(Powered(pwr - 1)); - } - } + return Multiplied(Powered(pwr - 1)); else - { return Inverted().Powered(-pwr); - } } //================================================================================================= -// Two locations are Equal if the Items have the same LocalValues and Powers +// two locations are Equal if the Items have the same LocalValues and Powers +// this is a recursive function to test it -Standard_Boolean TopLoc_Location::IsEqual(const TopLoc_Location& theOther) const noexcept +Standard_Boolean TopLoc_Location::IsEqual(const TopLoc_Location& Other) const { - // Fast path: same node means equal (handles identity == identity case too) - if (myItems.Node() == theOther.myItems.Node()) + const void** p = (const void**)&myItems; + const void** q = (const void**)&Other.myItems; + if (*p == *q) { return Standard_True; } - // Fast rejection: different cached hashes means not equal - if (myItems.HashCode() != theOther.myItems.HashCode()) + if (IsIdentity() || Other.IsIdentity()) { return Standard_False; } - // Hash collision: need element-by-element comparison - TopLoc_SListOfItemLocation anIt1 = myItems; - TopLoc_SListOfItemLocation anIt2 = theOther.myItems; - while (anIt1.More() && anIt2.More()) + if (FirstDatum() != Other.FirstDatum()) { - if (anIt1.Node() == anIt2.Node()) - { - return Standard_True; // Shared tail - } - const TopLoc_ItemLocation& aItem1 = anIt1.Value(); - const TopLoc_ItemLocation& aItem2 = anIt2.Value(); - if (aItem1.myDatum != aItem2.myDatum || aItem1.myPower != aItem2.myPower) - { - return Standard_False; - } - anIt1.Next(); - anIt2.Next(); + return Standard_False; + } + if (FirstPower() != Other.FirstPower()) + { + return Standard_False; + } + else + { + return NextLocation() == Other.NextLocation(); } - // Equal only if both exhausted - return !anIt1.More() && !anIt2.More(); } //================================================================================================= diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx index 6d566b71bb..fcbe4d6fb3 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx @@ -39,7 +39,7 @@ public: //! Constructs an empty local coordinate system object. //! Note: A Location constructed from a default datum is said to be "empty". - TopLoc_Location() noexcept = default; + Standard_EXPORT TopLoc_Location(); //! Constructs the local coordinate system object defined //! by the transformation T. T invokes in turn, a TopLoc_Datum3D object. @@ -52,10 +52,10 @@ public: Standard_EXPORT TopLoc_Location(const Handle(TopLoc_Datum3D)& D); //! Returns true if this location is equal to the Identity transformation. - Standard_Boolean IsIdentity() const noexcept; + Standard_Boolean IsIdentity() const; //! Resets this location to the Identity transformation. - void Identity() noexcept; + void Identity(); //! Returns the first elementary datum of the //! Location. Use the NextLocation function recursively to access @@ -98,10 +98,7 @@ public: } //! Returns / . - Standard_NODISCARD TopLoc_Location Divided(const TopLoc_Location& Other) const - { - return Multiplied(Other.Inverted()); - } + Standard_NODISCARD Standard_EXPORT TopLoc_Location Divided(const TopLoc_Location& Other) const; Standard_NODISCARD TopLoc_Location operator/(const TopLoc_Location& Other) const { @@ -109,30 +106,23 @@ public: } //! Returns .Inverted() * . - Standard_NODISCARD TopLoc_Location Predivided(const TopLoc_Location& Other) const - { - return Other.Inverted().Multiplied(*this); - } + Standard_NODISCARD Standard_EXPORT TopLoc_Location Predivided(const TopLoc_Location& Other) const; //! Returns me at the power . If is zero //! returns Identity. can be lower than zero //! (usual meaning for powers). Standard_NODISCARD Standard_EXPORT TopLoc_Location Powered(const Standard_Integer pwr) const; - //! Returns the square of this location (optimized version of Powered(2)). - //! This is the most common power operation in actual usage. - Standard_NODISCARD TopLoc_Location Squared() const { return Multiplied(*this); } - //! Returns a hashed value for this local coordinate system. This value is used, with map tables, //! to store and retrieve the object easily //! @return a computed hash code - size_t HashCode() const noexcept; + size_t HashCode() const; //! Returns true if this location and the location Other //! have the same elementary data, i.e. contain the same //! series of TopLoc_Datum3D and respective powers. //! This method is an alias for operator ==. - Standard_EXPORT Standard_Boolean IsEqual(const TopLoc_Location& Other) const noexcept; + Standard_EXPORT Standard_Boolean IsEqual(const TopLoc_Location& Other) const; Standard_Boolean operator==(const TopLoc_Location& Other) const { return IsEqual(Other); } @@ -151,10 +141,11 @@ public: Standard_EXPORT void ShallowDump(Standard_OStream& S) const; //! Clear myItems - void Clear() noexcept { myItems.Clear(); } + void Clear() { myItems.Clear(); } - static constexpr Standard_Real ScalePrec() { return 1.e-14; } + static Standard_Real ScalePrec() { return 1.e-14; } +protected: private: TopLoc_SListOfItemLocation myItems; }; diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx index e6a6519c4c..bfdc712186 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx @@ -21,14 +21,14 @@ //================================================================================================= -inline Standard_Boolean TopLoc_Location::IsIdentity() const noexcept +inline Standard_Boolean TopLoc_Location::IsIdentity() const { return myItems.IsEmpty(); } //================================================================================================= -inline void TopLoc_Location::Identity() noexcept +inline void TopLoc_Location::Identity() { myItems.Clear(); } @@ -56,10 +56,25 @@ inline const TopLoc_Location& TopLoc_Location::NextLocation() const //================================================================================================= -inline size_t TopLoc_Location::HashCode() const noexcept +inline size_t TopLoc_Location::HashCode() const { - // Use cached hash from the list node - return myItems.HashCode(); + // Hashing base on IsEqual function + if (myItems.IsEmpty()) + { + return 0; + } + size_t aHash = opencascade::MurmurHash::optimalSeed(); + TopLoc_SListOfItemLocation items = myItems; + size_t aCombined[3]; + while (items.More()) + { + aCombined[0] = std::hash{}(items.Value().myDatum); + aCombined[1] = opencascade::hash(items.Value().myPower); + aCombined[2] = aHash; + aHash = opencascade::hashBytes(aCombined, sizeof(aCombined)); + items.Next(); + } + return aHash; } //================================================================================================= diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx index d8cd5ac818..d364e57c29 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx @@ -31,23 +31,11 @@ class TopLoc_SListNodeOfItemLocation : public Standard_Transient public: TopLoc_SListNodeOfItemLocation(const TopLoc_ItemLocation& I, - const TopLoc_SListOfItemLocation& aTail, - size_t theHash) - : myTail(aTail), - myValue(I), - myHash(theHash) - { - } + const TopLoc_SListOfItemLocation& aTail); - TopLoc_SListOfItemLocation& Tail() { return myTail; } + TopLoc_SListOfItemLocation& Tail() const; - const TopLoc_SListOfItemLocation& Tail() const { return myTail; } - - TopLoc_ItemLocation& Value() { return myValue; } - - const TopLoc_ItemLocation& Value() const { return myValue; } - - size_t HashCode() const noexcept { return myHash; } + TopLoc_ItemLocation& Value() const; DEFINE_STANDARD_RTTIEXT(TopLoc_SListNodeOfItemLocation, Standard_Transient) @@ -55,7 +43,8 @@ protected: private: TopLoc_SListOfItemLocation myTail; TopLoc_ItemLocation myValue; - size_t myHash; }; +#include + #endif // _TopLoc_SListNodeOfItemLocation_HeaderFile diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx new file mode 100644 index 0000000000..d25c20ef02 --- /dev/null +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx @@ -0,0 +1,33 @@ +// Copyright (c) 1998-1999 Matra Datavision +// Copyright (c) 1999-2014 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 + +inline TopLoc_SListNodeOfItemLocation::TopLoc_SListNodeOfItemLocation( + const TopLoc_ItemLocation& I, + const TopLoc_SListOfItemLocation& T) + : myTail(T), + myValue(I) +{ +} + +inline TopLoc_SListOfItemLocation& TopLoc_SListNodeOfItemLocation::Tail() const +{ + return (TopLoc_SListOfItemLocation&)myTail; +} + +inline TopLoc_ItemLocation& TopLoc_SListNodeOfItemLocation::Value() const +{ + return (TopLoc_ItemLocation&)myValue; +} diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx index 92ecae1454..1ba89df5d2 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx @@ -15,24 +15,16 @@ // commercial license or contractual agreement. #include -#include #include #include #include -#include //================================================================================================= TopLoc_SListOfItemLocation::TopLoc_SListOfItemLocation(const TopLoc_ItemLocation& anItem, const TopLoc_SListOfItemLocation& aTail) + : myNode(new TopLoc_SListNodeOfItemLocation(anItem, aTail)) { - // Compute hash combining item's datum, power, and tail's cached hash - const size_t aCombined[3] = {std::hash{}(anItem.myDatum), - static_cast(anItem.myPower), - aTail.HashCode()}; - const size_t aHash = opencascade::hashBytes(aCombined, sizeof(aCombined)); - - myNode = new TopLoc_SListNodeOfItemLocation(anItem, aTail, aHash); if (!myNode->Tail().IsEmpty()) { const gp_Trsf& aT = myNode->Tail().Value().myTrsf; @@ -70,10 +62,3 @@ const TopLoc_SListOfItemLocation& TopLoc_SListOfItemLocation::Tail() const else return *this; } - -//================================================================================================= - -size_t TopLoc_SListOfItemLocation::HashCode() const noexcept -{ - return myNode.IsNull() ? 0 : myNode->HashCode(); -} diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx index cbcc7bac61..51f9e31b76 100644 --- a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx +++ b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx @@ -46,7 +46,7 @@ public: DEFINE_STANDARD_ALLOC //! Creates an empty List. - TopLoc_SListOfItemLocation() = default; + TopLoc_SListOfItemLocation() {} //! Creates a List with as value and as tail. Standard_EXPORT TopLoc_SListOfItemLocation(const TopLoc_ItemLocation& anItem, @@ -82,10 +82,10 @@ public: } //! Return true if this list is empty - Standard_Boolean IsEmpty() const noexcept { return myNode.IsNull(); } + Standard_Boolean IsEmpty() const { return myNode.IsNull(); } //! Sets the list to be empty. - void Clear() noexcept { myNode.Nullify(); } + void Clear() { myNode.Nullify(); } //! Destructor ~TopLoc_SListOfItemLocation() { Clear(); } @@ -98,14 +98,6 @@ public: //! list the tail is the list itself. Standard_EXPORT const TopLoc_SListOfItemLocation& Tail() const; - //! Returns the cached hash of this list. - //! For an empty list, returns 0. - Standard_EXPORT size_t HashCode() const noexcept; - - //! Returns the underlying node handle. - //! Used for fast identity comparison between lists. - const Handle(TopLoc_SListNodeOfItemLocation)& Node() const noexcept { return myNode; } - //! Replaces the list by a list with as Value //! and the list as tail. void Construct(const TopLoc_ItemLocation& anItem) @@ -118,7 +110,7 @@ public: //! Returns True if the iterator has a current value. //! This is !IsEmpty() - Standard_Boolean More() const noexcept { return !IsEmpty(); } + Standard_Boolean More() const { return !IsEmpty(); } //! Moves the iterator to the next object in the list. //! If the iterator is empty it will stay empty. This is ToTail() diff --git a/src/FoundationClasses/TKernel/GTests/FILES.cmake b/src/FoundationClasses/TKernel/GTests/FILES.cmake index 2f1a9831f1..5bc29c2ab9 100644 --- a/src/FoundationClasses/TKernel/GTests/FILES.cmake +++ b/src/FoundationClasses/TKernel/GTests/FILES.cmake @@ -35,5 +35,6 @@ set(OCCT_TKernel_GTests_FILES Standard_Handle_Test.cxx TCollection_AsciiString_Test.cxx TCollection_ExtendedString_Test.cxx + TopLoc_Location_Test.cxx UnitsAPI_Test.cxx ) diff --git a/src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx b/src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx new file mode 100644 index 0000000000..890e3fa85b --- /dev/null +++ b/src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx @@ -0,0 +1,91 @@ +// 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 + +namespace +{ +//! Functor for testing concurrent access to TopLoc_Location::Transformation() +struct TopLocTransformFunctor +{ + TopLocTransformFunctor(const std::vector& theShapeVec) + : myShapeVec(&theShapeVec), + myIsRaceDetected(0) + { + } + + void operator()(size_t i) const + { + if (!myIsRaceDetected) + { + const TopoDS_Vertex& aVertex = TopoDS::Vertex(myShapeVec->at(i)); + gp_Pnt aPoint = BRep_Tool::Pnt(aVertex); + if (aPoint.X() != static_cast(i)) + { + ++myIsRaceDetected; + } + } + } + + const std::vector* myShapeVec; + mutable std::atomic myIsRaceDetected; +}; +} // namespace + +TEST(TopLoc_Location_Test, OCC25545_ConcurrentTransformationAccess) +{ + // Bug OCC25545: TopLoc_Location::Transformation() provokes data races + // This test verifies that concurrent access to TopLoc_Location::Transformation() + // does not cause data races or incorrect geometry results + + // Place vertices in a vector, giving the i-th vertex the + // transformation that translates it on the vector (i,0,0) from the origin + Standard_Integer n = 1000; + std::vector aShapeVec(n); + std::vector aLocVec(n); + TopoDS_Shape aShape = BRepBuilderAPI_MakeVertex(gp::Origin()); + aShapeVec[0] = aShape; + + for (Standard_Integer i = 1; i < n; ++i) + { + gp_Trsf aTrsf; + aTrsf.SetTranslation(gp_Vec(1, 0, 0)); + aLocVec[i] = aLocVec[i - 1] * aTrsf; + aShapeVec[i] = aShape.Moved(aLocVec[i]); + } + + // Evaluator function will access vertices geometry concurrently + TopLocTransformFunctor aFunc(aShapeVec); + + // Process concurrently + OSD_Parallel::For(0, n, aFunc); + + // Verify no data race was detected + EXPECT_EQ(aFunc.myIsRaceDetected, 0) + << "Data race detected in concurrent TopLoc_Location::Transformation() access"; +} -- 2.39.5