diff --git a/libs/MeshKernel/CMakeLists.txt b/libs/MeshKernel/CMakeLists.txt index 3cc7122be..6c45b68cb 100644 --- a/libs/MeshKernel/CMakeLists.txt +++ b/libs/MeshKernel/CMakeLists.txt @@ -47,6 +47,7 @@ set( ${SRC_DIR}/Mesh2D.cpp ${SRC_DIR}/Mesh2DGenerateGlobal.cpp ${SRC_DIR}/Mesh2DIntersections.cpp + ${SRC_DIR}/Mesh2DToCurvilinear.cpp ${SRC_DIR}/MeshRefinement.cpp ${SRC_DIR}/Network1D.cpp ${SRC_DIR}/Operations.cpp @@ -147,6 +148,7 @@ set( ${DOMAIN_INC_DIR}/Mesh2D.hpp ${DOMAIN_INC_DIR}/Mesh2DGenerateGlobal.hpp ${DOMAIN_INC_DIR}/Mesh2DIntersections.hpp + ${DOMAIN_INC_DIR}/Mesh2DToCurvilinear.hpp ${DOMAIN_INC_DIR}/MeshConversion.hpp ${DOMAIN_INC_DIR}/MeshInterpolation.hpp ${DOMAIN_INC_DIR}/MeshRefinement.hpp diff --git a/libs/MeshKernel/include/MeshKernel/Mesh2DToCurvilinear.hpp b/libs/MeshKernel/include/MeshKernel/Mesh2DToCurvilinear.hpp new file mode 100644 index 000000000..776bab077 --- /dev/null +++ b/libs/MeshKernel/include/MeshKernel/Mesh2DToCurvilinear.hpp @@ -0,0 +1,89 @@ +//---- 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 "MeshKernel/CurvilinearGrid/CurvilinearGrid.hpp" +#include "MeshKernel/Definitions.hpp" +#include "MeshKernel/Mesh2D.hpp" +#include "MeshKernel/Point.hpp" +#include "Utilities/LinearAlgebra.hpp" + +using namespace meshkernel::constants; + +namespace meshkernel +{ + /// @brief Construct a curvilinear grid from an unstructured mesh + class Mesh2DToCurvilinear + { + public: + /// @brief Constructor + /// @param[in] mesh The input unstructured mesh + explicit Mesh2DToCurvilinear(Mesh2D& mesh); + + /// @brief Computes the curvilinear grid starting from a specific point + /// @param[in] point The point from which start growing the curvilinear mesh. The point must be inside a quadrangular face + /// @returns The curvilinear grid + std::unique_ptr Compute(const Point& point); + + private: + /// @brief Computes the local mapping of the nodes composing the face + [[nodiscard]] Eigen::Matrix ComputeLocalNodeMapping(UInt face) const; + + /// @brief Computes the node indices of the neighbouring faces + [[nodiscard]] UInt ComputeNeighbouringFaceNodes(const UInt face, + const Eigen::Matrix& localNodeMapping, + const UInt d, + const std::vector& visitedFace); + + /// @brief Computes the final curvilinear matrix + [[nodiscard]] lin_alg::Matrix ComputeCurvilinearMatrix(); + + Mesh2D& m_mesh; ///< The mesh to convert + std::vector m_i; ///< The i indices of each node on the curvilinear grid + std::vector m_j; ///< The j indices of each node on the curvilinear grid + + const std::array, 4> m_nodeFrom = {{{0, 0}, + {0, 0}, + {1, 0}, + {1, 1}}}; ///< starting edge node indices for each direction in the local mapping + + const std::array, 4> m_nodeTo = {{{0, 1}, + {1, 0}, + {1, 1}, + {0, 1}}}; ///< ending edge node indices for each direction in the local mapping + + const std::array, 4> m_directionsDeltas = {{{-1, 0}, + {0, -1}, + {1, 0}, + {0, 1}}}; ///< increments for the new nodes depending on the node direction + + const int n_maxNumRowsColumns = 1000000; ///< The maximum number of allowed rows or columns + }; + +} // namespace meshkernel diff --git a/libs/MeshKernel/src/Mesh2DToCurvilinear.cpp b/libs/MeshKernel/src/Mesh2DToCurvilinear.cpp new file mode 100644 index 000000000..b71550568 --- /dev/null +++ b/libs/MeshKernel/src/Mesh2DToCurvilinear.cpp @@ -0,0 +1,296 @@ +//---- 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. +// +//------------------------------------------------------------------------------ + +#include + +#include "MeshKernel/Mesh2DToCurvilinear.hpp" + +using namespace meshkernel; +using namespace constants; + +Mesh2DToCurvilinear::Mesh2DToCurvilinear(Mesh2D& mesh) : m_mesh(mesh) +{ + if (mesh.GetNumNodes() <= 0) + { + throw AlgorithmError("Mesh with no nodes"); + } + + mesh.Administrate(); + + if (mesh.GetNumFaces() <= 0) + { + throw AlgorithmError("Mesh with no faces"); + } +} + +std::unique_ptr Mesh2DToCurvilinear::Compute(const Point& point) +{ + // 1. Find the face index + m_mesh.BuildTree(Location::Faces); + const auto initialFaceIndex = m_mesh.FindLocationIndex(point, Location::Faces); + if (m_mesh.GetNumFaceEdges(initialFaceIndex) != geometric::numNodesInQuadrilateral) + { + throw AlgorithmError("The initial face is not a quadrilateral"); + } + + // 2. Check if the point is inside the face + std::vector polygonPoints; + for (UInt n = 0; n < geometric::numNodesInQuadrilateral; ++n) + { + const auto node = m_mesh.m_facesNodes[initialFaceIndex][n]; + polygonPoints.emplace_back(m_mesh.Node(node)); + } + const auto node = m_mesh.m_facesNodes[initialFaceIndex][0]; + polygonPoints.emplace_back(m_mesh.Node(node)); + + Polygon polygon(polygonPoints, m_mesh.m_projection); + if (!polygon.Contains(point)) + { + throw AlgorithmError("The initial face does not contain the starting point"); + } + + // 3. Build the local coordinate system + const auto numNodes = m_mesh.GetNumNodes(); + m_i = std::vector(numNodes, missing::intValue); + m_j = std::vector(numNodes, missing::intValue); + + const auto firstEdge = m_mesh.m_facesEdges[initialFaceIndex][0]; + const auto secondEdge = m_mesh.m_facesEdges[initialFaceIndex][1]; + const auto thirdEdge = m_mesh.m_facesEdges[initialFaceIndex][2]; + const auto fourthEdge = m_mesh.m_facesEdges[initialFaceIndex][3]; + + const auto firstNodeIndex = m_mesh.FindCommonNode(firstEdge, secondEdge); + m_i[firstNodeIndex] = 0; + m_j[firstNodeIndex] = 0; + + const auto secondNodeIndex = m_mesh.FindCommonNode(secondEdge, thirdEdge); + m_i[secondNodeIndex] = 1; + m_j[secondNodeIndex] = 0; + + const auto thirdNodeIndex = m_mesh.FindCommonNode(thirdEdge, fourthEdge); + m_i[thirdNodeIndex] = 1; + m_j[thirdNodeIndex] = 1; + + const auto fourthNodeIndex = m_mesh.FindCommonNode(fourthEdge, firstEdge); + m_i[fourthNodeIndex] = 0; + m_j[fourthNodeIndex] = 1; + + // 4. Grow the front using the breath first search algorithm + const auto numFaces = m_mesh.GetNumFaces(); + std::vector visitedFace(numFaces, false); + std::queue q; + q.push(initialFaceIndex); + + while (!q.empty()) + { + const auto face = q.front(); + q.pop(); + + if (visitedFace[face]) + { + continue; + } + visitedFace[face] = true; + + if (m_mesh.GetNumFaceEdges(face) != geometric::numNodesInQuadrilateral) + { + continue; + } + + const auto localNodeMapping = ComputeLocalNodeMapping(face); + for (UInt d = 0; d < geometric::numNodesInQuadrilateral; ++d) + { + const auto newFaceIndex = ComputeNeighbouringFaceNodes(face, localNodeMapping, d, visitedFace); + if (newFaceIndex != missing::uintValue) + { + q.push(newFaceIndex); + } + } + } + return std::make_unique(ComputeCurvilinearMatrix(), m_mesh.m_projection); +} + +Eigen::Matrix Mesh2DToCurvilinear::ComputeLocalNodeMapping(UInt face) const +{ + const auto& faceIndices = m_mesh.m_facesNodes[face]; + + const auto node0 = faceIndices[0]; + const auto node1 = faceIndices[1]; + const auto node2 = faceIndices[2]; + const auto node3 = faceIndices[3]; + + const std::vector localI{m_i[node0], m_i[node1], m_i[node2], m_i[node3]}; + const std::vector localJ{m_j[node0], m_j[node1], m_j[node2], m_j[node3]}; + const std::vector localNodes{node0, node1, node2, node3}; + + const auto minI = *std::ranges::min_element(localI); + const auto minJ = *std::ranges::min_element(localJ); + + Eigen::Matrix matrix; + for (UInt i = 0; i < localI.size(); ++i) + { + if (localI[i] == minI && localJ[i] == minJ) + { + matrix(0, 0) = localNodes[i]; + } + if (localI[i] == minI + 1 && localJ[i] == minJ) + { + matrix(1, 0) = localNodes[i]; + } + if (localI[i] == minI && localJ[i] == minJ + 1) + { + matrix(0, 1) = localNodes[i]; + } + if (localI[i] == minI + 1 && localJ[i] == minJ + 1) + { + matrix(1, 1) = localNodes[i]; + } + } + return matrix; +} + +UInt Mesh2DToCurvilinear::ComputeNeighbouringFaceNodes(const UInt face, + const Eigen::Matrix& localNodeMapping, + const UInt d, + const std::vector& visitedFace) +{ + + const auto firstNode = localNodeMapping(m_nodeFrom[d][0], m_nodeFrom[d][1]); + const auto secondNode = localNodeMapping(m_nodeTo[d][0], m_nodeTo[d][1]); + + // find the edge index + const auto edgeIndex = m_mesh.FindEdge(firstNode, secondNode); + if (edgeIndex == missing::uintValue) + { + return missing::uintValue; + } + + // this edge belongs only to the current face + if (m_mesh.m_edgesNumFaces[edgeIndex] < 2) + { + return missing::uintValue; + } + + const auto newFace = face == m_mesh.m_edgesFaces[edgeIndex][0] ? m_mesh.m_edgesFaces[edgeIndex][1] : m_mesh.m_edgesFaces[edgeIndex][0]; + + if (visitedFace[newFace]) + { + return missing::uintValue; + } + + if (m_mesh.GetNumFaceEdges(newFace) != geometric::numNodesInQuadrilateral) + { + return missing::uintValue; + } + + int edgeIndexInNewFace = 0; + for (UInt e = 0u; e < geometric::numNodesInQuadrilateral; ++e) + { + if (m_mesh.m_facesEdges[newFace][e] == edgeIndex) + { + edgeIndexInNewFace = static_cast(e); + break; + } + } + auto nextEdgeIndexInNewFace = edgeIndexInNewFace + 1; + nextEdgeIndexInNewFace = nextEdgeIndexInNewFace == 4 ? 0 : nextEdgeIndexInNewFace; + const auto nextEdgeInNewFace = m_mesh.m_facesEdges[newFace][nextEdgeIndexInNewFace]; + const auto firstCommonNode = m_mesh.FindCommonNode(edgeIndex, nextEdgeInNewFace); + const auto firstOtherNode = OtherNodeOfEdge(m_mesh.GetEdge(nextEdgeInNewFace), firstCommonNode); + const auto iFirstOtherNode = m_i[firstCommonNode] + m_directionsDeltas[d][0]; + const auto jFirstOtherNode = m_j[firstCommonNode] + m_directionsDeltas[d][1]; + + auto previousEdgeIndexInNewFace = edgeIndexInNewFace - 1; + previousEdgeIndexInNewFace = previousEdgeIndexInNewFace == -1 ? 3 : previousEdgeIndexInNewFace; + const auto previousEdgeInNewFace = m_mesh.m_facesEdges[newFace][previousEdgeIndexInNewFace]; + const auto secondCommonNode = m_mesh.FindCommonNode(edgeIndex, previousEdgeInNewFace); + const auto secondOtherNode = OtherNodeOfEdge(m_mesh.GetEdge(previousEdgeInNewFace), secondCommonNode); + const auto iSecondCommonNode = m_i[secondCommonNode] + m_directionsDeltas[d][0]; + const auto jSecondCommonNode = m_j[secondCommonNode] + m_directionsDeltas[d][1]; + + const auto invalid = (m_i[firstOtherNode] != missing::intValue && m_i[firstOtherNode] != iFirstOtherNode) || + (m_j[firstOtherNode] != missing::intValue && m_j[firstOtherNode] != jFirstOtherNode) || + (m_i[secondOtherNode] != missing::intValue && m_i[secondOtherNode] != iSecondCommonNode) || + (m_j[secondOtherNode] != missing::intValue && m_j[secondOtherNode] != jSecondCommonNode); + + if (!invalid) + { + m_i[firstOtherNode] = iFirstOtherNode; + m_j[firstOtherNode] = jFirstOtherNode; + m_i[secondOtherNode] = iSecondCommonNode; + m_j[secondOtherNode] = jSecondCommonNode; + return newFace; + } + return missing::uintValue; +} + +lin_alg::Matrix Mesh2DToCurvilinear::ComputeCurvilinearMatrix() +{ + + int minI = n_maxNumRowsColumns; + int minJ = n_maxNumRowsColumns; + const auto numNodes = m_mesh.GetNumNodes(); + for (UInt n = 0; n < numNodes; ++n) + { + if (m_i[n] != missing::intValue) + { + minI = std::min(minI, m_i[n]); + } + if (m_j[n] != missing::intValue) + { + minJ = std::min(minJ, m_j[n]); + } + } + + int maxI = -n_maxNumRowsColumns; + int maxJ = -n_maxNumRowsColumns; + for (UInt n = 0; n < numNodes; ++n) + { + if (m_i[n] != missing::intValue) + { + m_i[n] = m_i[n] - minI; + maxI = std::max(maxI, m_i[n]); + } + if (m_j[n] != missing::intValue) + { + m_j[n] = m_j[n] - minJ; + maxJ = std::max(maxJ, m_j[n]); + } + } + + lin_alg::Matrix result(maxI + 1, maxJ + 1); + for (UInt n = 0; n < numNodes; ++n) + { + const auto i = m_i[n]; + const auto j = m_j[n]; + if (i != missing::intValue && j != missing::intValue) + { + result(i, j) = m_mesh.Node(n); + } + } + return result; +} diff --git a/libs/MeshKernel/tests/src/Mesh2DTest.cpp b/libs/MeshKernel/tests/src/Mesh2DTest.cpp index 6907512cc..f86bc0ed6 100644 --- a/libs/MeshKernel/tests/src/Mesh2DTest.cpp +++ b/libs/MeshKernel/tests/src/Mesh2DTest.cpp @@ -1,20 +1,20 @@ -#include "MeshKernel/Mesh2DIntersections.hpp" - #include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include "MeshKernel/Constants.hpp" +#include "MeshKernel/Entities.hpp" +#include "MeshKernel/Mesh.hpp" +#include "MeshKernel/Mesh2D.hpp" +#include "MeshKernel/Mesh2DIntersections.hpp" +#include "MeshKernel/Mesh2DToCurvilinear.hpp" +#include "MeshKernel/Operations.hpp" +#include "MeshKernel/Polygons.hpp" +#include "MeshKernel/RemoveDisconnectedRegions.hpp" -#include +#include "TestUtils/Definitions.hpp" +#include "TestUtils/MakeMeshes.hpp" TEST(Mesh2D, OneQuadTestConstructor) { @@ -1230,3 +1230,111 @@ TEST(Mesh2D, DeleteMesh_WithPolygonAndIncludedCircumcenters_ShouldDeleteInnerFac EXPECT_EQ(originalEdges[i].second, mesh->GetEdge(i).second); } } + +TEST(Mesh2D, Mesh2DToCurvilinear_WithRectangularMesh_ShouldCreateFullCurvilinearMesh) +{ + // Prepare + const auto mesh = MakeRectangularMeshForTesting(3, + 3, + 10.0, + 10.0, + meshkernel::Projection::cartesian); + meshkernel::Mesh2DToCurvilinear mesh2DToCurvilinear(*mesh); + + // Execute + const meshkernel::Point point(5.0, 5.0); + const auto curvilinearGrid = mesh2DToCurvilinear.Compute(point); + + // Assert + ASSERT_EQ(3, curvilinearGrid->NumM()); + ASSERT_EQ(3, curvilinearGrid->NumN()); + ASSERT_EQ(9, curvilinearGrid->GetNumNodes()); + + ASSERT_EQ(0.0, curvilinearGrid->GetNode(0, 2).x); + ASSERT_EQ(0.0, curvilinearGrid->GetNode(0, 2).y); + + ASSERT_EQ(5.0, curvilinearGrid->GetNode(0, 1).x); + ASSERT_EQ(0.0, curvilinearGrid->GetNode(0, 1).y); + + ASSERT_EQ(10.0, curvilinearGrid->GetNode(0, 0).x); + ASSERT_EQ(0.0, curvilinearGrid->GetNode(0, 0).y); +} + +TEST(Mesh2D, Mesh2DToCurvilinear_WithStartingPointOutsideMesh_ShouldThrowAnException) +{ + // Prepare + const auto mesh = MakeRectangularMeshForTesting(3, + 3, + 10.0, + 10.0, + meshkernel::Projection::cartesian); + meshkernel::Mesh2DToCurvilinear mesh2DToCurvilinear(*mesh); + + // Execute + const meshkernel::Point point(-20.0, -20.0); + + // Assert + EXPECT_THROW(mesh2DToCurvilinear.Compute(point), meshkernel::AlgorithmError); +} + +TEST(Mesh2D, Mesh2DToCurvilinear_WithMixedMesh_ShouldCreatePartialCurvilinearMesh) +{ + // Prepare a mixed mesh with two triangles at the boundary + std::vector nodes; + nodes.push_back({-10.0, 0.0}); + nodes.push_back({0.0, 0.0}); + nodes.push_back({10.0, 0.0}); + nodes.push_back({20.0, 0.0}); + nodes.push_back({0.0, 10.0}); + nodes.push_back({10.0, 10.0}); + nodes.push_back({20.0, 10.0}); + nodes.push_back({0.0, 20.0}); + nodes.push_back({10.0, 20.0}); + + std::vector edges; + edges.push_back({0, 4}); + edges.push_back({0, 1}); + edges.push_back({1, 4}); + edges.push_back({1, 2}); + edges.push_back({2, 5}); + edges.push_back({2, 3}); + edges.push_back({3, 6}); + edges.push_back({4, 7}); + edges.push_back({4, 5}); + edges.push_back({5, 8}); + edges.push_back({5, 6}); + edges.push_back({7, 8}); + edges.push_back({6, 8}); + + // 2 Execute + auto mesh = meshkernel::Mesh2D(edges, nodes, meshkernel::Projection::cartesian); + meshkernel::Mesh2DToCurvilinear mesh2DToCurvilinear(mesh); + const meshkernel::Point point(5.0, 5.0); + const auto curvilinearGrid = mesh2DToCurvilinear.Compute(point); + + // Assert + ASSERT_EQ(3, curvilinearGrid->NumM()); + ASSERT_EQ(3, curvilinearGrid->NumN()); + ASSERT_EQ(9, curvilinearGrid->GetNumNodes()); + + ASSERT_EQ(20.0, curvilinearGrid->GetNode(0, 0).x); + ASSERT_EQ(0.0, curvilinearGrid->GetNode(0, 0).y); + ASSERT_EQ(20.0, curvilinearGrid->GetNode(1, 0).x); + ASSERT_EQ(10.0, curvilinearGrid->GetNode(1, 0).y); + ASSERT_EQ(-999.0, curvilinearGrid->GetNode(2, 0).x); + ASSERT_EQ(-999.0, curvilinearGrid->GetNode(2, 0).y); + + ASSERT_EQ(10.0, curvilinearGrid->GetNode(0, 1).x); + ASSERT_EQ(0.0, curvilinearGrid->GetNode(0, 1).y); + ASSERT_EQ(10.0, curvilinearGrid->GetNode(1, 1).x); + ASSERT_EQ(10.0, curvilinearGrid->GetNode(1, 1).y); + ASSERT_EQ(10.0, curvilinearGrid->GetNode(2, 1).x); + ASSERT_EQ(20.0, curvilinearGrid->GetNode(2, 1).y); + + ASSERT_EQ(0.0, curvilinearGrid->GetNode(0, 2).x); + ASSERT_EQ(0.0, curvilinearGrid->GetNode(0, 2).y); + ASSERT_EQ(0.0, curvilinearGrid->GetNode(1, 2).x); + ASSERT_EQ(10.0, curvilinearGrid->GetNode(1, 2).y); + ASSERT_EQ(0.0, curvilinearGrid->GetNode(2, 2).x); + ASSERT_EQ(20.0, curvilinearGrid->GetNode(2, 2).y); +} diff --git a/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp b/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp index ddf518974..17c871892 100644 --- a/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp +++ b/libs/MeshKernelApi/include/MeshKernelApi/MeshKernel.hpp @@ -855,6 +855,14 @@ namespace meshkernelapi /// @returns Error code MKERNEL_API int mkernel_mesh2d_convert_projection(int meshKernelId, int projectionType, const char* const zoneString); + /// @brief Converts a mesh to a curvilinear mesh + /// + /// @param[in] meshKernelId The id of the mesh state + /// @param[in] xPointCoordinate The x coordinate of the point where to start the conversion point coordinate + /// @param[in] yPointCoordinate The y point coordinate + /// @returns Error code + MKERNEL_API int mkernel_mesh2d_convert_to_curvilinear(int meshKernelId, double xPointCoordinate, double yPointCoordinate); + /// @brief Count the number of hanging edges in a mesh2d. /// An hanging edge is an edge where one of the two nodes is not connected. /// @param[in] meshKernelId The id of the mesh state diff --git a/libs/MeshKernelApi/src/MeshKernel.cpp b/libs/MeshKernelApi/src/MeshKernel.cpp index 61d942c88..a6a2c1571 100644 --- a/libs/MeshKernelApi/src/MeshKernel.cpp +++ b/libs/MeshKernelApi/src/MeshKernel.cpp @@ -27,6 +27,7 @@ #include "MeshKernel/CurvilinearGrid/CurvilinearGridDeleteExterior.hpp" #include "MeshKernel/Mesh2DIntersections.hpp" +#include "MeshKernel/Mesh2DToCurvilinear.hpp" #include "MeshKernel/SamplesHessianCalculator.hpp" #include "MeshKernel/CurvilinearGrid/CurvilinearGridDeleteInterior.hpp" @@ -770,6 +771,28 @@ namespace meshkernelapi return lastExitCode; } + MKERNEL_API int mkernel_mesh2d_convert_to_curvilinear(int meshKernelId, double xPointCoordinate, double yPointCoordinate) + { + lastExitCode = meshkernel::ExitCode::Success; + try + { + if (!meshKernelState.contains(meshKernelId)) + { + throw meshkernel::MeshKernelError("The selected mesh kernel id does not exist."); + } + + meshkernel::Mesh2DToCurvilinear mesh2DToCurvilinear(*meshKernelState[meshKernelId].m_mesh2d); + + meshKernelState[meshKernelId].m_curvilinearGrid = mesh2DToCurvilinear.Compute({xPointCoordinate, yPointCoordinate}); + meshKernelState[meshKernelId].m_mesh2d = std::make_unique(meshKernelState[meshKernelId].m_projection); + } + catch (...) + { + lastExitCode = HandleException(); + } + return lastExitCode; + } + MKERNEL_API int mkernel_mesh2d_count_hanging_edges(int meshKernelId, int& numHangingEdges) { lastExitCode = meshkernel::ExitCode::Success; diff --git a/libs/MeshKernelApi/tests/src/ApiTest.cpp b/libs/MeshKernelApi/tests/src/ApiTest.cpp index edefd6241..74273d570 100644 --- a/libs/MeshKernelApi/tests/src/ApiTest.cpp +++ b/libs/MeshKernelApi/tests/src/ApiTest.cpp @@ -3248,3 +3248,44 @@ TEST_P(MeshLocationIndexTests, GetLocationIndex_OnAMesh_ShouldGetTheLocationInde ASSERT_EQ(locationIndex, expectedIndex); } INSTANTIATE_TEST_SUITE_P(LocationIndexParametrizedTests, MeshLocationIndexTests, ::testing::ValuesIn(MeshLocationIndexTests::GetData())); + +TEST(Mesh2D, ConvertToCurvilinear_ShouldConvertMeshToCurvilinear) +{ + // create first mesh + auto [num_nodes, num_edges, node_x, node_y, edge_nodes] = MakeRectangularMeshForApiTesting(10, 10, 1.0, meshkernel::Point(0.0, 0.0)); + + meshkernelapi::Mesh2D mesh2d{}; + mesh2d.num_nodes = static_cast(num_nodes); + mesh2d.num_edges = static_cast(num_edges); + mesh2d.node_x = node_x.data(); + mesh2d.node_y = node_y.data(); + mesh2d.edge_nodes = edge_nodes.data(); + + // allocate state + int meshKernelId = 0; + int errorCode = meshkernelapi::mkernel_allocate_state(0, meshKernelId); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + + // first initialise using the first mesh, mesh2d + errorCode = mkernel_mesh2d_set(meshKernelId, mesh2d); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + + // Execute + errorCode = meshkernelapi::mkernel_mesh2d_convert_to_curvilinear(meshKernelId, 5.0, 5.0); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + + meshkernelapi::Mesh2D mesh2dOut{}; + errorCode = mkernel_mesh2d_get_dimensions(meshKernelId, mesh2dOut); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + + meshkernelapi::CurvilinearGrid curvilinearOut{}; + errorCode = mkernel_curvilinear_get_dimensions(meshKernelId, curvilinearOut); + ASSERT_EQ(meshkernel::ExitCode::Success, errorCode); + + // Assert + ASSERT_EQ(0, mesh2dOut.num_nodes); + ASSERT_EQ(0, mesh2dOut.num_edges); + + ASSERT_EQ(11, curvilinearOut.num_m); + ASSERT_EQ(11, curvilinearOut.num_n); +}