From 82b470f9b1652a8c4a6106a0270a7767f4b1c347 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Mon, 23 Oct 2023 18:32:30 +0200 Subject: [PATCH 01/18] GRIDEDIT-758 Added mesh transformation. --- libs/MeshKernel/CMakeLists.txt | 1 + .../include/MeshKernel/MeshTransformation.hpp | 253 ++++++++++++++++++ libs/MeshKernel/tests/CMakeLists.txt | 1 + .../tests/src/MeshTransformationTest.cpp | 223 +++++++++++++++ 4 files changed, 478 insertions(+) create mode 100644 libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp create mode 100644 libs/MeshKernel/tests/src/MeshTransformationTest.cpp diff --git a/libs/MeshKernel/CMakeLists.txt b/libs/MeshKernel/CMakeLists.txt index b7dfaf46e..f1098bf4a 100644 --- a/libs/MeshKernel/CMakeLists.txt +++ b/libs/MeshKernel/CMakeLists.txt @@ -114,6 +114,7 @@ set( ${DOMAIN_INC_DIR}/Mesh2DIntersections.hpp ${DOMAIN_INC_DIR}/MeshInterpolation.hpp ${DOMAIN_INC_DIR}/MeshRefinement.hpp + ${DOMAIN_INC_DIR}/MeshTransformation.hpp ${DOMAIN_INC_DIR}/Network1D.hpp ${DOMAIN_INC_DIR}/Operations.hpp ${DOMAIN_INC_DIR}/OrthogonalizationAndSmoothing.hpp diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp new file mode 100644 index 000000000..332a5c0e0 --- /dev/null +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -0,0 +1,253 @@ +//---- GPL --------------------------------------------------------------------- +// +// Copyright (C) Stichting Deltares, 2011-2023. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// contact: delft3d.support@deltares.nl +// Stichting Deltares +// P.O. Box 177 +// 2600 MH Delft, The Netherlands +// +// All indications and logos of, and references to, "Delft3D" and "Deltares" +// are registered trademarks of Stichting Deltares, and remain the property of +// Stichting Deltares. All rights reserved. +// +//------------------------------------------------------------------------------ + +#pragma once + +#include + +#include + +#include "MeshKernel/Mesh.hpp" + +namespace meshkernel +{ + + /// @brief Ensure any instantiation of the MeshTransformation Compute function is with the correct operation + template + concept TransformationOperation = requires(Operation op, Point p) {{ op.apply(p)} -> std::same_as; }; + + /// @brief Apply a translation transformation to a point or a vector. + class Translation + { + public: + /// @brief Default constructor, default is no translation + Translation() = default; + + /// @brief Construct with user defined translation + explicit Translation(const Vector& trans) : translation(trans) {} + + /// @brief Reset translation to identity translation (i.e. no translation) + void identity() + { + translation = {0.0, 0.0}; + } + + /// @brief Reset the translation to a new translation quantity + void reset(const Vector& trans) + { + translation = trans; + } + + /// @brief Get the current defined translation vector. + const Vector& vector() const + { + return translation; + } + + /// @brief Compose two translation objects. + Translation compose(const Translation& trans) const + { + return Translation(translation + trans.translation); + } + + /// @brief Apply the translation to a point in Cartesian coordinate system + Point apply(const Point& pnt) const + { + return pnt + translation; + } + + /// @brief Apply the translation to a vector in Cartesian coordinate system + Vector apply(const Vector& vec) const + { + return vec + translation; + } + + private: + /// @brief The translation values + Vector translation{0.0, 0.0}; + }; + + /// @brief Apply a rotation transformation to a point or a vector. + class Rotation + { + public: + /// @brief Default constructor, default is theta = 0. + Rotation() = default; + + /// @brief Construct with user defined rotation angle, in radians. + explicit Rotation(const double angle) : theta(angle), cosTheta(std::cos(angle)), sinTheta(std::sin(angle)) {} + + /// @brief Reset rotation to identity translation (i.e. no rotation, theta = 0) + void identity() + { + theta = 0.0; + cosTheta = 1.0; + sinTheta = 0.0; + } + + /// @brief Reset the rotation to a new rotation angle + void reset(const double angle) + { + theta = angle; + cosTheta = std::cos(theta); + sinTheta = std::sin(theta); + } + + /// @brief Get the current defined rotation angle + double angle() const + { + return theta; + } + + /// @brief Compose two rotation objects. + Rotation compose(const Rotation& rot) const + { + return Rotation(theta + rot.theta); + } + + /// @brief Compose rotation and translation object. + /// + /// Will be applied rot (trans). + Translation compose(const Translation& trans) const + { + Vector vec(apply(trans.vector())); + return Translation(vec); + } + + /// @brief Apply the rotation to a point in Cartesian coordinate system + Point apply(const Point& pnt) const + { + Point result({cosTheta * pnt.x - sinTheta * pnt.y, sinTheta * pnt.x + cosTheta * pnt.y}); + return result; + } + + /// @brief Apply the rotation to a vector in Cartesian coordinate system + Vector apply(const Vector& vec) const + { + Vector result({cosTheta * vec.x() - sinTheta * vec.y(), + sinTheta * vec.x() + cosTheta * vec.y()}); + return result; + } + + private: + /// @brief The rotation angle, theta + double theta = 0.0; + /// @brief cosine of the rotation angle + double cosTheta = 1.0; + /// @brief sine of the rotation angle + double sinTheta = 0.0; + }; + + /// @brief A composition of translation and rotation transformations + class RigidBodyTransformation + { + public: + /// @brief Default constructor, default is no transformation + RigidBodyTransformation() = default; + + /// @brief Reset trasformation to identity transformation (i.e. no transformation) + void identity() + { + rotation_.identity(); + translation_.identity(); + } + + /// @brief Compose rotation and transformation object. + /// + /// Will be applied rot (transformation). + void compose(const Rotation& rot) + { + rotation_ = rot.compose(rotation_); + translation_ = rot.compose(translation_); + } + + /// @brief Compose translation and transformation object. + /// + /// Will be applied translation (transformation). + void compose(const Translation& trans) + { + translation_ = trans.compose(translation_); + } + + /// @brief Get the current rotation. + const Rotation& rotation() const + { + return rotation_; + } + + /// @brief Get the current translation. + const Translation& translation() const + { + return translation_; + } + + /// @brief Apply the transformation to a point in Cartesian coordinate system + Point apply(const Point& pnt) const + { + Point result = rotation_.apply(pnt); + result = translation_.apply(result); + return result; + } + + /// @brief Apply the transformation to a vector in Cartesian coordinate system + Vector apply(const Vector& vec) const + { + Vector result = rotation_.apply(vec); + result = translation_.apply(result); + return result; + } + + private: + /// @brief The rotation part of the transformation + Rotation rotation_; + /// @brief The translation part of the transformation + Translation translation_; + }; + + /// @brief Apply a transformation to a mesh + class MeshTransformation + { + public: + /// @brief Apply a transformation to a mesh + template + static void Compute(Mesh& mesh, Transformation transformation); + }; + +} // namespace meshkernel + +template +void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transformation) +{ + + for (UInt i = 0; i < mesh.GetNumNodes(); ++i) + { + if (mesh.m_nodes[i].IsValid()) + { + mesh.m_nodes[i] = transformation.apply(mesh.m_nodes[i]); + } + } +} diff --git a/libs/MeshKernel/tests/CMakeLists.txt b/libs/MeshKernel/tests/CMakeLists.txt index 1af27935c..a91e878f6 100644 --- a/libs/MeshKernel/tests/CMakeLists.txt +++ b/libs/MeshKernel/tests/CMakeLists.txt @@ -36,6 +36,7 @@ set( ${SRC_DIR}/Mesh2DTest.cpp ${SRC_DIR}/MeshRefinementTests.cpp ${SRC_DIR}/MeshTests.cpp + ${SRC_DIR}/MeshTransformationTest.cpp ${SRC_DIR}/OrthogonalizationTests.cpp ${SRC_DIR}/ParametersTests.cpp ${SRC_DIR}/PointTests.cpp diff --git a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp new file mode 100644 index 000000000..095e05004 --- /dev/null +++ b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp @@ -0,0 +1,223 @@ +#include +#include +#include + +#include "MeshKernel/Constants.hpp" +#include "MeshKernel/Entities.hpp" +#include "MeshKernel/Mesh2D.hpp" +#include "MeshKernel/MeshTransformation.hpp" +#include "MeshKernel/Polygons.hpp" + +#include "TestUtils/Definitions.hpp" +#include "TestUtils/MakeMeshes.hpp" + +namespace mk = meshkernel; + +TEST(MeshTransformationTest, BasicTranslationTest) +{ + // Test basic functionality of the translation class + double translationX = 1.0; + double translationY = 2.0; + + mk::Vector vec(translationX, translationY); + mk::Translation translation(vec); + + EXPECT_EQ(translationX, translation.vector().x()); + EXPECT_EQ(translationY, translation.vector().y()); + + translation.identity(); + EXPECT_EQ(0.0, translation.vector().x()); + EXPECT_EQ(0.0, translation.vector().y()); + + std::swap(vec.x(), vec.y()); + vec.x() = -vec.x(); + vec.y() = -vec.y(); + translation.reset(vec); + + EXPECT_EQ(-translationY, translation.vector().x()); + EXPECT_EQ(-translationX, translation.vector().y()); +} + +TEST(MeshTransformationTest, BasicRotationTest) +{ + // Test basic functionality of the rotation class + double theta = 45.0 * M_PI / 180.0; + + mk::Rotation rotation(theta); + + EXPECT_EQ(theta, rotation.angle()); + + theta *= 1.5; + rotation.reset(theta); + EXPECT_EQ(theta, rotation.angle()); + + rotation.identity(); + EXPECT_EQ(0.0, rotation.angle()); +} + +TEST(MeshTransformationTest, PointTranslationTest) +{ + // Test a simple translation of a point in Cartesian coordinates. + double originX = 12.0; + double originY = -19.0; + double translationX = 1.0; + double translationY = 2.0; + + mk::Vector vec(translationX, translationY); + mk::Translation translation(vec); + mk::Point pnt(originX, originY); + pnt = translation.apply(pnt); + + EXPECT_EQ(originX + translationX, pnt.x); + EXPECT_EQ(originY + translationY, pnt.y); +} + +TEST(MeshTransformationTest, BasicRigidBodyTransformationTest) +{ + // Test the rigid body transformation + // The test will rotate a series of points about a point that is not the origin + // + // Step 1: create composition of all simple transformations + // 1. translate the rotation point (-1,1) to origin (0,0) + // 2. rotate by 90 degrees + // 3. translate back to original rotation point (-1,1) + // + // Step 2 apply composite transformation to a series of points + constexpr double tolerance = 1.0e-10; + + double theta = -90.0 * M_PI / 180.0; + mk::Point rotationPoint(-1.0, 1.0); + + mk::RigidBodyTransformation transformation; + + // Expect initial state to be the identity + EXPECT_EQ(0.0, transformation.rotation().angle()); + EXPECT_EQ(0.0, transformation.translation().vector().x()); + EXPECT_EQ(0.0, transformation.translation().vector().y()); + + // First translate + mk::Vector vec(-rotationPoint.x, -rotationPoint.y); + mk::Translation translation(vec); + transformation.compose(translation); + + EXPECT_EQ(0.0, transformation.rotation().angle()); + EXPECT_EQ(-rotationPoint.x, transformation.translation().vector().x()); + EXPECT_EQ(-rotationPoint.y, transformation.translation().vector().y()); + + // Second rotate + mk::Rotation rotation(theta); + transformation.compose(rotation); + + mk::Vector rotatedVector = rotation.apply(vec); + + EXPECT_EQ(theta, transformation.rotation().angle()); + EXPECT_EQ(rotatedVector.x(), transformation.translation().vector().x()); + EXPECT_EQ(rotatedVector.y(), transformation.translation().vector().y()); + + // Third rotate back + vec = {rotationPoint.x, rotationPoint.y}; + translation.reset(vec); + transformation.compose(translation); + + EXPECT_EQ(theta, transformation.rotation().angle()); + + double expectedTranslationX = -2.0; + double expectedTranslationY = 0.0; + + EXPECT_NEAR(expectedTranslationX, transformation.translation().vector().x(), tolerance); + EXPECT_NEAR(expectedTranslationY, transformation.translation().vector().y(), tolerance); + + //------------------------------------------------------------ + // Apply the composite transformation to a series of points + + mk::Point pnt1{-3.0, 4.0}; + mk::Point pnt2{-2.0, 2.0}; + mk::Point pnt3{-3.0, 1.0}; + + mk::Point expected1{2.0, 3.0}; + mk::Point expected2{0.0, 2.0}; + mk::Point expected3{-1.0, 3.0}; + + pnt1 = transformation.apply(pnt1); + EXPECT_NEAR(expected1.x, pnt1.x, tolerance); + EXPECT_NEAR(expected1.y, pnt1.y, tolerance); + + pnt2 = transformation.apply(pnt2); + EXPECT_NEAR(expected2.x, pnt2.x, tolerance); + EXPECT_NEAR(expected2.y, pnt2.y, tolerance); + + pnt3 = transformation.apply(pnt3); + EXPECT_NEAR(expected3.x, pnt3.x, tolerance); + EXPECT_NEAR(expected3.y, pnt3.y, tolerance); +} + +TEST(MeshTransformationTest, PointRotationTest) +{ + // Test a simple rotation of a point in Cartesian coordinates. + double originX = 12.0; + double originY = -19.0; + + double theta = 45.0 * M_PI / 180.0; + double cosTheta = std::cos(theta); + double sinTheta = std::sin(theta); + + mk::Rotation rotation(theta); + mk::Point pnt(originX, originY); + pnt = rotation.apply(pnt); + + EXPECT_EQ(originX * cosTheta - originY * sinTheta, pnt.x); + EXPECT_EQ(originX * sinTheta + originY * cosTheta, pnt.y); +} + +TEST(MeshTransformationTest, MeshTranslationTest) +{ + + mk::UInt nx = 11; + mk::UInt ny = 11; + + double delta = 10.0; + + // Make two copies of the same mesh so that they can be compared in the test. + std::shared_ptr originalMesh = MakeRectangularMeshForTesting(nx, ny, delta, mk::Projection::cartesian); + std::shared_ptr mesh = MakeRectangularMeshForTesting(nx, ny, delta, mk::Projection::cartesian); + + mk::Vector vec(1.0, 2.0); + + mk::Translation translation(vec); + + mk::MeshTransformation::Compute(*mesh, translation); + + for (mk::UInt i = 0; i < mesh->GetNumNodes(); ++i) + { + EXPECT_EQ(originalMesh->m_nodes[i].x + vec.x(), mesh->m_nodes[i].x); + EXPECT_EQ(originalMesh->m_nodes[i].y + vec.y(), mesh->m_nodes[i].y); + } +} + +TEST(MeshTransformationTest, MeshRotationTest) +{ + + mk::UInt nx = 11; + mk::UInt ny = 11; + + double delta = 10.0; + + // Make two copies of the same mesh so that they can be compared in the test. + std::shared_ptr originalMesh = MakeRectangularMeshForTesting(nx, ny, delta, mk::Projection::cartesian); + std::shared_ptr mesh = MakeRectangularMeshForTesting(nx, ny, delta, mk::Projection::cartesian); + + double theta = 45.0 * M_PI / 180.0; + + mk::Rotation rotation(theta); + double cosTheta = std::cos(theta); + double sinTheta = std::sin(theta); + + mk::MeshTransformation::Compute(*mesh, rotation); + + for (mk::UInt i = 0; i < mesh->GetNumNodes(); ++i) + { + mk::Point expected{originalMesh->m_nodes[i].x * cosTheta - originalMesh->m_nodes[i].y * sinTheta, originalMesh->m_nodes[i].x * sinTheta + originalMesh->m_nodes[i].y * cosTheta}; + EXPECT_EQ(expected.x, mesh->m_nodes[i].x); + EXPECT_EQ(expected.y, mesh->m_nodes[i].y); + } +} From cfb481542edbba9b5b765d5cc4da75f805655e50 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 10:45:44 +0200 Subject: [PATCH 02/18] GRIDEDIT-758 Updated comments, added check for Cartesian projection --- .../include/MeshKernel/MeshTransformation.hpp | 20 ++++++++++----- .../tests/src/MeshTransformationTest.cpp | 25 ++++++++++++++++++- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 332a5c0e0..75f59b16e 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -29,8 +29,8 @@ #include -#include - +#include "MeshKernel/Definitions.hpp" +#include "MeshKernel/Exceptions.hpp" #include "MeshKernel/Mesh.hpp" namespace meshkernel @@ -156,8 +156,10 @@ namespace meshkernel private: /// @brief The rotation angle, theta double theta = 0.0; + /// @brief cosine of the rotation angle double cosTheta = 1.0; + /// @brief sine of the rotation angle double sinTheta = 0.0; }; @@ -169,7 +171,7 @@ namespace meshkernel /// @brief Default constructor, default is no transformation RigidBodyTransformation() = default; - /// @brief Reset trasformation to identity transformation (i.e. no transformation) + /// @brief Reset transformation to identity transformation (i.e. no transformation) void identity() { rotation_.identity(); @@ -178,7 +180,7 @@ namespace meshkernel /// @brief Compose rotation and transformation object. /// - /// Will be applied rot (transformation). + /// Will be applied: rot (transformation). void compose(const Rotation& rot) { rotation_ = rot.compose(rotation_); @@ -187,7 +189,7 @@ namespace meshkernel /// @brief Compose translation and transformation object. /// - /// Will be applied translation (transformation). + /// Will be applied: translation (transformation). void compose(const Translation& trans) { translation_ = trans.compose(translation_); @@ -224,6 +226,7 @@ namespace meshkernel private: /// @brief The rotation part of the transformation Rotation rotation_; + /// @brief The translation part of the transformation Translation translation_; }; @@ -232,7 +235,7 @@ namespace meshkernel class MeshTransformation { public: - /// @brief Apply a transformation to a mesh + /// @brief Apply a transformation to a mesh with a Cartesian projection template static void Compute(Mesh& mesh, Transformation transformation); }; @@ -242,6 +245,11 @@ namespace meshkernel template void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transformation) { + if (mesh.m_projection != Projection::cartesian) + { + throw MeshKernelError ("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", + ToString (mesh.m_projection)); + } for (UInt i = 0; i < mesh.GetNumNodes(); ++i) { diff --git a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp index 095e05004..f0ce21a77 100644 --- a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp +++ b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp @@ -171,6 +171,7 @@ TEST(MeshTransformationTest, PointRotationTest) TEST(MeshTransformationTest, MeshTranslationTest) { + // Test the translation on the entire mesh. mk::UInt nx = 11; mk::UInt ny = 11; @@ -196,6 +197,7 @@ TEST(MeshTransformationTest, MeshTranslationTest) TEST(MeshTransformationTest, MeshRotationTest) { + // Test the rotation on the entire mesh. mk::UInt nx = 11; mk::UInt ny = 11; @@ -216,8 +218,29 @@ TEST(MeshTransformationTest, MeshRotationTest) for (mk::UInt i = 0; i < mesh->GetNumNodes(); ++i) { - mk::Point expected{originalMesh->m_nodes[i].x * cosTheta - originalMesh->m_nodes[i].y * sinTheta, originalMesh->m_nodes[i].x * sinTheta + originalMesh->m_nodes[i].y * cosTheta}; + mk::Point expected{originalMesh->m_nodes[i].x * cosTheta - originalMesh->m_nodes[i].y * sinTheta, + originalMesh->m_nodes[i].x * sinTheta + originalMesh->m_nodes[i].y * cosTheta}; EXPECT_EQ(expected.x, mesh->m_nodes[i].x); EXPECT_EQ(expected.y, mesh->m_nodes[i].y); } } + +TEST(MeshTransformationTest, IncorrectProjectionTest) +{ + // Test correct failure with non Cartesian projection. + + // Generate mesh in spherical coordinate system + std::shared_ptr mesh = MakeRectangularMeshForTesting(11, 11, 10.0, mk::Projection::spherical); + + mk::Rotation rotation(45.0 * M_PI / 180.0); + + // Should throw an exception with spherical coordinate system + EXPECT_THROW(mk::MeshTransformation::Compute(*mesh, rotation), mk::MeshKernelError); + + // // Change projection to Projection::sphericalAccurate + // mesh->m_projection = mk::Projection::sphericalAccurate; + + // // Should throw an exception with spherical-accurate coordinate system + // EXPECT_THROW(mk::MeshTransformation::Compute(*mesh, rotation), mk::MeshKernelError); + +} From a746c777131e3c8c4d7a4755c4cc54cfbfb06750 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 10:55:58 +0200 Subject: [PATCH 03/18] GRIDEDIT-758 Added missing headers, another try to get windows building --- .../include/MeshKernel/MeshTransformation.hpp | 10 ++++++---- libs/MeshKernel/tests/src/MeshTransformationTest.cpp | 9 ++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 75f59b16e..41bfc4fc1 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -32,6 +32,8 @@ #include "MeshKernel/Definitions.hpp" #include "MeshKernel/Exceptions.hpp" #include "MeshKernel/Mesh.hpp" +#include "MeshKernel/Point.hpp" +#include "MeshKernel/Vector.hpp" namespace meshkernel { @@ -236,19 +238,19 @@ namespace meshkernel { public: /// @brief Apply a transformation to a mesh with a Cartesian projection - template + template static void Compute(Mesh& mesh, Transformation transformation); }; } // namespace meshkernel -template +template void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transformation) { if (mesh.m_projection != Projection::cartesian) { - throw MeshKernelError ("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", - ToString (mesh.m_projection)); + throw MeshKernelError("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", + ToString (mesh.m_projection)); } for (UInt i = 0; i < mesh.GetNumNodes(); ++i) diff --git a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp index f0ce21a77..b3383f60a 100644 --- a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp +++ b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp @@ -237,10 +237,9 @@ TEST(MeshTransformationTest, IncorrectProjectionTest) // Should throw an exception with spherical coordinate system EXPECT_THROW(mk::MeshTransformation::Compute(*mesh, rotation), mk::MeshKernelError); - // // Change projection to Projection::sphericalAccurate - // mesh->m_projection = mk::Projection::sphericalAccurate; - - // // Should throw an exception with spherical-accurate coordinate system - // EXPECT_THROW(mk::MeshTransformation::Compute(*mesh, rotation), mk::MeshKernelError); + // Change projection to Projection::sphericalAccurate + mesh->m_projection = mk::Projection::sphericalAccurate; + // Should throw an exception with spherical-accurate coordinate system + EXPECT_THROW(mk::MeshTransformation::Compute(*mesh, rotation), mk::MeshKernelError); } From c41f8f87587143bdcd9f1be40e3c9d6c07dc58eb Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 11:01:21 +0200 Subject: [PATCH 04/18] GRIDEDIT-758 Renamed the apply function to operator() --- .../include/MeshKernel/MeshTransformation.hpp | 28 +++++++++---------- .../tests/src/MeshTransformationTest.cpp | 12 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 41bfc4fc1..2058d8e40 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -40,7 +40,7 @@ namespace meshkernel /// @brief Ensure any instantiation of the MeshTransformation Compute function is with the correct operation template - concept TransformationOperation = requires(Operation op, Point p) {{ op.apply(p)} -> std::same_as; }; + concept TransformationOperation = requires(Operation op, Point p) {{ op(p)} -> std::same_as; }; /// @brief Apply a translation transformation to a point or a vector. class Translation @@ -77,13 +77,13 @@ namespace meshkernel } /// @brief Apply the translation to a point in Cartesian coordinate system - Point apply(const Point& pnt) const + Point operator()(const Point& pnt) const { return pnt + translation; } /// @brief Apply the translation to a vector in Cartesian coordinate system - Vector apply(const Vector& vec) const + Vector operator()(const Vector& vec) const { return vec + translation; } @@ -136,19 +136,19 @@ namespace meshkernel /// Will be applied rot (trans). Translation compose(const Translation& trans) const { - Vector vec(apply(trans.vector())); + Vector vec(operator()(trans.vector())); return Translation(vec); } /// @brief Apply the rotation to a point in Cartesian coordinate system - Point apply(const Point& pnt) const + Point operator()(const Point& pnt) const { Point result({cosTheta * pnt.x - sinTheta * pnt.y, sinTheta * pnt.x + cosTheta * pnt.y}); return result; } /// @brief Apply the rotation to a vector in Cartesian coordinate system - Vector apply(const Vector& vec) const + Vector operator()(const Vector& vec) const { Vector result({cosTheta * vec.x() - sinTheta * vec.y(), sinTheta * vec.x() + cosTheta * vec.y()}); @@ -210,18 +210,18 @@ namespace meshkernel } /// @brief Apply the transformation to a point in Cartesian coordinate system - Point apply(const Point& pnt) const + Point operator()(const Point& pnt) const { - Point result = rotation_.apply(pnt); - result = translation_.apply(result); + Point result = rotation_(pnt); + result = translation_(result); return result; } /// @brief Apply the transformation to a vector in Cartesian coordinate system - Vector apply(const Vector& vec) const + Vector operator()(const Vector& vec) const { - Vector result = rotation_.apply(vec); - result = translation_.apply(result); + Vector result = rotation_(vec); + result = translation_(result); return result; } @@ -250,14 +250,14 @@ void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transfor if (mesh.m_projection != Projection::cartesian) { throw MeshKernelError("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", - ToString (mesh.m_projection)); + ToString(mesh.m_projection)); } for (UInt i = 0; i < mesh.GetNumNodes(); ++i) { if (mesh.m_nodes[i].IsValid()) { - mesh.m_nodes[i] = transformation.apply(mesh.m_nodes[i]); + mesh.m_nodes[i] = transformation(mesh.m_nodes[i]); } } } diff --git a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp index b3383f60a..42923f6ca 100644 --- a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp +++ b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp @@ -66,7 +66,7 @@ TEST(MeshTransformationTest, PointTranslationTest) mk::Vector vec(translationX, translationY); mk::Translation translation(vec); mk::Point pnt(originX, originY); - pnt = translation.apply(pnt); + pnt = translation(pnt); EXPECT_EQ(originX + translationX, pnt.x); EXPECT_EQ(originY + translationY, pnt.y); @@ -108,7 +108,7 @@ TEST(MeshTransformationTest, BasicRigidBodyTransformationTest) mk::Rotation rotation(theta); transformation.compose(rotation); - mk::Vector rotatedVector = rotation.apply(vec); + mk::Vector rotatedVector = rotation(vec); EXPECT_EQ(theta, transformation.rotation().angle()); EXPECT_EQ(rotatedVector.x(), transformation.translation().vector().x()); @@ -138,15 +138,15 @@ TEST(MeshTransformationTest, BasicRigidBodyTransformationTest) mk::Point expected2{0.0, 2.0}; mk::Point expected3{-1.0, 3.0}; - pnt1 = transformation.apply(pnt1); + pnt1 = transformation(pnt1); EXPECT_NEAR(expected1.x, pnt1.x, tolerance); EXPECT_NEAR(expected1.y, pnt1.y, tolerance); - pnt2 = transformation.apply(pnt2); + pnt2 = transformation(pnt2); EXPECT_NEAR(expected2.x, pnt2.x, tolerance); EXPECT_NEAR(expected2.y, pnt2.y, tolerance); - pnt3 = transformation.apply(pnt3); + pnt3 = transformation(pnt3); EXPECT_NEAR(expected3.x, pnt3.x, tolerance); EXPECT_NEAR(expected3.y, pnt3.y, tolerance); } @@ -163,7 +163,7 @@ TEST(MeshTransformationTest, PointRotationTest) mk::Rotation rotation(theta); mk::Point pnt(originX, originY); - pnt = rotation.apply(pnt); + pnt = rotation(pnt); EXPECT_EQ(originX * cosTheta - originY * sinTheta, pnt.x); EXPECT_EQ(originX * sinTheta + originY * cosTheta, pnt.y); From 2212ba33e162410a27e90cadbb1d2f2983454de5 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 11:10:04 +0200 Subject: [PATCH 05/18] GRIDEDIT-758 One last attempt at getting the windows build working with concepts --- .../include/MeshKernel/MeshTransformation.hpp | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 2058d8e40..705d042b8 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -238,13 +238,30 @@ namespace meshkernel { public: /// @brief Apply a transformation to a mesh with a Cartesian projection - template - static void Compute(Mesh& mesh, Transformation transformation); + template + static void Compute(Mesh& mesh, Transformation transformation) + { + if (mesh.m_projection != Projection::cartesian) + { + throw MeshKernelError("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", + ToString(mesh.m_projection)); + } + + for (UInt i = 0; i < mesh.GetNumNodes(); ++i) + { + if (mesh.m_nodes[i].IsValid()) + { + mesh.m_nodes[i] = transformation(mesh.m_nodes[i]); + } + } + } + }; } // namespace meshkernel -template +#if 0 +template void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transformation) { if (mesh.m_projection != Projection::cartesian) @@ -261,3 +278,4 @@ void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transfor } } } +#endif From 0eb44840991f083dac3cc5054a5af06b9fb91aa8 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 11:26:54 +0200 Subject: [PATCH 06/18] GRIDEDIT-758 Windows now building with concepts, problem was in incorrect headers --- .../include/MeshKernel/MeshTransformation.hpp | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 705d042b8..7d43205e7 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -239,28 +239,11 @@ namespace meshkernel public: /// @brief Apply a transformation to a mesh with a Cartesian projection template - static void Compute(Mesh& mesh, Transformation transformation) - { - if (mesh.m_projection != Projection::cartesian) - { - throw MeshKernelError("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", - ToString(mesh.m_projection)); - } - - for (UInt i = 0; i < mesh.GetNumNodes(); ++i) - { - if (mesh.m_nodes[i].IsValid()) - { - mesh.m_nodes[i] = transformation(mesh.m_nodes[i]); - } - } - } - + static void Compute(Mesh& mesh, Transformation transformation); }; } // namespace meshkernel -#if 0 template void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transformation) { @@ -278,4 +261,3 @@ void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transfor } } } -#endif From 0a64e423cd5c537c227b22af54c2316ecf1ea41c Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 11:48:41 +0200 Subject: [PATCH 07/18] GRIDEDIT-758 --- .../include/MeshKernel/MeshTransformation.hpp | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 7d43205e7..301e3f943 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -239,25 +239,23 @@ namespace meshkernel public: /// @brief Apply a transformation to a mesh with a Cartesian projection template - static void Compute(Mesh& mesh, Transformation transformation); + static void Compute(Mesh& mesh, Transformation transformation) + { + if (mesh.m_projection != Projection::cartesian) + { + throw MeshKernelError("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", + ToString(mesh.m_projection)); + } + +#pragma omp parallel for + for (UInt i = 0; i < mesh.GetNumNodes(); ++i) + { + if (mesh.m_nodes[i].IsValid()) + { + mesh.m_nodes[i] = transformation(mesh.m_nodes[i]); + } + } + } }; } // namespace meshkernel - -template -void meshkernel::MeshTransformation::Compute(Mesh& mesh, Transformation transformation) -{ - if (mesh.m_projection != Projection::cartesian) - { - throw MeshKernelError("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", - ToString(mesh.m_projection)); - } - - for (UInt i = 0; i < mesh.GetNumNodes(); ++i) - { - if (mesh.m_nodes[i].IsValid()) - { - mesh.m_nodes[i] = transformation(mesh.m_nodes[i]); - } - } -} From afd1244440e39001f290a83a12ee167df323e4f4 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 11:57:38 +0200 Subject: [PATCH 08/18] GRIDEDIT-758 Changed loop variable to signed int for openmp --- libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 301e3f943..4738b6fae 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -248,7 +248,7 @@ namespace meshkernel } #pragma omp parallel for - for (UInt i = 0; i < mesh.GetNumNodes(); ++i) + for (int i = 0; i < static_cast(mesh.GetNumNodes()); ++i) { if (mesh.m_nodes[i].IsValid()) { From 548b9435c2a5c5f32d76264c7d89d4d4527ae830 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 12:01:40 +0200 Subject: [PATCH 09/18] GRIDEDIT-758 Corrected headers --- libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 4738b6fae..11090a2c0 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -27,7 +27,8 @@ #pragma once -#include +#include +#include #include "MeshKernel/Definitions.hpp" #include "MeshKernel/Exceptions.hpp" From cd1f32fde38fdec8348cf0cd2fc44a79098c388d Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 13:51:33 +0200 Subject: [PATCH 10/18] GRIDEDIT-758 Removed potentially confusing composition function, corrected comments --- .../include/MeshKernel/MeshTransformation.hpp | 11 +---------- libs/MeshKernel/tests/src/MeshTransformationTest.cpp | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 11090a2c0..0f12921aa 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -132,15 +132,6 @@ namespace meshkernel return Rotation(theta + rot.theta); } - /// @brief Compose rotation and translation object. - /// - /// Will be applied rot (trans). - Translation compose(const Translation& trans) const - { - Vector vec(operator()(trans.vector())); - return Translation(vec); - } - /// @brief Apply the rotation to a point in Cartesian coordinate system Point operator()(const Point& pnt) const { @@ -187,7 +178,7 @@ namespace meshkernel void compose(const Rotation& rot) { rotation_ = rot.compose(rotation_); - translation_ = rot.compose(translation_); + translation_.reset(rot(translation_.vector())); } /// @brief Compose translation and transformation object. diff --git a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp index 42923f6ca..6f9ae7e34 100644 --- a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp +++ b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp @@ -114,7 +114,7 @@ TEST(MeshTransformationTest, BasicRigidBodyTransformationTest) EXPECT_EQ(rotatedVector.x(), transformation.translation().vector().x()); EXPECT_EQ(rotatedVector.y(), transformation.translation().vector().y()); - // Third rotate back + // Third translate back vec = {rotationPoint.x, rotationPoint.y}; translation.reset(vec); transformation.compose(translation); From 1b1ea36ddcbf685a8eacf5b70569bcd7cdd1f83d Mon Sep 17 00:00:00 2001 From: BillSenior Date: Tue, 24 Oct 2023 15:47:31 +0200 Subject: [PATCH 11/18] GRIDEDIT-758 Minor update to exception error message --- libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 0f12921aa..16967a6d3 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -235,7 +235,7 @@ namespace meshkernel { if (mesh.m_projection != Projection::cartesian) { - throw MeshKernelError("Incorrect mesh coordinate system, should be 'Projection::cartesian', found {}", + throw MeshKernelError("Incorrect mesh coordinate system, expecting 'Projection::cartesian', found '{}'", ToString(mesh.m_projection)); } From 2e8f9dadd1070d9d2e820b7b4e478f4fbb0add93 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Wed, 25 Oct 2023 11:25:24 +0200 Subject: [PATCH 12/18] GRIDEDIT-758 Added a transformation projection function --- .../include/MeshKernel/MeshTransformation.hpp | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 16967a6d3..79dd8c261 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -40,8 +40,16 @@ namespace meshkernel { /// @brief Ensure any instantiation of the MeshTransformation Compute function is with the correct operation - template - concept TransformationOperation = requires(Operation op, Point p) {{ op(p)} -> std::same_as; }; + template + concept HasTransformationFunction = requires(Function op, Point p) {{ op(p)} -> std::same_as; }; + + /// @brief Ensure any instantiation of the MeshTransformation Compute function is able to determine the projection. + template + concept HasTransformationProjection = requires(Function op) {{ op.TransformationProjection()} -> std::same_as; }; + + /// @brief To ensure the MeshTransformation Compute template parameter has a valid interface. + template + concept TransformationFunction = HasTransformationFunction && HasTransformationProjection; /// @brief Apply a translation transformation to a point or a vector. class Translation @@ -53,6 +61,12 @@ namespace meshkernel /// @brief Construct with user defined translation explicit Translation(const Vector& trans) : translation(trans) {} + /// @brief Get the projection required for the translation + Projection TransformationProjection() const + { + return Projection::cartesian; + } + /// @brief Reset translation to identity translation (i.e. no translation) void identity() { @@ -104,6 +118,12 @@ namespace meshkernel /// @brief Construct with user defined rotation angle, in radians. explicit Rotation(const double angle) : theta(angle), cosTheta(std::cos(angle)), sinTheta(std::sin(angle)) {} + /// @brief Get the projection required for the rotation + Projection TransformationProjection() const + { + return Projection::cartesian; + } + /// @brief Reset rotation to identity translation (i.e. no rotation, theta = 0) void identity() { @@ -165,6 +185,12 @@ namespace meshkernel /// @brief Default constructor, default is no transformation RigidBodyTransformation() = default; + /// @brief Get the projection required for the transformation + Projection TransformationProjection() const + { + return Projection::cartesian; + } + /// @brief Reset transformation to identity transformation (i.e. no transformation) void identity() { @@ -230,13 +256,13 @@ namespace meshkernel { public: /// @brief Apply a transformation to a mesh with a Cartesian projection - template + template static void Compute(Mesh& mesh, Transformation transformation) { - if (mesh.m_projection != Projection::cartesian) + if (mesh.m_projection != transformation.TransformationProjection()) { - throw MeshKernelError("Incorrect mesh coordinate system, expecting 'Projection::cartesian', found '{}'", - ToString(mesh.m_projection)); + throw MeshKernelError("Incorrect mesh coordinate system, expecting '{}', found '{}'", + ToString(transformation.TransformationProjection()), ToString(mesh.m_projection)); } #pragma omp parallel for From f4ef83bdc496cd0f342a38cb44b2695111d3c6c0 Mon Sep 17 00:00:00 2001 From: Luca Carniato Date: Fri, 10 Nov 2023 11:55:56 +0100 Subject: [PATCH 13/18] Add no discard --- libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 79dd8c261..c3181e35e 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -62,7 +62,7 @@ namespace meshkernel explicit Translation(const Vector& trans) : translation(trans) {} /// @brief Get the projection required for the translation - Projection TransformationProjection() const + [[nodiscard]] Projection TransformationProjection() const { return Projection::cartesian; } @@ -119,7 +119,7 @@ namespace meshkernel explicit Rotation(const double angle) : theta(angle), cosTheta(std::cos(angle)), sinTheta(std::sin(angle)) {} /// @brief Get the projection required for the rotation - Projection TransformationProjection() const + [[nodiscard]] Projection TransformationProjection() const { return Projection::cartesian; } @@ -186,7 +186,7 @@ namespace meshkernel RigidBodyTransformation() = default; /// @brief Get the projection required for the transformation - Projection TransformationProjection() const + [[nodiscard]] Projection TransformationProjection() const { return Projection::cartesian; } From 36c5df1e5a39b2879a0433d7d17dc6d1880a2c2a Mon Sep 17 00:00:00 2001 From: BillSenior Date: Fri, 10 Nov 2023 13:06:59 +0100 Subject: [PATCH 14/18] GRIDEDIT-758 Changes made after code reivew --- .../include/MeshKernel/MeshTransformation.hpp | 73 ++++++++++--------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 79dd8c261..4262099f8 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -59,7 +59,7 @@ namespace meshkernel Translation() = default; /// @brief Construct with user defined translation - explicit Translation(const Vector& trans) : translation(trans) {} + explicit Translation(const Vector& trans) : m_translation(trans) {} /// @brief Get the projection required for the translation Projection TransformationProjection() const @@ -70,42 +70,42 @@ namespace meshkernel /// @brief Reset translation to identity translation (i.e. no translation) void identity() { - translation = {0.0, 0.0}; + m_translation = {0.0, 0.0}; } /// @brief Reset the translation to a new translation quantity void reset(const Vector& trans) { - translation = trans; + m_translation = trans; } /// @brief Get the current defined translation vector. const Vector& vector() const { - return translation; + return m_translation; } /// @brief Compose two translation objects. Translation compose(const Translation& trans) const { - return Translation(translation + trans.translation); + return Translation(m_translation + trans.m_translation); } /// @brief Apply the translation to a point in Cartesian coordinate system Point operator()(const Point& pnt) const { - return pnt + translation; + return pnt + m_translation; } /// @brief Apply the translation to a vector in Cartesian coordinate system Vector operator()(const Vector& vec) const { - return vec + translation; + return vec + m_translation; } private: /// @brief The translation values - Vector translation{0.0, 0.0}; + Vector m_translation{0.0, 0.0}; }; /// @brief Apply a rotation transformation to a point or a vector. @@ -116,7 +116,7 @@ namespace meshkernel Rotation() = default; /// @brief Construct with user defined rotation angle, in radians. - explicit Rotation(const double angle) : theta(angle), cosTheta(std::cos(angle)), sinTheta(std::sin(angle)) {} + explicit Rotation(const double angle) : m_theta(angle), m_cosTheta(std::cos(angle)), m_sinTheta(std::sin(angle)) {} /// @brief Get the projection required for the rotation Projection TransformationProjection() const @@ -127,55 +127,56 @@ namespace meshkernel /// @brief Reset rotation to identity translation (i.e. no rotation, theta = 0) void identity() { - theta = 0.0; - cosTheta = 1.0; - sinTheta = 0.0; + m_theta = 0.0; + m_cosTheta = 1.0; + m_sinTheta = 0.0; } /// @brief Reset the rotation to a new rotation angle void reset(const double angle) { - theta = angle; - cosTheta = std::cos(theta); - sinTheta = std::sin(theta); + m_theta = angle; + m_cosTheta = std::cos(m_theta); + m_sinTheta = std::sin(m_theta); } /// @brief Get the current defined rotation angle double angle() const { - return theta; + return m_theta; } /// @brief Compose two rotation objects. Rotation compose(const Rotation& rot) const { - return Rotation(theta + rot.theta); + return Rotation(m_theta + rot.m_theta); } /// @brief Apply the rotation to a point in Cartesian coordinate system Point operator()(const Point& pnt) const { - Point result({cosTheta * pnt.x - sinTheta * pnt.y, sinTheta * pnt.x + cosTheta * pnt.y}); + Point result({m_cosTheta * pnt.x - m_sinTheta * pnt.y, + m_sinTheta * pnt.x + m_cosTheta * pnt.y}); return result; } /// @brief Apply the rotation to a vector in Cartesian coordinate system Vector operator()(const Vector& vec) const { - Vector result({cosTheta * vec.x() - sinTheta * vec.y(), - sinTheta * vec.x() + cosTheta * vec.y()}); + Vector result({m_cosTheta * vec.x() - m_sinTheta * vec.y(), + m_sinTheta * vec.x() + m_cosTheta * vec.y()}); return result; } private: /// @brief The rotation angle, theta - double theta = 0.0; + double m_theta = 0.0; /// @brief cosine of the rotation angle - double cosTheta = 1.0; + double m_cosTheta = 1.0; /// @brief sine of the rotation angle - double sinTheta = 0.0; + double m_sinTheta = 0.0; }; /// @brief A composition of translation and rotation transformations @@ -194,8 +195,8 @@ namespace meshkernel /// @brief Reset transformation to identity transformation (i.e. no transformation) void identity() { - rotation_.identity(); - translation_.identity(); + m_rotation.identity(); + m_translation.identity(); } /// @brief Compose rotation and transformation object. @@ -203,8 +204,8 @@ namespace meshkernel /// Will be applied: rot (transformation). void compose(const Rotation& rot) { - rotation_ = rot.compose(rotation_); - translation_.reset(rot(translation_.vector())); + m_rotation = rot.compose(m_rotation); + m_translation.reset(rot(m_translation.vector())); } /// @brief Compose translation and transformation object. @@ -212,43 +213,43 @@ namespace meshkernel /// Will be applied: translation (transformation). void compose(const Translation& trans) { - translation_ = trans.compose(translation_); + m_translation = trans.compose(m_translation); } /// @brief Get the current rotation. const Rotation& rotation() const { - return rotation_; + return m_rotation; } /// @brief Get the current translation. const Translation& translation() const { - return translation_; + return m_translation; } /// @brief Apply the transformation to a point in Cartesian coordinate system Point operator()(const Point& pnt) const { - Point result = rotation_(pnt); - result = translation_(result); + Point result = m_rotation(pnt); + result = m_translation(result); return result; } /// @brief Apply the transformation to a vector in Cartesian coordinate system Vector operator()(const Vector& vec) const { - Vector result = rotation_(vec); - result = translation_(result); + Vector result = m_rotation(vec); + result = m_translation(result); return result; } private: /// @brief The rotation part of the transformation - Rotation rotation_; + Rotation m_rotation; /// @brief The translation part of the transformation - Translation translation_; + Translation m_translation; }; /// @brief Apply a transformation to a mesh From 9f6f689781e5c0b94212993ea0ac6e390a8d7f95 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Fri, 10 Nov 2023 13:23:32 +0100 Subject: [PATCH 15/18] GRIDEDIT-758 Added call to administrate after node transformation --- libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 8b4a74184..77f054641 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -274,6 +274,8 @@ namespace meshkernel mesh.m_nodes[i] = transformation(mesh.m_nodes[i]); } } + + mesh.Administrate(); } }; From 18e868dcd5da5423571b75bf4a019f59a424f703 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Fri, 10 Nov 2023 13:34:45 +0100 Subject: [PATCH 16/18] GRIDEDIT-758 Fixed sonarcloud warnings --- .../MeshKernel/ProjectionConversions.hpp | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 libs/MeshKernel/include/MeshKernel/ProjectionConversions.hpp diff --git a/libs/MeshKernel/include/MeshKernel/ProjectionConversions.hpp b/libs/MeshKernel/include/MeshKernel/ProjectionConversions.hpp new file mode 100644 index 000000000..0e4e0c07c --- /dev/null +++ b/libs/MeshKernel/include/MeshKernel/ProjectionConversions.hpp @@ -0,0 +1,176 @@ +//---- GPL --------------------------------------------------------------------- +// +// Copyright (C) Stichting Deltares, 2011-2023. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation version 3. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// contact: delft3d.support@deltares.nl +// Stichting Deltares +// P.O. Box 177 +// 2600 MH Delft, The Netherlands +// +// All indications and logos of, and references to, "Delft3D" and "Deltares" +// are registered trademarks of Stichting Deltares, and remain the property of +// Stichting Deltares. All rights reserved. +// +//------------------------------------------------------------------------------ + +#pragma once + +#include + +#include +#include + +#include "MeshKernel/Mesh.hpp" +#include "MeshKernel/Vector.hpp" + +namespace meshkernel +{ + + /// @brief Namespace alias for boost::geometry + namespace bg = boost::geometry; + + /// @brief Converts points from spherical to Cartesian coordinate system. + template + class ConvertSphericalToCartesianBase + { + public: + /// @brief point in longitude-latitude space + using LongLat = bg::model::d2::point_xy>; + + /// @brief Point in x-y space + using UTM = bg::model::d2::point_xy; + + /// @brief Constructor with projection + explicit ConvertSphericalToCartesianBase(const ProjectionConversion& proj) : m_projection(proj) {} + + /// @brief Default destructor + virtual ~ConvertSphericalToCartesianBase() = default; + + /// @brief The coordinate system of the point parameter to the conversion operation. + Projection SourceProjection() const + { + return Projection::spherical; + } + + /// @brief The coordinate system of the point result of the conversion operation. + Projection TargetProjection() const + { + return Projection::cartesian; + } + + /// @brief Apply the conversion of a point in Spherical coordinate system to Cartesian + Point operator()(const Point& pnt) const + { + LongLat longLat(pnt.x, pnt.y); + UTM utm{0.0, 0.0}; + m_projection.forward(longLat, utm); + + Point result(utm.x(), utm.y()); + return result; + } + + private: + /// @brief The projection conversion object. + ProjectionConversion m_projection; + }; + + /// @brief Converts points from spherical to Cartesian coordinate system using an ESPG code. + template + class ConvertSphericalToCartesianEPSG : public ConvertSphericalToCartesianBase>> + { + public: + /// @brief The EPSG projection + using EpsgProjection = bg::srs::projection>; + + /// @brief Construct spherical to Cartesian with an EPSG code + ConvertSphericalToCartesianEPSG() : ConvertSphericalToCartesianBase>>(EpsgProjection()) {} + }; + + /// @brief Converts points from spherical to Cartesian coordinate system. + class ConvertSphericalToCartesian : public ConvertSphericalToCartesianBase> + { + public: + /// @brief Construct spherical to Cartesian with an zone string + explicit ConvertSphericalToCartesian(const std::string& zone) : ConvertSphericalToCartesianBase>(bg::srs::proj4(zone)) {} + }; + + //-------------------------------- + + /// @brief Converts points from spherical to Cartesian coordinate system. + template + class ConvertCartesianToSphericalBase + { + public: + /// @brief point in longitude-latitude space + using LongLat = bg::model::d2::point_xy>; + + /// @brief Point in x-y space + using UTM = bg::model::d2::point_xy; + + /// @brief Constructor with projection + explicit ConvertCartesianToSphericalBase(const ProjectionConversion& proj) : m_projection(proj) {} + + /// @brief Default destructor + virtual ~ConvertCartesianToSphericalBase() = default; + + /// @brief The coordinate system of the point parameter to the conversion operation. + Projection SourceProjection() const + { + return Projection::cartesian; + } + + /// @brief The coordinate system of the point result of the conversion operation. + Projection TargetProjection() const + { + return Projection::spherical; + } + + /// @brief Apply the conversion of a point in Cartesian coordinate system to spherical + Point operator()(const Point& pnt) const + { + UTM utm{pnt.x, pnt.y}; + LongLat longLat{0.0, 0.0}; + m_projection.inverse(utm, longLat); + + Point result(longLat.x(), longLat.y()); + return result; + } + + private: + /// @brief The projection conversion object. + ProjectionConversion m_projection; + }; + + /// @brief Converts points from spherical to Cartesian coordinate system using an EPSG code. + template + class ConvertCartesianToSphericalEPSG : public ConvertCartesianToSphericalBase>> + { + public: + /// @brief The EPSG projection + using EpsgProjection = boost::geometry::srs::projection>; + + /// @brief Construct spherical to Cartesian with an EPSG code + ConvertCartesianToSphericalEPSG() : ConvertCartesianToSphericalBase>>(EpsgProjection()) {} + }; + + /// @brief Converts points from spherical to Cartesian coordinate system. + class ConvertCartesianToSpherical : public ConvertCartesianToSphericalBase> + { + public: + /// @brief Construct spherical to Cartesian with an zone string + explicit ConvertCartesianToSpherical(const std::string& zone) : ConvertCartesianToSphericalBase>(bg::srs::proj4(zone)) {} + }; + +} // namespace meshkernel From e03370ca844a497b479d82c7cf5a80672d8f3203 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Fri, 10 Nov 2023 13:38:13 +0100 Subject: [PATCH 17/18] GRIDEDIT-758 Removed file added by mistake --- .../MeshKernel/ProjectionConversions.hpp | 176 ------------------ 1 file changed, 176 deletions(-) delete mode 100644 libs/MeshKernel/include/MeshKernel/ProjectionConversions.hpp diff --git a/libs/MeshKernel/include/MeshKernel/ProjectionConversions.hpp b/libs/MeshKernel/include/MeshKernel/ProjectionConversions.hpp deleted file mode 100644 index 0e4e0c07c..000000000 --- a/libs/MeshKernel/include/MeshKernel/ProjectionConversions.hpp +++ /dev/null @@ -1,176 +0,0 @@ -//---- GPL --------------------------------------------------------------------- -// -// Copyright (C) Stichting Deltares, 2011-2023. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation version 3. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// contact: delft3d.support@deltares.nl -// Stichting Deltares -// P.O. Box 177 -// 2600 MH Delft, The Netherlands -// -// All indications and logos of, and references to, "Delft3D" and "Deltares" -// are registered trademarks of Stichting Deltares, and remain the property of -// Stichting Deltares. All rights reserved. -// -//------------------------------------------------------------------------------ - -#pragma once - -#include - -#include -#include - -#include "MeshKernel/Mesh.hpp" -#include "MeshKernel/Vector.hpp" - -namespace meshkernel -{ - - /// @brief Namespace alias for boost::geometry - namespace bg = boost::geometry; - - /// @brief Converts points from spherical to Cartesian coordinate system. - template - class ConvertSphericalToCartesianBase - { - public: - /// @brief point in longitude-latitude space - using LongLat = bg::model::d2::point_xy>; - - /// @brief Point in x-y space - using UTM = bg::model::d2::point_xy; - - /// @brief Constructor with projection - explicit ConvertSphericalToCartesianBase(const ProjectionConversion& proj) : m_projection(proj) {} - - /// @brief Default destructor - virtual ~ConvertSphericalToCartesianBase() = default; - - /// @brief The coordinate system of the point parameter to the conversion operation. - Projection SourceProjection() const - { - return Projection::spherical; - } - - /// @brief The coordinate system of the point result of the conversion operation. - Projection TargetProjection() const - { - return Projection::cartesian; - } - - /// @brief Apply the conversion of a point in Spherical coordinate system to Cartesian - Point operator()(const Point& pnt) const - { - LongLat longLat(pnt.x, pnt.y); - UTM utm{0.0, 0.0}; - m_projection.forward(longLat, utm); - - Point result(utm.x(), utm.y()); - return result; - } - - private: - /// @brief The projection conversion object. - ProjectionConversion m_projection; - }; - - /// @brief Converts points from spherical to Cartesian coordinate system using an ESPG code. - template - class ConvertSphericalToCartesianEPSG : public ConvertSphericalToCartesianBase>> - { - public: - /// @brief The EPSG projection - using EpsgProjection = bg::srs::projection>; - - /// @brief Construct spherical to Cartesian with an EPSG code - ConvertSphericalToCartesianEPSG() : ConvertSphericalToCartesianBase>>(EpsgProjection()) {} - }; - - /// @brief Converts points from spherical to Cartesian coordinate system. - class ConvertSphericalToCartesian : public ConvertSphericalToCartesianBase> - { - public: - /// @brief Construct spherical to Cartesian with an zone string - explicit ConvertSphericalToCartesian(const std::string& zone) : ConvertSphericalToCartesianBase>(bg::srs::proj4(zone)) {} - }; - - //-------------------------------- - - /// @brief Converts points from spherical to Cartesian coordinate system. - template - class ConvertCartesianToSphericalBase - { - public: - /// @brief point in longitude-latitude space - using LongLat = bg::model::d2::point_xy>; - - /// @brief Point in x-y space - using UTM = bg::model::d2::point_xy; - - /// @brief Constructor with projection - explicit ConvertCartesianToSphericalBase(const ProjectionConversion& proj) : m_projection(proj) {} - - /// @brief Default destructor - virtual ~ConvertCartesianToSphericalBase() = default; - - /// @brief The coordinate system of the point parameter to the conversion operation. - Projection SourceProjection() const - { - return Projection::cartesian; - } - - /// @brief The coordinate system of the point result of the conversion operation. - Projection TargetProjection() const - { - return Projection::spherical; - } - - /// @brief Apply the conversion of a point in Cartesian coordinate system to spherical - Point operator()(const Point& pnt) const - { - UTM utm{pnt.x, pnt.y}; - LongLat longLat{0.0, 0.0}; - m_projection.inverse(utm, longLat); - - Point result(longLat.x(), longLat.y()); - return result; - } - - private: - /// @brief The projection conversion object. - ProjectionConversion m_projection; - }; - - /// @brief Converts points from spherical to Cartesian coordinate system using an EPSG code. - template - class ConvertCartesianToSphericalEPSG : public ConvertCartesianToSphericalBase>> - { - public: - /// @brief The EPSG projection - using EpsgProjection = boost::geometry::srs::projection>; - - /// @brief Construct spherical to Cartesian with an EPSG code - ConvertCartesianToSphericalEPSG() : ConvertCartesianToSphericalBase>>(EpsgProjection()) {} - }; - - /// @brief Converts points from spherical to Cartesian coordinate system. - class ConvertCartesianToSpherical : public ConvertCartesianToSphericalBase> - { - public: - /// @brief Construct spherical to Cartesian with an zone string - explicit ConvertCartesianToSpherical(const std::string& zone) : ConvertCartesianToSphericalBase>(bg::srs::proj4(zone)) {} - }; - -} // namespace meshkernel From 5765c4793b6d49dd027adc90090f5d573bf669a7 Mon Sep 17 00:00:00 2001 From: BillSenior Date: Fri, 10 Nov 2023 16:38:11 +0100 Subject: [PATCH 18/18] GRIDEDIT-758 Added mesh kernel api functions for translation and rotation Also, all angles for the rotation have been changed to degrees. --- .../include/MeshKernel/MeshTransformation.hpp | 15 +- .../tests/src/MeshTransformationTest.cpp | 18 +- .../include/MeshKernelApi/MeshKernel.hpp | 17 ++ libs/MeshKernelApi/src/MeshKernel.cpp | 52 +++++ libs/MeshKernelApi/tests/src/ApiTest.cpp | 181 ++++++++++++++++++ 5 files changed, 269 insertions(+), 14 deletions(-) diff --git a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp index 77f054641..9d97caae0 100644 --- a/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp +++ b/libs/MeshKernel/include/MeshKernel/MeshTransformation.hpp @@ -115,8 +115,11 @@ namespace meshkernel /// @brief Default constructor, default is theta = 0. Rotation() = default; - /// @brief Construct with user defined rotation angle, in radians. - explicit Rotation(const double angle) : m_theta(angle), m_cosTheta(std::cos(angle)), m_sinTheta(std::sin(angle)) {} + /// @brief Construct with user defined rotation angle, in degrees. + explicit Rotation(const double angle) + { + reset(angle); + } /// @brief Get the projection required for the rotation [[nodiscard]] Projection TransformationProjection() const @@ -133,14 +136,16 @@ namespace meshkernel } /// @brief Reset the rotation to a new rotation angle + /// + /// The angle in degrees. void reset(const double angle) { m_theta = angle; - m_cosTheta = std::cos(m_theta); - m_sinTheta = std::sin(m_theta); + m_cosTheta = std::cos(m_theta * constants::conversion::degToRad); + m_sinTheta = std::sin(m_theta * constants::conversion::degToRad); } - /// @brief Get the current defined rotation angle + /// @brief Get the current defined rotation angle in degrees double angle() const { return m_theta; diff --git a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp index 6f9ae7e34..a638167ff 100644 --- a/libs/MeshKernel/tests/src/MeshTransformationTest.cpp +++ b/libs/MeshKernel/tests/src/MeshTransformationTest.cpp @@ -41,7 +41,7 @@ TEST(MeshTransformationTest, BasicTranslationTest) TEST(MeshTransformationTest, BasicRotationTest) { // Test basic functionality of the rotation class - double theta = 45.0 * M_PI / 180.0; + double theta = 45.0; mk::Rotation rotation(theta); @@ -85,7 +85,7 @@ TEST(MeshTransformationTest, BasicRigidBodyTransformationTest) // Step 2 apply composite transformation to a series of points constexpr double tolerance = 1.0e-10; - double theta = -90.0 * M_PI / 180.0; + double theta = -90.0; mk::Point rotationPoint(-1.0, 1.0); mk::RigidBodyTransformation transformation; @@ -157,9 +157,9 @@ TEST(MeshTransformationTest, PointRotationTest) double originX = 12.0; double originY = -19.0; - double theta = 45.0 * M_PI / 180.0; - double cosTheta = std::cos(theta); - double sinTheta = std::sin(theta); + double theta = 45.0; + double cosTheta = std::cos(theta * M_PI / 180.0); + double sinTheta = std::sin(theta * M_PI / 180.0); mk::Rotation rotation(theta); mk::Point pnt(originX, originY); @@ -208,11 +208,11 @@ TEST(MeshTransformationTest, MeshRotationTest) std::shared_ptr originalMesh = MakeRectangularMeshForTesting(nx, ny, delta, mk::Projection::cartesian); std::shared_ptr mesh = MakeRectangularMeshForTesting(nx, ny, delta, mk::Projection::cartesian); - double theta = 45.0 * M_PI / 180.0; + double theta = 45.0; mk::Rotation rotation(theta); - double cosTheta = std::cos(theta); - double sinTheta = std::sin(theta); + double cosTheta = std::cos(theta * M_PI / 180.0); + double sinTheta = std::sin(theta * M_PI / 180.0); mk::MeshTransformation::Compute(*mesh, rotation); @@ -232,7 +232,7 @@ TEST(MeshTransformationTest, IncorrectProjectionTest) // Generate mesh in spherical coordinate system std::shared_ptr mesh = MakeRectangularMeshForTesting(11, 11, 10.0, mk::Projection::spherical); - mk::Rotation rotation(45.0 * M_PI / 180.0); + mk::Rotation rotation(45.0); // Should throw an exception with spherical coordinate system EXPECT_THROW(mk::MeshTransformation::Compute(*mesh, rotation), mk::MeshKernelError); diff --git a/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp b/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp index c1dfcd4dc..6eb48b58f 100644 --- a/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp +++ b/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp @@ -710,6 +710,15 @@ namespace meshkernelapi /// @returns Error code MKERNEL_API int mkernel_mesh2d_compute_inner_ortogonalization_iteration(int meshKernelId); + /// @brief Rotate a mesh2d about a point. + /// + /// @param[in] meshKernelId The id of the mesh state + /// @param[in] centreX X-coordinate of the centre of rotation + /// @param[in] centreY Y-coordinate of the centre of rotation + /// @param[in] theta Angle of rotation + /// @returns Error code + MKERNEL_API int mkernel_mesh2d_rotate(int meshKernelId, double centreX, double centreY, double theta); + /// @brief Snaps the spline (or splines) to the land boundary /// /// @param[in] meshKernelId The id of the mesh state @@ -724,6 +733,14 @@ namespace meshkernelapi int startSplineIndex, int endSplineIndex); + /// @brief Translate a mesh2d + /// + /// @param[in] meshKernelId The id of the mesh state + /// @param[in] translationX X-component of the translation vector + /// @param[in] translationY Y-component of the translation vector + /// @returns Error code + MKERNEL_API int mkernel_mesh2d_translate(int meshKernelId, double translationX, double translationY); + /// The function modifies the mesh for achieving orthogonality between the edges and the segments connecting the face circumcenters. /// The amount of orthogonality is traded against the mesh smoothing (in this case the equality of face areas). /// The parameter to regulate the amount of orthogonalization is contained in \ref meshkernel::OrthogonalizationParameters::orthogonalization_to_smoothing_factor diff --git a/libs/MeshKernelApi/src/MeshKernel.cpp b/libs/MeshKernelApi/src/MeshKernel.cpp index 2fcc78685..63ee6d1a8 100644 --- a/libs/MeshKernelApi/src/MeshKernel.cpp +++ b/libs/MeshKernelApi/src/MeshKernel.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -1787,6 +1788,57 @@ namespace meshkernelapi return lastExitCode; } + MKERNEL_API int mkernel_mesh2d_rotate(int meshKernelId, double centreX, double centreY, double theta) + { + lastExitCode = meshkernel::ExitCode::Success; + + try + { + if (!meshKernelState.contains(meshKernelId)) + { + throw meshkernel::MeshKernelError("The selected mesh kernel id does not exist."); + } + + meshkernel::RigidBodyTransformation transformation; + meshkernel::Translation translation; + + // Translate centre of rotation to origin + transformation.compose(meshkernel::Translation(meshkernel::Vector(-centreX, -centreY))); + // Add rotation + transformation.compose(meshkernel::Rotation(theta)); + // Translate origin back to centre of rotation + transformation.compose(meshkernel::Translation(meshkernel::Vector(centreX, centreY))); + + meshkernel::MeshTransformation::Compute(*meshKernelState[meshKernelId].m_mesh2d, transformation); + } + catch (...) + { + lastExitCode = HandleException(); + } + return lastExitCode; + } + + MKERNEL_API int mkernel_mesh2d_translate(int meshKernelId, double translationX, double translationY) + { + lastExitCode = meshkernel::ExitCode::Success; + + try + { + if (!meshKernelState.contains(meshKernelId)) + { + throw meshkernel::MeshKernelError("The selected mesh kernel id does not exist."); + } + + meshkernel::Translation translation(meshkernel::Vector(translationX, translationY)); + meshkernel::MeshTransformation::Compute(*meshKernelState[meshKernelId].m_mesh2d, translation); + } + catch (...) + { + lastExitCode = HandleException(); + } + return lastExitCode; + } + MKERNEL_API int mkernel_splines_snap_to_landboundary(int meshKernelId, const GeometryList& land, GeometryList& splines, diff --git a/libs/MeshKernelApi/tests/src/ApiTest.cpp b/libs/MeshKernelApi/tests/src/ApiTest.cpp index 06bf5c62c..a2bc37487 100644 --- a/libs/MeshKernelApi/tests/src/ApiTest.cpp +++ b/libs/MeshKernelApi/tests/src/ApiTest.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -2487,3 +2488,183 @@ TEST_F(CartesianApiTestFixture, GenerateTriangularGridThroughApi_OnClockWisePoly ASSERT_EQ(5, mesh2d.num_nodes); ASSERT_EQ(8, mesh2d.num_edges); } + +TEST_F(CartesianApiTestFixture, TranslateMesh) +{ + // Prepare + + MakeMesh(); + auto const meshKernelId = GetMeshKernelId(); + + meshkernelapi::Mesh2D mesh2d{}; + [[maybe_unused]] int errorCode = mkernel_mesh2d_get_dimensions(meshKernelId, mesh2d); + + // Get copy of original mesh + // Will be used later to compare with translated mesh + std::vector edge_faces(mesh2d.num_edges * 2); + std::vector edge_nodes(mesh2d.num_edges * 2); + std::vector face_nodes(mesh2d.num_face_nodes); + std::vector face_edges(mesh2d.num_face_nodes); + std::vector nodes_per_face(mesh2d.num_faces); + std::vector node_x(mesh2d.num_nodes); + std::vector node_y(mesh2d.num_nodes); + std::vector edge_x(mesh2d.num_edges); + std::vector edge_y(mesh2d.num_edges); + std::vector face_x(mesh2d.num_faces); + std::vector face_y(mesh2d.num_faces); + + mesh2d.edge_faces = edge_faces.data(); + mesh2d.edge_nodes = edge_nodes.data(); + mesh2d.face_nodes = face_nodes.data(); + mesh2d.face_edges = face_edges.data(); + mesh2d.nodes_per_face = nodes_per_face.data(); + mesh2d.node_x = node_x.data(); + mesh2d.node_y = node_y.data(); + mesh2d.edge_x = edge_x.data(); + mesh2d.edge_y = edge_y.data(); + mesh2d.face_x = face_x.data(); + mesh2d.face_y = face_y.data(); + errorCode = mkernel_mesh2d_get_data(meshKernelId, mesh2d); + + double translationX = 10.0; + double translationY = 5.0; + + meshkernelapi::mkernel_mesh2d_translate(meshKernelId, translationX, translationY); + + meshkernelapi::Mesh2D mesh2dTranslated{}; + errorCode = mkernel_mesh2d_get_dimensions(meshKernelId, mesh2dTranslated); + + // Data for translated mesh + std::vector edge_faces_t(mesh2dTranslated.num_edges * 2); + std::vector edge_nodes_t(mesh2dTranslated.num_edges * 2); + std::vector face_nodes_t(mesh2dTranslated.num_face_nodes); + std::vector face_edges_t(mesh2dTranslated.num_face_nodes); + std::vector nodes_per_face_t(mesh2dTranslated.num_faces); + std::vector node_x_t(mesh2dTranslated.num_nodes); + std::vector node_y_t(mesh2dTranslated.num_nodes); + std::vector edge_x_t(mesh2dTranslated.num_edges); + std::vector edge_y_t(mesh2dTranslated.num_edges); + std::vector face_x_t(mesh2dTranslated.num_faces); + std::vector face_y_t(mesh2dTranslated.num_faces); + + mesh2dTranslated.edge_faces = edge_faces_t.data(); + mesh2dTranslated.edge_nodes = edge_nodes_t.data(); + mesh2dTranslated.face_nodes = face_nodes_t.data(); + mesh2dTranslated.face_edges = face_edges_t.data(); + mesh2dTranslated.nodes_per_face = nodes_per_face_t.data(); + mesh2dTranslated.node_x = node_x_t.data(); + mesh2dTranslated.node_y = node_y_t.data(); + mesh2dTranslated.edge_x = edge_x_t.data(); + mesh2dTranslated.edge_y = edge_y_t.data(); + mesh2dTranslated.face_x = face_x_t.data(); + mesh2dTranslated.face_y = face_y_t.data(); + + errorCode = mkernel_mesh2d_get_data(meshKernelId, mesh2dTranslated); + + const double tolerance = 1e-12; + + for (int i = 0; i < mesh2d.num_nodes; ++i) + { + EXPECT_NEAR(mesh2d.node_x[i] + translationX, mesh2dTranslated.node_x[i], tolerance); + EXPECT_NEAR(mesh2d.node_y[i] + translationY, mesh2dTranslated.node_y[i], tolerance); + } +} + +TEST_F(CartesianApiTestFixture, RotateMesh) +{ + // Prepare + + MakeMesh(); + auto const meshKernelId = GetMeshKernelId(); + + meshkernelapi::Mesh2D mesh2d{}; + [[maybe_unused]] int errorCode = mkernel_mesh2d_get_dimensions(meshKernelId, mesh2d); + + // Get copy of original mesh + // Will be used later to compare with translated mesh + std::vector edge_faces(mesh2d.num_edges * 2); + std::vector edge_nodes(mesh2d.num_edges * 2); + std::vector face_nodes(mesh2d.num_face_nodes); + std::vector face_edges(mesh2d.num_face_nodes); + std::vector nodes_per_face(mesh2d.num_faces); + std::vector node_x(mesh2d.num_nodes); + std::vector node_y(mesh2d.num_nodes); + std::vector edge_x(mesh2d.num_edges); + std::vector edge_y(mesh2d.num_edges); + std::vector face_x(mesh2d.num_faces); + std::vector face_y(mesh2d.num_faces); + + mesh2d.edge_faces = edge_faces.data(); + mesh2d.edge_nodes = edge_nodes.data(); + mesh2d.face_nodes = face_nodes.data(); + mesh2d.face_edges = face_edges.data(); + mesh2d.nodes_per_face = nodes_per_face.data(); + mesh2d.node_x = node_x.data(); + mesh2d.node_y = node_y.data(); + mesh2d.edge_x = edge_x.data(); + mesh2d.edge_y = edge_y.data(); + mesh2d.face_x = face_x.data(); + mesh2d.face_y = face_y.data(); + errorCode = mkernel_mesh2d_get_data(meshKernelId, mesh2d); + + double centreX = 10.0; + double centreY = 5.0; + double angle = 45.0; + + meshkernelapi::mkernel_mesh2d_rotate(meshKernelId, centreX, centreY, angle); + + meshkernelapi::Mesh2D mesh2dTranslated{}; + errorCode = mkernel_mesh2d_get_dimensions(meshKernelId, mesh2dTranslated); + + // Data for translated mesh + std::vector edge_faces_t(mesh2dTranslated.num_edges * 2); + std::vector edge_nodes_t(mesh2dTranslated.num_edges * 2); + std::vector face_nodes_t(mesh2dTranslated.num_face_nodes); + std::vector face_edges_t(mesh2dTranslated.num_face_nodes); + std::vector nodes_per_face_t(mesh2dTranslated.num_faces); + std::vector node_x_t(mesh2dTranslated.num_nodes); + std::vector node_y_t(mesh2dTranslated.num_nodes); + std::vector edge_x_t(mesh2dTranslated.num_edges); + std::vector edge_y_t(mesh2dTranslated.num_edges); + std::vector face_x_t(mesh2dTranslated.num_faces); + std::vector face_y_t(mesh2dTranslated.num_faces); + + mesh2dTranslated.edge_faces = edge_faces_t.data(); + mesh2dTranslated.edge_nodes = edge_nodes_t.data(); + mesh2dTranslated.face_nodes = face_nodes_t.data(); + mesh2dTranslated.face_edges = face_edges_t.data(); + mesh2dTranslated.nodes_per_face = nodes_per_face_t.data(); + mesh2dTranslated.node_x = node_x_t.data(); + mesh2dTranslated.node_y = node_y_t.data(); + mesh2dTranslated.edge_x = edge_x_t.data(); + mesh2dTranslated.edge_y = edge_y_t.data(); + mesh2dTranslated.face_x = face_x_t.data(); + mesh2dTranslated.face_y = face_y_t.data(); + + errorCode = mkernel_mesh2d_get_data(meshKernelId, mesh2dTranslated); + + const double tolerance = 1e-12; + + meshkernel::RigidBodyTransformation transformation; + + // First translate + meshkernel::Translation translation(meshkernel::Vector(-centreX, -centreY)); + transformation.compose(translation); + + // Second rotate + transformation.compose(meshkernel::Rotation(angle)); + + // Third translate back + translation.reset(meshkernel::Vector(centreX, centreY)); + transformation.compose(translation); + + for (int i = 0; i < mesh2d.num_nodes; ++i) + { + // Apply expected transformed to the original mesh node + meshkernel::Point expected = transformation(meshkernel::Point(mesh2d.node_x[i], mesh2d.node_y[i])); + + // The compare the expected node with the transformed mesh node + EXPECT_NEAR(expected.x, mesh2dTranslated.node_x[i], tolerance); + EXPECT_NEAR(expected.y, mesh2dTranslated.node_y[i], tolerance); + } +}