]> OCCT Git - occt.git/commitdiff
Foundation Classes - TopLoc package update (#849)
authorPasukhin Dmitry <dpasukhi@opencascade.com>
Tue, 25 Nov 2025 16:07:49 +0000 (16:07 +0000)
committerGitHub <noreply@github.com>
Tue, 25 Nov 2025 16:07:49 +0000 (16:07 +0000)
- Performance optimizations including binary exponentiation for `Powered()`, optimized hash code computation, and fast-path optimizations for common operations
- Code modernization with `noexcept` qualifiers, `constexpr` for compile-time constants, and inline wrapper methods

14 files changed:
src/FoundationClasses/TKMath/GTests/FILES.cmake
src/FoundationClasses/TKMath/GTests/TopLoc_Location_Test.cxx [new file with mode: 0644]
src/FoundationClasses/TKMath/TopLoc/FILES.cmake
src/FoundationClasses/TKMath/TopLoc/TopLoc_Datum3D.hxx
src/FoundationClasses/TKMath/TopLoc/TopLoc_ItemLocation.cxx
src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.cxx
src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.hxx
src/FoundationClasses/TKMath/TopLoc/TopLoc_Location.lxx
src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.hxx
src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx [deleted file]
src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.cxx
src/FoundationClasses/TKMath/TopLoc/TopLoc_SListOfItemLocation.hxx
src/FoundationClasses/TKernel/GTests/FILES.cmake
src/FoundationClasses/TKernel/GTests/TopLoc_Location_Test.cxx [deleted file]

index 615955d3ad63c6d865eabcf33e3a2eb9dff2e903..7f3afa417b0c7fb771dfc00a5d31d7a0172f5958 100644 (file)
@@ -45,4 +45,5 @@ 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
new file mode 100644 (file)
index 0000000..8cf4fa9
--- /dev/null
@@ -0,0 +1,722 @@
+// 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 <TopLoc_Location.hxx>
+#include <TopoDS_Shape.hxx>
+#include <TopoDS_Vertex.hxx>
+#include <TopoDS.hxx>
+#include <BRepBuilderAPI_MakeVertex.hxx>
+#include <BRep_Tool.hxx>
+#include <gp.hxx>
+#include <gp_Ax1.hxx>
+#include <gp_Trsf.hxx>
+#include <gp_Vec.hxx>
+#include <gp_Pnt.hxx>
+#include <OSD_Parallel.hxx>
+
+#include <gtest/gtest.h>
+
+#include <atomic>
+#include <vector>
+
+namespace
+{
+//! Functor for testing concurrent access to TopLoc_Location::Transformation()
+struct TopLocTransformFunctor
+{
+  TopLocTransformFunctor(const std::vector<TopoDS_Shape>& 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<double>(i))
+      {
+        ++myIsRaceDetected;
+      }
+    }
+  }
+
+  const std::vector<TopoDS_Shape>* myShapeVec;
+  mutable std::atomic<int>         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<TopoDS_Shape>    aShapeVec(n);
+  std::vector<TopLoc_Location> 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<size_t>(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);
+}
index 44593cb6e0b72eeb5b34e6de5b3e17a5d927d111..e70862c03b918b1e6d19c01eb89abecfad5e17ec 100644 (file)
@@ -14,7 +14,6 @@ set(OCCT_TopLoc_FILES
   TopLoc_MapOfLocation.hxx
   TopLoc_SListNodeOfItemLocation.cxx
   TopLoc_SListNodeOfItemLocation.hxx
-  TopLoc_SListNodeOfItemLocation.lxx
   TopLoc_SListOfItemLocation.cxx
   TopLoc_SListOfItemLocation.hxx
 )
index f8c8abdd31948c6e937ec492afab3ca36822a036..79279209e0aebcf0025d57f1e00d2c6d65c36ff5 100644 (file)
@@ -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 { return myTrsf; }
+  const gp_Trsf& Transformation() const noexcept { return myTrsf; }
 
   //! Returns a gp_Trsf which, when applied to this datum, produces the default datum.
-  const gp_Trsf& Trsf() const { return myTrsf; }
+  const gp_Trsf& Trsf() const noexcept { return myTrsf; }
 
   //! Return transformation form.
-  gp_TrsfForm Form() const { return myTrsf.Form(); }
+  gp_TrsfForm Form() const noexcept { return myTrsf.Form(); }
 
   //! Dumps the content of me into the stream
   Standard_EXPORT void DumpJson(Standard_OStream& theOStream, Standard_Integer theDepth = -1) const;
index 9cbd5791fec6f71c482821e3f79ca374d520fb6b..bf374e4059d4de7e036990cc6ff889ffb9382e75 100644 (file)
@@ -24,7 +24,7 @@
 TopLoc_ItemLocation::TopLoc_ItemLocation(const Handle(TopLoc_Datum3D)& D, const Standard_Integer P)
     : myDatum(D),
       myPower(P),
-      myTrsf(D->Transformation().Powered(P))
+      myTrsf(P == 1 ? D->Transformation() : D->Transformation().Powered(P))
 {
 }
 
index f78330be47183507881b5095f9213b232c01ff3e..0ada67b56f4e36c8c6907920ae78ffc577bc33d6 100644 (file)
 #include <Standard_Dump.hxx>
 #include <TopLoc_Datum3D.hxx>
 #include <TopLoc_Location.hxx>
+#include <TopLoc_SListNodeOfItemLocation.hxx>
 #include <TopLoc_SListOfItemLocation.hxx>
 
 //=================================================================================================
 
-TopLoc_Location::TopLoc_Location() {}
-
-//=================================================================================================
-
 TopLoc_Location::TopLoc_Location(const Handle(TopLoc_Datum3D)& D)
 {
   myItems.Construct(TopLoc_ItemLocation(D, 1));
@@ -108,26 +105,6 @@ 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
@@ -138,6 +115,8 @@ 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())
@@ -147,41 +126,62 @@ 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)
-    return Multiplied(Powered(pwr - 1));
+  {
+    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));
+    }
+  }
   else
+  {
     return Inverted().Powered(-pwr);
+  }
 }
 
 //=================================================================================================
 
-// two locations are Equal if the Items have the same LocalValues and Powers
-// this is a recursive function to test it
+// Two locations are Equal if the Items have the same LocalValues and Powers
 
-Standard_Boolean TopLoc_Location::IsEqual(const TopLoc_Location& Other) const
+Standard_Boolean TopLoc_Location::IsEqual(const TopLoc_Location& theOther) const noexcept
 {
-  const void** p = (const void**)&myItems;
-  const void** q = (const void**)&Other.myItems;
-  if (*p == *q)
+  // Fast path: same node means equal (handles identity == identity case too)
+  if (myItems.Node() == theOther.myItems.Node())
   {
     return Standard_True;
   }
-  if (IsIdentity() || Other.IsIdentity())
-  {
-    return Standard_False;
-  }
-  if (FirstDatum() != Other.FirstDatum())
+  // Fast rejection: different cached hashes means not equal
+  if (myItems.HashCode() != theOther.myItems.HashCode())
   {
     return Standard_False;
   }
-  if (FirstPower() != Other.FirstPower())
+  // Hash collision: need element-by-element comparison
+  TopLoc_SListOfItemLocation anIt1 = myItems;
+  TopLoc_SListOfItemLocation anIt2 = theOther.myItems;
+  while (anIt1.More() && anIt2.More())
   {
-    return Standard_False;
-  }
-  else
-  {
-    return NextLocation() == Other.NextLocation();
+    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();
   }
+  // Equal only if both exhausted
+  return !anIt1.More() && !anIt2.More();
 }
 
 //=================================================================================================
index fcbe4d6fb3ad141104c574b3463156fa36b5c064..6d566b71bb2bd97ab5e6c19b80a6046ef0df3221 100644 (file)
@@ -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".
-  Standard_EXPORT TopLoc_Location();
+  TopLoc_Location() noexcept = default;
 
   //! 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;
+  Standard_Boolean IsIdentity() const noexcept;
 
   //! Resets this location to the Identity transformation.
-  void Identity();
+  void Identity() noexcept;
 
   //! Returns the first elementary datum of the
   //! Location. Use the NextLocation function recursively to access
@@ -98,7 +98,10 @@ public:
   }
 
   //! Returns <me> / <Other>.
-  Standard_NODISCARD Standard_EXPORT TopLoc_Location Divided(const TopLoc_Location& Other) const;
+  Standard_NODISCARD TopLoc_Location Divided(const TopLoc_Location& Other) const
+  {
+    return Multiplied(Other.Inverted());
+  }
 
   Standard_NODISCARD TopLoc_Location operator/(const TopLoc_Location& Other) const
   {
@@ -106,23 +109,30 @@ public:
   }
 
   //! Returns <Other>.Inverted() * <me>.
-  Standard_NODISCARD Standard_EXPORT TopLoc_Location Predivided(const TopLoc_Location& Other) const;
+  Standard_NODISCARD TopLoc_Location Predivided(const TopLoc_Location& Other) const
+  {
+    return Other.Inverted().Multiplied(*this);
+  }
 
   //! Returns me at the power <pwr>. If <pwr> is zero
   //! returns Identity. <pwr> 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;
+  size_t HashCode() const noexcept;
 
   //! 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;
+  Standard_EXPORT Standard_Boolean IsEqual(const TopLoc_Location& Other) const noexcept;
 
   Standard_Boolean operator==(const TopLoc_Location& Other) const { return IsEqual(Other); }
 
@@ -141,11 +151,10 @@ public:
   Standard_EXPORT void ShallowDump(Standard_OStream& S) const;
 
   //! Clear myItems
-  void Clear() { myItems.Clear(); }
+  void Clear() noexcept { myItems.Clear(); }
 
-  static Standard_Real ScalePrec() { return 1.e-14; }
+  static constexpr Standard_Real ScalePrec() { return 1.e-14; }
 
-protected:
 private:
   TopLoc_SListOfItemLocation myItems;
 };
index bfdc712186c8d1b5bc0ca2bb1b328b5131e5cd22..e6a6519c4c470ff96d35bd6c07822661b6352b94 100644 (file)
 
 //=================================================================================================
 
-inline Standard_Boolean TopLoc_Location::IsIdentity() const
+inline Standard_Boolean TopLoc_Location::IsIdentity() const noexcept
 {
   return myItems.IsEmpty();
 }
 
 //=================================================================================================
 
-inline void TopLoc_Location::Identity()
+inline void TopLoc_Location::Identity() noexcept
 {
   myItems.Clear();
 }
@@ -56,25 +56,10 @@ inline const TopLoc_Location& TopLoc_Location::NextLocation() const
 
 //=================================================================================================
 
-inline size_t TopLoc_Location::HashCode() const
+inline size_t TopLoc_Location::HashCode() const noexcept
 {
-  // Hashing base on IsEqual function
-  if (myItems.IsEmpty())
-  {
-    return 0;
-  }
-  size_t                     aHash = opencascade::MurmurHash::optimalSeed<size_t>();
-  TopLoc_SListOfItemLocation items = myItems;
-  size_t                     aCombined[3];
-  while (items.More())
-  {
-    aCombined[0] = std::hash<Handle(TopLoc_Datum3D)>{}(items.Value().myDatum);
-    aCombined[1] = opencascade::hash(items.Value().myPower);
-    aCombined[2] = aHash;
-    aHash        = opencascade::hashBytes(aCombined, sizeof(aCombined));
-    items.Next();
-  }
-  return aHash;
+  // Use cached hash from the list node
+  return myItems.HashCode();
 }
 
 //=================================================================================================
index d364e57c29502c145d5d68cc294d9dd5c94ade8c..d8cd5ac818c3bab7a2a9d0bba60853e5806e540f 100644 (file)
@@ -31,11 +31,23 @@ class TopLoc_SListNodeOfItemLocation : public Standard_Transient
 
 public:
   TopLoc_SListNodeOfItemLocation(const TopLoc_ItemLocation&        I,
-                                 const TopLoc_SListOfItemLocation& aTail);
+                                 const TopLoc_SListOfItemLocation& aTail,
+                                 size_t                            theHash)
+      : myTail(aTail),
+        myValue(I),
+        myHash(theHash)
+  {
+  }
 
-  TopLoc_SListOfItemLocation& Tail() const;
+  TopLoc_SListOfItemLocation& Tail() { return myTail; }
 
-  TopLoc_ItemLocation& Value() 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; }
 
   DEFINE_STANDARD_RTTIEXT(TopLoc_SListNodeOfItemLocation, Standard_Transient)
 
@@ -43,8 +55,7 @@ protected:
 private:
   TopLoc_SListOfItemLocation myTail;
   TopLoc_ItemLocation        myValue;
+  size_t                     myHash;
 };
 
-#include <TopLoc_SListNodeOfItemLocation.lxx>
-
 #endif // _TopLoc_SListNodeOfItemLocation_HeaderFile
diff --git a/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx b/src/FoundationClasses/TKMath/TopLoc/TopLoc_SListNodeOfItemLocation.lxx
deleted file mode 100644 (file)
index d25c20e..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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 <TopLoc_ItemLocation.hxx>
-
-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;
-}
index 1ba89df5d27b779b21d8532e5005c493d7293ba0..92ecae145468f3e2241f47837f96856cbf9a7a45 100644 (file)
 // commercial license or contractual agreement.
 
 #include <Standard_NoSuchObject.hxx>
+#include <Standard_HashUtils.hxx>
 #include <TopLoc_ItemLocation.hxx>
 #include <TopLoc_SListNodeOfItemLocation.hxx>
 #include <TopLoc_SListOfItemLocation.hxx>
+#include <TopLoc_Datum3D.hxx>
 
 //=================================================================================================
 
 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<Handle(TopLoc_Datum3D)>{}(anItem.myDatum),
+                               static_cast<size_t>(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;
@@ -62,3 +70,10 @@ const TopLoc_SListOfItemLocation& TopLoc_SListOfItemLocation::Tail() const
   else
     return *this;
 }
+
+//=================================================================================================
+
+size_t TopLoc_SListOfItemLocation::HashCode() const noexcept
+{
+  return myNode.IsNull() ? 0 : myNode->HashCode();
+}
index 51f9e31b76f3e15b556342f140aac533abdb89e5..cbcc7bac61233d29a5d730252de82a9bd18429ec 100644 (file)
@@ -46,7 +46,7 @@ public:
   DEFINE_STANDARD_ALLOC
 
   //! Creates an empty List.
-  TopLoc_SListOfItemLocation() {}
+  TopLoc_SListOfItemLocation() = default;
 
   //! Creates a List with <anItem> as value and <aTail> 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 { return myNode.IsNull(); }
+  Standard_Boolean IsEmpty() const noexcept { return myNode.IsNull(); }
 
   //! Sets the list to be empty.
-  void Clear() { myNode.Nullify(); }
+  void Clear() noexcept { myNode.Nullify(); }
 
   //! Destructor
   ~TopLoc_SListOfItemLocation() { Clear(); }
@@ -98,6 +98,14 @@ 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 <anItem> as Value
   //! and the list <me> as tail.
   void Construct(const TopLoc_ItemLocation& anItem)
@@ -110,7 +118,7 @@ public:
 
   //! Returns True if the iterator has a current value.
   //! This is !IsEmpty()
-  Standard_Boolean More() const { return !IsEmpty(); }
+  Standard_Boolean More() const noexcept { return !IsEmpty(); }
 
   //! Moves the iterator to the next object in the list.
   //! If the iterator is empty it will stay empty. This is ToTail()
index 5bc29c2ab9d5bacd80eee9ce5b28e687dcb0a933..2f1a9831f1f68e0da3c8b58a56191a34e8cf1d75 100644 (file)
@@ -35,6 +35,5 @@ 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
deleted file mode 100644 (file)
index 890e3fa..0000000
+++ /dev/null
@@ -1,91 +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 <TopLoc_Location.hxx>
-#include <TopoDS_Shape.hxx>
-#include <TopoDS_Vertex.hxx>
-#include <TopoDS.hxx>
-#include <BRepBuilderAPI_MakeVertex.hxx>
-#include <BRep_Tool.hxx>
-#include <gp.hxx>
-#include <gp_Trsf.hxx>
-#include <gp_Vec.hxx>
-#include <gp_Pnt.hxx>
-#include <OSD_Parallel.hxx>
-
-#include <gtest/gtest.h>
-
-#include <atomic>
-#include <vector>
-
-namespace
-{
-//! Functor for testing concurrent access to TopLoc_Location::Transformation()
-struct TopLocTransformFunctor
-{
-  TopLocTransformFunctor(const std::vector<TopoDS_Shape>& 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<double>(i))
-      {
-        ++myIsRaceDetected;
-      }
-    }
-  }
-
-  const std::vector<TopoDS_Shape>* myShapeVec;
-  mutable std::atomic<int>         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<TopoDS_Shape>    aShapeVec(n);
-  std::vector<TopLoc_Location> 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";
-}